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 20, 2019
1 parent 3668631 commit 5f5b82193dc0eb2b5a9c3e3b8cef417fcc355d1f
@@ -210,7 +210,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)


@@ -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')
@@ -343,6 +344,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,47 @@ 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
for wo in self:
if wo.product_tracking != 'none':
filigrane = {}
for line in wo.production_id.workorder_ids.mapped('final_lot_line_ids'):
if line.final_lot_id.id not in filigrane:
filigrane[line.final_lot_id.id] = line.qty_done
elif line.qty_done > filigrane[line.final_lot_id.id]:
filigrane[line.final_lot_id.id] = line.qty_done
if sum(filigrane.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(filigrane.keys())
else:
wo.final_lot_domain = self.env['stock.production.lot'].browse(filigrane.keys())

@api.multi
def name_get(self):
@@ -194,9 +221,28 @@ 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.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:
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 {
@@ -230,32 +276,101 @@ 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)
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):
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()
@@ -390,6 +505,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 5f5b821

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