Files
stock-logistics-warehouse/stock_available_mrp/models/product_product.py
2021-05-27 12:42:07 +02:00

140 lines
5.2 KiB
Python

# Copyright 2014 Numérigraphe SARL
# 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"
bom_id = fields.Many2one(
comodel_name="mrp.bom", compute="_compute_bom_id", string="BOM"
)
@api.depends("virtual_available", "bom_id", "bom_id.product_qty")
def _compute_available_quantities(self):
super()._compute_available_quantities()
def _get_bom_id_domain(self):
"""
Real multi domain
:return:
"""
return [
"|",
("product_id", "in", self.ids),
"&",
("product_id", "=", False),
("product_tmpl_id", "in", self.mapped("product_tmpl_id.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")
for product in self:
product.bom_id = product.bom_id
product_boms = boms.filtered(
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)
def _compute_available_quantities_dict(self):
res, stock_dict = super()._compute_available_quantities_dict()
# compute qty for product with bom
product_with_bom = self.filtered("bom_id")
if not product_with_bom:
return res, stock_dict
icp = self.env["ir.config_parameter"]
stock_available_mrp_based_on = icp.sudo().get_param(
"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()
for exploded_components in exploded_boms.values():
for bom_component in exploded_components:
component_products |= first(bom_component).product_id
# Compute stock for product components.
# {'productid': {field_name: qty}}
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()
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
}
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)
if not component_needs:
# The BoM has no line we can use
potential_qty = immediately_usable_qty = 0.0
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()
]
)
bom_id = product.bom_id
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
# Rounding down as we want to be pesimistic.
potential_qty = bom_id.product_uom_id._compute_quantity(
potential_qty,
product.bom_id.product_tmpl_id.uom_id,
rounding_method="DOWN",
)
res[product.id]["potential_qty"] = potential_qty
immediately_usable_qty = potential_qty if bom_id.type != "phantom" else 0
res[product.id]["immediately_usable_qty"] += immediately_usable_qty
return res, stock_dict
def _explode_boms(self):
"""
return a dict by product_id of exploded bom lines
:return:
"""
exploded_boms = {}
for rec in self:
exploded_boms[rec.id] = rec.bom_id.explode(rec, 1.0)[1]
return exploded_boms
@api.model
def _get_components_needs(self, exploded_components):
""" Return the needed qty of each compoments in the exploded_components
:type exploded_components
:rtype: collections.Counter
"""
needs = Counter()
for bom_component in exploded_components:
component = bom_component[0].product_id
needs += Counter({component: bom_component[1]["qty"]})
return needs