mirror of
https://github.com/bringout/oca-ocb-mrp.git
synced 2026-04-24 05:12:00 +02:00
19.0 vanilla
This commit is contained in:
parent
accf5918df
commit
6e65e8c877
688 changed files with 225434 additions and 199401 deletions
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import Command
|
||||
from odoo.tests import Form
|
||||
from odoo.addons.mrp.tests.common import TestMrpCommon
|
||||
from odoo.exceptions import UserError
|
||||
|
|
@ -10,9 +11,8 @@ class TestUnbuild(TestMrpCommon):
|
|||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.stock_location = cls.env.ref('stock.stock_location_stock')
|
||||
cls.env.ref('base.group_user').write({
|
||||
'implied_ids': [(4, cls.env.ref('stock.group_production_lot').id)]
|
||||
'implied_ids': [Command.link(cls.env.ref('stock.group_production_lot').id)],
|
||||
})
|
||||
|
||||
def test_unbuild_standart(self):
|
||||
|
|
@ -87,7 +87,6 @@ class TestUnbuild(TestMrpCommon):
|
|||
lot = self.env['stock.lot'].create({
|
||||
'name': 'lot1',
|
||||
'product_id': p_final.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
|
||||
self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 100)
|
||||
|
|
@ -96,7 +95,7 @@ class TestUnbuild(TestMrpCommon):
|
|||
|
||||
mo_form = Form(mo)
|
||||
mo_form.qty_producing = 5.0
|
||||
mo_form.lot_producing_id = lot
|
||||
mo_form.lot_producing_ids.set(lot)
|
||||
mo = mo_form.save()
|
||||
|
||||
mo.button_mark_done()
|
||||
|
|
@ -152,7 +151,7 @@ class TestUnbuild(TestMrpCommon):
|
|||
self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location), 120, 'You should have 80 products in stock')
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location), 10, 'You should have consumed all the 5 product in stock')
|
||||
|
||||
def test_unbuild_with_comnsumed_lot(self):
|
||||
def test_unbuild_with_consumed_lot(self):
|
||||
""" This test creates a MO and then creates 3 unbuild
|
||||
orders for the final product. Only once of the two consumed
|
||||
product is tracked by lot. It checks the stock state after each
|
||||
|
|
@ -164,7 +163,6 @@ class TestUnbuild(TestMrpCommon):
|
|||
lot = self.env['stock.lot'].create({
|
||||
'name': 'lot1',
|
||||
'product_id': p1.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
|
||||
self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 100, lot_id=lot)
|
||||
|
|
@ -181,9 +179,10 @@ class TestUnbuild(TestMrpCommon):
|
|||
details_operation_form = Form(mo.move_raw_ids[1], view=self.env.ref('stock.view_stock_move_operations'))
|
||||
with details_operation_form.move_line_ids.edit(0) as ml:
|
||||
ml.lot_id = lot
|
||||
ml.qty_done = 20
|
||||
ml.quantity = 20
|
||||
details_operation_form.save()
|
||||
|
||||
mo.move_raw_ids.picked = True
|
||||
mo.button_mark_done()
|
||||
self.assertEqual(mo.state, 'done', "Production order should be in done state.")
|
||||
# Check quantity in stock before unbuild.
|
||||
|
|
@ -198,7 +197,6 @@ class TestUnbuild(TestMrpCommon):
|
|||
x = Form(self.env['mrp.unbuild'])
|
||||
x.product_id = p_final
|
||||
x.bom_id = bom
|
||||
x.product_qty = 3
|
||||
unbuild_order = x.save()
|
||||
|
||||
# This should fail since we do not provide the MO that we wanted to unbuild. (without MO we do not know which consumed lot we have to restore)
|
||||
|
|
@ -208,6 +206,7 @@ class TestUnbuild(TestMrpCommon):
|
|||
self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location), 5, 'You should have consumed 3 final product in stock')
|
||||
|
||||
unbuild_order.mo_id = mo.id
|
||||
unbuild_order.product_qty = 3
|
||||
unbuild_order.action_unbuild()
|
||||
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location), 2, 'You should have consumed 3 final product in stock')
|
||||
|
|
@ -248,17 +247,14 @@ class TestUnbuild(TestMrpCommon):
|
|||
lot_final = self.env['stock.lot'].create({
|
||||
'name': 'lot_final',
|
||||
'product_id': p_final.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
lot_1 = self.env['stock.lot'].create({
|
||||
'name': 'lot_consumed_1',
|
||||
'product_id': p1.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
lot_2 = self.env['stock.lot'].create({
|
||||
'name': 'lot_consumed_2',
|
||||
'product_id': p2.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
|
||||
self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 100, lot_id=lot_1)
|
||||
|
|
@ -268,17 +264,18 @@ class TestUnbuild(TestMrpCommon):
|
|||
# FIXME sle: behavior change
|
||||
mo_form = Form(mo)
|
||||
mo_form.qty_producing = 5.0
|
||||
mo_form.lot_producing_id = lot_final
|
||||
mo_form.lot_producing_ids.set(lot_final)
|
||||
mo = mo_form.save()
|
||||
details_operation_form = Form(mo.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations'))
|
||||
with details_operation_form.move_line_ids.edit(0) as ml:
|
||||
ml.qty_done = 5
|
||||
ml.quantity = 5
|
||||
details_operation_form.save()
|
||||
details_operation_form = Form(mo.move_raw_ids[1], view=self.env.ref('stock.view_stock_move_operations'))
|
||||
with details_operation_form.move_line_ids.edit(0) as ml:
|
||||
ml.qty_done = 20
|
||||
ml.quantity = 20
|
||||
details_operation_form.save()
|
||||
|
||||
mo.move_raw_ids.picked = True
|
||||
mo.button_mark_done()
|
||||
self.assertEqual(mo.state, 'done', "Production order should be in done state.")
|
||||
# Check quantity in stock before unbuild.
|
||||
|
|
@ -311,6 +308,7 @@ class TestUnbuild(TestMrpCommon):
|
|||
x.product_id = p_final
|
||||
x.bom_id = bom
|
||||
x.mo_id = mo
|
||||
x.lot_id = lot_final
|
||||
x.product_qty = 3
|
||||
x.save().action_unbuild()
|
||||
|
||||
|
|
@ -322,6 +320,7 @@ class TestUnbuild(TestMrpCommon):
|
|||
x.product_id = p_final
|
||||
x.bom_id = bom
|
||||
x.mo_id = mo
|
||||
x.lot_id = lot_final
|
||||
x.product_qty = 2
|
||||
x.save().action_unbuild()
|
||||
|
||||
|
|
@ -333,6 +332,7 @@ class TestUnbuild(TestMrpCommon):
|
|||
x.product_id = p_final
|
||||
x.bom_id = bom
|
||||
x.mo_id = mo
|
||||
x.lot_id = lot_final
|
||||
x.product_qty = 5
|
||||
x.save().action_unbuild()
|
||||
|
||||
|
|
@ -350,17 +350,14 @@ class TestUnbuild(TestMrpCommon):
|
|||
lot_1 = self.env['stock.lot'].create({
|
||||
'name': 'lot_1',
|
||||
'product_id': p2.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
lot_2 = self.env['stock.lot'].create({
|
||||
'name': 'lot_2',
|
||||
'product_id': p2.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
lot_3 = self.env['stock.lot'].create({
|
||||
'name': 'lot_3',
|
||||
'product_id': p2.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 100)
|
||||
self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 1, lot_id=lot_1)
|
||||
|
|
@ -371,15 +368,8 @@ class TestUnbuild(TestMrpCommon):
|
|||
mo_form = Form(mo)
|
||||
mo_form.qty_producing = 5.0
|
||||
mo = mo_form.save()
|
||||
details_operation_form = Form(mo.move_raw_ids.filtered(lambda ml: ml.product_id == p2), view=self.env.ref('stock.view_stock_move_operations'))
|
||||
with details_operation_form.move_line_ids.edit(0) as ml:
|
||||
ml.qty_done = ml.reserved_uom_qty
|
||||
with details_operation_form.move_line_ids.edit(1) as ml:
|
||||
ml.qty_done = ml.reserved_uom_qty
|
||||
with details_operation_form.move_line_ids.edit(2) as ml:
|
||||
ml.qty_done = ml.reserved_uom_qty
|
||||
details_operation_form.save()
|
||||
|
||||
mo.move_raw_ids.picked = True
|
||||
mo.button_mark_done()
|
||||
self.assertEqual(mo.state, 'done', "Production order should be in done state.")
|
||||
# Check quantity in stock before unbuild.
|
||||
|
|
@ -412,64 +402,100 @@ class TestUnbuild(TestMrpCommon):
|
|||
lot_1 = self.env['stock.lot'].create({
|
||||
'name': 'lot_1',
|
||||
'product_id': p2.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
|
||||
self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 3, lot_id=lot_1)
|
||||
lot_finished_1 = self.env['stock.lot'].create({
|
||||
'name': 'lot_finished_1',
|
||||
'product_id': p_final.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
|
||||
self.assertEqual(mo.product_qty, 5)
|
||||
mo_form = Form(mo)
|
||||
mo_form.qty_producing = 3.0
|
||||
mo_form.lot_producing_id = lot_finished_1
|
||||
mo_form.lot_producing_ids.set(lot_finished_1)
|
||||
mo = mo_form.save()
|
||||
self.assertEqual(mo.move_raw_ids[1].quantity_done, 12)
|
||||
self.assertEqual(mo.move_raw_ids[1].quantity, 12)
|
||||
details_operation_form = Form(mo.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations'))
|
||||
with details_operation_form.move_line_ids.new() as ml:
|
||||
ml.qty_done = 3
|
||||
with details_operation_form.move_line_ids.edit(0) as ml:
|
||||
ml.quantity = 3
|
||||
ml.lot_id = lot_1
|
||||
details_operation_form.save()
|
||||
action = mo.button_mark_done()
|
||||
backorder = Form(self.env[action['res_model']].with_context(**action['context']))
|
||||
backorder.save().action_backorder()
|
||||
mo.move_raw_ids.picked = True
|
||||
Form.from_action(self.env, mo.button_mark_done()).save().action_backorder()
|
||||
|
||||
lot_2 = self.env['stock.lot'].create({
|
||||
'name': 'lot_2',
|
||||
'product_id': p2.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
|
||||
self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 4, lot_id=lot_2)
|
||||
lot_finished_2 = self.env['stock.lot'].create({
|
||||
'name': 'lot_finished_2',
|
||||
'product_id': p_final.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
|
||||
mo = mo.procurement_group_id.mrp_production_ids[1]
|
||||
mo = mo.production_group_id.production_ids[1]
|
||||
# FIXME sle: issue in backorder?
|
||||
mo.move_raw_ids.move_line_ids.unlink()
|
||||
self.assertEqual(mo.product_qty, 2)
|
||||
mo_form = Form(mo)
|
||||
mo_form.qty_producing = 2
|
||||
mo_form.lot_producing_id = lot_finished_2
|
||||
mo_form.lot_producing_ids.set(lot_finished_2)
|
||||
mo = mo_form.save()
|
||||
details_operation_form = Form(mo.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations'))
|
||||
with details_operation_form.move_line_ids.new() as ml:
|
||||
ml.qty_done = 2
|
||||
with details_operation_form.move_line_ids.edit(0) as ml:
|
||||
ml.quantity = 2
|
||||
ml.lot_id = lot_2
|
||||
details_operation_form.save()
|
||||
action = mo.button_mark_done()
|
||||
|
||||
mo1 = mo.procurement_group_id.mrp_production_ids[0]
|
||||
mo1 = mo.production_group_id.production_ids[0]
|
||||
ml = mo1.finished_move_line_ids[0].consume_line_ids.filtered(lambda m: m.product_id == p1 and lot_finished_1 in m.produce_line_ids.lot_id)
|
||||
self.assertEqual(sum(ml.mapped('qty_done')), 12.0, 'Should have consumed 12 for the first lot')
|
||||
self.assertEqual(sum(ml.mapped('quantity')), 12.0, 'Should have consumed 12 for the first lot')
|
||||
ml = mo.finished_move_line_ids[0].consume_line_ids.filtered(lambda m: m.product_id == p1 and lot_finished_2 in m.produce_line_ids.lot_id)
|
||||
self.assertEqual(sum(ml.mapped('qty_done')), 8.0, 'Should have consumed 8 for the second lot')
|
||||
self.assertEqual(sum(ml.mapped('quantity')), 8.0, 'Should have consumed 8 for the second lot')
|
||||
|
||||
def test_unbuild_without_lot_after_tracking_change(self):
|
||||
""" This test creates a MO without lots, and later one of the consumed products starts being tracking by lot.
|
||||
And then creates 1 unbuild order for the final product.
|
||||
It checks the stock state after each order and ensure it is correct.
|
||||
"""
|
||||
mo, bom, p_final, p1, p2 = self.generate_mo()
|
||||
self.assertEqual(len(mo), 1, 'MO should have been created')
|
||||
|
||||
self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 100)
|
||||
self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5)
|
||||
mo.action_assign()
|
||||
|
||||
mo_form = Form(mo)
|
||||
mo_form.qty_producing = 5.0
|
||||
mo = mo_form.save()
|
||||
mo.button_mark_done()
|
||||
self.assertEqual(mo.state, 'done', "Production order should be in done state.")
|
||||
|
||||
# Check quantity in stock before unbuild.
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location), 5, 'You should have the 5 final product in stock')
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location), 80, 'You should have 80 products in stock')
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location), 0, 'You should have consumed all the 5 product in stock')
|
||||
|
||||
p1.tracking = 'lot'
|
||||
|
||||
# ---------------------------------------------------
|
||||
# unbuild
|
||||
# ---------------------------------------------------
|
||||
|
||||
x = Form(self.env['mrp.unbuild'])
|
||||
x.product_id = p_final
|
||||
x.mo_id = mo
|
||||
x.product_qty = 3
|
||||
unbuild_order = x.save()
|
||||
self.assertEqual(unbuild_order.bom_id, bom, 'Should have filled bom field automatically')
|
||||
unbuild_order.action_unbuild()
|
||||
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location), 2, 'You should have consumed 3 final product in stock')
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location, strict=True), 92, 'You should have 92 products in stock without lot')
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location), 3, 'You should have consumed all the 5 product in stock')
|
||||
|
||||
def test_unbuild_with_routes(self):
|
||||
""" This test creates a MO of a stockable product (Table). A new route for rule QC/Unbuild -> Stock
|
||||
|
|
@ -480,22 +506,21 @@ class TestUnbuild(TestMrpCommon):
|
|||
StockQuant = self.env['stock.quant']
|
||||
ProductObj = self.env['product.product']
|
||||
# Create new QC/Unbuild location
|
||||
warehouse = self.env.ref('stock.warehouse0')
|
||||
unbuild_location = self.env['stock.location'].create({
|
||||
'name': 'QC/Unbuild',
|
||||
'usage': 'internal',
|
||||
'location_id': warehouse.view_location_id.id
|
||||
'location_id': self.warehouse_1.view_location_id.id
|
||||
})
|
||||
|
||||
# Create a product route containing a stock rule that will move product from QC/Unbuild location to stock
|
||||
self.env['stock.route'].create({
|
||||
'name': 'QC/Unbuild -> Stock',
|
||||
'warehouse_selectable': True,
|
||||
'warehouse_ids': [(4, warehouse.id)],
|
||||
'rule_ids': [(0, 0, {
|
||||
'warehouse_ids': [Command.link(self.warehouse_1.id)],
|
||||
'rule_ids': [Command.create({
|
||||
'name': 'Send Matrial QC/Unbuild -> Stock',
|
||||
'action': 'push',
|
||||
'picking_type_id': self.ref('stock.picking_type_internal'),
|
||||
'picking_type_id': self.picking_type_int.id,
|
||||
'location_src_id': unbuild_location.id,
|
||||
'location_dest_id': self.stock_location.id,
|
||||
})],
|
||||
|
|
@ -504,15 +529,15 @@ class TestUnbuild(TestMrpCommon):
|
|||
# Create a stockable product and its components
|
||||
finshed_product = ProductObj.create({
|
||||
'name': 'Table',
|
||||
'type': 'product',
|
||||
'is_storable': True,
|
||||
})
|
||||
component1 = ProductObj.create({
|
||||
'name': 'Table head',
|
||||
'type': 'product',
|
||||
'is_storable': True,
|
||||
})
|
||||
component2 = ProductObj.create({
|
||||
'name': 'Table stand',
|
||||
'type': 'product',
|
||||
'is_storable': True,
|
||||
})
|
||||
|
||||
# Create bom and add components
|
||||
|
|
@ -523,8 +548,8 @@ class TestUnbuild(TestMrpCommon):
|
|||
'product_qty': 1.0,
|
||||
'type': 'normal',
|
||||
'bom_line_ids': [
|
||||
(0, 0, {'product_id': component1.id, 'product_qty': 1}),
|
||||
(0, 0, {'product_id': component2.id, 'product_qty': 1})
|
||||
Command.create({'product_id': component1.id, 'product_qty': 1}),
|
||||
Command.create({'product_id': component2.id, 'product_qty': 1}),
|
||||
]})
|
||||
|
||||
# Set on hand quantity
|
||||
|
|
@ -580,8 +605,8 @@ class TestUnbuild(TestMrpCommon):
|
|||
self.assertEqual(picking.location_dest_id.id, self.stock_location.id, 'Wrong destination location in picking')
|
||||
|
||||
# Transfer it
|
||||
for ml in picking.move_ids_without_package:
|
||||
ml.quantity_done = 1
|
||||
for ml in picking.move_ids:
|
||||
ml.write({'quantity': 1, 'picked': True})
|
||||
picking._action_done()
|
||||
|
||||
# Check the available quantity of components and final product in stock
|
||||
|
|
@ -595,8 +620,7 @@ class TestUnbuild(TestMrpCommon):
|
|||
- decimal accuracy of Product UoM > decimal accuracy of Units
|
||||
- unbuild a product with a decimal quantity of component
|
||||
"""
|
||||
self.env['decimal.precision'].search([('name', '=', 'Product Unit of Measure')]).digits = 4
|
||||
self.uom_unit.rounding = 0.001
|
||||
self.env['decimal.precision'].search([('name', '=', 'Product Unit')]).digits = 4
|
||||
|
||||
self.bom_1.product_qty = 3
|
||||
self.bom_1.bom_line_ids.product_qty = 5
|
||||
|
|
@ -629,17 +653,16 @@ class TestUnbuild(TestMrpCommon):
|
|||
"""
|
||||
compo, finished = self.env['product.product'].create([{
|
||||
'name': 'compo',
|
||||
'type': 'product',
|
||||
'is_storable': True,
|
||||
'tracking': 'serial',
|
||||
}, {
|
||||
'name': 'finished',
|
||||
'type': 'product',
|
||||
'is_storable': True,
|
||||
}])
|
||||
|
||||
lot01, lot02 = self.env['stock.lot'].create([{
|
||||
'name': n,
|
||||
'product_id': compo.id,
|
||||
'company_id': self.env.company.id,
|
||||
} for n in ['lot01', 'lot02']])
|
||||
self.env['stock.quant']._update_available_quantity(compo, self.stock_location, 1, lot_id=lot01)
|
||||
self.env['stock.quant']._update_available_quantity(compo, self.stock_location, 1, lot_id=lot02)
|
||||
|
|
@ -662,12 +685,13 @@ class TestUnbuild(TestMrpCommon):
|
|||
|
||||
details_operation_form = Form(mo.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations'))
|
||||
with details_operation_form.move_line_ids.edit(0) as ml:
|
||||
ml.qty_done = 1
|
||||
ml.quantity = 1
|
||||
details_operation_form.save()
|
||||
details_operation_form = Form(mo.move_raw_ids[1], view=self.env.ref('stock.view_stock_move_operations'))
|
||||
with details_operation_form.move_line_ids.edit(0) as ml:
|
||||
ml.qty_done = 1
|
||||
ml.quantity = 1
|
||||
details_operation_form.save()
|
||||
mo.move_raw_ids.picked = True
|
||||
mo.button_mark_done()
|
||||
|
||||
uo_form = Form(self.env['mrp.unbuild'])
|
||||
|
|
@ -685,7 +709,7 @@ class TestUnbuild(TestMrpCommon):
|
|||
unbuild order are applied on the stock moves
|
||||
"""
|
||||
grp_multi_loc = self.env.ref('stock.group_stock_multi_locations')
|
||||
self.env.user.write({'groups_id': [(4, grp_multi_loc.id, 0)]})
|
||||
self.env.user.write({'group_ids': [Command.link(grp_multi_loc.id)]})
|
||||
warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1)
|
||||
prod_location = self.env['stock.location'].search([('usage', '=', 'production'), ('company_id', '=', self.env.user.id)])
|
||||
subloc01, subloc02, = self.stock_location.child_ids[:2]
|
||||
|
|
@ -706,13 +730,11 @@ class TestUnbuild(TestMrpCommon):
|
|||
internal_form.picking_type_id = warehouse.int_type_id
|
||||
internal_form.location_id = self.stock_location
|
||||
internal_form.location_dest_id = subloc01
|
||||
with internal_form.move_ids_without_package.new() as move:
|
||||
with internal_form.move_ids.new() as move:
|
||||
move.product_id = p_final
|
||||
move.product_uom_qty = 1.0
|
||||
move.quantity = 1.0
|
||||
move.picked = True
|
||||
internal_transfer = internal_form.save()
|
||||
internal_transfer.action_confirm()
|
||||
internal_transfer.action_assign()
|
||||
internal_transfer.move_line_ids.qty_done = 1.0
|
||||
internal_transfer.button_validate()
|
||||
|
||||
unbuild_order_form = Form(self.env['mrp.unbuild'])
|
||||
|
|
@ -739,9 +761,8 @@ class TestUnbuild(TestMrpCommon):
|
|||
order = self.env['mrp.unbuild'].create({
|
||||
'product_id': self.product_4.id,
|
||||
})
|
||||
warehouse = self.env.ref('stock.warehouse0')
|
||||
self.assertEqual(order.location_id, warehouse.lot_stock_id)
|
||||
self.assertEqual(order.location_dest_id, warehouse.lot_stock_id)
|
||||
self.assertEqual(order.location_id, self.stock_location)
|
||||
self.assertEqual(order.location_dest_id, self.stock_location)
|
||||
|
||||
def test_use_unbuilt_sn_in_mo(self):
|
||||
"""
|
||||
|
|
@ -750,17 +771,16 @@ class TestUnbuild(TestMrpCommon):
|
|||
"""
|
||||
product_1 = self.env['product.product'].create({
|
||||
'name': 'Product tracked by sn',
|
||||
'type': 'product',
|
||||
'is_storable': True,
|
||||
'tracking': 'serial',
|
||||
})
|
||||
product_1_sn = self.env['stock.lot'].create({
|
||||
'name': 'sn1',
|
||||
'product_id': product_1.id,
|
||||
'company_id': self.env.company.id
|
||||
})
|
||||
component = self.env['product.product'].create({
|
||||
'name': 'Product component',
|
||||
'type': 'product',
|
||||
'is_storable': True,
|
||||
})
|
||||
bom_1 = self.env['mrp.bom'].create({
|
||||
'product_id': product_1.id,
|
||||
|
|
@ -769,12 +789,12 @@ class TestUnbuild(TestMrpCommon):
|
|||
'product_qty': 1.0,
|
||||
'type': 'normal',
|
||||
'bom_line_ids': [
|
||||
(0, 0, {'product_id': component.id, 'product_qty': 1}),
|
||||
Command.create({'product_id': component.id, 'product_qty': 1}),
|
||||
],
|
||||
})
|
||||
product_2 = self.env['product.product'].create({
|
||||
'name': 'finished Product',
|
||||
'type': 'product',
|
||||
'is_storable': True,
|
||||
})
|
||||
self.env['mrp.bom'].create({
|
||||
'product_id': product_2.id,
|
||||
|
|
@ -783,7 +803,7 @@ class TestUnbuild(TestMrpCommon):
|
|||
'product_qty': 1.0,
|
||||
'type': 'normal',
|
||||
'bom_line_ids': [
|
||||
(0, 0, {'product_id': product_1.id, 'product_qty': 1}),
|
||||
Command.create({'product_id': product_1.id, 'product_qty': 1}),
|
||||
],
|
||||
})
|
||||
# mo1
|
||||
|
|
@ -795,8 +815,7 @@ class TestUnbuild(TestMrpCommon):
|
|||
mo.action_confirm()
|
||||
|
||||
mo_form = Form(mo)
|
||||
mo_form.qty_producing = 1.0
|
||||
mo_form.lot_producing_id = product_1_sn
|
||||
mo_form.lot_producing_ids.set(product_1_sn)
|
||||
mo = mo_form.save()
|
||||
mo.button_mark_done()
|
||||
self.assertEqual(mo.state, 'done', "Production order should be in done state.")
|
||||
|
|
@ -804,6 +823,7 @@ class TestUnbuild(TestMrpCommon):
|
|||
#unbuild order
|
||||
unbuild_form = Form(self.env['mrp.unbuild'])
|
||||
unbuild_form.mo_id = mo
|
||||
unbuild_form.lot_id = product_1_sn
|
||||
unbuild_form.save().action_unbuild()
|
||||
|
||||
#mo2
|
||||
|
|
@ -814,11 +834,12 @@ class TestUnbuild(TestMrpCommon):
|
|||
details_operation_form = Form(mo2.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations'))
|
||||
with details_operation_form.move_line_ids.new() as ml:
|
||||
ml.lot_id = product_1_sn
|
||||
ml.qty_done = 1
|
||||
ml.quantity = 1
|
||||
details_operation_form.save()
|
||||
mo_form = Form(mo2)
|
||||
mo_form.qty_producing = 1
|
||||
mo2 = mo_form.save()
|
||||
mo2.move_raw_ids.picked = True
|
||||
mo2.button_mark_done()
|
||||
self.assertEqual(mo2.state, 'done', "Production order should be in done state.")
|
||||
|
||||
|
|
@ -829,26 +850,25 @@ class TestUnbuild(TestMrpCommon):
|
|||
"""
|
||||
finished_product = self.env['product.product'].create({
|
||||
'name': 'Product tracked by sn',
|
||||
'type': 'product',
|
||||
'is_storable': True,
|
||||
'tracking': 'serial',
|
||||
})
|
||||
finished_product_sn = self.env['stock.lot'].create({
|
||||
'name': 'sn1',
|
||||
'product_id': finished_product.id,
|
||||
'company_id': self.env.company.id
|
||||
})
|
||||
component = self.env['product.product'].create({
|
||||
'name': 'Product component',
|
||||
'type': 'product',
|
||||
'is_storable': True,
|
||||
})
|
||||
bom_1 = self.env['mrp.bom'].create({
|
||||
'product_id': finished_product.id,
|
||||
'product_tmpl_id': finished_product.product_tmpl_id.id,
|
||||
'product_uom_id': self.env.ref('uom.product_uom_unit').id,
|
||||
'product_uom_id': self.uom_unit.id,
|
||||
'product_qty': 1.0,
|
||||
'type': 'normal',
|
||||
'bom_line_ids': [
|
||||
(0, 0, {'product_id': component.id, 'product_qty': 1}),
|
||||
Command.create({'product_id': component.id, 'product_qty': 1}),
|
||||
],
|
||||
})
|
||||
# mo_1
|
||||
|
|
@ -858,15 +878,12 @@ class TestUnbuild(TestMrpCommon):
|
|||
mo_form.product_qty = 1.0
|
||||
mo = mo_form.save()
|
||||
mo.action_confirm()
|
||||
mo.qty_producing = 1.0
|
||||
mo.lot_producing_id = finished_product_sn
|
||||
mo.move_raw_ids.quantity_done = 1
|
||||
mo.lot_producing_ids = finished_product_sn
|
||||
mo.move_raw_ids.write({'quantity': 1, 'picked': True})
|
||||
mo.button_mark_done()
|
||||
self.assertEqual(mo.state, 'done', "Production order should be in done state.")
|
||||
# unbuild order mo_1
|
||||
action = mo.button_unbuild()
|
||||
wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
|
||||
wizard.action_validate()
|
||||
Form.from_action(self.env, mo.button_unbuild()).save().action_validate()
|
||||
self.assertEqual(mo.unbuild_ids.produce_line_ids[0].product_id, finished_product)
|
||||
self.assertEqual(mo.unbuild_ids.produce_line_ids[0].lot_ids, finished_product_sn)
|
||||
self.assertEqual(mo.unbuild_ids.produce_line_ids[1].product_id, component)
|
||||
|
|
@ -877,7 +894,6 @@ class TestUnbuild(TestMrpCommon):
|
|||
component_sn = self.env['stock.lot'].create({
|
||||
'name': 'component-sn1',
|
||||
'product_id': component.id,
|
||||
'company_id': self.env.company.id
|
||||
})
|
||||
self.env['stock.quant']._update_available_quantity(component, self.stock_location, 1, lot_id=component_sn)
|
||||
#mo2 with tracked component
|
||||
|
|
@ -887,15 +903,13 @@ class TestUnbuild(TestMrpCommon):
|
|||
mo_form.product_qty = 1.0
|
||||
mo_2 = mo_form.save()
|
||||
mo_2.action_confirm()
|
||||
mo_2.qty_producing = 1.0
|
||||
mo_2.lot_producing_id = finished_product_sn
|
||||
mo_2.move_raw_ids.quantity_done = 1
|
||||
mo_2.lot_producing_ids = finished_product_sn
|
||||
mo_2.move_raw_ids.write({'quantity': 1, 'picked': True})
|
||||
mo_2.button_mark_done()
|
||||
self.assertEqual(mo_2.state, 'done', "Production order should be in done state.")
|
||||
# unbuild mo_2
|
||||
action = mo_2.button_unbuild()
|
||||
wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
|
||||
wizard.action_validate()
|
||||
Form.from_action(self.env, action).save().action_validate()
|
||||
self.assertEqual(mo_2.unbuild_ids.produce_line_ids[0].product_id, finished_product)
|
||||
self.assertEqual(mo_2.unbuild_ids.produce_line_ids[0].lot_ids, finished_product_sn)
|
||||
self.assertEqual(mo_2.unbuild_ids.produce_line_ids[1].product_id, component)
|
||||
|
|
@ -921,6 +935,7 @@ class TestUnbuild(TestMrpCommon):
|
|||
mo = mo_form.save()
|
||||
|
||||
mo.action_confirm()
|
||||
mo.move_finished_ids._do_unreserve()
|
||||
mo_form = Form(mo)
|
||||
mo_form.qty_producing = 4
|
||||
mo = mo_form.save()
|
||||
|
|
@ -941,42 +956,233 @@ class TestUnbuild(TestMrpCommon):
|
|||
unbuild_order.action_unbuild()
|
||||
self.assertRecordValues(unbuild_order.produce_line_ids.move_line_ids, [
|
||||
# pylint: disable=bad-whitespace
|
||||
{'product_id': self.bom_1.product_id.id, 'qty_done': 3},
|
||||
{'product_id': self.bom_1.bom_line_ids[0].product_id.id, 'qty_done': 0.6},
|
||||
{'product_id': self.bom_1.bom_line_ids[1].product_id.id, 'qty_done': 1.2},
|
||||
{'product_id': self.bom_1.product_id.id, 'quantity': 3},
|
||||
{'product_id': self.bom_1.bom_line_ids[0].product_id.id, 'quantity': 0.6},
|
||||
{'product_id': self.bom_1.bom_line_ids[1].product_id.id, 'quantity': 1.2},
|
||||
])
|
||||
|
||||
def test_unbuild_update_forecasted_qty(self):
|
||||
def test_unbuild_less_quantity_consumed(self):
|
||||
"""
|
||||
Test that the unbuild correctly updates the forecasted quantity of a product.
|
||||
Tests that you don't unbuild more than you consumed during production.
|
||||
BoM uses component x20, but only 15 are consumed during the production order.
|
||||
Unbuilding the MO should only put 15 components back in stock.
|
||||
"""
|
||||
bom = self.bom_4
|
||||
product = bom.product_id
|
||||
# qty_available + incoming_qty - outgoing_qty = virtual_available
|
||||
# Currently: 0.0 + 0.0 - 0.0 = 0.0
|
||||
self.assertRecordValues(product, [{"qty_available": 0.0, "incoming_qty": 0.0, "outgoing_qty": 0.0, "virtual_available": 0.0}])
|
||||
# Manufacture 20 unit
|
||||
bom = self.env['mrp.bom'].create({
|
||||
'product_id': self.product_2.id,
|
||||
'product_tmpl_id': self.product_2.product_tmpl_id.id,
|
||||
'consumption': 'flexible',
|
||||
'product_qty': 1.0,
|
||||
'type': 'normal',
|
||||
'bom_line_ids': [
|
||||
Command.create({'product_id': self.product_3.id, 'product_qty': 20}),
|
||||
]
|
||||
})
|
||||
|
||||
with Form(self.env['mrp.production']) as mo_form:
|
||||
mo_form.product_id = self.product_2
|
||||
mo_form.bom_id = bom
|
||||
mo_form.product_qty = 1
|
||||
mo = mo_form.save()
|
||||
mo.action_confirm()
|
||||
|
||||
mo.qty_producing = 1.0
|
||||
mo.move_raw_ids.write({'quantity': 15, 'picked': True})
|
||||
mo.button_mark_done()
|
||||
|
||||
Form.from_action(self.env, mo.button_unbuild()).save().action_validate()
|
||||
self.assertEqual(mo.unbuild_ids.produce_line_ids.filtered(lambda m: m.product_id == self.product_3).product_uom_qty, 15)
|
||||
|
||||
def test_unbuild_mo_different_qty(self):
|
||||
# Test the unbuild of a MO with qty_produced > product_qty
|
||||
|
||||
bom = self.env['mrp.bom'].create({
|
||||
'product_id': self.product_2.id,
|
||||
'product_tmpl_id': self.product_2.product_tmpl_id.id,
|
||||
'consumption': 'flexible',
|
||||
'product_qty': 1.0,
|
||||
'type': 'normal',
|
||||
'bom_line_ids': [Command.create({'product_id': self.product_3.id, 'product_qty': 1})]
|
||||
})
|
||||
|
||||
with Form(self.env['mrp.production']) as mo_form:
|
||||
mo_form.product_id = self.product_2
|
||||
mo_form.bom_id = bom
|
||||
mo_form.product_qty = 10
|
||||
mo = mo_form.save()
|
||||
mo.action_confirm()
|
||||
|
||||
mo.qty_producing = 12
|
||||
mo.move_raw_ids.write({'quantity': 12, 'picked': True})
|
||||
mo.button_mark_done()
|
||||
|
||||
unbuild_action = mo.button_unbuild()
|
||||
unbuild_action['context']['default_product_qty'] = 12
|
||||
Form.from_action(self.env, unbuild_action).save().action_validate()
|
||||
|
||||
unbuild_fns_move = mo.unbuild_ids.produce_line_ids.filtered(lambda m: m.product_id == self.product_2)
|
||||
self.assertEqual(len(unbuild_fns_move), 1)
|
||||
self.assertEqual(unbuild_fns_move.state, "done")
|
||||
self.assertEqual(unbuild_fns_move.quantity, 12)
|
||||
|
||||
def test_putaway_strategy_with_unbuild(self):
|
||||
"""
|
||||
Test that the putaway strategy is correctly applied when unbuilding a product
|
||||
"""
|
||||
# Create a putaway strategy for the component product
|
||||
putaway_strategy = self.env["stock.putaway.rule"].create({
|
||||
"location_in_id": self.stock_location.id,
|
||||
"location_out_id": self.shelf_1.id,
|
||||
'product_id': self.bom_4.bom_line_ids.product_id.id,
|
||||
})
|
||||
# Create a MO for the finished product
|
||||
mo = self.env['mrp.production'].create({
|
||||
'product_id': self.bom_4.product_id.id,
|
||||
'product_qty': 1.0,
|
||||
'bom_id': self.bom_4.id,
|
||||
'product_uom_id': self.bom_4.product_uom_id.id,
|
||||
})
|
||||
mo.action_confirm()
|
||||
mo.qty_producing = 1.0
|
||||
mo.move_raw_ids.write({'quantity': 1, 'picked': True})
|
||||
mo.button_mark_done()
|
||||
|
||||
# Unbuild the MO and check that the putaway strategy is applied for the component product
|
||||
unbuild_action = mo.button_unbuild()
|
||||
unbuild_action['context']['default_product_qty'] = 1
|
||||
unbuild_order = Form.from_action(self.env, unbuild_action).save()
|
||||
unbuild_order.action_unbuild()
|
||||
|
||||
component_move_unbuild = unbuild_order.produce_line_ids.filtered(lambda m: m.product_id == self.bom_4.bom_line_ids.product_id)
|
||||
self.assertEqual(component_move_unbuild.move_line_ids.location_dest_id, putaway_strategy.location_out_id)
|
||||
|
||||
def test_unbuild_consigned_comp(self):
|
||||
""" Test that after unbuild, consigned quant still have the same owner as before the MO."""
|
||||
consigned_partner = self.env['res.partner'].create({'name': 'consigned partner'})
|
||||
mo, _, _, p1, _ = self.generate_mo(qty_final=1, qty_base_1=7)
|
||||
self.assertEqual(len(mo), 1, 'MO should have been created')
|
||||
self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 3)
|
||||
self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 4, owner_id=consigned_partner)
|
||||
|
||||
mo.action_assign()
|
||||
mo_form = Form(mo)
|
||||
mo_form.qty_producing = 1
|
||||
mo = mo_form.save()
|
||||
mo.button_mark_done()
|
||||
self.assertEqual(mo.state, 'done', "Production order should be in done state.")
|
||||
|
||||
unbuild_form = Form(self.env['mrp.unbuild'])
|
||||
unbuild_form.mo_id = mo
|
||||
unbuild_form.save().action_unbuild()
|
||||
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location), 7)
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location, owner_id=consigned_partner), 4)
|
||||
|
||||
def test_unbuild_non_storable_product(self):
|
||||
"""Check that the move values of an unbuild of a non-storable product are correct.
|
||||
"""
|
||||
self.product_4.is_storable = False
|
||||
self.product_3.is_storable = False
|
||||
|
||||
self.env['mrp.bom.byproduct'].create({
|
||||
'bom_id': self.bom_1.id,
|
||||
'product_id': self.product_3.id,
|
||||
'product_qty': 1,
|
||||
'product_uom_id': self.product_3.uom_id.id
|
||||
})
|
||||
|
||||
# Create mo
|
||||
mo_form = Form(self.env['mrp.production'])
|
||||
mo_form.product_id = product
|
||||
mo_form.bom_id = bom
|
||||
mo_form.product_qty = 20.0
|
||||
mo_form.product_id = self.product_4
|
||||
mo_form.bom_id = self.bom_1
|
||||
mo_form.product_uom_id = self.product_4.uom_id
|
||||
mo_form.product_qty = 4.0
|
||||
mo = mo_form.save()
|
||||
mo.action_confirm()
|
||||
self.assertRecordValues(product, [{"qty_available": 0.0, "incoming_qty": 20.0, "outgoing_qty": 0.0, "virtual_available": 20.0}])
|
||||
mo.qty_producing = 20.0
|
||||
mo.move_raw_ids.quantity_done = 20.0
|
||||
|
||||
# Produce the final product
|
||||
mo_form = Form(mo)
|
||||
mo_form.qty_producing = 4.0
|
||||
mo_form.save()
|
||||
|
||||
mo.button_mark_done()
|
||||
self.assertRecordValues(product, [{"qty_available": 20.0, "incoming_qty": 0.0, "outgoing_qty": 0.0, "virtual_available": 20.0}])
|
||||
# Unlock the MO and add 10 additional produced quantity
|
||||
mo.action_toggle_is_locked()
|
||||
with Form(mo) as mo_form:
|
||||
mo_form.qty_producing = 30.0
|
||||
mo = mo_form.save()
|
||||
self.assertRecordValues(product, [{"qty_available": 30.0, "incoming_qty": 0.0, "outgoing_qty": 0.0, "virtual_available": 30.0}])
|
||||
# Unbuild the 15 units
|
||||
action = mo.button_unbuild()
|
||||
unbuild_form = Form(self.env[action['res_model']].with_context(action['context']))
|
||||
unbuild_form.product_qty = 15.0
|
||||
wizard = unbuild_form.save()
|
||||
wizard.action_validate()
|
||||
self.assertRecordValues(product, [{"qty_available": 15.0, "incoming_qty": 0.0, "outgoing_qty": 0.0, "virtual_available": 15.0}])
|
||||
|
||||
unbuild_wizard = Form(self.env['mrp.unbuild'])
|
||||
unbuild_wizard.mo_id = mo
|
||||
unbuild = unbuild_wizard.save()
|
||||
unbuild.action_unbuild()
|
||||
|
||||
self.assertRecordValues(unbuild.produce_line_ids, [
|
||||
{'product_id': self.product_4.id, 'quantity': 4, 'state': 'done'}, # Stick
|
||||
{'product_id': self.product_3.id, 'quantity': 12, 'state': 'done'}, # Stone
|
||||
{'product_id': self.product_2.id, 'quantity': 24, 'state': 'done'}, # Wood
|
||||
{'product_id': self.product_1.id, 'quantity': 48, 'state': 'done'}, # Courage
|
||||
])
|
||||
|
||||
def test_unbuild_tracked_component_multiple_unbuilds_same_mo(self):
|
||||
"""
|
||||
Create a Manufacturing Order producing 2 units of a serial-tracked finished product
|
||||
from a serial-tracked component, and verify that during two successive unbuilds,
|
||||
the correct component serial numbers are restored.
|
||||
|
||||
- The MO produces 2 units of P1, consuming serials SN1 and SN2 of C1
|
||||
- Unbuild the first P1 → serial SN1 of C1 is restored
|
||||
- Unbuild the second P1 → serial SN2 of C1 is restored (SN1 must not be reused)
|
||||
"""
|
||||
(self.bom_4.product_id | self.bom_4.bom_line_ids.product_id).is_storable = True
|
||||
(self.bom_4.product_id | self.bom_4.bom_line_ids.product_id).tracking = 'serial'
|
||||
component = self.bom_4.bom_line_ids.product_id
|
||||
# Serials for component
|
||||
sn1 = self.env['stock.lot'].create({
|
||||
'name': 'SN1',
|
||||
'product_id': component.id,
|
||||
})
|
||||
sn2 = self.env['stock.lot'].create({
|
||||
'name': 'SN2',
|
||||
'product_id': component.id,
|
||||
})
|
||||
self.env['stock.quant']._update_available_quantity(component, self.stock_location, 1, lot_id=sn1)
|
||||
self.env['stock.quant']._update_available_quantity(component, self.stock_location, 1, lot_id=sn2)
|
||||
# Serials for finished product
|
||||
fp_sn1 = self.env['stock.lot'].create({
|
||||
'name': 'SN1-P1',
|
||||
'product_id': self.bom_4.product_id.id,
|
||||
})
|
||||
fp_sn2 = self.env['stock.lot'].create({
|
||||
'name': 'SN2-P1',
|
||||
'product_id': self.bom_4.product_id.id,
|
||||
})
|
||||
mo = self.env['mrp.production'].create({
|
||||
'product_id': self.bom_4.product_id.id,
|
||||
'product_qty': 2.0,
|
||||
'bom_id': self.bom_4.id,
|
||||
'product_uom_id': self.bom_4.product_uom_id.id,
|
||||
})
|
||||
mo.action_confirm()
|
||||
mo.lot_producing_ids = fp_sn1 + fp_sn2
|
||||
mo._set_qty_producing()
|
||||
mo.button_mark_done()
|
||||
self.assertEqual(mo.state, 'done')
|
||||
self.assertEqual(mo.move_raw_ids.move_line_ids.lot_id, sn1 + sn2)
|
||||
|
||||
unbuild_1 = self.env['mrp.unbuild'].create({
|
||||
'mo_id': mo.id,
|
||||
'product_id': self.bom_4.product_id.id,
|
||||
'lot_id': fp_sn1.id,
|
||||
})
|
||||
unbuild_1.action_unbuild()
|
||||
self.assertEqual(unbuild_1.state, 'done')
|
||||
self.assertEqual(
|
||||
self.env['stock.quant']._get_available_quantity(component, self.stock_location, lot_id=sn1),
|
||||
1, 'SN1 should be restored after first unbuild'
|
||||
)
|
||||
unbuild_2 = self.env['mrp.unbuild'].create({
|
||||
'mo_id': mo.id,
|
||||
'product_id': self.bom_4.product_id.id,
|
||||
'lot_id': fp_sn2.id,
|
||||
})
|
||||
unbuild_2.action_unbuild()
|
||||
self.assertEqual(unbuild_2.state, 'done')
|
||||
self.assertEqual(
|
||||
self.env['stock.quant']._get_available_quantity(component, self.stock_location, lot_id=sn2),
|
||||
1, 'SN2 should be restored after second unbuild'
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue