Skip to content

Commit

Permalink
[FIX] mrp: allow skipping lot/sn on workorder
Browse files Browse the repository at this point in the history
Usecase to reproduce:
- Create a MO for drawer with SEC assemble BoM
- Plan the workorders
- In the first workorder record the production without specify a lot or
serial number.
- Second workorder, register a new serial for finished product and
record production.

A UserError that prevent to use a new serial number is raised. It
happens because the system check the remaing quantity on other
workorders + the quantity already produced for the current lot. In this
case the remaining quantity for the first workorder is 0 and the current
lot was not produced. However it should count the lines without lot as
a 'remaining quantity' since they could be used later to create a new
serial or lot number.

This commit register the lines without lot and use them to raise the
UserError and compute the allowed lot domain for finished lots.
  • Loading branch information
amoyaux committed Apr 23, 2019
1 parent 3fc2577 commit a42d2b1
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 10 deletions.
28 changes: 20 additions & 8 deletions addons/mrp/models/mrp_workorder.py
Expand Up @@ -130,8 +130,14 @@ def _onchange_final_lot_id(self):
if line:
self.qty_producing = line.qty_done

@api.depends('production_id.workorder_ids.finished_workorder_line_ids',
'production_id.workorder_ids.finished_workorder_line_ids.qty_done',
'production_id.workorder_ids.finished_workorder_line_ids.lot_id')
def _compute_allowed_lots_domain(self):
""" browse already created lot and respective quantities"""
""" Check if all the finished products has been assigned to a serial
number or a lot in other workorders. If yes, restrict the selectable lot
to the lot/sn used in other workorders.
"""
productions = self.mapped('production_id')
for production in productions:
if production.product_id.tracking == 'none':
Expand All @@ -141,7 +147,10 @@ def _compute_allowed_lots_domain(self):
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)
# It is possible to have finished workorder lines without a lot (eg using the dummy
# test type). Ignore them when computing the allowed lots.
if finished_workorder_line.lot_id:
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']
Expand Down Expand Up @@ -321,8 +330,8 @@ def record_production(self):
# 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:
# Transfer lot (if present) and quantity produced to a finished workorder line
if self.product_tracking != 'none':
self._create_or_update_finished_line()

# Update workorder quantity produced
Expand Down Expand Up @@ -380,10 +389,13 @@ def _create_or_update_finished_line(self):
# 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
line_without_lot = workorder.finished_workorder_line_ids.filtered(lambda line: line.product_id == workorder.product_id and not line.lot_id)
quantity_remaining = workorder.qty_remaining + line_without_lot.qty_done
quantity = line.qty_done + quantity_remaining
if line and float_compare(quantity, final_lot_quantity, precision_rounding=rounding) <= 0:
final_lot_quantity = quantity
elif float_compare(quantity_remaining, final_lot_quantity, precision_rounding=rounding) < 0:
final_lot_quantity = quantity_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)
Expand Down
63 changes: 61 additions & 2 deletions addons/mrp/tests/test_workorder_operation.py
Expand Up @@ -2,9 +2,10 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from datetime import datetime, timedelta
import unittest
from odoo.tests import Form

from odoo.addons.mrp.tests.common import TestMrpCommon
from odoo.exceptions import UserError
from odoo.tests import Form


class TestWorkOrderProcess(TestMrpCommon):
Expand Down Expand Up @@ -720,6 +721,64 @@ def test_03_test_serial_number_defaults(self):
workorder.record_production()
self.assertEqual(workorder.state, 'done')

def test_03b_test_serial_number_defaults(self):
""" Check the constraint on the workorder final_lot. The first workorder
produces 2/2 units without serial number (serial is only required when
you register a component) then the second workorder try to register a
serial number. It should be allowed since the first workorder did not
specify a seiral number.
"""
bom = self.env.ref('mrp.mrp_bom_laptop_cust_rout')
product = bom.product_tmpl_id.product_variant_id
product.tracking = 'serial'

lot_1 = self.env['stock.production.lot'].create({
'product_id': product.id,
'name': 'LOT000001'
})

lot_2 = self.env['stock.production.lot'].create({
'product_id': product.id,
'name': 'LOT000002'
})
self.env['stock.production.lot'].create({
'product_id': product.id,
'name': 'LOT000003'
})

mo_form = Form(self.env['mrp.production'])
mo_form.product_id = product
mo_form.bom_id = bom
mo_form.product_qty = 2.0
mo = mo_form.save()

mo.action_confirm()
mo.button_plan()

workorder_0 = mo.workorder_ids[0]
workorder_0.record_production()
workorder_0.record_production()
with self.assertRaises(UserError):
workorder_0.record_production()

workorder_1 = mo.workorder_ids[1]
with Form(workorder_1) as wo:
wo.final_lot_id = lot_1
workorder_1.record_production()

self.assertTrue(len(workorder_1.allowed_lots_domain) > 1)
with Form(workorder_1) as wo:
wo.final_lot_id = lot_2
workorder_1.record_production()

workorder_2 = mo.workorder_ids[2]
self.assertEqual(workorder_2.allowed_lots_domain, lot_1 | lot_2)

self.assertEqual(workorder_0.finished_workorder_line_ids.qty_done, 2)
self.assertFalse(workorder_0.finished_workorder_line_ids.lot_id)
self.assertEqual(sum(workorder_1.finished_workorder_line_ids.mapped('qty_done')), 2)
self.assertEqual(workorder_1.finished_workorder_line_ids.mapped('lot_id'), lot_1 | lot_2)

def test_04_test_planning_date(self):
""" Test that workorder are planned at the correct time. """
# Remove attendances linked to the calendar, this means that the workcenter is working 24/7
Expand Down

0 comments on commit a42d2b1

Please sign in to comment.