From 2a2c27fa45d2a9859cf3714dc5c7d66cf0022e33 Mon Sep 17 00:00:00 2001 From: svs-odoo Date: Thu, 13 Jun 2019 11:34:05 +0000 Subject: [PATCH] [IMP] mrp, sale_mrp: planned date on MO The purpose of this commit is to simplify the `mrp.production` model in reducing the number of date field. The following fields was kept: - `date_planned_start`: the planned date for the beginning of the MO - `date_planned_finished`: the planned date for the ending of the MO - `date_start_wo`: the preferred date to start a Work Order in case the MO will plan one. - `date_deadline`: is only indicative and is automatically set when the manufacturing order is needed for a sale order. - `date_start` and `date_finished`: no changes. Task #1935229 Co-authored-by: Whenrow --- addons/mrp/models/mrp_production.py | 61 +++++++------- addons/mrp/models/mrp_workorder.py | 20 ++++- addons/mrp/models/stock_rule.py | 2 +- addons/mrp/tests/test_workorder_operation.py | 7 ++ addons/mrp/views/mrp_production_views.xml | 84 ++++++++++++++----- .../sale_mrp/tests/test_sale_mrp_lead_time.py | 8 +- 6 files changed, 125 insertions(+), 57 deletions(-) diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index 465cf23398d..ff757d69e5e 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -51,6 +51,12 @@ class MrpProduction(models.Model): location = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1).lot_stock_id return location and location.id or False + @api.model + def _get_default_date_planned_finished(self): + if self.env.context.get('default_date_planned_start'): + 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) + name = fields.Char( 'Reference', copy=False, readonly=True, default=lambda x: _('New')) origin = fields.Char( @@ -89,28 +95,24 @@ class MrpProduction(models.Model): states={'draft': [('readonly', False)]}, help="Location where the system will stock the finished products.") date_planned_start = fields.Datetime( - 'Deadline Start', copy=False, default=fields.Datetime.now, - index=True, required=True, - states={'draft': [('readonly', False)]}) + 'Planned Date', copy=False, default=fields.Datetime.now, + help="Date at which you plan to start the production.", + index=True, required=True, store=True) date_planned_finished = fields.Datetime( - 'Deadline End', copy=False, default=fields.Datetime.now, - index=True, - states={'draft': [('readonly', False)]}) - date_planned_start_wo = fields.Datetime( - 'Scheduled Start Date', compute='_compute_date_planned', - copy=False, store=True, - states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}) - date_planned_finished_wo = fields.Datetime( - 'Scheduled End Date', compute='_compute_date_planned', - copy=False, store=True, - states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}) + 'Planned End Date', + default=_get_default_date_planned_finished, + help="Date at which you plan to finish the production.", + copy=False, store=True) + date_deadline = fields.Datetime( + 'Deadline', copy=False, index=True, + 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) date_start_wo = fields.Datetime( - 'Planned Start Date', copy=False, readonly=True, - states={'draft': [('readonly', False)], 'confirmed': [('readonly', False)]}, - help="The work orders will be planned based on the availability of the work centers starting from " - "this date. If emtpy, the work orders are planned as soon as possible.") + 'Plan From', copy=False, readonly=True, + help="Work orders will be planned based on the availability of the work centers\ + starting from this date. If empty, the work orders will be planned as soon as possible.", + ) bom_id = fields.Many2one( 'mrp.bom', 'Bill of Material', readonly=True, states={'draft': [('readonly', False)]}, @@ -370,18 +372,6 @@ class MrpProduction(models.Model): for production in self: production.scrap_count = count_data.get(production.id, 0) - @api.depends('workorder_ids.date_planned_start', 'workorder_ids.date_planned_finished') - def _compute_date_planned(self): - for order in self: - date_planned_start_wo = date_planned_finished_wo = False - if order.workorder_ids: - start_dates = order.workorder_ids.filtered(lambda r: r.date_planned_start is not False).sorted(key=lambda r: r.date_planned_start) - date_planned_start_wo = start_dates[0].date_planned_start if start_dates else False - finished_dates = order.workorder_ids.filtered(lambda r: r.date_planned_finished is not False).sorted(key=lambda r: r.date_planned_finished) - date_planned_finished_wo = finished_dates[-1].date_planned_finished if finished_dates else False - order.date_planned_start_wo = date_planned_start_wo - order.date_planned_finished_wo = date_planned_finished_wo - _sql_constraints = [ ('name_uniq', 'unique(name, company_id)', 'Reference must be unique per Company!'), ('qty_positive', 'check (product_qty > 0)', 'The quantity to produce must be positive!'), @@ -414,6 +404,8 @@ class MrpProduction(models.Model): 'date': self.date_planned_start, 'date_expected': self.date_planned_start, }) + if not self.routing_id: + self.date_planned_finished = self.date_planned_start + datetime.timedelta(hours=1) @api.onchange('bom_id', 'product_id', 'product_qty', 'product_uom_id') def _onchange_move_raw(self): @@ -451,6 +443,11 @@ class MrpProduction(models.Model): 'date_expected': vals['date_planned_start'], }) for production in self: + if 'date_planned_start' in vals: + if production.state in ['done', 'cancel']: + raise UserError(_('You cannot move a manufacturing order once it is cancelled or done.')) + if production.workorder_ids and not self.env.context.get('force_date', False): + raise UserError(_('You cannot move a planned manufacturing order.')) if 'move_raw_ids' in vals and production.state != 'draft': production.move_raw_ids.filtered(lambda m: m.state == 'draft')._action_confirm() return res @@ -715,6 +712,10 @@ class MrpProduction(models.Model): }) vals['leave_id'] = leave.id workorder.write(vals) + self.with_context(force_date=True).write({ + 'date_planned_start': self.workorder_ids[0].date_planned_start, + 'date_planned_finished': self.workorder_ids[-1].date_planned_finished + }) def button_unplan(self): if any(wo.state == 'done' for wo in self.workorder_ids): diff --git a/addons/mrp/models/mrp_workorder.py b/addons/mrp/models/mrp_workorder.py index f7be61d8325..2fd03cc7654 100644 --- a/addons/mrp/models/mrp_workorder.py +++ b/addons/mrp/models/mrp_workorder.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from datetime import datetime +from datetime import datetime, timedelta from dateutil.relativedelta import relativedelta from collections import defaultdict @@ -146,6 +146,14 @@ class MrpWorkorder(models.Model): 'date_to': date_to, }) + @api.onchange('date_planned_start') + def _onchange_date_planned_start(self): + if self.duration_expected: + time_delta = timedelta(minutes=self.duration_expected) + else: + time_delta = timedelta(hours=1) + self.update({'date_planned_finished': self.date_planned_start + time_delta}) + @api.onchange('finished_lot_id') def _onchange_finished_lot_id(self): """When the user changes the lot being currently produced, suggest @@ -256,6 +264,16 @@ class MrpWorkorder(models.Model): end_date = fields.Datetime.to_datetime(values.get('date_planned_finished')) or workorder.date_planned_finished if start_date and end_date and start_date > end_date: raise UserError(_('The planned end date of the work order cannot be prior to the planned start date, please correct this to save the work order.')) + # Update MO dates if the start date of the first WO or the + # finished date of the last WO is update. + if workorder == workorder.production_id.workorder_ids[0] and 'date_planned_start' in values: + workorder.production_id.with_context(force_date=True).write({ + 'date_planned_start': values['date_planned_start'] + }) + if workorder == workorder.production_id.workorder_ids[-1] and 'date_planned_finished' in values: + workorder.production_id.with_context(force_date=True).write({ + 'date_planned_finished': values['date_planned_finished'] + }) return super(MrpWorkorder, self).write(values) def _generate_wo_lines(self): diff --git a/addons/mrp/models/stock_rule.py b/addons/mrp/models/stock_rule.py index 5e7e9d1bf81..ff840682e1d 100644 --- a/addons/mrp/models/stock_rule.py +++ b/addons/mrp/models/stock_rule.py @@ -82,7 +82,7 @@ class StockRule(models.Model): 'location_src_id': self.location_src_id.id or self.picking_type_id.default_location_src_id.id or location_id.id, 'location_dest_id': location_id.id, 'bom_id': bom.id, - 'date_planned_start': fields.Datetime.to_string(self._get_date_planned(product_id, company_id, values)), + 'date_deadline': fields.Datetime.to_string(self._get_date_planned(product_id, company_id, values)), 'date_planned_finished': values['date_planned'], 'procurement_group_id': False, 'propagate_cancel': self.propagate_cancel, diff --git a/addons/mrp/tests/test_workorder_operation.py b/addons/mrp/tests/test_workorder_operation.py index 0c711d85302..d4a1f84076d 100644 --- a/addons/mrp/tests/test_workorder_operation.py +++ b/addons/mrp/tests/test_workorder_operation.py @@ -908,6 +908,7 @@ class TestWorkOrderProcess(TestMrpCommon): - calendar wc1 :[mo1][mo4] - calendar wc2 :[mo2 ][mo5 ] - calendar wc3 :[mo3 ][mo6 ]""" + planned_date = datetime(2019, 5, 13, 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): @@ -916,11 +917,14 @@ class TestWorkOrderProcess(TestMrpCommon): mo_form.product_id = self.product_4 mo_form.bom_id = self.planning_bom mo_form.product_qty = 1 + mo_form.date_start_wo = planned_date mo = mo_form.save() mo.action_confirm() mo.button_plan() # Check that workcenters change self.assertEqual(mo.workorder_ids.workcenter_id, workcenters[i], "wrong workcenter %d" % i) + self.assertAlmostEqual(mo.date_planned_start, planned_date, delta=timedelta(seconds=10)) + self.assertAlmostEqual(mo.date_planned_start, mo.workorder_ids.date_planned_start, delta=timedelta(seconds=10)) for i in range(3): # Planning 3 more should choose workcenters in opposite order as @@ -931,11 +935,14 @@ class TestWorkOrderProcess(TestMrpCommon): mo_form.product_id = self.product_4 mo_form.bom_id = self.planning_bom mo_form.product_qty = 1 + mo_form.date_start_wo = planned_date mo = mo_form.save() mo.action_confirm() mo.button_plan() # Check that workcenters change self.assertEqual(mo.workorder_ids.workcenter_id, workcenters[i], "wrong workcenter %d" % i) + self.assertNotEqual(mo.date_planned_start, planned_date) + self.assertAlmostEqual(mo.date_planned_start, mo.workorder_ids.date_planned_start, delta=timedelta(seconds=10)) def test_planning_2(self): """ Plan some manufacturing orders with 2 workorders each diff --git a/addons/mrp/views/mrp_production_views.xml b/addons/mrp/views/mrp_production_views.xml index 8be9bcbe634..2bf94654de2 100644 --- a/addons/mrp/views/mrp_production_views.xml +++ b/addons/mrp/views/mrp_production_views.xml @@ -9,7 +9,6 @@ - @@ -46,15 +45,15 @@