Skip to content

Commit 2a3a8a6

Browse files
committed
[FIX] stock: display the forecast symbol for inter-wh SM
To reproduce the issue: (Let WH01 be the existing warehouse) 1. In Settings, enable "Storage Locations" 2. Create a second warehouse WH02 3. Create a storable product P 4. Create a planned and internal transfer: - From: WH01/Stock - To: WH02/Stock - With: 1 x P 5. Save the transfer - Error01: the forecast symbol is green, it should be red 6. Confirm the transfer - Error02: idem Error 01: Because the picking type is internal, we don't consider the picking as a consuming one. Therefore, we don't reach the line that define the forecast availability as negative: https://github.com/odoo/odoo/blob/892232b5aef42dd2706cb9d1d027b1c463ca50dd/addons/stock/models/stock_move.py#L446-L448 Error 02: Fixing the first error is not enough. Thanks to the first correction, we reach this line: https://github.com/odoo/odoo/blob/892232b5aef42dd2706cb9d1d027b1c463ca50dd/addons/stock/models/stock_move.py#L449-L450 And we will then call another method to compute the forecast availability: https://github.com/odoo/odoo/blob/892232b5aef42dd2706cb9d1d027b1c463ca50dd/addons/stock/models/stock_move.py#L457-L463 However, `_get_forecast_availability_outgoing` won't define any forecast availability for the move (it won't find any quantity to fulfill the need). So, when getting the value (`forecast_info[move]`), we will a have the default values: https://github.com/odoo/odoo/blob/892232b5aef42dd2706cb9d1d027b1c463ca50dd/addons/stock/models/stock_move.py#L2035 where `result` is the dict returned by `_get_forecast_availability_outgoing`. Later on, when using the field `forecast_availability` to render the view: in case of an outgoing transfer, we use the forecast widget: https://github.com/odoo/odoo/blob/73ab94402878a16c34c0e131818ffc0d2e8da3da/addons/stock/views/stock_picking_views.xml#L393-L394 And it correctly works because we compare the forecast availability with the demand: https://github.com/odoo/odoo/blob/6eaa4a2ae3b12b244f4c4277ef9cbc172f492f0a/addons/stock/static/src/js/forecast_widget.js#L34 However, in case of an internal transfer, we don't use the forecast widget: https://github.com/odoo/odoo/blob/3a2ee95c3ddfa0b2cf9383772ba48ebf6d9d5bb2/addons/stock/views/stock_picking_views.xml#L388-L391 And, if `forecast_availability` is equal to zero, it should mean that there will be just enough stock to fulfill the need [1]. This explains why the symbol is green. So, the issue comes from the values returned by `_get_forecast_availability_outgoing`: it should not set the forecast availability to zero if there isn't any stock available. [1] Some tests need to be fixed to respect this definition of `forecast_availability` task-2822157 closes odoo#94292 X-original-commit: 7610664 Signed-off-by: Arnold Moyaux (arm) <[email protected]> Signed-off-by: Adrien Widart <[email protected]>
1 parent be47b61 commit 2a3a8a6

File tree

3 files changed

+32
-6
lines changed

3 files changed

+32
-6
lines changed

addons/stock/models/stock_move.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ def key_virtual_available(move, incoming=False):
436436
prefetch_virtual_available = defaultdict(set)
437437
virtual_available_dict = {}
438438
for move in product_moves:
439-
if move.picking_type_id.code in self._consuming_picking_types() and move.state == 'draft':
439+
if (move.picking_type_id.code in self._consuming_picking_types() or move._is_inter_wh()) and move.state == 'draft':
440440
prefetch_virtual_available[key_virtual_available(move)].add(move.product_id.id)
441441
elif move.picking_type_id.code == 'incoming':
442442
prefetch_virtual_available[key_virtual_available(move, incoming=True)].add(move.product_id.id)
@@ -445,7 +445,7 @@ def key_virtual_available(move, incoming=False):
445445
virtual_available_dict[key_context] = {res['id']: res['virtual_available'] for res in read_res}
446446

447447
for move in product_moves:
448-
if move.picking_type_id.code in self._consuming_picking_types():
448+
if move.picking_type_id.code in self._consuming_picking_types() or move._is_inter_wh():
449449
if move.state == 'assigned':
450450
move.forecast_availability = move.product_uom._compute_quantity(
451451
move.reserved_availability, move.product_id.uom_id, rounding_method='HALF-UP')
@@ -1185,6 +1185,12 @@ def _should_be_assigned(self):
11851185
self.ensure_one()
11861186
return bool(not self.picking_id and self.picking_type_id)
11871187

1188+
def _is_inter_wh(self):
1189+
self.ensure_one()
1190+
from_wh = self.location_id.warehouse_id
1191+
to_wh = self.location_dest_id.warehouse_id
1192+
return from_wh and to_wh and from_wh != to_wh
1193+
11881194
def _action_confirm(self, merge=True, merge_into=False):
11891195
""" Confirms stock move or put it in waiting if it's linked to another move.
11901196
:param: merge: According to this boolean, a newly confirmed move will be merged
@@ -2078,6 +2084,8 @@ def _reconcile_out_with_ins(result, out, ins, demand, product_rounding, only_mat
20782084
unreconciled_outs.append((demand, out))
20792085

20802086
for demand, out in unreconciled_outs:
2081-
_reconcile_out_with_ins(result, out, ins_per_product[product.id], demand, product_rounding, only_matching_move_dest=False)
2087+
remaining = _reconcile_out_with_ins(result, out, ins_per_product[product.id], demand, product_rounding, only_matching_move_dest=False)
2088+
if not float_is_zero(remaining, precision_rounding=out.product_id.uom_id.rounding) and out not in result:
2089+
result[out] = (-remaining, False)
20822090

20832091
return result

addons/stock/tests/test_move.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5723,3 +5723,21 @@ def test_SML_location_selection(self):
57235723
line.qty_done = 1
57245724

57255725
self.assertEqual(picking.move_line_ids_without_package.location_dest_id, self.stock_location.child_ids[0])
5726+
5727+
def test_inter_wh_and_forecast_availability(self):
5728+
dest_wh = self.env['stock.warehouse'].create({
5729+
'name': 'Second Warehouse',
5730+
'code': 'WH02',
5731+
})
5732+
5733+
move = self.env['stock.move'].create({
5734+
'name': 'test_interwh',
5735+
'location_id': self.stock_location.id,
5736+
'location_dest_id': dest_wh.lot_stock_id.id,
5737+
'product_id': self.product.id,
5738+
'product_uom': self.uom_unit.id,
5739+
'product_uom_qty': 1.0,
5740+
})
5741+
self.assertEqual(move.forecast_availability, -1)
5742+
move._action_confirm()
5743+
self.assertEqual(move.forecast_availability, -1)

addons/stock/tests/test_report.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -977,7 +977,7 @@ def test_report_forecast_8_delivery_to_receipt_link(self):
977977
receipt.action_confirm()
978978

979979
# Test compute _compute_forecast_information
980-
self.assertEqual(delivery.move_ids.forecast_availability, 0.0)
980+
self.assertEqual(delivery.move_ids.forecast_availability, -100)
981981
self.assertEqual(delivery2.move_ids.forecast_availability, 200)
982982
self.assertFalse(delivery.move_ids.forecast_expected_date)
983983
self.assertEqual(delivery2.move_ids.forecast_expected_date, receipt.move_ids.date)
@@ -1209,7 +1209,7 @@ def test_report_forecast_11_non_reserved_order(self):
12091209
self.assertEqual(lines[3]['document_out'].id, delivery_manual.id)
12101210

12111211
all_delivery = delivery_by_date | delivery_at_confirm | delivery_by_date_priority | delivery_manual
1212-
self.assertEqual(all_delivery.move_ids.mapped("forecast_availability"), [0, 0, 0, 0])
1212+
self.assertEqual(all_delivery.move_ids.mapped("forecast_availability"), [-3.0, -3.0, -3.0, -3.0])
12131213

12141214
# Creation of one receipt to fulfill the 2 first deliveries delivery_by_date and delivery_at_confirm
12151215
receipt_form = Form(self.env['stock.picking'])
@@ -1222,7 +1222,7 @@ def test_report_forecast_11_non_reserved_order(self):
12221222
receipt1 = receipt_form.save()
12231223
receipt1.action_confirm()
12241224

1225-
self.assertEqual(all_delivery.move_ids.mapped("forecast_availability"), [3, 3, 0, 0])
1225+
self.assertEqual(all_delivery.move_ids.mapped("forecast_availability"), [3, 3, -3.0, -3.0])
12261226

12271227
def test_report_reception_1_one_receipt(self):
12281228
""" Create 2 deliveries and 1 receipt where some of the products being received

0 commit comments

Comments
 (0)