diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py
index ff9e74f2605..88fa2af2375 100644
--- a/addons/mrp/models/mrp_production.py
+++ b/addons/mrp/models/mrp_production.py
@@ -282,20 +282,32 @@ class MrpProduction(models.Model):
for production in self:
production.mrp_production_backorder_count = len(production.procurement_group_id.mrp_production_ids)
- @api.depends('move_raw_ids', 'state', 'date_planned_start', 'move_raw_ids.forecast_availability', 'move_raw_ids.forecast_expected_date')
+ @api.depends('state', 'reservation_state', 'date_planned_start', 'move_raw_ids', 'move_raw_ids.forecast_availability', 'move_raw_ids.forecast_expected_date')
def _compute_components_availability(self):
- self.components_availability = False
- self.components_availability_state = 'available'
- productions = self.filtered(lambda mo: mo.state not in ['cancel', 'draft', 'done'])
- productions.components_availability = _('Available')
- for production in productions:
- forecast_date = max(production.move_raw_ids.filtered('forecast_expected_date').mapped('forecast_expected_date'), default=False)
- if any(float_compare(move.forecast_availability, move.product_qty, move.product_id.uom_id.rounding) == -1 for move in production.move_raw_ids):
+ productions = self.filtered(lambda mo: mo.state not in ('cancel', 'done', 'draft'))
+ productions.components_availability_state = 'available'
+ other_productions = self - productions
+ other_productions.components_availability = False
+ other_productions.components_availability_state = False
+
+ productions_ready = productions.filtered(lambda mo: mo.reservation_state == 'assigned')
+ productions_ready.components_availability = _('Ready')
+ productions_not_ready = (productions - productions_ready)
+ productions_not_ready.components_availability = _('Available')
+
+ all_raw_moves = productions_not_ready.move_raw_ids
+ # Force to prefetch more than 1000 by 1000
+ all_raw_moves._fields['forecast_availability'].compute_value(all_raw_moves)
+ for production in productions_not_ready:
+ if any(float_compare(move.forecast_availability, 0 if move.state == 'draft' else move.product_qty, move.product_id.uom_id.rounding) == -1 for move in production.move_raw_ids):
production.components_availability = _('Not Available')
production.components_availability_state = 'late'
- elif forecast_date:
- production.components_availability = _('Exp %s', format_date(self.env, forecast_date))
- production.components_availability_state = 'late' if forecast_date > production.date_planned_start else 'expected'
+ else:
+ forecast_date = max(production.move_raw_ids.filtered('forecast_expected_date').mapped('forecast_expected_date'), default=False)
+ if forecast_date:
+ production.components_availability = _('Exp %s', format_date(self.env, forecast_date))
+ if production.date_planned_start:
+ production.components_availability_state = 'late' if forecast_date > production.date_planned_start else 'expected'
@api.depends('move_finished_ids.date_deadline')
def _compute_date_deadline(self):
diff --git a/addons/mrp/tests/test_order.py b/addons/mrp/tests/test_order.py
index 442dc1949d1..ef8ec4a34c1 100644
--- a/addons/mrp/tests/test_order.py
+++ b/addons/mrp/tests/test_order.py
@@ -4,9 +4,10 @@
from odoo.tests import Form
from datetime import datetime, timedelta
-from odoo.fields import Datetime as Dt
+from odoo import fields
from odoo.exceptions import UserError
from odoo.addons.mrp.tests.common import TestMrpCommon
+from odoo.tools.misc import format_date
class TestMrpOrder(TestMrpCommon):
@@ -54,7 +55,7 @@ class TestMrpOrder(TestMrpCommon):
'inventory_quantity': 500
}).action_apply_inventory()
- test_date_planned = Dt.now() - timedelta(days=1)
+ test_date_planned = fields.Datetime.now() - timedelta(days=1)
test_quantity = 3.0
man_order_form = Form(self.env['mrp.production'].with_user(self.user_mrp_user))
man_order_form.product_id = self.product_4
@@ -1509,6 +1510,57 @@ class TestMrpOrder(TestMrpCommon):
self.assertEqual(mos.move_finished_ids.mapped('state'), ['done'] * 3)
self.assertEqual(mos.move_finished_ids.mapped('quantity_done'), [1] * 3)
+ def test_components_availability(self):
+ self.bom_2.unlink() # remove the kit bom of product_5
+ now = fields.Datetime.now()
+ mo_form = Form(self.env['mrp.production'])
+ mo_form.bom_id = self.bom_3 # product_5 (2), product_4 (8), product_2 (12)
+ mo_form.date_planned_start = now
+ mo = mo_form.save()
+ self.assertEqual(mo.components_availability, False) # no compute for draft
+ mo.action_confirm()
+ self.assertEqual(mo.components_availability, 'Not Available')
+
+ tommorrow = fields.Datetime.now() + timedelta(days=1)
+ after_tommorrow = fields.Datetime.now() + timedelta(days=2)
+ warehouse = self.env.ref('stock.warehouse0')
+ move1 = self._create_move(
+ self.product_5, self.env.ref('stock.stock_location_suppliers'), warehouse.lot_stock_id,
+ product_uom_qty=2, date=tommorrow
+ )
+ move2 = self._create_move(
+ self.product_4, self.env.ref('stock.stock_location_suppliers'), warehouse.lot_stock_id,
+ product_uom_qty=8, date=tommorrow
+ )
+ move3 = self._create_move(
+ self.product_2, self.env.ref('stock.stock_location_suppliers'), warehouse.lot_stock_id,
+ product_uom_qty=12, date=tommorrow
+ )
+ (move1 | move2 | move3)._action_confirm()
+
+ mo.invalidate_cache(['components_availability', 'components_availability_state'], mo.ids)
+ self.assertEqual(mo.components_availability, 'Exp %s' % format_date(self.env, tommorrow))
+ self.assertEqual(mo.components_availability_state, 'late')
+
+ mo.date_planned_start = after_tommorrow
+
+ self.assertEqual(mo.components_availability, 'Exp %s' % format_date(self.env, tommorrow))
+ self.assertEqual(mo.components_availability_state, 'expected')
+
+ (move1 | move2 | move3)._set_quantities_to_reservation()
+ (move1 | move2 | move3)._action_done()
+
+ mo.invalidate_cache(['components_availability', 'components_availability_state'], mo.ids)
+ self.assertEqual(mo.components_availability, 'Available')
+ self.assertEqual(mo.components_availability_state, 'available')
+
+ mo.action_assign()
+
+ self.assertEqual(mo.reservation_state, 'assigned')
+ self.assertEqual(mo.components_availability, 'Ready')
+ self.assertEqual(mo.components_availability_state, 'available')
+
+
def test_immediate_validate_6(self):
"""In a production for a tracked product, clicking on mark as done without filling any quantities should
pop up the immediate transfer wizard. Processing should choose a new lot for the finished product. """
@@ -1847,7 +1899,7 @@ class TestMrpOrder(TestMrpCommon):
})
# Next Monday at 6:00 am UTC
- date_planned = (Dt.now() + timedelta(days=7 - Dt.now().weekday())).replace(hour=6, minute=0, second=0)
+ date_planned = (fields.Datetime.now() + timedelta(days=7 - fields.Datetime.now().weekday())).replace(hour=6, minute=0, second=0)
mo_form = Form(self.env['mrp.production'])
mo_form.bom_id = bom
mo_form.date_planned_start = date_planned
diff --git a/addons/mrp/views/mrp_production_views.xml b/addons/mrp/views/mrp_production_views.xml
index 1898d66ec52..44cb55c7887 100644
--- a/addons/mrp/views/mrp_production_views.xml
+++ b/addons/mrp/views/mrp_production_views.xml
@@ -22,7 +22,14 @@
-
+
+
+
@@ -204,6 +211,11 @@
+
+
@@ -506,7 +518,7 @@
-
+
diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py
index 8f135895ee6..7d6a640aa45 100644
--- a/addons/stock/models/stock_picking.py
+++ b/addons/stock/models/stock_picking.py
@@ -431,20 +431,32 @@ class Picking(models.Model):
for picking in self:
picking.is_signed = picking.signature
- @api.depends('move_lines', 'state', 'picking_type_code', 'move_lines.forecast_availability', 'move_lines.forecast_expected_date')
+ @api.depends('state', 'picking_type_code', 'scheduled_date', 'move_lines', 'move_lines.forecast_availability', 'move_lines.forecast_expected_date')
def _compute_products_availability(self):
- self.products_availability = False
- self.products_availability_state = 'available'
- pickings = self.filtered(lambda picking: picking.state not in ['cancel', 'draft', 'done'] and picking.picking_type_code == 'outgoing')
- pickings.products_availability = _('Available')
+ pickings = self.filtered(lambda picking: picking.state in ('waiting', 'confirmed', 'assigned') and picking.picking_type_code == 'outgoing')
+ pickings.products_availability_state = 'available'
+ other_pickings = self - pickings
+ other_pickings.products_availability = False
+ other_pickings.products_availability_state = False
+
+ pickings_ready = pickings.filtered(lambda picking: picking.state == 'assigned')
+ pickings_ready.products_availability = _('Ready')
+ pickings_not_ready = pickings - pickings_ready
+ pickings_not_ready.products_availability = _('Available')
+
+ all_moves = pickings_not_ready.move_lines
+ # Force to prefetch more than 1000 by 1000
+ all_moves._fields['forecast_availability'].compute_value(all_moves)
for picking in pickings:
- forecast_date = max(picking.move_lines.filtered('forecast_expected_date').mapped('forecast_expected_date'), default=False)
- if any(float_compare(move.forecast_availability, move.product_qty, precision_rounding=move.product_id.uom_id.rounding) == -1 for move in picking.move_lines):
+ # In case of draft the behavior of forecast_availability is different : if forecast_availability < 0 then there is a issue else not.
+ if any(float_compare(move.forecast_availability, 0 if move.state == 'draft' else move.product_qty, precision_rounding=move.product_id.uom_id.rounding) == -1 for move in picking.move_lines):
picking.products_availability = _('Not Available')
picking.products_availability_state = 'late'
- elif forecast_date:
- picking.products_availability = _('Exp %s', format_date(self.env, forecast_date))
- picking.products_availability_state = 'late' if picking.date_deadline and picking.date_deadline < forecast_date else 'expected'
+ else:
+ forecast_date = max(picking.move_lines.filtered('forecast_expected_date').mapped('forecast_expected_date'), default=False)
+ if forecast_date:
+ picking.products_availability = _('Exp %s', format_date(self.env, forecast_date))
+ picking.products_availability_state = 'late' if picking.scheduled_date and picking.scheduled_date < forecast_date else 'expected'
@api.depends('picking_type_id.show_operations')
def _compute_show_operations(self):
diff --git a/addons/stock/views/stock_picking_views.xml b/addons/stock/views/stock_picking_views.xml
index a033886d978..05267039e7e 100644
--- a/addons/stock/views/stock_picking_views.xml
+++ b/addons/stock/views/stock_picking_views.xml
@@ -202,10 +202,17 @@
+
+
+
-
@@ -326,6 +333,12 @@
attrs="{'invisible': ['|', ('state', 'in', ('done', 'cancel')), ('date_deadline', '=', False)]}"
decoration-danger="date_deadline and date_deadline < current_date"
decoration-bf="date_deadline and date_deadline < current_date"/>
+
+