summaryrefslogtreecommitdiff
path: root/addons/coupon/models/coupon_program.py
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/coupon/models/coupon_program.py
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/coupon/models/coupon_program.py')
-rw-r--r--addons/coupon/models/coupon_program.py159
1 files changed, 159 insertions, 0 deletions
diff --git a/addons/coupon/models/coupon_program.py b/addons/coupon/models/coupon_program.py
new file mode 100644
index 00000000..afcb3da6
--- /dev/null
+++ b/addons/coupon/models/coupon_program.py
@@ -0,0 +1,159 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models, _
+from odoo.exceptions import UserError, ValidationError
+
+import ast
+
+
+class CouponProgram(models.Model):
+ _name = 'coupon.program'
+ _description = "Coupon Program"
+ _inherits = {'coupon.rule': 'rule_id', 'coupon.reward': 'reward_id'}
+ # We should apply 'discount' promotion first to avoid offering free product when we should not.
+ # Eg: If the discount lower the SO total below the required threshold
+ # Note: This is only revelant when programs have the same sequence (which they have by default)
+ _order = "sequence, reward_type"
+
+ name = fields.Char(required=True, translate=True)
+ active = fields.Boolean('Active', default=True, help="A program is available for the customers when active")
+ rule_id = fields.Many2one('coupon.rule', string="Coupon Rule", ondelete='restrict', required=True)
+ reward_id = fields.Many2one('coupon.reward', string="Reward", ondelete='restrict', required=True, copy=False)
+ sequence = fields.Integer(copy=False,
+ help="Coupon program will be applied based on given sequence if multiple programs are " +
+ "defined on same condition(For minimum amount)")
+ maximum_use_number = fields.Integer(help="Maximum number of sales orders in which reward can be provided")
+ program_type = fields.Selection([
+ ('promotion_program', 'Promotional Program'),
+ ('coupon_program', 'Coupon Program'),
+ ],
+ help="""A promotional program can be either a limited promotional offer without code (applied automatically)
+ or with a code (displayed on a magazine for example) that may generate a discount on the current
+ order or create a coupon for a next order.
+
+ A coupon program generates coupons with a code that can be used to generate a discount on the current
+ order or create a coupon for a next order.""")
+ promo_code_usage = fields.Selection([
+ ('no_code_needed', 'Automatically Applied'),
+ ('code_needed', 'Use a code')],
+ help="Automatically Applied - No code is required, if the program rules are met, the reward is applied (Except the global discount or the free shipping rewards which are not cumulative)\n" +
+ "Use a code - If the program rules are met, a valid code is mandatory for the reward to be applied\n")
+ promo_code = fields.Char('Promotion Code', copy=False,
+ 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', 'Send a Coupon')],
+ default='on_current_order', string="Applicability")
+ coupon_ids = fields.One2many('coupon.coupon', 'program_id', string="Generated Coupons", copy=False)
+ coupon_count = fields.Integer(compute='_compute_coupon_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=30,
+ help="Validity duration for a coupon after its generation")
+
+ @api.constrains('promo_code')
+ def _check_promo_code_constraint(self):
+ """ Program code must be unique """
+ for program in self.filtered(lambda p: p.promo_code):
+ domain = [('id', '!=', program.id), ('promo_code', '=', program.promo_code)]
+ if self.search(domain):
+ raise ValidationError(_('The program code must be unique!'))
+
+ @api.depends('coupon_ids')
+ def _compute_coupon_count(self):
+ coupon_data = self.env['coupon.coupon'].read_group([('program_id', 'in', self.ids)], ['program_id'], ['program_id'])
+ mapped_data = dict([(m['program_id'][0], m['program_id_count']) for m in coupon_data])
+ for program in self:
+ program.coupon_count = mapped_data.get(program.id, 0)
+
+ @api.onchange('promo_code_usage')
+ def _onchange_promo_code_usage(self):
+ if self.promo_code_usage == 'no_code_needed':
+ self.promo_code = False
+
+ @api.onchange('reward_product_id')
+ def _onchange_reward_product_id(self):
+ if self.reward_product_id:
+ self.reward_product_uom_id = self.reward_product_id.uom_id
+
+ @api.onchange('discount_type')
+ def _onchange_discount_type(self):
+ if self.discount_type == 'fixed_amount':
+ self.discount_apply_on = 'on_order'
+
+ @api.model
+ def create(self, vals):
+ program = super(CouponProgram, self).create(vals)
+ if not vals.get('discount_line_product_id', False):
+ values = program._get_discount_product_values()
+ discount_line_product_id = self.env['product.product'].create(values)
+ program.write({'discount_line_product_id': discount_line_product_id.id})
+ return program
+
+ def write(self, vals):
+ res = super(CouponProgram, self).write(vals)
+ reward_fields = [
+ 'reward_type', 'reward_product_id', 'discount_type', 'discount_percentage',
+ 'discount_apply_on', 'discount_specific_product_ids', 'discount_fixed_amount'
+ ]
+ if any(field in reward_fields for field in vals):
+ self.mapped('discount_line_product_id').write({'name': self[0].reward_id.display_name})
+ return res
+
+ def unlink(self):
+ if self.filtered('active'):
+ raise UserError(_('You can not delete a program in active state'))
+ # get reference to rule and reward
+ rule = self.rule_id
+ reward = self.reward_id
+ # unlink the program
+ super(CouponProgram, self).unlink()
+ # then unlink the rule and reward
+ rule.unlink()
+ reward.unlink()
+ return True
+
+ def toggle_active(self):
+ super(CouponProgram, self).toggle_active()
+ for program in self:
+ program.discount_line_product_id.active = program.active
+ coupons = self.filtered(lambda p: not p.active and p.promo_code_usage == 'code_needed').mapped('coupon_ids')
+ coupons.filtered(lambda x: x.state != 'used').write({'state': 'expired'})
+
+ def _compute_program_amount(self, field, currency_to):
+ self.ensure_one()
+ return self.currency_id._convert(self[field], currency_to, self.company_id, fields.Date.today())
+
+ def _is_valid_partner(self, partner):
+ if self.rule_partners_domain and self.rule_partners_domain != '[]':
+ domain = ast.literal_eval(self.rule_partners_domain) + [('id', '=', partner.id)]
+ return bool(self.env['res.partner'].search_count(domain))
+ else:
+ return True
+
+ def _is_valid_product(self, product):
+ # NOTE: if you override this method, think of also overriding _get_valid_products
+ # we also encourage the use of _get_valid_products as its execution is faster
+ if self.rule_products_domain:
+ domain = ast.literal_eval(self.rule_products_domain) + [('id', '=', product.id)]
+ return bool(self.env['product.product'].search_count(domain))
+ else:
+ return True
+
+ def _get_valid_products(self, products):
+ if self.rule_products_domain:
+ domain = ast.literal_eval(self.rule_products_domain)
+ return products.filtered_domain(domain)
+ return products
+
+ def _get_discount_product_values(self):
+ return {
+ 'name': self.reward_id.display_name,
+ 'type': 'service',
+ 'taxes_id': False,
+ 'supplier_taxes_id': False,
+ 'sale_ok': False,
+ 'purchase_ok': False,
+ 'lst_price': 0, #Do not set a high value to avoid issue with coupon code
+ }