[IMP] product,purchase: compute price with discount

Add two computed fields, `price_discounted` on `product.supplierinfo`
and `price_unit_discounted` on `purchase.order.line`, to get more
precise price.
For the vendor pricelist, it's useful to get the actual lower price (a
vendor price can be lower than another one but the result can be
different if the discount is take in account).
For the purchase order line, it's useful to get the right price in the
products catalog.

task-3373589

closes odoo/odoo#135502

Related: odoo/upgrade#5223
Related: odoo/enterprise#48375
Signed-off-by: Arnold Moyaux (arm) <arm@odoo.com>
This commit is contained in:
svs-odoo
2023-10-26 07:22:56 +00:00
committed by Arnold Moyaux
parent bc01c7bcec
commit 28a9fba05f
5 changed files with 79 additions and 6 deletions
+8 -2
View File
@@ -3,6 +3,7 @@
import re
from collections import defaultdict
from operator import itemgetter
from odoo import api, fields, models, tools, _
from odoo.exceptions import ValidationError
@@ -676,13 +677,18 @@ class ProductProduct(models.Model):
sellers |= seller
return sellers
def _select_seller(self, partner_id=False, quantity=0.0, date=None, uom_id=False, ordered_by='price', params=False):
def _select_seller(self, partner_id=False, quantity=0.0, date=None, uom_id=False, ordered_by='price_discounted', params=False):
# Always sort by discounted price but another field can take the primacy through the `ordered_by` param.
sort_key = itemgetter('price_discounted', 'sequence', 'id')
if ordered_by != 'price_discounted':
sort_key = itemgetter(ordered_by, 'price_discounted', 'sequence', 'id')
sellers = self._get_filtered_sellers(partner_id=partner_id, quantity=quantity, date=date, uom_id=uom_id, params=params)
res = self.env['product.supplierinfo']
for seller in sellers:
if not res or res.partner_id == seller.partner_id:
res |= seller
return res and res.sorted(ordered_by)[:1]
return res and res.sorted(sort_key)[:1]
def _get_product_price_context(self, combination):
self.ensure_one()
@@ -39,6 +39,7 @@ class SupplierInfo(models.Model):
price = fields.Float(
'Price', default=0.0, digits='Product Price',
required=True, help="The price to purchase a product")
price_discounted = fields.Float('Discounted Price', compute='_compute_price_discounted')
company_id = fields.Many2one(
'res.company', 'Company',
default=lambda self: self.env.company.id, index=1)
@@ -67,6 +68,11 @@ class SupplierInfo(models.Model):
digits='Discount',
readonly=False)
@api.depends('discount', 'price')
def _compute_price_discounted(self):
for rec in self:
rec.price_discounted = rec.price * (1 - rec.discount / 100)
@api.onchange('product_tmpl_id')
def _onchange_product_tmpl_id(self):
if self.product_id and self.product_tmpl_id and self.product_id not in self.product_tmpl_id.product_variant_ids:
+3 -3
View File
@@ -877,7 +877,7 @@ class PurchaseOrder(models.Model):
)
if seller:
product_infos.update(
price=seller.price,
price=seller.price_discounted,
min_qty=seller.min_qty,
)
# Check if the product uses some packaging.
@@ -990,8 +990,8 @@ class PurchaseOrder(models.Model):
uom_id=pol.product_uom)
if seller:
# Fix the PO line's price on the seller's one.
pol.price_unit = seller.price
return pol.price_unit
pol.price_unit = seller.price_discounted
return pol.price_unit_discounted
def _create_update_date_activity(self, updated_dates):
note = Markup('<p>%s</p>\n') % _('%s modified receipt dates for the following products:', self.partner_id.name)
@@ -38,6 +38,7 @@ class PurchaseOrderLine(models.Model):
price_unit = fields.Float(
string='Unit Price', required=True, digits='Product Price',
compute="_compute_price_unit_and_date_planned_and_name", readonly=False, store=True)
price_unit_discounted = fields.Float('Unit Price (Discounted)', compute='_compute_price_unit_discounted')
price_subtotal = fields.Monetary(compute='_compute_amount', string='Subtotal', store=True)
price_total = fields.Monetary(compute='_compute_amount', string='Total', store=True)
@@ -126,6 +127,11 @@ class PurchaseOrderLine(models.Model):
taxes = line.product_id.supplier_taxes_id.filtered_domain(self.env['account.tax']._check_company_domain(line.company_id))
line.taxes_id = fpos.map_tax(taxes)
@api.depends('discount', 'price_unit')
def _compute_price_unit_discounted(self):
for line in self:
line.price_unit_discounted = line.price_unit * (1 - line.discount / 100)
@api.depends('invoice_lines.move_id.state', 'invoice_lines.quantity', 'qty_received', 'product_uom_qty', 'order_id.state')
def _compute_qty_invoiced(self):
for line in self:
@@ -497,7 +503,7 @@ class PurchaseOrderLine(models.Model):
}
catalog_info.update(
quantity=self.product_qty,
price=self.price_unit,
price=self.price_unit * (1 - self.discount / 100),
readOnly=self.order_id._is_readonly(),
uom=uom,
)
@@ -648,6 +648,61 @@ class TestPurchaseToInvoice(TestPurchaseToInvoiceCommon):
po_line.write({'product_qty': 4})
self.assertEqual(40.0, po_line.price_unit, "Unit price should be set to 40.0 for 4 quantity")
def test_supplier_discounted_price(self):
""" Check the lower price (discount included) is used.
"""
uom_dozen = self.env.ref('uom.product_uom_dozen')
supplierinfo_common_vals = {
'partner_id': self.partner_a.id,
'product_id': self.product_order.id,
'product_tmpl_id': self.product_order.product_tmpl_id.id,
}
supplierinfo = self.env['product.supplierinfo'].create([
{
**supplierinfo_common_vals,
'price': 100.0,
'discount': 0, # Real price by unit: 100.00
}, {
**supplierinfo_common_vals,
'price': 120.0,
'discount': 20, # Real price by unit: 96.00
}, {
**supplierinfo_common_vals,
'min_qty': 10,
'price': 140.0,
'discount': 50, # Real price by unit: 70.00
},
])
self.assertRecordValues(supplierinfo, [
{'price_discounted': 100},
{'price_discounted': 96},
{'price_discounted': 70},
])
po_form = Form(self.env['purchase.order'])
po_form.partner_id = self.partner_a
with po_form.order_line.new() as po_line_form:
po_line_form.product_id = self.product_order
po_line_form.product_qty = 1
po = po_form.save()
po_line = po.order_line[0]
self.assertEqual(120.0, po_line.price_unit)
self.assertEqual(20, po_line.discount)
self.assertEqual(96.0, po_line.price_unit_discounted)
self.assertEqual(96.0, po_line.price_subtotal)
# Increase the PO line quantity: it should take another price if min. qty. is reached.
po_form = Form(po)
with po_form.order_line.edit(0) as po_line_form:
po_line_form.product_uom = uom_dozen
po_line_form.product_qty = 3
po = po_form.save()
po_line = po.order_line[0]
self.assertEqual(1680.0, po_line.price_unit, "140.0 * 12 = 1680.0")
self.assertEqual(50, po_line.discount)
self.assertEqual(840.0, po_line.price_unit_discounted, "1680.0 * 0.5 = 840.0")
self.assertEqual(2520.0, po_line.price_subtotal, "840.0 * 3 = 2520.0")
@tagged('post_install', '-at_install')
class TestInvoicePurchaseMatch(TestPurchaseToInvoiceCommon):