[IMP] mrp: clean test files + adapt to code
No need a common file for 3 tests. On top of it contains global variable and a lot of indirection, making it impossible to read. Also before the quant reservation commit, a savepoint was call during the action_assign and trigger a flush and recompute all. Since it has been remove, it's computed during the first read on it, that's during the mark as done and since the MO state is not draft it will keep the default value (False). Call it after create to trigger the computation at the right time. + a bit of linting closes odoo/odoo#115328 Related: odoo/enterprise#38207 Signed-off-by: William Henrotin (whe) <whe@odoo.com>
This commit is contained in:
@@ -15,5 +15,5 @@ from . import test_multicompany
|
||||
from . import test_backorder
|
||||
from . import test_smp
|
||||
from . import test_performance
|
||||
from . import test_consume_tracked_component
|
||||
from . import test_consume_component
|
||||
from . import test_manual_consumption
|
||||
|
||||
@@ -1,233 +0,0 @@
|
||||
import copy
|
||||
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import common
|
||||
|
||||
class TestConsumeTrackedComponentCommon(common.TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""
|
||||
The following variables are used in each test to define the number of MO to generate.
|
||||
They're also used as a verification in the executeConsumptionTriggers() to see if enough MO were passed to it
|
||||
in order to test all the triggers.
|
||||
|
||||
SERIAL : MO's product_tracking is 'serial'
|
||||
DEFAULT : MO's product_tracking is 'none' or 'lot'
|
||||
AVAILABLE : MO'S raw components are fully available
|
||||
"""
|
||||
super().setUpClass()
|
||||
|
||||
cls.SERIAL_AVAILABLE_TRIGGERS_COUNT = 3
|
||||
cls.DEFAULT_AVAILABLE_TRIGGERS_COUNT = 2
|
||||
cls.SERIAL_TRIGGERS_COUNT = 2
|
||||
cls.DEFAULT_TRIGGERS_COUNT = 1
|
||||
|
||||
cls.manufacture_route = cls.env.ref('mrp.route_warehouse0_manufacture')
|
||||
cls.stock_id = cls.env.ref('stock.stock_location_stock').id
|
||||
|
||||
cls.picking_type = cls.env['stock.picking.type'].search([('code', '=', 'mrp_operation')])[0]
|
||||
cls.picking_type.use_create_components_lots = True
|
||||
cls.picking_type.use_auto_consume_components_lots = True
|
||||
|
||||
#Create Products & Components
|
||||
cls.produced_lot = cls.env['product.product'].create({
|
||||
'name': 'Produced Lot',
|
||||
'type': 'product',
|
||||
'categ_id': cls.env.ref('product.product_category_all').id,
|
||||
'tracking' : 'lot',
|
||||
'route_ids': [(4, cls.manufacture_route.id, 0)],
|
||||
})
|
||||
cls.produced_serial = cls.env['product.product'].create({
|
||||
'name': 'Produced Serial',
|
||||
'type': 'product',
|
||||
'categ_id': cls.env.ref('product.product_category_all').id,
|
||||
'tracking' : 'serial',
|
||||
'route_ids': [(4, cls.manufacture_route.id, 0)],
|
||||
})
|
||||
cls.produced_none = cls.env['product.product'].create({
|
||||
'name': 'Produced None',
|
||||
'type': 'product',
|
||||
'categ_id': cls.env.ref('product.product_category_all').id,
|
||||
'tracking' : 'none',
|
||||
'route_ids': [(4, cls.manufacture_route.id, 0)],
|
||||
})
|
||||
|
||||
cls.raw_lot = cls.env['product.product'].create({
|
||||
'name': 'Raw Lot',
|
||||
'type': 'product',
|
||||
'categ_id': cls.env.ref('product.product_category_all').id,
|
||||
'tracking' : 'lot',
|
||||
})
|
||||
cls.raw_serial = cls.env['product.product'].create({
|
||||
'name': 'Raw Serial',
|
||||
'type': 'product',
|
||||
'categ_id': cls.env.ref('product.product_category_all').id,
|
||||
'tracking' : 'serial',
|
||||
})
|
||||
cls.raw_none = cls.env['product.product'].create({
|
||||
'name': 'Raw None',
|
||||
'type': 'product',
|
||||
'categ_id': cls.env.ref('product.product_category_all').id,
|
||||
'tracking' : 'none',
|
||||
})
|
||||
|
||||
cls.raws = [cls.raw_none, cls.raw_lot, cls.raw_serial]
|
||||
|
||||
#Workcenter
|
||||
cls.workcenter = cls.env['mrp.workcenter'].create({
|
||||
'name' : 'Assembly Line',
|
||||
})
|
||||
|
||||
#BoMs
|
||||
cls.bom_none = cls.env['mrp.bom'].create({
|
||||
'product_tmpl_id' : cls.produced_none.product_tmpl_id.id,
|
||||
'product_uom_id' : cls.produced_none.uom_id.id,
|
||||
'consumption' : 'flexible',
|
||||
'sequence' : 1
|
||||
})
|
||||
|
||||
cls.bom_none_lines = cls.create_bom_lines(cls.bom_none, cls.raws, [3, 2, 1])
|
||||
|
||||
cls.bom_lot = cls.env['mrp.bom'].create({
|
||||
'product_tmpl_id' : cls.produced_lot.product_tmpl_id.id,
|
||||
'product_uom_id' : cls.produced_lot.uom_id.id,
|
||||
'consumption' : 'flexible',
|
||||
'sequence' : 2
|
||||
})
|
||||
|
||||
cls.bom_lot_lines = cls.create_bom_lines(cls.bom_lot, cls.raws, [3, 2, 1])
|
||||
|
||||
cls.bom_serial = cls.env['mrp.bom'].create({
|
||||
'product_tmpl_id' : cls.produced_serial.product_tmpl_id.id,
|
||||
'product_uom_id' : cls.produced_serial.uom_id.id,
|
||||
'consumption' : 'flexible',
|
||||
'sequence' : 1
|
||||
})
|
||||
|
||||
cls.bom_serial_lines = cls.create_bom_lines(cls.bom_serial, cls.raws, [3, 2, 1])
|
||||
|
||||
#Manufacturing Orders
|
||||
cls.mo_none_tmpl = {
|
||||
'product_id' : cls.produced_none.id,
|
||||
'product_uom_id' : cls.produced_none.uom_id.id,
|
||||
'product_qty' : 1,
|
||||
'bom_id' : cls.bom_none.id
|
||||
}
|
||||
|
||||
cls.mo_lot_tmpl = {
|
||||
'product_id' : cls.produced_lot.id,
|
||||
'product_uom_id' : cls.produced_lot.uom_id.id,
|
||||
'product_qty' : 1,
|
||||
'bom_id' : cls.bom_lot.id
|
||||
}
|
||||
|
||||
cls.mo_serial_tmpl = {
|
||||
'product_id' : cls.produced_serial.id,
|
||||
'product_uom_id' : cls.produced_serial.uom_id.id,
|
||||
'product_qty' : 1,
|
||||
'bom_id' : cls.bom_serial.id
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def create_quant(cls, product, qty, offset=0, name="L"):
|
||||
i = 1
|
||||
if product.tracking == 'serial':
|
||||
i, qty = qty, 1
|
||||
if name == "L":
|
||||
name = "S"
|
||||
|
||||
vals = []
|
||||
for x in range(1, i+1):
|
||||
qDict = {
|
||||
'location_id': cls.stock_id,
|
||||
'product_id': product.id,
|
||||
'inventory_quantity': qty,
|
||||
}
|
||||
|
||||
if product.tracking != 'none':
|
||||
qDict['lot_id'] = cls.env['stock.lot'].create({
|
||||
'name': name + str(offset + x),
|
||||
'product_id': product.id,
|
||||
'company_id': cls.env.company.id
|
||||
}).id
|
||||
vals.append(qDict)
|
||||
|
||||
return cls.env['stock.quant'].create(vals)
|
||||
|
||||
@classmethod
|
||||
def create_bom_lines(cls, bom, products, quantities=None):
|
||||
if quantities is None:
|
||||
quantities = [1 for i in range(len(products))]
|
||||
|
||||
vals = []
|
||||
for product, seq in zip(products, range(len(products))):
|
||||
vals.append({
|
||||
'product_id' : product.id,
|
||||
'product_qty' : quantities[seq],
|
||||
'product_uom_id' : product.uom_id.id,
|
||||
'sequence' : seq,
|
||||
'bom_id' : bom.id,
|
||||
})
|
||||
|
||||
return cls.env['mrp.bom.line'].create(vals)
|
||||
|
||||
@classmethod
|
||||
def create_mo(cls, template, count):
|
||||
vals = []
|
||||
for _ in range(count):
|
||||
vals.append(copy.deepcopy(template))
|
||||
return cls.env['mrp.production'].create(vals)
|
||||
|
||||
def executeConsumptionTriggers(self, mrp_productions):
|
||||
"""
|
||||
There's 3 different triggers to test : _onchange_producing(), action_generate_serial(), button_mark_done().
|
||||
|
||||
Depending on the tracking of the final product and the availability of the components,
|
||||
only a part of these 3 triggers is available or intended to work.
|
||||
|
||||
This function automatically call and process the appropriate triggers.
|
||||
"""
|
||||
tracking = mrp_productions[0].product_tracking
|
||||
sameTracking = True
|
||||
for mo in mrp_productions:
|
||||
sameTracking = sameTracking and mo.product_tracking == tracking
|
||||
self.assertTrue(sameTracking, "MOs passed to the executeConsumptionTriggers method shall have the same product_tracking")
|
||||
|
||||
isSerial = tracking == 'serial'
|
||||
isAvailable = all(move.state == 'assigned' for move in mrp_productions.move_raw_ids)
|
||||
isComponentTracking = any(move.has_tracking != 'none' for move in mrp_productions.move_raw_ids)
|
||||
|
||||
countOk = True
|
||||
length = len(mrp_productions)
|
||||
if isSerial:
|
||||
if isAvailable:
|
||||
countOk = length == self.SERIAL_AVAILABLE_TRIGGERS_COUNT
|
||||
else:
|
||||
countOk = length == self.SERIAL_TRIGGERS_COUNT
|
||||
else:
|
||||
if isAvailable:
|
||||
countOk = length == self.DEFAULT_AVAILABLE_TRIGGERS_COUNT
|
||||
else:
|
||||
countOk = length == self.DEFAULT_TRIGGERS_COUNT
|
||||
self.assertTrue(countOk, "The number of MOs passed to the executeConsumptionTriggers method does not match the associated TRIGGERS_COUNT")
|
||||
|
||||
mrp_productions[0].qty_producing = mrp_productions[0].product_qty
|
||||
mrp_productions[0]._onchange_producing()
|
||||
|
||||
i = 1
|
||||
if isSerial:
|
||||
mrp_productions[i].action_generate_serial()
|
||||
i += 1
|
||||
|
||||
if isAvailable:
|
||||
error = False
|
||||
try:
|
||||
mrp_productions[i].button_mark_done()
|
||||
except UserError:
|
||||
error = True
|
||||
|
||||
if isComponentTracking and not mrp_productions[i].picking_type_id.use_auto_consume_components_lots:
|
||||
self.assertTrue(error, "Immediate Production shall raise an error when tracked product are not provided.")
|
||||
else:
|
||||
self.assertFalse(error, "Immediate Production shall not raise an error.")
|
||||
@@ -0,0 +1,385 @@
|
||||
import copy
|
||||
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import common, tagged
|
||||
|
||||
|
||||
class TestConsumeComponentCommon(common.TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""
|
||||
The following variables are used in each test to define the number of MO to generate.
|
||||
They're also used as a verification in the executeConsumptionTriggers() to see if enough MO were passed to it
|
||||
in order to test all the triggers.
|
||||
|
||||
SERIAL : MO's product_tracking is 'serial'
|
||||
DEFAULT : MO's product_tracking is 'none' or 'lot'
|
||||
AVAILABLE : MO'S raw components are fully available
|
||||
"""
|
||||
super().setUpClass()
|
||||
|
||||
cls.SERIAL_AVAILABLE_TRIGGERS_COUNT = 3
|
||||
cls.DEFAULT_AVAILABLE_TRIGGERS_COUNT = 2
|
||||
cls.SERIAL_TRIGGERS_COUNT = 2
|
||||
cls.DEFAULT_TRIGGERS_COUNT = 1
|
||||
|
||||
cls.manufacture_route = cls.env.ref('mrp.route_warehouse0_manufacture')
|
||||
cls.stock_id = cls.env.ref('stock.stock_location_stock').id
|
||||
|
||||
cls.picking_type = cls.env['stock.picking.type'].search([('code', '=', 'mrp_operation')])[0]
|
||||
cls.picking_type.use_create_components_lots = True
|
||||
cls.picking_type.use_auto_consume_components_lots = True
|
||||
|
||||
# Create Products & Components
|
||||
cls.produced_lot = cls.env['product.product'].create({
|
||||
'name': 'Produced Lot',
|
||||
'type': 'product',
|
||||
'categ_id': cls.env.ref('product.product_category_all').id,
|
||||
'tracking': 'lot',
|
||||
'route_ids': [(4, cls.manufacture_route.id, 0)],
|
||||
})
|
||||
cls.produced_serial = cls.env['product.product'].create({
|
||||
'name': 'Produced Serial',
|
||||
'type': 'product',
|
||||
'categ_id': cls.env.ref('product.product_category_all').id,
|
||||
'tracking': 'serial',
|
||||
'route_ids': [(4, cls.manufacture_route.id, 0)],
|
||||
})
|
||||
cls.produced_none = cls.env['product.product'].create({
|
||||
'name': 'Produced None',
|
||||
'type': 'product',
|
||||
'categ_id': cls.env.ref('product.product_category_all').id,
|
||||
'tracking': 'none',
|
||||
'route_ids': [(4, cls.manufacture_route.id, 0)],
|
||||
})
|
||||
|
||||
cls.raw_lot = cls.env['product.product'].create({
|
||||
'name': 'Raw Lot',
|
||||
'type': 'product',
|
||||
'categ_id': cls.env.ref('product.product_category_all').id,
|
||||
'tracking': 'lot',
|
||||
})
|
||||
cls.raw_serial = cls.env['product.product'].create({
|
||||
'name': 'Raw Serial',
|
||||
'type': 'product',
|
||||
'categ_id': cls.env.ref('product.product_category_all').id,
|
||||
'tracking': 'serial',
|
||||
})
|
||||
cls.raw_none = cls.env['product.product'].create({
|
||||
'name': 'Raw None',
|
||||
'type': 'product',
|
||||
'categ_id': cls.env.ref('product.product_category_all').id,
|
||||
'tracking': 'none',
|
||||
})
|
||||
|
||||
cls.raws = [cls.raw_none, cls.raw_lot, cls.raw_serial]
|
||||
|
||||
# Workcenter
|
||||
cls.workcenter = cls.env['mrp.workcenter'].create({
|
||||
'name': 'Assembly Line',
|
||||
})
|
||||
|
||||
# BoMs
|
||||
cls.bom_none = cls.env['mrp.bom'].create({
|
||||
'product_tmpl_id': cls.produced_none.product_tmpl_id.id,
|
||||
'product_uom_id': cls.produced_none.uom_id.id,
|
||||
'consumption': 'flexible',
|
||||
'sequence': 1
|
||||
})
|
||||
|
||||
cls.bom_none_lines = cls.create_bom_lines(cls.bom_none, cls.raws, [3, 2, 1])
|
||||
|
||||
cls.bom_lot = cls.env['mrp.bom'].create({
|
||||
'product_tmpl_id': cls.produced_lot.product_tmpl_id.id,
|
||||
'product_uom_id': cls.produced_lot.uom_id.id,
|
||||
'consumption': 'flexible',
|
||||
'sequence': 2
|
||||
})
|
||||
|
||||
cls.bom_lot_lines = cls.create_bom_lines(cls.bom_lot, cls.raws, [3, 2, 1])
|
||||
|
||||
cls.bom_serial = cls.env['mrp.bom'].create({
|
||||
'product_tmpl_id': cls.produced_serial.product_tmpl_id.id,
|
||||
'product_uom_id': cls.produced_serial.uom_id.id,
|
||||
'consumption': 'flexible',
|
||||
'sequence': 1
|
||||
})
|
||||
|
||||
cls.bom_serial_lines = cls.create_bom_lines(cls.bom_serial, cls.raws, [3, 2, 1])
|
||||
|
||||
# Manufacturing Orders
|
||||
cls.mo_none_tmpl = {
|
||||
'product_id': cls.produced_none.id,
|
||||
'product_uom_id': cls.produced_none.uom_id.id,
|
||||
'product_qty': 1,
|
||||
'bom_id': cls.bom_none.id
|
||||
}
|
||||
|
||||
cls.mo_lot_tmpl = {
|
||||
'product_id': cls.produced_lot.id,
|
||||
'product_uom_id': cls.produced_lot.uom_id.id,
|
||||
'product_qty': 1,
|
||||
'bom_id': cls.bom_lot.id
|
||||
}
|
||||
|
||||
cls.mo_serial_tmpl = {
|
||||
'product_id': cls.produced_serial.id,
|
||||
'product_uom_id': cls.produced_serial.uom_id.id,
|
||||
'product_qty': 1,
|
||||
'bom_id': cls.bom_serial.id
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def create_quant(cls, product, qty, offset=0, name="L"):
|
||||
i = 1
|
||||
if product.tracking == 'serial':
|
||||
i, qty = qty, 1
|
||||
if name == "L":
|
||||
name = "S"
|
||||
|
||||
vals = []
|
||||
for x in range(1, i + 1):
|
||||
qDict = {
|
||||
'location_id': cls.stock_id,
|
||||
'product_id': product.id,
|
||||
'inventory_quantity': qty,
|
||||
}
|
||||
|
||||
if product.tracking != 'none':
|
||||
qDict['lot_id'] = cls.env['stock.lot'].create({
|
||||
'name': name + str(offset + x),
|
||||
'product_id': product.id,
|
||||
'company_id': cls.env.company.id
|
||||
}).id
|
||||
vals.append(qDict)
|
||||
|
||||
return cls.env['stock.quant'].create(vals)
|
||||
|
||||
@classmethod
|
||||
def create_bom_lines(cls, bom, products, quantities=None):
|
||||
if quantities is None:
|
||||
quantities = [1 for i in range(len(products))]
|
||||
|
||||
vals = []
|
||||
for product, seq in zip(products, range(len(products))):
|
||||
vals.append({
|
||||
'product_id': product.id,
|
||||
'product_qty': quantities[seq],
|
||||
'product_uom_id': product.uom_id.id,
|
||||
'sequence': seq,
|
||||
'bom_id': bom.id,
|
||||
})
|
||||
|
||||
return cls.env['mrp.bom.line'].create(vals)
|
||||
|
||||
@classmethod
|
||||
def create_mo(cls, template, count):
|
||||
vals = []
|
||||
for _ in range(count):
|
||||
vals.append(copy.deepcopy(template))
|
||||
mos = cls.env['mrp.production'].create(vals)
|
||||
mos.move_raw_ids.mapped('manual_consumption')
|
||||
return mos
|
||||
|
||||
def executeConsumptionTriggers(self, mrp_productions):
|
||||
"""There's 3 different triggers to test : _onchange_producing(), action_generate_serial(), button_mark_done().
|
||||
|
||||
Depending on the tracking of the final product and the availability of the components,
|
||||
only a part of these 3 triggers is available or intended to work.
|
||||
|
||||
This function automatically call and process the appropriate triggers.
|
||||
"""
|
||||
tracking = mrp_productions[0].product_tracking
|
||||
sameTracking = True
|
||||
for mo in mrp_productions:
|
||||
sameTracking = sameTracking and mo.product_tracking == tracking
|
||||
self.assertTrue(sameTracking, "MOs passed to the executeConsumptionTriggers method shall have the same product_tracking")
|
||||
|
||||
isSerial = tracking == 'serial'
|
||||
isAvailable = all(move.state == 'assigned' for move in mrp_productions.move_raw_ids)
|
||||
isComponentTracking = any(move.has_tracking != 'none' for move in mrp_productions.move_raw_ids)
|
||||
|
||||
countOk = True
|
||||
length = len(mrp_productions)
|
||||
if isSerial:
|
||||
if isAvailable:
|
||||
countOk = length == self.SERIAL_AVAILABLE_TRIGGERS_COUNT
|
||||
else:
|
||||
countOk = length == self.SERIAL_TRIGGERS_COUNT
|
||||
else:
|
||||
if isAvailable:
|
||||
countOk = length == self.DEFAULT_AVAILABLE_TRIGGERS_COUNT
|
||||
else:
|
||||
countOk = length == self.DEFAULT_TRIGGERS_COUNT
|
||||
self.assertTrue(countOk, "The number of MOs passed to the executeConsumptionTriggers method does not match the associated TRIGGERS_COUNT")
|
||||
|
||||
mrp_productions[0].qty_producing = mrp_productions[0].product_qty
|
||||
mrp_productions[0]._onchange_producing()
|
||||
|
||||
i = 1
|
||||
if isSerial:
|
||||
mrp_productions[i].action_generate_serial()
|
||||
i += 1
|
||||
|
||||
if isAvailable:
|
||||
error = False
|
||||
try:
|
||||
mrp_productions[i].button_mark_done()
|
||||
except UserError:
|
||||
error = True
|
||||
|
||||
if isComponentTracking and not mrp_productions[i].picking_type_id.use_auto_consume_components_lots:
|
||||
self.assertTrue(error, "Immediate Production shall raise an error when tracked product are not provided.")
|
||||
else:
|
||||
self.assertFalse(error, "Immediate Production shall not raise an error.")
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestConsumeComponent(TestConsumeComponentCommon):
|
||||
def test_option_disabled_and_qty_available(self):
|
||||
"""Option disabled, qty available
|
||||
-> Not Tracked components are fully consumed
|
||||
-> Tracked components are only consumed on button_mark_done trigger
|
||||
"""
|
||||
self.picking_type.use_auto_consume_components_lots = False
|
||||
|
||||
mo_none = self.create_mo(self.mo_none_tmpl, self.DEFAULT_AVAILABLE_TRIGGERS_COUNT)
|
||||
mo_serial = self.create_mo(self.mo_serial_tmpl, self.SERIAL_AVAILABLE_TRIGGERS_COUNT)
|
||||
mo_lot = self.create_mo(self.mo_lot_tmpl, self.DEFAULT_AVAILABLE_TRIGGERS_COUNT)
|
||||
|
||||
mo_all = mo_none + mo_serial + mo_lot
|
||||
mo_all.action_confirm()
|
||||
|
||||
all_qty = 2 * self.DEFAULT_AVAILABLE_TRIGGERS_COUNT + self.SERIAL_AVAILABLE_TRIGGERS_COUNT
|
||||
|
||||
quant = self.create_quant(self.raw_none, 3 * all_qty)
|
||||
quant |= self.create_quant(self.raw_lot, 2 * all_qty)
|
||||
quant |= self.create_quant(self.raw_serial, 1 * all_qty)
|
||||
quant.action_apply_inventory()
|
||||
|
||||
# Quantities are fully reserved (stock.move state is available)
|
||||
mo_all.action_assign()
|
||||
for mov in mo_all.move_raw_ids:
|
||||
self.assertEqual(mov.product_qty, mov.reserved_availability, "Reserved quantity shall be equal to To Consume quantity.")
|
||||
|
||||
# Test for Serial Product
|
||||
self.executeConsumptionTriggers(mo_serial)
|
||||
self.executeConsumptionTriggers(mo_none)
|
||||
self.executeConsumptionTriggers(mo_lot)
|
||||
for mov in mo_all.move_raw_ids:
|
||||
if mov.has_tracking == 'none' or mov.raw_material_production_id.state == 'done':
|
||||
self.assertEqual(mov.product_qty, mov.quantity_done, "Done quantity shall be equal to To Consume quantity.")
|
||||
else:
|
||||
self.assertEqual(0, mov.quantity_done, "Done quantity shall be equal to 0.")
|
||||
|
||||
def test_option_enabled_and_qty_available(self):
|
||||
"""Option enabled, qty available
|
||||
-> Not Tracked components are fully consumed
|
||||
-> Tracked components are fully consumed
|
||||
"""
|
||||
|
||||
mo_none = self.create_mo(self.mo_none_tmpl, self.DEFAULT_AVAILABLE_TRIGGERS_COUNT)
|
||||
mo_serial = self.create_mo(self.mo_serial_tmpl, self.SERIAL_AVAILABLE_TRIGGERS_COUNT)
|
||||
mo_lot = self.create_mo(self.mo_lot_tmpl, self.DEFAULT_AVAILABLE_TRIGGERS_COUNT)
|
||||
|
||||
mo_all = mo_none + mo_serial + mo_lot
|
||||
mo_all.action_confirm()
|
||||
|
||||
all_qty = 2 * self.DEFAULT_AVAILABLE_TRIGGERS_COUNT + self.SERIAL_AVAILABLE_TRIGGERS_COUNT
|
||||
|
||||
quant = self.create_quant(self.raw_none, 3*all_qty)
|
||||
quant |= self.create_quant(self.raw_lot, 2*all_qty)
|
||||
quant |= self.create_quant(self.raw_serial, 1*all_qty)
|
||||
quant.action_apply_inventory()
|
||||
|
||||
# Quantities are fully reserved (stock.move state is available)
|
||||
mo_all.action_assign()
|
||||
for mov in mo_all.move_raw_ids:
|
||||
self.assertEqual(mov.product_qty, mov.reserved_availability, "Reserved quantity shall be equal to To Consume quantity.")
|
||||
|
||||
self.executeConsumptionTriggers(mo_serial)
|
||||
self.executeConsumptionTriggers(mo_none)
|
||||
self.executeConsumptionTriggers(mo_lot)
|
||||
for mov in mo_all.move_raw_ids:
|
||||
self.assertEqual(mov.product_qty, mov.quantity_done, "Done quantity shall be equal to To Consume quantity.")
|
||||
|
||||
def test_option_enabled_and_qty_not_available(self):
|
||||
"""Option enabled, qty not available
|
||||
-> Not Tracked components are fully consumed
|
||||
-> Tracked components are not consumed
|
||||
"""
|
||||
|
||||
mo_none = self.create_mo(self.mo_none_tmpl, self.DEFAULT_TRIGGERS_COUNT)
|
||||
mo_serial = self.create_mo(self.mo_serial_tmpl, self.SERIAL_TRIGGERS_COUNT)
|
||||
mo_lot = self.create_mo(self.mo_lot_tmpl, self.DEFAULT_TRIGGERS_COUNT)
|
||||
|
||||
mo_all = mo_none + mo_serial + mo_lot
|
||||
mo_all.action_confirm()
|
||||
|
||||
# Quantities are not reserved at all (stock.move state is confirmed)
|
||||
mo_all.action_assign()
|
||||
for mov in mo_all.move_raw_ids:
|
||||
self.assertEqual(0, mov.reserved_availability, "Reserved quantity shall be equal to 0.")
|
||||
|
||||
self.executeConsumptionTriggers(mo_serial)
|
||||
self.executeConsumptionTriggers(mo_none)
|
||||
self.executeConsumptionTriggers(mo_lot)
|
||||
|
||||
for mov in mo_all.move_raw_ids:
|
||||
if mov.has_tracking == 'none':
|
||||
self.assertEqual(mov.product_qty, mov.quantity_done, "Done quantity shall be equal to To Consume quantity.")
|
||||
else:
|
||||
self.assertEqual(0, mov.quantity_done, "Done quantity shall be equal to To Consume quantity.")
|
||||
|
||||
def test_option_enabled_and_qty_partially_available(self):
|
||||
"""Option enabled, qty partially available
|
||||
-> Not Tracked components are fully consumed
|
||||
-> Tracked components are partially consumed
|
||||
"""
|
||||
|
||||
# Update BoM serial component qty
|
||||
self.bom_none_lines[2].product_qty = 2
|
||||
self.bom_serial_lines[2].product_qty = 2
|
||||
self.bom_lot_lines[2].product_qty = 2
|
||||
|
||||
raw_none_qty = 2
|
||||
raw_tracked_qty = 1
|
||||
|
||||
quant = self.create_quant(self.raw_none, raw_none_qty)
|
||||
quant |= self.create_quant(self.raw_lot, raw_tracked_qty)
|
||||
quant |= self.create_quant(self.raw_serial, raw_tracked_qty)
|
||||
quant.action_apply_inventory()
|
||||
|
||||
# We must create & process each MO at once as we must assign quants for each individually
|
||||
def testUnit(mo_tmpl, serialTrigger=None):
|
||||
mo = self.create_mo(mo_tmpl, 1)
|
||||
mo.action_confirm()
|
||||
|
||||
# are partially reserved (stock.move state is partially_available)
|
||||
mo.action_assign()
|
||||
for mov in mo.move_raw_ids:
|
||||
if mov.has_tracking == "none":
|
||||
self.assertEqual(raw_none_qty, mov.reserved_availability, "Reserved quantity shall be equal to " + str(raw_none_qty)+ ".")
|
||||
else:
|
||||
self.assertEqual(raw_tracked_qty, mov.reserved_availability, "Reserved quantity shall be equal to " + str(raw_tracked_qty)+ ".")
|
||||
|
||||
if serialTrigger is None:
|
||||
self.executeConsumptionTriggers(mo)
|
||||
elif serialTrigger == 1:
|
||||
mo.qty_producing = mo.product_qty
|
||||
mo._onchange_producing()
|
||||
elif serialTrigger == 2:
|
||||
mo.action_generate_serial()
|
||||
|
||||
for mov in mo.move_raw_ids:
|
||||
if mov.has_tracking == "none":
|
||||
self.assertEqual(mov.product_qty, mov.quantity_done, "Done quantity shall be equal to To Consume quantity.")
|
||||
else:
|
||||
self.assertEqual(raw_tracked_qty, mov.quantity_done, "Done quantity shall be equal to " + str(raw_tracked_qty)+ ".")
|
||||
mo.action_cancel()
|
||||
|
||||
testUnit(self.mo_none_tmpl)
|
||||
testUnit(self.mo_lot_tmpl)
|
||||
testUnit(self.mo_serial_tmpl, 1)
|
||||
testUnit(self.mo_serial_tmpl, 2)
|
||||
@@ -1,157 +0,0 @@
|
||||
from odoo.tests import tagged
|
||||
from odoo.addons.mrp.tests.common_consume_tracked_component import TestConsumeTrackedComponentCommon
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestConsumeTrackedComponent(TestConsumeTrackedComponentCommon):
|
||||
|
||||
def test_option_disabled_and_qty_available(self):
|
||||
"""
|
||||
Option disabled, qty available
|
||||
-> Not Tracked components are fully consumed
|
||||
-> Tracked components are only consumed on button_mark_done trigger
|
||||
"""
|
||||
|
||||
self.picking_type.use_auto_consume_components_lots = False
|
||||
|
||||
mo_none = self.create_mo(self.mo_none_tmpl, self.DEFAULT_AVAILABLE_TRIGGERS_COUNT)
|
||||
mo_serial = self.create_mo(self.mo_serial_tmpl, self.SERIAL_AVAILABLE_TRIGGERS_COUNT)
|
||||
mo_lot = self.create_mo(self.mo_lot_tmpl, self.DEFAULT_AVAILABLE_TRIGGERS_COUNT)
|
||||
|
||||
mo_all = mo_none + mo_serial + mo_lot
|
||||
mo_all.action_confirm()
|
||||
|
||||
all_qty = 2 * self.DEFAULT_AVAILABLE_TRIGGERS_COUNT + self.SERIAL_AVAILABLE_TRIGGERS_COUNT
|
||||
|
||||
quant = self.create_quant(self.raw_none, 3*all_qty)
|
||||
quant |= self.create_quant(self.raw_lot, 2*all_qty)
|
||||
quant |= self.create_quant(self.raw_serial, 1*all_qty)
|
||||
quant.action_apply_inventory()
|
||||
|
||||
#Quantities are fully reserved (stock.move state is available)
|
||||
mo_all.action_assign()
|
||||
for mov in mo_all.move_raw_ids:
|
||||
self.assertEqual(mov.product_qty, mov.reserved_availability, "Reserved quantity shall be equal to To Consume quantity.")
|
||||
|
||||
#Test for Serial Product
|
||||
self.executeConsumptionTriggers(mo_serial)
|
||||
self.executeConsumptionTriggers(mo_none)
|
||||
self.executeConsumptionTriggers(mo_lot)
|
||||
for mov in mo_all.move_raw_ids:
|
||||
if mov.has_tracking == 'none' or mov.raw_material_production_id.state == 'done':
|
||||
self.assertEqual(mov.product_qty, mov.quantity_done, "Done quantity shall be equal to To Consume quantity.")
|
||||
else:
|
||||
self.assertEqual(0, mov.quantity_done, "Done quantity shall be equal to 0.")
|
||||
|
||||
def test_option_enabled_and_qty_available(self):
|
||||
"""
|
||||
Option enabled, qty available
|
||||
-> Not Tracked components are fully consumed
|
||||
-> Tracked components are fully consumed
|
||||
"""
|
||||
|
||||
mo_none = self.create_mo(self.mo_none_tmpl, self.DEFAULT_AVAILABLE_TRIGGERS_COUNT)
|
||||
mo_serial = self.create_mo(self.mo_serial_tmpl, self.SERIAL_AVAILABLE_TRIGGERS_COUNT)
|
||||
mo_lot = self.create_mo(self.mo_lot_tmpl, self.DEFAULT_AVAILABLE_TRIGGERS_COUNT)
|
||||
|
||||
mo_all = mo_none + mo_serial + mo_lot
|
||||
mo_all.action_confirm()
|
||||
|
||||
all_qty = 2 * self.DEFAULT_AVAILABLE_TRIGGERS_COUNT + self.SERIAL_AVAILABLE_TRIGGERS_COUNT
|
||||
|
||||
quant = self.create_quant(self.raw_none, 3*all_qty)
|
||||
quant |= self.create_quant(self.raw_lot, 2*all_qty)
|
||||
quant |= self.create_quant(self.raw_serial, 1*all_qty)
|
||||
quant.action_apply_inventory()
|
||||
|
||||
#Quantities are fully reserved (stock.move state is available)
|
||||
mo_all.action_assign()
|
||||
for mov in mo_all.move_raw_ids:
|
||||
self.assertEqual(mov.product_qty, mov.reserved_availability, "Reserved quantity shall be equal to To Consume quantity.")
|
||||
|
||||
self.executeConsumptionTriggers(mo_serial)
|
||||
self.executeConsumptionTriggers(mo_none)
|
||||
self.executeConsumptionTriggers(mo_lot)
|
||||
for mov in mo_all.move_raw_ids:
|
||||
self.assertEqual(mov.product_qty, mov.quantity_done, "Done quantity shall be equal to To Consume quantity.")
|
||||
|
||||
def test_option_enabled_and_qty_not_available(self):
|
||||
"""
|
||||
Option enabled, qty not available
|
||||
-> Not Tracked components are fully consumed
|
||||
-> Tracked components are not consumed
|
||||
"""
|
||||
|
||||
mo_none = self.create_mo(self.mo_none_tmpl, self.DEFAULT_TRIGGERS_COUNT)
|
||||
mo_serial = self.create_mo(self.mo_serial_tmpl, self.SERIAL_TRIGGERS_COUNT)
|
||||
mo_lot = self.create_mo(self.mo_lot_tmpl, self.DEFAULT_TRIGGERS_COUNT)
|
||||
|
||||
mo_all = mo_none + mo_serial + mo_lot
|
||||
mo_all.action_confirm()
|
||||
|
||||
#Quantities are not reserved at all (stock.move state is confirmed)
|
||||
mo_all.action_assign()
|
||||
for mov in mo_all.move_raw_ids:
|
||||
self.assertEqual(0, mov.reserved_availability, "Reserved quantity shall be equal to 0.")
|
||||
|
||||
self.executeConsumptionTriggers(mo_serial)
|
||||
self.executeConsumptionTriggers(mo_none)
|
||||
self.executeConsumptionTriggers(mo_lot)
|
||||
|
||||
for mov in mo_all.move_raw_ids:
|
||||
if mov.has_tracking == 'none':
|
||||
self.assertEqual(mov.product_qty, mov.quantity_done, "Done quantity shall be equal to To Consume quantity.")
|
||||
else:
|
||||
self.assertEqual(0, mov.quantity_done, "Done quantity shall be equal to To Consume quantity.")
|
||||
|
||||
def test_option_enabled_and_qty_partially_available(self):
|
||||
"""
|
||||
Option enabled, qty partially available
|
||||
-> Not Tracked components are fully consumed
|
||||
-> Tracked components are partially consumed
|
||||
"""
|
||||
|
||||
#update BoM serial component qty
|
||||
self.bom_none_lines[2].product_qty = 2
|
||||
self.bom_serial_lines[2].product_qty = 2
|
||||
self.bom_lot_lines[2].product_qty = 2
|
||||
|
||||
raw_none_qty = 2
|
||||
raw_tracked_qty = 1
|
||||
|
||||
quant = self.create_quant(self.raw_none, raw_none_qty)
|
||||
quant |= self.create_quant(self.raw_lot, raw_tracked_qty)
|
||||
quant |= self.create_quant(self.raw_serial, raw_tracked_qty)
|
||||
quant.action_apply_inventory()
|
||||
|
||||
#We must create & process each MO at once as we must assign quants for each individually
|
||||
def testUnit(mo_tmpl, serialTrigger=None):
|
||||
mo = self.create_mo(mo_tmpl, 1)
|
||||
mo.action_confirm()
|
||||
|
||||
#Quantities are partially reserved (stock.move state is partially_available)
|
||||
mo.action_assign()
|
||||
for mov in mo.move_raw_ids:
|
||||
if mov.has_tracking == "none":
|
||||
self.assertEqual(raw_none_qty, mov.reserved_availability, "Reserved quantity shall be equal to " + str(raw_none_qty)+ ".")
|
||||
else:
|
||||
self.assertEqual(raw_tracked_qty, mov.reserved_availability, "Reserved quantity shall be equal to " + str(raw_tracked_qty)+ ".")
|
||||
|
||||
if serialTrigger is None:
|
||||
self.executeConsumptionTriggers(mo)
|
||||
elif serialTrigger == 1:
|
||||
mo.qty_producing = mo.product_qty
|
||||
mo._onchange_producing()
|
||||
elif serialTrigger == 2:
|
||||
mo.action_generate_serial()
|
||||
|
||||
for mov in mo.move_raw_ids:
|
||||
if mov.has_tracking == "none":
|
||||
self.assertEqual(mov.product_qty, mov.quantity_done, "Done quantity shall be equal to To Consume quantity.")
|
||||
else:
|
||||
self.assertEqual(raw_tracked_qty, mov.quantity_done, "Done quantity shall be equal to " + str(raw_tracked_qty)+ ".")
|
||||
mo.action_cancel()
|
||||
|
||||
testUnit(self.mo_none_tmpl)
|
||||
testUnit(self.mo_lot_tmpl)
|
||||
testUnit(self.mo_serial_tmpl, 1)
|
||||
testUnit(self.mo_serial_tmpl, 2)
|
||||
Reference in New Issue
Block a user