Skip to content
Permalink
Browse files

[IMP] mrp: multiple finished product lots

This commit allows the user in charge of manufacturing products to
split his production into different lots in a workorder. Entering a
lot in a workorder will add constraint to all the next ones. It will
also prefil the final lot field for futur wo as well as the corresponding quantity

Task : 1891864
  • Loading branch information...
Whenrow committed Mar 25, 2019
1 parent 95e6a86 commit 24e6b0103008edbc1eff2bf12bee4784009fd72f
@@ -225,7 +225,7 @@ def _update_raw_moves(self):
if float_compare(line.qty_done, 0, precision_rounding=line.product_uom_id.rounding) > 0:
vals_list += line._create_extra_move_lines()

self.workorder_line_ids.unlink()
workorder_lines_to_process.filtered(lambda line: line.qty_done == 0).unlink()
self.env['stock.move.line'].create(vals_list)


@@ -238,6 +238,7 @@ class MrpAbstractWorkorderLine(models.AbstractModel):
product_id = fields.Many2one('product.product', 'Product', required=True)
product_tracking = fields.Selection(related="product_id.tracking")
lot_id = fields.Many2one('stock.production.lot', 'Lot/Serial Number')
final_lot_id = fields.Many2one('stock.production.lot', 'Finished Product Lot/Serial Number')
qty_to_consume = fields.Float('To Consume', digits=dp.get_precision('Product Unit of Measure'))
product_uom_id = fields.Many2one('uom.uom', string='Unit of Measure')
qty_done = fields.Float('Consumed', digits=dp.get_precision('Product Unit of Measure'))
@@ -363,6 +364,7 @@ def _create_extra_move_lines(self):
vals.update({'lot_id': self.lot_id.id})

vals_list.append(vals)
self.qty_done -= vals['qty_done']

return vals_list

@@ -103,20 +103,49 @@ class MrpWorkorder(models.Model):
capacity = fields.Float(
'Capacity', default=1.0,
help="Number of pieces that can be produced in parallel.")
workorder_line_ids = fields.One2many('mrp.workorder.line', 'workorder_id', string='Workorder lines')
workorder_line_ids = fields.One2many(
'mrp.workorder.line',
'workorder_id',
string='Workorder lines',
domain=[('finished_check', '=', False)]
)
final_lot_line_ids = fields.One2many(
'mrp.workorder.line',
'workorder_id',
string='Final Lot lines',
domain=[('finished_check', '=', True)]
)
final_lot_domain = fields.One2many('stock.production.lot', compute="_compute_final_lot_domain")

@api.depends('production_id')
@api.onchange('final_lot_id')
def _onchange_final_lot(self):
previous_wo = self.env['mrp.workorder'].search([
('next_work_order_id', '=', self.id)
])
# update default quantity to produce depending on the previous qty_producing
# in the previous workorder for the current final lot
if previous_wo:
line = previous_wo.final_lot_line_ids.filtered(lambda line: line.final_lot_id == self.final_lot_id)
if line:
self.qty_producing = line.qty_done

def _compute_final_lot_domain(self):
# check if self is not the first workorder in the list
if self.env['mrp.workorder'].search([('next_work_order_id', '=', self.id)]):
self.final_lot_domain = self.env['stock.production.lot'].search([
('use_next_on_work_order_id', '=', self.id),
]).ids
else:
self.final_lot_domain = self.env['stock.production.lot'].search([
('product_id', '=', self.product_id.id),
]).ids
for wo in self:
if wo.product_tracking != 'none':
lots_stack = {}
for line in wo.production_id.workorder_ids.mapped('final_lot_line_ids'):
if line.final_lot_id.id not in lots_stack:
lots_stack[line.final_lot_id.id] = line.qty_done
elif line.qty_done > lots_stack[line.final_lot_id.id]:
lots_stack[line.final_lot_id.id] = line.qty_done
if sum(lots_stack.values()) < wo.qty_production:
wo.final_lot_domain = self.env['stock.production.lot'].search([
('product_id', '=', wo.product_id.id),
]).ids
if wo.product_tracking == 'serial':
wo.final_lot_domain -= self.env['stock.production.lot'].browse(lots_stack.keys())
else:
wo.final_lot_domain = self.env['stock.production.lot'].browse(lots_stack.keys())

@api.multi
def name_get(self):
@@ -194,9 +223,29 @@ def generate_wo_lines(self):
line_values = self._generate_lines_values(move, qty_to_consume)
self.workorder_line_ids |= self.env['mrp.workorder.line'].create(line_values)

def _assign_default_final_lot_id(self):
self.final_lot_id = self.env['stock.production.lot'].search([('use_next_on_work_order_id', '=', self.id)],
order='create_date, id', limit=1)
def _assign_default_final_lot_id(self, reference_lot_lines):
if not reference_lot_lines:
# self is the first workorder
self.qty_producing = self.qty_remaining
self.final_lot_id = False
if self.product_tracking == 'serial':
self.qty_producing = 1
for r_line in reference_lot_lines:
# see which lot we could suggest and its related qty_producing
if not r_line.final_lot_id:
continue
candidates = self.final_lot_line_ids.filtered(lambda line: line.final_lot_id == r_line.final_lot_id)
if not candidates:
self.write({
'final_lot_id': r_line.final_lot_id.id,
'qty_producing': r_line.qty_done,
})
elif candidates.qty_done < r_line.qty_done:
self.write({
'final_lot_id': r_line.final_lot_id.id,
'qty_producing': r_line.qty_done - candidates.qty_done,
})
break

def _get_byproduct_move_line(self, by_product_move, quantity):
return {
@@ -230,32 +279,100 @@ def record_production(self):
# Transfer quantities from temporary to final move line or make them final
self._update_raw_moves()

# Record the final lot and quantity produced to suggest on next workorders
if self.product_tracking != 'none':
self._update_lot_lines()

# Update workorder quantity produced
self.qty_produced += self.qty_producing

if self.final_lot_id:
self.final_lot_id.use_next_on_work_order_id = self.next_work_order_id
self.final_lot_id = False
if self.next_work_order_id and self.production_id.product_id.tracking != 'none' and not self.qty_remaining:
self.next_work_order_id._assign_default_final_lot_id(self.final_lot_line_ids)

# Set a qty producing
rounding = self.production_id.product_uom_id.rounding
if float_compare(self.qty_produced, self.production_id.product_qty, precision_rounding=rounding) >= 0:
self.qty_producing = 0
elif self.production_id.product_id.tracking == 'serial':
self._assign_default_final_lot_id()
self.qty_producing = 1.0
self.generate_wo_lines()
else:
self.qty_producing = float_round(self.production_id.product_qty - self.qty_produced, precision_rounding=rounding)
self.generate_wo_lines()

if self.next_work_order_id and self.production_id.product_id.tracking != 'none':
self.next_work_order_id._assign_default_final_lot_id()
if self.product_tracking != 'none':
previous_wo = self.env['mrp.workorder'].search([
('next_work_order_id', '=', self.id)
])
self._assign_default_final_lot_id(previous_wo.final_lot_line_ids)
line_values = self._update_workorder_lines()
self.workorder_line_ids |= self.workorder_line_ids.create(line_values['to_create'])
if line_values['to_delete']:
line_values['to_delete'].unlink()
for line, vals in line_values['to_update'].items():
line.write(vals)

if float_compare(self.qty_produced, self.production_id.product_qty, precision_rounding=rounding) >= 0:
self.button_finish()
return True

def _update_lot_lines(self):
"""
1. Check that the final lot and the quantity producing is valid regarding
other workorders of this production
2. Save final lot and quantity producing to suggest on next workorder
"""
ll = self.final_lot_line_ids.filtered(lambda line: line.final_lot_id == self.final_lot_id)
previous_wo = self.env['mrp.workorder'].search([
('next_work_order_id', '=', self.id)
])
message = False
if previous_wo:
previous_ll = previous_wo.final_lot_line_ids.filtered(lambda line: line.final_lot_id == self.final_lot_id)
empty_ll = previous_wo.final_lot_line_ids.filtered(lambda line: not line.final_lot_id)
# lot was already entered in this workorder;
if ll:
if ll.qty_done + self.qty_producing <= previous_ll.qty_done:
ll.qty_done += self.qty_producing
else:
if ll.qty_done + self.qty_producing <= previous_ll.qty_done + empty_ll.qty_done:
ll.qty_done += self.qty_producing
empty_ll.qty_done -= previous_ll.qty_done - (ll.qty_done + self.qty_producing)
else:
message = _('You have produced %s %s of lot %s in the previous workorder. You are trying to produce %s in this one') % (previous_ll.qty_done, self.product_id.uom_id.name, self.final_lot_id.name, ll.qty_done + self.qty_producing)
else:
# see if this lot was entered before
if previous_ll:
if self.qty_producing <= previous_ll.qty_done:
previous_ll.copy({
'workorder_id': self.id,
'qty_done': self.qty_producing,
})
elif empty_ll and self.qty_producing <= previous_ll.qty_done + empty_ll.qty_done:
previous_ll.copy({
'workorder_id': self.id,
'qty_done': self.qty_producing,
})
empty_ll.qty_done -= previous_ll.qty_done - self.qty_producing
else:
message = _('You have produced %s %s of lot %s in the previous workorder. You are trying to produce %s in this one') % (previous_ll.qty_done, self.product_id.uom_id.name, self.final_lot_id.name, ll.qty_done + self.qty_producing)
elif empty_ll and self.qty_producing <= empty_ll.qty_done:
empty_ll.copy({
'workorder_id': self.id,
'qty_done': self.qty_producing,
'final_lot_id': self.final_lot_id.id,
})
empty_ll.qty_done -= self.qty_producing
else:
message = _('You are trying to produce a lot no used in previous workorder')
else:
if ll:
ll.qty_done += self.qty_producing
else:
self.env['mrp.workorder.line'].create({
'workorder_id': self.id,
'qty_done': self.qty_producing,
'final_lot_id': self.final_lot_id.id,
'finished_check': True,
'product_id': self.product_id.id,
})
if message:
raise UserError(message)

@api.multi
def button_start(self):
self.ensure_one()
@@ -390,6 +507,7 @@ class MrpWorkorderLine(models.Model):
_description = "Workorder move line"

workorder_id = fields.Many2one('mrp.workorder', 'Workorder')
finished_check = fields.Boolean('Finished Lot Line', default=False)

def _get_final_lot(self):
return self.workorder_id.final_lot_id
@@ -1,17 +1,13 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import fields, models, _, api
from odoo import models, _, api
from odoo.exceptions import UserError


class StockProductionLot(models.Model):
_inherit = 'stock.production.lot'

use_next_on_work_order_id = fields.Many2one('mrp.workorder',
string="Next Work Order to Use",
help='Technical: used to figure out default serial number on work orders')

def _check_edit_create(self):
active_mo_id = self.env.context.get('active_mo_id')
if active_mo_id:
@@ -209,6 +209,7 @@ def test_00b_workorder_process(self):
# ---------------------------------------------------------
workorders[1].button_start()
workorders[1].qty_producing = 1.0
workorders[1].final_lot_id = finished_lot
workorders[1].workorder_line_ids[0].write({'lot_id': lot_leg.id, 'qty_done': 4})
workorders[1].record_production()
move_leg = production_table.move_raw_ids.filtered(lambda p: p.product_id == product_table_leg)
@@ -220,6 +221,7 @@ def test_00b_workorder_process(self):
# ---------------------------------------------------------
workorders[2].button_start()
workorders[2].qty_producing = 1.0
workorders[2].final_lot_id = finished_lot
move_lot = workorders[2].workorder_line_ids[0]
move_lot.write({'lot_id': lot_bolt.id, 'qty_done': 4})
move_table_bolt = production_table.move_raw_ids.filtered(lambda p: p.product_id.id == product_bolt.id)
@@ -166,6 +166,13 @@
<field name="move_id" invisible="1"/>
</tree>
</field>
<field name="final_lot_line_ids" attrs="{'invisible': [('final_lot_line_ids', '=', [])]}" readonly="1">
<tree editable="bottom" create="0" delete="0">
<field name="product_id"/>
<field name="final_lot_id"/>
<field name="qty_done" string="Done"/>
</tree>
</field>
</page>
<page string="Time Tracking" groups="mrp.group_mrp_manager">
<group>

0 comments on commit 24e6b01

Please sign in to comment.
You can’t perform that action at this time.