flectra/addons/sale_advance_pricelist/models/sale.py

643 lines
29 KiB
Python

# -*- coding: utf-8 -*-
# Part of flectra. See LICENSE file for full copyright and licensing details.
import json
import flectra.addons.decimal_precision as dp
from flectra import api, fields, models, _
from flectra.exceptions import Warning, UserError
from flectra.tools.misc import formatLang
class SaleOrder(models.Model):
_inherit = "sale.order"
@api.depends('amount_total', 'currency_id')
def _get_amount_word(self):
for order in self:
if not order.currency_id:
return
order.amount_words = order.currency_id.amount_to_text(
order.discount)
@api.depends('order_line', 'partner_id', 'coupon_flag')
def _check_cart_rules(self):
for order in self:
if order.pricelist_id.pricelist_type == 'advance':
order._update_all()
if order.pricelist_id.discount_policy == 'without_discount':
cart_discount = 0.0
cart_discount_per = \
order.get_cart_rules_discount(order.get_values())
for line in order.order_line:
if line.get_line_percentage() < 100:
cart_discount += \
((line.price_unit * line.product_uom_qty
) * cart_discount_per) / 100
order.cart_discount = cart_discount
else:
for line in order.order_line:
line.set_line_amount()
@api.multi
def get_cart_discount(self, cart_rule_id, values):
cart_discount = cart_rule_id._get_cart_discount_amt(
self.pricelist_id, total=values.get('amount_untaxed'),
item_count=values.get('item_count'),
item_sum_count=values.get('item_sum_count'),
product_ids=values.get('product_ids'),
categ_ids=values.get('categ_ids'), order=self)
return cart_discount
@api.multi
def get_cart_rules_discount(self, values):
if not self.pricelist_id:
return 0.0
date = fields.Date.context_today(self)
self._cr.execute(
'SELECT rule.id '
'FROM cart_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',
(self.pricelist_id.id, date, date))
item_ids = [x[0] for x in self._cr.fetchall()]
cart_rule_ids = self.env['cart.rule'].browse(item_ids)
if not cart_rule_ids:
return 0.0
final_dis_price = 0.0
one_dis_price = all_dis_price = 0.0
max_dis_price = []
min_dis_price = []
cart_discount = 0.0
for cart_rule_id in cart_rule_ids:
cart_discount = self.get_cart_discount(cart_rule_id, values)
if cart_rule_id.pricelist_id.apply_method == \
'first_matched_rule' and \
cart_discount > 0.0:
one_dis_price = cart_discount
break
elif cart_rule_id.pricelist_id.apply_method == 'all_matched_rules':
all_dis_price += cart_discount
elif cart_rule_id.pricelist_id.apply_method == \
'smallest_discount' and cart_discount:
min_dis_price.append(cart_discount)
else:
max_dis_price.append(cart_discount)
if one_dis_price > 0.0:
final_dis_price = one_dis_price
elif all_dis_price > 0.0:
final_dis_price = all_dis_price
elif min_dis_price:
final_dis_price = min(min_dis_price)
elif max_dis_price:
final_dis_price = max(max_dis_price)
return final_dis_price
@api.model
def _get_discount_vals(self):
payment_vals = []
price_list_discount = price_rule_discount = coupon_code_discount = 0.0
coupon_code_obj = self.env['coupon.code']
partner_id = self.partner_id
pricelist_id = self.pricelist_id
for line in self.order_line:
if not (line.product_id and line.product_uom and
partner_id and pricelist_id and
pricelist_id.discount_policy == 'without_discount' and
self.env.user.has_group(
'sale.group_discount_per_so_line')):
return
if pricelist_id.pricelist_type == 'basic':
price_list_discount = self.discount
else:
if line.product_uom_qty < 0 and line.coupon_code_id:
continue
if line.order_id.have_coupon_code and line.coupon_code_id:
coupon_code_discount += \
coupon_code_obj.get_coupon_discount(line, True)
if line.coupon_code_id and line.price_unit == 0:
continue
if pricelist_id.pricelist_type != 'basic':
price_rule_discount = (self.discount - coupon_code_discount
) - self.cart_discount
untaxed_amount = self.gross_amount - self.discount
payment_vals.append({
'gross_amount': formatLang(self.env, self.gross_amount, digits=2),
'price_list_discount':
formatLang(self.env, price_list_discount, digits=2),
'price_rule_discount':
formatLang(self.env, price_rule_discount, digits=2),
'cart_rule_discount':
formatLang(self.env, self.cart_discount, digits=2),
'coupon_code_discount':
formatLang(self.env, coupon_code_discount, digits=2),
'currency': self.pricelist_id.currency_id.symbol,
'untaxed_amount': formatLang(self.env, untaxed_amount, digits=2),
'position': self.pricelist_id.currency_id.position,
'amount_words': self.amount_words,
'discount': formatLang(self.env, self.discount, digits=2),
})
return payment_vals
@api.depends('discount')
def _get_discount_info_JSON(self):
for record in self:
info = {'title': _('Discount'), 'outstanding': False,
'content': record._get_discount_vals()}
record.discount_widget = json.dumps(info)
def _update_all(self):
for order in self:
amount_untaxed = amount_tax = item_count = item_sum_count = 0.0
product_ids = categ_ids = []
check_dup_product = []
for line in order.order_line:
amount_untaxed += line.price_subtotal
amount_tax += line.price_tax
item_sum_count += line.product_uom_qty
if line.product_id.id not in check_dup_product:
item_count += 1
check_dup_product.append(line.product_id.id)
product_ids.append(line.product_id.id)
categ_ids.append(line.product_id.categ_id.id)
return {
'amount_untaxed': amount_untaxed,
'amount_tax': amount_tax,
'item_sum_count': item_sum_count,
'item_count': item_count,
'product_ids': product_ids,
'categ_ids': categ_ids,
}
@api.multi
def get_values(self):
return self._update_all()
have_coupon_code = fields.Char('Have Coupon Code')
cart_discount = fields.Float(
compute='_check_cart_rules', string='Cart Discount')
coupon_flag = fields.Boolean('Check Coupon Apply')
amount_words = fields.Char(string="Discount Amount Words",
compute='_get_amount_word', store=True)
discount_widget = fields.Text(compute='_get_discount_info_JSON')
coupon_code_id = fields.Many2one('coupon.code', 'Coupon Ref')
@api.multi
def _get_percentage_coupon_discount(self, line, coupon_code_id,
onchange_context, cal_coupon,
discount_per, remove):
coupon_discount_amount = 0.0
total_price = (line.product_uom_qty * line.price_unit)
if self.coupon_flag and not onchange_context and line.coupon_code_id:
if line.dummy_discount:
percent = line.dummy_discount - discount_per
else:
percent = line.discount - discount_per
line.write({'check_coupon': False,
'discount': percent,
'coupon_code_id': False,
'check_coupon': False})
elif not cal_coupon and not remove:
coupon_discount_amount = (total_price * discount_per) / 100
percent = line.discount + discount_per
if percent > 100:
line.dummy_discount = percent
line.discount = 100
else:
line.discount = percent
line.dummy_discount = 0.0
line.coupon_code_id = coupon_code_id.id
line.check_coupon = True
else:
coupon_discount_amount = (total_price * discount_per) / 100
return coupon_discount_amount
@api.multi
def _get_fixed_coupon_discount(self, line, coupon_code_id,
onchange_context, cal_coupon, remove):
line_discount = 0.0
if not line.price_unit:
return 0.0
discount_amount = 0.0
if line.price_unit < coupon_code_id.discount_amount:
discount_amount = line.price_unit
if self.coupon_flag and not onchange_context and line.coupon_code_id:
if discount_amount:
line_discount = (line.price_unit * line.dummy_discount / 100
) - line.price_unit
line.write({'check_coupon': False,
'discount': line_discount / line.price_unit * 100,
'coupon_code_id': False})
else:
if line.dummy_discount:
line_discount = \
(line.price_unit * line.dummy_discount / 100
) - coupon_code_id.discount_amount
else:
line_discount = (line.price_unit * line.discount / 100
) - coupon_code_id.discount_amount
line.write({'check_coupon': False,
'discount': line_discount / line.price_unit * 100,
'coupon_code_id': False})
elif not cal_coupon and not remove:
if discount_amount:
line_discount = (line.price_unit * line.discount / 100
) + discount_amount
else:
line_discount = (line.price_unit * line.discount / 100
) + coupon_code_id.discount_amount
percent = line_discount / line.price_unit * 100
if percent > 100:
line.dummy_discount = percent
line.discount = 100
else:
line.discount = percent
line.coupon_code_id = coupon_code_id.id
line.check_coupon = True
return line.product_uom_qty * coupon_code_id.discount_amount
@api.multi
def _get_same_product_coupon_discount(self, line, coupon_code_id, remove):
qty = int((line.product_uom_qty / coupon_code_id.number_of_x_product)
) * coupon_code_id.number_of_y_product
if line.coupon_code_id and line.check_coupon:
line.write({'check_coupon': False, 'coupon_code_id': False})
elif self.coupon_flag and line.coupon_code_id and \
not line.check_coupon:
line.unlink()
elif line.product_uom_qty == coupon_code_id.number_of_x_product and \
not remove:
line.write({'check_coupon': True,
'coupon_code_id': coupon_code_id.id})
self.env['sale.order.line'].create(
{
'product_id': line.product_id.id,
'product_uom_qty': coupon_code_id.number_of_y_product,
'order_id': self.id,
'discount': 0.0,
'coupon_code_id': coupon_code_id.id,
'price_unit': 0.0
})
elif not self.coupon_flag and qty >= 1:
line.write({'check_coupon': True,
'coupon_code_id': coupon_code_id.id})
self.env['sale.order.line'].create(
{
'product_id': line.product_id.id,
'product_uom_qty': int(qty),
'order_id': self.id,
'discount': 0.0,
'coupon_code_id': coupon_code_id.id,
'price_unit': 0.0
})
@api.multi
def _get_other_product_coupon_discount(self, line, coupon_code_id):
qty = int((line.product_uom_qty / coupon_code_id.number_of_x_product)
) * coupon_code_id.number_of_y_product
if line.coupon_code_id and line.check_coupon:
line.write({'check_coupon': False, 'coupon_code_id': False})
return 0.0
elif self.coupon_flag and line.coupon_code_id \
and not line.check_coupon:
line.unlink()
return 0.0
elif not self.coupon_flag and qty >= 1:
line.write({'check_coupon': True,
'coupon_code_id': coupon_code_id.id})
return qty
return 0.0
@api.multi
def buy_x_get_percentage_coupon_discount(
self, line, coupon_code_id, onchange_context, cal_coupon, remove):
coupon_discount_amount = 0.0
total_price = (line.product_uom_qty * line.price_unit)
if self.coupon_flag and not onchange_context and line.coupon_code_id \
and line.product_uom_qty >= coupon_code_id.number_of_x_product:
if line.dummy_discount:
percent = line.dummy_discount - coupon_code_id.discount_amount
else:
percent = line.discount - coupon_code_id.discount_amount
line.write({'check_coupon': False,
'discount': percent,
'coupon_code_id': False,
'check_coupon': False})
elif not cal_coupon and not remove and line.product_uom_qty >= \
coupon_code_id.number_of_x_product:
coupon_discount_amount = \
(total_price * coupon_code_id.discount_amount) / 100
percent = line.discount + coupon_code_id.discount_amount
if percent > 100:
line.dummy_discount = percent
line.discount = 100
else:
line.discount = percent
line.dummy_discount = 0.0
line.coupon_code_id = coupon_code_id.id
line.check_coupon = True
else:
coupon_discount_amount = \
(total_price * coupon_code_id.discount_amount) / 100
return coupon_discount_amount
@api.multi
def _check_Constraints(self):
self.get_values()
order_line = self.order_line
if not self.have_coupon_code:
raise UserError(_("Please enter the Coupon code!"))
if not order_line:
raise UserError(_("There is no sale order line!"))
if self.pricelist_id.pricelist_type != 'advance' or not \
self.pricelist_id.apply_coupon_code:
raise UserError(_("Coupon code does not apply to "
"sale order pricelist!"))
coupon_obj = self.env['coupon.code']
coupon_code_id = coupon_obj.get_coupon_records(
self.have_coupon_code, self.pricelist_id)
if not coupon_code_id:
raise UserError(_("Coupon code (%s) not found!"
) % (self.have_coupon_code))
if coupon_code_id.usage_limit > 0 \
and coupon_code_id.remaining_limit <= 0:
raise UserError(_("Coupon code (%s) Remaining Limit exceeds!"
) % (self.have_coupon_code))
if coupon_code_id.min_order_amount \
and self.amount_untaxed < coupon_code_id.min_order_amount \
and not self.env.context.get('remove', False):
raise UserError(_("Untaxed Amount (%s) must be greater than "
"Min Order Amount (%s) which required for "
"the apply coupon code!") % (
formatLang(self.env, self.amount_untaxed, digits=2),
formatLang(self.env, coupon_code_id.min_order_amount,
digits=2)))
if coupon_code_id.model_id:
check_coupon = coupon_obj.check_condition(
coupon_code_id, self.partner_id)
if check_coupon:
raise Warning(_("Coupon code (%s) condition criteria not "
"match!") % (self.have_coupon_code))
return coupon_code_id
@api.multi
def apply_coupon_code(self):
if self._context.get('website_id', False):
coupon_obj = self.env['coupon.code']
coupon_id = coupon_obj.get_coupon_records(
self.have_coupon_code, self.pricelist_id)
else:
coupon_id = self._check_Constraints()
order_line = self.order_line
coupon_discount_amount = 0.0
have_coupon_code = self.have_coupon_code
coupon_flag = True
onchange_context = True
coupon_ref_id = coupon_id.id
remove = False
cal_coupon = False
if self.coupon_flag:
have_coupon_code = ''
coupon_flag = False
onchange_context = False
coupon_ref_id = False
remove = True
check_coupon = True
qty = 0.0
for line in order_line:
if coupon_id.apply_on == 'category' and not \
line.product_id.categ_id == coupon_id.categ_id:
continue
elif coupon_id.apply_on == 'product_template' and not \
line.product_id.product_tmpl_id == \
coupon_id.product_tmpl_id:
continue
elif coupon_id.apply_on == 'product' and not \
line.product_id == coupon_id.product_id:
continue
else:
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_discount_amount += \
self._get_percentage_coupon_discount(
line, coupon_id, onchange_context,
cal_coupon, discount_per, remove)
check_coupon = False
elif coupon_id.coupon_type == 'fixed_amount':
coupon_discount_amount += self._get_fixed_coupon_discount(
line, coupon_id, onchange_context, cal_coupon, remove)
check_coupon = False
elif coupon_id.coupon_type == 'buy_x_get_y' and \
coupon_id.number_of_x_product and \
coupon_id.number_of_y_product:
if line.product_uom_qty < \
coupon_id.number_of_x_product and not \
line.coupon_code_id and check_coupon:
check_coupon = True
continue
self._get_same_product_coupon_discount(
line, coupon_id, remove)
check_coupon = False
elif coupon_id.coupon_type == 'buy_x_get_y_other':
if line.product_uom_qty < \
coupon_id.number_of_x_product and not \
line.coupon_code_id and check_coupon:
check_coupon = True
continue
qty += self._get_other_product_coupon_discount(
line, coupon_id)
check_coupon = False
elif coupon_id.coupon_type == 'buy_x_get_percent':
if line.product_uom_qty < \
coupon_id.number_of_x_product and not \
line.coupon_code_id and check_coupon:
check_coupon = True
continue
coupon_discount_amount += \
self.buy_x_get_percentage_coupon_discount(
line, coupon_id, onchange_context,
cal_coupon, remove)
check_coupon = False
if check_coupon and not self._context.get('website_id', False):
raise Warning(_("Coupon code (%s) condition criteria not match!"
) % (self.have_coupon_code))
if qty:
self.env['sale.order.line'].create({
'product_id': coupon_id.other_product_id.id,
'product_uom_qty': int(qty), 'order_id': self.id,
'coupon_code_id': coupon_id.id, 'price_unit': 0.0})
self.write({'have_coupon_code': have_coupon_code,
'coupon_flag': coupon_flag,
'coupon_code_id': coupon_ref_id})
class SaleOrderLine(models.Model):
_inherit = "sale.order.line"
coupon_code_id = fields.Many2one('coupon.code', 'Coupon Ref')
check_coupon = fields.Boolean('Apply Coupon')
dummy_discount = fields.Float(
string='Discount (%)',
digits=dp.get_precision('Discount'), default=0.0)
@api.multi
def get_line_percentage(self):
self._onchange_discount()
discount = self.discount
if discount > 100:
self.dummy_discount = discount
discount = 100
self.discount = discount
return discount
@api.multi
def set_line_amount(self):
discount, product_price = self.get_rule_discount()
if product_price:
discount = product_price * (discount) / 100
self.price_unit = product_price - discount
@api.multi
def get_total_coupon_code(self):
return self.env['coupon.code'].get_coupon_discount(self, False)
def _get_real_price_currency_advance(self, product, uom,
pricelist_id, price_unit):
currency_id = pricelist_id.currency_id
product_currency = \
(product.company_id and product.company_id.currency_id
) or self.env.user.company_id.currency_id
if currency_id.id == product_currency.id:
cur_factor = 1.0
else:
cur_factor = currency_id._get_conversion_rate(
product_currency, currency_id)
product_uom = self.env.context.get('uom') or product.uom_id.id
if uom and uom.id != product_uom:
uom_factor = uom._compute_price(1.0, product.uom_id)
else:
uom_factor = 1.0
return price_unit * uom_factor * cur_factor, currency_id.id
@api.multi
def get_rule_discount(self):
date = fields.Date.context_today(self)
rules = self.env['price.rule'].get_rules(
self.order_id.pricelist_id, date)
max_dis_price = []
min_dis_price = []
discount_per = 0.0
apply_method = self.order_id.pricelist_id.apply_method
discount = 0.0
context_partner = dict(self.env.context,
partner_id=self.order_id.partner_id.id,
date=self.order_id.date_order)
pricelist_context = dict(context_partner, uom=self.product_uom.id,
order_id=self.order_id,
price_unit=self.price_unit)
product_price = 0.0
for rule in rules:
adv_price, adv_rule_id = \
self.order_id.pricelist_id.with_context(
pricelist_context).get_product_price_rule_advance(
self.product_id, self.product_uom_qty,
self.order_id.partner_id)
rule_line_id = self.env['rule.line'].browse(adv_rule_id)
adv_new_price = 0.0
currency_id = False
if not rule_line_id:
adv_new_price, currency_id = self.with_context(
context_partner)._get_real_price_currency(
self.product_id, False,
self.product_uom_qty,
self.product_uom,
self.order_id.pricelist_id.id)
else:
if rule_line_id.rule_type == 'percent':
adv_new_price, currency_id = self.with_context(
context_partner)._get_real_price_currency(
self.product_id, False,
self.product_uom_qty,
self.product_uom,
self.order_id.pricelist_id.id)
elif rule_line_id.rule_type == 'fixed_amount':
adv_new_price, currency_id = self.with_context(
context_partner)._get_real_price_currency_advance(
self.product_id,
self.product_uom,
self.order_id.pricelist_id, self.price_unit)
if adv_new_price != 0:
if self.order_id.pricelist_id.currency_id.id != currency_id:
adv_new_price = self.env['res.currency'].browse(
currency_id).with_context(context_partner).compute(
adv_new_price, rule.pricelist_id.currency_id)
if not product_price:
product_price = adv_new_price
discount_per =\
(adv_new_price - adv_price) / adv_new_price * 100
if apply_method == 'first_matched_rule':
discount += discount_per
break
elif apply_method == 'all_matched_rules':
discount += discount_per
elif apply_method == 'smallest_discount' and adv_rule_id:
min_dis_price.append(discount_per)
else:
max_dis_price.append(discount_per)
if min_dis_price:
discount += min(min_dis_price)
if max_dis_price:
discount += max(max_dis_price)
return discount, product_price
# Overrides Function
@api.onchange('product_id', 'price_unit', 'product_uom',
'product_uom_qty', 'tax_id')
def _onchange_discount(self):
self.discount = 0.0
if not (self.product_id and self.product_uom and
self.order_id.partner_id and self.order_id.pricelist_id and
self.order_id.pricelist_id.discount_policy ==
'without_discount' and
self.env.user.has_group('sale.group_discount_per_so_line')):
return
discount = 0.0
context_partner = dict(self.env.context,
partner_id=self.order_id.partner_id.id,
date=self.order_id.date_order)
pricelist_context = dict(context_partner, uom=self.product_uom.id)
if self.order_id.pricelist_id.pricelist_type == 'basic':
price, rule_id = self.order_id.pricelist_id.with_context(
pricelist_context).get_product_price_rule(
self.product_id, self.product_uom_qty or 1.0,
self.order_id.partner_id)
new_list_price, currency_id = self.with_context(
context_partner)._get_real_price_currency(
self.product_id, rule_id, self.product_uom_qty,
self.product_uom, self.order_id.pricelist_id.id)
if new_list_price != 0:
if self.order_id.pricelist_id.currency_id.id != currency_id:
new_list_price = self.env['res.currency'].browse(
currency_id).with_context(context_partner).compute(
new_list_price, self.order_id.pricelist_id.currency_id)
discount = (new_list_price - price) / new_list_price * 100
if discount > 0:
self.discount = discount
else:
if self.coupon_code_id and (self._context.get(
'quantity', False) or self._context.get(
'price_unit', False) or self._context.get('tax', False)):
raise Warning(_('You can not change order line. '
'Please remove coupon code first!'))
discount, product_price = self.get_rule_discount()
if discount > 0:
self.discount = discount
if self.order_id.have_coupon_code and self.coupon_code_id:
self.get_total_coupon_code()