diff --git a/addons/product/models/product_product.py b/addons/product/models/product_product.py index 5a607058936..01fa220e3c8 100644 --- a/addons/product/models/product_product.py +++ b/addons/product/models/product_product.py @@ -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() diff --git a/addons/product/models/product_supplierinfo.py b/addons/product/models/product_supplierinfo.py index 5545895383e..6d27f248026 100644 --- a/addons/product/models/product_supplierinfo.py +++ b/addons/product/models/product_supplierinfo.py @@ -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: diff --git a/addons/purchase/models/purchase_order.py b/addons/purchase/models/purchase_order.py index 5dd2203c37f..3ffcc6f5a54 100644 --- a/addons/purchase/models/purchase_order.py +++ b/addons/purchase/models/purchase_order.py @@ -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('

%s

\n') % _('%s modified receipt dates for the following products:', self.partner_id.name) diff --git a/addons/purchase/models/purchase_order_line.py b/addons/purchase/models/purchase_order_line.py index 989e640a5ff..820fac3707b 100644 --- a/addons/purchase/models/purchase_order_line.py +++ b/addons/purchase/models/purchase_order_line.py @@ -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, ) diff --git a/addons/purchase/tests/test_purchase_invoice.py b/addons/purchase/tests/test_purchase_invoice.py index f7d58d56917..75b39fdb0bf 100644 --- a/addons/purchase/tests/test_purchase_invoice.py +++ b/addons/purchase/tests/test_purchase_invoice.py @@ -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):