[FIX] mrp_multi_level: Manage Kits in MRP Multi Level

This commit is contained in:
Bernat Puig Font
2022-09-05 17:47:59 +02:00
parent 72529ea238
commit abc97d5bfb
8 changed files with 200 additions and 25 deletions

View File

@@ -44,6 +44,28 @@
<field name="bom_id" ref="mrp_bom_fp_2"/>
</record>
<!-- FP-3 -->
<record id="mrp_bom_fp_3" model="mrp.bom">
<field name="product_tmpl_id"
ref="product_product_fp_3_product_template"/>
<field name="product_uom_id" ref="uom.product_uom_unit"/>
<field name="sequence">5</field>
</record>
<record id="mrp_bom_fp_3_line_sf_3" model="mrp.bom.line">
<field name="product_id" ref="product_product_sf_3"/>
<field name="product_qty">2</field>
<field name="product_uom_id" ref="uom.product_uom_unit"/>
<field name="sequence">5</field>
<field name="bom_id" ref="mrp_bom_fp_3"/>
</record>
<record id="mrp_bom_fp_3_line_pp_3" model="mrp.bom.line">
<field name="product_id" ref="product_product_pp_3"/>
<field name="product_qty">2</field>
<field name="product_uom_id" ref="uom.product_uom_unit"/>
<field name="sequence">5</field>
<field name="bom_id" ref="mrp_bom_fp_3"/>
</record>
<!-- Customizable Desk -->
<record id="mrp_bom_product_4" model="mrp.bom">
<field name="product_tmpl_id"
@@ -119,4 +141,26 @@
<field name="sequence">5</field>
<field name="bom_id" ref="mrp_bom_sf_2"/>
</record>
<!-- SF-3 -->
<record id="mrp_bom_sf_3" model="mrp.bom">
<field name="product_tmpl_id"
ref="product_product_sf_3_product_template"/>
<field name="product_uom_id" ref="uom.product_uom_unit"/>
<field name="type">phantom</field>
<field name="sequence">5</field>
</record>
<record id="mrp_bom_sf_3_line_pp_3" model="mrp.bom.line">
<field name="product_id" ref="product_product_pp_3"/>
<field name="product_qty">1</field>
<field name="product_uom_id" ref="uom.product_uom_unit"/>
<field name="sequence">5</field>
<field name="bom_id" ref="mrp_bom_sf_3"/>
</record>
<record id="mrp_bom_sf_3_line_pp_4" model="mrp.bom.line">
<field name="product_id" ref="product_product_pp_4"/>
<field name="product_qty">3</field>
<field name="product_uom_id" ref="uom.product_uom_unit"/>
<field name="sequence">5</field>
<field name="bom_id" ref="mrp_bom_sf_3"/>
</record>
</odoo>

View File

@@ -8,6 +8,10 @@
<field name="product_id" ref="product_product_fp_2"/>
<field name="mrp_area_id" ref="mrp_area_stock_wh0"/>
</record>
<record id="product_mrp_area_fp_3" model="product.mrp.area">
<field name="product_id" ref="product_product_fp_3"/>
<field name="mrp_area_id" ref="mrp_area_stock_wh0"/>
</record>
<record id="product_mrp_area_product_4" model="product.mrp.area">
<field name="product_id" ref="product.product_product_4"/>
<field name="mrp_area_id" ref="mrp_area_stock_wh0"/>
@@ -32,6 +36,10 @@
<field name="product_id" ref="product_product_sf_2"/>
<field name="mrp_area_id" ref="mrp_area_stock_wh0"/>
</record>
<record id="product_mrp_area_sf_3" model="product.mrp.area">
<field name="product_id" ref="product_product_sf_3"/>
<field name="mrp_area_id" ref="mrp_area_stock_wh0"/>
</record>
<record id="product_mrp_area_pp_1" model="product.mrp.area">
<field name="product_id" ref="product_product_pp_1"/>
<field name="mrp_area_id" ref="mrp_area_stock_wh0"/>
@@ -40,6 +48,14 @@
<field name="product_id" ref="product_product_pp_2"/>
<field name="mrp_area_id" ref="mrp_area_stock_wh0"/>
</record>
<record id="product_mrp_area_pp_3" model="product.mrp.area">
<field name="product_id" ref="product_product_pp_3"/>
<field name="mrp_area_id" ref="mrp_area_stock_wh0"/>
</record>
<record id="product_mrp_area_pp_4" model="product.mrp.area">
<field name="product_id" ref="product_product_pp_4"/>
<field name="mrp_area_id" ref="mrp_area_stock_wh0"/>
</record>
<record id="product_mrp_area_av_11" model="product.mrp.area">
<field name="product_id" ref="product_product_av_11"/>
<field name="mrp_area_id" ref="mrp_area_stock_wh0"/>

View File

@@ -20,6 +20,16 @@
<field name="route_ids" eval="[(6, 0, [ref('mrp.route_warehouse0_manufacture')])]"/>
</record>
<record id="product_product_fp_3" model="product.product">
<field name="name">FP-3</field>
<field name="categ_id" ref="product_category_mrp"/>
<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="produce_delay">3</field>
<field name="route_ids" eval="[(6, 0, [ref('mrp.route_warehouse0_manufacture')])]"/>
</record>
<!-- Customizable Desk -->
<record id="product.product_product_4_product_template" model="product.template">
<field name="route_ids" eval="[(6, 0, [ref('mrp.route_warehouse0_manufacture')])]"/>
@@ -43,6 +53,15 @@
<field name="produce_delay">3</field>
<field name="route_ids" eval="[(6, 0, [ref('mrp.route_warehouse0_manufacture')])]"/>
</record>
<record id="product_product_sf_3" model="product.product">
<field name="name">SF-3</field>
<field name="categ_id" ref="product_category_mrp"/>
<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="produce_delay">3</field>
<field name="route_ids" eval="[(6, 0, [ref('mrp.route_warehouse0_manufacture')])]"/>
</record>
<record id="product_product_pp_1" model="product.product">
<field name="name">PP-1</field>
@@ -62,6 +81,24 @@
<field name="route_ids" eval="[(6, 0, [ref('purchase_stock.route_warehouse0_buy')])]"/>
</record>
<record id="product_product_pp_3" model="product.product">
<field name="name">PP-3</field>
<field name="categ_id" ref="product_category_mrp"/>
<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="route_ids" eval="[(6, 0, [ref('purchase_stock.route_warehouse0_buy')])]"/>
</record>
<record id="product_product_pp_4" model="product.product">
<field name="name">PP-4</field>
<field name="categ_id" ref="product_category_mrp"/>
<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="route_ids" eval="[(6, 0, [ref('purchase_stock.route_warehouse0_buy')])]"/>
</record>
<record id="product_product_av_11" model="product.product">
<field name="name">AV-11 steel</field>
<field name="categ_id" ref="product_category_mrp"/>

View File

@@ -44,4 +44,20 @@
<field name="min_qty">0</field>
<field name="price">100</field>
</record>
<record id="product_supplierinfo_pp_3" model="product.supplierinfo">
<field name="product_tmpl_id" ref="product_product_pp_3_product_template"/>
<field name="name" ref="res_partner_lazer_tech"/>
<field name="delay">2</field>
<field name="min_qty">0</field>
<field name="price">10</field>
</record>
<record id="product_supplierinfo_pp_4" model="product.supplierinfo">
<field name="product_tmpl_id" ref="product_product_pp_4_product_template"/>
<field name="name" ref="res_partner_lazer_tech"/>
<field name="delay">3</field>
<field name="min_qty">0</field>
<field name="price">80</field>
</record>
</odoo>

View File

@@ -87,6 +87,7 @@ class ProductMRPArea(models.Model):
selection=[('buy', 'Buy'),
('none', 'Undefined'),
('manufacture', 'Produce'),
('phantom', 'Kit'),
('pull', 'Pull From'),
('push', 'Push To'),
('pull_push', 'Pull & Push')],
@@ -177,7 +178,12 @@ class ProductMRPArea(models.Model):
# TODO: better way to get company
}
rule = group_obj._get_rule(rec.product_id, proc_loc, values)
rec.supply_method = rule.action if rule else 'none'
if rule.action == 'manufacture' and \
rec.product_id.product_tmpl_id.bom_ids and \
rec.product_id.product_tmpl_id.bom_ids[0].type == 'phantom':
rec.supply_method = 'phantom'
else:
rec.supply_method = rule.action if rule else 'none'
@api.multi
@api.depends('supply_method', 'product_id.route_ids',
@@ -224,4 +230,4 @@ class ProductMRPArea(models.Model):
@api.multi
def _to_be_exploded(self):
self.ensure_one()
return self.supply_method == 'manufacture'
return self.supply_method in ['manufacture', 'phantom']

View File

@@ -29,10 +29,14 @@ class TestMrpMultiLevelCommon(SavepointCase):
cls.fp_1 = cls.env.ref('mrp_multi_level.product_product_fp_1')
cls.fp_2 = cls.env.ref('mrp_multi_level.product_product_fp_2')
cls.fp_3 = cls.env.ref('mrp_multi_level.product_product_fp_3')
cls.sf_1 = cls.env.ref('mrp_multi_level.product_product_sf_1')
cls.sf_2 = cls.env.ref('mrp_multi_level.product_product_sf_2')
cls.sf_3 = cls.env.ref('mrp_multi_level.product_product_sf_3')
cls.pp_1 = cls.env.ref('mrp_multi_level.product_product_pp_1')
cls.pp_2 = cls.env.ref('mrp_multi_level.product_product_pp_2')
cls.pp_3 = cls.env.ref('mrp_multi_level.product_product_pp_3')
cls.pp_4 = cls.env.ref('mrp_multi_level.product_product_pp_4')
cls.product_4b = cls.env.ref('product.product_product_4b')
cls.av_11 = cls.env.ref('mrp_multi_level.product_product_av_11')
cls.av_12 = cls.env.ref('mrp_multi_level.product_product_av_12')
@@ -222,6 +226,16 @@ class TestMrpMultiLevelCommon(SavepointCase):
'location_id': cls.stock_location.id,
'location_dest_id': cls.customer_location.id
}),
(0, 0, {
'name': 'Test move fp-3',
'product_id': cls.fp_3.id,
'date_expected': date_move,
'date': date_move,
'product_uom': cls.fp_3.uom_id.id,
'product_uom_qty': 5,
'location_id': cls.stock_location.id,
'location_dest_id': cls.customer_location.id
}),
(0, 0, {
'name': 'Test move product-4b',
'product_id': cls.product_4b.id,

View File

@@ -328,3 +328,42 @@ class TestMrpMultiLevel(TestMrpMultiLevelCommon):
)
self.assertEqual(len(inventory), 1)
self.assertEqual(inventory.date, date_move.date())
def test_15_phantom_comp_planning(self):
"""
Phantom components will not appear in MRP Inventory or Planned Orders.
MRP Parameter will have 'phantom' supply method.
"""
# SF-3
sf_3_line_1 = self.mrp_inventory_obj.search([
('product_mrp_area_id.product_id', '=', self.sf_3.id)
])
self.assertEqual(len(sf_3_line_1), 0)
sf_3_planned_order_1 = self.planned_order_obj.search([
('product_mrp_area_id.product_id', '=', self.sf_3.id)
])
self.assertEqual(len(sf_3_planned_order_1), 0)
sf_3_mrp_parameter = self.product_mrp_area_obj.search([
('product_id', '=', self.sf_3.id)
])
self.assertEqual(sf_3_mrp_parameter.supply_method, 'phantom')
# PP-3
pp_3_line_1 = self.mrp_inventory_obj.search([
('product_mrp_area_id.product_id', '=', self.pp_3.id),
])
self.assertEqual(len(pp_3_line_1), 1)
self.assertEqual(pp_3_line_1.demand_qty, 20.0)
pp_3_planned_orders = self.planned_order_obj.search([
('product_mrp_area_id.product_id', '=', self.pp_3.id),
])
self.assertEqual(len(pp_3_planned_orders), 2)
# PP-4
pp_4_line_1 = self.mrp_inventory_obj.search([
('product_mrp_area_id.product_id', '=', self.pp_4.id),
])
self.assertEqual(len(pp_4_line_1), 1)
self.assertEqual(pp_4_line_1.demand_qty, 30.0)
pp_4_planned_orders = self.planned_order_obj.search([
('product_mrp_area_id.product_id', '=', self.pp_4.id),
])
self.assertEqual(len(pp_4_planned_orders), 1)

View File

@@ -7,6 +7,7 @@
from odoo import api, fields, models, exceptions, _
from datetime import date, timedelta
import logging
logger = logging.getLogger(__name__)
@@ -97,8 +98,8 @@ class MultiLevelMrp(models.TransientModel):
@api.model
def _prepare_planned_order_data(
self, product_mrp_area, qty, mrp_date_supply,
mrp_action_date, name
self, product_mrp_area, qty, mrp_date_supply,
mrp_action_date, name
):
return {
'product_mrp_area_id': product_mrp_area.id,
@@ -169,7 +170,7 @@ class MultiLevelMrp(models.TransientModel):
@api.model
def explode_action(
self, product_mrp_area_id, mrp_action_date, name, qty, action
self, product_mrp_area_id, mrp_action_date, name, qty, action
):
"""Explode requirements."""
mrp_date_demand = mrp_action_date
@@ -189,7 +190,7 @@ class MultiLevelMrp(models.TransientModel):
bomline.product_id.type != 'product':
continue
if self.with_context(mrp_explosion=True)._exclude_from_mrp(
bomline.product_id,
bomline.product_id,
product_mrp_area_id.mrp_area_id):
# Stop explosion.
continue
@@ -211,7 +212,7 @@ class MultiLevelMrp(models.TransientModel):
@api.model
def create_action(
self, product_mrp_area_id, mrp_date, mrp_qty, name, values=None,
self, product_mrp_area_id, mrp_date, mrp_qty, name, values=None,
):
if not values:
values = {}
@@ -225,12 +226,12 @@ class MultiLevelMrp(models.TransientModel):
@api.model
def create_planned_order(
self, product_mrp_area_id, mrp_qty,
name, mrp_date_supply, mrp_action_date, values=None,
self, product_mrp_area_id, mrp_qty,
name, mrp_date_supply, mrp_action_date, values=None,
):
self = self.with_context(auditlog_disabled=True)
if self._exclude_from_mrp(
product_mrp_area_id.product_id,
product_mrp_area_id.product_id,
product_mrp_area_id.mrp_area_id):
values['qty_ordered'] = 0.0
return values
@@ -243,7 +244,10 @@ class MultiLevelMrp(models.TransientModel):
order_data = self._prepare_planned_order_data(
product_mrp_area_id, qty, mrp_date_supply, mrp_action_date,
name)
planned_order = self.env['mrp.planned.order'].create(order_data)
# Do not create planned order for products that are Kits
planned_order = False
if not product_mrp_area_id.supply_method == 'phantom':
planned_order = self.env['mrp.planned.order'].create(order_data)
qty_ordered = qty_ordered + qty
if product_mrp_area_id._to_be_exploded():
@@ -297,7 +301,7 @@ class MultiLevelMrp(models.TransientModel):
products = bom_lines.mapped('product_id')
products.write({'llc': llc})
counter = self.env['product.product'].search_count([('llc', '=',
llc)])
llc)])
log_msg = 'Low level code %s finished - Nbr. products: %s' % (
llc, counter)
logger.info(log_msg)
@@ -383,8 +387,7 @@ class MultiLevelMrp(models.TransientModel):
return True
@api.model
def _prepare_mrp_move_data_from_purchase_order(
self, poline, product_mrp_area):
def _prepare_mrp_move_data_from_purchase_order(self, poline, product_mrp_area):
mrp_date = date.today()
if fields.Date.from_string(poline.date_planned) > date.today():
mrp_date = fields.Date.from_string(poline.date_planned)
@@ -484,12 +487,12 @@ class MultiLevelMrp(models.TransientModel):
if self._exclude_move(move):
continue
if last_date and (
fields.Date.from_string(move.mrp_date)
>= last_date + timedelta(days=grouping_delta)) and (
(onhand + last_qty + move.mrp_qty)
< product_mrp_area.mrp_minimum_stock
or (onhand + last_qty)
< product_mrp_area.mrp_minimum_stock):
fields.Date.from_string(move.mrp_date)
>= last_date + timedelta(days=grouping_delta)) and (
(onhand + last_qty + move.mrp_qty)
< product_mrp_area.mrp_minimum_stock
or (onhand + last_qty)
< product_mrp_area.mrp_minimum_stock):
name = 'Grouped Demand for %d Days' % grouping_delta
qtytoorder = product_mrp_area.mrp_minimum_stock - \
onhand - last_qty
@@ -504,8 +507,8 @@ class MultiLevelMrp(models.TransientModel):
last_qty = 0.00
nbr_create += 1
if (onhand + last_qty + move.mrp_qty) < \
product_mrp_area.mrp_minimum_stock or \
(onhand + last_qty) < \
product_mrp_area.mrp_minimum_stock or \
(onhand + last_qty) < \
product_mrp_area.mrp_minimum_stock:
if not last_date or last_qty == 0.0:
last_date = fields.Date.from_string(move.mrp_date)
@@ -720,9 +723,9 @@ class MultiLevelMrp(models.TransientModel):
for product_mrp_area in product_mrp_area_ids:
# Build the time-phased inventory
if self._exclude_from_mrp(
product_mrp_area.product_id,
product_mrp_area.mrp_area_id):
if self._exclude_from_mrp(product_mrp_area.product_id,
product_mrp_area.mrp_area_id) or \
product_mrp_area.supply_method == 'phantom':
continue
self._init_mrp_inventory(product_mrp_area)
logger.info('End MRP final process')