Files
odoo_source/addons/coupon/models/coupon_program.py
T
Andrea Grazioso (agr-odoo) 8eefb69252 [FIX] sale_coupon: correct discount application values from view
Create a coupon program following these steps:

- Set name
- set 'Discount Apply On' to 'On Specific Product'
- Add specific product
- Set 'Apply Discount' to Fixed price
- Input the fixed price
- Save

Generate a coupon and try to apply it on a quotation.
Error will popup because Not all conditions are met.

The field discount_apply_on (invisible with fixed price) keep the
application "specific_products". Fixing by checking the values in the
backend.

opw-2257783

closes odoo/odoo#51574

Signed-off-by: Nicolas Martinelli (nim) <nim@odoo.com>
2020-05-25 07:44:35 +00:00

155 lines
7.6 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", 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):
discount_line_product_id = self.env['product.product'].create({
'name': program.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
})
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:
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
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) + [('id', 'in', products.ids)]
return self.env['product.product'].search(domain)
return products