348 lines
13 KiB
Python
348 lines
13 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo.tests import Form, tagged
|
|
from odoo.addons.mrp.tests.common import TestMrpCommon
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestRepairTraceability(TestMrpCommon):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.env.ref('base.group_user').write({'implied_ids': [(4, cls.env.ref('stock.group_production_lot').id)]})
|
|
|
|
def test_tracking_repair_production(self):
|
|
"""
|
|
Test that removing a tracked component with a repair does not block the flow of using that component in another
|
|
bom
|
|
"""
|
|
product_to_repair = self.env['product.product'].create({
|
|
'name': 'product first serial to act repair',
|
|
'tracking': 'serial',
|
|
})
|
|
ptrepair_lot = self.env['stock.lot'].create({
|
|
'name': 'A1',
|
|
'product_id': product_to_repair.id,
|
|
})
|
|
product_to_remove = self.env['product.product'].create({
|
|
'name': 'other first serial to remove with repair',
|
|
'tracking': 'serial',
|
|
})
|
|
ptremove_lot = self.env['stock.lot'].create({
|
|
'name': 'B2',
|
|
'product_id': product_to_remove.id,
|
|
})
|
|
# Create a manufacturing order with product (with SN A1)
|
|
mo_form = Form(self.env['mrp.production'])
|
|
mo_form.product_id = product_to_repair
|
|
with mo_form.move_raw_ids.new() as move:
|
|
move.product_id = product_to_remove
|
|
move.product_uom_qty = 1
|
|
mo = mo_form.save()
|
|
mo.action_confirm()
|
|
# Set serial to A1
|
|
mo.lot_producing_id = ptrepair_lot
|
|
# Set component serial to B2
|
|
mo.move_raw_ids.move_line_ids.lot_id = ptremove_lot
|
|
mo.move_raw_ids.picked = True
|
|
mo.button_mark_done()
|
|
|
|
with Form(self.env['repair.order']) as ro_form:
|
|
ro_form.product_id = product_to_repair
|
|
ro_form.lot_id = ptrepair_lot # Repair product Serial A1
|
|
with ro_form.move_ids.new() as operation:
|
|
operation.repair_line_type = 'remove'
|
|
operation.product_id = product_to_remove
|
|
ro = ro_form.save()
|
|
ro.action_validate()
|
|
ro.move_ids[0].lot_ids = ptremove_lot # Remove product Serial B2 from the product.
|
|
ro.action_repair_start()
|
|
ro.move_ids.picked = True
|
|
ro.action_repair_end()
|
|
|
|
# Create a manufacturing order with product (with SN A2)
|
|
mo2_form = Form(self.env['mrp.production'])
|
|
mo2_form.product_id = product_to_repair
|
|
with mo2_form.move_raw_ids.new() as move:
|
|
move.product_id = product_to_remove
|
|
move.product_uom_qty = 1
|
|
mo2 = mo2_form.save()
|
|
mo2.action_confirm()
|
|
# Set serial to A2
|
|
mo2.lot_producing_id = self.env['stock.lot'].create({
|
|
'name': 'A2',
|
|
'product_id': product_to_repair.id,
|
|
})
|
|
# Set component serial to B2 again, it is possible
|
|
mo2.move_raw_ids.move_line_ids.lot_id = ptremove_lot
|
|
mo2.move_raw_ids.picked = True
|
|
# We are not forbidden to use that serial number, so nothing raised here
|
|
mo2.button_mark_done()
|
|
|
|
def test_mo_with_used_sn_component(self):
|
|
"""
|
|
Suppose a tracked-by-usn component has been used to produce a product. Then, using a repair order,
|
|
this component is removed from the product and returned as available stock. The user should be able to
|
|
use the component in a new MO
|
|
"""
|
|
def produce_one(product, component):
|
|
mo_form = Form(self.env['mrp.production'])
|
|
mo_form.product_id = product
|
|
with mo_form.move_raw_ids.new() as raw_line:
|
|
raw_line.product_id = component
|
|
raw_line.product_uom_qty = 1
|
|
mo = mo_form.save()
|
|
mo.action_confirm()
|
|
mo.action_assign()
|
|
mo.move_raw_ids.picked = True
|
|
mo.button_mark_done()
|
|
return mo
|
|
|
|
|
|
stock_location = self.env.ref('stock.stock_location_stock')
|
|
|
|
finished, component = self.env['product.product'].create([{
|
|
'name': 'Finished Product',
|
|
'is_storable': True,
|
|
}, {
|
|
'name': 'SN Componentt',
|
|
'is_storable': True,
|
|
'tracking': 'serial',
|
|
}])
|
|
|
|
sn_lot = self.env['stock.lot'].create({
|
|
'product_id': component.id,
|
|
'name': 'USN01',
|
|
})
|
|
self.env['stock.quant']._update_available_quantity(component, stock_location, 1, lot_id=sn_lot)
|
|
|
|
mo = produce_one(finished, component)
|
|
self.assertEqual(mo.state, 'done')
|
|
self.assertEqual(mo.move_raw_ids.lot_ids, sn_lot)
|
|
ro_form = Form(self.env['repair.order'])
|
|
ro_form.product_id = finished
|
|
with ro_form.move_ids.new() as ro_line:
|
|
ro_line.repair_line_type = 'recycle'
|
|
ro_line.product_id = component
|
|
ro = ro_form.save()
|
|
ro.action_validate()
|
|
ro.move_ids[0].lot_ids = sn_lot
|
|
ro.action_repair_start()
|
|
ro.move_ids.picked = True
|
|
ro.action_repair_end()
|
|
mo = produce_one(finished, component)
|
|
self.assertEqual(mo.state, 'done')
|
|
self.assertEqual(mo.move_raw_ids.lot_ids, sn_lot)
|
|
# Now, we will test removing the component and putting it back in stock,
|
|
# then placing it back into the product and removing it a second time.
|
|
# The user should be able to use the component in a new MO.
|
|
ro_form = Form(self.env['repair.order'])
|
|
ro_form.product_id = finished
|
|
with ro_form.move_ids.new() as ro_line:
|
|
ro_line.repair_line_type = 'recycle'
|
|
ro_line.product_id = component
|
|
ro_line.location_dest_id = stock_location
|
|
ro = ro_form.save()
|
|
ro.action_validate()
|
|
ro.move_ids[0].lot_ids = sn_lot
|
|
ro.action_repair_start()
|
|
ro.action_repair_end()
|
|
self.assertEqual(ro.state, 'done')
|
|
# Add the component into the product
|
|
ro_form = Form(self.env['repair.order'])
|
|
ro_form.product_id = finished
|
|
with ro_form.move_ids.new() as ro_line:
|
|
ro_line.repair_line_type = 'add'
|
|
ro_line.product_id = component
|
|
ro_line.location_id = stock_location
|
|
ro = ro_form.save()
|
|
ro.action_validate()
|
|
ro.move_ids[0].lot_ids = sn_lot
|
|
ro.action_repair_start()
|
|
ro.action_repair_end()
|
|
self.assertEqual(ro.state, 'done')
|
|
# Removing it a second time
|
|
ro_form = Form(self.env['repair.order'])
|
|
ro_form.product_id = finished
|
|
with ro_form.move_ids.new() as ro_line:
|
|
ro_line.repair_line_type = 'recycle'
|
|
ro_line.product_id = component
|
|
ro_line.location_dest_id = stock_location
|
|
ro = ro_form.save()
|
|
ro.action_validate()
|
|
ro.move_ids[0].lot_ids = sn_lot
|
|
ro.action_repair_start()
|
|
ro.action_repair_end()
|
|
self.assertEqual(ro.state, 'done')
|
|
# check if the removed component can be used in a new MO
|
|
mo = produce_one(finished, component)
|
|
self.assertEqual(mo.state, 'done')
|
|
self.assertEqual(mo.move_raw_ids.lot_ids, sn_lot)
|
|
|
|
def test_mo_with_used_sn_component_02(self):
|
|
"""
|
|
Suppose a tracked-by-usn component has been remvoed in a repair order. Then, using to produce a product,
|
|
but this product has been unbuild. The user should be able to use the component in a new MO
|
|
"""
|
|
finished, component = self.env['product.product'].create([{
|
|
'name': 'Finished Product',
|
|
'is_storable': True,
|
|
}, {
|
|
'name': 'SN Componentt',
|
|
'is_storable': True,
|
|
'tracking': 'serial',
|
|
}])
|
|
|
|
sn_lot = self.env['stock.lot'].create({
|
|
'product_id': component.id,
|
|
'name': 'USN01',
|
|
'company_id': self.env.company.id,
|
|
})
|
|
|
|
# create a repair order
|
|
ro_form = Form(self.env['repair.order'])
|
|
ro_form.product_id = self.product_1
|
|
with ro_form.move_ids.new() as ro_line:
|
|
ro_line.repair_line_type = 'remove'
|
|
ro_line.product_id = component
|
|
ro = ro_form.save()
|
|
ro.action_validate()
|
|
ro.move_ids[0].lot_ids = sn_lot
|
|
ro.action_repair_start()
|
|
ro.action_repair_end()
|
|
|
|
stock_location = self.env.ref('stock.stock_location_stock')
|
|
self.env['stock.quant']._update_available_quantity(component, stock_location, 1, lot_id=sn_lot)
|
|
self.assertEqual(component.qty_available, 1)
|
|
|
|
# create a manufacturing order
|
|
mo_form = Form(self.env['mrp.production'])
|
|
mo_form.product_id = finished
|
|
with mo_form.move_raw_ids.new() as raw_line:
|
|
raw_line.product_id = component
|
|
raw_line.product_uom_qty = 1
|
|
mo = mo_form.save()
|
|
mo.action_confirm()
|
|
mo.action_assign()
|
|
mo.move_raw_ids.move_line_ids.quantity = 1
|
|
mo.move_raw_ids.picked = True
|
|
mo.button_mark_done()
|
|
self.assertEqual(mo.state, 'done')
|
|
self.assertEqual(mo.move_raw_ids.lot_ids, sn_lot)
|
|
# unbuild the mo
|
|
unbuild_form = Form(self.env['mrp.unbuild'])
|
|
unbuild_form.mo_id = mo
|
|
unbuild_form.save().action_unbuild()
|
|
# create another mo and use the same SN
|
|
mo_form = Form(self.env['mrp.production'])
|
|
mo_form.product_id = finished
|
|
with mo_form.move_raw_ids.new() as raw_line:
|
|
raw_line.product_id = component
|
|
raw_line.product_uom_qty = 1
|
|
mo = mo_form.save()
|
|
mo.action_confirm()
|
|
mo.action_assign()
|
|
mo.move_raw_ids.move_line_ids.quantity = 1
|
|
mo.move_raw_ids.picked = True
|
|
mo.button_mark_done()
|
|
self.assertEqual(mo.state, 'done')
|
|
self.assertEqual(mo.move_raw_ids.lot_ids, sn_lot)
|
|
|
|
def test_mo_with_unscrapped_tracked_component(self):
|
|
"""
|
|
Tracked-by-sn component
|
|
Use it in a MO
|
|
Repair the finished product:
|
|
Remove the component, destination: scrap location
|
|
Move the component back to the stock
|
|
Use it in a MO
|
|
"""
|
|
stock_location = self.env.ref('stock.stock_location_stock')
|
|
scrap_location = self.env['stock.location'].search([('company_id', '=', self.env.company.id), ('scrap_location', '=', True)], limit=1)
|
|
|
|
finished = self.bom_4.product_id
|
|
component = self.bom_4.bom_line_ids.product_id
|
|
component.write({
|
|
'is_storable': True,
|
|
'tracking': 'serial',
|
|
})
|
|
|
|
sn_lot = self.env['stock.lot'].create({
|
|
'product_id': component.id,
|
|
'name': 'SN01',
|
|
'company_id': self.env.company.id,
|
|
})
|
|
self.env['stock.quant']._update_available_quantity(component, stock_location, 1, lot_id=sn_lot)
|
|
|
|
mo_form = Form(self.env['mrp.production'])
|
|
mo_form.bom_id = self.bom_4
|
|
mo = mo_form.save()
|
|
mo.action_confirm()
|
|
mo.qty_producing = 1
|
|
mo.move_raw_ids.move_line_ids.quantity = 1
|
|
mo.move_raw_ids.move_line_ids.picked = True
|
|
mo.button_mark_done()
|
|
|
|
ro = self.env['repair.order'].create({
|
|
'product_id': finished.id,
|
|
'picking_type_id': self.warehouse_1.repair_type_id.id,
|
|
'move_ids': [
|
|
(0, 0, {
|
|
'name': 'foo',
|
|
'product_id': component.id,
|
|
'lot_ids': [(4, sn_lot.id)],
|
|
'repair_line_type': 'remove',
|
|
'location_dest_id': scrap_location.id,
|
|
'price_unit': 0,
|
|
})
|
|
],
|
|
})
|
|
ro.action_validate()
|
|
ro.action_repair_start()
|
|
ro.action_repair_end()
|
|
|
|
sm = self.env['stock.move'].create({
|
|
'name': component.name,
|
|
'product_id': component.id,
|
|
'product_uom_qty': 1,
|
|
'product_uom': component.uom_id.id,
|
|
'location_id': scrap_location.id,
|
|
'location_dest_id': stock_location.id,
|
|
})
|
|
sm._action_confirm()
|
|
sm.move_line_ids.write({
|
|
'quantity': 1.0,
|
|
'lot_id': sn_lot.id,
|
|
'picked': True,
|
|
})
|
|
sm._action_done()
|
|
|
|
mo_form = Form(self.env['mrp.production'])
|
|
mo_form.bom_id = self.bom_4
|
|
mo = mo_form.save()
|
|
mo.action_confirm()
|
|
mo.qty_producing = 1
|
|
mo.move_raw_ids.move_line_ids.quantity = 1
|
|
mo.move_raw_ids.move_line_ids.picked = True
|
|
mo.button_mark_done()
|
|
|
|
self.assertRecordValues(mo.move_raw_ids.move_line_ids, [
|
|
{'product_id': component.id, 'lot_id': sn_lot.id, 'quantity': 1.0, 'state': 'done'},
|
|
])
|
|
|
|
def test_repair_with_consumable_kit(self):
|
|
"""Test that a consumable kit can be repaired."""
|
|
self.assertEqual(self.bom_2.type, 'phantom')
|
|
kit_product = self.bom_2.product_id
|
|
kit_product.type = 'consu'
|
|
self.assertEqual(kit_product.type, 'consu')
|
|
ro = self.env['repair.order'].create({
|
|
'product_id': kit_product.id,
|
|
'picking_type_id': self.warehouse_1.repair_type_id.id,
|
|
})
|
|
ro.action_validate()
|
|
ro.action_repair_start()
|
|
ro.action_repair_end()
|
|
self.assertEqual(ro.state, 'done')
|