diff options
| -rw-r--r-- | addons/stock/models/stock_move.py | 38 | ||||
| -rw-r--r-- | addons/stock/tests/test_move2.py | 169 |
2 files changed, 205 insertions, 2 deletions
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 740854283dc..dd93a25a484 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -1548,6 +1548,15 @@ class StockMove(models.Model): """ This method will try to apply the procure method MTO on some moves if a compatible MTO route is found. Else the procure method will be set to MTS """ + # Prepare the MTSO variables. They are needed since MTSO moves are handled separately. + # We need 2 dicts: + # - needed quantity per location per product + # - forecasted quantity per location per product + mtso_products_by_locations = defaultdict(list) + mtso_needed_qties_by_loc = defaultdict(dict) + mtso_free_qties_by_loc = {} + mtso_moves = self.env['stock.move'] + for move in self: product_id = move.product_id domain = [ @@ -1556,7 +1565,32 @@ class StockMove(models.Model): ('action', '!=', 'push') ] rules = self.env['procurement.group']._search_rule(False, product_id, move.warehouse_id, domain) - if rules and (rules.procure_method == 'make_to_order'): - move.procure_method = rules.procure_method + if rules: + if rules.procure_method in ['make_to_order', 'make_to_stock']: + move.procure_method = rules.procure_method + else: + # Get the needed quantity for the `mts_else_mto` moves. + mtso_needed_qties_by_loc[rules.location_src_id].setdefault(product_id.id, 0) + mtso_needed_qties_by_loc[rules.location_src_id][product_id.id] += move.product_qty + + # This allow us to get the forecasted quantity in batch later on + mtso_products_by_locations[rules.location_src_id].append(product_id.id) + mtso_moves |= move else: move.procure_method = 'make_to_stock' + + # Get the forecasted quantity for the `mts_else_mto` moves. + for location, product_ids in mtso_products_by_locations.items(): + products = self.env['product.product'].browse(product_ids).with_context(location=location.id) + mtso_free_qties_by_loc[location] = {product.id: product.free_qty for product in products} + + # Now that we have the needed and forecasted quantity per location and per product, we can + # choose whether the mtso_moves need to be MTO or MTS. + for move in mtso_moves: + needed_qty = move.product_qty + forecasted_qty = mtso_free_qties_by_loc[move.location_id][move.product_id.id] + if float_compare(needed_qty, forecasted_qty, precision_rounding=product_id.uom_id.rounding) <= 0: + move.procure_method = 'make_to_stock' + mtso_free_qties_by_loc[move.location_id][move.product_id.id] -= needed_qty + else: + move.procure_method = 'make_to_order' diff --git a/addons/stock/tests/test_move2.py b/addons/stock/tests/test_move2.py index f037dce934a..6b7b822e0af 100644 --- a/addons/stock/tests/test_move2.py +++ b/addons/stock/tests/test_move2.py @@ -2390,6 +2390,175 @@ class TestRoutes(TestStockCommon): self.assertEqual(len(picking.move_lines), 1) self.assertEqual(picking.move_lines.product_uom_qty, 2) + def test_mtso_mto_adjust_01(self): + """ Run '_adjust_procure_method' for products A & B: + - Product A has 5.0 available + - Product B has 3.0 available + Stock moves (SM) are created for 4.0 units + After '_adjust_procure_method': + - SM for A is 'make_to_stock' + - SM for B is 'make_to_order' + """ + warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1) + final_location = self.partner.property_stock_customer + product_A = self.env['product.product'].create({ + 'name': 'Product A', + 'type': 'product', + }) + product_B = self.env['product.product'].create({ + 'name': 'Product B', + 'type': 'product', + }) + + # We alter one rule and we set it to 'mts_else_mto' + rule = self.env['procurement.group']._get_rule(product_A, final_location, {'warehouse_id': warehouse}) + rule.procure_method = 'mts_else_mto' + + self.env['stock.quant']._update_available_quantity(product_A, warehouse.lot_stock_id, 5.0) + self.env['stock.quant']._update_available_quantity(product_B, warehouse.lot_stock_id, 3.0) + + move_tmpl = { + 'name': 'Product', + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 4.0, + 'location_id': warehouse.lot_stock_id.id, + 'location_dest_id': self.partner.property_stock_customer.id, + 'warehouse_id': warehouse.id, + } + move_A_vals = dict(move_tmpl) + move_A_vals.update({ + 'product_id': product_A.id, + }) + move_A = self.env['stock.move'].create(move_A_vals) + move_B_vals = dict(move_tmpl) + move_B_vals.update({ + 'product_id': product_B.id, + }) + move_B = self.env['stock.move'].create(move_B_vals) + moves = move_A + move_B + + self.assertEqual(move_A.procure_method, 'make_to_stock', 'Move A should be "make_to_stock"') + self.assertEqual(move_B.procure_method, 'make_to_stock', 'Move A should be "make_to_order"') + moves._adjust_procure_method() + self.assertEqual(move_A.procure_method, 'make_to_stock', 'Move A should be "make_to_stock"') + self.assertEqual(move_B.procure_method, 'make_to_order', 'Move A should be "make_to_order"') + + def test_mtso_mto_adjust_02(self): + """ Run '_adjust_procure_method' for products A & B: + - Product A has 5.0 available + - Product B has 3.0 available + Stock moves (SM) are created for 2.0 + 2.0 units + After '_adjust_procure_method': + - SM for A is 'make_to_stock' + - SM for B is 'make_to_stock' and 'make_to_order' + """ + warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1) + final_location = self.partner.property_stock_customer + product_A = self.env['product.product'].create({ + 'name': 'Product A', + 'type': 'product', + }) + product_B = self.env['product.product'].create({ + 'name': 'Product B', + 'type': 'product', + }) + + # We alter one rule and we set it to 'mts_else_mto' + rule = self.env['procurement.group']._get_rule(product_A, final_location, {'warehouse_id': warehouse}) + rule.procure_method = 'mts_else_mto' + + self.env['stock.quant']._update_available_quantity(product_A, warehouse.lot_stock_id, 5.0) + self.env['stock.quant']._update_available_quantity(product_B, warehouse.lot_stock_id, 3.0) + + move_tmpl = { + 'name': 'Product', + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 2.0, + 'location_id': warehouse.lot_stock_id.id, + 'location_dest_id': self.partner.property_stock_customer.id, + 'warehouse_id': warehouse.id, + } + move_A1_vals = dict(move_tmpl) + move_A1_vals.update({ + 'product_id': product_A.id, + }) + move_A1 = self.env['stock.move'].create(move_A1_vals) + move_A2_vals = dict(move_tmpl) + move_A2_vals.update({ + 'product_id': product_A.id, + }) + move_A2 = self.env['stock.move'].create(move_A2_vals) + move_B1_vals = dict(move_tmpl) + move_B1_vals.update({ + 'product_id': product_B.id, + }) + move_B1 = self.env['stock.move'].create(move_B1_vals) + move_B2_vals = dict(move_tmpl) + move_B2_vals.update({ + 'product_id': product_B.id, + }) + move_B2 = self.env['stock.move'].create(move_B2_vals) + moves = move_A1 + move_A2 + move_B1 + move_B2 + + self.assertEqual(move_A1.procure_method, 'make_to_stock', 'Move A1 should be "make_to_stock"') + self.assertEqual(move_A2.procure_method, 'make_to_stock', 'Move A2 should be "make_to_stock"') + self.assertEqual(move_B1.procure_method, 'make_to_stock', 'Move B1 should be "make_to_stock"') + self.assertEqual(move_B2.procure_method, 'make_to_stock', 'Move B2 should be "make_to_stock"') + moves._adjust_procure_method() + self.assertEqual(move_A1.procure_method, 'make_to_stock', 'Move A1 should be "make_to_stock"') + self.assertEqual(move_A2.procure_method, 'make_to_stock', 'Move A2 should be "make_to_stock"') + self.assertEqual(move_B1.procure_method, 'make_to_stock', 'Move B1 should be "make_to_stock"') + self.assertEqual(move_B2.procure_method, 'make_to_order', 'Move B2 should be "make_to_order"') + + def test_mtso_mto_adjust_03(self): + """ Run '_adjust_procure_method' for products A with 4.0 available + 2 Stock moves (SM) are created: + - SM1 for 5.0 Units + - SM2 for 3.0 Units + SM1 is confirmed, so 'virtual_available' is -1.0. + SM1 should become 'make_to_order' + SM2 should remain 'make_to_stock' + """ + warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1) + final_location = self.partner.property_stock_customer + product_A = self.env['product.product'].create({ + 'name': 'Product A', + 'type': 'product', + }) + + # We alter one rule and we set it to 'mts_else_mto' + rule = self.env['procurement.group']._get_rule(product_A, final_location, {'warehouse_id': warehouse}) + rule.procure_method = 'mts_else_mto' + + self.env['stock.quant']._update_available_quantity(product_A, warehouse.lot_stock_id, 4.0) + + move_tmpl = { + 'name': 'Product', + 'product_id': product_A.id, + 'product_uom': self.uom_unit.id, + 'location_id': warehouse.lot_stock_id.id, + 'location_dest_id': self.partner.property_stock_customer.id, + 'warehouse_id': warehouse.id, + } + move_A1_vals = dict(move_tmpl) + move_A1_vals.update({ + 'product_uom_qty': 5.0, + }) + move_A1 = self.env['stock.move'].create(move_A1_vals) + move_A2_vals = dict(move_tmpl) + move_A2_vals.update({ + 'product_uom_qty': 3.0, + }) + move_A2 = self.env['stock.move'].create(move_A2_vals) + moves = move_A1 + move_A2 + + self.assertEqual(move_A1.procure_method, 'make_to_stock', 'Move A1 should be "make_to_stock"') + self.assertEqual(move_A2.procure_method, 'make_to_stock', 'Move A2 should be "make_to_stock"') + move_A1._action_confirm() + moves._adjust_procure_method() + self.assertEqual(move_A1.procure_method, 'make_to_order', 'Move A should be "make_to_stock"') + self.assertEqual(move_A2.procure_method, 'make_to_stock', 'Move A should be "make_to_order"') + def test_delay_alert_1(self): """ On a pick pack ship scenario, enable the delay alert flag on the pack rule. Edit the schedule date on the pick, a delay alert should be created for the ship. |
