19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:32:12 +01:00
parent 79f83631d5
commit 73afc09215
6267 changed files with 1534193 additions and 1130106 deletions

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_sale_mrp_flow

View file

@ -12,19 +12,20 @@ class TestMultistepManufacturing(TestMrpCommon):
super().setUpClass()
# Required for `uom_id ` to be visible in the view
cls.env.user.groups_id += cls.env.ref('uom.group_uom')
cls.env.user.group_ids += cls.env.ref('uom.group_uom')
# Required for `manufacture_steps` to be visible in the view
cls.env.user.groups_id += cls.env.ref('stock.group_adv_location')
cls.env.user.group_ids += cls.env.ref('stock.group_adv_location')
# Required for `product_id` to be visible in the view
cls.env.user.groups_id += cls.env.ref('product.group_product_variant')
cls._enable_variants()
cls.env.ref('stock.route_warehouse0_mto').active = True
cls.route_mto.active = True
cls.MrpProduction = cls.env['mrp.production']
# Create warehouse
warehouse_form = Form(cls.env['stock.warehouse'])
warehouse_form.name = 'Test'
warehouse_form.code = 'Test'
cls.warehouse = warehouse_form.save()
cls.warehouse.mto_pull_id.route_id.rule_ids.procure_method = "make_to_order"
cls.uom_unit = cls.env.ref('uom.product_uom_unit')
@ -32,7 +33,6 @@ class TestMultistepManufacturing(TestMrpCommon):
product_form = Form(cls.env['product.product'])
product_form.name = 'Stick'
product_form.uom_id = cls.uom_unit
product_form.uom_po_id = cls.uom_unit
product_form.route_ids.clear()
product_form.route_ids.add(cls.warehouse.manufacture_pull_id.route_id)
product_form.route_ids.add(cls.warehouse.mto_pull_id.route_id)
@ -42,12 +42,10 @@ class TestMultistepManufacturing(TestMrpCommon):
product_form = Form(cls.env['product.product'])
product_form.name = 'Raw Stick'
product_form.uom_id = cls.uom_unit
product_form.uom_po_id = cls.uom_unit
cls.product_raw = product_form.save()
# Create bom for manufactured product
bom_product_form = Form(cls.env['mrp.bom'])
bom_product_form.product_id = cls.product_manu
bom_product_form.product_tmpl_id = cls.product_manu.product_tmpl_id
bom_product_form.product_qty = 1.0
bom_product_form.type = 'normal'
@ -65,7 +63,6 @@ class TestMultistepManufacturing(TestMrpCommon):
line.name = cls.product_manu.name
line.product_id = cls.product_manu
line.product_uom_qty = 1.0
line.product_uom = cls.uom_unit
line.price_unit = 10.0
cls.sale_order = sale_form.save()
@ -120,7 +117,6 @@ class TestMultistepManufacturing(TestMrpCommon):
# New BoM for raw material product, it will generate another Production order i.e. child Production order
bom_product_form = Form(self.env['mrp.bom'])
bom_product_form.product_id = self.product_raw
bom_product_form.product_tmpl_id = self.product_raw.product_tmpl_id
bom_product_form.product_qty = 1.0
with bom_product_form.bom_line_ids.new() as bom_line:
@ -157,3 +153,56 @@ class TestMultistepManufacturing(TestMrpCommon):
self.assertEqual(self.sale_order.action_view_mrp_production()['res_id'], mo.id)
self.assertEqual(mo.action_view_sale_orders()['res_id'], self.sale_order.id)
def test_sales_order_with_mto_manufacturing(self):
self.route_mto.active = True
warehouse = self.warehouse_1
warehouse.manufacture_steps = 'pbm_sam'
prod1 = self.env['product.product'].create({
'name': 'elct1',
'type': 'consu',
'route_ids': [(6, 0, [
warehouse.manufacture_pull_id.route_id.id,
warehouse.mto_pull_id.route_id.id
])],
})
prod2 = self.env['product.product'].create({
'name': 'elct2',
'type': 'consu',
'route_ids': [(6, 0, [
warehouse.manufacture_pull_id.route_id.id,
warehouse.mto_pull_id.route_id.id
])],
})
partner = self.env['res.partner'].create({'name': 'Steve Buscemi'})
so = self.env['sale.order'].create({
'partner_id': partner.id,
'order_line': [(0, 0, {'product_id': prod1.id, 'product_uom_qty': 1}),
(0, 0, {'product_id': prod2.id, 'product_uom_qty': 1})],
'client_order_ref': 'Test Reference'
})
so.action_confirm()
def test_mto_cancel_3_steps_mo(self):
'''
In 3 step manufacturing, test that when the MO gets cancelled, the
delivery (to the client) can be made from stock.
'''
self.warehouse.manufacture_steps = 'pbm_sam'
self.sale_order.order_line.product_id.is_storable = True
self.env['stock.quant']._update_available_quantity(
self.sale_order.order_line.product_id,
self.sale_order.warehouse_id.lot_stock_id,
10
)
self.sale_order.action_confirm()
self.assertEqual(self.sale_order.picking_ids.state, 'waiting')
self.assertEqual(self.sale_order.picking_ids.move_ids.procure_method, 'make_to_order')
mo = self.sale_order.mrp_production_ids
self.assertTrue(mo)
self.assertEqual(self.sale_order.picking_ids.move_ids.move_orig_ids, mo.move_finished_ids)
mo.action_cancel()
self.assertEqual(self.sale_order.picking_ids.state, 'confirmed')
self.assertFalse(self.sale_order.picking_ids.move_ids.move_orig_ids)
self.sale_order.picking_ids.action_assign()
self.assertEqual(self.sale_order.picking_ids.move_ids.quantity, 1.0)

View file

@ -1,33 +1,33 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from unittest import skip
from odoo.fields import Command
from odoo.tests import Form, tagged
from odoo.addons.sale.tests.common import TestSaleCommon
from odoo.addons.stock_account.tests.test_anglo_saxon_valuation_reconciliation_common import ValuationReconciliationTestCommon
@tagged('post_install', '-at_install')
class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
@skip('Temporary to fast merge new valuation')
class TestSaleMRPAngloSaxonValuation(TestSaleCommon, ValuationReconciliationTestCommon):
@classmethod
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
def setUpClass(cls):
super().setUpClass()
cls.env.user.company_id.anglo_saxon_accounting = True
cls.uom_unit = cls.env.ref('uom.product_uom_unit')
def _create_product(self, name, product_type, price):
return self.env['product.product'].create({
'name': name,
'type': product_type,
'standard_price': price,
'categ_id': self.stock_account_product_categ.id if product_type == 'product' else self.env.ref('product.product_category_all').id,
})
@classmethod
def _create_product(cls, **create_vals):
if create_vals.get('is_storable'):
create_vals['categ_id'] = cls.stock_account_product_categ.id
return super()._create_product(**create_vals)
def test_sale_mrp_kit_bom_cogs(self):
"""Check invoice COGS aml after selling and delivering a product
with Kit BoM having another product with Kit BoM as component"""
# ----------------------------------------------
# BoM of Kit A:
# - BoM Type: Kit
@ -44,11 +44,12 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
# * 3 x Component BB (Cost: $5, Consumable)
# ----------------------------------------------
self.component_a = self._create_product('Component A', 'product', 3.00)
self.component_b = self._create_product('Component B', 'product', 4.00)
self.component_bb = self._create_product('Component BB', 'consu', 5.00)
self.kit_a = self._create_product('Kit A', 'product', 0.00)
self.kit_b = self._create_product('Kit B', 'consu', 0.00)
self.component_a = self._create_product(name='Component A', is_storable=True, standard_price=3.00)
self.component_b = self._create_product(name='Component B', is_storable=True, standard_price=4.00)
self.component_bb = self._create_product(name='Component BB', is_storable=False, standard_price=5.00)
self.kit_a = self._create_product(name='Kit A', is_storable=True, standard_price=0.00)
self.kit_b = self._create_product(name='Kit B', is_storable=False, standard_price=0.00)
pack_2 = self.env['uom.uom'].create({'name': 'Pack of 2', 'relative_factor': 2, 'relative_uom_id': self.kit_a.uom_id.id})
self.kit_a.write({
'property_account_expense_id': self.company_data['default_account_expense'].id,
@ -57,7 +58,6 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
# Create BoM for Kit A
bom_product_form = Form(self.env['mrp.bom'])
bom_product_form.product_id = self.kit_a
bom_product_form.product_tmpl_id = self.kit_a.product_tmpl_id
bom_product_form.product_qty = 3.0
bom_product_form.type = 'phantom'
@ -71,7 +71,6 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
# Create BoM for Kit B
bom_product_form = Form(self.env['mrp.bom'])
bom_product_form.product_id = self.kit_b
bom_product_form.product_tmpl_id = self.kit_b.product_tmpl_id
bom_product_form.product_qty = 10.0
bom_product_form.type = 'phantom'
@ -89,14 +88,14 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
(0, 0, {
'name': self.kit_a.name,
'product_id': self.kit_a.id,
'product_uom_qty': 1.0,
'product_uom': self.kit_a.uom_id.id,
'product_uom_qty': 0.5,
'product_uom_id': pack_2.id,
'price_unit': 1,
'tax_id': False,
'tax_ids': False,
})],
})
so.action_confirm()
so.picking_ids.move_ids.quantity_done = 1
so.picking_ids.move_ids.write({'quantity': 1, 'picked': True})
so.picking_ids.button_validate()
invoice = so.with_context(default_journal_id=self.company_data['default_journal_sale'].id)._create_invoices()
@ -126,7 +125,7 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
# Create Product template with variants
self.product_template = self.env['product.template'].create({
'name': 'Product Template',
'type': 'product',
'is_storable': True,
'uom_id': self.uom_unit.id,
'invoice_policy': 'delivery',
'categ_id': self.stock_account_product_categ.id,
@ -145,7 +144,7 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
def create_simple_bom_for_product(product, name, price):
component = self.env['product.product'].create({
'name': 'Component ' + name,
'type': 'product',
'is_storable': True,
'uom_id': self.uom_unit.id,
'categ_id': self.stock_account_product_categ.id,
'standard_price': price
@ -179,10 +178,8 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
'name': product.name,
'product_id': product.id,
'product_uom_qty': 2,
'product_uom': product.uom_id.id,
'price_unit': product.list_price
})],
'pricelist_id': self.env.ref('product.list0').id,
'company_id': self.company_data['company'].id,
}
so = self.env['sale.order'].create(so_vals)
@ -191,9 +188,7 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
# Deliver the three finished products
pick = so.picking_ids
# To check the products on the picking
wiz_act = pick.button_validate()
wiz = Form(self.env[wiz_act['res_model']].with_context(wiz_act['context'])).save()
wiz.process()
pick.button_validate()
# Create the invoice
so._create_invoices()
invoice = so.invoice_ids
@ -223,8 +218,8 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
"""
self.stock_account_product_categ.property_cost_method = 'fifo'
kit = self._create_product('Simple Kit', 'product', 0)
component = self._create_product('Compo A', 'product', 0)
kit = self._create_product(name='Simple Kit', is_storable=True, standard_price=0)
component = self._create_product(name='Compo A', is_storable=True, standard_price=0)
kit.property_account_expense_id = self.company_data['default_account_expense']
self.env['mrp.bom'].create({
@ -236,7 +231,6 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
# Receive 3 components: one @10, one @20 and one @60
in_moves = self.env['stock.move'].create([{
'name': 'IN move @%s' % p,
'product_id': component.id,
'location_id': self.env.ref('stock.stock_location_suppliers').id,
'location_dest_id': self.company_data['default_warehouse'].lot_stock_id.id,
@ -245,7 +239,7 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
'price_unit': p,
} for p in [10, 20, 60]])
in_moves._action_confirm()
in_moves.quantity_done = 1
in_moves.write({'quantity': 1, 'picked': True})
in_moves._action_done()
# Sell 3 kits
@ -256,9 +250,8 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
'name': kit.name,
'product_id': kit.id,
'product_uom_qty': 3.0,
'product_uom': kit.uom_id.id,
'price_unit': 100,
'tax_id': False,
'tax_ids': False,
})],
})
so.action_confirm()
@ -268,11 +261,10 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
picking = so.picking_ids
while picking:
pickings.append(picking)
picking.move_ids.quantity_done = 1
picking.move_ids.write({'quantity': 1, 'picked': True})
action = picking.button_validate()
if isinstance(action, dict):
wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
wizard.process()
Form.from_action(self.env, action).save().process()
picking = picking.backorder_ids
invoice = so._create_invoices()
@ -280,7 +272,6 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
# Receive one @100
in_moves = self.env['stock.move'].create({
'name': 'IN move @100',
'product_id': component.id,
'location_id': self.env.ref('stock.stock_location_suppliers').id,
'location_dest_id': self.company_data['default_warehouse'].lot_stock_id.id,
@ -289,24 +280,23 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
'price_unit': 100,
})
in_moves._action_confirm()
in_moves.quantity_done = 1
in_moves.write({'quantity': 1, 'picked': True})
in_moves._action_done()
# Return the second picking (i.e. one component @20)
ctx = {'active_id': pickings[1].id, 'active_model': 'stock.picking'}
return_wizard = Form(self.env['stock.return.picking'].with_context(ctx)).save()
return_picking_id, dummy = return_wizard._create_returns()
return_picking = self.env['stock.picking'].browse(return_picking_id)
return_picking.move_ids.quantity_done = 1
return_wizard.product_return_moves.quantity = 1
return_picking = return_wizard._create_return()
return_picking.move_ids.write({'quantity': 1, 'picked': True})
return_picking.button_validate()
# Add a credit note for the returned kit
ctx = {'active_model': 'account.move', 'active_ids': invoice.ids}
refund_wizard = self.env['account.move.reversal'].with_context(ctx).create({
'refund_method': 'refund',
'journal_id': invoice.journal_id.id,
})
action = refund_wizard.reverse_moves()
action = refund_wizard.refund_moves()
reverse_invoice = self.env['account.move'].browse(action['res_id'])
with Form(reverse_invoice) as reverse_invoice_form:
with reverse_invoice_form.invoice_line_ids.edit(0) as line:
@ -328,8 +318,8 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
"""
self.stock_account_product_categ.property_cost_method = 'fifo'
kit = self._create_product('Simple Kit', 'product', 0)
component = self._create_product('Compo A', 'product', 0)
kit = self._create_product(name='Simple Kit', is_storable=True, standard_price=0)
component = self._create_product(name='Compo A', is_storable=True, standard_price=0)
(kit + component).invoice_policy = 'delivery'
kit.property_account_expense_id = self.company_data['default_account_expense']
@ -342,7 +332,6 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
# Receive 3 components: one @10, one @20 and one @60
in_moves = self.env['stock.move'].create([{
'name': 'IN move @%s' % p,
'product_id': component.id,
'location_id': self.env.ref('stock.stock_location_suppliers').id,
'location_dest_id': self.company_data['default_warehouse'].lot_stock_id.id,
@ -351,7 +340,7 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
'price_unit': p,
} for p in [10, 20, 60]])
in_moves._action_confirm()
in_moves.quantity_done = 1
in_moves.write({'quantity': 1, 'picked': True})
in_moves._action_done()
# Sell 3 kits
@ -362,9 +351,8 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
'name': kit.name,
'product_id': kit.id,
'product_uom_qty': 3.0,
'product_uom': kit.uom_id.id,
'price_unit': 100,
'tax_id': False,
'tax_ids': False,
})],
})
so.action_confirm()
@ -374,11 +362,10 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
picking = so.picking_ids
while picking:
pickings.append(picking)
picking.move_ids.quantity_done = 1
picking.move_ids.write({'quantity': 1, 'picked': True})
action = picking.button_validate()
if isinstance(action, dict):
wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
wizard.process()
Form.from_action(self.env, action).save().process()
picking = picking.backorder_ids
invoice = so._create_invoices()
@ -386,7 +373,6 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
# Receive one @100
in_moves = self.env['stock.move'].create({
'name': 'IN move @100',
'product_id': component.id,
'location_id': self.env.ref('stock.stock_location_suppliers').id,
'location_dest_id': self.company_data['default_warehouse'].lot_stock_id.id,
@ -395,15 +381,15 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
'price_unit': 100,
})
in_moves._action_confirm()
in_moves.quantity_done = 1
in_moves.write({'quantity': 1, 'picked': True})
in_moves._action_done()
# Return the second picking (i.e. one component @20)
ctx = {'active_id': pickings[1].id, 'active_model': 'stock.picking'}
return_wizard = Form(self.env['stock.return.picking'].with_context(ctx)).save()
return_picking_id, dummy = return_wizard._create_returns()
return_picking = self.env['stock.picking'].browse(return_picking_id)
return_picking.move_ids.quantity_done = 1
return_wizard.product_return_moves.quantity = 1
return_picking = return_wizard._create_return()
return_picking.move_ids.write({'quantity': 1, 'picked': True})
return_picking.button_validate()
# Create a new invoice for the returned kit
@ -428,9 +414,9 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
def test_kit_avco_fully_owned_and_delivered_invoice_post_delivery(self):
self.stock_account_product_categ.property_cost_method = 'average'
compo01 = self._create_product('Compo 01', 'product', 10)
compo02 = self._create_product('Compo 02', 'product', 20)
kit = self._create_product('Kit', 'product', 0)
compo01 = self._create_product(name='Compo 01', is_storable=True, standard_price=10)
compo02 = self._create_product(name='Compo 02', is_storable=True, standard_price=20)
kit = self._create_product(name='Kit', is_storable=True, standard_price=0)
(compo01 + compo02 + kit).invoice_policy = 'delivery'
@ -456,13 +442,12 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
'name': kit.name,
'product_id': kit.id,
'product_uom_qty': 1.0,
'product_uom': kit.uom_id.id,
'price_unit': 5,
'tax_id': False,
'tax_ids': False,
})],
})
so.action_confirm()
so.picking_ids.move_ids.quantity_done = 1
so.picking_ids.move_ids.write({'quantity': 1, 'picked': True})
so.picking_ids.button_validate()
invoice = so._create_invoices()
@ -479,9 +464,9 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
def test_kit_avco_partially_owned_and_delivered_invoice_post_delivery(self):
self.stock_account_product_categ.property_cost_method = 'average'
compo01 = self._create_product('Compo 01', 'product', 10)
compo02 = self._create_product('Compo 02', 'product', 20)
kit = self._create_product('Kit', 'product', 0)
compo01 = self._create_product(name='Compo 01', is_storable=True, standard_price=10)
compo02 = self._create_product(name='Compo 02', is_storable=True, standard_price=20)
kit = self._create_product(name='Kit', is_storable=True, standard_price=0)
(compo01 + compo02 + kit).invoice_policy = 'delivery'
@ -509,13 +494,13 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
'name': kit.name,
'product_id': kit.id,
'product_uom_qty': 2.0,
'product_uom': kit.uom_id.id,
'price_unit': 5,
'tax_id': False,
'tax_ids': False,
})],
})
so.action_confirm()
so.picking_ids.move_line_ids.qty_done = 1
so.picking_ids.move_line_ids.quantity = 1
so.picking_ids.move_ids.picked = True
so.picking_ids.button_validate()
invoice = so._create_invoices()
@ -557,68 +542,63 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
# * 2 x Component B (Cost: $6, Storable)
# ----------------------------------------------
self.component_a = self._create_product('Component A', 'product', 10.00)
self.component_b = self._create_product('Component B', 'product', 6.00)
self.subkit_a = self._create_product('Subkit A', 'product', 0.00)
self.subkit_b = self._create_product('Subkit B', 'product', 0.00)
self.main_kit = self._create_product('Main kit', 'product', 0.00)
component_a = self._create_product(name='Component A', is_storable=True, standard_price=10.00)
component_b = self._create_product(name='Component B', is_storable=True, standard_price=6.00)
subkit_a = self._create_product(name='Subkit A', is_storable=True, standard_price=0.00)
subkit_b = self._create_product(name='Subkit B', is_storable=True, standard_price=0.00)
main_kit = self._create_product(name='Main kit', is_storable=True, standard_price=0.00)
self.main_kit.write({
main_kit.write({
'property_account_expense_id': self.company_data['default_account_expense'].id,
'property_account_income_id': self.company_data['default_account_revenue'].id,
})
# Create BoM for Main kit
bom_product_form = Form(self.env['mrp.bom'])
bom_product_form.product_id = self.main_kit
bom_product_form.product_tmpl_id = self.main_kit.product_tmpl_id
bom_product_form.product_qty = 4.0
bom_product_form.type = 'phantom'
with bom_product_form.bom_line_ids.new() as bom_line:
bom_line.product_id = self.subkit_a
bom_line.product_qty = 1.0
with bom_product_form.bom_line_ids.new() as bom_line:
bom_line.product_id = self.subkit_b
bom_line.product_qty = 1.0
self.bom_main = bom_product_form.save()
self.env['mrp.bom'].create({
'product_id': main_kit.id,
'product_tmpl_id': main_kit.product_tmpl_id.id,
'product_qty': 4.0,
'type': 'phantom',
'bom_line_ids': [
(0, 0, {'product_id': subkit_a.id, 'product_qty': 1.0}),
(0, 0, {'product_id': subkit_b.id, 'product_qty': 1.0}),
],
})
# Create BoM for Subkit A
bom_product_form = Form(self.env['mrp.bom'])
bom_product_form.product_id = self.subkit_a
bom_product_form.product_tmpl_id = self.subkit_a.product_tmpl_id
bom_product_form.product_qty = 1.0
bom_product_form.type = 'phantom'
with bom_product_form.bom_line_ids.new() as bom_line:
bom_line.product_id = self.component_a
bom_line.product_qty = 2.0
self.bom_sub_b = bom_product_form.save()
self.env['mrp.bom'].create({
'product_id': subkit_a.id,
'product_tmpl_id': subkit_a.product_tmpl_id.id,
'product_qty': 1.0,
'type': 'phantom',
'bom_line_ids': [
(0, 0, {'product_id': component_a.id, 'product_qty': 2.0}),
],
})
# Create BoM for Subkit B
bom_product_form = Form(self.env['mrp.bom'])
bom_product_form.product_id = self.subkit_b
bom_product_form.product_tmpl_id = self.subkit_b.product_tmpl_id
bom_product_form.product_qty = 1.0
bom_product_form.type = 'phantom'
with bom_product_form.bom_line_ids.new() as bom_line:
bom_line.product_id = self.component_b
bom_line.product_qty = 2.0
self.bom_sub_b = bom_product_form.save()
self.env['mrp.bom'].create({
'product_id': subkit_b.id,
'product_tmpl_id': subkit_b.product_tmpl_id.id,
'product_qty': 1.0,
'type': 'phantom',
'bom_line_ids': [
(0, 0, {'product_id': component_b.id, 'product_qty': 2.0}),
],
})
so = self.env['sale.order'].create({
'partner_id': self.partner_a.id,
'order_line': [
(0, 0, {
'name': self.main_kit.name,
'product_id': self.main_kit.id,
'name': main_kit.name,
'product_id': main_kit.id,
'product_uom_qty': 1.0,
'product_uom': self.main_kit.uom_id.id,
'price_unit': 1,
'tax_id': False,
'tax_ids': False,
})],
})
so.action_confirm()
for move in so.picking_ids.move_ids:
move.quantity_done = move.product_qty
move.quantity = move.product_uom_qty
so.picking_ids.button_validate()
invoice = so.with_context(default_journal_id=self.company_data['default_journal_sale'].id)._create_invoices()
@ -633,3 +613,59 @@ class TestSaleMRPAngloSaxonValuation(ValuationReconciliationTestCommon):
cogs_aml = amls.filtered(lambda aml: aml.account_id == self.company_data['default_account_expense'])
self.assertAlmostEqual(cogs_aml.debit, 8.00, msg="Should include include the components from all subkits, with the price adapted for 1 Main kit")
self.assertEqual(cogs_aml.credit, 0)
def test_sell_kit_invoice_before_delivery(self):
""" When a kit product is invoiced prior to delivery, we want to make sure to reconcile all
the AMLs from its explosion together, else we risk re-reconciliation attempts (which will
block certain actions from being performed altogether).
"""
self.stock_account_product_categ.property_cost_method = 'average'
compo01 = self._create_product(name="Compo 01", is_storable=True, standard_price=10)
compo02 = self._create_product(name="Compo 02", is_storable=True, standard_price=20)
kit = self._create_product(name="Kit", is_storable=True, standard_price=30)
(compo01 + compo02 + kit).write({'invoice_policy': 'order'})
warehouse = self.company_data['default_warehouse']
self.env['stock.quant']._update_available_quantity(compo01, warehouse.lot_stock_id, 1.0)
self.env['stock.quant']._update_available_quantity(compo02, warehouse.lot_stock_id, 2.0)
self.env['mrp.bom'].create({
'type': 'phantom',
'product_id': kit.id,
'product_tmpl_id': kit.product_tmpl_id.id,
'product_qty': 1,
'bom_line_ids': [
Command.create({'product_id': compo01.id, 'product_qty': 1}),
Command.create({'product_id': compo02.id, 'product_qty': 1}),
],
})
sale_order = self.env['sale.order'].create({
'partner_id': self.partner_a.id,
'order_line': [
Command.create({
'product_id': kit.id,
'product_uom_qty': 1,
'price_unit': 10,
}),
Command.create({
'product_id': compo02.id,
'product_uom_qty': 1,
'price_unit': 5,
}),
],
})
sale_order.action_confirm()
invoice = sale_order.with_context(default_journal_id=self.company_data['default_journal_sale'].id)._create_invoices()
invoice.action_post()
delivery = sale_order.picking_ids
# would fail due to attempted re-reconciliation prior to this commit
delivery.button_validate()
stock_output_amls = self.env['account.move.line'].search([('account_id', '=', self.company_data['default_account_stock_out'].id)], order='id asc')
self.assertRecordValues(stock_output_amls,
[
{'product_id': kit.id, 'reconciled': True, 'debit': 0.0, 'credit': 30.0},
{'product_id': compo02.id, 'reconciled': True, 'debit': 0.0, 'credit': 20.0},
{'product_id': compo01.id, 'reconciled': True, 'debit': 10.0, 'credit': 0.0},
{'product_id': compo02.id, 'reconciled': True, 'debit': 20.0, 'credit': 0.0},
{'product_id': compo02.id, 'reconciled': True, 'debit': 20.0, 'credit': 0.0},
]
)

View file

@ -1,16 +1,28 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests.common import TransactionCase, Form, tagged
from unittest import skip
from odoo.tests import Form, tagged
from odoo import Command
from odoo.addons.base.tests.common import BaseCommon
@tagged('post_install', '-at_install')
class TestSaleMrpKitBom(TransactionCase):
class TestSaleMrpKitBom(BaseCommon):
def _create_product(self, name, product_type, price):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env.ref('base.user_admin').write({
'email': 'mitchell.admin@example.com',
})
cls.env.user.group_ids += cls.quick_ref('product.group_product_variant')
def _create_product(self, name, storable, price):
return self.env['product.product'].create({
'name': name,
'type': product_type,
'is_storable': storable,
'standard_price': price,
})
@ -77,6 +89,7 @@ class TestSaleMrpKitBom(TransactionCase):
# The actual test, there should be no traceback here
order_line_change.product_id = product_variant_ids[1]
@skip('Temporary to fast merge new valuation')
def test_sale_mrp_kit_cost(self):
"""
Check the total cost of a KIT:
@ -92,11 +105,11 @@ class TestSaleMrpKitBom(TransactionCase):
'name': 'customer'
})
self.kit_product = self._create_product('Kit Product', 'product', 1.00)
self.kit_product = self._create_product('Kit Product', True, 1.00)
# Creating components
self.component_a = self._create_product('Component A', 'product', 1.00)
self.component_a = self._create_product('Component A', True, 1.00)
self.component_a.product_tmpl_id.standard_price = 6
self.component_b = self._create_product('Component B', 'product', 1.00)
self.component_b = self._create_product('Component B', True, 1.00)
self.component_b.product_tmpl_id.standard_price = 10
cat = self.env['product.category'].create({
@ -134,25 +147,94 @@ class TestSaleMrpKitBom(TransactionCase):
'name': self.kit_product.name,
'product_id': self.kit_product.id,
'product_uom_qty': 1.0,
'product_uom': self.kit_product.uom_id.id,
})],
})
so.action_confirm()
line = so.order_line
purchase_price = line.product_id.with_company(line.company_id)._compute_average_price(0, line.product_uom_qty, line.move_ids)
self.assertEqual(line.move_ids.mapped('description_picking'), ['Kit Product - 1/2', 'Kit Product - 2/2'])
self.assertEqual(purchase_price, 92, "The purchase price must be the total cost of the components multiplied by their unit of measure")
def test_sale_mrp_kit_sale_price(self):
"""Check the total sale price of a KIT:
# BoM of Kit A:
# - BoM Type: Kit
# - Quantity: 1
# - Components:
# * 1 x Component A (Price: $ 8, QTY: 10, UOM: Meter)
# * 1 x Component B (Price: $ 5, QTY: 2, UOM: Dozen)
# sale price of Kit A = (8 * 10) + (5 * 2 * 12) = $ 200
"""
if "sale_price" not in self.env["stock.move.line"]._fields:
self.skipTest("This test only runs with both sale_mrp and stock_delivery installed")
self.customer = self.env['res.partner'].create({
'name': 'customer',
})
self.warehouse = self.env["stock.warehouse"].create({
'name': 'Warehouse #2',
'code': 'WH02',
})
self.kit_product = self._create_product('Kit Product', 'product', 1.00)
# Creating components
self.component_a = self._create_product('Component A', 'product', 1.00)
self.component_a.uom_id = self.env.ref('uom.product_uom_meter').id
self.component_a.product_tmpl_id.list_price = 8
self.component_b = self._create_product('Component B', 'product', 1.00)
self.component_b.product_tmpl_id.list_price = 5
location_id = self.warehouse.lot_stock_id.id
self.env["stock.quant"].with_context(inventory_mode=True).create([
{"product_id": self.component_a.id, "inventory_quantity": 10, "location_id": location_id},
{"product_id": self.component_b.id, "inventory_quantity": 24, "location_id": location_id},
]).action_apply_inventory()
self.bom = self.env['mrp.bom'].create({
'product_tmpl_id': self.kit_product.product_tmpl_id.id,
'product_qty': 1.0,
'type': 'phantom',
'bom_line_ids': [
Command.create({
'product_id': self.component_a.id,
'product_qty': 10.0,
'product_uom_id': self.env.ref('uom.product_uom_meter').id,
}),
Command.create({
'product_id': self.component_b.id,
'product_qty': 2.0,
'product_uom_id': self.env.ref('uom.product_uom_dozen').id,
}),
]
})
# Create a SO with one unit of the kit product
so = self.env['sale.order'].create({
'partner_id': self.customer.id,
'order_line': [
(0, 0, {
'name': self.kit_product.name,
'product_id': self.kit_product.id,
'product_uom_qty': 1.0,
'product_uom_id': self.kit_product.uom_id.id,
})],
'warehouse_id': self.warehouse.id,
})
so.action_confirm()
so.picking_ids._action_done()
move_lines = so.picking_ids.move_ids.move_line_ids
self.assertEqual(move_lines.mapped("sale_price"), [80, 120], 'wrong shipping value')
def test_qty_delivered_with_bom(self):
"""Check the quantity delivered, when a bom line has a non integer quantity"""
self.env.ref('product.decimal_product_uom').digits = 5
self.env.ref('uom.decimal_product_uom').digits = 5
self.kit = self._create_product('Kit', 'product', 0.00)
self.comp = self._create_product('Component', 'product', 0.00)
self.kit = self._create_product('Kit', True, 0.00)
self.comp = self._create_product('Component', True, 0.00)
# Create BoM for Kit
bom_product_form = Form(self.env['mrp.bom'])
bom_product_form.product_id = self.kit
bom_product_form.product_tmpl_id = self.kit.product_tmpl_id
bom_product_form.product_qty = 1.0
bom_product_form.type = 'phantom'
@ -173,9 +255,8 @@ class TestSaleMrpKitBom(TransactionCase):
'name': self.kit.name,
'product_id': self.kit.id,
'product_uom_qty': 10.0,
'product_uom': self.kit.uom_id.id,
'price_unit': 1,
'tax_id': False,
'tax_ids': False,
})],
})
so.action_confirm()
@ -184,7 +265,7 @@ class TestSaleMrpKitBom(TransactionCase):
self.assertEqual(so.order_line.qty_delivered, 0)
picking = so.picking_ids
picking.move_ids.quantity_done = 0.86000
picking.move_ids.write({'quantity': 0.86000, 'picked': True})
picking.button_validate()
# Checks the delivery amount (must be 10).
@ -194,14 +275,13 @@ class TestSaleMrpKitBom(TransactionCase):
"""Check the quantity delivered, when one product is a kit
and his bom uses another product that is also a kit"""
self.kitA = self._create_product('Kit A', 'consu', 0.00)
self.kitB = self._create_product('Kit B', 'consu', 0.00)
self.compA = self._create_product('ComponentA', 'consu', 0.00)
self.compB = self._create_product('ComponentB', 'consu', 0.00)
self.kitA = self._create_product('Kit A', False, 0.00)
self.kitB = self._create_product('Kit B', False, 0.00)
self.compA = self._create_product('ComponentA', False, 0.00)
self.compB = self._create_product('ComponentB', False, 0.00)
# Create BoM for KitB
bom_product_formA = Form(self.env['mrp.bom'])
bom_product_formA.product_id = self.kitB
bom_product_formA.product_tmpl_id = self.kitB.product_tmpl_id
bom_product_formA.product_qty = 1.0
bom_product_formA.type = 'phantom'
@ -215,7 +295,6 @@ class TestSaleMrpKitBom(TransactionCase):
# Create BoM for KitA
bom_product_formB = Form(self.env['mrp.bom'])
bom_product_formB.product_id = self.kitA
bom_product_formB.product_tmpl_id = self.kitA.product_tmpl_id
bom_product_formB.product_qty = 1.0
bom_product_formB.type = 'phantom'
@ -238,9 +317,8 @@ class TestSaleMrpKitBom(TransactionCase):
'name': self.kitA.name,
'product_id': self.kitA.id,
'product_uom_qty': 1.0,
'product_uom': self.kitA.uom_id.id,
'price_unit': 1,
'tax_id': False,
'tax_ids': False,
})],
})
so.action_confirm()
@ -249,9 +327,7 @@ class TestSaleMrpKitBom(TransactionCase):
self.assertEqual(so.order_line.qty_delivered, 0)
picking = so.picking_ids
action = picking.button_validate()
wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
wizard.process()
picking.button_validate()
# Checks the delivery amount (must be 1).
self.assertEqual(so.order_line.qty_delivered, 1)
@ -265,13 +341,12 @@ class TestSaleMrpKitBom(TransactionCase):
wh = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1)
wh.write({'delivery_steps': 'pick_ship'})
kitA = self._create_product('Kit Product', 'product', 0.00)
compA = self._create_product('ComponentA', 'product', 0.00)
compB = self._create_product('ComponentB', 'product', 0.00)
kitA = self._create_product('Kit Product', True, 0.00)
compA = self._create_product('ComponentA', True, 0.00)
compB = self._create_product('ComponentB', True, 0.00)
# Create BoM for KitB
bom_product_formA = Form(self.env['mrp.bom'])
bom_product_formA.product_id = kitA
bom_product_formA.product_tmpl_id = kitA.product_tmpl_id
bom_product_formA.product_qty = 1.0
bom_product_formA.type = 'phantom'
@ -294,20 +369,21 @@ class TestSaleMrpKitBom(TransactionCase):
'name': kitA.name,
'product_id': kitA.id,
'product_uom_qty': 1.0,
'product_uom': kitA.uom_id.id,
'price_unit': 1,
'tax_id': False,
'tax_ids': False,
})]
})
so.action_confirm()
pick = so.picking_ids[0]
ship = so.picking_ids[1]
self.assertTrue(pick.move_ids[0].bom_line_id, "All component from kits should have a bom line")
self.assertTrue(pick.move_ids[1].bom_line_id, "All component from kits should have a bom line")
pick.move_ids.write({'quantity': 1, 'picked': True})
pick.button_validate()
self.assertTrue(pick.move_ids_without_package[0].bom_line_id, "All component from kits should have a bom line")
self.assertTrue(pick.move_ids_without_package[1].bom_line_id, "All component from kits should have a bom line")
self.assertTrue(ship.move_ids_without_package[0].bom_line_id, "All component from kits should have a bom line")
self.assertTrue(ship.move_ids_without_package[1].bom_line_id, "All component from kits should have a bom line")
ship = so.picking_ids[1]
self.assertTrue(ship.move_ids[0].bom_line_id, "All component from kits should have a bom line")
self.assertTrue(ship.move_ids[1].bom_line_id, "All component from kits should have a bom line")
def test_qty_delivered_with_bom_using_kit2(self):
"""Create 2 kits products that have common components and activate 2 steps delivery
@ -319,15 +395,14 @@ class TestSaleMrpKitBom(TransactionCase):
wh = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1)
wh.write({'delivery_steps': 'pick_ship'})
kitAB = self._create_product('Kit AB', 'product', 0.00)
kitABC = self._create_product('Kit ABC', 'product', 0.00)
compA = self._create_product('ComponentA', 'product', 0.00)
compB = self._create_product('ComponentB', 'product', 0.00)
compC = self._create_product('ComponentC', 'product', 0.00)
kitAB = self._create_product('Kit AB', True, 0.00)
kitABC = self._create_product('Kit ABC', True, 0.00)
compA = self._create_product('ComponentA', True, 0.00)
compB = self._create_product('ComponentB', True, 0.00)
compC = self._create_product('ComponentC', True, 0.00)
# Create BoM for KitB
bom_product_formA = Form(self.env['mrp.bom'])
bom_product_formA.product_id = kitAB
bom_product_formA.product_tmpl_id = kitAB.product_tmpl_id
bom_product_formA.product_qty = 1.0
bom_product_formA.type = 'phantom'
@ -341,7 +416,6 @@ class TestSaleMrpKitBom(TransactionCase):
# Create BoM for KitA
bom_product_formB = Form(self.env['mrp.bom'])
bom_product_formB.product_id = kitABC
bom_product_formB.product_tmpl_id = kitABC.product_tmpl_id
bom_product_formB.product_qty = 1.0
bom_product_formB.type = 'phantom'
@ -367,35 +441,30 @@ class TestSaleMrpKitBom(TransactionCase):
'name': kitAB.name,
'product_id': kitAB.id,
'product_uom_qty': 1.0,
'product_uom': kitAB.uom_id.id,
'price_unit': 1,
'tax_id': False,
'tax_ids': False,
}),
(0, 0, {
'name': kitABC.name,
'product_id': kitABC.id,
'product_uom_qty': 1.0,
'product_uom': kitABC.uom_id.id,
'price_unit': 1,
'tax_id': False,
'tax_ids': False,
})],
})
so.action_confirm()
pick = so.picking_ids[0]
ship = so.picking_ids[1]
for move in pick.move_ids:
move.quantity_done = 1
move.write({'quantity': 1, 'picked': True})
pick.action_put_in_pack()
pick.button_validate()
ship.package_level_ids.write({'is_done': True})
ship.package_level_ids._set_is_done()
ship = so.picking_ids[1]
for move_line in ship.move_line_ids:
self.assertEqual(move_line.move_id.product_uom_qty, move_line.qty_done, "Quantity done should be equal to the quantity reserved in the move line")
self.assertEqual(move_line.move_id.product_uom_qty, move_line.quantity, "Quantity done should be equal to the quantity reserved in the move line")
def test_kit_in_delivery_slip(self):
"""
@ -420,7 +489,7 @@ class TestSaleMrpKitBom(TransactionCase):
"""
kit_1, component_1, product_1, kit_3, kit_4 = self.env['product.product'].create([{
'name': n,
'type': 'product',
'is_storable': True,
} for n in ['Kit 1', 'Compo 1', 'Product 1', 'Kit 3', 'Kit 4']])
kit_1.description_sale = "test"
@ -510,8 +579,8 @@ class TestSaleMrpKitBom(TransactionCase):
})
so.action_confirm()
picking = so.picking_ids
self.assertEqual(len(so.picking_ids.move_ids_without_package), 7)
picking.move_ids.quantity_done = 1
self.assertEqual(len(so.picking_ids.move_ids), 7)
picking.move_ids.write({'quantity': 1, 'picked': True})
picking.button_validate()
self.assertEqual(picking.state, 'done')
@ -527,3 +596,257 @@ class TestSaleMrpKitBom(TransactionCase):
if keys[0] in line:
keys = keys[1:]
self.assertFalse(keys, "All keys should be in the report with the defined order")
def test_sale_multistep_kit_qty_change(self):
warehouse = self.env['stock.warehouse'].search([], limit=1)
warehouse.write({'delivery_steps': 'pick_ship'})
self.partner = self.env['res.partner'].create({'name': 'Test Partner'})
kit_prod = self._create_product('kit_prod', 'product', 0.00)
sub_kit = self._create_product('sub_kit', 'product', 0.00)
component = self._create_product('component', 'product', 0.00)
component.uom_id = self.env.ref('uom.product_uom_dozen')
self.env['stock.quant']._update_available_quantity(component, warehouse.lot_stock_id, 30)
# 6 kit_prod == 5 component
self.env['mrp.bom'].create([{ # 2 kit_prod == 5 sub_kit
'product_tmpl_id': kit_prod.product_tmpl_id.id,
'product_qty': 2.0,
'type': 'phantom',
'bom_line_ids': [(0, 0, {
'product_id': sub_kit.id,
'product_qty': 5,
})],
}, { # 3 sub_kit == 1 component
'product_tmpl_id': sub_kit.product_tmpl_id.id,
'product_qty': 3.0,
'type': 'phantom',
'bom_line_ids': [(0, 0, {
'product_id': component.id,
'product_qty': 1,
})],
}])
so = self.env['sale.order'].create({
'partner_id': self.partner.id,
'order_line': [(0, 0, {
'name': kit_prod.name,
'product_id': kit_prod.id,
'product_uom_qty': 30,
})],
})
# Validate the SO
so.action_confirm()
picking_pick = so.picking_ids[0]
picking_pick.picking_type_id.create_backorder = 'never'
# Check the component qty in the created picking should be 25
self.assertEqual(picking_pick.move_ids.product_qty, 30 * 5 / 6)
# Update the kit quantity in the SO
so.order_line[0].product_uom_qty = 60
# Check the component qty after the update should be 50
self.assertEqual(picking_pick.move_ids.product_qty, 60 * 5 / 6)
# Deliver half the quantity 25 component == 30 kit_prod
picking_pick.move_ids.quantity = 25
picking_pick.button_validate()
picking_ship = so.picking_ids[1]
picking_ship.picking_type_id.create_backorder = 'never'
picking_ship.move_ids.quantity = 25
picking_ship.button_validate()
self.assertEqual(so.order_line.qty_delivered, 25 / 5 * 6)
# Return 10 components
stock_return_picking_form = Form(self.env['stock.return.picking']
.with_context(active_ids=picking_ship.ids, active_id=picking_ship.id,
active_model='stock.picking'))
return_wiz = stock_return_picking_form.save()
for return_move in return_wiz.product_return_moves:
return_move.write({
'quantity': 10,
'to_refund': True
})
res = return_wiz.action_create_returns()
return_pick = self.env['stock.picking'].browse(res['res_id'])
# Process all components and validate the return
return_pick.button_validate()
self.assertEqual(so.order_line.qty_delivered, 15 / 5 * 6)
# Resend 5 components
stock_return_picking_form = Form(self.env['stock.return.picking']
.with_context(active_ids=return_pick.ids, active_id=return_pick.id,
active_model='stock.picking'))
return_wiz = stock_return_picking_form.save()
for return_move in return_wiz.product_return_moves:
return_move.write({
'quantity': 5,
'to_refund': True
})
res = return_wiz.action_create_returns()
# Validate the return
self.env['stock.picking'].browse(res['res_id']).button_validate()
self.assertEqual(so.order_line.qty_delivered, 20 / 5 * 6)
def test_sale_kit_qty_change(self):
# Create record rule
mrp_bom_model = self.env['ir.model']._get('mrp.bom')
self.env['ir.rule'].create({
'name': "No one allowed to access BoMs",
'model_id': mrp_bom_model.id,
'domain_force': [(0, '=', 1)],
})
# Create BoM
kit_product = self._create_product('Kit Product', 'product', 1)
component_a = self._create_product('Component A', 'product', 1)
self.env['mrp.bom'].create({
'product_id': kit_product.id,
'product_tmpl_id': kit_product.product_tmpl_id.id,
'product_qty': 1,
'consumption': 'flexible',
'type': 'phantom',
'bom_line_ids': [(0, 0, {'product_id': component_a.id, 'product_qty': 1})]
})
# Create sale order
partner = self.env['res.partner'].create({'name': 'Testing Man'})
so = self.env['sale.order'].create({
'partner_id': partner.id,
})
sol = self.env['sale.order.line'].create({
'name': "Order line",
'product_id': kit_product.id,
'order_id': so.id,
})
so.action_confirm()
user_admin = self.env['res.users'].search([('login', '=', 'admin')])
sol.with_user(user_admin).write({'product_uom_qty': 5})
self.assertEqual(sum(sol.move_ids.mapped('product_uom_qty')), 5)
def test_sale_kit_with_mto_components_qty_change(self):
"""
Check that updating the demand on a sale order line for a kit product
updates the associated deliveries accordingly
"""
partner = self.env['res.partner'].create({'name': 'Test Partner'})
warehouse = self.env.ref('stock.warehouse0')
mto_route = self.env.ref('stock.route_warehouse0_mto')
mto_route.action_unarchive()
manufacturing_route_id = self.ref('mrp.route_warehouse0_manufacture')
kit_product, comp, mto_comp, subcomp = self.env['product.product'].create([
{
'name': 'kit_product',
'is_storable': True,
'route_ids': [],
},
{
'name': 'component',
'is_storable': True,
'route_ids': [],
},
{
'name': 'mto_component',
'is_storable': True,
'route_ids': [Command.set([mto_route.id, manufacturing_route_id])],
},
{
'name': 'subcomponent',
'is_storable': True,
'route_ids': [],
},
])
self.env['stock.quant']._update_available_quantity(comp, warehouse.lot_stock_id, 30.0)
self.env['mrp.bom'].create([
{ # 2 kit_prod -> 5 comp and 3 mto_comp
'product_tmpl_id': kit_product.product_tmpl_id.id,
'product_qty': 2.0,
'type': 'phantom',
'bom_line_ids': [
Command.create({'product_id': comp.id, 'product_qty': 5}),
Command.create({'product_id': mto_comp.id, 'product_qty': 3}),
],
},
{ # bom to manufacture mto_comp
'product_tmpl_id': mto_comp.product_tmpl_id.id,
'product_qty': 1.0,
'bom_line_ids': [
Command.create({'product_id': subcomp.id, 'product_qty': 1}),
],
}
])
so = self.env['sale.order'].create({
'partner_id': partner.id,
'order_line': [
Command.create({
'name': kit_product.name,
'product_id': kit_product.id,
'product_uom_qty': 4,
})],
})
# confirm the SO and check the delivery
so.action_confirm()
self.assertRecordValues(so.picking_ids.move_ids.sorted('product_uom_qty'), [
{'product_id': mto_comp.id, 'product_uom_qty': 6.0},
{'product_id': comp.id, 'product_uom_qty': 10.0},
])
with Form(so) as so_form:
with so_form.order_line.edit(0) as line_form:
line_form.product_uom_qty = 10
# the moves assocaited to the mto component are expected to be separated as
# the are linked to a different MO
self.assertRecordValues(so.picking_ids.move_ids.sorted('product_uom_qty'), [
{'product_id': mto_comp.id, 'product_uom_qty': 6.0},
{'product_id': mto_comp.id, 'product_uom_qty': 9.0},
{'product_id': comp.id, 'product_uom_qty': 25.0},
])
def test_inter_company_qty_delivered_with_kit(self):
"""
Test that the delivered quantity is updated on a sale order line when selling a kit
through an inter-company transaction.
"""
self.env.user.write({'group_ids': [(4, self.env.ref('base.group_multi_company').id)]})
# Create the kit product and BoM
kit_product = self._create_product('Kit', 'product', 1)
component_product = self._create_product('Component', 'product', 1)
self.env['mrp.bom'].create({
'product_id': kit_product.id,
'product_tmpl_id': kit_product.product_tmpl_id.id,
'product_qty': 1,
'consumption': 'flexible',
'type': 'phantom',
'bom_line_ids': [(0, 0, {'product_id': component_product.id, 'product_qty': 1})]
})
# Create the sale order with a partner that uses the inter company location
inter_comp_location = self.env.ref('stock.stock_location_inter_company')
partner = self.env['res.partner'].create({'name': 'Testing Partner'})
partner.property_stock_customer = inter_comp_location
partner.property_stock_supplier = inter_comp_location
so = self.env['sale.order'].create({
'partner_id': partner.id,
'order_line': [
(0, 0, {
'name': kit_product.name,
'product_id': kit_product.id,
'product_uom_qty': 1.0,
})
]
})
so.action_confirm()
self.assertTrue(so.picking_ids)
self.assertEqual(so.order_line.qty_delivered, 0)
picking = so.picking_ids
picking.move_ids.write({'quantity': 1, 'picked': True})
picking.button_validate()
self.assertEqual(so.order_line.qty_delivered, 1)

View file

@ -4,7 +4,7 @@
from datetime import timedelta
from odoo import fields
from odoo.addons.stock.tests.common2 import TestStockCommon
from odoo.addons.stock.tests.common import TestStockCommon
from odoo.tests import Form
@ -14,13 +14,12 @@ class TestSaleMrpLeadTime(TestStockCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env.ref('stock.route_warehouse0_mto').active = True
cls.route_mto.active = True
# Update the product_1 with type, route, Manufacturing Lead Time and Customer Lead Time
with Form(cls.product_1) as p1:
# `type` is invisible in the view,
# and it's a compute field based on `detailed_type` which is the field visible in the view
p1.detailed_type = 'product'
p1.produce_delay = 5.0
# and it's a compute field based on `type` which is the field visible in the view
p1.is_storable = True
p1.sale_delay = 5.0
p1.route_ids.clear()
p1.route_ids.add(cls.warehouse_1.manufacture_pull_id.route_id)
@ -29,17 +28,33 @@ class TestSaleMrpLeadTime(TestStockCommon):
# Update the product_2 with type
with Form(cls.product_2) as p2:
# `type` is invisible in the view,
# and it's a compute field based on `detailed_type` which is the field visible in the view
p2.detailed_type = 'consu'
# and it's a compute field based on `type` which is the field visible in the view
p2.type = 'consu'
# Create Bill of materials for product_1
with Form(cls.env['mrp.bom']) as bom:
bom.product_tmpl_id = cls.product_1.product_tmpl_id
bom.product_qty = 2
bom.produce_delay = 5.0
with bom.bom_line_ids.new() as line:
line.product_id = cls.product_2
line.product_qty = 4
cls.warehouse_3_steps_pull = cls.env['stock.warehouse'].create({
'name': 'Warehouse 3 steps',
'code': '3S',
'delivery_steps': 'pick_pack_ship',
})
delivery_route_3 = cls.warehouse_3_steps_pull.delivery_route_id
delivery_route_3.rule_ids[0].write({
'location_dest_id': delivery_route_3.rule_ids[1].location_src_id.id,
})
delivery_route_3.rule_ids[1].write({'action': 'pull'})
delivery_route_3.rule_ids[2].write({'action': 'pull'})
cls.warehouse_3_steps_pull.mto_pull_id.write({
'location_dest_id': delivery_route_3.rule_ids[1].location_src_id.id,
})
def test_00_product_company_level_delays(self):
""" In order to check schedule date, set product's Manufacturing Lead Time
and Customer Lead Time and also set company's Manufacturing Lead Time
@ -48,8 +63,7 @@ class TestSaleMrpLeadTime(TestStockCommon):
company = self.env.ref('base.main_company')
# Update company with Manufacturing Lead Time and Sales Safety Days
company.write({'manufacturing_lead': 3.0,
'security_lead': 3.0})
company.security_lead = 3
# Create sale order of product_1
order_form = Form(self.env['sale.order'])
@ -80,9 +94,9 @@ class TestSaleMrpLeadTime(TestStockCommon):
)
# Check schedule date and deadline of manufacturing order
mo_scheduled = out_date - timedelta(days=self.product_1.produce_delay) - timedelta(days=company.manufacturing_lead)
mo_date_start = out_date - timedelta(days=manufacturing_order.bom_id.produce_delay)
self.assertAlmostEqual(
fields.Datetime.from_string(manufacturing_order.date_planned_start), mo_scheduled,
fields.Datetime.from_string(manufacturing_order.date_start), mo_date_start,
delta=timedelta(seconds=1),
msg="Schedule date of manufacturing order should be equal to: Schedule date of picking - product's Manufacturing Lead Time - company's Manufacturing Lead Time."
)
@ -92,21 +106,20 @@ class TestSaleMrpLeadTime(TestStockCommon):
msg="Deadline date of manufacturing order should be equal to the deadline of sale picking"
)
def test_01_product_route_level_delays(self):
def test_01_product_route_mrp_delays(self):
""" In order to check schedule dates, set product's Manufacturing Lead Time
and Customer Lead Time and also set warehouse route's delay."""
# Update warehouse_1 with Outgoing Shippings pick + pack + ship
self.warehouse_1.write({'delivery_steps': 'pick_pack_ship'})
warehouse = self.warehouse_3_steps_pull
# Set delay on pull rule
for pull_rule in self.warehouse_1.delivery_route_id.rule_ids:
for pull_rule in warehouse.delivery_route_id.rule_ids:
pull_rule.write({'delay': 2})
# Create sale order of product_1
order_form = Form(self.env['sale.order'])
order_form.partner_id = self.partner_1
order_form.warehouse_id = self.warehouse_1
order_form.warehouse_id = warehouse
with order_form.order_line.new() as line:
line.product_id = self.product_1
line.product_uom_qty = 6
@ -115,17 +128,17 @@ class TestSaleMrpLeadTime(TestStockCommon):
order.action_confirm()
# Run scheduler
self.env['procurement.group'].run_scheduler()
self.env['stock.rule'].run_scheduler()
# Check manufacturing order created or not
manufacturing_order = self.env['mrp.production'].search([('product_id', '=', self.product_1.id)])
manufacturing_order = self.env['mrp.production'].search([('product_id', '=', self.product_1.id)])
self.assertTrue(manufacturing_order, 'Manufacturing order should be created.')
# Check the picking crated or not
self.assertTrue(order.picking_ids, "Pickings should be created.")
# Check schedule date of ship type picking
out = order.picking_ids.filtered(lambda r: r.picking_type_id == self.warehouse_1.out_type_id)
out = order.picking_ids.filtered(lambda r: r.picking_type_id == warehouse.out_type_id)
out_min_date = fields.Datetime.from_string(out.scheduled_date)
out_date = fields.Datetime.from_string(order.date_order) + timedelta(days=self.product_1.sale_delay) - timedelta(days=out.move_ids[0].rule_id.delay)
self.assertAlmostEqual(
@ -135,7 +148,7 @@ class TestSaleMrpLeadTime(TestStockCommon):
)
# Check schedule date of pack type picking
pack = order.picking_ids.filtered(lambda r: r.picking_type_id == self.warehouse_1.pack_type_id)
pack = order.picking_ids.filtered(lambda r: r.picking_type_id == warehouse.pack_type_id)
pack_min_date = fields.Datetime.from_string(pack.scheduled_date)
pack_date = out_date - timedelta(days=pack.move_ids[0].rule_id.delay)
self.assertAlmostEqual(
@ -145,7 +158,7 @@ class TestSaleMrpLeadTime(TestStockCommon):
)
# Check schedule date of pick type picking
pick = order.picking_ids.filtered(lambda r: r.picking_type_id == self.warehouse_1.pick_type_id)
pick = order.picking_ids.filtered(lambda r: r.picking_type_id == warehouse.pick_type_id)
pick_min_date = fields.Datetime.from_string(pick.scheduled_date)
self.assertAlmostEqual(
pick_min_date, pack_date,
@ -154,9 +167,9 @@ class TestSaleMrpLeadTime(TestStockCommon):
)
# Check schedule date and deadline date of manufacturing order
mo_scheduled = out_date - timedelta(days=self.product_1.produce_delay) - timedelta(days=self.warehouse_1.delivery_route_id.rule_ids[0].delay) - timedelta(days=self.env.ref('base.main_company').manufacturing_lead)
mo_date_start = out_date - timedelta(days=manufacturing_order.bom_id.produce_delay) - timedelta(days=warehouse.delivery_route_id.rule_ids[0].delay)
self.assertAlmostEqual(
fields.Datetime.from_string(manufacturing_order.date_planned_start), mo_scheduled,
fields.Datetime.from_string(manufacturing_order.date_start), mo_date_start,
delta=timedelta(seconds=1),
msg="Schedule date of manufacturing order should be equal to: Schedule date of picking - product's Manufacturing Lead Time- delay pull_rule."
)

View file

@ -3,7 +3,7 @@
import time
from odoo.tests.common import TransactionCase, Form
from odoo.tests import Form, TransactionCase
from odoo.tools import mute_logger
from odoo import Command
@ -17,7 +17,7 @@ class TestSaleMrpProcurement(TransactionCase):
def test_sale_mrp(self):
# Required for `uom_id` to be visible in the view
self.env.user.groups_id += self.env.ref('uom.group_uom')
self.env.user.group_ids += self.env.ref('uom.group_uom')
self.env.ref('stock.route_warehouse0_mto').active = True
warehouse0 = self.env.ref('stock.warehouse0')
# In order to test the sale_mrp module in OpenERP, I start by creating a new product 'Slider Mobile'
@ -40,9 +40,8 @@ class TestSaleMrpProcurement(TransactionCase):
product.categ_id = product_category_allproductssellable0
product.list_price = 200.0
product.name = 'Slider Mobile'
product.detailed_type = 'product'
product.is_storable = True
product.uom_id = uom_unit
product.uom_po_id = uom_unit
product.route_ids.clear()
product.route_ids.add(warehouse0.manufacture_pull_id.route_id)
product.route_ids.add(warehouse0.mto_pull_id.route_id)
@ -86,9 +85,9 @@ class TestSaleMrpProcurement(TransactionCase):
to the customer location
"""
# Required for `uom_id` to be visible in the view
self.env.user.groups_id += self.env.ref('uom.group_uom')
self.env.user.group_ids += self.env.ref('uom.group_uom')
# Required for `manufacture_step` to be visible in the view
self.env.user.groups_id += self.env.ref('stock.group_adv_location')
self.env.user.group_ids += self.env.ref('stock.group_adv_location')
self.env.ref('stock.route_warehouse0_mto').active = True
# Create warehouse
self.customer_location = self.env['ir.model.data']._xmlid_to_res_id('stock.stock_location_customers')
@ -102,17 +101,15 @@ class TestSaleMrpProcurement(TransactionCase):
# Create raw product for manufactured product
product_form = Form(self.env['product.product'])
product_form.name = 'Raw Stick'
product_form.detailed_type = 'product'
product_form.is_storable = True
product_form.uom_id = self.uom_unit
product_form.uom_po_id = self.uom_unit
self.raw_product = product_form.save()
# Create manufactured product
product_form = Form(self.env['product.product'])
product_form.name = 'Stick'
product_form.uom_id = self.uom_unit
product_form.uom_po_id = self.uom_unit
product_form.detailed_type = 'product'
product_form.is_storable = True
product_form.route_ids.clear()
product_form.route_ids.add(self.warehouse.manufacture_pull_id.route_id)
product_form.route_ids.add(self.warehouse.mto_pull_id.route_id)
@ -121,7 +118,7 @@ class TestSaleMrpProcurement(TransactionCase):
# Create manifactured product which uses another manifactured
product_form = Form(self.env['product.product'])
product_form.name = 'Arrow'
product_form.detailed_type = 'product'
product_form.is_storable = True
product_form.route_ids.clear()
product_form.route_ids.add(self.warehouse.manufacture_pull_id.route_id)
product_form.route_ids.add(self.warehouse.mto_pull_id.route_id)
@ -130,14 +127,12 @@ class TestSaleMrpProcurement(TransactionCase):
## Create raw product for manufactured product
product_form = Form(self.env['product.product'])
product_form.name = 'Raw Iron'
product_form.detailed_type = 'product'
product_form.is_storable = True
product_form.uom_id = self.uom_unit
product_form.uom_po_id = self.uom_unit
self.raw_product_2 = product_form.save()
# Create bom for manufactured product
bom_product_form = Form(self.env['mrp.bom'])
bom_product_form.product_id = self.finished_product
bom_product_form.product_tmpl_id = self.finished_product.product_tmpl_id
bom_product_form.product_qty = 1.0
bom_product_form.type = 'normal'
@ -149,7 +144,6 @@ class TestSaleMrpProcurement(TransactionCase):
## Create bom for manufactured product
bom_product_form = Form(self.env['mrp.bom'])
bom_product_form.product_id = self.complex_product
bom_product_form.product_tmpl_id = self.complex_product.product_tmpl_id
with bom_product_form.bom_line_ids.new() as line:
line.product_id = self.finished_product
@ -178,7 +172,9 @@ class TestSaleMrpProcurement(TransactionCase):
sale_order_so0.action_confirm()
# Verify buttons are working as expected
self.assertEqual(sale_order_so0.mrp_production_count, 2, "User should see the correct number of manufacture orders in smart button")
self.assertEqual(sale_order_so0.mrp_production_count, 2, "2 Mos for the 2 sale order line")
self.assertEqual(sale_order_so0.mrp_production_ids[0].product_qty, 1)
self.assertEqual(sale_order_so0.mrp_production_ids[1].product_qty, 2)
pickings = sale_order_so0.picking_ids
@ -202,7 +198,7 @@ class TestSaleMrpProcurement(TransactionCase):
product, component = self.env['product.product'].create([{
'name': 'Finished',
'type': 'product',
'is_storable': True,
'route_ids': [(6, 0, manufacture_route.ids)],
}, {
'name': 'Component',
@ -236,7 +232,6 @@ class TestSaleMrpProcurement(TransactionCase):
'name': product.name,
'product_id': product.id,
'product_uom_qty': 1.0,
'product_uom': product.uom_id.id,
'price_unit': 1,
})],
})
@ -249,7 +244,7 @@ class TestSaleMrpProcurement(TransactionCase):
def test_so_reordering_rule(self):
kit_1, component_1 = self.env['product.product'].create([{
'name': n,
'type': 'product',
'is_storable': True,
} for n in ['Kit 1', 'Compo 1']])
self.env['mrp.bom'].create([{
@ -278,6 +273,68 @@ class TestSaleMrpProcurement(TransactionCase):
[('product_id', '=', kit_1.id)])
self.assertFalse(orderpoint_product)
def test_so_reordering_rule_02(self):
"""
Have a manufactured product in kg unit of measure with the manufacturing route,
the mto route and a BoM in grams.
Confirm a SO with that product in 510 grams -> It should generate a MO with 510g.
Create a second SO with 510g -> It should update the MO to 1020g.
"""
warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
manufacture_route = warehouse.manufacture_pull_id.route_id
mto_route = warehouse.mto_pull_id.route_id
mto_route.active = True
uom_kg = self.env.ref('uom.product_uom_kgm')
uom_gram = self.env.ref('uom.product_uom_gram')
product, component = self.env['product.product'].create([{
'name': 'Finished',
'is_storable': True,
'uom_id': uom_kg.id,
'route_ids': [
Command.link(manufacture_route.id),
Command.link(mto_route.id),
],
}, {
'name': 'Component',
'type': 'consu',
}])
self.env['mrp.bom'].create({
'product_id': product.id,
'product_tmpl_id': product.product_tmpl_id.id,
'product_uom_id': uom_gram.id,
'product_qty': 1.0,
'type': 'normal',
'bom_line_ids': [
(0, 0, {'product_id': component.id, 'product_qty': 1}),
],
})
so = self.env['sale.order'].create({
'partner_id': self.env['res.partner'].create({'name': 'Super Partner'}).id,
'order_line': [
(0, 0, {
'name': product.name,
'product_id': product.id,
'product_uom_qty': 510,
'product_uom_id': uom_gram.id,
'price_unit': 1,
})],
})
so.action_confirm()
self.assertEqual(so.state, 'sale')
mo = self.env['mrp.production'].search([('product_id', '=', product.id)], order='id desc', limit=1)
self.assertIn(so.name, mo.origin)
self.assertEqual(mo.product_uom_id, uom_gram)
self.assertEqual(mo.product_qty, 510)
so.order_line.product_uom_qty = 510 * 2
self.assertEqual(mo.product_uom_id, uom_gram)
self.assertEqual(mo.product_qty, 1020)
def test_sale_mrp_avoid_multiple_pickings(self):
"""
Test sale of multiple products. Avoid multiple pickings being
@ -294,13 +351,11 @@ class TestSaleMrpProcurement(TransactionCase):
'name': 'sol_p1',
'product_id': self.env['product.product'].create({'name': 'p1'}).id,
'product_uom_qty': 1,
'product_uom': self.env.ref('uom.product_uom_unit').id,
}),
Command.create({
'name': 'sol_p2',
'product_id': self.env['product.product'].create({'name': 'p2'}).id,
'product_uom_qty': 1,
'product_uom': self.env.ref('uom.product_uom_unit').id,
}),
],
})

View file

@ -1,22 +1,22 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import Command
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.tests import common, Form
from odoo.fields import Command
from odoo.tests import tagged
from odoo.tools import html2plaintext
from odoo.addons.sale.tests.common import TestSaleCommon
@common.tagged('post_install', '-at_install')
class TestSaleMrpInvoices(AccountTestInvoicingCommon):
@tagged('post_install', '-at_install')
class TestSaleMrpInvoices(TestSaleCommon):
@classmethod
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
def setUpClass(cls):
super().setUpClass()
cls.product_by_lot = cls.env['product.product'].create({
'name': 'Product By Lot',
'type': 'product',
'is_storable': True,
'tracking': 'lot',
})
cls.warehouse = cls.env['stock.warehouse'].search([('company_id', '=', cls.env.company.id)], limit=1)
@ -24,7 +24,6 @@ class TestSaleMrpInvoices(AccountTestInvoicingCommon):
cls.lot = cls.env['stock.lot'].create({
'name': 'LOT0001',
'product_id': cls.product_by_lot.id,
'company_id': cls.env.company.id,
})
cls.env['stock.quant']._update_available_quantity(cls.product_by_lot, cls.stock_location, 10, lot_id=cls.lot)
@ -40,7 +39,6 @@ class TestSaleMrpInvoices(AccountTestInvoicingCommon):
'product_qty': 1,
})]
})
cls.partner = cls.env['res.partner'].create({'name': 'Test Partner'})
def test_deliver_and_invoice_tracked_components(self):
"""
@ -50,7 +48,7 @@ class TestSaleMrpInvoices(AccountTestInvoicingCommon):
"""
display_lots = self.env.ref('stock_account.group_lot_on_invoice')
display_uom = self.env.ref('uom.group_uom')
self.env.user.write({'groups_id': [(4, display_lots.id), (4, display_uom.id)]})
self.env.user.write({'group_ids': [(4, display_lots.id), (4, display_uom.id)]})
so = self.env['sale.order'].create({
'partner_id': self.partner.id,
@ -60,16 +58,14 @@ class TestSaleMrpInvoices(AccountTestInvoicingCommon):
})
so.action_confirm()
action = so.picking_ids.button_validate()
wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
wizard.process()
so.picking_ids.button_validate()
invoice = so._create_invoices()
invoice.action_post()
html = self.env['ir.actions.report']._render_qweb_html(
'account.report_invoice_with_payments', invoice.ids)[0]
text = html2plaintext(html)
text = html2plaintext(html.decode())
self.assertRegex(text, r'Product By Lot\n1.00Units\nLOT0001', "There should be a line that specifies 1 x LOT0001")
def test_report_forecast_for_mto_procure_method(self):
@ -81,9 +77,19 @@ class TestSaleMrpInvoices(AccountTestInvoicingCommon):
manufacturing_route = self.env.ref('mrp.route_warehouse0_manufacture')
product = self.env['product.product'].create({
'name': 'SuperProduct',
'type': 'product',
'is_storable': True,
'route_ids': [Command.set((mto_route + manufacturing_route).ids)]
})
product.bom_ids = [Command.create({
'product_id': product.id,
'product_tmpl_id': product.product_tmpl_id.id,
'product_uom_id': product.uom_id.id,
'bom_line_ids': [Command.create({
'product_id': self.product_by_lot.id,
'product_qty': 1,
})]
})]
warehouse = self.warehouse
# make 2 so: so_1 can be fulfilled and so_2 requires a replenishment
self.env['stock.quant']._update_available_quantity(product, warehouse.lot_stock_id, 10.0)
@ -94,7 +100,6 @@ class TestSaleMrpInvoices(AccountTestInvoicingCommon):
'name': product.name,
'product_id': product.id,
'product_uom_qty': 8.0,
'product_uom': product.uom_id.id,
'price_unit': product.list_price,
})]
},
@ -104,27 +109,26 @@ class TestSaleMrpInvoices(AccountTestInvoicingCommon):
'name': product.name,
'product_id': product.id,
'product_uom_qty': 7.0,
'product_uom': product.uom_id.id,
'price_unit': product.list_price,
})]
},
])
(so_1 | so_2).action_confirm()
report_lines = self.env['report.stock.report_product_product_replenishment'].with_context(warehouse=warehouse.id).get_report_values(docids=product.ids)['docs']['lines']
report_lines = self.env['stock.forecasted_product_product'].with_context(warehouse=warehouse.id).get_report_values(docids=product.ids)['docs']['lines']
self.assertEqual(len(report_lines), 3)
so_1_line = next(filter(lambda line: line.get('document_out') == so_1, report_lines))
so_1_line = report_lines[0]
self.assertEqual(
[so_1_line['quantity'], so_1_line['move_out'], so_1_line['replenishment_filled']],
[8.0, so_1.picking_ids.move_ids, True]
[so_1_line['quantity'], so_1_line['move_out']['id'], so_1_line['replenishment_filled']],
[8.0, so_1.picking_ids.move_ids.id, True]
)
so_2_line = next(filter(lambda line: line.get('document_out') == so_2, report_lines))
so_2_line = report_lines[1]
self.assertEqual(
[so_2_line['quantity'], so_2_line['move_out'], so_2_line['replenishment_filled']],
[7.0, so_2.picking_ids.move_ids, False]
[so_2_line['quantity'], so_2_line['move_out']['id'], so_2_line['replenishment_filled']],
[7.0, so_2.picking_ids.move_ids.id, True]
)
quant_line = next(filter(lambda line: not line.get('document_out'), report_lines))
replenisment_line = report_lines[2]
self.assertEqual(
[quant_line['document_out'], quant_line['quantity'], quant_line['replenishment_filled']],
[False, 2.0, True]
[replenisment_line['document_in'], replenisment_line['document_out'], replenisment_line['quantity'], replenisment_line['move_out'], replenisment_line['replenishment_filled']],
[False, False, 10.0, None, True]
)