From 328efb085a45c517c0e5ff0602f6d7e7ab096a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Voet=20=28ryv=29?= Date: Wed, 27 May 2020 10:00:21 +0200 Subject: [PATCH] [REF] mrp: plan fields refactor - The Scheduled Date can't be change if the MO have some planned WO. (WO defines the scheduled date of the MO) - Now the Scheduled Date 'date_planned_start' is set by default with the deadline date if there is or now if there isn't (same than before). - For MO, 'plan_date_finished' is now invisible. But keep the field because it is used to set the date_expected of finished moves. - Change the plan behaviour: Plan depending of the scheduled date if this one is bigger than now, else try to plan as soon as possible. task-2241471 --- addons/mrp/models/mrp_production.py | 41 +++++++--------- addons/mrp/tests/test_workorder_operation.py | 24 ++++----- addons/mrp/views/mrp_production_views.xml | 51 +++----------------- 3 files changed, 38 insertions(+), 78 deletions(-) diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index df9a822307a..f45f5e65373 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -59,6 +59,12 @@ class MrpProduction(models.Model): return fields.Datetime.to_datetime(self.env.context.get('default_date_planned_start')) + datetime.timedelta(hours=1) return datetime.datetime.now() + datetime.timedelta(hours=1) + @api.model + def _get_default_date_planned_start(self): + if self.env.context.get('default_date_deadline'): + return fields.Datetime.to_datetime(self.env.context.get('default_date_deadline')) + return datetime.datetime.now() + @api.model def _get_default_is_locked(self): return self.user_has_groups('mrp.group_locked_by_default') @@ -113,7 +119,7 @@ class MrpProduction(models.Model): states={'draft': [('readonly', False)]}, check_company=True, help="Location where the system will stock the finished products.") date_planned_start = fields.Datetime( - 'Planned Date', copy=False, default=fields.Datetime.now, + '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) @@ -145,7 +151,6 @@ class MrpProduction(models.Model): ('type', '=', 'normal')]""", check_company=True, help="Bill of Materials allow you to define the list of required components to make a finished product.") - bom_has_operations = fields.Boolean(compute='_compute_bom_has_operations') state = fields.Selection([ ('draft', 'Draft'), @@ -188,7 +193,6 @@ class MrpProduction(models.Model): ) workorder_ids = fields.One2many( 'mrp.workorder', 'production_id', 'Work Orders', copy=True) - workorder_count = fields.Integer('# Work Orders', compute='_compute_workorder_count') workorder_done_count = fields.Integer('# Done Work Orders', compute='_compute_workorder_done_count') move_dest_ids = fields.One2many('stock.move', 'created_production_id', string="Stock Movements of Produced Goods") @@ -229,6 +233,8 @@ class MrpProduction(models.Model): readonly=True, states={'draft': [('readonly', False)]}, default='1') is_locked = fields.Boolean('Is Locked', default=_get_default_is_locked, copy=False) is_planned = fields.Boolean('Its Operations are Planned', compute="_compute_is_planned") + is_partially_planned = fields.Boolean('One operation is Planned', compute="_compute_is_planned") + show_final_lots = fields.Boolean('Show Final Lots', compute='_compute_show_lots') production_location_id = fields.Many2one('stock.location', "Production Location", compute="_compute_production_location", store=True) picking_ids = fields.Many2many('stock.picking', compute='_compute_picking_ids', string='Picking associated to this manufacturing order') @@ -284,7 +290,8 @@ class MrpProduction(models.Model): for production in self: if production.state != 'done': production.date_planned_start = max(production.mapped('move_raw_ids.date_expected') or [fields.Datetime.now()]) - production.date_planned_finished = max(production.mapped('move_finished_ids.date_expected') or [production.date_deadline or fields.Datetime.now()]) + if production.move_finished_ids: + production.date_planned_finished = max(production.mapped('move_finished_ids.date_expected')) def _set_date_planned_start(self): self.move_raw_ids.write({'date_expected': self.date_planned_start}) @@ -292,20 +299,14 @@ class MrpProduction(models.Model): def _set_date_planned_finished(self): self.move_finished_ids.write({'date_expected': self.date_planned_finished}) - @api.depends('bom_id') - def _compute_bom_has_operations(self): - for production in self: - if not production.bom_id: - production.bom_has_operations = False - else: - production.bom_has_operations = len(production.bom_id.operation_ids) > 0 - def _compute_is_planned(self): for production in self: if production.workorder_ids: production.is_planned = all(wo.date_planned_start and wo.date_planned_finished for wo in production.workorder_ids) + production.is_partially_planned = any(wo.date_planned_start and wo.date_planned_finished for wo in production.workorder_ids if production.state != 'draft') else: production.is_planned = False + production.is_partially_planned = False @api.depends('move_raw_ids.delay_alert_date') def _compute_delay_alert_date(self): @@ -410,13 +411,6 @@ class MrpProduction(models.Model): for production in self: production.finished_move_line_ids = production.move_finished_ids.mapped('move_line_ids') - @api.depends('workorder_ids') - def _compute_workorder_count(self): - data = self.env['mrp.workorder'].read_group([('production_id', 'in', self.ids)], ['production_id'], ['production_id']) - count_data = dict((item['production_id'][0], item['production_id_count']) for item in data) - for production in self: - production.workorder_count = count_data.get(production.id, 0) - @api.depends('workorder_ids.state') def _compute_workorder_done_count(self): data = self.env['mrp.workorder'].read_group([ @@ -636,11 +630,11 @@ class MrpProduction(models.Model): res = super(MrpProduction, self).write(vals) for production in self: - if 'date_planned_start' in vals: + if 'date_planned_start' in vals and not self.env.context.get('force_date', False): if production.state in ['done', 'cancel']: raise UserError(_('You cannot move a manufacturing order once it is cancelled or done.')) - if production.state == 'done' and not self.env.context.get('force_date', False): - raise UserError(_('You cannot move a done manufacturing order.')) + 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 ('move_raw_ids' in vals or 'move_finished_ids' 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 @@ -1049,12 +1043,11 @@ class MrpProduction(models.Model): # Schedule all work orders (new ones and those already created) qty_to_produce = max(self.product_qty - self.qty_produced, 0) qty_to_produce = self.product_uom_id._compute_quantity(qty_to_produce, self.product_id.uom_id) - start_date = self.date_planned_start + start_date = max(self.date_planned_start, datetime.datetime.now()) if replan: workorder_ids = self.workorder_ids.filtered(lambda wo: wo.state in ['ready', 'pending']) # We plan the manufacturing order according to its `date_planned_start`, but if # `date_planned_start` is in the past, we plan it as soon as possible. - start_date = max(start_date, datetime.datetime.now()) workorder_ids.leave_id.unlink() else: workorder_ids = self.workorder_ids.filtered(lambda wo: not wo.date_planned_start) diff --git a/addons/mrp/tests/test_workorder_operation.py b/addons/mrp/tests/test_workorder_operation.py index 91bacd60d47..190768adc9f 100644 --- a/addons/mrp/tests/test_workorder_operation.py +++ b/addons/mrp/tests/test_workorder_operation.py @@ -1062,7 +1062,7 @@ class TestWorkOrderProcess(TestWorkOrderProcessCommon): - calendar wc1 :[mo1][mo4] - calendar wc2 :[mo2 ][mo5 ] - calendar wc3 :[mo3 ][mo6 ]""" - planned_date = datetime(2019, 5, 13, 9, 0) + planned_date = datetime(2023, 5, 15, 9, 0) self.workcenter_1.alternative_workcenter_ids = self.wc_alt_1 | self.wc_alt_2 workcenters = [self.wc_alt_2, self.wc_alt_1, self.workcenter_1] for i in range(3): @@ -1146,6 +1146,7 @@ class TestWorkOrderProcess(TestWorkOrderProcessCommon): one take the free slot before on the calendar calendar after first mo : [ ][mo1] calendar after second mo: [mo2][mo1] """ + planned_date = datetime(2023, 5, 15, 14, 0) self.workcenter_1.alternative_workcenter_ids = self.wc_alt_1 | self.wc_alt_2 self.env['mrp.workcenter'].search([]).write({'tz': 'UTC'}) # compute all date in UTC @@ -1154,7 +1155,7 @@ class TestWorkOrderProcess(TestWorkOrderProcessCommon): mo_form.product_id = self.product_4 mo_form.bom_id = self.planning_bom mo_form.product_qty = 1 - mo_form.date_planned_start = datetime(2019, 5, 13, 14, 0, 0, 0) + mo_form.date_planned_start = planned_date mo = mo_form.save() start = mo.date_planned_start mo.action_confirm() @@ -1166,19 +1167,20 @@ class TestWorkOrderProcess(TestWorkOrderProcessCommon): self.assertAlmostEqual(wo1_stop, start + timedelta(minutes=85.58), delta=timedelta(seconds=10), msg="Wrong plannification") # second MO should be plan before as there is a free slot before + planned_date = datetime(2023, 5, 15, 9, 0) mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.product_4 mo_form.bom_id = self.planning_bom mo_form.product_qty = 1 - mo_form.date_planned_start = datetime(2019, 5, 13, 9, 0, 0, 0) + mo_form.date_planned_start = planned_date mo = mo_form.save() mo.action_confirm() mo.button_plan() self.assertEqual(mo.workorder_ids[0].workcenter_id, self.wc_alt_2, "wrong workcenter") wo1_start = mo.workorder_ids[0].date_planned_start wo1_stop = mo.workorder_ids[0].date_planned_finished - self.assertAlmostEqual(wo1_start, datetime(2019, 5, 13, 9, 0, 0, 0), delta=timedelta(seconds=10), msg="Wrong plannification") - self.assertAlmostEqual(wo1_stop, datetime(2019, 5, 13, 9, 0, 0, 0) + timedelta(minutes=85.59), delta=timedelta(seconds=10), msg="Wrong plannification") + self.assertAlmostEqual(wo1_start, planned_date, delta=timedelta(seconds=10), msg="Wrong plannification") + self.assertAlmostEqual(wo1_stop, planned_date + timedelta(minutes=85.59), delta=timedelta(seconds=10), msg="Wrong plannification") def test_planning_4(self): """ Plan a manufacturing orders with 1 workorder on 1 workcenter @@ -1229,17 +1231,18 @@ class TestWorkOrderProcess(TestWorkOrderProcessCommon): 'time_cycle': 60, })] }) + planned_date = datetime(2023, 5, 15, 9, 0) mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.product_4 mo_form.bom_id = self.planning_bom mo_form.product_qty = 1 - mo_form.date_planned_start = datetime(2019, 5, 13, 9, 0, 0, 0) + mo_form.date_planned_start = planned_date mo = mo_form.save() mo.action_confirm() mo.button_plan() wo = mo.workorder_ids - self.assertAlmostEqual(wo.date_planned_start, datetime(2019, 5, 13, 9, 0, 0, 0), delta=timedelta(seconds=10)) - self.assertAlmostEqual(wo.date_planned_finished, datetime(2019, 5, 13, 9, 0, 0, 0) + timedelta(minutes=60), delta=timedelta(seconds=10)) + self.assertAlmostEqual(wo.date_planned_start, planned_date, delta=timedelta(seconds=10)) + self.assertAlmostEqual(wo.date_planned_finished, planned_date + timedelta(minutes=60), delta=timedelta(seconds=10)) wo.button_start() wo.record_production() # Marking workorder as done should change the finished date @@ -1250,7 +1253,7 @@ class TestWorkOrderProcess(TestWorkOrderProcessCommon): mo_form.product_id = self.product_4 mo_form.bom_id = self.planning_bom mo_form.product_qty = 1 - mo_form.date_planned_start = datetime(2019, 5, 13, 9, 0, 0, 0) + mo_form.date_planned_start = planned_date mo = mo_form.save() mo.action_confirm() mo.button_plan() @@ -1258,7 +1261,6 @@ class TestWorkOrderProcess(TestWorkOrderProcessCommon): wo.button_start() self.assertAlmostEqual(wo.date_start, datetime.now(), delta=timedelta(seconds=10)) self.assertAlmostEqual(wo.date_planned_start, datetime.now(), delta=timedelta(seconds=10)) - self.assertAlmostEqual(wo.date_planned_finished, datetime.now(), delta=timedelta(seconds=10)) def test_planning_7(self): """ set the workcenter capacity to 10. Produce a dozen of product tracked by @@ -1284,7 +1286,7 @@ class TestWorkOrderProcess(TestWorkOrderProcessCommon): """ Testing planning a workorder then cancel it and then plan it again. The planned date must be the same the first time and the second time the workorder is planned.""" - planned_date = datetime(2019, 5, 13, 9, 0) + planned_date = datetime(2023, 5, 15, 9, 0) mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.product_4 mo_form.bom_id = self.planning_bom diff --git a/addons/mrp/views/mrp_production_views.xml b/addons/mrp/views/mrp_production_views.xml index 334d549627b..9dbbaab857d 100644 --- a/addons/mrp/views/mrp_production_views.xml +++ b/addons/mrp/views/mrp_production_views.xml @@ -59,7 +59,6 @@
-
+ + @@ -157,47 +158,11 @@ context="{'default_product_tmpl_id': product_tmpl_id}" attrs="{'readonly': [('state', '!=', 'draft')]}"/> - -
-
-
+ +