[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:
committed by
Simon Lejeune
parent
518621d707
commit
328efb085a
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'), '&', ('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'), '&', ('state', '!=', 'draft'), ('additional', '=', False) ]}"/>
|
||||
|
||||
Reference in New Issue
Block a user