[REF] mrp: compute state for production

Refactor production order state:
- Directly have an idea of linked workorders's state
- Computed in order to manage it in a single method
- Add tooltips for each state

Remove the None in availability selection field instead use a False

Also refactor the ready_to_produce field in order to only set as
assigned a production order that have the component for its first
workorder.
This commit is contained in:
Arnold Moyaux
2018-11-27 11:56:56 +00:00
parent 257bd0e874
commit c407d9afb9
13 changed files with 152 additions and 95 deletions
+100 -44
View File
@@ -98,6 +98,34 @@ class MrpProduction(models.Model):
help="The list of operations (list of work centers) to produce the finished product. The routing "
"is mainly used to compute work center costs during operations and to plan future loads on "
"work centers based on production planning.")
state = fields.Selection([
('confirmed', 'Confirmed'),
('planned', 'Planned'),
('progress', 'In Progress'),
('to_close', 'To Close'),
('done', 'Done'),
('cancel', 'Cancelled')], string='State',
compute='_compute_state', copy=False, index=True, readonly=True,
store=True, track_visibility='onchange',
help=" * Confirmed: The MO is confirmed, the stock rules and the reordering of the components are trigerred.\n"
" * Planned: The WO are planned.\n"
" * In Progress: The production has started (on the MO or on the WO).\n"
" * To Close: The production is done, the MO has to be closed.\n"
" * Done: The MO is closed, the stock moves are posted. \n"
" * Cancelled: The MO has been cancelled, can't be confirmed anymore.")
reservation_state = fields.Selection([
('confirmed', 'Waiting'),
('assigned', 'Ready'),
('waiting', 'Waiting Another Operation')],
string='Material Availability',
compute='_compute_state', copy=False, index=True, readonly=True,
store=True, track_visibility='onchange', oldname='availability',
help=" * Ready: The material is available to start the production.\n\
* Waiting: The material is not available to start the production.\n\
The material availability is impacted by the manufacturing readiness\
defined on the BoM.")
move_raw_ids = fields.One2many(
'stock.move', 'raw_material_production_id', 'Raw Materials', oldname='move_lines',
copy=False, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
@@ -117,20 +145,6 @@ class MrpProduction(models.Model):
move_dest_ids = fields.One2many('stock.move', 'created_production_id',
string="Stock Movements of Produced Goods")
state = fields.Selection([
('confirmed', 'Confirmed'),
('planned', 'Planned'),
('progress', 'In Progress'),
('done', 'Done'),
('cancel', 'Cancelled')], string='State',
copy=False, default='confirmed', track_visibility='onchange')
availability = fields.Selection([
('assigned', 'Available'),
('partially_available', 'Partially Available'),
('waiting', 'Waiting'),
('none', 'None')], string='Materials Availability',
compute='_compute_availability', store=True)
unreserve_visible = fields.Boolean(
'Allowed to Unreserve Inventory', compute='_compute_unreserve_visible',
help='Technical field to check when we can unreserve')
@@ -147,8 +161,6 @@ class MrpProduction(models.Model):
default=lambda self: self.env['res.company']._company_default_get('mrp.production'),
required=True)
check_to_done = fields.Boolean(compute="_get_produced_qty", string="Check Produced Qty",
help="Technical Field to see if we can show 'Mark as Done' button")
qty_produced = fields.Float(compute="_get_produced_qty", string="Quantity Produced")
procurement_group_id = fields.Many2one(
'procurement.group', 'Procurement Group',
@@ -156,7 +168,6 @@ class MrpProduction(models.Model):
propagate = fields.Boolean(
'Propagate cancel and split',
help='If checked, when the previous move of the move (which was generated by a next procurement) is cancelled or split, the move generated by this move will too')
has_moves = fields.Boolean(compute='_has_moves')
scrap_ids = fields.One2many('stock.scrap', 'production_id', 'Scraps')
scrap_count = fields.Integer(compute='_compute_scrap_move_count', string='Scrap Move')
priority = fields.Selection([('0', 'Not urgent'), ('1', 'Normal'), ('2', 'Urgent'), ('3', 'Very Urgent')], 'Priority',
@@ -240,19 +251,51 @@ class MrpProduction(models.Model):
production.workorder_done_count = count_data.get(production.id, 0)
@api.multi
@api.depends('move_raw_ids.state', 'workorder_ids.move_raw_ids', 'bom_id.ready_to_produce')
def _compute_availability(self):
for order in self:
if not order.move_raw_ids:
order.availability = 'none'
continue
if order.bom_id.ready_to_produce == 'all_available':
order.availability = any(move.state not in ('assigned', 'done', 'cancel') for move in order.move_raw_ids) and 'waiting' or 'assigned'
@api.depends('move_raw_ids.state', 'move_finished_ids.state', 'workorder_ids', 'workorder_ids.state', 'qty_produced', 'move_raw_ids.quantity_done', 'product_qty')
def _compute_state(self):
""" Compute the production state. It use the same process than stock
picking. It exists 3 extra steps for production:
- planned: Workorder has been launched (workorders only)
- progress: At least one item is produced.
- to_close: The quantity produced is greater than the quantity to
produce and all work orders has been finished.
"""
# TODO: duplicated code with stock_picking.py
for production in self:
if not production.move_raw_ids:
production.state = 'confirmed'
elif all(move.state == 'draft' for move in production.move_raw_ids):
production.state = 'confirmed'
elif all(move.state == 'cancel' for move in production.move_raw_ids):
production.state = 'cancel'
elif all(move.state in ['cancel', 'done'] for move in production.move_raw_ids):
production.state = 'done'
elif production.move_finished_ids.filtered(lambda m: m.state not in ('cancel', 'done') and m.product_id.id == production.product_id.id)\
and (production.qty_produced >= production.product_qty)\
and (not production.routing_id or all(wo_state in ('cancel', 'done') for wo_state in production.workorder_ids.mapped('state'))):
production.state = 'to_close'
elif production.workorder_ids and any(wo_state in ('progress') for wo_state in production.workorder_ids.mapped('state'))\
or production.qty_produced > 0 and production.qty_produced < production.product_uom_qty:
production.state = 'progress'
elif production.workorder_ids:
production.state = 'planned'
else:
move_raw_ids = order.move_raw_ids.filtered(lambda m: m.product_qty)
partial_list = [x.state in ('partially_available', 'assigned') for x in move_raw_ids]
assigned_list = [x.state in ('assigned', 'done', 'cancel') for x in move_raw_ids]
order.availability = (all(assigned_list) and 'assigned') or (any(partial_list) and 'partially_available') or 'waiting'
production.state = 'confirmed'
# Compute reservation state
# State where the reservation does not matter.
if production.state in ('done', 'cancel'):
production.reservation_state = False
# Compute reservation state according to its component's moves.
else:
relevant_move_state = production.move_raw_ids._get_relevant_state_among_moves()
if relevant_move_state == 'partially_available':
if production.routing_id and production.routing_id.operation_ids and production.bom_id.ready_to_produce == 'asap':
production.reservation_state = production._get_ready_to_produce_state()
else:
production.reservation_state = 'confirmed'
elif relevant_move_state != 'draft':
production.reservation_state = relevant_move_state
@api.depends('move_raw_ids', 'is_locked', 'state', 'move_raw_ids.quantity_done')
def _compute_unreserve_visible(self):
@@ -273,6 +316,9 @@ class MrpProduction(models.Model):
@api.multi
@api.depends('move_raw_ids.quantity_done', 'move_raw_ids.product_uom_qty')
def _compute_consumed_less_than_planned(self):
""" Display a warning to the user if a component of the BoM has less
quantity than planned.
"""
for order in self:
order.consumed_less_than_planned = any(order.move_raw_ids.filtered(
lambda move: float_compare(move.quantity_done,
@@ -281,24 +327,14 @@ class MrpProduction(models.Model):
)
@api.multi
@api.depends('workorder_ids.state', 'move_finished_ids', 'is_locked')
@api.depends('workorder_ids.state', 'move_finished_ids', 'move_finished_ids.quantity_done', 'is_locked')
def _get_produced_qty(self):
for production in self:
done_moves = production.move_finished_ids.filtered(lambda x: x.state != 'cancel' and x.product_id.id == production.product_id.id)
qty_produced = sum(done_moves.mapped('quantity_done'))
wo_done = True
if any([x.state not in ('done', 'cancel') for x in production.workorder_ids]):
wo_done = False
production.check_to_done = production.is_locked and done_moves and (qty_produced >= production.product_qty) and (production.state not in ('done', 'cancel')) and wo_done
production.qty_produced = qty_produced
return True
@api.multi
@api.depends('move_raw_ids')
def _has_moves(self):
for mo in self:
mo.has_moves = any(mo.move_raw_ids)
@api.multi
def _compute_scrap_move_count(self):
data = self.env['stock.scrap'].read_group([('production_id', 'in', self.ids)], ['production_id'], ['production_id'])
@@ -497,6 +533,26 @@ class MrpProduction(models.Model):
move = self._generate_raw_move(bom_line, line_data)
return move, 0, quantity
def _get_ready_to_produce_state(self):
""" returns 'assigned' if enough components are reserved in order to complete
the first operation in the routing. If not returns 'waiting'
"""
self.ensure_one()
first_operation = self.routing_id.operation_ids[0]
# Get BoM line related to first opeation in rounting. If there is only
# one opeation in the routing then it will need all BoM lines.
bom_line_ids = self.env['mrp.bom.line']
if len(self.routing_id.operation_ids) == 1:
bom_line_ids = self.bom_id.bom_line_ids
else:
bom_line_ids = self.bom_id.bom_line_ids.filtered(lambda bl: bl.operation_id == first_operation)
bom_line_ids = bom_line_ids.filtered(lambda bl: not bl._skip_bom_line(self.product_id))
moves_in_first_operation = self.move_raw_ids.filtered(lambda m: m.bom_line_id in bom_line_ids)
if all(move.state == 'assigned' for move in moves_in_first_operation):
return 'assigned'
return 'confirmed'
@api.multi
def action_assign(self):
for production in self:
@@ -517,7 +573,7 @@ class MrpProduction(models.Model):
quantity = order.product_uom_id._compute_quantity(order.product_qty, order.bom_id.product_uom_id) / order.bom_id.product_qty
boms, lines = order.bom_id.explode(order.product_id, quantity, picking_type=order.bom_id.picking_type_id)
order._generate_workorders(boms)
return orders_to_plan.write({'state': 'planned'})
return True
@api.multi
def _generate_workorders(self, exploded_boms):
@@ -620,7 +676,7 @@ class MrpProduction(models.Model):
(finish_moves | raw_moves)._action_cancel()
picking_ids = production.picking_ids.filtered(lambda x: x.state not in ('done', 'cancel'))
picking_ids.action_cancel()
self.write({'state': 'cancel', 'is_locked': True})
if documents:
filtered_documents = {}
for (parent, responsible), rendering_context in documents.items():
@@ -673,8 +729,8 @@ class MrpProduction(models.Model):
self.post_inventory()
moves_to_cancel = (self.move_raw_ids | self.move_finished_ids).filtered(lambda x: x.state not in ('done', 'cancel'))
moves_to_cancel._action_cancel()
self.write({'state': 'done', 'date_finished': fields.Datetime.now()})
return self.write({'state': 'done'})
self.write({'date_finished': fields.Datetime.now()})
return True
@api.multi
def do_unreserve(self):
+1 -2
View File
@@ -40,7 +40,7 @@ class MrpWorkorder(models.Model):
help='Technical: used in views only.')
production_availability = fields.Selection(
'Stock Availability', readonly=True,
related='production_id.availability', store=True,
related='production_id.reservation_state', store=True,
help='Technical: used in views and domains only.')
production_state = fields.Selection(
'Production State', readonly=True,
@@ -428,7 +428,6 @@ class MrpWorkorder(models.Model):
raise UserError(_("You need to define at least one productivity loss in the category 'Performance'. Create one from the Manufacturing app, menu: Configuration / Productivity Losses."))
if self.production_id.state != 'progress':
self.production_id.write({
'state': 'progress',
'date_start': datetime.now(),
})
timeline.create({
+1 -1
View File
@@ -20,8 +20,8 @@ class StockPickingType(models.Model):
if not mrp_picking_types:
return
domains = {
'count_mo_waiting': [('availability', '=', 'waiting')],
'count_mo_todo': [('state', 'in', ('confirmed', 'planned', 'progress'))],
'count_mo_waiting': [('reservation_state', '=', 'waiting')],
'count_mo_late': [('date_planned_start', '<', fields.Date.today()), ('state', '=', 'confirmed')],
}
for field in domains:
+7 -6
View File
@@ -195,7 +195,7 @@ class TestMrpOrder(TestMrpCommon):
})
# assign consume material
man_order.action_assign()
self.assertEqual(man_order.availability, 'waiting', "Production order should be in waiting state.")
self.assertEqual(man_order.reservation_state, 'confirmed', "Production order should be in waiting state.")
# check consume materials of manufacturing order
self.assertEqual(len(man_order.move_raw_ids), 4, "Consume material lines are not generated proper.")
@@ -247,7 +247,7 @@ class TestMrpOrder(TestMrpCommon):
man_order.action_assign()
# Check production order status after assign.
self.assertEqual(man_order.availability, 'assigned', "Production order should be in assigned state.")
self.assertEqual(man_order.reservation_state, 'assigned', "Production order should be in assigned state.")
# Plan production order.
man_order.button_plan()
@@ -302,6 +302,7 @@ class TestMrpOrder(TestMrpCommon):
"""
self.bom_3.bom_line_ids.filtered(lambda x: x.product_id == self.product_5).unlink()
self.bom_3.bom_line_ids.filtered(lambda x: x.product_id == self.product_4).unlink()
self.bom_3.ready_to_produce = 'all_available'
production_2 = self.env['mrp.production'].create({
'name': 'MO-Test001',
@@ -313,7 +314,7 @@ class TestMrpOrder(TestMrpCommon):
production_2.action_assign()
# check sub product availability state is waiting
self.assertEqual(production_2.availability, 'waiting', 'Production order should be availability for waiting state')
self.assertEqual(production_2.reservation_state, 'confirmed', 'Production order should be availability for waiting state')
# Update Inventory
inventory_wizard = self.env['stock.change.product.qty'].create({
@@ -324,7 +325,7 @@ class TestMrpOrder(TestMrpCommon):
production_2.action_assign()
# check sub product availability state is partially available
self.assertEqual(production_2.availability, 'partially_available', 'Production order should be availability for partially available state')
self.assertEqual(production_2.reservation_state, 'confirmed', 'Production order should be availability for partially available state')
# Update Inventory
inventory_wizard = self.env['stock.change.product.qty'].create({
@@ -335,7 +336,7 @@ class TestMrpOrder(TestMrpCommon):
production_2.action_assign()
# check sub product availability state is assigned
self.assertEqual(production_2.availability, 'assigned', 'Production order should be availability for assigned state')
self.assertEqual(production_2.reservation_state, 'assigned', 'Production order should be availability for assigned state')
def test_empty_routing(self):
""" Check what happens when you work with an empty routing"""
@@ -409,7 +410,7 @@ class TestMrpOrder(TestMrpCommon):
'bom_id': bom_custom_laptop.id
})
mo_custom_laptop.action_assign()
self.assertEqual(mo_custom_laptop.availability, 'assigned')
self.assertEqual(mo_custom_laptop.reservation_state, 'assigned')
# produce one item, call `post_inventory`
context = {"active_ids": [mo_custom_laptop.id], "active_id": mo_custom_laptop.id}
+3 -3
View File
@@ -45,7 +45,7 @@ class TestProcurement(TestMrpCommon):
produce_product_4 = self.env['mrp.production'].search([('product_id', '=', self.product_4.id),
('move_dest_ids', '=', move_raw_product4[0].id)])
# produce product
self.assertEqual(produce_product_4.availability, 'waiting', "Consume material not available")
self.assertEqual(produce_product_4.reservation_state, 'confirmed', "Consume material not available")
# Create production order
# -------------------------
@@ -61,7 +61,7 @@ class TestProcurement(TestMrpCommon):
produce_product_4.action_assign()
self.assertEqual(produce_product_4.product_qty, 8, "Wrong quantity of finish product.")
self.assertEqual(produce_product_4.product_uom_id, self.uom_dozen, "Wrong quantity of finish product.")
self.assertEqual(produce_product_4.availability, 'assigned', "Consume material not available")
self.assertEqual(produce_product_4.reservation_state, 'assigned', "Consume material not available")
# produce product4
# ---------------
@@ -91,7 +91,7 @@ class TestProcurement(TestMrpCommon):
# ------------------------------------
self.assertEqual(production_product_6.availability, 'assigned', "Consume material not available")
self.assertEqual(production_product_6.reservation_state, 'assigned', "Consume material not available")
produce_form = Form(self.env['mrp.product.produce'].with_context({
'active_id': production_product_6.id,
'active_ids': [production_product_6.id],
+1 -1
View File
@@ -84,7 +84,7 @@ class TestWarehouse(common.TestMrpCommon):
production_3.action_assign()
# Check Manufacturing order's availability.
self.assertEqual(production_3.availability, 'assigned', "Production order's availability should be Available.")
self.assertEqual(production_3.reservation_state, 'assigned', "Production order's availability should be Available.")
location_id = production_3.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel')) and production_3.location_src_id.id or production_3.location_dest_id.id,
@@ -179,7 +179,7 @@ class TestMultistepManufacturingWarehouse(TestMrpCommon):
self.assertTrue(self.env['stock.quant']._gather(self.raw_product, self.warehouse.pbm_loc_id))
production_order.action_assign()
self.assertEqual(production_order.availability, 'assigned')
self.assertEqual(production_order.reservation_state, 'assigned')
self.assertEqual(picking_stock_postprod.state, 'waiting')
produce_form = Form(self.env['mrp.product.produce'].with_context({
+2 -2
View File
@@ -288,7 +288,7 @@ class TestWorkOrderProcess(common.TransactionCase):
# Check production order status of availablity
self.assertEqual(mo_custom_laptop.availability, 'waiting')
self.assertEqual(mo_custom_laptop.reservation_state, 'confirmed')
# --------------------------------------------------
# Set inventory for rawmaterial charger and keybord
@@ -321,7 +321,7 @@ class TestWorkOrderProcess(common.TransactionCase):
# Check consumed move status
mo_custom_laptop.action_assign()
self.assertEqual( mo_custom_laptop.availability, 'assigned')
self.assertEqual(mo_custom_laptop.reservation_state, 'assigned')
# Check current status of raw materials.
for move in mo_custom_laptop.move_raw_ids:
+24 -23
View File
@@ -14,9 +14,9 @@
<field name="origin"/>
<field name="product_qty" sum="Total Qty" string="Quantity"/>
<field name="product_uom_id" string="Unit of Measure" options="{'no_open':True,'no_create':True}" groups="uom.group_uom"/>
<field name="availability"/>
<field name="routing_id" groups="mrp.group_mrp_routings"/>
<field name="state"/>
<field name="reservation_state"/>
</tree>
</field>
</record>
@@ -36,17 +36,16 @@
<form string="Manufacturing Orders">
<header>
<field name="consumed_less_than_planned" invisible="1"/>
<button name="button_mark_done" attrs="{'invisible': ['|', ('check_to_done', '=', False), ('consumed_less_than_planned', '=', True)]}" string="Mark as Done" type="object" class="oe_highlight"/>
<button name="button_mark_done" attrs="{'invisible': ['|', ('check_to_done', '=', False), ('consumed_less_than_planned', '=', False)]}" string="Mark as Done" type="object" class="oe_highlight" confirm="You have consumed less material than what was planned. Are you sure you want to close this MO?"/>
<button name="action_assign" attrs="{'invisible': ['|', ('availability', 'in', ('assigned', 'none')), ('is_locked', '=', False)]}" string="Check availability" type="object" class="oe_highlight"/>
<button name="button_mark_done" attrs="{'invisible': ['|', ('state', '!=', 'to_close'), ('consumed_less_than_planned', '=', True)]}" string="Mark as Done" type="object" class="oe_highlight"/>
<button name="button_mark_done" attrs="{'invisible': ['|', ('state', '!=', 'to_close'), ('consumed_less_than_planned', '=', False)]}" string="Mark as Done" type="object" class="oe_highlight" confirm="You have consumed less material than what was planned. Are you sure you want to close this MO?"/>
<button name="action_assign" attrs="{'invisible': ['|', '|', ('is_locked', '=', False), ('state', 'in', ('done', 'cancel')), ('reservation_state', '=', 'assigned')]}" string="Check availability" type="object" class="oe_highlight"/>
<button name="button_plan" attrs="{'invisible': ['|', '|', ('state', '!=', 'confirmed'), ('routing_id', '=', False), ('is_locked', '=', False)]}" type="object" string="Create Workorders" class="oe_highlight"/>
<button name="open_produce_product" attrs="{'invisible': ['|', '|', '|', '|', ('is_locked', '=', False), ('check_to_done', '=', True), ('availability', 'not in', ['partially_available', 'assigned']), ('state', 'not in', ('confirmed','progress')), ('routing_id', '!=', False)]}" string="Produce" type="object" class="oe_highlight"/>
<button name="open_produce_product" attrs="{'invisible': ['|', '|', '|', '|', ('is_locked', '=', False), ('check_to_done', '=', True), ('availability', '!=', 'waiting'), ('state', 'not in', ('confirmed','progress')), ('routing_id', '!=', False)]}" string="Produce" type="object"/>
<button name="open_produce_product" attrs="{'invisible': ['|', '|', '|', ('state', '=', 'to_close'), ('is_locked', '=', False), ('reservation_state', '!=', 'assigned'), ('routing_id', '!=', False)]}" string="Produce" type="object" class="oe_highlight"/>
<button name="open_produce_product" attrs="{'invisible': ['|', '|', '|', ('state', '=', 'to_close'), ('is_locked', '=', False), ('reservation_state', 'not in', ('confirmed', 'waiting')), ('routing_id', '!=', False)]}" string="Produce" type="object"/>
<button name="post_inventory" string="Post Inventory" type="object" attrs="{'invisible': [('post_visible', '=', False)]}" groups="base.group_no_one"/>
<button name="action_cancel" type="object" string="Cancel" attrs="{'invisible': ['|', '|', ('is_locked', '=', False), ('state', 'in', ('done','cancel')), ('check_to_done', '=', True)]}"/>
<button name="button_scrap" type="object" string="Scrap" attrs="{'invisible': ['|', '|', ('availability', '=', 'none'), ('state', 'in', ('cancel')), ('is_locked', '=', False)]}"/>
<button name="action_cancel" type="object" string="Cancel" attrs="{'invisible': ['|', '|', ('is_locked', '=', False), ('state', 'in', ('done','cancel'))]}"/>
<button name="button_scrap" type="object" string="Scrap" attrs="{'invisible': ['|', ('state', '=', 'cancel'), ('is_locked', '=', False)]}"/>
<button name="button_unreserve" type="object" string="Unreserve" attrs="{'invisible': [('unreserve_visible', '=', False)]}"/>
<span class="badge badge-danger" attrs="{'invisible': ['|',('availability', 'in', ('assigned', 'none')), ('state', 'not in', ('confirmed','progress'))]}">Raw materials not available!</span>
<field name="state" widget="statusbar" statusbar_visible="confirmed,progress,done"/>
<button name="action_toggle_is_locked" attrs="{'invisible': ['|', '|', ('state', '=', 'cancel'), ('id', '=', False), ('is_locked', '=', False)]}" string="Unlock" groups="mrp.group_mrp_manager" type="object" help="Unlock the manufacturing order to correct what has been consumed or produced."/>
<button name="action_toggle_is_locked" attrs="{'invisible': [('is_locked', '=', True)]}" string="Lock" class="oe_highlight" groups="mrp.group_mrp_manager" type="object"/>
@@ -80,15 +79,13 @@
</div>
<group>
<group>
<field name="has_moves" invisible="1"/>
<field name="check_to_done" invisible="1"/>
<field name="id" invisible="1"/>
<field name="product_id" attrs="{'readonly': [('has_moves', '=', True)]}" domain="[('bom_ids', '!=', False), ('bom_ids.active', '=', True), ('bom_ids.type', '=', 'normal')]"/>
<field name="product_id" attrs="{'readonly': [('move_raw_ids', '!=', False)]}" domain="[('bom_ids', '!=', False), ('bom_ids.active', '=', True), ('bom_ids.type', '=', 'normal')]"/>
<field name="product_tmpl_id" invisible="1"/>
<label for="product_qty"/>
<div class="o_row">
<field name="product_qty" attrs="{'readonly': [('has_moves', '=', True)]}"/>
<field name="product_uom_id" options="{'no_open':True,'no_create':True}" groups="uom.group_uom" attrs="{'readonly': [('has_moves', '=', True)]}"/>
<field name="product_qty" attrs="{'readonly': [('move_raw_ids', '!=', False')]}"/>
<field name="product_uom_id" options="{'no_open':True,'no_create':True}" force_save="1" groups="uom.group_uom" attrs="{'readonly': [('move_raw_ids', '!=', False)]}"/>
<button type="action"
name="%(mrp.action_change_production_qty)d"
string="Update" class="oe_link" attrs="{'invisible': ['|', ('state', 'in', ('done','cancel')), ('id', '=', False)]}"/>
@@ -100,7 +97,7 @@
('product_tmpl_id.product_variant_ids','=',product_id),
('product_id','=',False),
('type', '=', 'normal')]"
context="{'default_product_tmpl_id': product_tmpl_id}" required="1" attrs="{'readonly': [('has_moves', '=', True)]}"/>
context="{'default_product_tmpl_id': product_tmpl_id}" required="1" attrs="{'readonly': [('move_raw_ids', '!=', False)]}"/>
<field name="routing_id" groups="mrp.group_mrp_routings"/>
</group>
<group>
@@ -108,7 +105,7 @@
<field name="date_planned_finished" invisible="1"/>
<field name="user_id"/>
<field name="origin"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}" attrs="{'readonly': [('has_moves', '=', True)]}"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}" attrs="{'readonly': [('move_raw_ids', '!=', False)]}"/>
<field name="show_final_lots" invisible="1"/>
<field name="production_location_id" invisible="1" readonly="1"/>
</group>
@@ -251,15 +248,18 @@
<field name="arch" type="xml">
<search string="Search Production">
<field name="name" string="Production" filter_domain="['|',('name','ilike',self),('origin','ilike',self)]"/>
<filter string="To Do" name="todo" domain="[('state','in',('confirmed', 'planned','progress'))]"
<filter string="To Do" name="todo" domain="[('state','in',('confirmed', 'planned','progress', 'to_close'))]"
help="Manufacturing Orders which are in confirmed state."/>
<separator/>
<filter string="Confirmed" name="confirmed" domain="[('state','=','confirmed')]"/>
<filter string="Waiting for Materials" name="waiting" domain="[('availability', '=', 'waiting')]"/>
<filter string="Planned" name="planned" domain="[('state','=','planned')]" groups="mrp.group_mrp_routings"/>
<filter string="In Progress" name="inprogress" domain="[('state','=','progress')]"
help="Manufacturing Orders which are currently in production."/>
<filter string="Done" name="done" domain="[('state', '=', 'done')]"/>
<filter string="Confirmed" name="filter_confirmed" domain="[('state','=','confirmed')]"/>
<filter string="Planned" name="filter_planned" domain="[('state','=','planned')]" groups="mrp.group_mrp_routings"/>
<filter string="In Progress" name="filter_in_progress" domain="[('state','=','progress')]"/>
<filter string="To Close" name="filter_to_close" domain="[('state', '=', 'to_close')]"/>
<filter string="Done" name="filter_done" domain="[('state', '=', 'done')]"/>
<filter string="Cancelled" name="filter_cancel" domain="[('state', '=', 'cancel')]"/>
<separator/>
<filter string="Waiting for Material" name="waiting" domain="[('reservation_state', 'in', ('waiting', 'confirmed'))]"/>
<filter string="Material Available" name="filter_ready" domain="[('reservation_state', '=', 'assigned')]"/>
<separator/>
<filter string="Late" domain="['&amp;', ('date_planned_start', '&lt;', current_date), ('state', '=', 'confirmed')]"
name="late" help="Production started late"/>
@@ -286,6 +286,7 @@
<filter string="Routing" name="routing" domain="[]" context="{'group_by':'routing_id'}" groups="mrp.group_mrp_routings"/>
<filter string="Scheduled Date" name="scheduled_date" domain="[]" context="{'group_by':'date_planned_start'}" help="Scheduled Date by Month"/>
<filter string="Status" name="status" domain="[]" context="{'group_by': 'state'}"/>
<filter string="Material Availability" name="groupby_reservation_state" domain="[]" context="{'group_by': 'reservation_state'}"/>
</group>
</search>
</field>
-1
View File
@@ -65,7 +65,6 @@ class MrpProductProduce(models.TransientModel):
self.check_finished_move_lots()
if self.production_id.state == 'confirmed':
self.production_id.write({
'state': 'progress',
'date_start': datetime.now(),
})
return {'type': 'ir.actions.act_window_close'}
+10 -9
View File
@@ -254,7 +254,7 @@ class TestSaleMrpFlow(common.TransactionCase):
# --------------------------------------------------
mnf_product_d.action_assign()
self.assertEqual(mnf_product_d.availability, 'assigned', 'Availability should be assigned')
self.assertEqual(mnf_product_d.reservation_state, 'assigned', 'Availability should be assigned')
self.assertEqual(move.state, 'assigned', "Wrong state in 'To consume line' of manufacturing order.")
# ------------------
@@ -272,7 +272,7 @@ class TestSaleMrpFlow(common.TransactionCase):
mnf_product_d.post_inventory()
# Check state of manufacturing order.
self.assertEqual(mnf_product_d.state, 'progress', 'Manufacturing order should still be in progress state.')
self.assertEqual(mnf_product_d.state, 'done', 'Manufacturing order should still be in progress state.')
# Check available quantity of product D.
self.assertEqual(product_d.qty_available, 20, 'Wrong quantity available of product D.')
@@ -307,7 +307,7 @@ class TestSaleMrpFlow(common.TransactionCase):
# ---------------------------------------------------
mnf_product_a.action_assign()
self.assertEqual(mnf_product_a.availability, 'assigned', 'Manufacturing order inventory state should be available.')
self.assertEqual(mnf_product_a.reservation_state, 'assigned', 'Manufacturing order inventory state should be available.')
moves = self.StockMove.search([('raw_material_production_id', '=', mnf_product_a.id), ('product_id', '=', product_c.id)])
# Check product c move line state.
@@ -316,15 +316,16 @@ class TestSaleMrpFlow(common.TransactionCase):
# Produce product A.
# ------------------
produce_a = self.ProductProduce.with_context(
# {'active_ids': [mnf_product_a.id], 'active_id': mnf_product_a.id}).create({'mode': 'consume_produce', 'product_qty': mnf_product_a.product_qty})
# produce_a.on_change_qty()
{'active_ids': [mnf_product_a.id], 'active_id': mnf_product_a.id}).create({})
produce_form = Form(self.ProductProduce.with_context({
'active_id': mnf_product_a.id,
'active_ids': [mnf_product_a.id],
}))
produce_a = produce_form.save()
produce_a.do_produce()
mnf_product_a.post_inventory()
# Check state of manufacturing order product A.
self.assertEqual(mnf_product_a.state, 'progress', 'Manufacturing order should still be in the progress state.')
self.assertEqual(mnf_product_a.state, 'done', 'Manufacturing order should still be in the progress state.')
# Check product A avaialble quantity should be 120.
self.assertEqual(product_a.qty_available, 120, 'Wrong quantity available of product A.')
+1 -1
View File
@@ -614,7 +614,7 @@ class StockMove(models.Model):
.filtered(lambda move: move.state not in ['cancel', 'done'])\
.sorted(key=lambda move: (sort_map.get(move.state, 0), move.product_uom_qty))
# The picking should be the same for all moves.
if moves_todo[0].picking_id.move_type == 'one':
if moves_todo[0].picking_id and moves_todo[0].picking_id.move_type == 'one':
most_important_move = moves_todo[0]
if most_important_move.state == 'confirmed':
return 'confirmed' if most_important_move.product_uom_qty else 'assigned'
@@ -555,7 +555,7 @@ tour.register('main_flow_tour', {
content: _t("Check availability"),
position: "bottom",
}, {
trigger: ".o_statusbar_buttons > button.btn-primary:enabled:contains('Produce')",
trigger: ".o_statusbar_buttons > button:enabled:contains('Produce')",
content: _t("Produce"),
position: "bottom",
}, {