mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
[FIX] mrp_multi_level: Manage Kits in MRP Multi Level
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user