The company is used to compute currency of: - discount_fixed_amount - discount_max_amount - rule_minimum_amount If currently we don't set a company, we have an error when using the company. opw-2458950 note: forward-port in master set the company field as required setting it as required in the view. closes odoo/odoo#66231 X-original-commit: d260dddcdeddecd2bc0649d948f5e0cc30607e09 Signed-off-by: Nicolas Lempereur (nle) <nle@odoo.com>
169 lines
8.1 KiB
Python
169 lines
8.1 KiB
Python
# -*- 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", required=True, 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")
|
|
total_order_count = fields.Integer("Total Order Count", compute="_compute_total_order_count")
|
|
|
|
@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
|
|
|
|
@api.ondelete(at_uninstall=False)
|
|
def _unlink_except_active(self):
|
|
if self.filtered('active'):
|
|
raise UserError(_('You can not delete a program in active state'))
|
|
|
|
def unlink(self):
|
|
# 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 _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 _generate_coupons(self, partner_id):
|
|
'''Generate coupons that can be used in the next order for the given partner_id.'''
|
|
generated_coupons = self.env['coupon.coupon']
|
|
for program in self:
|
|
generated_coupons |= self.env['coupon.coupon'].create({
|
|
'program_id': program.id,
|
|
'partner_id': partner_id,
|
|
})
|
|
return generated_coupons
|
|
|
|
def _compute_total_order_count(self):
|
|
for program in self:
|
|
program.total_order_count = 0
|
|
|
|
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
|
|
}
|