[REF] mrp: date_planned_start and date_planned_finished
Before this patch, writing on the `date_expected` field on the raw moves did not update the `date_planned_start` (but writing on `date_planned_start` did write on the raw AND finished moves `date_expected`). This proved to be wrong: having the same `date_expected` on both raw and finished moves did not make sense (the manufacturing order is not instantaneous). It also makes difficult to make the rescheduling work: the rescheduling is done on the stock moves and thus updates `date_expected` on the raw moves. If this information is not transmitted to the manufacturing order, we won't be able to continue the rescheduling on the finished moves (the idea is to update the raw moves which will update the date_planned_start which will update the date_planned_finished which will update the finished moves) To have a more sane system, the `date_planned_start` and `date_planned_finished` are computed (with an inverse) on the raw and finished moves `date_expected` (similar to what's done on the picking model). There's a tweak though: as `run_manufacture` creates first the order with a `date_planned_finished` and then confirm it (which will only create the finished move at this moment), there's no place to write `date_planned_finished`. The compute thus defaults on `date_deadline` which is stored and the finished moves will be created later on with this value. We implement the same logic `_onchange_date_planned_start` in `write` so that it doesn't only rely on the onchange mechanism and the rescheduling can work when changing the `date_expected` on a child mo for example. task-1970595
This commit is contained in:
@@ -95,11 +95,13 @@ class MrpProduction(models.Model):
|
||||
help="Location where the system will stock the finished products.")
|
||||
date_planned_start = fields.Datetime(
|
||||
'Planned Date', copy=False, default=fields.Datetime.now,
|
||||
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)
|
||||
date_planned_finished = fields.Datetime(
|
||||
'Planned End Date',
|
||||
default=_get_default_date_planned_finished,
|
||||
compute='_compute_dates_planned', inverse='_set_date_planned_finished',
|
||||
help="Date at which you plan to finish the production.",
|
||||
copy=False, store=True)
|
||||
date_deadline = fields.Datetime(
|
||||
@@ -221,6 +223,18 @@ class MrpProduction(models.Model):
|
||||
delivery_count = fields.Integer(string='Delivery Orders', compute='_compute_picking_ids')
|
||||
confirm_cancel = fields.Boolean(compute='_compute_confirm_cancel')
|
||||
|
||||
@api.depends('move_raw_ids.date_expected', 'move_finished_ids.date_expected')
|
||||
def _compute_dates_planned(self):
|
||||
for production in self:
|
||||
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()])
|
||||
|
||||
def _set_date_planned_start(self):
|
||||
self.move_raw_ids.write({'date_expected': self.date_planned_start})
|
||||
|
||||
def _set_date_planned_finished(self):
|
||||
self.move_finished_ids.write({'date_expected': self.date_planned_finished})
|
||||
|
||||
@api.depends('move_raw_ids.state', 'move_finished_ids.state')
|
||||
def _compute_confirm_cancel(self):
|
||||
""" If the manufacturing order contains some done move (via an intermediate
|
||||
@@ -428,10 +442,6 @@ class MrpProduction(models.Model):
|
||||
|
||||
@api.onchange('date_planned_start')
|
||||
def _onchange_date_planned_start(self):
|
||||
self.move_raw_ids.update({
|
||||
'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)
|
||||
|
||||
@@ -474,12 +484,7 @@ class MrpProduction(models.Model):
|
||||
|
||||
def write(self, vals):
|
||||
res = super(MrpProduction, self).write(vals)
|
||||
if 'date_planned_start' in vals:
|
||||
moves = (self.mapped('move_raw_ids') + self.mapped('move_finished_ids')).filtered(
|
||||
lambda r: r.state not in ['done', 'cancel'])
|
||||
moves.write({
|
||||
'date_expected': fields.Datetime.to_datetime(vals['date_planned_start']),
|
||||
})
|
||||
|
||||
for production in self:
|
||||
if 'date_planned_start' in vals:
|
||||
if production.state in ['done', 'cancel']:
|
||||
@@ -488,6 +493,10 @@ class MrpProduction(models.Model):
|
||||
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()
|
||||
if not production.routing_id and vals.get('date_planned_start') and not vals.get('date_planned_finished'):
|
||||
new_date_planned_start = fields.Datetime.to_datetime(vals.get('date_planned_start'))
|
||||
if not production.date_planned_finished or new_date_planned_start >= production.date_planned_finished:
|
||||
production.date_planned_finished = new_date_planned_start + datetime.timedelta(hours=1)
|
||||
return res
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import fields
|
||||
from odoo.tests import Form
|
||||
from odoo.addons.mrp.tests.common import TestMrpCommon
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class TestProcurement(TestMrpCommon):
|
||||
|
||||
def test_procurement(self):
|
||||
@@ -177,6 +179,9 @@ class TestProcurement(TestMrpCommon):
|
||||
('product_id', '=', product_1.id),
|
||||
('state', '=', 'confirmed')
|
||||
])
|
||||
|
||||
self.assertAlmostEqual(mo.move_finished_ids.date_expected, mo.move_raw_ids.date_expected + timedelta(hours=1))
|
||||
|
||||
self.assertEqual(len(mo), 1, 'the manufacture order is not created')
|
||||
|
||||
mo_form = Form(mo)
|
||||
@@ -193,10 +198,12 @@ class TestProcurement(TestMrpCommon):
|
||||
self.assertEqual(move_orig.product_qty, 10, 'the quantity to produce is not good relative to the move')
|
||||
|
||||
move_dest_scheduled_date = move_dest.date_expected
|
||||
mo.date_planned_start += timedelta(days=5)
|
||||
# Adding 5 days to the date planned start makes the next move's date 4 days and 23 hours
|
||||
# later since the date planned start was set one hour before the date_planned_end.
|
||||
self.assertAlmostEqual(move_dest.date_expected, move_dest_scheduled_date + timedelta(days=4, hours=23), delta=timedelta(seconds=1), msg='date is not propagated')
|
||||
|
||||
mo_form = Form(mo)
|
||||
mo_form.date_planned_start = fields.Datetime.to_datetime(mo_form.date_planned_start) + timedelta(days=5)
|
||||
mo_form.save()
|
||||
|
||||
self.assertAlmostEqual(move_dest.date_expected, move_dest_scheduled_date + timedelta(days=5), delta=timedelta(seconds=1), msg='date is not propagated')
|
||||
|
||||
def test_finished_move_cancellation(self):
|
||||
"""Check state of finished move on cancellation of raw moves. """
|
||||
|
||||
Reference in New Issue
Block a user