[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:
@@ -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):
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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="['&', ('date_planned_start', '<', 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>
|
||||
|
||||
@@ -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'}
|
||||
|
||||
@@ -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.')
|
||||
|
||||
|
||||
@@ -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",
|
||||
}, {
|
||||
|
||||
Reference in New Issue
Block a user