summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRémy Voet (ryv) <ryv@odoo.com>2020-07-30 08:00:12 +0000
committerRémy Voet (ryv) <ryv@odoo.com>2020-08-18 07:48:27 +0000
commit57f805f71e9357870dfc2498c5ef72ebd8ab7273 (patch)
treec60b7e9f22ad71b8d0beed7f6d5898cdf7c87f31
parente40553723b893bcd2244e9b9fcc5c1a7e1bf27d3 (diff)
[IMP] stock*,mrp*: Dates Refactor
- The deadline of MO generated via procurements is calculated differently. Now, it doesn't take anymore in account the manufacturing_lead of the company (Security Lead Time of MO). The computation of planned date remains unchanged. - The date_expected fields was duplicated with the date fields except when the move was done. Merge both fields. The only information lost is: we can't know what was the scheduled date before processing move (`state` == done). Indeed, the date becomes the actual move processing datetime. - The `date` in `stock.picking` field contained the time of (purchase) order `date_order` (`purchase.order`). This field is was wrongly used in the kanban view where we expected to see date_planned instead. Also, the `_order` used this date instead of date_planned too. - The `delay_alert` is activate independently of stock rules. Then it is now activate in all case. - Remove the auto-reschedulting process of stock move via the stock rule (`propagate_date` and `propagate_date_minimum_delta`). Replace it by a automatic deadline date (`date_deadline`) propagation. The deadline is the promise done to/by vendor/client (SO/PO) then it is a readonly fields on picking/move and MO. - Now the Scheduled date (`date`) of stock move is never propagate and it is only related to the Scheduled date of related document (MO or picking). - Now when a move is created from procurement (sale), `date_planned` = `date_deadline` - `security_lead`. - The `delivery_date` of sale is no editable after confirmation and propagate as the deadline to related stock move linked to order_line - Adapt filter and decoration of MO and picking. - Because we are the client in case of purchase (PO), the promise of vendor can be not respected. Then we add the lead security to the deadline (inverse the sale order logic) of PO picking (promise reciept date + security lead) to match with the replenishment. task-2246665
-rw-r--r--addons/mrp/models/mrp_production.py69
-rw-r--r--addons/mrp/models/stock_rule.py8
-rw-r--r--addons/mrp/tests/test_procurement.py146
-rw-r--r--addons/mrp/views/mrp_production_views.xml33
-rw-r--r--addons/mrp_subcontracting/views/stock_move_views.xml1
-rw-r--r--addons/purchase/models/purchase.py2
-rw-r--r--addons/purchase_stock/models/purchase.py62
-rw-r--r--addons/purchase_stock/models/stock.py3
-rw-r--r--addons/purchase_stock/models/stock_rule.py9
-rw-r--r--addons/purchase_stock/tests/test_create_picking.py56
-rw-r--r--addons/purchase_stock/tests/test_purchase_lead_time.py75
-rw-r--r--addons/purchase_stock/tests/test_purchase_order.py10
-rw-r--r--addons/sale/models/sale.py5
-rw-r--r--addons/sale/views/sale_views.xml2
-rw-r--r--addons/sale_mrp/tests/test_sale_mrp_lead_time.py39
-rw-r--r--addons/sale_stock/models/sale_order.py19
-rw-r--r--addons/sale_stock/tests/test_sale_order_dates.py2
-rw-r--r--addons/sale_stock/tests/test_sale_stock.py2
-rw-r--r--addons/sale_stock/tests/test_sale_stock_lead_time.py181
-rw-r--r--addons/stock/data/stock_demo2.xml36
-rw-r--r--addons/stock/models/product.py20
-rw-r--r--addons/stock/models/stock_move.py110
-rw-r--r--addons/stock/models/stock_move_line.py2
-rw-r--r--addons/stock/models/stock_picking.py33
-rw-r--r--addons/stock/models/stock_rule.py32
-rw-r--r--addons/stock/models/stock_warehouse.py2
-rw-r--r--addons/stock/report/report_stock_forecasted.py14
-rw-r--r--addons/stock/report/report_stock_quantity.py4
-rw-r--r--addons/stock/static/src/xml/popover_widget.xml8
-rw-r--r--addons/stock/tests/test_move2.py176
-rw-r--r--addons/stock/tests/test_proc_rule.py85
-rw-r--r--addons/stock/tests/test_report.py6
-rw-r--r--addons/stock/views/stock_move_views.xml23
-rw-r--r--addons/stock/views/stock_picking_views.xml33
-rw-r--r--addons/stock/views/stock_rule_views.xml8
-rw-r--r--addons/stock/wizard/stock_picking_return.py2
-rw-r--r--addons/stock_dropshipping/tests/test_lifo_price.py2
37 files changed, 362 insertions, 958 deletions
diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py
index ca6f098bd11..21a41e59792 100644
--- a/addons/mrp/models/mrp_production.py
+++ b/addons/mrp/models/mrp_production.py
@@ -125,17 +125,15 @@ class MrpProduction(models.Model):
help="Location where the system will stock the finished products.")
date_planned_start = fields.Datetime(
'Scheduled Date', copy=False, default=_get_default_date_planned_start,
- compute='_compute_dates_planned', inverse='_set_date_planned_start',
help="Date at which you plan to start the production.",
- index=True, required=True, store=True)
+ index=True, required=True)
date_planned_finished = fields.Datetime(
'Scheduled End Date',
default=_get_default_date_planned_finished,
- compute='_compute_dates_planned', inverse='_set_date_planned_finished',
help="Date at which you plan to finish the production.",
- copy=False, store=True)
+ copy=False)
date_deadline = fields.Datetime(
- 'Deadline', copy=False, index=True,
+ 'Deadline', copy=False, store=True, readonly=True, compute='_compute_date_deadline', inverse='_set_date_deadline',
help="Informative date allowing to define when the manufacturing order should be processed at the latest to fulfill delivery on time.")
date_start = fields.Datetime('Start Date', copy=False, index=True, readonly=True)
date_finished = fields.Datetime('End Date', copy=False, index=True, readonly=True)
@@ -225,13 +223,8 @@ class MrpProduction(models.Model):
propagate_cancel = fields.Boolean(
'Propagate cancel and split',
help='If checked, when the previous move of the move (which was generated by a next procurement) is cancelled or split, the move generated by this move will too')
- delay_alert = fields.Boolean(string='Delay alert')
delay_alert_date = fields.Datetime('Delay Alert Date', compute='_compute_delay_alert_date', search='_search_delay_alert_date')
json_popover = fields.Char('JSON data for the popover widget', compute='_compute_json_popover')
- propagate_date = fields.Boolean(string="Propagate Rescheduling",
- help='The rescheduling is propagated to the next move.')
- propagate_date_minimum_delta = fields.Integer(string='Reschedule if Higher Than',
- help='The change must be higher than this value to be propagated')
scrap_ids = fields.One2many('stock.scrap', 'production_id', 'Scraps')
scrap_count = fields.Integer(compute='_compute_scrap_move_count', string='Scrap Move')
is_locked = fields.Boolean('Is Locked', default=_get_default_is_locked, copy=False)
@@ -314,25 +307,14 @@ class MrpProduction(models.Model):
production.components_availability = 'Not Available'
production.components_availability_state = 'late'
-
- @api.depends('move_raw_ids.date_expected', 'move_finished_ids.date_expected')
- def _compute_dates_planned(self):
+ @api.depends('move_finished_ids.date_deadline')
+ def _compute_date_deadline(self):
for production in self:
- if production.state != 'done':
- old_date = production.date_planned_start
- production.date_planned_start = max(production.mapped('move_raw_ids.date_expected') or [fields.Datetime.now()])
- if production.date_planned_start != old_date and production.is_planned:
- production._plan_workorders(replan=True)
- if production.move_finished_ids:
- production.date_planned_finished = max(production.mapped('move_finished_ids.date_expected'))
-
- def _set_date_planned_start(self):
- if self.date_planned_start:
- self.move_raw_ids.write({'date_expected': self.date_planned_start})
+ production.date_deadline = min(production.move_finished_ids.filtered('date_deadline').mapped('date_deadline'), default=production.date_deadline or False)
- def _set_date_planned_finished(self):
- if self.date_planned_finished:
- self.move_finished_ids.write({'date_expected': self.date_planned_finished})
+ def _set_date_deadline(self):
+ for production in self:
+ (self.move_raw_ids | self.move_finished_ids).date_deadline = production.date_deadline
def _compute_is_planned(self):
for production in self:
@@ -602,8 +584,8 @@ class MrpProduction(models.Model):
if date_planned_finished == self.date_planned_start:
date_planned_finished = date_planned_finished + relativedelta(hours=1)
self.date_planned_finished = date_planned_finished
- self.move_raw_ids = [(1, m.id, {'date_expected': self.date_planned_start, 'date': self.date_planned_start}) for m in self.move_raw_ids]
- self.move_finished_ids = [(1, m.id, {'date_expected': date_planned_finished, 'date': date_planned_finished}) for m in self.move_finished_ids]
+ self.move_raw_ids = [(1, m.id, {'date': self.date_planned_start}) for m in self.move_raw_ids]
+ self.move_finished_ids = [(1, m.id, {'date': date_planned_finished}) for m in self.move_finished_ids]
@api.onchange('bom_id', 'product_id', 'product_qty', 'product_uom_id')
def _onchange_move_raw(self):
@@ -702,18 +684,22 @@ class MrpProduction(models.Model):
raise UserError(_('You cannot move a manufacturing order once it is cancelled or done.'))
if production.is_partially_planned:
raise UserError(_('You cannot move a manufacturing order once it has a planned workorder, move related workorder(s) instead.'))
+ if vals.get('date_planned_start'):
+ production.move_raw_ids.write({'date': production.date_planned_start})
+ if vals.get('date_planned_finished'):
+ production.move_finished_ids.write({'date': production.date_planned_finished})
if any(field in ['move_raw_ids', 'move_finished_ids', 'workorder_ids'] for field in vals) and production.state != 'draft':
if production.state == 'done':
# for some reason moves added after state = 'done' won't save group_id, reference if added in
# "stock_move.default_get()"
- production.move_raw_ids.filtered(lambda move: move.additional and move.date_expected > production.date_planned_start).write({
+ production.move_raw_ids.filtered(lambda move: move.additional and move.date > production.date_planned_start).write({
'group_id': production.procurement_group_id.id,
'reference': production.name,
- 'date_expected': production.date_planned_start,
+ 'date': production.date_planned_start,
})
- production.move_finished_ids.filtered(lambda move: move.additional and move.date_expected > production.date_planned_finished).write({
+ production.move_finished_ids.filtered(lambda move: move.additional and move.date > production.date_planned_finished).write({
'reference': production.name,
- 'date_expected': production.date_planned_finished,
+ 'date': production.date_planned_finished,
})
production._autoconfirm_production()
if production in production_to_replan:
@@ -748,6 +734,8 @@ class MrpProduction(models.Model):
(production.move_raw_ids | production.move_finished_ids).write({
'group_id': production.procurement_group_id.id,
})
+ production.move_raw_ids.write({'date': production.date_planned_start})
+ production.move_finished_ids.write({'date': production.date_planned_finished})
# Trigger move_raw creation when importing a file
if 'import_file' in self.env.context:
production._onchange_move_raw()
@@ -817,7 +805,7 @@ class MrpProduction(models.Model):
'byproduct_id': byproduct_id,
'name': self.name,
'date': date_planned_finished,
- 'date_expected': date_planned_finished,
+ 'date_deadline': self.date_deadline,
'picking_type_id': self.picking_type_id.id,
'location_id': self.product_id.with_company(self.company_id).property_stock_production.id,
'location_dest_id': self.location_dest_id.id,
@@ -827,9 +815,6 @@ class MrpProduction(models.Model):
'origin': self.name,
'group_id': self.procurement_group_id.id,
'propagate_cancel': self.propagate_cancel,
- 'propagate_date': self.propagate_date,
- 'delay_alert': self.delay_alert,
- 'propagate_date_minimum_delta': self.propagate_date_minimum_delta,
'move_dest_ids': [(4, x.id) for x in move_dest_ids],
}
@@ -872,7 +857,7 @@ class MrpProduction(models.Model):
'sequence': bom_line.sequence if bom_line else 10,
'name': self.name,
'date': self.date_planned_start,
- 'date_expected': self.date_planned_start,
+ 'date_deadline': self.date_deadline,
'bom_line_id': bom_line.id if bom_line else False,
'picking_type_id': self.picking_type_id.id,
'product_id': product_id.id,
@@ -890,9 +875,6 @@ class MrpProduction(models.Model):
'warehouse_id': source_location.get_warehouse().id,
'group_id': self.procurement_group_id.id,
'propagate_cancel': self.propagate_cancel,
- 'propagate_date': self.propagate_date,
- 'propagate_date_minimum_delta': self.propagate_date_minimum_delta,
- 'delay_alert': self.delay_alert
}
return data
@@ -1101,11 +1083,6 @@ class MrpProduction(models.Model):
orders_to_plan = self.filtered(lambda order: not order.is_planned)
for order in orders_to_plan:
(order.move_raw_ids | order.move_finished_ids).filtered(lambda m: m.state == 'draft')._action_confirm()
- # `propagate_date` enables the automatic rescheduling which could lead to hard to
- # understand behavior if a manufacturing order is planned, i.e. if the work orders do
- # have their leaves booked in the workcenter calendar. We thus disable the
- # automatic rescheduling in this scenario.
- order.move_raw_ids.write({'propagate_date': False})
order._plan_workorders()
return True
diff --git a/addons/mrp/models/stock_rule.py b/addons/mrp/models/stock_rule.py
index 75d2df4a046..8aad49f3ad6 100644
--- a/addons/mrp/models/stock_rule.py
+++ b/addons/mrp/models/stock_rule.py
@@ -84,7 +84,8 @@ class StockRule(models.Model):
product=product_id, picking_type=self.picking_type_id, bom_type='normal', company_id=company_id.id)
def _prepare_mo_vals(self, product_id, product_qty, product_uom, location_id, name, origin, company_id, values, bom):
- date_deadline = fields.Datetime.to_string(self._get_date_planned(product_id, company_id, values))
+ date_planned = self._get_date_planned(product_id, company_id, values)
+ date_deadline = values.get('date_deadline') or date_planned + relativedelta(days=company_id.manufacturing_lead) # Remove manufacturing security of deadline
return {
'origin': origin,
'product_id': product_id.id,
@@ -95,12 +96,9 @@ class StockRule(models.Model):
'location_dest_id': location_id.id,
'bom_id': bom.id,
'date_deadline': date_deadline,
- 'date_planned_start': date_deadline,
+ 'date_planned_start': date_planned,
'procurement_group_id': False,
- 'delay_alert': self.delay_alert,
'propagate_cancel': self.propagate_cancel,
- 'propagate_date': self.propagate_date,
- 'propagate_date_minimum_delta': self.propagate_date_minimum_delta,
'orderpoint_id': values.get('orderpoint_id', False) and values.get('orderpoint_id').id,
'picking_type_id': self.picking_type_id.id or values['warehouse_id'].manu_type_id.id,
'company_id': company_id.id,
diff --git a/addons/mrp/tests/test_procurement.py b/addons/mrp/tests/test_procurement.py
index efb0e3581e9..ea62c577477 100644
--- a/addons/mrp/tests/test_procurement.py
+++ b/addons/mrp/tests/test_procurement.py
@@ -191,8 +191,8 @@ class TestProcurement(TestMrpCommon):
self.assertEqual(mo.move_raw_ids.reserved_availability, 5.0)
self.assertEqual(mo.move_raw_ids.quantity_done, 3.0)
- def test_date_propagation(self):
- """ Check propagation of shedule date for manufaturing route."""
+ def test_link_date_mo_moves(self):
+ """ Check link of shedule date for manufaturing with date stock move."""
# create a product with manufacture route
product_1 = self.env['product.product'].create({
@@ -220,8 +220,6 @@ class TestProcurement(TestMrpCommon):
'name': 'move_orig',
'product_id': product_1.id,
'product_uom': self.ref('uom.product_uom_unit'),
- 'propagate_date': True,
- 'propagate_date_minimum_delta': 1,
'location_id': self.ref('stock.stock_location_stock'),
'location_dest_id': self.ref('stock.stock_location_output'),
'product_uom_qty': 10,
@@ -234,7 +232,7 @@ class TestProcurement(TestMrpCommon):
('state', '=', 'confirmed')
])
- self.assertAlmostEqual(mo.move_finished_ids.date_expected, mo.move_raw_ids.date_expected + timedelta(hours=1), delta=timedelta(seconds=1))
+ self.assertAlmostEqual(mo.move_finished_ids.date, mo.move_raw_ids.date + timedelta(hours=1), delta=timedelta(seconds=1))
self.assertEqual(len(mo), 1, 'the manufacture order is not created')
@@ -251,75 +249,13 @@ class TestProcurement(TestMrpCommon):
self.assertEqual(len(move_orig), 1, 'the move orig is not created')
self.assertEqual(move_orig.product_qty, 10, 'the quantity to produce is not good relative to the move')
- move_dest_scheduled_date = move_dest.date_expected
-
+ new_sheduled_date = fields.Datetime.to_datetime(mo.date_planned_start) + timedelta(days=5)
mo_form = Form(mo)
- mo_form.date_planned_start = fields.Datetime.to_datetime(mo_form.date_planned_start) + timedelta(days=5)
- mo_form.save()
-
- self.assertAlmostEqual(move_dest.date_expected, move_dest_scheduled_date + timedelta(days=5), delta=timedelta(seconds=1), msg='date is not propagated')
-
- def test_planning_update(self):
- """ Test the delay propagation from a child bom to a source bom."""
- finished = self.env['product.product'].create({
- 'name': 'finished',
- 'type': 'product',
- })
- warehouse = self.env['stock.warehouse'].search([], limit=1)
- mto_route = warehouse.mto_pull_id.route_id
- mto_route.active = True
- inter = self.env['product.product'].create({
- 'name': 'inter',
- 'type': 'product',
- 'route_ids': [
- (4, self.ref('mrp.route_warehouse0_manufacture')),
- (4, mto_route.id)
- ],
- })
- component = self.env['product.product'].create({
- 'name': 'component',
- 'type': 'product',
- })
- source_bom = self.env['mrp.bom'].create({
- 'product_id': finished.id,
- 'product_tmpl_id': finished.product_tmpl_id.id,
- 'product_uom_id': self.uom_unit.id,
- 'product_qty': 1.0,
- 'type': 'normal',
- 'bom_line_ids': [
- (0, 0, {'product_id': inter.id, 'product_qty': 1}),
- ],
- 'operation_ids': [
- (0, 0, {'name': 'source operation', 'workcenter_id': self.workcenter_1.id, 'time_cycle': 12, 'sequence': 1}),
- ],
-
- })
- child_bom = self.env['mrp.bom'].create({
- 'product_id': inter.id,
- 'product_tmpl_id': inter.product_tmpl_id.id,
- 'product_uom_id': self.uom_unit.id,
- 'product_qty': 1.0,
- 'type': 'normal',
- 'bom_line_ids': [
- (0, 0, {'product_id': component.id, 'product_qty': 1}),
- ],
- 'operation_ids': [
- (0, 0, {'name': 'child operation', 'workcenter_id': self.workcenter_1.id, 'time_cycle': 12, 'sequence': 1}),
- ],
- })
- mo_form = Form(self.env['mrp.production'])
- mo_form.product_id = finished
+ mo_form.date_planned_start = new_sheduled_date
mo = mo_form.save()
- mo.action_confirm()
- self.assertEqual(mo.mrp_production_child_count, 1)
- mo.button_plan()
- old_wo_date = mo.workorder_ids.date_planned_start
- child = mo.procurement_group_id.stock_move_ids.created_production_id.procurement_group_id.mrp_production_ids
- child_form = Form(child)
- child_form.date_planned_start = datetime.now() + timedelta(days=10)
- child = child_form.save()
- new_wo_date = mo.workorder_ids.date_planned_start
- self.assertFalse(old_wo_date == new_wo_date)
+
+ self.assertAlmostEqual(mo.move_raw_ids.date, mo.date_planned_start, delta=timedelta(seconds=1))
+ self.assertAlmostEqual(mo.move_finished_ids.date, mo.date_planned_finished, delta=timedelta(seconds=1))
def test_finished_move_cancellation(self):
"""Check state of finished move on cancellation of raw moves. """
@@ -346,8 +282,6 @@ class TestProcurement(TestMrpCommon):
'name': 'move_bottle',
'product_id': product_bottle.id,
'product_uom': self.ref('uom.product_uom_unit'),
- 'propagate_date': True,
- 'propagate_date_minimum_delta': 1,
'location_id': self.ref('stock.stock_location_stock'),
'location_dest_id': self.ref('stock.stock_location_output'),
'product_uom_qty': 10,
@@ -364,70 +298,6 @@ class TestProcurement(TestMrpCommon):
self.assertEqual(mo.move_finished_ids[0].state, 'cancel', 'Finished move should be cancelled if mo is cancelled.')
self.assertEqual(mo.move_dest_ids[0].state, 'waiting', 'Destination move should not be cancelled if prapogation cancel is False on manufacturing rule.')
- def test_production_delay_alert(self):
- """Check if the delay alert is set to True on the stock move."""
- self.env['stock.rule'].search([]).delay_alert = True
- self.env.ref('stock.route_warehouse0_mto').active = True
- parent_product = self.env['product.template'].create({
- 'name': 'Parent product',
- 'route_ids': [
- (4, self.env.ref('mrp.route_warehouse0_manufacture').id, 0),
- (4, self.env.ref('stock.route_warehouse0_mto').id, 0)
- ]
- })
- child_product = self.env['product.template'].create({
- 'name': 'Child product',
- 'route_ids': [
- (4, self.env.ref('mrp.route_warehouse0_manufacture').id, 0),
- (4, self.env.ref('stock.route_warehouse0_mto').id, 0)
- ]
- })
- child_component = self.env['product.template'].create({
- 'name': 'Child product',
- })
- parent_bom = self.env['mrp.bom'].create({
- 'product_id': parent_product.product_variant_ids.id,
- 'product_tmpl_id': parent_product.id,
- 'product_uom_id': self.uom_unit.id,
- 'product_qty': 4.0,
- 'operation_ids': [
- (0, 0, {'name': 'Cutting Machine', 'workcenter_id': self.workcenter_1.id, 'time_cycle': 12, 'sequence': 1}),
- (0, 0, {'name': 'Weld Machine', 'workcenter_id': self.workcenter_1.id, 'time_cycle': 18, 'sequence': 2}),
- ],
- 'type': 'normal',
- })
- self.env['mrp.bom.line'].create({
- 'bom_id': parent_bom.id,
- 'product_id': child_product.product_variant_ids.id,
- 'product_qty': 2,
- })
- child_bom = self.env['mrp.bom'].create({
- 'product_id': child_product.product_variant_ids.id,
- 'product_tmpl_id': child_product.id,
- 'product_uom_id': self.uom_unit.id,
- 'product_qty': 4.0,
- 'operation_ids': [
- (0, 0, {'name': 'Cutting Machine', 'workcenter_id': self.workcenter_1.id, 'time_cycle': 12, 'sequence': 1}),
- (0, 0, {'name': 'Weld Machine', 'workcenter_id': self.workcenter_1.id, 'time_cycle': 18, 'sequence': 2}),
- ],
- 'type': 'normal',
- })
- self.env['mrp.bom.line'].create({
- 'bom_id': child_bom.id,
- 'product_id': child_component.product_variant_ids.id,
- 'product_qty': 2,
- })
-
- mrp_order_form = Form(self.env['mrp.production'])
- mrp_order_form.product_id = parent_product.product_variant_ids
- mrp_order = mrp_order_form.save()
- mrp_order.action_confirm()
-
- child_mrp_production = self.env['mrp.production'].search([('product_id', '=', child_product.product_variant_ids.id)])
-
- self.assertTrue(child_mrp_production.delay_alert)
- self.assertTrue(child_mrp_production.move_raw_ids.delay_alert)
-
def test_procurement_with_empty_bom(self):
"""Ensure that a procurement request using a product with an empty BoM
will create a MO in draft state that could be completed afterwards.
diff --git a/addons/mrp/views/mrp_production_views.xml b/addons/mrp/views/mrp_production_views.xml
index f62ea932cd8..c0a86603c16 100644
--- a/addons/mrp/views/mrp_production_views.xml
+++ b/addons/mrp/views/mrp_production_views.xml
@@ -14,8 +14,7 @@
<field name="priority" optional="show" widget="priority" nolabel="1"/>
<field name="message_needaction" invisible="1"/>
<field name="name" decoration-bf="1"/>
- <field name="date_planned_start" readonly="1" optional="show"
- attrs="{'readonly': [('state', 'in', ['done', 'cancel'])]}" decoration-danger="date_planned_start&lt;now and state not in ('done','cancel')"/>
+ <field name="date_planned_start" readonly="1" optional="show" widget="remaining_days"/>
<field name="date_deadline" widget="remaining_days" attrs="{'invisible': [('state', 'in', ['done', 'cancel'])]}" optional="hide"/>
<field name="product_id" readonly="1" optional="show"/>
<field name="product_uom_id" string="Unit of Measure" options="{'no_open':True,'no_create':True}" groups="uom.group_uom" optional="show"/>
@@ -178,7 +177,10 @@
<label for="date_planned_start"/>
<div class="o_row">
<field name="date_planned_start"
- attrs="{'readonly': ['|', ('is_partially_planned', '=', True), ('state', 'in', ['close', 'cancel'])]}"/>
+ attrs="{'readonly': ['|', ('is_partially_planned', '=', True), ('state', 'in', ['close', 'cancel'])]}"
+ decoration-warning="state not in ('done', 'cancel') and date_planned_start &lt; now"
+ decoration-danger="state not in ('done', 'cancel') and date_planned_start &lt; current_date"
+ decoration-bf="state not in ('done', 'cancel') and (date_planned_start &lt; current_date or date_planned_start &lt; now)"/>
<field name="delay_alert_date" invisible="1"/>
<field string=" " name="json_popover" widget="stock_rescheduling_popover" attrs="{'invisible': [('delay_alert_date', '=', False)]}"/>
</div>
@@ -194,8 +196,7 @@
<field name="operation_id"/>
<field name="byproduct_id"/>
<field name="name"/>
- <field name="date"/>
- <field name="date_expected"/>
+ <field name="date_deadline"/>
<field name="picking_type_id"/>
<field name="location_id"/>
<field name="location_dest_id"/>
@@ -203,10 +204,7 @@
<field name="warehouse_id"/>
<field name="origin"/>
<field name="group_id"/>
- <field name="propagate_date"/>
<field name="propagate_cancel"/>
- <field name="delay_alert"/>
- <field name="propagate_date_minimum_delta"/>
<field name="move_dest_ids"/>
<field name="state"/>
<!-- Useless as the editable in tree declaration -> For Form Test-->
@@ -219,10 +217,11 @@
<notebook>
<page string="Components" name="components">
<field name="move_raw_ids"
- context="{'default_product_uom_qty': 1.0, 'default_date_expected': date_planned_start, 'default_location_id': location_src_id, 'default_location_dest_id': production_location_id, 'default_state': 'draft', 'default_raw_material_production_id': id, 'default_picking_type_id': picking_type_id, 'default_company_id': company_id}"
+ context="{'default_product_uom_qty': 1.0, 'default_date': date_planned_start, 'default_date_deadline': date_deadline, 'default_location_id': location_src_id, 'default_location_dest_id': production_location_id, 'default_state': 'draft', 'default_raw_material_production_id': id, 'default_picking_type_id': picking_type_id, 'default_company_id': company_id}"
attrs="{'readonly': ['|', ('state', '=', 'cancel'), '&amp;', ('state', '=', 'done'), ('is_locked', '=', True)]}" options="{'delete': [('state', '=', 'draft')]}">
<tree default_order="is_done,sequence" editable="bottom">
<field name="product_id" force_save="1" required="1" context="{'default_type': 'product'}" attrs="{'readonly': ['|', '|', ('has_move_lines', '=', True), ('state', '=', 'cancel'), '&amp;', ('state', '!=', 'draft'), ('additional', '=', False) ]}"/>
+
<field name="move_line_ids" invisible="1">
<tree>
<field name="lot_id" invisible="1"/>
@@ -246,8 +245,8 @@
<field name="name" invisible="1"/>
<field name="allowed_operation_ids" invisible="1"/>
<field name="unit_factor" invisible="1"/>
+ <field name="date_deadline" invisible="1"/>
<field name="date" invisible="1"/>
- <field name="date_expected" invisible="1"/>
<field name="additional" invisible="1"/>
<field name="picking_type_id" invisible="1"/>
<field name="has_tracking" invisible="1"/>
@@ -278,7 +277,7 @@
<page string="Work Orders" name="operations" groups="mrp.group_mrp_routings">
<field name="workorder_ids" attrs="{'readonly': [('state', 'in', ['cancel', 'done'])]}" context="{'tree_view_ref': 'mrp.mrp_production_workorder_tree_editable_view', 'form_view_ref': 'mrp.mrp_production_workorder_form_view_inherit', 'default_production_id': id, 'default_product_uom_id': product_uom_id, 'default_consumption': consumption, 'default_company_id': company_id}"/> </page>
<page string="By-Products" name="finished_products" groups="mrp.group_mrp_byproducts">
- <field name="move_byproduct_ids" context="{'default_location_id': production_location_id, 'default_location_dest_id': location_src_id, 'default_state': 'draft', 'default_production_id': id, 'default_picking_type_id': picking_type_id, 'default_company_id': company_id}" attrs="{'readonly': ['|', ('state', '=', 'cancel'), '&amp;', ('state', '=', 'done'), ('is_locked', '=', True)]}" options="{'delete': [('state', '=', 'draft')]}">
+ <field name="move_byproduct_ids" context="{'default_date': date_planned_finished, 'default_date_deadline': date_deadline, 'default_location_id': production_location_id, 'default_location_dest_id': location_src_id, 'default_state': 'draft', 'default_production_id': id, 'default_picking_type_id': picking_type_id, 'default_company_id': company_id}" attrs="{'readonly': ['|', ('state', '=', 'cancel'), '&amp;', ('state', '=', 'done'), ('is_locked', '=', True)]}" options="{'delete': [('state', '=', 'draft')]}">
<tree default_order="is_done,sequence" decoration-muted="is_done" editable="bottom">
<field name="product_id" context="{'default_type': 'product'}" domain="[('id', '!=', parent.product_id)]" required="1"/>
@@ -306,7 +305,6 @@
<field name="allowed_operation_ids" invisible="1"/>
<field name="unit_factor" invisible="1"/>
<field name="date" invisible="1"/>
- <field name="date_expected" invisible="1"/>
<field name="additional" invisible="1"/>
<field name="picking_type_id" invisible="1"/>
<field name="has_tracking" invisible="1"/>
@@ -337,8 +335,11 @@
<field name="location_dest_id" groups="stock.group_stock_multi_locations" options="{'no_create': True}" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
</group>
<group>
- <field name="date_deadline"/>
<field name="origin"/>
+ <field name="date_deadline"
+ attrs="{'invisible': ['|', ('state', 'in', ('done', 'cancel')), ('date_deadline', '=', False)]}"
+ decoration-danger="date_deadline and date_deadline &lt; current_date"
+ decoration-bf="date_deadline and date_deadline &lt; current_date"/>
</group>
</group>
</page>
@@ -362,7 +363,7 @@
<field name="product_id"/>
<field name="product_qty"/>
<field name="product_uom_id" options="{'no_open':True,'no_create':True}"/>
- <field name="date_deadline"/>
+ <field name="date_planned_start"/>
<field name="state"/>
<field name="activity_state"/>
<progressbar field="activity_state" colors='{"planned": "success", "today": "warning", "overdue": "danger"}'/>
@@ -378,7 +379,7 @@
</div>
<div class="o_kanban_record_bottom">
<div class="oe_kanban_bottom_left text-muted">
- <span><t t-esc="record.name.value"/> <t t-esc="record.date_deadline.value and record.date_deadline.value.split(' ')[0] or False"/></span>
+ <span><t t-esc="record.name.value"/> <t t-esc="record.date_planned_start.value and record.date_planned_start.value.split(' ')[0] or False"/></span>
<field name="activity_ids" widget="kanban_activity"/>
<field name="delay_alert_date" invisible="1"/>
<field string=" " name="json_popover" widget="stock_rescheduling_popover" attrs="{'invisible': [('delay_alert_date', '=', False)]}"/>
@@ -422,7 +423,7 @@
<field name="model">mrp.production</field>
<field name="arch" type="xml">
<pivot string="Manufacturing Orders">
- <field name="date_deadline" type="row"/>
+ <field name="date_planned_start" type="row"/>
</pivot>
</field>
</record>
diff --git a/addons/mrp_subcontracting/views/stock_move_views.xml b/addons/mrp_subcontracting/views/stock_move_views.xml
index e2c52941da4..e5019810ff1 100644
--- a/addons/mrp_subcontracting/views/stock_move_views.xml
+++ b/addons/mrp_subcontracting/views/stock_move_views.xml
@@ -86,7 +86,6 @@
<field name="name" invisible="1"/>
<field name="unit_factor" invisible="1"/>
<field name="date" invisible="1"/>
- <field name="date_expected" invisible="1"/>
<field name="picking_type_id" invisible="1"/>
<field name="has_tracking" invisible="1"/>
<field name="operation_id" invisible="1"/>
diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py
index 137357dd7af..9678848ca7e 100644
--- a/addons/purchase/models/purchase.py
+++ b/addons/purchase/models/purchase.py
@@ -758,7 +758,7 @@ class PurchaseOrderLine(models.Model):
sequence = fields.Integer(string='Sequence', default=10)
product_qty = fields.Float(string='Quantity', digits='Product Unit of Measure', required=True)
product_uom_qty = fields.Float(string='Total Quantity', compute='_compute_product_uom_qty', store=True)
- date_planned = fields.Datetime(string='Scheduled Date', index=True,
+ date_planned = fields.Datetime(string='Delivery Date', index=True,
help="Delivery date expected from vendor. This date respectively defaults to vendor pricelist lead time then today's date.")
taxes_id = fields.Many2many('account.tax', string='Taxes', domain=['|', ('active', '=', False), ('active', '=', True)])
product_uom = fields.Many2one('uom.uom', string='Unit of Measure', domain="[('category_id', '=', product_uom_category_id)]")
diff --git a/addons/purchase_stock/models/purchase.py b/addons/purchase_stock/models/purchase.py
index de4dd55e33c..e68c8cddafb 100644
--- a/addons/purchase_stock/models/purchase.py
+++ b/addons/purchase_stock/models/purchase.py
@@ -3,7 +3,7 @@
from odoo import api, fields, models, SUPERUSER_ID, _
from odoo.tools.float_utils import float_compare
from datetime import datetime
-from dateutil import relativedelta
+from dateutil.relativedelta import relativedelta
from odoo.exceptions import UserError
from odoo.addons.purchase.models.purchase import PurchaseOrder as Purchase
@@ -88,14 +88,6 @@ class PurchaseOrder(models.Model):
to_log[order_line] = (order_line.product_qty, pre_order_line_qty[order_line])
if to_log:
order._log_decrease_ordered_quantity(to_log)
- # if date_planned value is set, use this to set move line expected dates
- if vals.get('date_planned'):
- new_date = fields.Datetime.to_datetime(vals['date_planned'])
- # let's make sure it's not "no date"
- if new_date:
- order_lines_to_update = self.order_line.filtered(lambda l: l.propagate_date)
- for order_line in order_lines_to_update:
- order_line._update_move_expected_date(new_date)
return res
# --------------------------------------------------
@@ -243,7 +235,7 @@ class PurchaseOrder(models.Model):
moves = order.order_line._create_stock_moves(picking)
moves = moves.filtered(lambda x: x.state not in ('done', 'cancel'))._action_confirm()
seq = 0
- for move in sorted(moves, key=lambda move: move.date_expected):
+ for move in sorted(moves, key=lambda move: move.date):
seq += 5
move.sequence = seq
moves._action_assign()
@@ -291,10 +283,7 @@ class PurchaseOrderLine(models.Model):
move_ids = fields.One2many('stock.move', 'purchase_line_id', string='Reservation', readonly=True, copy=False)
orderpoint_id = fields.Many2one('stock.warehouse.orderpoint', 'Orderpoint')
move_dest_ids = fields.One2many('stock.move', 'created_purchase_line_id', 'Downstream Moves')
- delay_alert = fields.Boolean(string='Delay alert')
product_description_variants = fields.Char('Custom Description')
- propagate_date = fields.Boolean(string="Propagate Rescheduling", help='The rescheduling is propagated to the next move.')
- propagate_date_minimum_delta = fields.Integer(string='Reschedule if Higher Than', help='The change must be higher than this value to be propagated')
propagate_cancel = fields.Boolean('Propagate cancellation', default=True)
def _compute_qty_received_method(self):
@@ -338,33 +327,15 @@ class PurchaseOrderLine(models.Model):
@api.model_create_multi
def create(self, vals_list):
lines = super(PurchaseOrderLine, self).create(vals_list)
-
- # when no "propagate_date", find if the product has a route with a buy rule,
- # if yes, use the setting on the rule, if no, set "True" as default.
- for line, vals in zip(lines, vals_list):
- if 'propagate_date' not in vals:
- buy_rules = line.product_id.route_ids.rule_ids.filtered(lambda r: r.action == "buy")
- if buy_rules:
- rule = min(buy_rules, key=lambda r: (r.route_id.sequence, r.sequence))
- line.write({
- 'propagate_date': rule.propagate_date,
- 'propagate_date_minimum_delta': rule.propagate_date_minimum_delta,
- })
- else:
- line.write({
- 'propagate_date': True,
- 'propagate_date_minimum_delta': 0,
- })
-
lines.filtered(lambda l: l.order_id.state == 'purchase')._create_or_update_picking()
return lines
def write(self, values):
for line in self.filtered(lambda l: not l.display_type):
# PO date_planned overrides any PO line date_planned values
- if values.get('date_planned') and line.propagate_date:
+ if values.get('date_planned'):
new_date = fields.Datetime.to_datetime(values['date_planned'])
- self._update_move_expected_date(new_date)
+ self._update_move_date_deadline(new_date)
result = super(PurchaseOrderLine, self).write(values)
if 'product_qty' in values:
self.filtered(lambda l: l.order_id.state == 'purchase')._create_or_update_picking()
@@ -374,15 +345,13 @@ class PurchaseOrderLine(models.Model):
# Business methods
# --------------------------------------------------
- def _update_move_expected_date(self, new_date):
- """ Updates corresponding move picking line expected dates that are not yet completed. """
+ def _update_move_date_deadline(self, new_date):
+ """ Updates corresponding move picking line deadline dates that are not yet completed. """
moves_to_update = self.move_ids.filtered(lambda m: m.state not in ('done', 'cancel'))
if not moves_to_update:
moves_to_update = self.move_dest_ids.filtered(lambda m: m.state not in ('done', 'cancel'))
for move in moves_to_update:
- delta_days = (new_date - move.date_expected).total_seconds() / 86400
- if abs(delta_days) >= self.propagate_date_minimum_delta:
- move.date_expected = move.date_expected + relativedelta.relativedelta(days=delta_days)
+ move.date_deadline = new_date + relativedelta(days=move.company_id.po_lead)
def _create_or_update_picking(self):
for line in self:
@@ -472,13 +441,14 @@ class PurchaseOrderLine(models.Model):
description_picking = product._get_description(self.order_id.picking_type_id)
if self.product_description_variants:
description_picking += self.product_description_variants
+ date_planned = self.date_planned or self.order_id.date_planned
return {
# truncate to 2000 to avoid triggering index limit error
# TODO: remove index in master?
'name': (self.name or '')[:2000],
'product_id': self.product_id.id,
- 'date': self.order_id.date_order,
- 'date_expected': self.order_id.date_planned or self.date_planned,
+ 'date': date_planned,
+ 'date_deadline': date_planned + relativedelta(days=self.order_id.company_id.po_lead),
'location_id': self.order_id.partner_id.property_stock_supplier.id,
'location_dest_id': (self.orderpoint_id and not (self.move_ids | self.move_dest_ids)) and self.orderpoint_id.location_id.id or self.order_id._get_destination_location(),
'picking_id': picking.id,
@@ -491,11 +461,8 @@ class PurchaseOrderLine(models.Model):
'picking_type_id': self.order_id.picking_type_id.id,
'group_id': self.order_id.group_id.id,
'origin': self.order_id.name,
- 'propagate_date': self.order_id.picking_type_id.warehouse_id.buy_pull_id.propagate_date if not self.move_dest_ids else self.propagate_date,
- 'propagate_date_minimum_delta': self.propagate_date_minimum_delta,
'description_picking': description_picking,
'propagate_cancel': self.propagate_cancel,
- 'delay_alert': self.delay_alert,
'route_ids': self.order_id.picking_type_id.warehouse_id and [(6, 0, [x.id for x in self.order_id.picking_type_id.warehouse_id.route_ids])] or [],
'warehouse_id': self.order_id.picking_type_id.warehouse_id.id,
'product_uom_qty': product_uom_qty,
@@ -513,9 +480,6 @@ class PurchaseOrderLine(models.Model):
res['move_dest_ids'] = [(4, x.id) for x in values.get('move_dest_ids', [])]
res['orderpoint_id'] = values.get('orderpoint_id', False) and values.get('orderpoint_id').id
res['propagate_cancel'] = values.get('propagate_cancel')
- res['delay_alert'] = values.get('delay_alert')
- res['propagate_date'] = values.get('propagate_date')
- res['propagate_date_minimum_delta'] = values.get('propagate_date_minimum_delta')
res['product_description_variants'] = values.get('product_description_variants')
return res
@@ -541,9 +505,7 @@ class PurchaseOrderLine(models.Model):
if values.get('product_description_variants'):
description_picking += values['product_description_variants']
lines = self.filtered(
- lambda l: l.propagate_date == values['propagate_date'] and
- l.propagate_date_minimum_delta == values['propagate_date_minimum_delta'] and
- l.propagate_cancel == values['propagate_cancel'] and
+ lambda l: l.propagate_cancel == values['propagate_cancel'] and
((values['orderpoint_id'] and not values['move_dest_ids']) and l.orderpoint_id == values['orderpoint_id'] or True) and
(not values.get('product_description_variants') or l.name == description_picking))
return lines and lines[0] or self.env['purchase.order.line']
@@ -565,7 +527,7 @@ class PurchaseOrderLine(models.Model):
move_to_update = self.move_ids.filtered(lambda m: m.state not in ['done', 'cancel'])
if move_to_update:
super()._update_date_planned(updated_date)
- self._update_move_expected_date(updated_date)
+ self._update_move_date_deadline(updated_date)
@api.model
def _update_qty_received_method(self):
diff --git a/addons/purchase_stock/models/stock.py b/addons/purchase_stock/models/stock.py
index 0577d5a10f4..89e46abbe1f 100644
--- a/addons/purchase_stock/models/stock.py
+++ b/addons/purchase_stock/models/stock.py
@@ -130,15 +130,12 @@ class StockWarehouse(models.Model):
'company_id': self.company_id.id,
'route_id': self._find_global_route('purchase_stock.route_warehouse0_buy', _('Buy')).id,
'propagate_cancel': self.reception_steps != 'one_step',
- 'delay_alert': True,
- 'propagate_date': self.reception_steps != 'one_step',
},
'update_values': {
'active': self.buy_to_resupply,
'name': self._format_rulename(location_id, False, 'Buy'),
'location_id': location_id.id,
'propagate_cancel': self.reception_steps != 'one_step',
- 'propagate_date': self.reception_steps != 'one_step',
}
}
})
diff --git a/addons/purchase_stock/models/stock_rule.py b/addons/purchase_stock/models/stock_rule.py
index c4800cfc893..bbbd767c5dd 100644
--- a/addons/purchase_stock/models/stock_rule.py
+++ b/addons/purchase_stock/models/stock_rule.py
@@ -73,9 +73,6 @@ class StockRule(models.Model):
partner = supplier.name
# we put `supplier_info` in values for extensibility purposes
procurement.values['supplier'] = supplier
- procurement.values['delay_alert'] = rule.delay_alert
- procurement.values['propagate_date'] = rule.propagate_date
- procurement.values['propagate_date_minimum_delta'] = rule.propagate_date_minimum_delta
procurement.values['propagate_cancel'] = rule.propagate_cancel
domain = rule._make_po_get_domain(procurement.company_id, procurement.values, partner)
@@ -171,15 +168,13 @@ class StockRule(models.Model):
# generated from the order line has the orderpoint's location as
# destination location. In case of move_dest_ids those two points are not
# necessary anymore since those values are taken from destination moves.
- return procurement.product_id, procurement.product_uom, procurement.values['propagate_date'],\
- procurement.values['propagate_date_minimum_delta'], procurement.values['propagate_cancel'],\
+ return procurement.product_id, procurement.product_uom, procurement.values['propagate_cancel'],\
procurement.values.get('product_description_variants'),\
(procurement.values.get('orderpoint_id') and not procurement.values.get('move_dest_ids')) and procurement.values['orderpoint_id']
@api.model
def _get_procurements_to_merge_sorted(self, procurement):
- return procurement.product_id.id, procurement.product_uom.id, procurement.values['propagate_date'],\
- procurement.values['propagate_date_minimum_delta'], procurement.values['propagate_cancel'],\
+ return procurement.product_id.id, procurement.product_uom.id, procurement.values['propagate_cancel'],\
procurement.values.get('product_description_variants'),\
(procurement.values.get('orderpoint_id') and not procurement.values.get('move_dest_ids')) and procurement.values['orderpoint_id']
diff --git a/addons/purchase_stock/tests/test_create_picking.py b/addons/purchase_stock/tests/test_create_picking.py
index fc9393abbe5..1dc58027c00 100644
--- a/addons/purchase_stock/tests/test_create_picking.py
+++ b/addons/purchase_stock/tests/test_create_picking.py
@@ -378,7 +378,7 @@ class TestCreatePicking(common.TestProductCommon):
self.assertEqual(move2.product_uom.id, uom_dozen.id)
self.assertEqual(move2.product_qty, 24)
- def create_delivery_order(self, propagate_date, propagate_date_minimum_delta):
+ def create_delivery_order(self):
stock_location = self.env['ir.model.data'].xmlid_to_object('stock.stock_location_stock')
customer_location = self.env['ir.model.data'].xmlid_to_object('stock.stock_location_customers')
unit = self.ref("uom.product_uom_unit")
@@ -393,10 +393,6 @@ class TestCreatePicking(common.TestProductCommon):
route_buy = warehouse1.buy_pull_id.route_id
route_mto = warehouse1.mto_pull_id.route_id
- # change propagete date and Reschedule if Higher Than on rules
- route_buy.rule_ids.write({'propagate_date': propagate_date,
- 'propagate_date_minimum_delta': propagate_date_minimum_delta})
-
product = self.env['product.product'].create({
'name': 'Usb Keyboard',
'type': 'product',
@@ -430,33 +426,11 @@ class TestCreatePicking(common.TestProductCommon):
return delivery_order, purchase_order
- def test_05_if_propagate_date(self):
- """ In order to check scheduled date of the delivery order is changed based
- stock rules with propagate date and minimum delta.
- """
+ def test_05_propagate_deadline(self):
+ """ In order to check deadline date of the delivery order is changed and the planned date not."""
# Create Delivery Order and with propagate date and minimum delta
- delivery_order, purchase_order = self.create_delivery_order(True, 5)
-
- # check po is created or not
- self.assertTrue(purchase_order, 'No purchase order created.')
-
- purchase_order_line = purchase_order.order_line
-
- # change scheduled date of po line.
- purchase_order_line.write({'date_planned': purchase_order_line.date_planned + timedelta(days=5)})
-
- # Now check scheduled date of delivery order is changed or not.
- self.assertEqual(purchase_order_line.date_planned, delivery_order.scheduled_date,
- 'Delivery order schedule date should be changed as we have set date propagate.')
-
- def test_06_no_propagate_date(self):
- """ In order to check scheduled date of the delivery order is changed based
- stock rules without propagate date and minimum delta.
- """
-
- # Create Delivery Order and without propagate date and minimum delta
- delivery_order, purchase_order = self.create_delivery_order(False, 5)
+ delivery_order, purchase_order = self.create_delivery_order()
# check po is created or not
self.assertTrue(purchase_order, 'No purchase order created.')
@@ -466,19 +440,17 @@ class TestCreatePicking(common.TestProductCommon):
# change scheduled date of po line.
purchase_order_line.write({'date_planned': purchase_order_line.date_planned + timedelta(days=5)})
- # Now check scheduled date of delivery order is changed or not.
- self.assertNotEqual(purchase_order_line.date_planned, delivery_order.scheduled_date,
- 'Delivery order schedule date should not changed.')
+ # Now check scheduled date and deadline of delivery order.
+ self.assertNotEqual(
+ purchase_order_line.date_planned, delivery_order.scheduled_date,
+ 'Scheduled delivery order date should not changed.')
+ self.assertEqual(
+ purchase_order_line.date_planned, delivery_order.date_deadline,
+ 'Delivery deadline date should be changed.')
def test_07_differed_schedule_date(self):
warehouse = self.env['stock.warehouse'].search([], limit=1)
- # mark all rules as propagate_date so that push rules will use it
- self.env['stock.rule'].search([]).write({
- 'propagate_date': True,
- 'propagate_date_minimum_delta': 5,
- })
-
with Form(warehouse) as w:
w.reception_steps = 'three_steps'
po_form = Form(self.env['purchase.order'])
@@ -493,12 +465,6 @@ class TestCreatePicking(common.TestProductCommon):
line.product_qty = 1.0
po = po_form.save()
- # mark the po line as propagate_date so that the created moves will use it
- po.order_line.write({
- 'propagate_date': True,
- 'propagate_date_minimum_delta': 5,
- })
-
po.button_approve()
po.picking_ids.move_line_ids.write({
diff --git a/addons/purchase_stock/tests/test_purchase_lead_time.py b/addons/purchase_stock/tests/test_purchase_lead_time.py
index e42e2104733..89773f52041 100644
--- a/addons/purchase_stock/tests/test_purchase_lead_time.py
+++ b/addons/purchase_stock/tests/test_purchase_lead_time.py
@@ -39,8 +39,9 @@ class TestPurchaseLeadTime(PurchaseTestCommon):
# check the picking created or not
self.assertTrue(purchase.picking_ids, "Picking should be created.")
- # Check scheduled date of In Type shipment
+ # Check scheduled and deadline date of In Type shipment
self.assertEqual(purchase.picking_ids.scheduled_date, schedule_date, 'Schedule date of In type shipment should be equal to: schedule date of purchase order.')
+ self.assertEqual(purchase.picking_ids.date_deadline, schedule_date + timedelta(days=company.po_lead), 'Deadline date of should be equal to: schedule date of purchase order + lead_po.')
def test_01_product_level_delay(self):
""" To check schedule dates of multiple purchase order line of the same purchase order,
@@ -87,6 +88,13 @@ class TestPurchaseLeadTime(PurchaseTestCommon):
# Check scheduled date of In Type shipment
self.assertEqual(purchase2.picking_ids.scheduled_date, po_schedule_date, 'Schedule date of In type shipment should be same as schedule date of purchase order.')
+ # Check deadline of pickings
+ self.assertEqual(purchase2.picking_ids.date_deadline, purchase2.date_planned, "Deadline of pickings should be equals to the receipt date of purchase")
+ purchase_form = Form(purchase2)
+ purchase_form.date_planned = purchase2.date_planned + timedelta(days=2)
+ purchase_form.save()
+ self.assertEqual(purchase2.picking_ids.date_deadline, purchase2.date_planned, "Deadline of pickings should be propagate")
+
def test_02_product_route_level_delays(self):
""" In order to check dates, set product's Delivery Lead Time
and warehouse route's delay."""
@@ -139,8 +147,7 @@ class TestPurchaseLeadTime(PurchaseTestCommon):
self.assertEqual(incoming_shipment2.scheduled_date, incoming_shipment2_date, 'Schedule date of Internal Type shipment for quality control stock location should be equal to: schedule date of Internal type shipment for input stock location + push rule delay..')
def test_merge_po_line(self):
- """Chage that merging po line for same procurement is done depending on
- propagate_date and propagate_date_minimum_delta"""
+ """Change that merging po line for same procurement is done."""
# create a product with manufacture route
product_1 = self.env['product.product'].create({
@@ -155,8 +162,6 @@ class TestPurchaseLeadTime(PurchaseTestCommon):
'name': 'move_1',
'product_id': product_1.id,
'product_uom': self.ref('uom.product_uom_unit'),
- 'propagate_date': True,
- 'propagate_date_minimum_delta': 1,
'location_id': self.ref('stock.stock_location_stock'),
'location_dest_id': self.ref('stock.stock_location_output'),
'product_uom_qty': 10,
@@ -174,8 +179,6 @@ class TestPurchaseLeadTime(PurchaseTestCommon):
'name': 'move_2',
'product_id': product_1.id,
'product_uom': self.ref('uom.product_uom_unit'),
- 'propagate_date': True,
- 'propagate_date_minimum_delta': 1,
'location_id': self.ref('stock.stock_location_stock'),
'location_dest_id': self.ref('stock.stock_location_output'),
'product_uom_qty': 5,
@@ -189,62 +192,8 @@ class TestPurchaseLeadTime(PurchaseTestCommon):
self.assertEqual(len(po_line), 1, 'the purchase order lines should be merged')
self.assertEqual(po_line.product_qty, 15, 'the purchase order line has a wrong quantity')
- def test_merge_po_line_2(self):
- """Chage that merging po line for same procurement is done depending on
- propagate_date and propagate_date_minimum_delta"""
-
- # create a product with manufacture route
- product_1 = self.env['product.product'].create({
- 'name': 'AAA',
- 'route_ids': [(4, self.route_buy)],
- 'seller_ids': [(0, 0, {'name': self.partner_1.id, 'delay': 5})]
- })
-
- # create a move for product_1 from stock to output and reserve to trigger the
- # rule
- move_1 = self.env['stock.move'].create({
- 'name': 'move_1',
- 'product_id': product_1.id,
- 'product_uom': self.ref('uom.product_uom_unit'),
- 'propagate_date': True,
- 'propagate_date_minimum_delta': 1,
- 'location_id': self.ref('stock.stock_location_stock'),
- 'location_dest_id': self.ref('stock.stock_location_output'),
- 'product_uom_qty': 10,
- 'procure_method': 'make_to_order'
- })
-
- move_1._action_confirm()
- po_line = self.env['purchase.order.line'].search([
- ('product_id', '=', product_1.id),
- ])
- self.assertEqual(len(po_line), 1, 'the purchase order line is not created')
- self.assertEqual(po_line.product_qty, 10, 'the purchase order line has a wrong quantity')
- po_line.propagate_date = not po_line.propagate_date
- move_2 = self.env['stock.move'].create({
- 'name': 'move_2',
- 'product_id': product_1.id,
- 'product_uom': self.ref('uom.product_uom_unit'),
- 'propagate_date': True,
- 'propagate_date_minimum_delta': 1,
- 'location_id': self.ref('stock.stock_location_stock'),
- 'location_dest_id': self.ref('stock.stock_location_output'),
- 'product_uom_qty': 5,
- 'procure_method': 'make_to_order'
- })
-
- move_2._action_confirm()
- po_line = self.env['purchase.order.line'].search([
- ('product_id', '=', product_1.id),
- ])
- self.assertEqual(len(po_line), 2, 'the purchase order lines are merged')
- self.assertEqual(po_line[0].product_qty, 10, 'the purchase order line has a wrong quantity')
- self.assertEqual(po_line[1].product_qty, 5, 'the purchase order line has a wrong quantity')
-
def test_merge_po_line_3(self):
- """Chage merging po line if same procurement is done depending on
- propagate_date, propagate_date_minimum_delta and custom values.
- """
+ """Change merging po line if same procurement is done depending on custom values."""
# Create procurement order of product_1
ProcurementGroup = self.env['procurement.group']
procurement_values = {
@@ -316,7 +265,7 @@ class TestPurchaseLeadTime(PurchaseTestCommon):
for i in range(0, 6):
delivery_moves |= self.env['stock.move'].create({
'name': 'Delivery',
- 'date_expected': datetime.today() + timedelta(days=i),
+ 'date': datetime.today() + timedelta(days=i),
'product_id': product.id,
'product_uom': product.uom_id.id,
'product_uom_qty': 5.0,
diff --git a/addons/purchase_stock/tests/test_purchase_order.py b/addons/purchase_stock/tests/test_purchase_order.py
index c1e958ffb41..63f59cda16c 100644
--- a/addons/purchase_stock/tests/test_purchase_order.py
+++ b/addons/purchase_stock/tests/test_purchase_order.py
@@ -212,16 +212,6 @@ class TestPurchaseOrder(ValuationReconciliationTestCommon):
self.assertEqual(po1.order_line.qty_received, 5)
self.assertEqual(po1.picking_ids[-1].move_lines.product_qty, 10)
- def test_propagate_date_of_move(self):
- """ Propagate date of move should be assigned as per value of mto
- buy route if PO is created manually (not from mto route).
- """
- warehouse = self.company_data['default_warehouse']
- self.po = self.env['purchase.order'].create(self.po_vals)
- self.po.button_confirm()
- self.assertFalse(self.po.order_line.mapped('move_dest_ids'))
- self.assertEqual(self.po.picking_ids.move_lines[0].propagate_date, warehouse.buy_pull_id.propagate_date)
-
def test_update_date_planned(self):
po = self.env['purchase.order'].create(self.po_vals)
po.button_confirm()
diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py
index 00966eef9a1..b9ad7c8b796 100644
--- a/addons/sale/models/sale.py
+++ b/addons/sale/models/sale.py
@@ -232,9 +232,8 @@ class SaleOrder(models.Model):
signed_by = fields.Char('Signed By', help='Name of the person that signed the SO.', copy=False)
signed_on = fields.Datetime('Signed On', help='Date of the signature.', copy=False)
- commitment_date = fields.Datetime('Delivery Date',
- states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
- copy=False, readonly=True,
+ commitment_date = fields.Datetime('Delivery Date', copy=False,
+ states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
help="This is the delivery date promised to the customer. "
"If set, the delivery order will be scheduled based on "
"this date rather than product lead times.")
diff --git a/addons/sale/views/sale_views.xml b/addons/sale/views/sale_views.xml
index 62b82ac7abd..f5d19126834 100644
--- a/addons/sale/views/sale_views.xml
+++ b/addons/sale/views/sale_views.xml
@@ -553,7 +553,7 @@
<field
name="customer_lead"
optional="hide"
- attrs="{'readonly': [('parent.state', 'not in', ['draft', 'sent'])]}"
+ attrs="{'readonly': [('parent.state', 'not in', ['draft', 'sent', 'sale'])]}"
/>
<field
name="price_unit"
diff --git a/addons/sale_mrp/tests/test_sale_mrp_lead_time.py b/addons/sale_mrp/tests/test_sale_mrp_lead_time.py
index a559605b71b..30fbc3792b8 100644
--- a/addons/sale_mrp/tests/test_sale_mrp_lead_time.py
+++ b/addons/sale_mrp/tests/test_sale_mrp_lead_time.py
@@ -61,22 +61,31 @@ class TestSaleMrpLeadTime(TestStockCommon):
self.assertTrue(manufacturing_order, 'Manufacturing order should be created.')
# Check schedule date of picking
- out_date = fields.Datetime.from_string(order.date_order) + timedelta(days=self.product_1.sale_delay) - timedelta(days=company.security_lead)
- min_date = fields.Datetime.from_string(order.picking_ids[0].scheduled_date)
+ deadline_picking = fields.Datetime.from_string(order.date_order) + timedelta(days=self.product_1.sale_delay)
+ out_date = deadline_picking - timedelta(days=company.security_lead)
self.assertAlmostEqual(
- min_date, out_date,
+ order.picking_ids[0].scheduled_date, out_date,
delta=timedelta(seconds=1),
msg='Schedule date of picking should be equal to: Order date + Customer Lead Time - Sales Safety Days.'
)
+ self.assertAlmostEqual(
+ order.picking_ids[0].date_deadline, deadline_picking,
+ delta=timedelta(seconds=1),
+ msg='Deadline date of picking should be equal to: Order date + Customer Lead Time - Sales Safety Days.'
+ )
- # Check schedule date of manufacturing order
- mo_date = out_date - timedelta(days=self.product_1.produce_delay) - timedelta(days=company.manufacturing_lead)
- date_deadline = fields.Datetime.from_string(manufacturing_order.date_deadline)
+ # Check schedule date and deadline of manufacturing order
+ mo_scheduled = out_date - timedelta(days=self.product_1.produce_delay) - timedelta(days=company.manufacturing_lead)
self.assertAlmostEqual(
- date_deadline, mo_date,
+ fields.Datetime.from_string(manufacturing_order.date_planned_start), mo_scheduled,
delta=timedelta(seconds=1),
msg="Schedule date of manufacturing order should be equal to: Schedule date of picking - product's Manufacturing Lead Time - company's Manufacturing Lead Time."
)
+ self.assertAlmostEqual(
+ fields.Datetime.from_string(manufacturing_order.date_deadline), deadline_picking,
+ delta=timedelta(seconds=1),
+ msg="Deadline date of manufacturing order should be equal to the deadline of sale picking"
+ )
def test_01_product_route_level_delays(self):
""" In order to check schedule dates, set product's Manufacturing Lead Time
@@ -139,11 +148,15 @@ class TestSaleMrpLeadTime(TestStockCommon):
msg='Schedule date of pick type picking should be equal to: Schedule date of pack type picking.'
)
- # Check schedule date of manufacturing order
- mo_date = pack_date - timedelta(days=self.product_1.produce_delay)
- date_deadline = fields.Datetime.from_string(manufacturing_order.date_deadline)
+ # Check schedule date and deadline date of manufacturing order
+ mo_scheduled = out_date - timedelta(days=self.product_1.produce_delay) - timedelta(days=self.warehouse_1.delivery_route_id.rule_ids[0].delay) - timedelta(days=self.env.ref('base.main_company').manufacturing_lead)
self.assertAlmostEqual(
- date_deadline, mo_date,
- delta=timedelta(seconds=10),
- msg="Schedule date of manufacturing order should be equal to: Schedule date of pack type picking - product's Manufacturing Lead Time."
+ fields.Datetime.from_string(manufacturing_order.date_planned_start), mo_scheduled,
+ delta=timedelta(seconds=1),
+ msg="Schedule date of manufacturing order should be equal to: Schedule date of picking - product's Manufacturing Lead Time- delay pull_rule."
+ )
+ self.assertAlmostEqual(
+ manufacturing_order.date_deadline, order.picking_ids[0].date_deadline,
+ delta=timedelta(seconds=1),
+ msg="Deadline date of manufacturing order should be equal to the deadline of sale picking"
)
diff --git a/addons/sale_stock/models/sale_order.py b/addons/sale_stock/models/sale_order.py
index 4e8dbee574d..6a7f809214b 100644
--- a/addons/sale_stock/models/sale_order.py
+++ b/addons/sale_stock/models/sale_order.py
@@ -112,6 +112,11 @@ class SaleOrder(models.Model):
You should probably update the partner on this document.""") % addresses
picking.activity_schedule('mail.mail_activity_data_warning', note=message, user_id=self.env.user.id)
+ if values.get('commitment_date'):
+ # protagate commitment_date as the deadline of the related stock move.
+ # TODO: Log a note on each down document
+ self.order_line.move_ids.date_deadline = fields.Datetime.to_datetime(values.get('commitment_date'))
+
res = super(SaleOrder, self).write(values)
if values.get('order_line') and self.state == 'sale':
for order in self:
@@ -433,6 +438,9 @@ class SaleOrderLine(models.Model):
res = super(SaleOrderLine, self).write(values)
if lines:
lines._action_launch_stock_rule(previous_product_uom_qty)
+ if 'customer_lead' in values and self.state == 'sale' and not self.order_id.commitment_date:
+ # Propagate deadline on related stock move
+ self.move_ids.date_deadline = self.order_id.date_order + timedelta(days=self.customer_lead or 0.0)
return res
@api.depends('order_id.state')
@@ -496,23 +504,20 @@ class SaleOrderLine(models.Model):
"""
values = super(SaleOrderLine, self)._prepare_procurement_values(group_id)
self.ensure_one()
- date_planned = self.order_id.date_order\
- + timedelta(days=self.customer_lead or 0.0) - timedelta(days=self.order_id.company_id.security_lead)
+ # Use the delivery date if there is else use date_order and lead time
+ date_deadline = self.order_id.commitment_date or (self.order_id.date_order + timedelta(days=self.customer_lead or 0.0))
+ date_planned = date_deadline - timedelta(days=self.order_id.company_id.security_lead)
values.update({
'group_id': group_id,
'sale_line_id': self.id,
'date_planned': date_planned,
+ 'date_deadline': date_deadline,
'route_ids': self.route_id,
'warehouse_id': self.order_id.warehouse_id or False,
'partner_id': self.order_id.partner_shipping_id.id,
'product_description_variants': self._get_sale_order_line_multiline_description_variants(),
'company_id': self.order_id.company_id,
})
- for line in self.filtered("order_id.commitment_date"):
- date_planned = fields.Datetime.from_string(line.order_id.commitment_date) - timedelta(days=line.order_id.company_id.security_lead)
- values.update({
- 'date_planned': fields.Datetime.to_string(date_planned),
- })
return values
def _get_qty_procurement(self, previous_product_uom_qty=False):
diff --git a/addons/sale_stock/tests/test_sale_order_dates.py b/addons/sale_stock/tests/test_sale_order_dates.py
index a6f8276a8a3..6e1103a3d58 100644
--- a/addons/sale_stock/tests/test_sale_order_dates.py
+++ b/addons/sale_stock/tests/test_sale_order_dates.py
@@ -112,4 +112,4 @@ class TestSaleExpectedDate(ValuationReconciliationTestCommon):
commitment_date = fields.Datetime.from_string(new_order.commitment_date)
right_date = commitment_date - security_delay
for line in new_order.order_line:
- self.assertEqual(line.move_ids[0].date_expected, right_date, "The expected date for the Stock Move is wrong")
+ self.assertEqual(line.move_ids[0].date, right_date, "The expected date for the Stock Move is wrong")
diff --git a/addons/sale_stock/tests/test_sale_stock.py b/addons/sale_stock/tests/test_sale_stock.py
index 4c9cb9779fc..ac789294516 100644
--- a/addons/sale_stock/tests/test_sale_stock.py
+++ b/addons/sale_stock/tests/test_sale_stock.py
@@ -644,7 +644,7 @@ class TestSaleStock(TestSaleCommon, ValuationReconciliationTestCommon):
# A new move of 10 unit (15 - 5 units)
self.assertEqual(so1.order_line.qty_delivered, 5)
- self.assertEqual(so1.picking_ids[-1].move_lines.product_qty, 10)
+ self.assertEqual(so1.picking_ids.sorted('id')[-1].move_lines.product_qty, 10)
def test_09_qty_available(self):
""" create a sale order in warehouse1, change to warehouse2 and check the
diff --git a/addons/sale_stock/tests/test_sale_stock_lead_time.py b/addons/sale_stock/tests/test_sale_stock_lead_time.py
index eb031fe43ee..ad3df809d32 100644
--- a/addons/sale_stock/tests/test_sale_stock_lead_time.py
+++ b/addons/sale_stock/tests/test_sale_stock_lead_time.py
@@ -99,41 +99,39 @@ class TestSaleStockLeadTime(TestSaleCommon, ValuationReconciliationTestCommon):
pick_date = pack_date - timedelta(days=pick.move_lines[0].rule_id.delay)
self.assertTrue(abs(pick_min_date - pick_date) <= timedelta(seconds=1), 'Schedule date of pick type picking should be equal to: Schedule date of pack type picking - pull rule delay.')
- def test_02_if_propagate_date(self):
- """ In order to check schedule dates, set product's Customer Lead Time
- and warehouse route's delay with propagate True in stock rules"""
+ def test_02_delivery_date_propagation(self):
+ """ In order to check deadline date propagation, set product's Customer Lead Time
+ and warehouse route's delay in stock rules"""
- #Example :
- #-> set 'propagate_date' = True in stock rules
- #-> set propagate_date_minimum_delta = 5 days
- #-> Set Warehouse with Outgoing Shipments : pick + pack + ship
- #-> Set delay and propagate_date_minimum_delta on stock rules : 5 days and propagate_date = True
- #-> Set Customer Lead Time on product : 30 days
- #-> Create an SO and confirm it with confirmation Date : 12/18/2018
+ # Example :
+ # -> Set Warehouse with Outgoing Shipments : pick + pack + ship
+ # -> Set Delay : 5 days on stock rules
+ # -> Set Customer Lead Time on product : 30 days
+ # -> Set Sales Safety Days : 2 days
+ # -> Create an SO and confirm it with confirmation Date : 12/18/2018
- #-> Pickings : OUT -> Scheduled Date : 01/12/2019
- # PACK -> Scheduled Date : 01/07/2019
- # PICK -> Scheduled Date : 01/02/2019
+ # -> Pickings : OUT -> Scheduled Date : 01/12/2019, Deadline Date: 01/14/2019
+ # PACK -> Scheduled Date : 01/07/2019, Deadline Date: 01/14/2019
+ # PICK -> Scheduled Date : 01/02/2019, Deadline Date: 01/14/2019
- #-> Now, change date of pick = +5 days
+ # -> Now, change commitment_date in the sale order = out_deadline_date + 5 days
- #-> Scheduled Date should be changed:
- # OUT -> Scheduled Date : 01/17/2019
- # PACK -> Scheduled Date : 01/12/2019
- # PICK -> Scheduled Date : 01/07/2019
+ # -> Deadline Date should be changed and Scheduled date should be unchanged:
+ # OUT -> Deadline Date : 01/19/2019
+ # PACK -> Deadline Date : 01/19/2019
+ # PICK -> Deadline Date : 01/19/2019
- # set the propagate_date and
- # set propagate_date_minimum_delta = 5 in the stock rule
+ # Update company with Sales Safety Days
+ self.env.company.security_lead = 2.00
# Update warehouse_1 with Outgoing Shippings pick + pack + ship
self.company_data['default_warehouse'].write({'delivery_steps': 'pick_pack_ship'})
# Set delay on pull rule
- self.company_data['default_warehouse'].delivery_route_id.rule_ids.write({'delay': 5, 'propagate_date': True, 'propagate_date_minimum_delta': 5})
+ self.company_data['default_warehouse'].delivery_route_id.rule_ids.write({'delay': 5})
# Update the product_1 with type and Customer Lead Time
- self.test_product_order.write({'type': 'product',
- 'sale_delay': 30.0})
+ self.test_product_order.write({'type': 'product', 'sale_delay': 30.0})
# Now, create sale order of product_1 with customer_lead set on product
order = self.env['sale.order'].create({
@@ -155,106 +153,43 @@ class TestSaleStockLeadTime(TestSaleCommon, ValuationReconciliationTestCommon):
# Check the picking crated or not
self.assertTrue(order.picking_ids, "Pickings should be created.")
- # Check schedule date of ship type picking
- out = order.picking_ids.filtered(lambda r: r.picking_type_id == self.company_data['default_warehouse'].out_type_id)
- out_min_date = out.scheduled_date
- out_date = order.date_order + timedelta(days=self.test_product_order.sale_delay) - timedelta(days=out.move_lines[0].rule_id.delay)
- self.assertTrue(abs(out_min_date - out_date) <= timedelta(seconds=1), 'Schedule date of ship type picking should be equal to: order date + Customer Lead Time - pull rule delay.')
-
- # Check schedule date of pack type picking
- pack = order.picking_ids.filtered(lambda r: r.picking_type_id == self.company_data['default_warehouse'].pack_type_id)
- pack_min_date = pack.scheduled_date
- pack_date = out_date - timedelta(days=pack.move_lines[0].rule_id.delay)
- self.assertTrue(abs(pack_min_date - pack_date) <= timedelta(seconds=1), 'Schedule date of pack type picking should be equal to: Schedule date of ship type picking - pull rule delay.')
-
- # Check schedule date of pick type picking
- pick = order.picking_ids.filtered(lambda r: r.picking_type_id == self.company_data['default_warehouse'].pick_type_id)
- pick_min_date = pick.scheduled_date
- pick_date = pack_date - timedelta(days=pick.move_lines[0].rule_id.delay)
- self.assertTrue(abs(pick_min_date - pick_date) <= timedelta(seconds=1), 'Schedule date of pick type picking should be equal to: Schedule date of pack type picking - pull rule delay.')
-
- # Now change the schedule date of pick
- # Note : pack and out has change scheduled_date automatically based on delay set on pick
- pick.write({'scheduled_date': pick_min_date + timedelta(days=5)})
-
- # Now check scheduled_date of pack and out are changed or not based on propagate is true on rules?
- self.assertEqual(pack.scheduled_date, (pack_min_date + timedelta(days=5)),
- 'Schedule date of pack should be changed based on delay.')
- self.assertEqual(out.scheduled_date, (out_min_date + timedelta(days=5)),
- 'Schedule date of out should be changed based on delay.')
-
- def test_03_no_propagate_date(self):
- """ In order to check schedule dates, set product's Customer Lead Time
- and warehouse route's delay with propagate False in stock rule"""
-
- #Example :
- #-> Set Warehouse with Outgoing Shipments : pick + pack + ship
- #-> Set delay on stock rules : 5 days and propagate = False
- #-> Set Customer Lead Time on product : 30 days
- #-> Create an SO and confirm it with confirmation Date : 12/18/2018
-
- #-> Pickings : OUT -> Scheduled Date : 01/12/2019
- # PACK -> Scheduled Date : 01/07/2019
- # PICK -> Scheduled Date : 01/02/2019
-
- #-> Now, change date of pick = +5 days
-
- #-> Scheduled Date should be not changed:
- # OUT -> Scheduled Date : 01/12/2019
- # PACK -> Scheduled Date : 01/07/2019
- # PICK -> Scheduled Date : 01/07/2019
-
- # Update warehouse_1 with Outgoing Shippings pick + pack + ship
- self.company_data['default_warehouse'].write({'delivery_steps': 'pick_pack_ship'})
-
- # Set delay on pull rule
- for pull_rule in self.company_data['default_warehouse'].delivery_route_id.rule_ids:
- pull_rule.write({'delay': 5, 'propagate_date': False})
-
- # Update the product_1 with type and Customer Lead Time
- self.test_product_order.write({'type': 'product',
- 'sale_delay': 30.0})
-
- #Create sale order of product_1
- order = self.env['sale.order'].create({
- 'partner_id': self.partner_a.id,
- 'partner_invoice_id': self.partner_a.id,
- 'partner_shipping_id': self.partner_a.id,
- 'pricelist_id': self.company_data['default_pricelist'].id,
- 'picking_policy': 'direct',
- 'warehouse_id': self.company_data['default_warehouse'].id,
- 'order_line': [(0, 0, {'name': self.test_product_order.name,
- 'product_id': self.test_product_order.id,
- 'product_uom_qty': 5,
- 'product_uom': self.env.ref('uom.product_uom_unit').id,
- 'customer_lead': self.test_product_order.sale_delay})]})
- # Confirm our standard sale order
- order.action_confirm()
-
- # Check the picking crated or not
- self.assertTrue(order.picking_ids, "Pickings should be created.")
-
- # Check schedule date of ship type picking
+ # Check schedule/deadline date of ship type picking
out = order.picking_ids.filtered(lambda r: r.picking_type_id == self.company_data['default_warehouse'].out_type_id)
- out_min_date = out.scheduled_date
- out_date = order.date_order + timedelta(days=self.test_product_order.sale_delay) - timedelta(days=out.move_lines[0].rule_id.delay)
- self.assertTrue(abs(out_min_date - out_date) <= timedelta(seconds=1), 'Schedule date of ship type picking should be equal to: order date + Customer Lead Time - pull rule delay.')
-
- # Check schedule date of pack type picking
+ deadline_date = order.date_order + timedelta(days=self.test_product_order.sale_delay)
+ self.assertAlmostEqual(
+ out.date_deadline, deadline_date, delta=timedelta(seconds=1),
+ msg='Deadline date of ship type picking should be equal to: order date + Customer Lead Time - pull rule delay.')
+ out_scheduled_date = deadline_date - timedelta(days=self.env.company.security_lead) - timedelta(days=out.move_lines[0].rule_id.delay)
+ self.assertAlmostEqual(
+ out.scheduled_date, out_scheduled_date, delta=timedelta(seconds=1),
+ msg='Schedule date of ship type picking should be equal to: order date + Customer Lead Time - pull rule delay - security_lead')
+
+ # Check schedule/deadline date of pack type picking
pack = order.picking_ids.filtered(lambda r: r.picking_type_id == self.company_data['default_warehouse'].pack_type_id)
- pack_min_date = pack.scheduled_date
- pack_date = out_date - timedelta(days=pack.move_lines[0].rule_id.delay)
- self.assertTrue(abs(pack_min_date - pack_date) <= timedelta(seconds=1), 'Schedule date of pack type picking should be equal to: Schedule date of ship type picking - pull rule delay.')
-
- # Check schedule date of pick type picking
+ pack_scheduled_date = out_scheduled_date - timedelta(days=pack.move_lines[0].rule_id.delay)
+ self.assertAlmostEqual(
+ pack.scheduled_date, pack_scheduled_date, delta=timedelta(seconds=1),
+ msg='Schedule date of pack type picking should be equal to: Schedule date of ship type picking - pull rule delay.')
+ self.assertAlmostEqual(
+ pack.date_deadline, deadline_date, delta=timedelta(seconds=1),
+ msg='Deadline date of pack type picking should be equal to: Deadline date of ship type picking - pull rule delay.')
+
+ # Check schedule/deadline date of pick type picking
pick = order.picking_ids.filtered(lambda r: r.picking_type_id == self.company_data['default_warehouse'].pick_type_id)
- pick_min_date = pick.scheduled_date
- pick_date = pack_date - timedelta(days=pick.move_lines[0].rule_id.delay)
- self.assertTrue(abs(pick_min_date - pick_date) <= timedelta(seconds=1), 'Schedule date of pick type picking should be equal to: Schedule date of pack type picking - pull rule delay.')
-
- # Now change the schedule date of pick
- pick.write({'scheduled_date': pick_min_date + timedelta(days=5)})
-
- # Now check scheduled_date of pack and out are changed or not based on propagate is false on rules?
- self.assertEqual(pack.scheduled_date, pack_min_date, 'Schedule date of pack should not be changed.')
- self.assertEqual(out.scheduled_date, out_min_date, 'Schedule date of out should not be changed.')
+ pick_scheduled_date = pack_scheduled_date - timedelta(days=pick.move_lines[0].rule_id.delay)
+ self.assertAlmostEqual(
+ pick.scheduled_date, pick_scheduled_date, delta=timedelta(seconds=1),
+ msg='Schedule date of pack type picking should be equal to: Schedule date of ship type picking - pull rule delay.')
+ self.assertAlmostEqual(
+ pick.date_deadline, deadline_date, delta=timedelta(seconds=1),
+ msg='Deadline date of pack type picking should be equal to: Deadline date of ship type picking - pull rule delay.')
+
+ # Now change the commitment date (Delivery Date) of the sale order
+ new_deadline = deadline_date + timedelta(days=5)
+ order.write({'commitment_date': new_deadline})
+
+ # Now check date_deadline of pick, pack and out are forced
+ # TODO : add note in case of change of deadline and check
+ self.assertEqual(out.date_deadline, new_deadline)
+ self.assertEqual(pick.date_deadline, new_deadline)
+ self.assertEqual(pack.date_deadline, new_deadline)
diff --git a/addons/stock/data/stock_demo2.xml b/addons/stock/data/stock_demo2.xml
index 1817e939d27..39043719ac5 100644
--- a/addons/stock/data/stock_demo2.xml
+++ b/addons/stock/data/stock_demo2.xml
@@ -86,7 +86,7 @@
<field name="origin">outgoing shipment main_warehouse</field>
<field name="user_id"></field>
<field name="partner_id" ref="base.res_partner_1"/>
- <field name="date" eval="DateTime.today()"/>
+ <field name="scheduled_date" eval="DateTime.today()"/>
<field name="location_id" ref="stock.stock_location_stock"/>
<field name="location_dest_id" ref="stock.stock_location_customers"/>
<field name="move_lines" model="stock.move" eval="[(0, 0, {
@@ -105,7 +105,7 @@
<field name="origin">outgoing shipment</field>
<field name="user_id"></field>
<field name="partner_id" ref="base.res_partner_1"/>
- <field name="date" eval="DateTime.today() - timedelta(days=15)"/>
+ <field name="scheduled_date" eval="DateTime.today() - timedelta(days=15)"/>
<field name="location_id" ref="stock.stock_location_stock"/>
<field name="location_dest_id" ref="stock.stock_location_customers"/>
<field name="move_lines" model="stock.move" eval="[(0, 0, {
@@ -131,7 +131,7 @@
<field name="origin">outgoing shipment your_company warehouse</field>
<field name="user_id"></field>
<field name="partner_id" ref="base.res_partner_1"/>
- <field name="date" eval="DateTime.today() - timedelta(days=5)"/>
+ <field name="scheduled_date" eval="DateTime.today() - timedelta(days=5)"/>
<field name="location_id" ref="stock.stock_location_stock"/>
<field name="location_dest_id" ref="stock.stock_location_customers"/>
<field name="move_lines" model="stock.move" eval="[(0, 0, {
@@ -150,7 +150,7 @@
<field name="user_id"></field>
<field name="origin">your company warehouse</field>
<field name="partner_id" ref="base.res_partner_1"/>
- <field name="date" eval="DateTime.today()"/>
+ <field name="scheduled_date" eval="DateTime.today()"/>
<field name="location_id" ref="stock.stock_location_stock"/>
<field name="location_dest_id" ref="stock.stock_location_customers"/>
<field name="move_lines" model="stock.move" eval="[(0, 0, {
@@ -169,7 +169,7 @@
<field name="origin">outgoing shipment</field>
<field name="user_id"></field>
<field name="partner_id" ref="base.res_partner_1"/>
- <field name="date" eval="DateTime.today() - timedelta(days=7)"/>
+ <field name="scheduled_date" eval="DateTime.today() - timedelta(days=7)"/>
<field name="location_id" ref="stock.stock_location_stock"/>
<field name="location_dest_id" ref="stock.stock_location_customers"/>
<field name="move_lines" model="stock.move" eval="[(0, 0, {
@@ -195,7 +195,7 @@
<field name="origin">outgoing shipment</field>
<field name="user_id"></field>
<field name="partner_id" ref="base.res_partner_1"/>
- <field name="date" eval="DateTime.today() - timedelta(days=12)"/>
+ <field name="scheduled_date" eval="DateTime.today() - timedelta(days=12)"/>
<field name="location_id" ref="stock.stock_location_stock"/>
<field name="location_dest_id" ref="stock.stock_location_customers"/>
<field name="move_lines" model="stock.move" eval="[(0, 0, {
@@ -221,7 +221,7 @@
<field name="origin">outgoing shipment</field>
<field name="user_id"></field>
<field name="partner_id" ref="base.res_partner_1"/>
- <field name="date" eval="DateTime.today() - timedelta(days=20)"/>
+ <field name="scheduled_date" eval="DateTime.today() - timedelta(days=20)"/>
<field name="location_id" ref="stock.stock_location_stock"/>
<field name="location_dest_id" ref="stock.stock_location_customers"/>
<field name="move_lines" model="stock.move" eval="[(0, 0, {
@@ -263,7 +263,7 @@
<field name="user_id"></field>
<field name="picking_type_id" ref="stock.picking_type_in"/>
<field name="partner_id" ref="base.res_partner_1"/>
- <field name="date" eval="DateTime.today() - timedelta(days=5)"/>
+ <field name="scheduled_date" eval="DateTime.today() - timedelta(days=5)"/>
<field name="location_id" ref="stock.stock_location_suppliers"/>
<field name="location_dest_id" ref="stock.stock_location_stock"/>
<field name="move_lines" model="stock.move" eval="[(0, 0, {
@@ -333,7 +333,7 @@
<field name="picking_type_id" ref="stock.chi_picking_type_in"/>
<field name="origin">incoming_chicago_warehouse</field>
<field name="user_id"></field>
- <field name="date" eval="DateTime.today()"/>
+ <field name="scheduled_date" eval="DateTime.today()"/>
<field name="location_id" ref="stock.stock_location_suppliers"/>
<field name="location_dest_id" ref="stock.stock_location_shop0"/>
<field name="company_id" ref="stock.res_company_1"/>
@@ -352,7 +352,7 @@
<record id="incomming_chicago_warehouse1" model="stock.picking">
<field name="user_id"></field>
<field name="picking_type_id" ref="stock.chi_picking_type_in"/>
- <field name="date" eval="DateTime.today() - timedelta(days=5)"/>
+ <field name="scheduled_date" eval="DateTime.today() - timedelta(days=5)"/>
<field name="location_id" ref="stock.stock_location_suppliers"/>
<field name="location_dest_id" ref="stock.stock_location_shop0"/>
<field name="company_id" ref="stock.res_company_1"/>
@@ -397,7 +397,7 @@
<field name="user_id"></field>
<field name="picking_type_id" ref="stock.chi_picking_type_in"/>
<field name="origin">chicago_warehouse</field>
- <field name="date" eval="DateTime.today() - timedelta(days=2)"/>
+ <field name="scheduled_date" eval="DateTime.today() - timedelta(days=2)"/>
<field name="location_id" ref="stock.stock_location_suppliers"/>
<field name="location_dest_id" ref="stock.stock_location_shop0"/>
<field name="company_id" ref="stock.res_company_1"/>
@@ -418,7 +418,7 @@
<field name="picking_type_id" ref="stock.chi_picking_type_out"/>
<field name="user_id"></field>
<field name="origin">outgoing_chicago_warehouse</field>
- <field name="date" eval="DateTime.today()"/>
+ <field name="scheduled_date" eval="DateTime.today()"/>
<field name="location_id" ref="stock.stock_location_shop0"/>
<field name="location_dest_id" ref="stock.stock_location_customers"/>
<field name="company_id" ref="stock.res_company_1"/>
@@ -438,7 +438,7 @@
<field name="user_id"></field>
<field name="picking_type_id" ref="stock.chi_picking_type_out"/>
<field name="origin">outgoing_shipment_chicago_warehouse</field>
- <field name="date" eval="DateTime.today() - timedelta(days=10)"/>
+ <field name="scheduled_date" eval="DateTime.today() - timedelta(days=10)"/>
<field name="location_id" ref="stock.stock_location_shop0"/>
<field name="location_dest_id" ref="stock.stock_location_customers"/>
<field name="company_id" ref="stock.res_company_1"/>
@@ -465,7 +465,7 @@
<field name="user_id"></field>
<field name="picking_type_id" ref="stock.chi_picking_type_out"/>
<field name="origin">chicago_warehouse</field>
- <field name="date" eval="DateTime.today()"/>
+ <field name="scheduled_date" eval="DateTime.today()"/>
<field name="location_id" ref="stock.stock_location_shop0"/>
<field name="location_dest_id" ref="stock.stock_location_customers"/>
<field name="company_id" ref="stock.res_company_1"/>
@@ -485,7 +485,7 @@
<field name="user_id"></field>
<field name="picking_type_id" ref="stock.chi_picking_type_out"/>
<field name="origin">outgoing chicago warehouse</field>
- <field name="date" eval="DateTime.today()"/>
+ <field name="scheduled_date" eval="DateTime.today()"/>
<field name="location_id" ref="stock.stock_location_shop0"/>
<field name="location_dest_id" ref="stock.stock_location_customers"/>
<field name="company_id" ref="stock.res_company_1"/>
@@ -543,17 +543,17 @@
<function model="stock.move" name="write">
<value model="stock.move" search="[('picking_id', '=', ref('stock.outgoing_shipment_main_warehouse4'))]"/>
- <value eval="{'date_expected': DateTime.today() + timedelta(days=3)}"/>
+ <value eval="{'date': DateTime.today() + timedelta(days=3)}"/>
</function>
<function model="stock.move" name="write">
<value model="stock.move" search="[('picking_id', '=', ref('stock.outgoing_shipment_main_warehouse5'))]"/>
- <value eval="{'date_expected': DateTime.today() - timedelta(days=18)}"/>
+ <value eval="{'date': DateTime.today() - timedelta(days=18)}"/>
</function>
<function model="stock.move" name="write">
<value model="stock.move" search="[('picking_id', '=', ref('stock.outgoing_shipment_main_warehouse6'))]"/>
- <value eval="{'date_expected': DateTime.today() - timedelta(days=7)}"/>
+ <value eval="{'date': DateTime.today() - timedelta(days=7)}"/>
</function>
</data>
diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py
index e937f070ac0..f411c3d98af 100644
--- a/addons/stock/models/product.py
+++ b/addons/stock/models/product.py
@@ -143,27 +143,11 @@ class Product(models.Model):
domain_move_in_done = list(domain_move_in)
domain_move_out_done = list(domain_move_out)
if from_date:
- date_date_expected_domain_from = [
- '|',
- '&',
- ('state', '=', 'done'),
- ('date', '<=', from_date),
- '&',
- ('state', '!=', 'done'),
- ('date_expected', '<=', from_date),
- ]
+ date_date_expected_domain_from = [('date', '<=', from_date)]
domain_move_in += date_date_expected_domain_from
domain_move_out += date_date_expected_domain_from
if to_date:
- date_date_expected_domain_to = [
- '|',
- '&',
- ('state', '=', 'done'),
- ('date', '<=', to_date),
- '&',
- ('state', '!=', 'done'),
- ('date_expected', '<=', to_date),
- ]
+ date_date_expected_domain_to = [('date', '<=', to_date)]
domain_move_in += date_date_expected_domain_to
domain_move_out += date_date_expected_domain_to
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index 5b478120e54..f1d2cd7dfb1 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -36,17 +36,15 @@ class StockMove(models.Model):
compute="_compute_priority", store=True, index=True)
create_date = fields.Datetime('Creation Date', index=True, readonly=True)
date = fields.Datetime(
- 'Date', default=fields.Datetime.now, index=True, required=True,
- states={'done': [('readonly', True)]},
- help="Move date: scheduled date until move is done, then date of actual move processing")
+ 'Date Scheduled', default=fields.Datetime.now, index=True, required=True,
+ help="Scheduled date until move is done, then date of actual move processing")
+ date_deadline = fields.Datetime(
+ "Deadline", readonly=True,
+ help="Date Promise to the customer on the top level document (SO/PO)")
company_id = fields.Many2one(
'res.company', 'Company',
default=lambda self: self.env.company,
index=True, required=True)
- date_expected = fields.Datetime(
- 'Expected Date', default=fields.Datetime.now, index=True, required=True,
- states={'done': [('readonly', True)]},
- help="Scheduled date for the processing of this move")
product_id = fields.Many2one(
'product.product', 'Product',
check_company=True,
@@ -135,11 +133,6 @@ class StockMove(models.Model):
propagate_cancel = fields.Boolean(
'Propagate cancel and split', default=True,
help='If checked, when this move is cancelled, cancel the linked move too')
- propagate_date = fields.Boolean(string="Propagate Rescheduling",
- help='The rescheduling is propagated to the next move.')
- propagate_date_minimum_delta = fields.Integer(string='Reschedule if Higher Than',
- help='The change must be higher than this value to be propagated')
- delay_alert = fields.Boolean('Alert if Delay')
delay_alert_date = fields.Datetime('Delay Alert Date', help='Process at this date to be on time')
picking_type_id = fields.Many2one('stock.picking.type', 'Operation Type', check_company=True)
inventory_id = fields.Many2one('stock.inventory', 'Inventory', check_company=True)
@@ -446,7 +439,7 @@ class StockMove(models.Model):
move.json_forecast = json.dumps({'reservedAvailability': reserved_availability})
else:
move.json_forecast = json.dumps({
- 'sortingDate': format_date(self.env, found[0]['move_in'].date_expected, date_format='yyyy-MM-dd'),
+ 'sortingDate': format_date(self.env, found[0]['move_in'].date, date_format='yyyy-MM-dd'),
'expectedDate': found[0]["receipt_date_short"],
'isLate': found[0]["is_late"],
'replenishmentFilled': found[0]["replenishment_filled"]
@@ -454,6 +447,12 @@ class StockMove(models.Model):
else:
move.json_forecast = json.dumps({'expectedDate': None})
+ def _set_date_deadline(self):
+ # Handle the propagation of `date_deadline` fields (up and down stream - only update by up/downstream documents)
+ for move in self:
+ move_linked = (move.move_dest_ids | move.move_orig_ids).filtered(lambda m: m.state not in ('done', 'cancel'))
+ move_linked.filtered(lambda m: m.date_deadline != move.date_deadline).date_deadline = move.date_deadline
+
@api.constrains('product_uom')
def _check_uom(self):
moves_error = self.filtered(lambda move: move.product_id.uom_id.category_id != move.product_uom.category_id)
@@ -515,36 +514,24 @@ class StockMove(models.Model):
# When editing the initial demand, directly run again action assign on receipt moves.
receipt_moves_to_reassign |= move_to_unreserve.filtered(lambda m: m.location_id.usage == 'supplier')
receipt_moves_to_reassign |= (self - move_to_unreserve).filtered(lambda m: m.location_id.usage == 'supplier' and m.state in ('partially_available', 'assigned'))
-
- # Handle the propagation of `date_expected` and `date` fields.
- propagated_date_field = False
- if vals.get('date_expected'):
- propagated_date_field = 'date_expected'
- elif vals.get('state', '') == 'done' and vals.get('date'):
- propagated_date_field = 'date'
- if propagated_date_field:
- new_date = fields.Datetime.to_datetime(vals.get(propagated_date_field))
+ # Handle alert date about the scheduled date
+ if vals.get('date'):
+ new_date = fields.Datetime.to_datetime(vals.get('date'))
for move in self:
- move_dest_ids = move.move_dest_ids.filtered(lambda m: m.state not in ('done', 'cancel'))
- delta_days = (new_date - move.date_expected).total_seconds() / 86400
- if move.propagate_date and abs(delta_days) >= move.propagate_date_minimum_delta and move_dest_ids:
- for move_dest in move_dest_ids:
- # We want to propagate a negative delta, but not propagate an expected date
- # in the past.
- new_move_date = max(move_dest.date_expected + relativedelta.relativedelta(days=delta_days or 0), fields.Datetime.now())
- move_dest.date_expected = new_move_date
- move_dest_ids.filtered(lambda m: m.delay_alert)._propagate_date_log_note(move)
- if move.delay_alert:
- move._delay_alert_check(new_date)
+ move._delay_alert_check(new_date)
res = super(StockMove, self).write(vals)
- if vals.get('date_expected'):
- for move in self:
- if move.state not in ('done', 'cancel'):
- move.date = move.date_expected
+ if 'date_deadline' in vals:
+ self._set_date_deadline()
if receipt_moves_to_reassign:
receipt_moves_to_reassign._action_assign()
return res
+ @api.model_create_multi
+ def create(self, vals_list):
+ res = super().create(vals_list)
+ res.filtered('date_deadline')._set_date_deadline()
+ return res
+
def _delay_alert_get_documents(self):
"""Returns a list of recordset of the documents linked to the stock.move in `self` in order
to post the delay alert next activity. These documents are deduplicated. This method is meant
@@ -557,17 +544,15 @@ class StockMove(models.Model):
return list(self.mapped('picking_id'))
def _propagate_date_log_note(self, move_orig):
- """Post a delay alert log note on the documents linked to `self`.
-
- :param move_orig: the stock move triggering the delay alert on the next document
- """
+ """Post a deadline change alert log note on the documents linked to `self`."""
+ # TODO : get the end document (PO/SO/MO)
doc_orig = move_orig._delay_alert_get_documents()
documents = self._delay_alert_get_documents()
if not documents or not doc_orig:
return
- msg = _("The scheduled date has been automatically updated due to a delay on <a href='#' data-oe-model='%s' data-oe-id='%s'>%s</a>.") % (doc_orig[0]._name, doc_orig[0].id, doc_orig[0].name)
- msg_subject = _("Scheduled date update due to delay on %s", doc_orig[0].name)
+ msg = _("The deadline has been automatically updated due to a delay on <a href='#' data-oe-model='%s' data-oe-id='%s'>%s</a>.") % (doc_orig[0]._name, doc_orig[0].id, doc_orig[0].name)
+ msg_subject = _("Deadline updates due to delay on %s", doc_orig[0].name)
# write the message on each document
for doc in documents:
last_message = doc.message_ids[:1]
@@ -589,19 +574,12 @@ class StockMove(models.Model):
return
if new_date is None:
- new_date = self.date_expected
+ new_date = self.date
# Check if `self` is scheduled after the next moves. If so, the next moves are late.
- next_done_moves = self.browse()
- next_nondone_moves = self.browse()
- next_moves_dates = []
- for move in self.move_dest_ids:
- if move.state == 'done':
- next_done_moves |= move
- elif move.state != 'cancel':
- next_nondone_moves |= move
- next_moves_dates += next_done_moves.mapped('date')
- next_moves_dates += next_nondone_moves.mapped('date_expected')
+ next_moves = self.move_dest_ids.filtered(lambda move: move.state != 'cancel')
+ next_nondone_moves = next_moves.filtered(lambda move: move.state != 'done')
+ next_moves_dates = next_moves.mapped('date')
if next_moves_dates:
next_moves_date = min(next_moves_dates)
if new_date > next_moves_date:
@@ -610,16 +588,8 @@ class StockMove(models.Model):
next_nondone_moves.write({'delay_alert_date': False})
# Check if `self` is scheduled before the previous moves. If so, `self` if late.
- previous_done_moves = self.browse()
- previous_nondone_moves = self.browse()
- previous_moves_dates = []
- for move in self.move_orig_ids:
- if move.state == 'done':
- previous_done_moves |= move
- elif move.state != 'cancel':
- previous_nondone_moves |= move
- previous_moves_dates += previous_done_moves.mapped('date')
- previous_moves_dates += previous_nondone_moves.mapped('date_expected')
+ previous_moves = self.move_orig_ids.filtered(lambda move: move.state != 'cancel')
+ previous_moves_dates = previous_moves.mapped('date')
if previous_moves_dates:
previous_moves_date = max(previous_moves_dates)
if new_date < previous_moves_date:
@@ -772,8 +742,7 @@ class StockMove(models.Model):
origin = '/'.join(set(self.filtered(lambda m: m.origin).mapped('origin')))
return {
'product_uom_qty': sum(self.mapped('product_uom_qty')),
- 'date': min(self.mapped('date')),
- 'date_expected': min(self.mapped('date_expected')) if self.mapped('picking_id').move_type == 'direct' else max(self.mapped('date_expected')),
+ 'date': min(self.mapped('date')) if self.mapped('picking_id').move_type == 'direct' else max(self.mapped('date')),
'move_dest_ids': [(4, m.id) for m in self.mapped('move_dest_ids')],
'move_orig_ids': [(4, m.id) for m in self.mapped('move_orig_ids')],
'state': state,
@@ -785,8 +754,7 @@ class StockMove(models.Model):
return [
'product_id', 'price_unit', 'procure_method', 'location_id', 'location_dest_id',
'product_uom', 'restrict_partner_id', 'scrapped', 'origin_returned_move_id',
- 'package_level_id', 'propagate_cancel', 'propagate_date', 'propagate_date_minimum_delta',
- 'delay_alert', 'description_picking'
+ 'package_level_id', 'propagate_cancel', 'description_picking', 'date_deadline'
]
@api.model
@@ -795,8 +763,7 @@ class StockMove(models.Model):
return [
move.product_id.id, move.price_unit, move.procure_method, move.location_id, move.location_dest_id,
move.product_uom.id, move.restrict_partner_id.id, move.scrapped, move.origin_returned_move_id.id,
- move.package_level_id.id, move.propagate_cancel, move.propagate_date, move.propagate_date_minimum_delta,
- move.delay_alert, move.description_picking
+ move.package_level_id.id, move.propagate_cancel, move.description_picking
]
def _clean_merged(self):
@@ -1139,7 +1106,8 @@ class StockMove(models.Model):
group_id = False
return {
'product_description_variants': self.description_picking and self.description_picking.replace(self.product_id._get_description(self.picking_type_id), ''),
- 'date_planned': self.date_expected,
+ 'date_planned': self.date,
+ 'date_deadline': self.date_deadline,
'move_dest_ids': self,
'group_id': group_id,
'route_ids': self.route_ids,
diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py
index ad170bc57ae..12176eaeac8 100644
--- a/addons/stock/models/stock_move_line.py
+++ b/addons/stock/models/stock_move_line.py
@@ -572,7 +572,7 @@ class StockMoveLine(models.Model):
# We take the current picking first, then the pickings with the latest scheduled date
current_picking_first = lambda cand: (
cand.picking_id != self.move_id.picking_id,
- -(cand.picking_id.scheduled_date or cand.move_id.date_expected).timestamp()
+ -(cand.picking_id.scheduled_date or cand.move_id.date).timestamp()
if cand.picking_id or cand.move_id
else -cand.id,
)
diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py
index d28dde6de9b..95b77f2997c 100644
--- a/addons/stock/models/stock_picking.py
+++ b/addons/stock/models/stock_picking.py
@@ -240,7 +240,7 @@ class Picking(models.Model):
_name = "stock.picking"
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = "Transfer"
- _order = "priority desc, date asc, id desc"
+ _order = "priority desc, scheduled_date asc, id desc"
name = fields.Char(
'Reference', default='/',
@@ -287,6 +287,12 @@ class Picking(models.Model):
index=True, default=fields.Datetime.now, tracking=True,
states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
help="Scheduled time for the first part of the shipment to be processed. Setting manually a value here would set it as expected date for all the stock moves.")
+ date_deadline = fields.Datetime(
+ "Deadline", compute='_compute_date_deadline', store=True,
+ help="Date Promise to the customer on the top level document (SO/PO)")
+ has_deadline_issue = fields.Boolean(
+ "Is late", compute='_compute_has_deadline_issue', store=True, default=False,
+ help="Is late or will be late depending of the deadline and scheduled date")
date = fields.Datetime(
'Creation Date',
default=fields.Datetime.now, index=True, tracking=True,
@@ -383,6 +389,11 @@ class Picking(models.Model):
for picking in self:
picking.has_tracking = any(m.has_tracking != 'none' for m in picking.move_lines)
+ @api.depends('date_deadline', 'scheduled_date')
+ def _compute_has_deadline_issue(self):
+ for picking in self:
+ picking.has_deadline_issue = picking.date_deadline and picking.date_deadline < picking.scheduled_date or False
+
@api.depends('move_lines.delay_alert_date')
def _compute_delay_alert_date(self):
delay_alert_date_data = self.env['stock.move'].read_group([('id', 'in', self.move_lines.ids), ('delay_alert_date', '!=', False)], ['delay_alert_date:max'], 'picking_id')
@@ -437,6 +448,9 @@ class Picking(models.Model):
def _compute_json_popover(self):
for picking in self:
+ if picking.state in ('done', 'cancel') or not picking.delay_alert_date:
+ picking.json_popover = False
+ continue
picking.json_popover = json.dumps({
'popoverTemplate': 'stock.PopoverStockRescheduling',
'delay_alert_date': format_datetime(self.env, picking.delay_alert_date, dt_format=False) if picking.delay_alert_date else False,
@@ -480,19 +494,28 @@ class Picking(models.Model):
else:
picking.state = relevant_move_state
- @api.depends('move_lines.date_expected')
+ @api.depends('move_lines.state', 'move_lines.date', 'move_type')
def _compute_scheduled_date(self):
for picking in self:
+ moves_dates = picking.move_lines.filtered(lambda move: move.state not in ('done', 'cancel')).mapped('date')
+ if picking.move_type == 'direct':
+ picking.scheduled_date = min(moves_dates, default=picking.scheduled_date or fields.Datetime.now())
+ else:
+ picking.scheduled_date = max(moves_dates, default=picking.scheduled_date or fields.Datetime.now())
+
+ @api.depends('move_lines.date_deadline', 'move_type')
+ def _compute_date_deadline(self):
+ for picking in self:
if picking.move_type == 'direct':
- picking.scheduled_date = min(picking.move_lines.mapped('date_expected') or [fields.Datetime.now()])
+ picking.date_deadline = min(picking.move_lines.filtered('date_deadline').mapped('date_deadline'), default=False)
else:
- picking.scheduled_date = max(picking.move_lines.mapped('date_expected') or [fields.Datetime.now()])
+ picking.date_deadline = max(picking.move_lines.filtered('date_deadline').mapped('date_deadline'), default=False)
def _set_scheduled_date(self):
for picking in self:
if picking.state in ('done', 'cancel'):
raise UserError(_("You cannot change the Scheduled Date on a done or cancelled transfer."))
- picking.move_lines.write({'date_expected': picking.scheduled_date})
+ picking.move_lines.write({'date': picking.scheduled_date})
def _has_scrap_move(self):
for picking in self:
diff --git a/addons/stock/models/stock_rule.py b/addons/stock/models/stock_rule.py
index a4d10b153ad..0c0ca8ef9de 100644
--- a/addons/stock/models/stock_rule.py
+++ b/addons/stock/models/stock_rule.py
@@ -89,14 +89,6 @@ class StockRule(models.Model):
help="The 'Manual Operation' value will create a stock move after the current one. "
"With 'Automatic No Step Added', the location is replaced in the original move.")
rule_message = fields.Html(compute='_compute_action_message')
- propagate_date = fields.Boolean(string="Propagate Rescheduling", default=True,
- help='The rescheduling is propagated to the next move.')
- propagate_date_minimum_delta = fields.Integer(string='Reschedule if Higher Than',
- help='The change must be higher than this value to be propagated', default=1)
- delay_alert = fields.Boolean(
- 'Alert if Delay',
- help='Log an exception on the picking if this move has to be delayed (due to a change in the previous move scheduled date).',
- )
@api.onchange('picking_type_id')
def _onchange_picking_type(self):
@@ -106,8 +98,6 @@ class StockRule(models.Model):
"""
self.location_src_id = self.picking_type_id.default_location_src_id.id
self.location_id = self.picking_type_id.default_location_dest_id.id
- if self.picking_type_id.code == 'outgoing':
- self.delay_alert = True
@api.onchange('route_id', 'company_id')
def _onchange_route(self):
@@ -174,12 +164,9 @@ class StockRule(models.Model):
Care this function is not call by method run. It is called explicitely
in stock_move.py inside the method _push_apply
"""
- new_date = fields.Datetime.to_string(move.date_expected + relativedelta(days=self.delay))
+ new_date = fields.Datetime.to_string(move.date + relativedelta(days=self.delay))
if self.auto == 'transparent':
- move.write({
- 'date': new_date,
- 'date_expected': new_date,
- 'location_dest_id': self.location_id.id})
+ move.write({'date': new_date, 'location_dest_id': self.location_id.id})
# avoid looping if a push rule is not well configured; otherwise call again push_apply to see if a next step is defined
if self.location_id != move.location_dest_id:
# TDE FIXME: should probably be done in the move model IMO
@@ -202,15 +189,11 @@ class StockRule(models.Model):
'location_id': move_to_copy.location_dest_id.id,
'location_dest_id': self.location_id.id,
'date': new_date,
- 'date_expected': new_date,
'company_id': company_id,
'picking_id': False,
'picking_type_id': self.picking_type_id.id,
'propagate_cancel': self.propagate_cancel,
- 'propagate_date': self.propagate_date,
- 'propagate_date_minimum_delta': self.propagate_date_minimum_delta,
'warehouse_id': self.warehouse_id.id,
- 'delay_alert': self.delay_alert,
'procure_method': 'make_to_order',
}
return new_move_vals
@@ -280,7 +263,7 @@ class StockRule(models.Model):
elif self.group_propagation_option == 'fixed':
group_id = self.group_id.id
- date_expected = fields.Datetime.to_string(
+ date_scheduled = fields.Datetime.to_string(
fields.Datetime.from_string(values['date_planned']) - relativedelta(days=self.delay or 0)
)
partner = self.partner_address_id or (values.get('group_id', False) and values['group_id'].partner_id)
@@ -314,14 +297,11 @@ class StockRule(models.Model):
'group_id': group_id,
'route_ids': [(4, route.id) for route in values.get('route_ids', [])],
'warehouse_id': self.propagate_warehouse_id.id or self.warehouse_id.id,
- 'date': date_expected,
- 'date_expected': date_expected,
+ 'date': date_scheduled,
+ 'date_deadline': values.get('date_deadline'),
'propagate_cancel': self.propagate_cancel,
- 'propagate_date': self.propagate_date,
- 'propagate_date_minimum_delta': self.propagate_date_minimum_delta,
'description_picking': picking_description,
'priority': values.get('priority', "0"),
- 'delay_alert': self.delay_alert,
'orderpoint_id': values.get('orderpoint_id') and values['orderpoint_id'].id,
}
for field in self._get_custom_move_fields():
@@ -510,7 +490,7 @@ class ProcurementGroup(models.Model):
# Search all confirmed stock_moves and try to assign them
domain = self._get_moves_to_assign_domain()
moves_to_assign = self.env['stock.move'].search(domain, limit=None,
- order='priority desc, date_expected asc')
+ order='priority desc, date asc')
for moves_chunk in split_every(100, moves_to_assign.ids):
self.env['stock.move'].browse(moves_chunk)._action_assign()
if use_new_cursor:
diff --git a/addons/stock/models/stock_warehouse.py b/addons/stock/models/stock_warehouse.py
index 2a981782395..a0c9de9a738 100644
--- a/addons/stock/models/stock_warehouse.py
+++ b/addons/stock/models/stock_warehouse.py
@@ -381,7 +381,6 @@ class Warehouse(models.Model):
'company_id': self.company_id.id,
'action': 'pull',
'auto': 'manual',
- 'delay_alert': True,
'route_id': self._find_global_route('stock.route_warehouse0_mto', _('Make To Order')).id
},
'update_values': {
@@ -771,7 +770,6 @@ class Warehouse(models.Model):
'procure_method': first_rule and 'make_to_stock' or 'make_to_order',
'warehouse_id': self.id,
'company_id': self.company_id.id,
- 'delay_alert': routing.picking_type.code == 'outgoing',
}
route_rule_values.update(values or {})
rules_list.append(route_rule_values)
diff --git a/addons/stock/report/report_stock_forecasted.py b/addons/stock/report/report_stock_forecasted.py
index 9d4ae670fa3..1316f24858d 100644
--- a/addons/stock/report/report_stock_forecasted.py
+++ b/addons/stock/report/report_stock_forecasted.py
@@ -112,7 +112,7 @@ class ReplenishmentReport(models.AbstractModel):
def _prepare_report_line(self, quantity, move_out=None, move_in=None, replenishment_filled=True, product=False):
timezone = self._context.get('tz')
product = product or (move_out.product_id if move_out else move_in.product_id)
- is_late = move_out.date_expected < move_in.date_expected if (move_out and move_in) else False
+ is_late = move_out.date < move_in.date if (move_out and move_in) else False
return {
'document_in': move_in._get_source_document() if move_in else False,
'document_out': move_out._get_source_document() if move_out else False,
@@ -122,10 +122,10 @@ class ReplenishmentReport(models.AbstractModel):
},
'replenishment_filled': replenishment_filled,
'uom_id': product.uom_id,
- 'receipt_date': format_datetime(self.env, move_in.date_expected, timezone, 'medium') if move_in else False,
- 'delivery_date': format_datetime(self.env, move_out.date_expected, timezone, 'medium') if move_out else False,
- 'receipt_date_short': format_date(self.env, move_in.date_expected) if move_in else False,
- 'delivery_date_short': format_date(self.env, move_out.date_expected) if move_out else False,
+ 'receipt_date': format_datetime(self.env, move_in.date, timezone, 'medium') if move_in else False,
+ 'delivery_date': format_datetime(self.env, move_out.date, timezone, 'medium') if move_out else False,
+ 'receipt_date_short': format_date(self.env, move_in.date) if move_in else False,
+ 'delivery_date_short': format_date(self.env, move_out.date) if move_out else False,
'is_late': is_late,
'quantity': quantity,
'move_out': move_out,
@@ -136,11 +136,11 @@ class ReplenishmentReport(models.AbstractModel):
in_domain, out_domain = self._move_confirmed_domain(
product_template_ids, product_variant_ids, wh_location_ids
)
- outs = self.env['stock.move'].search(out_domain, order='priority desc, date_expected, id')
+ outs = self.env['stock.move'].search(out_domain, order='priority desc, date, id')
outs_per_product = defaultdict(lambda: [])
for out in outs:
outs_per_product[out.product_id.id].append(out)
- ins = self.env['stock.move'].search(in_domain, order='priority desc, date_expected, id')
+ ins = self.env['stock.move'].search(in_domain, order='priority desc, date, id')
ins_per_product = defaultdict(lambda: [])
for in_ in ins:
ins_per_product[in_.product_id.id].append([in_.product_qty, in_])
diff --git a/addons/stock/report/report_stock_quantity.py b/addons/stock/report/report_stock_quantity.py
index 4a054285df0..0a6d35f3c73 100644
--- a/addons/stock/report/report_stock_quantity.py
+++ b/addons/stock/report/report_stock_quantity.py
@@ -33,7 +33,7 @@ SELECT
WHEN (whs.id IS NOT NULL AND whd.id IS NULL) OR ls.usage = 'transit' THEN 'out'
WHEN (whs.id IS NULL AND whd.id IS NOT NULL) OR ld.usage = 'transit' THEN 'in'
END AS state,
- m.date_expected::date AS date,
+ m.date::date AS date,
CASE
WHEN (whs.id IS NOT NULL AND whd.id IS NULL) OR ls.usage = 'transit' THEN -product_qty
WHEN (whs.id IS NULL AND whd.id IS NOT NULL) OR ld.usage = 'transit' THEN product_qty
@@ -83,7 +83,7 @@ SELECT
GENERATE_SERIES(
CASE
WHEN m.state = 'done' THEN (now() at time zone 'utc')::date - interval '3month'
- ELSE m.date_expected::date
+ ELSE m.date::date
END,
CASE
WHEN m.state != 'done' THEN (now() at time zone 'utc')::date + interval '3 month'
diff --git a/addons/stock/static/src/xml/popover_widget.xml b/addons/stock/static/src/xml/popover_widget.xml
index d9cf2b4fe8d..d5c503565ec 100644
--- a/addons/stock/static/src/xml/popover_widget.xml
+++ b/addons/stock/static/src/xml/popover_widget.xml
@@ -11,9 +11,9 @@
<div t-name="stock.PopoverStockRescheduling">
<p>Preceding operations
- <t t-foreach="late_elements" t-as="late_element">
- <a t-esc="late_element.name" href="#" t-att-element-id="late_element.id" t-att-element-model="late_element.model"/>,
- </t>
- planned on <t t-esc="delay_alert_date"/>.</p>
+ <t t-foreach="late_elements" t-as="late_element">
+ <a t-esc="late_element.name" href="#" t-att-element-id="late_element.id" t-att-element-model="late_element.model"/>,
+ </t>
+ planned on <t t-esc="delay_alert_date"/>.</p>
</div>
</templates>
diff --git a/addons/stock/tests/test_move2.py b/addons/stock/tests/test_move2.py
index 0dd9e044a8e..cf3d945428e 100644
--- a/addons/stock/tests/test_move2.py
+++ b/addons/stock/tests/test_move2.py
@@ -2562,150 +2562,6 @@ class TestRoutes(TestStockCommon):
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.
- by default:
- - delay alert set is only ship rule
- - propagate date is True on all the pick-pack-ship rules
- """
- warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
- warehouse.delivery_steps = 'pick_pack_ship'
- partner_demo_customer = self.partner
- final_location = partner_demo_customer.property_stock_customer
- product_a = self.env['product.product'].create({
- 'name': 'ProductA',
- 'type': 'product',
- })
- product_b = self.env['product.product'].create({
- 'name': 'ProductA',
- 'type': 'product',
- })
-
- pg = self.env['procurement.group'].create({'name': 'Test-delay_alert'})
-
- self.env['procurement.group'].run([
- pg.Procurement(
- product_a,
- 4.0,
- product_a.uom_id,
- final_location,
- 'delay',
- 'delay',
- warehouse.company_id,
- {
- 'warehouse_id': warehouse,
- 'group_id': pg
- }
- ),
- pg.Procurement(
- product_b,
- 4.0,
- product_a.uom_id,
- final_location,
- 'delay',
- 'delay',
- warehouse.company_id,
- {
- 'warehouse_id': warehouse,
- 'group_id': pg
- }
- )
- ])
- first_move = self.env['stock.move'].search([
- ('product_id', '=', product_a.id),
- ('state', '=', 'confirmed')
- ])
- # Get back pack and ship pickings.
- picking_pack = first_move.move_dest_ids.picking_id
- picking_ship = first_move.move_dest_ids.move_dest_ids.picking_id
- self.assertEqual(len(picking_pack.message_ids), 1)
- self.assertEqual(len(picking_ship.message_ids), 1)
-
- # Change the schedule date on the pick.
- first_move.picking_id.scheduled_date += timedelta(days=2)
-
- # No activity should be created on the pack.
- self.assertEqual(len(picking_pack.message_ids), 1)
-
- # An activity is created on the ship.
- self.assertEqual(len(picking_ship.message_ids), 2)
- self.assertTrue('has been automatically' in picking_ship.message_ids[0].body)
-
- # Change second time the schedule date on the pick.
- first_move.picking_id.scheduled_date += timedelta(days=2)
- self.assertEqual(len(picking_ship.message_ids), 2)
-
- def test_delay_alert_2(self):
- """ On a pick ship scenario, two pick linked to a ship. The delay alert is set on the ship rule?
- When editing the schedule date on the two pick, two delay alerts activty should be created
- on the pack.
- """
- self._enable_pick_ship()
-
- # create a procurement group and set in on the pick stock rule
- procurement_group0 = self.env['procurement.group'].create({})
- procurement_group1 = self.env['procurement.group'].create({})
- pick_rule = self.pick_ship_route.rule_ids.filtered(lambda rule: 'Stock → Output' in rule.name)
- ship_rule = self.pick_ship_route.rule_ids - pick_rule
- ship_rule.write({
- 'group_propagation_option': 'fixed',
- 'group_id': procurement_group0.id,
- })
-
- ship_location = pick_rule.location_id
- customer_location = ship_rule.location_id
- product1 = self.env['product.product'].create({'name': 'product1'})
- product2 = self.env['product.product'].create({'name': 'product2'})
-
- picking_ship = self.env['stock.picking'].create({
- 'location_id': ship_location.id,
- 'location_dest_id': self.customer_location,
- 'picking_type_id': self.picking_type_out,
- })
-
- move1 = self.env['stock.move'].create({
- 'name': 'first out move',
- 'procure_method': 'make_to_order',
- 'location_id': ship_location.id,
- 'location_dest_id': customer_location.id,
- 'product_id': product1.id,
- 'product_uom': self.uom_unit.id,
- 'product_uom_qty': 1.0,
- 'warehouse_id': self.wh.id,
- 'group_id': procurement_group0.id,
- 'origin': 'origin1',
- 'picking_id': picking_ship.id,
- 'delay_alert': True,
- })
-
- move2 = self.env['stock.move'].create({
- 'name': 'second out move',
- 'procure_method': 'make_to_order',
- 'location_id': ship_location.id,
- 'location_dest_id': customer_location.id,
- 'product_id': product2.id,
- 'product_uom': self.uom_unit.id,
- 'product_uom_qty': 1.0,
- 'warehouse_id': self.wh.id,
- 'group_id': procurement_group1.id,
- 'origin': 'origin2',
- 'picking_id': picking_ship.id,
- 'delay_alert': True,
- })
-
- # confirm the picking to create the orig moves
- picking_ship.action_confirm()
- picking_pick_1 = move1.move_orig_ids.picking_id
- picking_pick_2 = move2.move_orig_ids.picking_id
-
- picking_pick_1.scheduled_date += timedelta(days=2)
- picking_pick_2.scheduled_date += timedelta(days=2)
- messages = picking_ship.message_ids
- self.assertEqual(len(messages), 3, 'not enough messages logged')
- self.assertTrue(picking_pick_1.name in messages[0].body + messages[1].body, 'Wrong message')
- self.assertTrue(picking_pick_2.name in messages[0].body + messages[1].body, 'Wrong message')
-
def test_delay_alert_3(self):
warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
warehouse.delivery_steps = 'pick_pack_ship'
@@ -2732,61 +2588,59 @@ class TestRoutes(TestStockCommon):
),
])
ship, pack, pick = self.env['stock.move'].search([('product_id', '=', product_a.id)])
- (ship + pack + pick).propagate_date = False
- (ship + pack + pick).delay_alert = True
- # by default they all the the same `date_expected`
- self.assertEqual(set((ship + pack + pick).mapped('date_expected')), {pick.date_expected})
+ # by default they all the the same `date`
+ self.assertEqual(set((ship + pack + pick).mapped('date')), {pick.date})
# pick - pack - ship
- ship.date_expected += timedelta(days=2)
- pack.date_expected += timedelta(days=1)
+ ship.date += timedelta(days=2)
+ pack.date += timedelta(days=1)
self.assertFalse(pick.delay_alert_date)
self.assertFalse(pack.delay_alert_date)
self.assertFalse(ship.delay_alert_date)
# move the pack after the ship
# pick - ship - pack
- pack.date_expected += timedelta(days=2)
+ pack.date += timedelta(days=2)
self.assertFalse(pick.delay_alert_date)
self.assertFalse(pack.delay_alert_date)
self.assertTrue(ship.delay_alert_date)
- self.assertAlmostEqual(ship.delay_alert_date, pack.date_expected)
+ self.assertAlmostEqual(ship.delay_alert_date, pack.date)
# restore the pack before the ship
# pick - pack - ship
- pack.date_expected -= timedelta(days=2)
+ pack.date -= timedelta(days=2)
self.assertFalse(pick.delay_alert_date)
self.assertFalse(pack.delay_alert_date)
self.assertFalse(ship.delay_alert_date)
# move the pick after the pack
# pack - ship - pick
- pick.date_expected += timedelta(days=3)
+ pick.date += timedelta(days=3)
self.assertFalse(pick.delay_alert_date)
self.assertTrue(pack.delay_alert_date)
self.assertFalse(ship.delay_alert_date)
- self.assertAlmostEqual(pack.delay_alert_date, pick.date_expected)
+ self.assertAlmostEqual(pack.delay_alert_date, pick.date)
# move the ship before the pack
# ship - pack - pick
- ship.date_expected -= timedelta(days=2)
+ ship.date -= timedelta(days=2)
self.assertFalse(pick.delay_alert_date)
self.assertTrue(pack.delay_alert_date)
self.assertTrue(ship.delay_alert_date)
- self.assertAlmostEqual(pack.delay_alert_date, pick.date_expected)
- self.assertAlmostEqual(ship.delay_alert_date, pack.date_expected)
+ self.assertAlmostEqual(pack.delay_alert_date, pick.date)
+ self.assertAlmostEqual(ship.delay_alert_date, pack.date)
# move the pack at the end
# ship - pick - pack
- pack.date_expected = pick.date_expected + timedelta(days=2)
+ pack.date = pick.date + timedelta(days=2)
self.assertFalse(pick.delay_alert_date)
self.assertFalse(pack.delay_alert_date)
self.assertTrue(ship.delay_alert_date)
- self.assertAlmostEqual(ship.delay_alert_date, pack.date_expected)
+ self.assertAlmostEqual(ship.delay_alert_date, pack.date)
# fix the ship
- ship.date_expected = pack.date_expected + timedelta(days=2)
+ ship.date = pack.date + timedelta(days=2)
self.assertFalse(pick.delay_alert_date)
self.assertFalse(pack.delay_alert_date)
self.assertFalse(ship.delay_alert_date)
diff --git a/addons/stock/tests/test_proc_rule.py b/addons/stock/tests/test_proc_rule.py
index a8c6327fc7f..f9885a5a992 100644
--- a/addons/stock/tests/test_proc_rule.py
+++ b/addons/stock/tests/test_proc_rule.py
@@ -77,11 +77,13 @@ class TestProcRule(TransactionCase):
])
self.assertEqual(len(moves.ids), 1, "It should have created a picking from Stock to Output with the original picking as destination")
- def test_rule_propagate_1(self):
+ def test_propagate_deadline_move(self):
+ deadline = datetime.now()
move_dest = self.env['stock.move'].create({
'name': 'move_dest',
'product_id': self.product.id,
'product_uom': self.uom_unit.id,
+ 'date_deadline': deadline,
'location_id': self.ref('stock.stock_location_output'),
'location_dest_id': self.ref('stock.stock_location_customers'),
})
@@ -90,82 +92,19 @@ class TestProcRule(TransactionCase):
'name': 'move_orig',
'product_id': self.product.id,
'product_uom': self.uom_unit.id,
+ 'date_deadline': deadline,
'move_dest_ids': [(4, move_dest.id)],
- 'propagate_date': True,
- 'propagate_date_minimum_delta': 5,
- 'location_id': self.ref('stock.stock_location_stock'),
- 'location_dest_id': self.ref('stock.stock_location_output'),
- })
-
- move_dest_initial_date = move_dest.date_expected
-
- # change above the minimum delta
- move_orig.date_expected += timedelta(days=6)
- self.assertAlmostEqual(move_dest.date_expected, move_dest_initial_date + timedelta(days=6), delta=timedelta(seconds=10), msg='date should be propagated as the minimum delta is below')
-
- # change below the minimum delta
- move_dest_initial_date = move_dest.date_expected
- move_orig.date_expected += timedelta(days=4)
-
- self.assertAlmostEqual(move_dest.date_expected, move_dest_initial_date, delta=timedelta(seconds=10), msg='date should not be propagated as the minimum delta is above')
-
- def test_rule_propagate_2(self):
- move_dest = self.env['stock.move'].create({
- 'name': 'move_dest',
- 'product_id': self.product.id,
- 'product_uom': self.uom_unit.id,
- 'location_id': self.ref('stock.stock_location_output'),
- 'location_dest_id': self.ref('stock.stock_location_customers'),
- })
-
- move_orig = self.env['stock.move'].create({
- 'name': 'move_orig',
- 'product_id': self.product.id,
- 'product_uom': self.uom_unit.id,
- 'move_dest_ids': [(4, move_dest.id)],
- 'propagate_date': False,
- 'propagate_date_minimum_delta': 5,
- 'location_id': self.ref('stock.stock_location_stock'),
- 'location_dest_id': self.ref('stock.stock_location_output'),
- })
-
- move_dest_initial_date = move_dest.date_expected
-
- # change below the minimum delta
- move_orig.date_expected += timedelta(days=4)
- self.assertAlmostEqual(move_dest.date_expected, move_dest_initial_date, delta=timedelta(seconds=10), msg='date should not be propagated')
-
- # change above the minimum delta
- move_orig.date_expected += timedelta(days=2)
- self.assertAlmostEqual(move_dest.date_expected, move_dest_initial_date, delta=timedelta(seconds=10), msg='date should not be propagated')
-
- def test_rule_propagate_3(self):
- move_dest = self.env['stock.move'].create({
- 'name': 'move_dest',
- 'product_id': self.product.id,
- 'product_uom': self.uom_unit.id,
- 'location_id': self.ref('stock.stock_location_output'),
- 'location_dest_id': self.ref('stock.stock_location_customers'),
- })
-
- move_orig = self.env['stock.move'].create({
- 'name': 'move_orig',
- 'product_id': self.product.id,
- 'product_uom': self.uom_unit.id,
- 'move_dest_ids': [(4, move_dest.id)],
- 'propagate_date': True,
- 'propagate_date_minimum_delta': 5,
'location_id': self.ref('stock.stock_location_stock'),
'location_dest_id': self.ref('stock.stock_location_output'),
'quantity_done': 10,
})
- move_orig.date_expected -= timedelta(days=6)
- move_dest_initial_date = move_dest.date_expected
- move_orig_initial_date = move_orig.date_expected
+ new_deadline = move_orig.date_deadline - timedelta(days=6)
+ move_orig.date_deadline = new_deadline
+ self.assertEqual(move_dest.date_deadline, new_deadline, msg='deadline date should be propagated')
move_orig._action_done()
- self.assertAlmostEqual(move_orig.date_expected, move_orig_initial_date, delta=timedelta(seconds=10), msg='schedule date should not be impacted by action_done')
self.assertAlmostEqual(move_orig.date, datetime.now(), delta=timedelta(seconds=10), msg='date should be now')
- self.assertAlmostEqual(move_dest.date_expected, move_dest_initial_date + timedelta(days=6), delta=timedelta(seconds=10), msg='date should be propagated')
+ self.assertEqual(move_orig.date_deadline, new_deadline, msg='deadline date should be unchanged')
+ self.assertEqual(move_dest.date_deadline, new_deadline, msg='deadline date should be unchanged')
def test_reordering_rule_1(self):
warehouse = self.env['stock.warehouse'].search([], limit=1)
@@ -190,7 +129,7 @@ class TestProcRule(TransactionCase):
delivery_move = self.env['stock.move'].create({
'name': 'Delivery',
- 'date_expected': datetime.today() + timedelta(days=5),
+ 'date': datetime.today() + timedelta(days=5),
'product_id': self.product.id,
'product_uom': self.uom_unit.id,
'product_uom_qty': 12.0,
@@ -206,7 +145,7 @@ class TestProcRule(TransactionCase):
('location_id', '=', self.env.ref('stock.stock_location_suppliers').id)
])
self.assertTrue(receipt_move)
- self.assertEqual(receipt_move.date_expected.date(), date.today())
+ self.assertEqual(receipt_move.date.date(), date.today())
self.assertEqual(receipt_move.product_uom_qty, 17.0)
def test_reordering_rule_2(self):
@@ -259,7 +198,7 @@ class TestProcRule(TransactionCase):
('location_id', '=', self.env.ref('stock.stock_location_suppliers').id)
])
self.assertTrue(receipt_move)
- self.assertEqual(receipt_move.date_expected.date(), date.today())
+ self.assertEqual(receipt_move.date.date(), date.today())
self.assertEqual(receipt_move.product_uom_qty, 17.0)
diff --git a/addons/stock/tests/test_report.py b/addons/stock/tests/test_report.py
index e282d3c941a..ab5ebedcd40 100644
--- a/addons/stock/tests/test_report.py
+++ b/addons/stock/tests/test_report.py
@@ -96,7 +96,7 @@ class TestReports(TestReportsCommon):
# Delivery of 20.0 units tomorrow
move_out = self.env['stock.move'].create({
'name': 'Move Out 20',
- 'date_expected': datetime.now() + timedelta(days=1),
+ 'date': datetime.now() + timedelta(days=1),
'location_id': stock.id,
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
'product_id': product.id,
@@ -123,7 +123,7 @@ class TestReports(TestReportsCommon):
# Receipt of 10.0 units tomorrow
move_in = self.env['stock.move'].create({
'name': 'Move In 10',
- 'date_expected': datetime.now() + timedelta(days=1),
+ 'date': datetime.now() + timedelta(days=1),
'location_id': self.env.ref('stock.stock_location_suppliers').id,
'location_dest_id': stock.id,
'product_id': product.id,
@@ -146,7 +146,7 @@ class TestReports(TestReportsCommon):
# Delivery of 20.0 units tomorrow
move_out = self.env['stock.move'].create({
'name': 'Move Out 30 - Day-1',
- 'date_expected': datetime.now() - timedelta(days=1),
+ 'date': datetime.now() - timedelta(days=1),
'location_id': stock.id,
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
'product_id': product.id,
diff --git a/addons/stock/views/stock_move_views.xml b/addons/stock/views/stock_move_views.xml
index 6d6cd999ed9..7496de3784d 100644
--- a/addons/stock/views/stock_move_views.xml
+++ b/addons/stock/views/stock_move_views.xml
@@ -13,8 +13,7 @@
<field name="location_id" options="{'no_create': True}" groups="stock.group_stock_multi_locations"/>
<field name="location_dest_id" options="{'no_create': True}" groups="stock.group_stock_multi_locations"/>
<field name="create_date" groups="base.group_no_one"/>
- <field name="date" string="Date" groups="base.group_no_one"/>
- <field name="date_expected" string="Date Expected"/>
+ <field name="date" groups="base.group_no_one"/>
<field name="state"/>
</tree>
</field>
@@ -74,7 +73,7 @@
<tree decoration-danger="state != 'done' and quantity_done > reserved_availability and show_reserved_availability" decoration-muted="scrapped == True or state == 'cancel' or (state == 'done' and is_locked == True)" string="Stock Moves" editable="bottom">
<field name="company_id" invisible="1"/>
<field name="name" invisible="1"/>
- <field name="date_expected" invisible="1"/>
+ <field name="date" invisible="1"/>
<field name="state" invisible="1" readonly="0"/>
<field name="picking_type_id" invisible="1"/>
<field name="location_id" invisible="1"/>
@@ -158,7 +157,7 @@
<field name="product_uom" options="{'no_open': True, 'no_create': True}" string="Unit of Measure" groups="uom.group_uom"/>
<field name="location_id" options="{'no_create': True}" invisible="1"/>
<field name="create_date" options="{'no_create': True}" invisible="1" />
- <field name="date_expected" invisible="1" />
+ <field name="date" invisible="1" />
<field name="picking_type_id" invisible="1"/>
<field name="scrapped" invisible="1"/>
<field name="availability" invisible="1"/>
@@ -337,8 +336,12 @@
<field name="product_uom" options="{'no_open': True, 'no_create': True}" groups="uom.group_uom"/>
</div>
<field name="name" invisible="1"/>
- <field name="date_expected"/>
- <field name="date" attrs="{'invisible': [('state', '!=', 'done')]}"/>
+ <div class="o_td_label">
+ <label for="date" string="Date Scheduled" attrs="{'invisible': [('state', '=', 'done')]}"/>
+ <label for="date" string="Date Processing" attrs="{'invisible': [('state', '!=', 'done')]}"/>
+ </div>
+ <field name="date" readonly="1" nolabel="1"/>
+ <field name="date_deadline" force_save="1"/>
</group>
</group>
<group name="origin_grp" string="Origin" groups="base.group_no_one" >
@@ -397,7 +400,7 @@
<filter string="Destination Location" name="groupby_dest_location_id" domain="[]" context="{'group_by': 'location_dest_id'}" groups="stock.group_stock_multi_locations"/>
<filter string="Status" name="status" domain="[]" context="{'group_by': 'state'}"/>
<filter string="Creation Date" name="groupby_create_date" domain="[]" context="{'group_by': 'create_date'}" groups="base.group_no_one"/>
- <filter string="Scheduled Date" name="groupby_date" domain="[]" context="{'group_by': 'date_expected'}"/>
+ <filter string="Scheduled Date" name="groupby_date" domain="[]" context="{'group_by': 'date'}"/>
</group>
</search>
</field>
@@ -466,9 +469,9 @@
<field name="model">stock.move</field>
<field name="priority" eval="6"/>
<field name="arch" type="xml">
- <tree decoration-muted="state == 'cancel'" default_order='date_expected, picking_id, sequence' string="Moves" create="0">
- <field name="date_expected" widget='date'/>
- <field name="date" string="Creation Date" widget="date" optional="hide"/>
+ <tree decoration-muted="state == 'cancel'" default_order='date, picking_id, sequence' string="Moves" create="0">
+ <field name="date" invisible="1"/>
+ <field name="date_deadline" optional="hide"/>
<field name="picking_id" string="Reference" invisible="1"/>
<field name="sequence" invisible="1"/>
<field name="origin" optional="show"/>
diff --git a/addons/stock/views/stock_picking_views.xml b/addons/stock/views/stock_picking_views.xml
index 646082c222d..901afe3b37f 100644
--- a/addons/stock/views/stock_picking_views.xml
+++ b/addons/stock/views/stock_picking_views.xml
@@ -153,7 +153,7 @@
<field name="partner_id"/>
<field name="location_dest_id"/>
<field name="state"/>
- <field name="date"/>
+ <field name="scheduled_date"/>
<field name="activity_state"/>
<progressbar field="activity_state" colors='{"planned": "success", "today": "warning", "overdue": "danger"}'/>
<templates>
@@ -172,11 +172,10 @@
<div class="oe_kanban_bottom_left">
<t t-esc="record.partner_id.value"/>
<field name="activity_ids" widget="kanban_activity"/>
- <field name="delay_alert_date" invisible="1"/>
- <field name="json_popover" string=" " widget="stock_rescheduling_popover" attrs="{'invisible': [('delay_alert_date', '=', False)]}"/>
+ <field name="json_popover" nolabel="1" widget="stock_rescheduling_popover" attrs="{'invisible': [('json_popover', '=', False)]}"/>
</div>
<div class="oe_kanban_bottom_right">
- <t t-esc="record.date.value and record.date.value.split(' ')[0] or False"/>
+ <t t-esc="record.scheduled_date.value and record.scheduled_date.value.split(' ')[0] or False"/>
</div>
</div>
</div>
@@ -205,8 +204,8 @@
<field name="products_availability" optional="show"
decoration-danger="products_availability_state == 'late'"
decoration-warning="products_availability_state == 'expected'"/>
- <field name="date" invisible="1"/>
- <field name="scheduled_date" optional="show" decoration-danger="scheduled_date &lt; current_date and state not in ('done', 'cancel')"/>
+ <field name="scheduled_date" optional="show" widget="remaining_days" attrs="{'invisible':[('state', 'in', ('done', 'cancel'))]}"/>
+ <field name="date_deadline" optional="hide" widget="remaining_days" attrs="{'invisible':[('state', 'in', ('done', 'cancel'))]}"/>
<field name="origin" optional="show"/>
<field name="group_id" invisible="1"/>
<field name="backorder_id" optional="hide"/>
@@ -214,8 +213,7 @@
<field name="company_id" groups="base.group_multi_company" optional="show"/>
<field name="state" optional="show" widget="badge" decoration-success="state == 'done'" decoration-info="state not in ('done', 'cancel')"/>
<field name="activity_exception_decoration" widget="activity_exception"/>
- <field name="delay_alert_date" invisible="1"/>
- <field name="json_popover" string=" " widget="stock_rescheduling_popover" attrs="{'invisible': [('delay_alert_date', '=', False)]}"/>
+ <field name="json_popover" nolabel="1" widget="stock_rescheduling_popover" attrs="{'invisible': [('json_popover', '=', False)]}"/>
</tree>
</field>
</record>
@@ -311,10 +309,16 @@
<group>
<label for="scheduled_date"/>
<div class="o_row">
- <field name="scheduled_date" attrs="{'required': [('id', '!=', False)]}"/>
- <field name="delay_alert_date" invisible="1"/>
- <field name="json_popover" widget="stock_rescheduling_popover" attrs="{'invisible': [('delay_alert_date', '=', False)]}"/>
+ <field name="scheduled_date" attrs="{'required': [('id', '!=', False)]}"
+ decoration-warning="state not in ('done', 'cancel') and scheduled_date &lt; now"
+ decoration-danger="state not in ('done', 'cancel') and scheduled_date &lt; current_date"
+ decoration-bf="state not in ('done', 'cancel') and (scheduled_date &lt; current_date or scheduled_date &lt; now)"/>
+ <field name="json_popover" widget="stock_rescheduling_popover" attrs="{'invisible': [('json_popover', '=', False)]}"/>
</div>
+ <field name="date_deadline"
+ attrs="{'invisible': ['|', ('state', 'in', ('done', 'cancel')), ('date_deadline', '=', False)]}"
+ decoration-danger="date_deadline and date_deadline &lt; current_date"
+ decoration-bf="date_deadline and date_deadline &lt; current_date"/>
<field name="date_done" string="Effective Date" attrs="{'invisible': [('state', '!=', 'done')]}"/>
<field name="origin" placeholder="e.g. PO0032"/>
<field name="owner_id" groups="stock.group_tracking_owner"
@@ -349,7 +353,7 @@
<page string="Operations" name="operations">
<field name="move_ids_without_package" mode="tree,kanban"
attrs="{'readonly': ['&amp;', ('state', '=', 'done'), ('is_locked', '=', True)]}"
- context="{'default_company_id': company_id, 'picking_type_code': picking_type_code, 'default_picking_id': id, 'form_view_ref':'stock.view_move_form', 'address_in_id': partner_id, 'default_picking_type_id': picking_type_id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id}">
+ context="{'default_company_id': company_id, 'default_date': scheduled_date, 'default_date_deadline': date_deadline, 'picking_type_code': picking_type_code, 'default_picking_id': id, 'form_view_ref':'stock.view_move_form', 'address_in_id': partner_id, 'default_picking_type_id': picking_type_id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id}">
<tree decoration-danger="not parent.immediate_transfer and state != 'done' and quantity_done > reserved_availability and show_reserved_availability" decoration-muted="scrapped == True or state == 'cancel' or (state == 'done' and is_locked == True)" string="Stock Moves" editable="bottom">
<field name="company_id" invisible="1"/>
<field name="name" invisible="1"/>
@@ -371,7 +375,8 @@
<field name="display_assign_serial" invisible="1"/>
<field name="product_id" required="1" context="{'default_type': 'product'}" attrs="{'readonly': ['|', '&amp;', ('state', '!=', 'draft'), ('additional', '=', False), ('has_move_lines', '=', True)]}"/>
<field name="description_picking" string="Description" optional="hide"/>
- <field name="date_expected" optional="hide" attrs="{'readonly': [('show_operations', '=', True), ('is_locked', '=', True)]}"/>
+ <field name="date" invisible="1"/>
+ <field name="date_deadline" optional="hide"/>
<field name="is_initial_demand_editable" invisible="1"/>
<field name="is_quantity_done_editable" invisible="1"/>
<field name="product_uom_qty" string="Demand" attrs="{'column_invisible': [('parent.immediate_transfer', '=', True)], 'readonly': ['|', ('is_initial_demand_editable', '=', False), '&amp;', '&amp;', ('show_operations', '=', True), ('is_locked', '=', True), ('is_initial_demand_editable', '=', False)]}"/>
@@ -456,6 +461,8 @@
<filter name="done" string="Done" domain="[('state', '=', 'done')]" help="Pickings already processed"/>
<filter name="cancel" string="Cancelled" domain="[('state', '=', 'cancel')]" help="Cancelled Moves"/>
<separator/>
+ <filter name="late" string="Late" help="Deadline exceed or/and by the scheduled"
+ domain="[('state', 'in', ('assigned', 'waiting', 'confirmed')), '|', '|', ('has_deadline_issue', '=', True), ('date_deadline', '&lt;', current_date), ('scheduled_date', '&lt;', current_date)]"/>
<filter string="Planning Issues" name="planning_issues" help="Transfers that are late on scheduled time or one of pickings will be late"
domain="['|', ('delay_alert_date', '!=', False), '&amp;', ('scheduled_date','&lt;', time.strftime('%%Y-%%m-%%d %%H:%%M:%%S')), ('state', 'in', ('assigned', 'waiting', 'confirmed'))]"/>
<separator/>
diff --git a/addons/stock/views/stock_rule_views.xml b/addons/stock/views/stock_rule_views.xml
index b1cfae3cb38..49191b1c649 100644
--- a/addons/stock/views/stock_rule_views.xml
+++ b/addons/stock/views/stock_rule_views.xml
@@ -105,14 +105,6 @@
<label for="delay"/>
<div><field name="delay" class="oe_inline"/> days</div>
</group>
- <group name="scheduling_group" string="Scheduling" attrs="{'invisible': [('action', '=', 'push')]}">
- <field name="delay_alert"/>
- <field name="propagate_date"/>
- <label for="propagate_date_minimum_delta" attrs="{'invisible': [('propagate_date', '=', False)]}"/>
- <div attrs="{'invisible': [('propagate_date', '=', False)]}">
- <field name="propagate_date_minimum_delta" class="oe_inline"/> days
- </div>
- </group>
</group>
</sheet>
</form>
diff --git a/addons/stock/wizard/stock_picking_return.py b/addons/stock/wizard/stock_picking_return.py
index c07b0948f64..370d0992a41 100644
--- a/addons/stock/wizard/stock_picking_return.py
+++ b/addons/stock/wizard/stock_picking_return.py
@@ -98,7 +98,7 @@ class ReturnPicking(models.TransientModel):
'product_uom': return_line.product_id.uom_id.id,
'picking_id': new_picking.id,
'state': 'draft',
- 'date_expected': fields.Datetime.now(),
+ 'date': fields.Datetime.now(),
'location_id': return_line.move_id.location_dest_id.id,
'location_dest_id': self.location_id.id or return_line.move_id.location_id.id,
'picking_type_id': new_picking.picking_type_id.id,
diff --git a/addons/stock_dropshipping/tests/test_lifo_price.py b/addons/stock_dropshipping/tests/test_lifo_price.py
index 819191e4830..98e8adc2a65 100644
--- a/addons/stock_dropshipping/tests/test_lifo_price.py
+++ b/addons/stock_dropshipping/tests/test_lifo_price.py
@@ -82,7 +82,7 @@ class TestLifoPrice(ValuationReconciliationTestCommon):
with out_form.move_ids_without_package.new() as move:
move.product_id = product_lifo_icecream
move.quantity_done = 20.0
- move.date_expected = fields.Datetime.now()
+ move.date = fields.Datetime.now()
outgoing_lifo_shipment = out_form.save()
# I assign this outgoing shipment