diff --git a/addons/sale_coupon/models/mail_compose_message.py b/addons/sale_coupon/models/mail_compose_message.py
new file mode 100644
index 00000000000..f9e1a8ffbc0
--- /dev/null
+++ b/addons/sale_coupon/models/mail_compose_message.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models
+
+
+class MailComposeMessage(models.TransientModel):
+ _inherit = 'mail.compose.message'
+
+ def send_mail(self, **kwargs):
+ for wizard in self:
+ if self._context.get('mark_coupon_as_sent') and wizard.model == 'sale.coupon' and wizard.partner_ids:
+ self.env[wizard.model].browse(wizard.res_id).state = 'sent'
+ return super().send_mail(**kwargs)
diff --git a/addons/sale_coupon/models/sale_coupon.py b/addons/sale_coupon/models/sale_coupon.py
index 934c1367e2f..4a0ae195bfc 100644
--- a/addons/sale_coupon/models/sale_coupon.py
+++ b/addons/sale_coupon/models/sale_coupon.py
@@ -53,11 +53,14 @@ class SaleCoupon(models.Model):
def _check_coupon_code(self, order):
message = {}
applicable_programs = order._get_applicable_programs()
- if self.state in ('used', 'expired') or \
- (self.expiration_date and self.expiration_date < order.date_order.date()):
- message = {'error': _('This coupon %s has been used or is expired.') % (self.code)}
+ if self.state == 'used':
+ message = {'error': _('This coupon has already been used (%s).') % (self.code)}
elif self.state == 'reserved':
message = {'error': _('This coupon %s exists but the origin sales order is not validated yet.') % (self.code)}
+ elif self.state == 'cancel':
+ message = {'error': _('This coupon has been cancelled (%s).') % (self.code)}
+ elif self.state == 'expired' or (self.expiration_date and self.expiration_date < order.date_order.date()):
+ message = {'error': _('This coupon is expired (%s).') % (self.code)}
# Minimum requirement should not be checked if the coupon got generated by a promotion program (the requirement should have only be checked to generate the coupon)
elif self.program_id.program_type == 'coupon_program' and not self.program_id._filter_on_mimimum_amount(order):
message = {'error': _('A minimum of %s %s should be purchased to get the reward') % (self.program_id.rule_minimum_amount, self.program_id.currency_id.name)}
diff --git a/addons/sale_coupon/models/sale_coupon_program.py b/addons/sale_coupon/models/sale_coupon_program.py
index 82fe37f5346..d7db3360316 100644
--- a/addons/sale_coupon/models/sale_coupon_program.py
+++ b/addons/sale_coupon/models/sale_coupon_program.py
@@ -43,14 +43,14 @@ class SaleCouponProgram(models.Model):
help="A promotion code is a code that is associated with a marketing discount. For example, a retailer might tell frequent customers to enter the promotion code 'THX001' to receive a 10%% discount on their whole order.")
promo_applicability = fields.Selection([
('on_current_order', 'Apply On Current Order'),
- ('on_next_order', 'Apply On Next Order')],
+ ('on_next_order', 'Send a Coupon')],
default='on_current_order', string="Applicability")
coupon_ids = fields.One2many('sale.coupon', 'program_id', string="Generated Coupons", copy=False)
coupon_count = fields.Integer(compute='_compute_coupon_count')
order_count = fields.Integer(compute='_compute_order_count')
company_id = fields.Many2one('res.company', string="Company", default=lambda self: self.env.company)
currency_id = fields.Many2one(string="Currency", related='company_id.currency_id', readonly=True)
- validity_duration = fields.Integer(default=1,
+ validity_duration = fields.Integer(default=30,
help="Validity duration for a coupon after its generation")
@api.constrains('promo_code')
diff --git a/addons/sale_coupon/report/sale_coupon_report_templates.xml b/addons/sale_coupon/report/sale_coupon_report_templates.xml
index aa1751f90ed..ad1076a6dc7 100644
--- a/addons/sale_coupon/report/sale_coupon_report_templates.xml
+++ b/addons/sale_coupon/report/sale_coupon_report_templates.xml
@@ -66,10 +66,12 @@