445 lines
19 KiB
Python
445 lines
19 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
# Part of flectra. See LICENSE file for full copyright and licensing details.
|
||
|
|
||
|
from flectra import api, fields, models, _
|
||
|
from flectra.exceptions import Warning
|
||
|
from flectra.tools.safe_eval import safe_eval
|
||
|
from flectra.tools.misc import formatLang
|
||
|
|
||
|
MODELS_LIST = [
|
||
|
'res.partner',
|
||
|
'res.partner.category',
|
||
|
'res.users',
|
||
|
'res.groups',
|
||
|
'res.country.state',
|
||
|
'res.country',
|
||
|
]
|
||
|
|
||
|
Cart_Option = [
|
||
|
('subtotal_at_least', 'Subtotal At Least'),
|
||
|
('subtotal_less_than', 'Subtotal less than'),
|
||
|
('item_count_atleast', 'Lines Count at least'),
|
||
|
('item_count_less_than', 'Lines less than'),
|
||
|
('item_sum_qty_atleast', 'Sum of Item Qty at least'),
|
||
|
('item_sum_qty_less_than', 'Sum of Item Qty less than'),
|
||
|
('one_product_al_least', 'At least one product in order'),
|
||
|
('none_of_sel_products', 'None of selected Products'),
|
||
|
('one_categ_al_least', 'At least one category in order'),
|
||
|
('none_of_sel_categs', 'None of selected Categories'),
|
||
|
]
|
||
|
|
||
|
|
||
|
class RulesLine(models.Model):
|
||
|
_name = 'rule.line'
|
||
|
_order = 'sequence'
|
||
|
|
||
|
sequence = fields.Integer('Sequence')
|
||
|
start_date = fields.Date('Start Date')
|
||
|
end_date = fields.Date('End Date')
|
||
|
min_qty = fields.Float('Min. Quantity')
|
||
|
max_qty = fields.Float('Max. Quantity')
|
||
|
rule_type = fields.Selection([
|
||
|
('percent', 'Percent'),
|
||
|
('fixed_amount', 'Fixed Amount'),
|
||
|
], 'Rule Type', required=True, default='percent')
|
||
|
discount_amount = fields.Float('Discount Amount')
|
||
|
price_rule_id = fields.Many2one('price.rule', 'Price Rule')
|
||
|
model_id = fields.Many2one(
|
||
|
'ir.model', string='Condition', domain=[('model', 'in', MODELS_LIST)])
|
||
|
model_name = fields.Char(related='model_id.model', string='Model Name')
|
||
|
model_domain = fields.Char(string='Domain', oldname='domain', default=[])
|
||
|
model_real = fields.Char(compute='_compute_model', string='Real Model')
|
||
|
pricelist_id = fields.Many2one(
|
||
|
'product.pricelist', related='price_rule_id.pricelist_id', store=True)
|
||
|
categ_id = fields.Many2one(
|
||
|
'product.category', related='price_rule_id.categ_id', store=True)
|
||
|
product_tmpl_id = fields.Many2one(
|
||
|
'product.template',
|
||
|
related='price_rule_id.product_tmpl_id', store=True)
|
||
|
product_id = fields.Many2one(
|
||
|
'product.product', related='price_rule_id.product_id', store=True)
|
||
|
rule_id_start_date = fields.Date(
|
||
|
related='price_rule_id.start_date', store=True)
|
||
|
rule_id_end_date = fields.Date(
|
||
|
related='price_rule_id.end_date', store=True)
|
||
|
|
||
|
@api.onchange('rule_type', 'discount_amount')
|
||
|
def check_percentage(self):
|
||
|
warning = {}
|
||
|
if self.rule_type == 'percent' and (
|
||
|
self.discount_amount > 100 or
|
||
|
self.discount_amount < 0):
|
||
|
warning.update({
|
||
|
'title': _("Warning"),
|
||
|
'message': _("Percentage should be between 0% to 100%!")})
|
||
|
self.discount_amount = 0.0
|
||
|
return {'warning': warning}
|
||
|
|
||
|
@api.depends('model_id')
|
||
|
def _compute_model(self):
|
||
|
for record in self:
|
||
|
if record.model_id:
|
||
|
record.model_real = record.model_name or 'res.partner'
|
||
|
|
||
|
@api.constrains('start_date', 'end_date',
|
||
|
'rule_id_start_date', 'rule_id_end_date')
|
||
|
def check_date(self):
|
||
|
for rule_line_id in self:
|
||
|
parent_start_date = rule_line_id.rule_id_start_date
|
||
|
child_start_date = rule_line_id.start_date
|
||
|
if parent_start_date and \
|
||
|
child_start_date and parent_start_date > child_start_date:
|
||
|
raise Warning(_("Start Date date not valid in "
|
||
|
"Product Rule Lines!"))
|
||
|
parent_end_date = rule_line_id.rule_id_end_date
|
||
|
child_end_date = rule_line_id.end_date
|
||
|
if parent_end_date and \
|
||
|
child_end_date and parent_end_date < child_end_date:
|
||
|
raise Warning(_("End Date date not valid in "
|
||
|
"Product Rule Lines!"))
|
||
|
|
||
|
|
||
|
class PriceRules(models.Model):
|
||
|
_name = 'price.rule'
|
||
|
_description = 'Price Rules'
|
||
|
_order = 'sequence'
|
||
|
|
||
|
@api.multi
|
||
|
@api.depends('apply_on', 'categ_id', 'product_tmpl_id', 'product_id')
|
||
|
def _get_pricerule_name_price(self):
|
||
|
for record in self:
|
||
|
if record.categ_id:
|
||
|
record.name = _("Category: %s") % (record.categ_id.name)
|
||
|
elif record.product_tmpl_id:
|
||
|
record.name = record.product_tmpl_id.name
|
||
|
elif record.product_id:
|
||
|
record.name = record.product_id.display_name.replace(
|
||
|
'[%s]' % record.product_id.code, '')
|
||
|
else:
|
||
|
record.name = _("All Products")
|
||
|
|
||
|
name = fields.Char('Name', compute='_get_pricerule_name_price')
|
||
|
sequence = fields.Integer('Sequence')
|
||
|
apply_on = fields.Selection([
|
||
|
('all', 'Global'),
|
||
|
('category', 'Category'),
|
||
|
('product_template', 'Product Template'),
|
||
|
('product', 'Product Variant')
|
||
|
], required=True, default='all', string="Apply On")
|
||
|
categ_id = fields.Many2one('product.category', 'Category')
|
||
|
product_tmpl_id = fields.Many2one('product.template', 'Product Template')
|
||
|
product_id = fields.Many2one('product.product', 'Product')
|
||
|
active = fields.Boolean('Active', default=True)
|
||
|
start_date = fields.Date('Start Date')
|
||
|
end_date = fields.Date('End Date')
|
||
|
note = fields.Text('Description')
|
||
|
pricelist_id = fields.Many2one('product.pricelist',
|
||
|
'Pricelist', index=True, ondelete='cascade')
|
||
|
rule_lines = fields.One2many('rule.line', 'price_rule_id',
|
||
|
'Product Rule Lines')
|
||
|
|
||
|
@api.onchange('apply_on')
|
||
|
def _onchange_apply_on(self):
|
||
|
if self.apply_on != 'product':
|
||
|
self.product_id = False
|
||
|
if self.apply_on != 'product_template':
|
||
|
self.product_tmpl_id = False
|
||
|
if self.apply_on != 'category':
|
||
|
self.categ_id = False
|
||
|
|
||
|
@api.multi
|
||
|
def get_rules(self, pricelist_id, date):
|
||
|
date = fields.Date.context_today(self)
|
||
|
self._cr.execute(
|
||
|
'SELECT rule.id '
|
||
|
'FROM price_rule AS rule '
|
||
|
'WHERE (rule.pricelist_id = %s) '
|
||
|
'AND (rule.start_date IS NULL OR rule.start_date<=%s) '
|
||
|
'AND (rule.end_date IS NULL OR rule.end_date>=%s)'
|
||
|
'ORDER BY rule.sequence',
|
||
|
(pricelist_id.id, date, date))
|
||
|
rules_ids = [x[0] for x in self._cr.fetchall()]
|
||
|
rules = self.browse(rules_ids)
|
||
|
return rules
|
||
|
|
||
|
|
||
|
class CartRules(models.Model):
|
||
|
_name = 'cart.rule'
|
||
|
_description = 'Cart Rules'
|
||
|
_order = 'sequence'
|
||
|
|
||
|
@api.multi
|
||
|
@api.depends('apply_on', 'amt_value', 'product_id', 'categ_id')
|
||
|
def _get_cart_name_price(self):
|
||
|
for record in self:
|
||
|
select_option = dict(Cart_Option)
|
||
|
if record.apply_on in ['subtotal_at_least', 'subtotal_less_than',
|
||
|
'item_count_atleast',
|
||
|
'item_count_less_than',
|
||
|
'item_sum_qty_atleast',
|
||
|
'item_sum_qty_less_than']:
|
||
|
record.name = select_option[record.apply_on] + ' : ' + str(
|
||
|
formatLang(self.env, record.amt_value, digits=2))
|
||
|
elif record.apply_on == 'one_product_al_least' and \
|
||
|
record.product_id:
|
||
|
record.name = select_option[record.apply_on] + ' : ' + str(
|
||
|
record.product_id.name)
|
||
|
elif record.apply_on == 'one_categ_al_least' and \
|
||
|
record.categ_id:
|
||
|
record.name = select_option[record.apply_on] + ' : ' + str(
|
||
|
record.categ_id.name)
|
||
|
elif record.apply_on:
|
||
|
record.name = select_option[record.apply_on]
|
||
|
|
||
|
name = fields.Char('Name', compute='_get_cart_name_price')
|
||
|
sequence = fields.Integer('Sequence')
|
||
|
active = fields.Boolean('Active', default=True)
|
||
|
start_date = fields.Date('Start Date')
|
||
|
end_date = fields.Date('End Date')
|
||
|
discount_percentage = fields.Float('Discount (%)')
|
||
|
apply_on = fields.Selection(Cart_Option, 'Apply On')
|
||
|
amt_value = fields.Float('Amount')
|
||
|
product_id = fields.Many2one('product.product', 'Product')
|
||
|
product_ids = fields.Many2many('product.product', column1='cart_line_id',
|
||
|
column2='product_id', string='Products')
|
||
|
categ_id = fields.Many2one('product.category', 'Category')
|
||
|
categ_ids = fields.Many2many('product.category', column1='cart_line_id',
|
||
|
column2='category_id', string='Categories')
|
||
|
note = fields.Text('Description')
|
||
|
pricelist_id = fields.Many2one('product.pricelist')
|
||
|
|
||
|
@api.onchange('discount_percentage')
|
||
|
def check_percentage(self):
|
||
|
warning = {}
|
||
|
if self.discount_percentage > 100 or self.discount_percentage < 0:
|
||
|
warning.update({
|
||
|
'title': _("Warning"),
|
||
|
'message': _("Percentage should be between 0% to 100%!")})
|
||
|
self.discount_percentage = 0.0
|
||
|
return {'warning': warning}
|
||
|
|
||
|
def _get_cart_discount_amt(self, pricelist, total=0.0,
|
||
|
item_count=0.0, item_sum_count=0.0,
|
||
|
product_ids=[], categ_ids=[], order=False):
|
||
|
discount_flag = False
|
||
|
dis_price = 0.0
|
||
|
if self.apply_on == 'subtotal_at_least' \
|
||
|
and total >= self.amt_value:
|
||
|
discount_flag = True
|
||
|
elif self.apply_on == 'subtotal_less_than' and total <= self.amt_value:
|
||
|
discount_flag = True
|
||
|
elif self.apply_on == 'item_count_atleast' \
|
||
|
and item_count >= self.amt_value:
|
||
|
discount_flag = True
|
||
|
elif self.apply_on == 'item_count_less_than' \
|
||
|
and item_count <= self.amt_value:
|
||
|
discount_flag = True
|
||
|
elif self.apply_on == 'item_sum_qty_atleast' \
|
||
|
and item_sum_count >= self.amt_value:
|
||
|
discount_flag = True
|
||
|
elif self.apply_on == 'item_sum_qty_less_than' \
|
||
|
and item_sum_count <= self.amt_value:
|
||
|
discount_flag = True
|
||
|
elif self.apply_on == 'one_product_al_least' \
|
||
|
and self.product_id.id in product_ids:
|
||
|
discount_flag = True
|
||
|
elif self.apply_on == 'none_of_sel_products' \
|
||
|
and not any(map(lambda v: v in [
|
||
|
x.id for x in self.product_ids],
|
||
|
product_ids)):
|
||
|
discount_flag = True
|
||
|
elif self.apply_on == 'one_categ_al_least' \
|
||
|
and self.categ_id.id in categ_ids:
|
||
|
discount_flag = True
|
||
|
elif self.apply_on == 'none_of_sel_categs' \
|
||
|
and not any(map(lambda v: v in [
|
||
|
x.id for x in self.categ_ids], categ_ids)):
|
||
|
discount_flag = True
|
||
|
if discount_flag:
|
||
|
dis_price = self.discount_percentage
|
||
|
return dis_price
|
||
|
|
||
|
|
||
|
class CouponCode(models.Model):
|
||
|
_name = 'coupon.code'
|
||
|
_description = 'Coupon Code'
|
||
|
|
||
|
@api.multi
|
||
|
def _compute_order_count(self):
|
||
|
sale_order_ids = self.env['sale.order'].search_count([
|
||
|
('coupon_code_id', '=', self.id), ('state', '=', 'sale')])
|
||
|
self.sale_order_count = sale_order_ids
|
||
|
self.remaining_limit = self.usage_limit - sale_order_ids
|
||
|
|
||
|
name = fields.Char('Name')
|
||
|
coupon_code = fields.Char('Coupon Code')
|
||
|
code_valid_from = fields.Date('Valid From')
|
||
|
code_valid_to = fields.Date('Valid To')
|
||
|
sale_order_count = fields.Integer(
|
||
|
compute='_compute_order_count', string='# of Sale Order')
|
||
|
coupon_type = fields.Selection([
|
||
|
('percent', 'Percent'),
|
||
|
('fixed_amount', 'Fixed Amount'),
|
||
|
('buy_x_get_y', 'Buy X Product Get Y Product Free'),
|
||
|
('buy_x_get_y_other', 'Buy X Product Get Y Other Product Free'),
|
||
|
('buy_x_get_percent', 'Range Based Discount('
|
||
|
'Buy X Product Get Percent Free)'),
|
||
|
('clubbed', 'Clubbed Discount'),
|
||
|
], 'Coupon Type', default='percent', required=True)
|
||
|
number_of_x_product = fields.Float('Number Of X Product')
|
||
|
number_of_y_product = fields.Float('Number Of Y Product')
|
||
|
other_categ_id = fields.Many2one('product.category', 'Category')
|
||
|
discount_amount = fields.Float('Discount Amount')
|
||
|
flat_discount = fields.Float('Flat Discount')
|
||
|
extra_discount_percentage = fields.Float('Extra Discount')
|
||
|
usage_limit = fields.Integer('Total Usage Limit')
|
||
|
remaining_limit = fields.Integer(compute='_compute_order_count',
|
||
|
string='Remaining Usage Limit')
|
||
|
min_order_amount = fields.Float('Min Order Amount')
|
||
|
active = fields.Boolean('Active', default=True)
|
||
|
apply_on = fields.Selection([
|
||
|
('all', 'Global'),
|
||
|
('category', 'Category'),
|
||
|
('product_template', 'Product Template'),
|
||
|
('product', 'Product Variant'),
|
||
|
], required=True, default='all', string="Apply On")
|
||
|
categ_id = fields.Many2one('product.category', 'Category')
|
||
|
product_tmpl_id = fields.Many2one('product.template', 'Product Template')
|
||
|
product_id = fields.Many2one('product.product', 'Product')
|
||
|
other_product_id = fields.Many2one('product.product', 'Other Product')
|
||
|
pricelist_id = fields.Many2one('product.pricelist',
|
||
|
'Pricelist', index=True, ondelete='cascade')
|
||
|
model_id = fields.Many2one('ir.model', string='Condition',
|
||
|
domain=[('model', 'in', MODELS_LIST)])
|
||
|
model_name = fields.Char(related='model_id.model', string='Model Name')
|
||
|
model_domain = fields.Char(string='Domain', oldname='domain', default=[])
|
||
|
model_real = fields.Char(compute='_compute_model', string='Real Model')
|
||
|
|
||
|
@api.onchange('apply_on')
|
||
|
def _onchange_apply_on(self):
|
||
|
if self.apply_on != 'product':
|
||
|
self.product_id = False
|
||
|
if self.apply_on != 'product_template':
|
||
|
self.product_tmpl_id = False
|
||
|
if self.apply_on != 'category':
|
||
|
self.categ_id = False
|
||
|
|
||
|
@api.onchange('coupon_type', 'discount_amount')
|
||
|
def check_percentage(self):
|
||
|
warning = {}
|
||
|
if self.coupon_type == 'percent' and self.discount_amount > 100 \
|
||
|
or self.discount_amount < 0:
|
||
|
warning.update({
|
||
|
'title': _("Warning"),
|
||
|
'message': _("Percentage should be between 0% to 100%!")})
|
||
|
self.discount_amount = 0.0
|
||
|
return {'warning': warning}
|
||
|
|
||
|
@api.onchange('flat_discount', 'extra_discount_percentage')
|
||
|
def check_clubbed_percentage(self):
|
||
|
warning = {}
|
||
|
percent = self.flat_discount + self.extra_discount_percentage
|
||
|
if percent > 100 or percent < 0:
|
||
|
warning.update({
|
||
|
'title': _("Warning"),
|
||
|
'message': _("Total Percentage ( Discount + Extra ) "
|
||
|
"should be between 0% to 100%!")})
|
||
|
self.flat_discount = 0.0
|
||
|
self.extra_discount_percentage = 0.0
|
||
|
return {'warning': warning}
|
||
|
|
||
|
@api.depends('model_id')
|
||
|
def _compute_model(self):
|
||
|
for record in self:
|
||
|
if record.model_id:
|
||
|
record.model_real = record.model_name or 'res.partner'
|
||
|
|
||
|
@api.multi
|
||
|
def view_sale_order(self):
|
||
|
sale_order_ids = self.env['sale.order'].search([
|
||
|
('coupon_code_id', '=', self.id), ('state', '=', 'sale')])
|
||
|
return {
|
||
|
'name': 'Sales Orders',
|
||
|
'type': 'ir.actions.act_window',
|
||
|
'view_type': 'form',
|
||
|
'view_mode': 'tree,form,kanban,pivot,graph',
|
||
|
'res_model': 'sale.order',
|
||
|
'domain': [('id', 'in', sale_order_ids.ids)],
|
||
|
}
|
||
|
|
||
|
@api.constrains('coupon_code')
|
||
|
def check_duplicate_coupon_code(self):
|
||
|
check_coupon_id = self.search([
|
||
|
('coupon_code', '=', self.coupon_code),
|
||
|
('id', '!=', self.id)])
|
||
|
if check_coupon_id:
|
||
|
raise Warning(_("Coupon code (%s) already exists!") % (
|
||
|
self.coupon_code))
|
||
|
|
||
|
@api.multi
|
||
|
def check_condition(self, record, partner_id):
|
||
|
domain = safe_eval(record.model_domain)
|
||
|
if record.model_real == 'res.partner':
|
||
|
domain += [('id', '=', partner_id.id)]
|
||
|
elif record.model_real == 'res.partner.category':
|
||
|
domain += [('id', 'in', partner_id.category_id.ids)]
|
||
|
elif record.model_real == 'res.users':
|
||
|
domain += [('id', 'in', self.env.user.id)]
|
||
|
elif record.model_real == 'res.groups':
|
||
|
domain += [('users', 'in', self.env.user.id)]
|
||
|
elif record.model_real == 'res.country.state':
|
||
|
domain += [('id', '=', partner_id.state_id.id)]
|
||
|
elif record.model_real == 'res.country':
|
||
|
domain += [('id', '=', partner_id.country_id.id)]
|
||
|
if self.env[record.model_real].search(domain):
|
||
|
return False
|
||
|
return True
|
||
|
|
||
|
@api.multi
|
||
|
def get_coupon_records(self, coupon_code, pricelist_id):
|
||
|
coupon_ids = []
|
||
|
if coupon_code:
|
||
|
date = fields.Date.context_today(self)
|
||
|
self._cr.execute(
|
||
|
'SELECT code.id '
|
||
|
'FROM coupon_code AS code '
|
||
|
'WHERE (code.coupon_code = %s) '
|
||
|
'AND (code.code_valid_from IS NULL OR '
|
||
|
'code.code_valid_from<=%s) '
|
||
|
'AND (code.code_valid_to IS NULL OR code.code_valid_to>=%s) '
|
||
|
'AND (code.pricelist_id = %s) ',
|
||
|
(coupon_code, date, date, pricelist_id.id))
|
||
|
coupons = [x[0] for x in self._cr.fetchall()]
|
||
|
coupon_ids = self.env['coupon.code'].browse(coupons)
|
||
|
return coupon_ids
|
||
|
|
||
|
@api.multi
|
||
|
def get_coupon_discount(self, line, cal_coupon):
|
||
|
if not line.order_id.pricelist_id.apply_coupon_code or not \
|
||
|
line.coupon_code_id:
|
||
|
return 0.0
|
||
|
coupon_amount = 0.0
|
||
|
onchange_context = True
|
||
|
coupon_ids = self.get_coupon_records(line.order_id.have_coupon_code,
|
||
|
line.order_id.pricelist_id)
|
||
|
for coupon_id in coupon_ids:
|
||
|
if coupon_id.coupon_type == 'percent' or \
|
||
|
coupon_id.coupon_type == 'clubbed':
|
||
|
discount_per = coupon_id.discount_amount
|
||
|
if coupon_id.coupon_type == 'clubbed':
|
||
|
discount_per = \
|
||
|
coupon_id.flat_discount + \
|
||
|
coupon_id.extra_discount_percentage
|
||
|
coupon_amount += line.order_id._get_percentage_coupon_discount(
|
||
|
line, coupon_id, onchange_context,
|
||
|
cal_coupon, discount_per, False)
|
||
|
elif coupon_id.coupon_type == 'fixed_amount':
|
||
|
coupon_amount += line.order_id._get_fixed_coupon_discount(
|
||
|
line, coupon_id, onchange_context, cal_coupon, False)
|
||
|
elif coupon_id.coupon_type == 'buy_x_get_percent' \
|
||
|
and line.product_uom_qty >= coupon_id.number_of_x_product:
|
||
|
coupon_amount += \
|
||
|
line.order_id.buy_x_get_percentage_coupon_discount(
|
||
|
line, coupon_id, onchange_context, cal_coupon, False)
|
||
|
return coupon_amount
|