[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:
@@ -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)])
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user