[IMP] mrp,stock: count kits product

When a product's has a kits bom, we consider it a kits product.
When a product is a kits product, we count its "on hand" and "forecast"
qty by caculating how many can be produced according to the bom.
We add status button to the show the qty on kit product form.

Task-2444000
COM PR odoo/odoo#75555
ENT PR odoo/enterprise#20440
UPG PR odoo/upgrade#2783

Signed-off-by: Arnold Moyaux <amoyaux@users.noreply.github.com>
This commit is contained in:
yhu-odoo
2021-09-03 08:11:42 +00:00
parent fa2231fb0c
commit caa908506b
4 changed files with 65 additions and 4 deletions
+39
View File
@@ -22,11 +22,26 @@ class ProductTemplate(models.Model):
produce_delay = fields.Float(
'Manufacturing Lead Time', default=0.0,
help="Average lead time in days to manufacture this product. In the case of multi-level BOM, the manufacturing lead times of the components will be added.")
is_kits = fields.Boolean(compute='_compute_is_kits', compute_sudo=False)
def _compute_bom_count(self):
for product in self:
product.bom_count = self.env['mrp.bom'].search_count(['|', ('product_tmpl_id', '=', product.id), ('byproduct_ids.product_id.product_tmpl_id', '=', product.id)])
def _compute_is_kits(self):
domain = [('product_tmpl_id', 'in', self.ids), ('type', '=', 'phantom')]
bom_mapping = self.env['mrp.bom'].search_read(domain, ['product_tmpl_id'])
kits_ids = set(b['product_tmpl_id'][0] for b in bom_mapping)
for template in self:
template.is_kits = (template.id in kits_ids)
def _compute_show_qty_status_button(self):
super()._compute_show_qty_status_button()
for template in self:
if template.is_kits:
template.show_on_hand_qty_status_button = True
template.show_forecasted_qty_status_button = False
def _compute_used_in_bom_count(self):
for template in self:
template.used_in_bom_count = self.env['mrp.bom'].search_count(
@@ -70,11 +85,35 @@ class ProductProduct(models.Model):
compute='_compute_used_in_bom_count', compute_sudo=False)
mrp_product_qty = fields.Float('Manufactured',
compute='_compute_mrp_product_qty', compute_sudo=False)
is_kits = fields.Boolean(compute="_compute_is_kits", compute_sudo=False)
def _compute_bom_count(self):
for product in self:
product.bom_count = self.env['mrp.bom'].search_count(['|', '|', ('byproduct_ids.product_id', '=', product.id), ('product_id', '=', product.id), '&', ('product_id', '=', False), ('product_tmpl_id', '=', product.product_tmpl_id.id)])
def _compute_is_kits(self):
domain = ['&', ('type', '=', 'phantom'),
'|', ('product_id', 'in', self.ids),
'&', ('product_id', '=', False),
('product_tmpl_id', 'in', self.product_tmpl_id.ids)]
bom_mapping = self.env['mrp.bom'].search_read(domain, ['product_tmpl_id', 'product_id'])
kits_template_ids = set([])
kits_product_ids = set([])
for bom_data in bom_mapping:
if bom_data['product_id']:
kits_product_ids.add(bom_data['product_id'][0])
else:
kits_template_ids.add(bom_data['product_tmpl_id'][0])
for product in self:
product.is_kits = (product.id in kits_product_ids or product.product_tmpl_id.id in kits_template_ids)
def _compute_show_qty_status_button(self):
super()._compute_show_qty_status_button()
for product in self:
if product.is_kits:
product.show_on_hand_qty_status_button = True
product.show_forecasted_qty_status_button = False
def _compute_used_in_bom_count(self):
for product in self:
product.used_in_bom_count = self.env['mrp.bom'].search_count([('bom_line_ids.product_id', '=', product.id)])
+3
View File
@@ -13,6 +13,9 @@
<field name="produce_delay" class="oe_inline"/> days
</div>
</xpath>
<xpath expr="//field[@name='product_variant_count']" position="after">
<field name="is_kits" invisible="1"/>
</xpath>
</field>
</record>
+14
View File
@@ -97,6 +97,13 @@ class Product(models.Model):
compute='_compute_nbr_reordering_rules', compute_sudo=False)
putaway_rule_ids = fields.One2many('stock.putaway.rule', 'product_id', 'Putaway Rules')
storage_category_capacity_ids = fields.One2many('stock.storage.category.capacity', 'product_id', 'Storage Category Capacity')
show_on_hand_qty_status_button = fields.Boolean(compute='_compute_show_qty_status_button')
show_forecasted_qty_status_button = fields.Boolean(compute='_compute_show_qty_status_button')
def _compute_show_qty_status_button(self):
for product in self:
product.show_on_hand_qty_status_button = product.product_tmpl_id.show_on_hand_qty_status_button
product.show_forecasted_qty_status_button = product.product_tmpl_id.show_forecasted_qty_status_button
@api.depends('stock_move_ids.product_qty', 'stock_move_ids.state')
@api.depends_context(
@@ -664,6 +671,13 @@ class ProductTemplate(models.Model):
route_from_categ_ids = fields.Many2many(
relation="stock.location.route", string="Category Routes",
related='categ_id.total_route_ids', related_sudo=False)
show_on_hand_qty_status_button = fields.Boolean(compute='_compute_show_qty_status_button')
show_forecasted_qty_status_button = fields.Boolean(compute='_compute_show_qty_status_button')
def _compute_show_qty_status_button(self):
for template in self:
template.show_on_hand_qty_status_button = template.type == 'product'
template.show_forecasted_qty_status_button = template.type == 'product'
@api.depends('type')
def _compute_has_available_route_ids(self):
+9 -4
View File
@@ -250,10 +250,13 @@
attrs="{'invisible': [('type', '!=', 'product')]}"/>
</header>
<div name="button_box" position="inside">
<field name="show_on_hand_qty_status_button" invisible="1"/>
<field name="show_forecasted_qty_status_button" invisible="1"/>
<button class="oe_stat_button"
name="action_open_quants"
icon="fa-cubes"
type="object" attrs="{'invisible':[('type', '!=', 'product')]}">
type="object"
attrs="{'invisible':[('show_on_hand_qty_status_button', '=', False)]}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="qty_available" widget="statinfo" nolabel="1" class="mr4"/>
@@ -264,7 +267,7 @@
</button>
<button type="object"
name="action_product_forecast_report"
attrs="{'invisible':[('type', '!=', 'product')]}"
attrs="{'invisible':[('show_forecasted_qty_status_button', '=', False)]}"
context="{'default_product_id': id}"
class="oe_stat_button" icon="fa-cubes">
<div class="o_field_widget o_stat_info">
@@ -352,9 +355,11 @@
attrs="{'invisible': [('type', '!=', 'product')]}"/>
</header>
<div name="button_box" position="inside">
<field name="show_on_hand_qty_status_button" invisible="1"/>
<field name="show_forecasted_qty_status_button" invisible="1"/>
<button type="object"
name="action_open_quants"
attrs="{'invisible':[('type', '!=', 'product')]}"
attrs="{'invisible':[('show_on_hand_qty_status_button', '=', False)]}"
class="oe_stat_button" icon="fa-cubes">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value" widget="statinfo">
@@ -366,7 +371,7 @@
</button>
<button type="object"
name="action_product_tmpl_forecast_report"
attrs="{'invisible':[('type', '!=', 'product')]}"
attrs="{'invisible':[('show_forecasted_qty_status_button', '=', False)]}"
context="{'default_product_tmpl_id': id}"
class="oe_stat_button" icon="fa-cubes">
<div class="o_field_widget o_stat_info">