[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
This commit is contained in:
Rémy Voet (ryv)
2020-06-15 16:59:41 +02:00
committed by Simon Lejeune
parent 518621d707
commit 328efb085a
3 changed files with 38 additions and 78 deletions
+17 -24
View File
@@ -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)
+13 -11
View File
@@ -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
+8 -43
View File
@@ -59,7 +59,6 @@
<field name="arch" type="xml">
<form string="Manufacturing Orders">
<header>
<field name="bom_has_operations" invisible="1"/>
<field name="confirm_cancel" invisible="1"/>
<field name="show_lock" invisible="1"/>
<button name="button_mark_done" attrs="{'invisible': ['|', ('state', '!=', 'progress'), ('qty_producing', '=', 0)]}" string="Backorder" type="object" class="oe_highlight"/>
@@ -82,6 +81,8 @@
</header>
<sheet>
<field name="reservation_state" invisible="1"/>
<field name="is_partially_planned" invisible="1"/>
<field name="date_planned_finished" invisible="1"/>
<field name="is_locked" invisible="1"/>
<field name="qty_produced" invisible="1"/>
<field name="unreserve_visible" invisible="1"/>
@@ -157,47 +158,11 @@
context="{'default_product_tmpl_id': product_tmpl_id}" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
</group>
<group>
<field name="date_deadline" attrs="{'readonly': [('state', 'in', ['done', 'cancel'])]}"/>
<div class="o_row o_td_label" attrs="{'invisible': [
'|',
('bom_has_operations', '!=', False),
('state', 'in', ['draft', 'confirmed', 'to_close'])
]}">
<label for="date_planned_start" string="Planned Date"
attrs="{'invisible': [
'|',
('bom_has_operations', '!=', False),
('state', 'in', ['draft', 'confirmed', 'to_close'])
]}"/>
</div>
<div class="o_row" attrs="{'invisible': [
'|',
('bom_has_operations', '!=', False),
('state', 'in', ['draft', 'confirmed', 'to_close'])
]}">
<field name="date_deadline"/>
<label for="date_planned_start"/>
<div class="o_row">
<field name="date_planned_start"
attrs="{'readonly': [
('bom_has_operations', '!=', False),
('state', 'not in', ['draft', 'confirmed', 'to_close'])],
'invisible': [
'|',
('bom_has_operations', '!=', False),
('state', 'in', ['draft', 'confirmed', 'to_close'])]}"/>
<label for="date_planned_finished" string="to" attrs="{'invisible': [
'|',
('id', '=', False),
'|',
('bom_has_operations', '!=', False),
('state', 'in', ['draft', 'confirmed', 'to_close'])
]}"/>
<field name="date_planned_finished" required="1"
attrs="{'readonly': [
('bom_has_operations', '!=', False)
], 'invisible': [
'|',
('bom_has_operations', '!=', False),
('state', 'in', ['draft', 'confirmed', 'to_close'])
]}"/>
attrs="{'readonly': ['|', ('is_partially_planned', '=', True), ('state', 'in', ['close', 'cancel'])]}"/>
<field name="delay_alert_date" invisible="1"/>
<field string=" " name="json_popover" widget="stock_rescheduling_popover" attrs="{'invisible': [('delay_alert_date', '=', False)]}"/>
</div>
@@ -210,8 +175,8 @@
</group>
<notebook>
<page string="Components" name="components">
<field name="move_raw_ids"
context="{'default_date_expected': date_planned_start, 'default_location_id': location_src_id, 'default_location_dest_id': production_location_id, 'default_state': 'draft', 'default_raw_material_production_id': id, 'default_picking_type_id': picking_type_id, 'default_company_id': company_id}"
<field name="move_raw_ids"
context="{'default_date_expected': date_planned_start, 'default_location_id': location_src_id, 'default_location_dest_id': production_location_id, 'default_state': 'draft', 'default_raw_material_production_id': id, 'default_picking_type_id': picking_type_id, 'default_company_id': company_id}"
attrs="{'readonly': ['|', ('state', '=', 'cancel'), '&amp;', ('state', '=', 'done'), ('is_locked', '=', True)]}">
<tree delete="0" default_order="is_done,sequence" editable="bottom">
<field name="product_id" required="1" attrs="{'readonly': ['|', '|', ('has_move_lines', '=', True), ('state', '=', 'cancel'), '&amp;', ('state', '!=', 'draft'), ('additional', '=', False) ]}"/>