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 Feb 18, 2019
1 parent d0a30e9 commit 73a701d666ee104e1cd305fb04efa12304aff983
@@ -223,6 +223,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')
@@ -844,6 +844,7 @@ def button_mark_done(self):
for wo in self.workorder_ids:
if wo.time_ids.filtered(lambda x: (not x.date_end) and (x.loss_type in ('productive', 'performance'))):
raise UserError(_('Work order %s is still running') % wo.name)
wo.workorder_line_ids.unlink()
self._check_lots()
self.post_inventory()
moves_to_cancel = (self.move_raw_ids | self.move_finished_ids).filtered(lambda x: x.state not in ('done', 'cancel'))
@@ -103,20 +103,48 @@ 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)
])
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
if self.product_tracking != 'none':
previous_wo = self.env['mrp.workorder'].search([
('next_work_order_id', '=', self.id)
])
if not previous_wo:
self.final_lot_domain = self.env['stock.production.lot'].search([
('product_id', '=', self.product_id.id),
]).ids
else:
# If the first wo get all the lot_id, propose only those ones
used_lots = previous_wo.final_lot_line_ids
if not used_lots or sum(used_lots.mapped('qty_done')) < self.qty_production:
self.final_lot_domain = self.env['stock.production.lot'].search([
('product_id', '=', self.product_id.id),
]).ids
else:
self.final_lot_domain = used_lots.mapped('final_lot_id').ids

@api.multi
def name_get(self):
@@ -194,9 +222,23 @@ 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):
for r_line in reference_lot_lines:
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,
})
break
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 {
@@ -234,32 +276,95 @@ def record_production(self):
# Transfer quantities from temporary to final move line or make them final
self._update_raw_moves()

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)

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):
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:
if previous_ll:
if self.qty_producing <= previous_ll.qty_done:
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,
})
elif empty_ll and self.qty_producing <= previous_ll.qty_done + empty_ll.qty_done:
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,
})
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:
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,
})
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()
@@ -394,6 +499,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:
@@ -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 73a701d

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