From 3f0ed7d04553c1eeec9eaad4455ff053ddcebf55 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Thu, 12 Sep 2019 15:51:28 +0000 Subject: [PATCH] [FIX] product: _select_seller With the new ORM, x2m records are not ordered by default. The _select_sellers implementation requires to loop on the supplier info records ordered by their _order. We fix that by sorting with a lambda with the same order set on the model. Simply using .sorted() isn't enough since it does a sort in sql, so newId records are completely removed from the resulting recordset. This is related to task 1947351 and PR 34049. We move the tests into purchase_stock and actually implement the tests proposed in the task. --- addons/product/models/product.py | 2 +- .../tests/test_purchase_order.py | 2 - .../tests/test_replenish_wizard.py | 195 ++++++++++++++++++ .../sale_purchase/tests/test_sale_purchase.py | 54 ----- 4 files changed, 196 insertions(+), 57 deletions(-) diff --git a/addons/product/models/product.py b/addons/product/models/product.py index 7f6113fc48a..3f085cf5b8c 100644 --- a/addons/product/models/product.py +++ b/addons/product/models/product.py @@ -579,7 +579,7 @@ class ProductProduct(models.Model): 'target': 'new'} def _prepare_sellers(self, params): - return self.seller_ids + return self.seller_ids.sorted(lambda s: (s.sequence, -s.min_qty, s.price)) def _select_seller(self, partner_id=False, quantity=0.0, date=None, uom_id=False, params=False): self.ensure_one() diff --git a/addons/purchase_stock/tests/test_purchase_order.py b/addons/purchase_stock/tests/test_purchase_order.py index 155a71f11a0..75eaefac587 100644 --- a/addons/purchase_stock/tests/test_purchase_order.py +++ b/addons/purchase_stock/tests/test_purchase_order.py @@ -42,9 +42,7 @@ class TestPurchaseOrder(AccountingTestCase): })], } - def test_00_purchase_order_flow(self): - # Ensure product_id_2 doesn't have res_partner_1 as supplier if self.partner_id in self.product_id_2.seller_ids.mapped('name'): id_to_remove = self.product_id_2.seller_ids.filtered(lambda r: r.name == self.partner_id).ids[0] if self.product_id_2.seller_ids.filtered(lambda r: r.name == self.partner_id) else False diff --git a/addons/purchase_stock/tests/test_replenish_wizard.py b/addons/purchase_stock/tests/test_replenish_wizard.py index 08bb1f998e3..bdb126dd57a 100644 --- a/addons/purchase_stock/tests/test_replenish_wizard.py +++ b/addons/purchase_stock/tests/test_replenish_wizard.py @@ -52,3 +52,198 @@ class TestReplenishWizard(TestStockCommon): self.assertTrue(order_line, 'The product is not in the Purchase Order') self.assertEqual(order_line.product_qty, self.product_uom_qty, 'Quantities does not match') self.assertEqual(order_line.price_unit, self.product1_price, 'Prices does not match') + + def test_chose_supplier_1(self): + """ Choose supplier based on the ordered quantity and minimum price + + replenish 10 + + 1)seq1 vendor1 140 min qty 1 + 2)seq2 vendor1 100 min qty 10 + -> 2) should be chosen + """ + product_to_buy = self.env['product.product'].create({ + 'name': "Furniture Service", + 'type': 'product', + 'categ_id': self.env.ref('product.product_category_all').id, + 'route_ids': [(4, self.env.ref('purchase_stock.route_warehouse0_buy').id, 0)], + }) + vendor1 = self.env['res.partner'].create({'name': 'vendor1', 'email': 'from.test@example.com'}) + + supplierinfo1 = self.env['product.supplierinfo'].create({ + 'product_tmpl_id': product_to_buy.product_tmpl_id.id, + 'name': vendor1.id, + 'min_qty': 1, + 'price': 140, + 'sequence': 1, + }) + supplierinfo2 = self.env['product.supplierinfo'].create({ + 'product_tmpl_id': product_to_buy.product_tmpl_id.id, + 'name': vendor1.id, + 'min_qty': 10, + 'price': 100, + 'sequence': 2, + }) + + replenish_wizard = self.env['product.replenish'].create({ + 'product_id': product_to_buy.id, + 'product_tmpl_id': product_to_buy.product_tmpl_id.id, + 'product_uom_id': self.uom_unit.id, + 'quantity': 10, + 'warehouse_id': self.wh.id, + }) + replenish_wizard.launch_replenishment() + last_po_id = self.env['purchase.order'].search([ + ('origin', 'ilike', '%Manual Replenishment%'), + ])[-1] + self.assertEqual(last_po_id.partner_id, vendor1) + self.assertEqual(last_po_id.order_line.price_unit, 100) + + def test_chose_supplier_2(self): + """ Choose supplier based on the ordered quantity and minimum price + + replenish 10 + + 1)seq1 vendor1 140 min qty 1 + 2)seq2 vendor2 90 min qty 10 + 3)seq3 vendor1 100 min qty 10 + -> 3) should be chosen + """ + product_to_buy = self.env['product.product'].create({ + 'name': "Furniture Service", + 'type': 'product', + 'categ_id': self.env.ref('product.product_category_all').id, + 'route_ids': [(4, self.env.ref('purchase_stock.route_warehouse0_buy').id, 0)], + }) + vendor1 = self.env['res.partner'].create({'name': 'vendor1', 'email': 'from.test@example.com'}) + vendor2 = self.env['res.partner'].create({'name': 'vendor2', 'email': 'from.test2@example.com'}) + + supplierinfo1 = self.env['product.supplierinfo'].create({ + 'product_tmpl_id': product_to_buy.product_tmpl_id.id, + 'name': vendor1.id, + 'min_qty': 1, + 'price': 140, + 'sequence': 1, + }) + supplierinfo2 = self.env['product.supplierinfo'].create({ + 'product_tmpl_id': product_to_buy.product_tmpl_id.id, + 'name': vendor2.id, + 'min_qty': 10, + 'price': 90, + 'sequence': 2, + }) + supplierinfo3 = self.env['product.supplierinfo'].create({ + 'product_tmpl_id': product_to_buy.product_tmpl_id.id, + 'name': vendor1.id, + 'min_qty': 10, + 'price': 100, + 'sequence': 3, + }) + + replenish_wizard = self.env['product.replenish'].create({ + 'product_id': product_to_buy.id, + 'product_tmpl_id': product_to_buy.product_tmpl_id.id, + 'product_uom_id': self.uom_unit.id, + 'quantity': 10, + 'warehouse_id': self.wh.id, + }) + replenish_wizard.launch_replenishment() + last_po_id = self.env['purchase.order'].search([ + ('origin', 'ilike', '%Manual Replenishment%'), + ])[-1] + self.assertEqual(last_po_id.partner_id, vendor1) + self.assertEqual(last_po_id.order_line.price_unit, 100) + + def test_chose_supplier_3(self): + """ Choose supplier based on the ordered quantity and minimum price + + replenish 10 + + 1)seq2 vendor1 50 + 2)seq1 vendor2 50 + -> 2) should be chosen + """ + product_to_buy = self.env['product.product'].create({ + 'name': "Furniture Service", + 'type': 'product', + 'categ_id': self.env.ref('product.product_category_all').id, + 'route_ids': [(4, self.env.ref('purchase_stock.route_warehouse0_buy').id, 0)], + }) + vendor1 = self.env['res.partner'].create({'name': 'vendor1', 'email': 'from.test@example.com'}) + vendor2 = self.env['res.partner'].create({'name': 'vendor2', 'email': 'from.test2@example.com'}) + + supplierinfo1 = self.env['product.supplierinfo'].create({ + 'product_tmpl_id': product_to_buy.product_tmpl_id.id, + 'name': vendor1.id, + 'price': 50, + 'sequence': 2, + }) + supplierinfo2 = self.env['product.supplierinfo'].create({ + 'product_tmpl_id': product_to_buy.product_tmpl_id.id, + 'name': vendor2.id, + 'price': 50, + 'sequence': 1, + }) + + replenish_wizard = self.env['product.replenish'].create({ + 'product_id': product_to_buy.id, + 'product_tmpl_id': product_to_buy.product_tmpl_id.id, + 'product_uom_id': self.uom_unit.id, + 'quantity': 10, + 'warehouse_id': self.wh.id, + }) + replenish_wizard.launch_replenishment() + last_po_id = self.env['purchase.order'].search([ + ('origin', 'ilike', '%Manual Replenishment%'), + ])[-1] + self.assertEqual(last_po_id.partner_id, vendor2) + + def test_chose_supplier_4(self): + """ Choose supplier based on the ordered quantity and minimum price + + replenish 10 + + 1)seq1 vendor1 100 min qty 2 + 2)seq2 vendor1 60 min qty 10 + 2)seq3 vendor1 80 min qty 5 + -> 2) should be chosen + """ + product_to_buy = self.env['product.product'].create({ + 'name': "Furniture Service", + 'type': 'product', + 'categ_id': self.env.ref('product.product_category_all').id, + 'route_ids': [(4, self.env.ref('purchase_stock.route_warehouse0_buy').id, 0)], + }) + vendor1 = self.env['res.partner'].create({'name': 'vendor1', 'email': 'from.test@example.com'}) + supplierinfo1 = self.env['product.supplierinfo'].create({ + 'name': vendor1.id, + 'price': 100, + 'product_tmpl_id': product_to_buy.product_tmpl_id.id, + 'min_qty': 2 + }) + supplierinfo2 = self.env['product.supplierinfo'].create({ + 'name': vendor1.id, + 'price': 60, + 'product_tmpl_id': product_to_buy.product_tmpl_id.id, + 'min_qty': 10 + }) + supplierinfo3 = self.env['product.supplierinfo'].create({ + 'name': vendor1.id, + 'price': 80, + 'product_tmpl_id': product_to_buy.product_tmpl_id.id, + 'min_qty': 5 + }) + replenish_wizard = self.env['product.replenish'].create({ + 'product_id': product_to_buy.id, + 'product_tmpl_id': product_to_buy.product_tmpl_id.id, + 'product_uom_id': self.uom_unit.id, + 'quantity': 10, + 'warehouse_id': self.wh.id, + }) + replenish_wizard.launch_replenishment() + last_po_id = self.env['purchase.order'].search([ + ('origin', 'ilike', '%Manual Replenishment%'), + ])[-1] + + self.assertEqual(last_po_id.partner_id, vendor1) + self.assertEqual(last_po_id.order_line.price_unit, 60) diff --git a/addons/sale_purchase/tests/test_sale_purchase.py b/addons/sale_purchase/tests/test_sale_purchase.py index 433b5ba47f7..9161b87cbbe 100644 --- a/addons/sale_purchase/tests/test_sale_purchase.py +++ b/addons/sale_purchase/tests/test_sale_purchase.py @@ -256,57 +256,3 @@ class TestSalePurchase(TestCommonSalePurchaseNoChart): self.assertEqual(purchase_line2.sale_line_id, self.sol1_service_purchase_1, "The 2nd PO line came from the SO line sol1_service_purchase_1") self.assertEqual(purchase_line2.product_qty, delta, "The quantity of the new PO line is the quantity added on the Sale Line, after first PO confirmation") - def test_choose_supplier(self): - """ Choose supplier based on the ordered quantity and minimum price""" - product_to_buy = self.env['product.product'].create({ - 'name': "Furniture Service", - 'type': 'service', - 'service_to_purchase': True, - }) - supplierinfo1 = self.env['product.supplierinfo'].create({ - 'name': self.partner_vendor_service.id, - 'price': 100, - 'product_tmpl_id': product_to_buy.product_tmpl_id.id, - 'min_qty': 2 - }) - supplierinfo2 = self.env['product.supplierinfo'].create({ - 'name': self.partner_vendor_service.id, - 'price': 60, - 'product_tmpl_id': product_to_buy.product_tmpl_id.id, - 'min_qty': 10 - }) - supplierinfo3 = self.env['product.supplierinfo'].create({ - 'name': self.partner_vendor_service.id, - 'price': 80, - 'product_tmpl_id': product_to_buy.product_tmpl_id.id, - 'min_qty': 5 - }) - # create sale order having ordered quantity 3. - sale_order = self.env['sale.order'].create({ - 'partner_id': self.partner_customer_usd.id, - 'pricelist_id': self.pricelist_usd.id, - 'order_line': [(0, 0, {'name': product_to_buy.name, - 'product_id': product_to_buy.id, - 'product_uom_qty': 3})] - }) - # confirm sale order - sale_order.action_confirm() - - # case1 for quantity 10. - pol_from_so = self.env['purchase.order.line'].search([('sale_line_id', 'in', sale_order.order_line.ids), ('state', '!=', 'purchase')]) - pol_from_so.order_id.button_confirm() # confirm PO, so that new PO will be generated. - # unit price of purchase order line must be 100. - self.assertEqual(pol_from_so.price_unit, supplierinfo1.price, "For 10qty unit price must be 100.") - - # case2. for quantity 6. - sale_order.order_line.product_uom_qty = 8 # update sale order line having ordered quantity from 2 to 6. - pol_from_so = self.env['purchase.order.line'].search([('sale_line_id', 'in', sale_order.order_line.ids), ('state', '!=', 'purchase')]) - pol_from_so.order_id.button_confirm() # confirm PO, so that new PO will be generated. - # unit price of purchase order line must be 80. - self.assertEqual(pol_from_so.price_unit, supplierinfo3.price, "For 10qty unit price must be 80.") - - # case3. for quantity 20. - sale_order.order_line.product_uom_qty = 20 # update sale order line having ordered quantity from 6 to 20. - pol_from_so = self.env['purchase.order.line'].search([('sale_line_id', 'in', sale_order.order_line.ids), ('state', '!=', 'purchase')]) - # unit price of purchase order line must be 60. - self.assertEqual(pol_from_so.price_unit, supplierinfo2.price, "For 10qty unit price must be 60.")