Skip to content
Snippets Groups Projects
Commit 90f83914 authored by MerlinGuillaume's avatar MerlinGuillaume
Browse files

[FIX] mrp: keep end time and duration consistent when moving work order


When drag and dropping a work order in the Work Orders Planning, the
end time wasn't recomputed. This can make the end time inconsistent with
the duration when the work order spans across a non-working time.

Steps to reproduce:
1. Install Manufacturing
2. Go to Settings > Manufacturing > Operations and enable Work Orders
3. Go to Manufacturing > Master Data > Routings and edit routing
   'Primary Assembly' to last 120:00 minutes
4. Go to Manufacturing > Operations > Manufacturing Orders and create
   one with values:
   - Product: Table Top
   - Plan From: today's date at 11:00:00
5. Save, mark as todo and plan the manufacturing order
6. Go to Manufacturing > Planning > Planning by Workcenter and trigger
   the day view
7. Move the work order to 8 am
8. The work order still lasts for 3 hours (according to its start and
   finish time) even though its expected duration is 2 hours

Solution:
Recompute `date_planned_finished` when we move a work order in the
planning (`date_planned_start` and `date_planned_finished` are passed in
values), and recompute `expected_duration` when we extend it (only one
of them is passed depending on the way we extend the work order).
(`duration_expected` is never passed in values when we manipulate a work
order through the planning)

Problem:
`date_planned_finished` wasn't recomputed when moving the work order in
the planning

opw-2893622

closes odoo/odoo#97805

Signed-off-by: default avatarWilliam Henrotin (whe) <whe@odoo.com>
parent 34db5005
No related branches found
No related tags found
No related merge requests found
...@@ -179,15 +179,6 @@ class MrpWorkorder(models.Model): ...@@ -179,15 +179,6 @@ class MrpWorkorder(models.Model):
if line: if line:
self.qty_producing = line.qty_done self.qty_producing = line.qty_done
@api.onchange('date_planned_finished')
def _onchange_date_planned_finished(self):
if self.date_planned_start and self.date_planned_finished:
interval = self.workcenter_id.resource_calendar_id.get_work_duration_data(
self.date_planned_start, self.date_planned_finished,
domain=[('time_type', 'in', ['leave', 'other'])]
)
self.duration_expected = interval['hours'] * 60
@api.depends('production_id.workorder_ids.finished_workorder_line_ids', @api.depends('production_id.workorder_ids.finished_workorder_line_ids',
'production_id.workorder_ids.finished_workorder_line_ids.qty_done', 'production_id.workorder_ids.finished_workorder_line_ids.qty_done',
'production_id.workorder_ids.finished_workorder_line_ids.lot_id') 'production_id.workorder_ids.finished_workorder_line_ids.lot_id')
...@@ -324,13 +315,28 @@ class MrpWorkorder(models.Model): ...@@ -324,13 +315,28 @@ class MrpWorkorder(models.Model):
for order in (self - late_orders): for order in (self - late_orders):
order.color = 2 order.color = 2
@api.onchange('date_planned_start', 'duration_expected') @api.onchange('date_planned_start', 'duration_expected', 'workcenter_id')
def _onchange_date_planned_start(self): def _onchange_date_planned_start(self):
if self.date_planned_start and self.duration_expected: if self.date_planned_start and self.duration_expected and self.workcenter_id:
self.date_planned_finished = self.workcenter_id.resource_calendar_id.plan_hours( self.date_planned_finished = self._calculate_date_planned_finished()
self.duration_expected / 60.0, self.date_planned_start,
compute_leaves=True, domain=[('time_type', 'in', ['leave', 'other'])] def _calculate_date_planned_finished(self, date_planned_start=False):
) return self.workcenter_id.resource_calendar_id.plan_hours(
self.duration_expected / 60.0, date_planned_start or self.date_planned_start,
compute_leaves=True, domain=[('time_type', 'in', ['leave', 'other'])]
)
@api.onchange('date_planned_finished')
def _onchange_date_planned_finished(self):
if self.date_planned_start and self.date_planned_finished:
self.duration_expected = self._calculate_duration_expected()
def _calculate_duration_expected(self, date_planned_start=False, date_planned_finished=False):
interval = self.workcenter_id.resource_calendar_id.get_work_duration_data(
date_planned_start or self.date_planned_start, date_planned_finished or self.date_planned_finished,
domain=[('time_type', 'in', ['leave', 'other'])]
)
return interval['hours'] * 60
def write(self, values): def write(self, values):
if 'production_id' in values: if 'production_id' in values:
...@@ -349,6 +355,16 @@ class MrpWorkorder(models.Model): ...@@ -349,6 +355,16 @@ class MrpWorkorder(models.Model):
end_date = fields.Datetime.to_datetime(values.get('date_planned_finished')) or workorder.date_planned_finished 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: 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.')) 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.'))
if 'duration_expected' not in values:
if 'date_planned_start' in values and 'date_planned_finished' in values:
computed_finished_time = self._calculate_date_planned_finished(start_date)
values['date_planned_finished'] = computed_finished_time
elif 'date_planned_start' in values:
computed_duration = self._calculate_duration_expected(date_planned_start=start_date)
values['duration_expected'] = computed_duration
elif 'date_planned_finished' in values:
computed_duration = self._calculate_duration_expected(date_planned_finished=end_date)
values['duration_expected'] = computed_duration
# Update MO dates if the start date of the first WO or the # Update MO dates if the start date of the first WO or the
# finished date of the last WO is update. # finished date of the last WO is update.
if workorder == workorder.production_id.workorder_ids[0] and 'date_planned_start' in values: if workorder == workorder.production_id.workorder_ids[0] and 'date_planned_start' in values:
......
...@@ -1195,7 +1195,7 @@ class TestWorkOrderProcess(TestMrpCommon): ...@@ -1195,7 +1195,7 @@ class TestWorkOrderProcess(TestMrpCommon):
wo.button_start() wo.button_start()
self.assertAlmostEqual(wo.date_start, datetime.now(), delta=timedelta(seconds=10)) 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_start, datetime.now(), delta=timedelta(seconds=10))
self.assertAlmostEqual(wo.date_planned_finished, datetime.now(), delta=timedelta(seconds=10)) self.assertAlmostEqual(wo.date_planned_finished, wo._calculate_date_planned_finished(wo.date_start), delta=timedelta(seconds=10))
def test_planning_7(self): def test_planning_7(self):
""" set the workcenter capacity to 10. Produce a dozen of product tracked by """ set the workcenter capacity to 10. Produce a dozen of product tracked by
...@@ -1217,6 +1217,65 @@ class TestWorkOrderProcess(TestMrpCommon): ...@@ -1217,6 +1217,65 @@ class TestWorkOrderProcess(TestMrpCommon):
wo = mo.workorder_ids wo = mo.workorder_ids
self.assertEqual(wo.duration_expected, 120) self.assertEqual(wo.duration_expected, 120)
def test_planning_8(self):
""" Plan a workorder and move it on the planning, the workorder duration
should always be consistent with the planned start and finish date"""
self.planning_bom.routing = self.env.ref("mrp.mrp_routing_0")
self.env['mrp.workcenter'].search([]).write({'tz': 'UTC'})
self.env['resource.calendar'].search([]).write({'tz': 'UTC'})
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = self.product_4
self.planning_bom.routing_id.operation_ids.time_cycle_manual = 120.0
mo_form.bom_id = self.planning_bom
mo_form.product_qty = 1
mo_form.date_start_wo = datetime(2022, 8, 8, 11, 0, 0, 0)
mo = mo_form.save()
mo.action_confirm()
mo.button_plan()
wo = mo.workorder_ids
wc = wo.workcenter_id
self.assertEqual(wo.date_planned_start, datetime(2022, 8, 8, 11, 0, 0, 0),
"Date planned start should not have changed")
self.assertEqual(wo.duration_expected, (120.0 * 100 / wc.time_efficiency) + wc.time_start + wc.time_stop,
"Duration expected should be the sum of the time_cycle (taking the workcenter efficiency into account), the time_start and time_stop")
self.assertEqual(wo.date_planned_finished, wo.date_planned_start + timedelta(minutes=wo.duration_expected+60),
"Date planned finished should take into consideration the midday break")
duration_expected = wo.duration_expected
# Move the workorder in the planning so that it doesn't span across the midday break
wo.write({'date_planned_start': datetime(2022, 8, 8, 9, 0, 0, 0), 'date_planned_finished': datetime(2022, 8, 8, 12, 45, 0, 0)})
self.assertEqual(wo.date_planned_start, datetime(2022, 8, 8, 9, 0, 0, 0),
"Date planned start should have changed")
self.assertEqual(wo.duration_expected, duration_expected,
"Duration expected should not have changed")
self.assertEqual(wo.date_planned_finished, wo.date_planned_start + timedelta(minutes=duration_expected),
"Date planned finished should have been recomputed to be consistent with the workorder duration")
date_planned_finished = wo.date_planned_finished
# Extend workorder one hour to the left
wo.write({'date_planned_start': datetime(2022, 8, 8, 8, 0, 0, 0)})
self.assertEqual(wo.date_planned_start, datetime(2022, 8, 8, 8, 0, 0, 0),
"Date planned start should have changed")
self.assertEqual(wo.duration_expected, duration_expected + 60,
"Duration expected should have been extended by one hour")
self.assertEqual(wo.date_planned_finished, date_planned_finished,
"Date planned finished should not have changed")
self.assertEqual(wo.date_planned_finished, wo.date_planned_start + timedelta(minutes=wo.duration_expected),
"Date planned finished should be consistent with the workorder duration")
duration_expected = wo.duration_expected
# Extend workorder 2 hours to the right (span across the midday break)
wo.write({'date_planned_finished': datetime(2022, 8, 8, 13, 45, 0, 0)})
self.assertEqual(wo.date_planned_start, datetime(2022, 8, 8, 8, 0, 0, 0),
"Date planned start should not have changed")
self.assertEqual(wo.duration_expected, duration_expected + 60,
"Duration expected should have been extended by one hour")
self.assertEqual(wo.date_planned_finished, datetime(2022, 8, 8, 13, 45, 0, 0),
"Date planned finished should have changed")
self.assertEqual(wo.date_planned_finished, wo.date_planned_start + timedelta(minutes=wo.duration_expected+60),
"Date planned finished should be consistent with the workorder duration")
def test_plan_unplan_date(self): def test_plan_unplan_date(self):
""" Testing planning a workorder then cancel it and then plan it again. """ 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 The planned date must be the same the first time and the second time the
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment