mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
[IMP] stock_available_mrp: black, isort, prettier
This commit is contained in:
committed by
Florian da Costa
parent
d2fbb13d87
commit
67767fb5b6
@@ -1,19 +1,13 @@
|
||||
# Copyright 2014 Numérigraphe SARL, Camptocamp
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
{
|
||||
'name': 'Consider the production potential is available to promise',
|
||||
'version': '12.0.1.0.2',
|
||||
"author": "Numérigraphe,"
|
||||
"Odoo Community Association (OCA)",
|
||||
'website': 'https://github.com/OCA/stock-logistics-warehouse',
|
||||
'category': 'Hidden',
|
||||
'depends': [
|
||||
'stock_available',
|
||||
'mrp'
|
||||
],
|
||||
'demo': [
|
||||
'demo/mrp_data.xml',
|
||||
],
|
||||
'license': 'AGPL-3',
|
||||
'installable': True,
|
||||
"name": "Consider the production potential is available to promise",
|
||||
"version": "12.0.1.0.2",
|
||||
"author": "Numérigraphe," "Odoo Community Association (OCA)",
|
||||
"website": "https://github.com/OCA/stock-logistics-warehouse",
|
||||
"category": "Hidden",
|
||||
"depends": ["stock_available", "mrp"],
|
||||
"demo": ["demo/mrp_data.xml",],
|
||||
"license": "AGPL-3",
|
||||
"installable": True,
|
||||
}
|
||||
|
||||
@@ -1,38 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo noupdate="1">
|
||||
<record id="product_kit_1a" model="product.product">
|
||||
<field name="default_code">PCSC234-WHITE</field>
|
||||
<field name="product_tmpl_id"
|
||||
ref="mrp.product_product_table_kit_product_template"/>
|
||||
<field name="attribute_value_ids" eval="[(6,0,[ref('product.product_attribute_value_3')])]"/>
|
||||
<field
|
||||
name="product_tmpl_id"
|
||||
ref="mrp.product_product_table_kit_product_template"
|
||||
/>
|
||||
<field
|
||||
name="attribute_value_ids"
|
||||
eval="[(6,0,[ref('product.product_attribute_value_3')])]"
|
||||
/>
|
||||
</record>
|
||||
|
||||
<record id="mrp.product_product_table_kit" model="product.product">
|
||||
<field name="attribute_value_ids"
|
||||
eval="[(6,0,[ref('product.product_attribute_value_4')])]"/>
|
||||
<field
|
||||
name="attribute_value_ids"
|
||||
eval="[(6,0,[ref('product.product_attribute_value_4')])]"
|
||||
/>
|
||||
</record>
|
||||
|
||||
<record id="mrp.mrp_bom_kit_line_2" model="mrp.bom.line">
|
||||
<field name="attribute_value_ids" eval="[(6,0,[ref('product.product_attribute_value_4')])]"/>
|
||||
<field
|
||||
name="attribute_value_ids"
|
||||
eval="[(6,0,[ref('product.product_attribute_value_4')])]"
|
||||
/>
|
||||
</record>
|
||||
|
||||
<record id="product_computer_desk_bolt_white" model="product.product">
|
||||
<field name="name">Bolt</field>
|
||||
<field name="categ_id" ref="product.product_category_5"/>
|
||||
<field name="categ_id" ref="product.product_category_5" />
|
||||
<field name="standard_price">1.0</field>
|
||||
<field name="list_price">5.0</field>
|
||||
<field name="type">product</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="default_code">BOLT-WHITE</field>
|
||||
</record>
|
||||
|
||||
<record id="mrp_bom_kit_line_3" model="mrp.bom.line">
|
||||
<field name="product_id" ref="product_computer_desk_bolt_white"/>
|
||||
<field name="product_id" ref="product_computer_desk_bolt_white" />
|
||||
<field name="product_qty">4</field>
|
||||
<field name="product_uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="product_uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="sequence">5</field>
|
||||
<field name="bom_id" ref="mrp.mrp_bom_kit"/>
|
||||
<field name="attribute_value_ids" eval="[(6,0,[ref('product.product_attribute_value_3')])]"/>
|
||||
<field name="bom_id" ref="mrp.mrp_bom_kit" />
|
||||
<field
|
||||
name="attribute_value_ids"
|
||||
eval="[(6,0,[ref('product.product_attribute_value_3')])]"
|
||||
/>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -2,21 +2,18 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from collections import Counter
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.fields import first
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
|
||||
_inherit = 'product.product'
|
||||
_inherit = "product.product"
|
||||
|
||||
bom_id = fields.Many2one(
|
||||
'mrp.bom',
|
||||
compute='_compute_bom_id',
|
||||
string='BOM'
|
||||
)
|
||||
bom_id = fields.Many2one("mrp.bom", compute="_compute_bom_id", string="BOM")
|
||||
|
||||
@api.depends('virtual_available', 'bom_id', 'bom_id.product_qty')
|
||||
@api.depends("virtual_available", "bom_id", "bom_id.product_qty")
|
||||
def _compute_available_quantities(self):
|
||||
super(ProductProduct, self)._compute_available_quantities()
|
||||
|
||||
@@ -27,49 +24,46 @@ class ProductProduct(models.Model):
|
||||
:return:
|
||||
"""
|
||||
return [
|
||||
'|',
|
||||
('product_id', 'in', self.ids),
|
||||
'&',
|
||||
('product_id', '=', False),
|
||||
('product_tmpl_id', 'in', self.mapped('product_tmpl_id.id'))
|
||||
"|",
|
||||
("product_id", "in", self.ids),
|
||||
"&",
|
||||
("product_id", "=", False),
|
||||
("product_tmpl_id", "in", self.mapped("product_tmpl_id.id")),
|
||||
]
|
||||
|
||||
@api.multi
|
||||
@api.depends('product_tmpl_id')
|
||||
@api.depends("product_tmpl_id")
|
||||
def _compute_bom_id(self):
|
||||
bom_obj = self.env['mrp.bom']
|
||||
boms = bom_obj.search(
|
||||
self._get_bom_id_domain(),
|
||||
order='sequence, product_id',
|
||||
)
|
||||
bom_obj = self.env["mrp.bom"]
|
||||
boms = bom_obj.search(self._get_bom_id_domain(), order="sequence, product_id",)
|
||||
for product in self:
|
||||
product_boms = boms.filtered(
|
||||
lambda b: b.product_id == product or
|
||||
(not b.product_id and
|
||||
b.product_tmpl_id == product.product_tmpl_id)
|
||||
lambda b: b.product_id == product
|
||||
or (not b.product_id and b.product_tmpl_id == product.product_tmpl_id)
|
||||
)
|
||||
if product_boms:
|
||||
product.bom_id = first(product_boms)
|
||||
|
||||
@api.multi
|
||||
def _compute_available_quantities_dict(self):
|
||||
res, stock_dict = super(ProductProduct,
|
||||
self)._compute_available_quantities_dict()
|
||||
res, stock_dict = super(
|
||||
ProductProduct, self
|
||||
)._compute_available_quantities_dict()
|
||||
# compute qty for product with bom
|
||||
product_with_bom = self.filtered('bom_id')
|
||||
product_with_bom = self.filtered("bom_id")
|
||||
|
||||
if not product_with_bom:
|
||||
return res, stock_dict
|
||||
icp = self.env['ir.config_parameter']
|
||||
icp = self.env["ir.config_parameter"]
|
||||
stock_available_mrp_based_on = icp.sudo().get_param(
|
||||
'stock_available_mrp_based_on', 'qty_available'
|
||||
"stock_available_mrp_based_on", "qty_available"
|
||||
)
|
||||
|
||||
# explode all boms at once
|
||||
exploded_boms = product_with_bom._explode_boms()
|
||||
|
||||
# extract the list of product used as bom component
|
||||
component_products = self.env['product.product'].browse()
|
||||
component_products = self.env["product.product"].browse()
|
||||
for exploded_components in exploded_boms.values():
|
||||
for bom_component in exploded_components:
|
||||
component_products |= first(bom_component).product_id
|
||||
@@ -79,22 +73,19 @@ class ProductProduct(models.Model):
|
||||
if res and stock_available_mrp_based_on in list(res.values())[0]:
|
||||
# If the qty is computed by the same method use it to avoid
|
||||
# stressing the cache
|
||||
component_qties, _ = \
|
||||
component_products._compute_available_quantities_dict()
|
||||
component_qties, _ = component_products._compute_available_quantities_dict()
|
||||
else:
|
||||
# The qty is a field computed by an other method than the
|
||||
# current one. Take the value on the record.
|
||||
component_qties = {
|
||||
p.id: {
|
||||
stock_available_mrp_based_on: p[
|
||||
stock_available_mrp_based_on]} for p in
|
||||
component_products}
|
||||
p.id: {stock_available_mrp_based_on: p[stock_available_mrp_based_on]}
|
||||
for p in component_products
|
||||
}
|
||||
|
||||
for product in product_with_bom:
|
||||
# Need by product (same product can be in many BOM lines/levels)
|
||||
exploded_components = exploded_boms[product.id]
|
||||
component_needs = product._get_components_needs(
|
||||
exploded_components)
|
||||
component_needs = product._get_components_needs(exploded_components)
|
||||
if not component_needs:
|
||||
# The BoM has no line we can use
|
||||
potential_qty = 0.0
|
||||
@@ -102,13 +93,15 @@ class ProductProduct(models.Model):
|
||||
else:
|
||||
# Find the lowest quantity we can make with the stock at hand
|
||||
components_potential_qty = min(
|
||||
[component_qties[component.id][
|
||||
stock_available_mrp_based_on] / need
|
||||
for component, need in component_needs.items()]
|
||||
[
|
||||
component_qties[component.id][stock_available_mrp_based_on]
|
||||
/ need
|
||||
for component, need in component_needs.items()
|
||||
]
|
||||
)
|
||||
|
||||
bom_id = product.bom_id
|
||||
potential_qty = (bom_id.product_qty * components_potential_qty)
|
||||
potential_qty = bom_id.product_qty * components_potential_qty
|
||||
potential_qty = potential_qty > 0.0 and potential_qty or 0.0
|
||||
|
||||
# We want to respect the rounding factor of the potential_qty
|
||||
@@ -116,11 +109,11 @@ class ProductProduct(models.Model):
|
||||
potential_qty = bom_id.product_uom_id._compute_quantity(
|
||||
potential_qty,
|
||||
product.bom_id.product_tmpl_id.uom_id,
|
||||
rounding_method='DOWN'
|
||||
rounding_method="DOWN",
|
||||
)
|
||||
|
||||
res[product.id]['potential_qty'] = potential_qty
|
||||
res[product.id]['immediately_usable_qty'] += potential_qty
|
||||
res[product.id]["potential_qty"] = potential_qty
|
||||
res[product.id]["immediately_usable_qty"] += potential_qty
|
||||
|
||||
return res, stock_dict
|
||||
|
||||
@@ -145,6 +138,6 @@ class ProductProduct(models.Model):
|
||||
needs = Counter()
|
||||
for bom_component in exploded_components:
|
||||
component = bom_component[0].product_id
|
||||
needs += Counter({component: bom_component[1]['qty']})
|
||||
needs += Counter({component: bom_component[1]["qty"]})
|
||||
|
||||
return needs
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Copyright 2014 Numérigraphe SARL
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.osv.expression import TRUE_LEAF
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestPotentialQty(TransactionCase):
|
||||
@@ -15,91 +15,90 @@ class TestPotentialQty(TransactionCase):
|
||||
self.bom_model = self.env["mrp.bom"]
|
||||
self.bom_line_model = self.env["mrp.bom.line"]
|
||||
self.stock_quant_model = self.env["stock.quant"]
|
||||
self.config = self.env['ir.config_parameter']
|
||||
self.location = self.env['stock.location']
|
||||
self.config = self.env["ir.config_parameter"]
|
||||
self.location = self.env["stock.location"]
|
||||
# Get the warehouses
|
||||
self.wh_main = self.browse_ref('stock.warehouse0')
|
||||
self.wh_ch = self.browse_ref('stock.stock_warehouse_shop0')
|
||||
self.wh_main = self.browse_ref("stock.warehouse0")
|
||||
self.wh_ch = self.browse_ref("stock.stock_warehouse_shop0")
|
||||
|
||||
# We need to compute parent_left and parent_right of the locations as
|
||||
# they are used to compute qty_available of the product.
|
||||
self.location._parent_store_compute()
|
||||
# An interesting product (multi-line BoM, variants)
|
||||
self.tmpl = self.browse_ref(
|
||||
'mrp.product_product_table_kit_product_template')
|
||||
self.tmpl = self.browse_ref("mrp.product_product_table_kit_product_template")
|
||||
# First variant
|
||||
self.var1 = self.browse_ref('mrp.product_product_table_kit')
|
||||
self.var1 = self.browse_ref("mrp.product_product_table_kit")
|
||||
# Second variant
|
||||
self.var2 = self.browse_ref(
|
||||
'stock_available_mrp.product_kit_1a')
|
||||
self.var2 = self.browse_ref("stock_available_mrp.product_kit_1a")
|
||||
# Make bolt a stockable product to be able to change its stock
|
||||
# we need to unreserve the existing move before being able to do it.
|
||||
bolt = self.env.ref('mrp.product_product_computer_desk_bolt')
|
||||
bolt_moves = self.env['stock.move'].search(
|
||||
[('product_id', '=', bolt.id),
|
||||
('state', 'not in', ('done', 'cancel'))])
|
||||
bolt = self.env.ref("mrp.product_product_computer_desk_bolt")
|
||||
bolt_moves = self.env["stock.move"].search(
|
||||
[("product_id", "=", bolt.id), ("state", "not in", ("done", "cancel"))]
|
||||
)
|
||||
bolt_moves._do_unreserve()
|
||||
bolt.type = 'product'
|
||||
bolt.type = "product"
|
||||
# Components that can be used to make the product
|
||||
component_ids = [
|
||||
# Bolt
|
||||
bolt.id,
|
||||
# Wood Panel
|
||||
self.ref('mrp.product_product_wood_panel'),
|
||||
self.ref("mrp.product_product_wood_panel"),
|
||||
]
|
||||
|
||||
# Zero-out the inventory of all variants and components
|
||||
for component_id in (
|
||||
component_ids + [v.id
|
||||
for v in self.tmpl.product_variant_ids]):
|
||||
for component_id in component_ids + [
|
||||
v.id for v in self.tmpl.product_variant_ids
|
||||
]:
|
||||
prod = self.product_model.browse(component_id)
|
||||
self.env['stock.quant'].search([
|
||||
('product_id', '=', prod.id)
|
||||
]).unlink()
|
||||
self.env["stock.quant"].search([("product_id", "=", prod.id)]).unlink()
|
||||
|
||||
self.product_model.invalidate_cache()
|
||||
# A product without a BoM
|
||||
self.product_wo_bom = self.browse_ref('product.product_product_11')
|
||||
self.product_wo_bom = self.browse_ref("product.product_product_11")
|
||||
|
||||
# Record the initial quantity available for sale
|
||||
self.initial_usable_qties = {i.id: i.immediately_usable_qty
|
||||
for i in [self.tmpl,
|
||||
self.var1,
|
||||
self.var2,
|
||||
self.product_wo_bom]}
|
||||
self.initial_usable_qties = {
|
||||
i.id: i.immediately_usable_qty
|
||||
for i in [self.tmpl, self.var1, self.var2, self.product_wo_bom]
|
||||
}
|
||||
|
||||
def create_inventory(self, product_id, qty, location_id=None):
|
||||
if location_id is None:
|
||||
location_id = self.wh_main.lot_stock_id.id
|
||||
|
||||
inventory = self.env['stock.inventory'].create({
|
||||
'name': 'Test inventory',
|
||||
'location_id': location_id,
|
||||
'filter': 'partial'
|
||||
})
|
||||
inventory = self.env["stock.inventory"].create(
|
||||
{"name": "Test inventory", "location_id": location_id, "filter": "partial"}
|
||||
)
|
||||
inventory.action_start()
|
||||
self.env['stock.inventory.line'].create({
|
||||
'inventory_id': inventory.id,
|
||||
'product_id': product_id,
|
||||
'location_id': location_id,
|
||||
'product_qty': qty
|
||||
})
|
||||
self.env["stock.inventory.line"].create(
|
||||
{
|
||||
"inventory_id": inventory.id,
|
||||
"product_id": product_id,
|
||||
"location_id": location_id,
|
||||
"product_qty": qty,
|
||||
}
|
||||
)
|
||||
inventory._action_done()
|
||||
|
||||
def create_simple_bom(self, product, sub_product,
|
||||
product_qty=1, sub_product_qty=1,
|
||||
routing_id=False):
|
||||
bom = self.bom_model.create({
|
||||
'product_tmpl_id': product.product_tmpl_id.id,
|
||||
'product_id': product.id,
|
||||
'product_qty': product_qty,
|
||||
'routing_id': routing_id,
|
||||
})
|
||||
self.bom_line_model.create({
|
||||
'bom_id': bom.id,
|
||||
'product_id': sub_product.id,
|
||||
'product_qty': sub_product_qty,
|
||||
})
|
||||
def create_simple_bom(
|
||||
self, product, sub_product, product_qty=1, sub_product_qty=1, routing_id=False
|
||||
):
|
||||
bom = self.bom_model.create(
|
||||
{
|
||||
"product_tmpl_id": product.product_tmpl_id.id,
|
||||
"product_id": product.id,
|
||||
"product_qty": product_qty,
|
||||
"routing_id": routing_id,
|
||||
}
|
||||
)
|
||||
self.bom_line_model.create(
|
||||
{
|
||||
"bom_id": bom.id,
|
||||
"product_id": sub_product.id,
|
||||
"product_qty": sub_product_qty,
|
||||
}
|
||||
)
|
||||
|
||||
return bom
|
||||
|
||||
@@ -109,235 +108,294 @@ class TestPotentialQty(TransactionCase):
|
||||
self.assertEqual(record.potential_qty, qty, msg)
|
||||
# Check the variation of quantity available for sale
|
||||
self.assertEqual(
|
||||
(record.immediately_usable_qty -
|
||||
self.initial_usable_qties[record.id]), qty, msg)
|
||||
(record.immediately_usable_qty - self.initial_usable_qties[record.id]),
|
||||
qty,
|
||||
msg,
|
||||
)
|
||||
|
||||
def test_potential_qty_no_bom(self):
|
||||
# Check the potential when there's no BoM
|
||||
self.assertPotentialQty(
|
||||
self.product_wo_bom, 0.0,
|
||||
"The potential without a BoM should be 0")
|
||||
self.product_wo_bom, 0.0, "The potential without a BoM should be 0"
|
||||
)
|
||||
|
||||
def test_potential_qty_no_bom_for_company(self):
|
||||
chicago_id = self.ref('stock.res_company_1')
|
||||
chicago_id = self.ref("stock.res_company_1")
|
||||
|
||||
# Receive 1000x Wood Panel owned by Chicago
|
||||
inventory = self.env['stock.inventory'].create(
|
||||
{'name': 'Receive CPUa8',
|
||||
'company_id': chicago_id,
|
||||
'location_id': self.wh_ch.lot_stock_id.id,
|
||||
'filter': 'partial'})
|
||||
inventory = self.env["stock.inventory"].create(
|
||||
{
|
||||
"name": "Receive CPUa8",
|
||||
"company_id": chicago_id,
|
||||
"location_id": self.wh_ch.lot_stock_id.id,
|
||||
"filter": "partial",
|
||||
}
|
||||
)
|
||||
inventory.action_start()
|
||||
self.env['stock.inventory.line'].create(
|
||||
{'inventory_id': inventory.id,
|
||||
'company_id': chicago_id,
|
||||
'product_id': self.ref('mrp.product_product_wood_panel'),
|
||||
'location_id': self.wh_ch.lot_stock_id.id,
|
||||
'product_qty': 1000.0})
|
||||
self.env["stock.inventory.line"].create(
|
||||
{
|
||||
"inventory_id": inventory.id,
|
||||
"company_id": chicago_id,
|
||||
"product_id": self.ref("mrp.product_product_wood_panel"),
|
||||
"location_id": self.wh_ch.lot_stock_id.id,
|
||||
"product_qty": 1000.0,
|
||||
}
|
||||
)
|
||||
inventory._action_done()
|
||||
|
||||
# Put Bolt owned by Chicago for 1000x the 1st variant in main WH
|
||||
inventory = self.env['stock.inventory'].create(
|
||||
{'name': 'components for 1st variant',
|
||||
'company_id': chicago_id,
|
||||
'location_id': self.wh_ch.lot_stock_id.id,
|
||||
'filter': 'partial'})
|
||||
inventory = self.env["stock.inventory"].create(
|
||||
{
|
||||
"name": "components for 1st variant",
|
||||
"company_id": chicago_id,
|
||||
"location_id": self.wh_ch.lot_stock_id.id,
|
||||
"filter": "partial",
|
||||
}
|
||||
)
|
||||
inventory.action_start()
|
||||
self.env['stock.inventory.line'].create(
|
||||
{'inventory_id': inventory.id,
|
||||
'company_id': chicago_id,
|
||||
'product_id': self.ref('mrp.product_product_computer_desk_bolt'),
|
||||
'location_id': self.wh_ch.lot_stock_id.id,
|
||||
'product_qty': 1000.0})
|
||||
self.env["stock.inventory.line"].create(
|
||||
{
|
||||
"inventory_id": inventory.id,
|
||||
"company_id": chicago_id,
|
||||
"product_id": self.ref("mrp.product_product_computer_desk_bolt"),
|
||||
"location_id": self.wh_ch.lot_stock_id.id,
|
||||
"product_qty": 1000.0,
|
||||
}
|
||||
)
|
||||
inventory._action_done()
|
||||
self.assertPotentialQty(
|
||||
self.tmpl, 250.0,
|
||||
"Wrong template potential after receiving components")
|
||||
self.tmpl, 250.0, "Wrong template potential after receiving components"
|
||||
)
|
||||
|
||||
test_user = self.env['res.users'].create(
|
||||
{'name': 'test_demo',
|
||||
'login': 'test_demo',
|
||||
'company_id': self.ref('base.main_company'),
|
||||
'company_ids': [(4, self.ref('base.main_company'))],
|
||||
'groups_id': [(4, self.ref('stock.group_stock_user')),
|
||||
(4, self.ref('mrp.group_mrp_user'))]})
|
||||
test_user = self.env["res.users"].create(
|
||||
{
|
||||
"name": "test_demo",
|
||||
"login": "test_demo",
|
||||
"company_id": self.ref("base.main_company"),
|
||||
"company_ids": [(4, self.ref("base.main_company"))],
|
||||
"groups_id": [
|
||||
(4, self.ref("stock.group_stock_user")),
|
||||
(4, self.ref("mrp.group_mrp_user")),
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
bom = self.env['mrp.bom'].search(
|
||||
[('product_tmpl_id', '=', self.tmpl.id)])
|
||||
bom = self.env["mrp.bom"].search([("product_tmpl_id", "=", self.tmpl.id)])
|
||||
|
||||
test_user_tmpl = self.tmpl.sudo(test_user)
|
||||
self.assertPotentialQty(
|
||||
test_user_tmpl, 250.0,
|
||||
"Simple user can access to the potential_qty")
|
||||
test_user_tmpl, 250.0, "Simple user can access to the potential_qty"
|
||||
)
|
||||
|
||||
# Set the bom on the main company (visible to members of main company)
|
||||
# and all products without company (visible to all)
|
||||
# and the demo user on Chicago (child of main company)
|
||||
self.env['product.product'].search([
|
||||
TRUE_LEAF]).write({'company_id': False})
|
||||
test_user.write({'company_id': chicago_id,
|
||||
'company_ids': [(4, chicago_id)]})
|
||||
bom.company_id = self.ref('base.main_company')
|
||||
self.env["product.product"].search([TRUE_LEAF]).write({"company_id": False})
|
||||
test_user.write({"company_id": chicago_id, "company_ids": [(4, chicago_id)]})
|
||||
bom.company_id = self.ref("base.main_company")
|
||||
self.assertPotentialQty(
|
||||
test_user_tmpl, 0,
|
||||
test_user_tmpl,
|
||||
0,
|
||||
"The bom should not be visible to non members of the bom's "
|
||||
"company or company child of the bom's company")
|
||||
"company or company child of the bom's company",
|
||||
)
|
||||
bom.company_id = chicago_id
|
||||
self.assertPotentialQty(
|
||||
test_user_tmpl, 250.0, '')
|
||||
self.assertPotentialQty(test_user_tmpl, 250.0, "")
|
||||
|
||||
def test_potential_qty(self):
|
||||
for i in [self.tmpl, self.var1, self.var2]:
|
||||
self.assertPotentialQty(
|
||||
i, 0.0,
|
||||
"The potential quantity should start at 0")
|
||||
self.assertPotentialQty(i, 0.0, "The potential quantity should start at 0")
|
||||
|
||||
# Receive 1000x Wood Panel
|
||||
inventory = self.env['stock.inventory'].create(
|
||||
{'name': 'Receive Mouses',
|
||||
'location_id': self.wh_main.lot_stock_id.id,
|
||||
'filter': 'partial'})
|
||||
inventory = self.env["stock.inventory"].create(
|
||||
{
|
||||
"name": "Receive Mouses",
|
||||
"location_id": self.wh_main.lot_stock_id.id,
|
||||
"filter": "partial",
|
||||
}
|
||||
)
|
||||
inventory.action_start()
|
||||
self.env['stock.inventory.line'].create(
|
||||
{'inventory_id': inventory.id,
|
||||
'product_id': self.ref('mrp.product_product_wood_panel'),
|
||||
'location_id': self.wh_main.lot_stock_id.id,
|
||||
'product_qty': 1000.0})
|
||||
self.env["stock.inventory.line"].create(
|
||||
{
|
||||
"inventory_id": inventory.id,
|
||||
"product_id": self.ref("mrp.product_product_wood_panel"),
|
||||
"location_id": self.wh_main.lot_stock_id.id,
|
||||
"product_qty": 1000.0,
|
||||
}
|
||||
)
|
||||
inventory._action_done()
|
||||
for i in [self.tmpl, self.var1, self.var2]:
|
||||
self.assertPotentialQty(
|
||||
i, 0.0,
|
||||
i,
|
||||
0.0,
|
||||
"Receiving a single component should not change the "
|
||||
"potential of %s" % i)
|
||||
"potential of %s" % i,
|
||||
)
|
||||
|
||||
# Receive enough bolt to make 1000x the 1st variant in main WH
|
||||
inventory = self.env['stock.inventory'].create(
|
||||
{'name': 'components for 1st variant',
|
||||
'location_id': self.wh_main.lot_stock_id.id,
|
||||
'filter': 'partial'})
|
||||
inventory = self.env["stock.inventory"].create(
|
||||
{
|
||||
"name": "components for 1st variant",
|
||||
"location_id": self.wh_main.lot_stock_id.id,
|
||||
"filter": "partial",
|
||||
}
|
||||
)
|
||||
inventory.action_start()
|
||||
self.env['stock.inventory.line'].create(
|
||||
{'inventory_id': inventory.id,
|
||||
'product_id': self.ref(
|
||||
'mrp.product_product_computer_desk_bolt'),
|
||||
'location_id': self.wh_main.lot_stock_id.id,
|
||||
'product_qty': 1000.0})
|
||||
self.env["stock.inventory.line"].create(
|
||||
{
|
||||
"inventory_id": inventory.id,
|
||||
"product_id": self.ref("mrp.product_product_computer_desk_bolt"),
|
||||
"location_id": self.wh_main.lot_stock_id.id,
|
||||
"product_qty": 1000.0,
|
||||
}
|
||||
)
|
||||
inventory._action_done()
|
||||
self.assertPotentialQty(
|
||||
self.tmpl, 250.0,
|
||||
"Wrong template potential after receiving components")
|
||||
self.tmpl, 250.0, "Wrong template potential after receiving components"
|
||||
)
|
||||
self.assertPotentialQty(
|
||||
self.var1, 250.0,
|
||||
"Wrong variant 1 potential after receiving components")
|
||||
self.var1, 250.0, "Wrong variant 1 potential after receiving components"
|
||||
)
|
||||
self.assertPotentialQty(
|
||||
self.var2, 0.0,
|
||||
self.var2,
|
||||
0.0,
|
||||
"Receiving variant 1's component should not change "
|
||||
"variant 2's potential")
|
||||
"variant 2's potential",
|
||||
)
|
||||
|
||||
# Receive enough components to make 213 the 2nd variant at Chicago
|
||||
inventory = self.env['stock.inventory'].create(
|
||||
{'name': 'components for 2nd variant',
|
||||
'location_id': self.wh_ch.lot_stock_id.id,
|
||||
'filter': 'partial'})
|
||||
inventory = self.env["stock.inventory"].create(
|
||||
{
|
||||
"name": "components for 2nd variant",
|
||||
"location_id": self.wh_ch.lot_stock_id.id,
|
||||
"filter": "partial",
|
||||
}
|
||||
)
|
||||
inventory.action_start()
|
||||
self.env['stock.inventory.line'].create(
|
||||
{'inventory_id': inventory.id,
|
||||
'product_id': self.ref('mrp.product_product_wood_panel'),
|
||||
'location_id': self.wh_ch.lot_stock_id.id,
|
||||
'product_qty': 1000.0})
|
||||
self.env['stock.inventory.line'].create(
|
||||
{'inventory_id': inventory.id,
|
||||
'product_id': self.ref(
|
||||
'stock_available_mrp.product_computer_desk_bolt_white'),
|
||||
'location_id': self.wh_ch.lot_stock_id.id,
|
||||
'product_qty': 852.0})
|
||||
self.env["stock.inventory.line"].create(
|
||||
{
|
||||
"inventory_id": inventory.id,
|
||||
"product_id": self.ref("mrp.product_product_wood_panel"),
|
||||
"location_id": self.wh_ch.lot_stock_id.id,
|
||||
"product_qty": 1000.0,
|
||||
}
|
||||
)
|
||||
self.env["stock.inventory.line"].create(
|
||||
{
|
||||
"inventory_id": inventory.id,
|
||||
"product_id": self.ref(
|
||||
"stock_available_mrp.product_computer_desk_bolt_white"
|
||||
),
|
||||
"location_id": self.wh_ch.lot_stock_id.id,
|
||||
"product_qty": 852.0,
|
||||
}
|
||||
)
|
||||
inventory._action_done()
|
||||
self.assertPotentialQty(
|
||||
self.tmpl.with_context(test=True), 250.0,
|
||||
"Wrong template potential after receiving components")
|
||||
self.tmpl.with_context(test=True),
|
||||
250.0,
|
||||
"Wrong template potential after receiving components",
|
||||
)
|
||||
self.assertPotentialQty(
|
||||
self.var1, 250.0,
|
||||
self.var1,
|
||||
250.0,
|
||||
"Receiving variant 2's component should not change "
|
||||
"variant 1's potential")
|
||||
"variant 1's potential",
|
||||
)
|
||||
self.assertPotentialQty(
|
||||
self.var2, 213.0,
|
||||
"Wrong variant 2 potential after receiving components")
|
||||
self.var2, 213.0, "Wrong variant 2 potential after receiving components"
|
||||
)
|
||||
# Check by warehouse
|
||||
self.assertPotentialQty(
|
||||
self.tmpl.with_context(warehouse=self.wh_main.id), 250.0,
|
||||
"Wrong potential quantity in main WH")
|
||||
self.tmpl.with_context(warehouse=self.wh_main.id),
|
||||
250.0,
|
||||
"Wrong potential quantity in main WH",
|
||||
)
|
||||
self.assertPotentialQty(
|
||||
self.tmpl.with_context(warehouse=self.wh_ch.id), 213.0,
|
||||
"Wrong potential quantity in Chicago WH")
|
||||
self.tmpl.with_context(warehouse=self.wh_ch.id),
|
||||
213.0,
|
||||
"Wrong potential quantity in Chicago WH",
|
||||
)
|
||||
# Check by location
|
||||
self.assertPotentialQty(
|
||||
self.tmpl.with_context(
|
||||
location=self.wh_main.lot_stock_id.id), 250.0,
|
||||
"Wrong potential quantity in main WH location")
|
||||
self.tmpl.with_context(location=self.wh_main.lot_stock_id.id),
|
||||
250.0,
|
||||
"Wrong potential quantity in main WH location",
|
||||
)
|
||||
self.assertPotentialQty(
|
||||
self.tmpl.with_context(
|
||||
location=self.wh_ch.lot_stock_id.id),
|
||||
self.tmpl.with_context(location=self.wh_ch.lot_stock_id.id),
|
||||
213.0,
|
||||
"Wrong potential quantity in Chicago WH location")
|
||||
"Wrong potential quantity in Chicago WH location",
|
||||
)
|
||||
|
||||
def test_multi_unit_recursive_bom(self):
|
||||
# Test multi-level and multi-units BOM
|
||||
uom_unit = self.env.ref('uom.product_uom_unit')
|
||||
uom_unit = self.env.ref("uom.product_uom_unit")
|
||||
uom_unit.rounding = 1.0
|
||||
p1 = self.product_model.create({
|
||||
'name': 'Test product with BOM',
|
||||
'type': 'product',
|
||||
'uom_id': self.env.ref('uom.product_uom_unit').id,
|
||||
})
|
||||
p1 = self.product_model.create(
|
||||
{
|
||||
"name": "Test product with BOM",
|
||||
"type": "product",
|
||||
"uom_id": self.env.ref("uom.product_uom_unit").id,
|
||||
}
|
||||
)
|
||||
|
||||
p2 = self.product_model.create({
|
||||
'name': 'Test sub product with BOM',
|
||||
'type': 'consu',
|
||||
'uom_id': self.env.ref('uom.product_uom_unit').id,
|
||||
})
|
||||
p2 = self.product_model.create(
|
||||
{
|
||||
"name": "Test sub product with BOM",
|
||||
"type": "consu",
|
||||
"uom_id": self.env.ref("uom.product_uom_unit").id,
|
||||
}
|
||||
)
|
||||
|
||||
p3 = self.product_model.create({
|
||||
'name': 'Test component',
|
||||
'type': 'product',
|
||||
'uom_id': self.env.ref('uom.product_uom_unit').id,
|
||||
})
|
||||
p3 = self.product_model.create(
|
||||
{
|
||||
"name": "Test component",
|
||||
"type": "product",
|
||||
"uom_id": self.env.ref("uom.product_uom_unit").id,
|
||||
}
|
||||
)
|
||||
|
||||
bom_p1 = self.bom_model.create({
|
||||
'product_tmpl_id': p1.product_tmpl_id.id,
|
||||
'product_id': p1.id,
|
||||
})
|
||||
bom_p1 = self.bom_model.create(
|
||||
{"product_tmpl_id": p1.product_tmpl_id.id, "product_id": p1.id,}
|
||||
)
|
||||
|
||||
self.bom_line_model.create({
|
||||
'bom_id': bom_p1.id,
|
||||
'product_id': p3.id,
|
||||
'product_qty': 1,
|
||||
'product_uom_id': self.env.ref('uom.product_uom_unit').id,
|
||||
|
||||
})
|
||||
self.bom_line_model.create(
|
||||
{
|
||||
"bom_id": bom_p1.id,
|
||||
"product_id": p3.id,
|
||||
"product_qty": 1,
|
||||
"product_uom_id": self.env.ref("uom.product_uom_unit").id,
|
||||
}
|
||||
)
|
||||
|
||||
# Two p2 which have a bom
|
||||
self.bom_line_model.create({
|
||||
'bom_id': bom_p1.id,
|
||||
'product_id': p2.id,
|
||||
'product_qty': 2,
|
||||
'product_uom_id': self.env.ref('uom.product_uom_unit').id,
|
||||
self.bom_line_model.create(
|
||||
{
|
||||
"bom_id": bom_p1.id,
|
||||
"product_id": p2.id,
|
||||
"product_qty": 2,
|
||||
"product_uom_id": self.env.ref("uom.product_uom_unit").id,
|
||||
}
|
||||
)
|
||||
|
||||
})
|
||||
|
||||
bom_p2 = self.bom_model.create({
|
||||
'product_tmpl_id': p2.product_tmpl_id.id,
|
||||
'product_id': p2.id,
|
||||
'type': 'phantom',
|
||||
})
|
||||
bom_p2 = self.bom_model.create(
|
||||
{
|
||||
"product_tmpl_id": p2.product_tmpl_id.id,
|
||||
"product_id": p2.id,
|
||||
"type": "phantom",
|
||||
}
|
||||
)
|
||||
|
||||
# p2 need 2 unit of component
|
||||
self.bom_line_model.create({
|
||||
'bom_id': bom_p2.id,
|
||||
'product_id': p3.id,
|
||||
'product_qty': 2,
|
||||
'product_uom_id': self.env.ref('uom.product_uom_unit').id,
|
||||
|
||||
})
|
||||
self.bom_line_model.create(
|
||||
{
|
||||
"bom_id": bom_p2.id,
|
||||
"product_id": p3.id,
|
||||
"product_qty": 2,
|
||||
"product_uom_id": self.env.ref("uom.product_uom_unit").id,
|
||||
}
|
||||
)
|
||||
|
||||
p1.refresh()
|
||||
|
||||
@@ -369,12 +427,11 @@ class TestPotentialQty(TransactionCase):
|
||||
# a recordset with multiple products
|
||||
# Recursive compute is not working
|
||||
|
||||
p1 = self.product_model.create({'name': 'Test P1'})
|
||||
p2 = self.product_model.create({'name': 'Test P2'})
|
||||
p3 = self.product_model.create({'name': 'Test P3', 'type': 'product'})
|
||||
p1 = self.product_model.create({"name": "Test P1"})
|
||||
p2 = self.product_model.create({"name": "Test P2"})
|
||||
p3 = self.product_model.create({"name": "Test P3", "type": "product"})
|
||||
|
||||
self.config.set_param('stock_available_mrp_based_on',
|
||||
'immediately_usable_qty')
|
||||
self.config.set_param("stock_available_mrp_based_on", "immediately_usable_qty")
|
||||
|
||||
# P1 need one P2
|
||||
self.create_simple_bom(p1, p2)
|
||||
@@ -385,11 +442,9 @@ class TestPotentialQty(TransactionCase):
|
||||
|
||||
self.product_model.invalidate_cache()
|
||||
|
||||
products = self.product_model.search(
|
||||
[('id', 'in', [p1.id, p2.id, p3.id])]
|
||||
)
|
||||
products = self.product_model.search([("id", "in", [p1.id, p2.id, p3.id])])
|
||||
|
||||
self.assertEqual(
|
||||
{p1.id: 3.0, p2.id: 3.0, p3.id: 0.0},
|
||||
{p.id: p.potential_qty for p in products}
|
||||
{p.id: p.potential_qty for p in products},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user