diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index 6d647ccd881..72eb978d19c 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -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, diff --git a/addons/mrp/models/stock_rule.py b/addons/mrp/models/stock_rule.py index 8aad49f3ad6..8da658867f3 100644 --- a/addons/mrp/models/stock_rule.py +++ b/addons/mrp/models/stock_rule.py @@ -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, diff --git a/addons/purchase_stock/tests/test_purchase_lead_time.py b/addons/purchase_stock/tests/test_purchase_lead_time.py index fb71d87dfec..b15bb9741ea 100644 --- a/addons/purchase_stock/tests/test_purchase_lead_time.py +++ b/addons/purchase_stock/tests/test_purchase_lead_time.py @@ -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() 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 30fbc3792b8..e9d0e171ebf 100644 --- a/addons/sale_mrp/tests/test_sale_mrp_lead_time.py +++ b/addons/sale_mrp/tests/test_sale_mrp_lead_time.py @@ -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 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 ad3df809d32..00339f1c542 100644 --- a/addons/sale_stock/tests/test_sale_stock_lead_time.py +++ b/addons/sale_stock/tests/test_sale_stock_lead_time.py @@ -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) diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index f4e08b05303..cd4b4e593f8 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -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 diff --git a/addons/stock/models/stock_orderpoint.py b/addons/stock/models/stock_orderpoint.py index 9a8755f6cf3..fc5f5309c64 100644 --- a/addons/stock/models/stock_orderpoint.py +++ b/addons/stock/models/stock_orderpoint.py @@ -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, diff --git a/addons/stock/models/stock_rule.py b/addons/stock/models/stock_rule.py index 0c0ca8ef9de..8202d3e3a0a 100644 --- a/addons/stock/models/stock_rule.py +++ b/addons/stock/models/stock_rule.py @@ -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"),