[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:
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user