diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/coupon/models/coupon_program.py | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/coupon/models/coupon_program.py')
| -rw-r--r-- | addons/coupon/models/coupon_program.py | 159 |
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 + } |
