[FIX] *stock*: fix date_deadline

date_deadline of the stock move has been reviewed:
- Now, it represents the datetime when the operation must
be done to be in time for the uppest document (SO/PO/MO/RR).
- At the creation of stock move the date_deadline is computed in
the same way than the sheduled date expected that it is not include
security days (inverse the logic for PO).
- When we update the delivery date (or lead time) of SO (or PO).
The deadline of related stock move is updated and the delta is propagate
between origin and dest moves.

task-2324039
This commit is contained in:
Rémy Voet (ryv)
2020-09-15 08:11:44 +00:00
parent 6248c016c7
commit dee9106278
8 changed files with 66 additions and 29 deletions
+5 -3
View File
@@ -315,7 +315,7 @@ class MrpProduction(models.Model):
def _set_date_deadline(self):
for production in self:
(self.move_raw_ids | self.move_finished_ids).date_deadline = production.date_deadline
production.move_finished_ids.date_deadline = production.date_deadline
@api.depends("workorder_ids")
def _compute_is_planned(self):
@@ -706,7 +706,7 @@ class MrpProduction(models.Model):
if any(wo.date_planned_start and wo.date_planned_finished for wo in production.workorder_ids):
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})
production.move_raw_ids.write({'date': production.date_planned_start, 'date_deadline': 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':
@@ -717,10 +717,12 @@ class MrpProduction(models.Model):
'group_id': production.procurement_group_id.id,
'reference': production.name,
'date': production.date_planned_start,
'date_deadline': production.date_planned_start
})
production.move_finished_ids.filtered(lambda move: move.additional and move.date > production.date_planned_finished).write({
'reference': production.name,
'date': production.date_planned_finished,
'date_deadline': production.date_deadline
})
production._autoconfirm_production()
if production in production_to_replan:
@@ -879,7 +881,7 @@ class MrpProduction(models.Model):
'sequence': bom_line.sequence if bom_line else 10,
'name': self.name,
'date': self.date_planned_start,
'date_deadline': self.date_deadline,
'date_deadline': self.date_planned_start,
'bom_line_id': bom_line.id if bom_line else False,
'picking_type_id': self.picking_type_id.id,
'product_id': product_id.id,
+1 -1
View File
@@ -85,7 +85,7 @@ class StockRule(models.Model):
def _prepare_mo_vals(self, product_id, product_qty, product_uom, location_id, name, origin, company_id, values, bom):
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
date_deadline = values.get('date_deadline') or date_planned + relativedelta(days=company_id.manufacturing_lead) + relativedelta(days=product_id.produce_delay)
return {
'origin': origin,
'product_id': product_id.id,
@@ -48,6 +48,9 @@ class TestPurchaseLeadTime(PurchaseTestCommon):
we create two procurements for the two different product with same vendor
and different Delivery Lead Time."""
company = self.env.ref('base.main_company')
company.write({'po_lead': 0.00})
# Make procurement request from product_1's form view, create procurement and check it's state
date_planned1 = fields.Datetime.to_string(fields.datetime.now() + timedelta(days=10))
self._create_make_procurement(self.product_1, 10.00, date_planned=date_planned1)
@@ -99,6 +102,9 @@ class TestPurchaseLeadTime(PurchaseTestCommon):
""" In order to check dates, set product's Delivery Lead Time
and warehouse route's delay."""
company = self.env.ref('base.main_company')
company.write({'po_lead': 1.00})
# Update warehouse_1 with Incoming Shipments 3 steps
self.warehouse_1.write({'reception_steps': 'three_steps'})
@@ -115,6 +121,7 @@ class TestPurchaseLeadTime(PurchaseTestCommon):
{
'warehouse_id': self.warehouse_1,
'date_planned': date_planned, # 10 days added to current date of procurement to get future schedule date and order date of purchase order.
'date_deadline': date_planned, # 10 days added to current date of procurement to get future schedule date and order date of purchase order.
'rule_id': self.warehouse_1.buy_pull_id,
'group_id': False,
'route_ids': [],
@@ -122,16 +129,15 @@ class TestPurchaseLeadTime(PurchaseTestCommon):
)])
# Confirm purchase order
purchase = self.env['purchase.order.line'].search([('product_id', '=', self.product_1.id)], limit=1).order_id
purchase.button_confirm()
# Check order date of purchase order
order_date = fields.Datetime.from_string(date_planned) - timedelta(days=self.product_1.seller_ids.delay + rule_delay)
order_date = fields.Datetime.from_string(date_planned) - timedelta(days=self.product_1.seller_ids.delay + rule_delay + company.po_lead)
self.assertEqual(purchase.date_order, order_date, 'Order date should be equal to: Date of the procurement order - Delivery Lead Time(supplier and pull rules).')
# Check scheduled date of purchase order
schedule_date = order_date + timedelta(days=self.product_1.seller_ids.delay + rule_delay)
schedule_date = order_date + timedelta(days=self.product_1.seller_ids.delay + rule_delay + company.po_lead)
self.assertEqual(date_planned, str(schedule_date), 'Schedule date should be equal to: Order date of Purchase order + Delivery Lead Time(supplier and pull rules).')
# Check the picking crated or not
@@ -139,12 +145,23 @@ class TestPurchaseLeadTime(PurchaseTestCommon):
# Check scheduled date of Internal Type shipment
incoming_shipment1 = self.env['stock.picking'].search([('move_lines.product_id', 'in', (self.product_1.id, self.product_2.id)), ('picking_type_id', '=', self.warehouse_1.int_type_id.id), ('location_id', '=', self.warehouse_1.wh_input_stock_loc_id.id), ('location_dest_id', '=', self.warehouse_1.wh_qc_stock_loc_id.id)])
incoming_shipment1_date = order_date + timedelta(days=self.product_1.seller_ids.delay)
incoming_shipment1_date = order_date + timedelta(days=self.product_1.seller_ids.delay + company.po_lead)
self.assertEqual(incoming_shipment1.scheduled_date, incoming_shipment1_date, 'Schedule date of Internal Type shipment for input stock location should be equal to: schedule date of purchase order + push rule delay.')
self.assertEqual(incoming_shipment1.date_deadline, incoming_shipment1_date)
old_deadline1 = incoming_shipment1.date_deadline
incoming_shipment2 = self.env['stock.picking'].search([('picking_type_id', '=', self.warehouse_1.int_type_id.id), ('location_id', '=', self.warehouse_1.wh_qc_stock_loc_id.id), ('location_dest_id', '=', self.warehouse_1.lot_stock_id.id)])
incoming_shipment2_date = schedule_date - timedelta(days=incoming_shipment2.move_lines[0].rule_id.delay)
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..')
self.assertEqual(incoming_shipment2.date_deadline, incoming_shipment2_date)
old_deadline2 = incoming_shipment2.date_deadline
# Modify the date_planned of the purchase -> propagate the deadline
purchase_form = Form(purchase)
purchase_form.date_planned = purchase.date_planned + timedelta(days=1)
purchase_form.save()
self.assertEqual(incoming_shipment2.date_deadline, old_deadline2 + timedelta(days=1), 'Deadline should be propagate')
self.assertEqual(incoming_shipment1.date_deadline, old_deadline1 + timedelta(days=1), 'Deadline should be propagate')
def test_merge_po_line(self):
"""Change that merging po line for same procurement is done."""
@@ -194,6 +211,8 @@ class TestPurchaseLeadTime(PurchaseTestCommon):
def test_merge_po_line_3(self):
"""Change merging po line if same procurement is done depending on custom values."""
company = self.env.ref('base.main_company')
company.write({'po_lead': 0.00})
# Create procurement order of product_1
ProcurementGroup = self.env['procurement.group']
procurement_values = {
@@ -241,6 +260,8 @@ class TestPurchaseLeadTime(PurchaseTestCommon):
self.assertEqual(purchase_order.picking_ids[0].move_ids_without_package.filtered(lambda x: x.product_uom_qty == 10).description_picking, order_line_description + "Color (Green)", 'wrong description in picking')
def test_reordering_days_to_purchase(self):
company = self.env.ref('base.main_company')
company.write({'po_lead': 0.00})
self.patcher = patch('odoo.addons.stock.models.stock_orderpoint.fields.Date', wraps=fields.Date)
self.mock_date = self.patcher.start()
@@ -71,7 +71,7 @@ class TestSaleMrpLeadTime(TestStockCommon):
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.'
msg='Deadline date of picking should be equal to: Order date + Customer Lead Time.'
)
# Check schedule date and deadline of manufacturing order
@@ -111,15 +111,15 @@ class TestSaleStockLeadTime(TestSaleCommon, ValuationReconciliationTestCommon):
# -> Create an SO and confirm it with confirmation Date : 12/18/2018
# -> 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
# PACK -> Scheduled Date : 01/07/2019, Deadline Date: 01/09/2019
# PICK -> Scheduled Date : 01/02/2019, Deadline Date: 01/04/2019
# -> Now, change commitment_date in the sale order = out_deadline_date + 5 days
# -> 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
# OUT -> Deadline Date : 01/19/2019
# PACK -> Deadline Date : 01/14/2019
# PICK -> Deadline Date : 01/09/2019
# Update company with Sales Safety Days
self.env.company.security_lead = 2.00
@@ -155,11 +155,11 @@ class TestSaleStockLeadTime(TestSaleCommon, ValuationReconciliationTestCommon):
# 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)
deadline_date = order.date_order + timedelta(days=self.test_product_order.sale_delay)
deadline_date = order.date_order + timedelta(days=self.test_product_order.sale_delay) - timedelta(days=out.move_lines[0].rule_id.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)
out_scheduled_date = deadline_date - timedelta(days=self.env.company.security_lead)
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')
@@ -170,6 +170,7 @@ class TestSaleStockLeadTime(TestSaleCommon, ValuationReconciliationTestCommon):
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.')
deadline_date -= timedelta(days=pack.move_lines[0].rule_id.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.')
@@ -180,6 +181,7 @@ class TestSaleStockLeadTime(TestSaleCommon, ValuationReconciliationTestCommon):
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.')
deadline_date -= timedelta(days=pick.move_lines[0].rule_id.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.')
@@ -191,5 +193,7 @@ class TestSaleStockLeadTime(TestSaleCommon, ValuationReconciliationTestCommon):
# 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)
new_deadline -= timedelta(days=pack.move_lines[0].rule_id.delay)
self.assertEqual(pack.date_deadline, new_deadline)
new_deadline -= timedelta(days=pick.move_lines[0].rule_id.delay)
self.assertEqual(pick.date_deadline, new_deadline)
+19 -11
View File
@@ -459,11 +459,25 @@ class StockMove(models.Model):
else:
move.json_forecast = json.dumps({'expectedDate': None})
def _set_date_deadline(self):
def _set_date_deadline(self, new_deadline):
# Handle the propagation of `date_deadline` fields (up and down stream - only update by up/downstream documents)
already_propagate_ids = self.env.context.get('date_deadline_propagate_ids', set()) | set(self.ids)
self = self.with_context(date_deadline_propagate_ids=already_propagate_ids)
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
moves_to_update = (move.move_dest_ids | move.move_orig_ids)
if move.date_deadline:
delta = move.date_deadline - fields.Datetime.to_datetime(new_deadline)
else:
delta = 0
for move_update in moves_to_update:
if move_update.state in ('done', 'cancel'):
continue
if move_update.id in already_propagate_ids:
continue
if move_update.date_deadline and delta:
move_update.date_deadline -= delta
else:
move_update.date_deadline = new_deadline
@api.depends('move_line_ids', 'move_line_ids.lot_id', 'move_line_ids.qty_done')
def _compute_lot_ids(self):
@@ -555,19 +569,13 @@ 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'))
res = super(StockMove, self).write(vals)
if 'date_deadline' in vals:
self._set_date_deadline()
self._set_date_deadline(vals.get('date_deadline'))
res = super(StockMove, self).write(vals)
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
+1
View File
@@ -387,6 +387,7 @@ class StockWarehouseOrderpoint(models.Model):
return {
'route_ids': self.route_id,
'date_planned': date_planned,
'date_deadline': date or False,
'warehouse_id': self.warehouse_id,
'orderpoint_id': self,
'group_id': group or self.group_id,
+2 -1
View File
@@ -266,6 +266,7 @@ class StockRule(models.Model):
date_scheduled = fields.Datetime.to_string(
fields.Datetime.from_string(values['date_planned']) - relativedelta(days=self.delay or 0)
)
date_deadline = values.get('date_deadline') and (fields.Datetime.to_datetime(values['date_deadline']) - relativedelta(days=self.delay or 0)) or False
partner = self.partner_address_id or (values.get('group_id', False) and values['group_id'].partner_id)
if partner:
product_id = product_id.with_context(lang=partner.lang or self.env.user.lang)
@@ -298,7 +299,7 @@ class StockRule(models.Model):
'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_scheduled,
'date_deadline': values.get('date_deadline'),
'date_deadline': date_deadline,
'propagate_cancel': self.propagate_cancel,
'description_picking': picking_description,
'priority': values.get('priority', "0"),