diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index 482ea0537a9..91c5bc3749f 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -702,7 +702,7 @@ class PurchaseOrderLine(models.Model): qty = 0.0 price_unit = self._get_stock_move_price_unit() for move in self.move_ids.filtered(lambda x: x.state != 'cancel' and not x.location_dest_id.usage == "supplier"): - qty += move.product_qty + qty += move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP') template = { 'name': self.name or '', 'product_id': self.product_id.id, @@ -726,7 +726,14 @@ class PurchaseOrderLine(models.Model): } diff_quantity = self.product_qty - qty if float_compare(diff_quantity, 0.0, precision_rounding=self.product_uom.rounding) > 0: - template['product_uom_qty'] = diff_quantity + quant_uom = self.product_id.uom_id + get_param = self.env['ir.config_parameter'].sudo().get_param + if self.product_uom.id != quant_uom.id and get_param('stock.propagate_uom') != '1': + product_qty = self.product_uom._compute_quantity(diff_quantity, quant_uom, rounding_method='HALF-UP') + template['product_uom'] = quant_uom.id + template['product_uom_qty'] = product_qty + else: + template['product_uom_qty'] = diff_quantity res.append(template) return res diff --git a/addons/purchase/tests/test_create_picking.py b/addons/purchase/tests/test_create_picking.py index b29e85b3825..9cdfe0fb4fd 100644 --- a/addons/purchase/tests/test_create_picking.py +++ b/addons/purchase/tests/test_create_picking.py @@ -174,3 +174,53 @@ class TestCreatePicking(common.TestProductCommon): customer_move._action_assign() self.assertEqual(customer_move.state, 'assigned', 'Reservation should work with the new quantity provided by the PO.') + + def test_03_uom(self): + """ Buy a dozen of products stocked in units. Check that the quantities on the purchase order + lines as well as the received quantities are handled in dozen while the moves themselves + are handled in units. Edit the ordered quantities, check that the quantites are correctly + updated on the moves. Edit the ir.config_parameter to propagate the uom of the purchase order + lines to the moves and edit a last time the ordered quantities. Receive, check the quantities. + """ + uom_unit = self.env.ref('product.product_uom_unit') + uom_dozen = self.env.ref('product.product_uom_dozen') + + self.assertEqual(self.product_id_1.uom_po_id.id, uom_unit.id) + + # buy a dozen + po = self.env['purchase.order'].create(self.po_vals) + + po.order_line.product_qty = 1 + po.order_line.product_uom = uom_dozen.id + po.button_confirm() + + # the move should be 12 units + # note: move.product_qty = computed field, always in the uom of the quant + # move.product_uom_qty = stored field representing the initial demand in move.product_uom + move1 = po.picking_ids.move_lines[0] + self.assertEqual(move1.product_uom_qty, 12) + self.assertEqual(move1.product_uom.id, uom_unit.id) + self.assertEqual(move1.product_qty, 12) + + # edit the so line, sell 2 dozen, the move should now be 24 units + po.order_line.product_qty = 2 + self.assertEqual(move1.product_uom_qty, 24) + self.assertEqual(move1.product_uom.id, uom_unit.id) + self.assertEqual(move1.product_qty, 24) + + # force the propagation of the uom, sell 3 dozen + self.env['ir.config_parameter'].sudo().set_param('stock.propagate_uom', '1') + po.order_line.product_qty = 3 + move2 = po.picking_ids.move_lines.filtered(lambda m: m.product_uom.id == uom_dozen.id) + self.assertEqual(move2.product_uom_qty, 1) + self.assertEqual(move2.product_uom.id, uom_dozen.id) + self.assertEqual(move2.product_qty, 12) + + # deliver everything + move1.quantity_done = 24 + move2.quantity_done = 1 + po.picking_ids.button_validate() + + # check the delivered quantity + self.assertEqual(po.order_line.qty_received, 3.0) + diff --git a/addons/sale_mrp/tests/test_sale_mrp_flow.py b/addons/sale_mrp/tests/test_sale_mrp_flow.py index 7efdc9a9e7d..341f72cd4c9 100644 --- a/addons/sale_mrp/tests/test_sale_mrp_flow.py +++ b/addons/sale_mrp/tests/test_sale_mrp_flow.py @@ -167,8 +167,8 @@ class TestSaleMrpFlow(common.TransactionCase): mnf_product_a = self.env['mrp.production'].search([('product_id', '=', product_a.id)]) self.assertTrue(mnf_product_a, 'Manufacturing order not created.') - self.assertEqual(mnf_product_a.product_qty, 10, 'Wrong product quantity in manufacturing order.') - self.assertEqual(mnf_product_a.product_uom_id.id, self.uom_dozen.id, 'Wrong unit of measure in manufacturing order.') + self.assertEqual(mnf_product_a.product_qty, 120, 'Wrong product quantity in manufacturing order.') + self.assertEqual(mnf_product_a.product_uom_id.id, self.uom_unit.id, 'Wrong unit of measure in manufacturing order.') self.assertEqual(mnf_product_a.state, 'confirmed', 'Manufacturing order should be confirmed.') # ------------------------------------------------------------------------------------------ diff --git a/addons/sale_stock/models/sale_order.py b/addons/sale_stock/models/sale_order.py index d3580f35884..29514b574de 100644 --- a/addons/sale_stock/models/sale_order.py +++ b/addons/sale_stock/models/sale_order.py @@ -235,7 +235,7 @@ class SaleOrderLine(models.Model): continue qty = 0.0 for move in line.move_ids.filtered(lambda r: r.state != 'cancel'): - qty += move.product_qty + qty += move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom, rounding_method='HALF-UP') if float_compare(qty, line.product_uom_qty, precision_digits=precision) >= 0: continue @@ -260,8 +260,16 @@ class SaleOrderLine(models.Model): values = line._prepare_procurement_values(group_id=group_id) product_qty = line.product_uom_qty - qty + + procurement_uom = line.product_uom + quant_uom = line.product_id.uom_id + get_param = self.env['ir.config_parameter'].sudo().get_param + if procurement_uom.id != quant_uom.id and get_param('stock.propagate_uom') != '1': + product_qty = line.product_uom._compute_quantity(product_qty, quant_uom, rounding_method='HALF-UP') + procurement_uom = quant_uom + try: - self.env['procurement.group'].run(line.product_id, product_qty, line.product_uom, line.order_id.partner_shipping_id.property_stock_customer, line.name, line.order_id.name, values) + self.env['procurement.group'].run(line.product_id, product_qty, procurement_uom, line.order_id.partner_shipping_id.property_stock_customer, line.name, line.order_id.name, values) except UserError as error: errors.append(error.name) if errors: diff --git a/addons/sale_stock/tests/test_sale_stock.py b/addons/sale_stock/tests/test_sale_stock.py index 5a6e43377fe..5bcffe08ea8 100644 --- a/addons/sale_stock/tests/test_sale_stock.py +++ b/addons/sale_stock/tests/test_sale_stock.py @@ -303,3 +303,61 @@ class TestSaleStock(TestSale): self.assertEqual(len(so1.picking_ids), 2) picking2 = so1.picking_ids.filtered(lambda p: p.state != 'cancel') self.assertEqual(picking2.partner_id.id, partner2) + + def test_06_uom(self): + """ Sell a dozen of products stocked in units. Check that the quantities on the sale order + lines as well as the delivered quantities are handled in dozen while the moves themselves + are handled in units. Edit the ordered quantities, check that the quantites are correctly + updated on the moves. Edit the ir.config_parameter to propagate the uom of the sale order + lines to the moves and edit a last time the ordered quantities. Deliver, check the + quantities. + """ + uom_unit = self.env.ref('product.product_uom_unit') + uom_dozen = self.env.ref('product.product_uom_dozen') + item1 = self.products['prod_order'] + + self.assertEqual(item1.uom_id.id, uom_unit.id) + + # sell a dozen + so1 = self.env['sale.order'].create({ + 'partner_id': self.partner.id, + 'order_line': [(0, 0, { + 'name': item1.name, + 'product_id': item1.id, + 'product_uom_qty': 1, + 'product_uom': uom_dozen.id, + 'price_unit': item1.list_price, + })], + }) + so1.action_confirm() + + # the move should be 12 units + # note: move.product_qty = computed field, always in the uom of the quant + # move.product_uom_qty = stored field representing the initial demand in move.product_uom + move1 = so1.picking_ids.move_lines[0] + self.assertEqual(move1.product_uom_qty, 12) + self.assertEqual(move1.product_uom.id, uom_unit.id) + self.assertEqual(move1.product_qty, 12) + + # edit the so line, sell 2 dozen, the move should now be 24 units + so1.order_line.product_uom_qty = 2 + self.assertEqual(move1.product_uom_qty, 24) + self.assertEqual(move1.product_uom.id, uom_unit.id) + self.assertEqual(move1.product_qty, 24) + + # force the propagation of the uom, sell 3 dozen + self.env['ir.config_parameter'].sudo().set_param('stock.propagate_uom', '1') + so1.order_line.product_uom_qty = 3 + move2 = so1.picking_ids.move_lines.filtered(lambda m: m.product_uom.id == uom_dozen.id) + self.assertEqual(move2.product_uom_qty, 1) + self.assertEqual(move2.product_uom.id, uom_dozen.id) + self.assertEqual(move2.product_qty, 12) + + # deliver everything + move1.quantity_done = 24 + move2.quantity_done = 1 + so1.picking_ids.button_validate() + + # check the delivered quantity + self.assertEqual(so1.order_line.qty_delivered, 3.0) +