diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 5b2cfdc743b..7b269c210ef 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -403,11 +403,8 @@ class StockMove(models.Model): vals['state'] = 'assigned' return vals - @api.model - def _consuming_picking_types(self): - res = super()._consuming_picking_types() - res.append('mrp_operation') - return res + def _is_consuming(self): + return super()._is_consuming() or self.picking_type_id.code == 'mrp_operation' def _get_backorder_move_vals(self): self.ensure_one() diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index faa075dbfe9..8be6934a9cd 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -432,11 +432,11 @@ class StockMove(models.Model): warehouse_id = move.location_dest_id.warehouse_id.id if incoming else move.location_id.warehouse_id.id return warehouse_id, max(move.date, now) - # Prefetch efficiently virtual_available for _consuming_picking_types draft move. + # Prefetch efficiently virtual_available for _is_consuming draft move. prefetch_virtual_available = defaultdict(set) virtual_available_dict = {} for move in product_moves: - if move.picking_type_id.code in self._consuming_picking_types() and move.state == 'draft': + if move._is_consuming() and move.state == 'draft': prefetch_virtual_available[key_virtual_available(move)].add(move.product_id.id) elif move.picking_type_id.code == 'incoming': prefetch_virtual_available[key_virtual_available(move, incoming=True)].add(move.product_id.id) @@ -445,12 +445,12 @@ class StockMove(models.Model): virtual_available_dict[key_context] = {res['id']: res['virtual_available'] for res in read_res} for move in product_moves: - if move.picking_type_id.code in self._consuming_picking_types(): + if move._is_consuming(): if move.state == 'assigned': move.forecast_availability = move.product_uom._compute_quantity( move.reserved_availability, move.product_id.uom_id, rounding_method='HALF-UP') elif move.state == 'draft': - # for move _consuming_picking_types and in draft -> the forecast_availability > 0 if in stock + # for move _is_consuming and in draft -> the forecast_availability > 0 if in stock move.forecast_availability = virtual_available_dict[key_virtual_available(move)][move.product_id.id] - move.product_qty elif move.state in ('waiting', 'confirmed', 'partially_available'): outgoing_unreserved_moves_per_warehouse[move.location_id.warehouse_id].add(move.id) @@ -723,7 +723,7 @@ class StockMove(models.Model): 'active_model': 'product.product', 'move_to_match_ids': self.ids, } - if self.picking_type_id.code in self._consuming_picking_types(): + if self._is_consuming(): warehouse = self.location_id.warehouse_id else: warehouse = self.location_dest_id.warehouse_id @@ -1787,9 +1787,11 @@ class StockMove(models.Model): for state, moves_ids in moves_state_to_write.items(): self.browse(moves_ids).filtered(lambda m: m.state != state).state = state - @api.model - def _consuming_picking_types(self): - return ['outgoing'] + def _is_consuming(self): + self.ensure_one() + from_wh = self.location_id.warehouse_id + to_wh = self.location_dest_id.warehouse_id + return self.picking_type_id.code == 'outgoing' or (from_wh and to_wh and from_wh != to_wh) def _get_lang(self): """Determine language to use for translated description""" @@ -2087,6 +2089,8 @@ class StockMove(models.Model): unreconciled_outs.append((demand, out)) for demand, out in unreconciled_outs: - _reconcile_out_with_ins(result, out, ins_per_product[product.id], demand, product_rounding, only_matching_move_dest=False) + remaining = _reconcile_out_with_ins(result, out, ins_per_product[product.id], demand, product_rounding, only_matching_move_dest=False) + if not float_is_zero(remaining, precision_rounding=out.product_id.uom_id.rounding) and out not in result: + result[out] = (-remaining, False) return result diff --git a/addons/stock/tests/test_move.py b/addons/stock/tests/test_move.py index 1724c5d6480..4bac10b48b2 100644 --- a/addons/stock/tests/test_move.py +++ b/addons/stock/tests/test_move.py @@ -5723,3 +5723,21 @@ class StockMove(TransactionCase): line.qty_done = 1 self.assertEqual(picking.move_line_ids_without_package.location_dest_id, self.stock_location.child_ids[0]) + + def test_inter_wh_and_forecast_availability(self): + dest_wh = self.env['stock.warehouse'].create({ + 'name': 'Second Warehouse', + 'code': 'WH02', + }) + + move = self.env['stock.move'].create({ + 'name': 'test_interwh', + 'location_id': self.stock_location.id, + 'location_dest_id': dest_wh.lot_stock_id.id, + 'product_id': self.product.id, + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 1.0, + }) + self.assertEqual(move.forecast_availability, -1) + move._action_confirm() + self.assertEqual(move.forecast_availability, -1) diff --git a/addons/stock/tests/test_report.py b/addons/stock/tests/test_report.py index ad67df12749..01ccbcfc337 100644 --- a/addons/stock/tests/test_report.py +++ b/addons/stock/tests/test_report.py @@ -977,7 +977,7 @@ class TestReports(TestReportsCommon): receipt.action_confirm() # Test compute _compute_forecast_information - self.assertEqual(delivery.move_ids.forecast_availability, 0.0) + self.assertEqual(delivery.move_ids.forecast_availability, -100) self.assertEqual(delivery2.move_ids.forecast_availability, 200) self.assertFalse(delivery.move_ids.forecast_expected_date) self.assertEqual(delivery2.move_ids.forecast_expected_date, receipt.move_ids.date) @@ -1209,7 +1209,7 @@ class TestReports(TestReportsCommon): self.assertEqual(lines[3]['document_out'].id, delivery_manual.id) all_delivery = delivery_by_date | delivery_at_confirm | delivery_by_date_priority | delivery_manual - self.assertEqual(all_delivery.move_ids.mapped("forecast_availability"), [0, 0, 0, 0]) + self.assertEqual(all_delivery.move_ids.mapped("forecast_availability"), [-3.0, -3.0, -3.0, -3.0]) # Creation of one receipt to fulfill the 2 first deliveries delivery_by_date and delivery_at_confirm receipt_form = Form(self.env['stock.picking']) @@ -1222,7 +1222,7 @@ class TestReports(TestReportsCommon): receipt1 = receipt_form.save() receipt1.action_confirm() - self.assertEqual(all_delivery.move_ids.mapped("forecast_availability"), [3, 3, 0, 0]) + self.assertEqual(all_delivery.move_ids.mapped("forecast_availability"), [3, 3, -3.0, -3.0]) def test_report_reception_1_one_receipt(self): """ Create 2 deliveries and 1 receipt where some of the products being received