diff --git a/addons/mrp/models/mrp_abstract_workorder.py b/addons/mrp/models/mrp_abstract_workorder.py
index a54691c6436..7e05d187f5a 100644
--- a/addons/mrp/models/mrp_abstract_workorder.py
+++ b/addons/mrp/models/mrp_abstract_workorder.py
@@ -28,6 +28,12 @@ class MrpAbstractWorkorder(models.AbstractModel):
@api.onchange('qty_producing')
def _onchange_qty_producing(self):
+ """ Modify the qty currently producing will modify the existing
+ workorder line in order to match the new quantity to consume for each
+ component and their reserved quantity.
+ """
+ if self.qty_producing <= 0:
+ raise UserError(_('You have to produce at least one %s.') % self.product_uom_id.name)
line_values = self._update_workorder_lines()
for vals in line_values['to_create']:
self.workorder_line_ids |= self.workorder_line_ids.new(vals)
diff --git a/addons/mrp/models/mrp_workorder.py b/addons/mrp/models/mrp_workorder.py
index 136537c13f7..4df8c4611e4 100644
--- a/addons/mrp/models/mrp_workorder.py
+++ b/addons/mrp/models/mrp_workorder.py
@@ -3,6 +3,7 @@
from datetime import datetime
from dateutil.relativedelta import relativedelta
+from collections import defaultdict
from odoo import api, fields, models, _
from odoo.exceptions import UserError
@@ -103,21 +104,60 @@ 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')
- final_lot_domain = fields.One2many('stock.production.lot', compute="_compute_final_lot_domain")
+ workorder_line_ids = fields.One2many(
+ 'mrp.workorder.line',
+ 'workorder_id',
+ string='Workorder lines',
+ domain=[('is_finished', '=', False)]
+ )
+ finished_workorder_line_ids = fields.One2many(
+ 'mrp.workorder.line',
+ 'workorder_id',
+ string='Final Lot lines',
+ domain=[('is_finished', '=', True)]
+ )
+ allowed_lots_domain = fields.One2many(comodel_name='stock.production.lot', compute="_compute_allowed_lots_domain")
- @api.depends('production_id')
- def _compute_final_lot_domain(self):
- for wo in self:
- # check if self is not the first workorder in the list
- if self.env['mrp.workorder'].search([('next_work_order_id', '=', wo.id)]):
- wo.final_lot_domain = self.env['stock.production.lot'].search([
- ('use_next_on_work_order_id', '=', wo.id),
- ]).ids
+ @api.onchange('final_lot_id')
+ def _onchange_final_lot_id(self):
+ """When the user changes the lot being currently produced, suggest
+ a quantity to produce consistent with the previous workorders. """
+ previous_wo = self.env['mrp.workorder'].search([
+ ('next_work_order_id', '=', self.id)
+ ])
+ if previous_wo:
+ line = previous_wo.finished_workorder_line_ids.filtered(lambda line: line.lot_id == self.final_lot_id)
+ if line:
+ self.qty_producing = line.qty_done
+
+ def _compute_allowed_lots_domain(self):
+ """ browse already created lot and respective quantities"""
+ productions = self.mapped('production_id')
+ for production in productions:
+ if production.product_id.tracking == 'none':
+ continue
+
+ rounding = production.product_uom_id.rounding
+ finished_workorder_lines = production.workorder_ids.mapped('finished_workorder_line_ids')
+ qties_done_per_lot = defaultdict(list)
+ for finished_workorder_line in finished_workorder_lines:
+ qties_done_per_lot[finished_workorder_line.lot_id.id].append(finished_workorder_line.qty_done)
+
+ qty_to_produce = production.product_qty
+ allowed_lot_ids = self.env['stock.production.lot']
+ qty_produced = sum([max(qty_dones) for qty_dones in qties_done_per_lot.values()])
+ if float_compare(qty_produced, qty_to_produce, precision_rounding=rounding) < 0:
+ # If we haven't produced enough, all lots are available
+ allowed_lot_ids = self.env['stock.production.lot'].search([('product_id', '=', production.product_id.id)])
else:
- wo.final_lot_domain = self.env['stock.production.lot'].search([
- ('product_id', '=', wo.product_id.id),
- ]).ids
+ # If we produced enough, only the already produced lots are available
+ allowed_lot_ids = self.env['stock.production.lot'].browse(qties_done_per_lot.keys())
+ workorders = production.workorder_ids.filtered(lambda wo: wo.state not in ('done', 'cancel'))
+ for workorder in workorders:
+ if workorder.product_tracking == 'serial':
+ workorder.allowed_lots_domain = allowed_lot_ids - workorder.finished_workorder_line_ids.mapped('lot_id')
+ else:
+ workorder.allowed_lots_domain = allowed_lot_ids
@api.multi
def name_get(self):
@@ -195,9 +235,38 @@ class MrpWorkorder(models.Model):
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 _apply_update_workorder_lines(self):
+ """ update existing line on the workorder. It could be trigger manually
+ after a modification of qty_producing.
+ """
+ self.ensure_one()
+ 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)
+
+ def _defaults_from_finished_workorder_line(self, reference_lot_lines):
+ for r_line in reference_lot_lines:
+ # see which lot we could suggest and its related qty_producing
+ if not r_line.lot_id:
+ continue
+ candidates = self.finished_workorder_line_ids.filtered(lambda line: line.lot_id == r_line.lot_id)
+ rounding = self.product_uom_id.rounding
+ if not candidates:
+ self.write({
+ 'final_lot_id': r_line.lot_id.id,
+ 'qty_producing': r_line.qty_done,
+ })
+ return True
+ elif float_compare(candidates.qty_done, r_line.qty_done, precision_rounding=rounding) < 0:
+ self.write({
+ 'final_lot_id': r_line.lot_id.id,
+ 'qty_producing': r_line.qty_done - candidates.qty_done,
+ })
+ return True
+ return False
def _get_byproduct_move_line(self, by_product_move, quantity):
return {
@@ -216,7 +285,7 @@ class MrpWorkorder(models.Model):
return True
self.ensure_one()
- if self.qty_producing <= 0:
+ if float_compare(self.qty_producing, 0, precision_rounding=self.product_uom_id.rounding) <= 0:
raise UserError(_('Please set the quantity you are currently producing. It should be different from zero.'))
# One a piece is produced, you can launch the next work order
@@ -224,39 +293,93 @@ class MrpWorkorder(models.Model):
self.next_work_order_id.state = 'ready'
# If last work order, then post lots used
- # TODO: should be same as checking if for every workorder something has been done?
if not self.next_work_order_id:
self._update_finished_move()
# Transfer quantities from temporary to final move line or make them final
self._update_raw_moves()
+ # Transfert lot and quantity produced to a finished workorder line
+ if self.product_tracking != 'none' and self.final_lot_id:
+ self._create_or_update_finished_line()
+
# 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
+ # Suggest a finished lot on the next workorder
+ if self.next_work_order_id and self.production_id.product_id.tracking != 'none' and not self.next_work_order_id.final_lot_id:
+ self.next_work_order_id._defaults_from_finished_workorder_line(self.finished_workorder_line_ids)
+ # As we may have changed the quantity to produce on the next workorder,
+ # make sure to update its wokorder lines
+ self.next_work_order_id._apply_update_workorder_lines()
- # Set a qty producing
+ # Test if the production is done
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()
+ if float_compare(self.qty_produced, self.production_id.product_qty, precision_rounding=rounding) < 0:
+ previous_wo = self.env['mrp.workorder']
+ if self.product_tracking != 'none':
+ previous_wo = self.env['mrp.workorder'].search([
+ ('next_work_order_id', '=', self.id)
+ ])
+ candidate_found_in_previous_wo = False
+ if previous_wo:
+ candidate_found_in_previous_wo = self._defaults_from_finished_workorder_line(previous_wo.finished_workorder_line_ids)
+ if not candidate_found_in_previous_wo:
+ # 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
+
+ self._apply_update_workorder_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 float_compare(self.qty_produced, self.production_id.product_qty, precision_rounding=rounding) >= 0:
+ self.qty_producing = 0
self.button_finish()
return True
+ def _create_or_update_finished_line(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
+ """
+ self.ensure_one()
+ final_lot_quantity = self.qty_production
+ rounding = self.product_uom_id.rounding
+ # Get the max quantity possible for current lot in other workorders
+ for workorder in (self.production_id.workorder_ids - self):
+ # We add the remaining quantity to the produced quantity for the
+ # current lot. For 5 finished products: if in the first wo it
+ # creates 4 lot A and 1 lot B and in the second it create 3 lot A
+ # and it remains 2 units to product, it could produce 5 lot A.
+ # In this case we select 4 since it would conflict with the first
+ # workorder otherwise.
+ line = workorder.finished_workorder_line_ids.filtered(lambda line: line.lot_id == self.final_lot_id)
+ if line and float_compare(line.qty_done + workorder.qty_remaining, final_lot_quantity, precision_rounding=rounding) <= 0:
+ final_lot_quantity = line.qty_done + workorder.qty_remaining
+ elif float_compare(workorder.qty_remaining, final_lot_quantity, precision_rounding=rounding) < 0:
+ final_lot_quantity = workorder.qty_remaining
+
+ # final lot line for this lot on this workorder.
+ current_lot_lines = self.finished_workorder_line_ids.filtered(lambda line: line.lot_id == self.final_lot_id)
+
+ # this lot has already been produced
+ if float_compare(final_lot_quantity, current_lot_lines.qty_done + self.qty_producing, precision_rounding=rounding) < 0:
+ raise UserError(_('You have produced %s %s of lot %s in the previous workorder. You are trying to produce %s in this one') %
+ (final_lot_quantity, self.product_id.uom_id.name, self.final_lot_id.name, current_lot_lines.qty_done + self.qty_producing))
+
+ # Update workorder line that regiter final lot created
+ if not current_lot_lines:
+ current_lot_lines = self.env['mrp.workorder.line'].create({
+ 'workorder_id': self.id,
+ 'is_finished': True,
+ 'product_id': self.product_id.id,
+ 'lot_id': self.final_lot_id.id,
+ 'qty_done': self.qty_producing,
+ })
+ else:
+ current_lot_lines.qty_done += self.qty_producing
+
@api.multi
def button_start(self):
self.ensure_one()
@@ -391,6 +514,7 @@ class MrpWorkorderLine(models.Model):
_description = "Workorder move line"
workorder_id = fields.Many2one('mrp.workorder', 'Workorder')
+ is_finished = fields.Boolean('Finished Lot Line', default=False)
def _get_final_lot(self):
return self.workorder_id.final_lot_id
diff --git a/addons/mrp/models/stock_production_lot.py b/addons/mrp/models/stock_production_lot.py
index 7a04e34f272..2df39cd7648 100644
--- a/addons/mrp/models/stock_production_lot.py
+++ b/addons/mrp/models/stock_production_lot.py
@@ -1,17 +1,13 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import fields, models, _
+from odoo import models, _
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_create(self):
active_mo_id = self.env.context.get('active_mo_id')
if active_mo_id:
diff --git a/addons/mrp/tests/test_order.py b/addons/mrp/tests/test_order.py
index 93c0b6f474a..1d71880011b 100644
--- a/addons/mrp/tests/test_order.py
+++ b/addons/mrp/tests/test_order.py
@@ -691,6 +691,7 @@ class TestMrpOrder(TestMrpCommon):
with self.assertRaises(UserError):
# try adding another line for a bom product to increase the quantity
+ produce_form.qty_producing = 1
with produce_form.workorder_line_ids.new() as line:
line.product_id = p1
line.qty_done = 1
@@ -709,6 +710,7 @@ class TestMrpOrder(TestMrpCommon):
'active_id': mo.id,
'active_ids': [mo.id],
}))
+ produce_form.qty_producing = 1
with produce_form.workorder_line_ids.new() as line:
line.product_id = self.product_4
line.qty_done = 1
diff --git a/addons/mrp/views/mrp_workorder_views.xml b/addons/mrp/views/mrp_workorder_views.xml
index f451bfd13bd..a3270e74811 100644
--- a/addons/mrp/views/mrp_workorder_views.xml
+++ b/addons/mrp/views/mrp_workorder_views.xml
@@ -105,7 +105,7 @@
-
+
@@ -149,7 +149,7 @@
-
+
@@ -166,6 +166,13 @@
+
+
+
+
+
+
+