diff --git a/mrp_multi_level/demo/mrp_bom_demo.xml b/mrp_multi_level/demo/mrp_bom_demo.xml
index b71257be6..d2a83a6d3 100644
--- a/mrp_multi_level/demo/mrp_bom_demo.xml
+++ b/mrp_multi_level/demo/mrp_bom_demo.xml
@@ -44,6 +44,28 @@
+
+
+
+
+ 5
+
+
+
+ 2
+
+ 5
+
+
+
+
+ 2
+
+ 5
+
+
+
5
+
+
+
+
+ phantom
+ 5
+
+
+
+ 1
+
+ 5
+
+
+
+
+ 3
+
+ 5
+
+
diff --git a/mrp_multi_level/demo/product_mrp_area_demo.xml b/mrp_multi_level/demo/product_mrp_area_demo.xml
index 64f129f2f..9f6b71efa 100644
--- a/mrp_multi_level/demo/product_mrp_area_demo.xml
+++ b/mrp_multi_level/demo/product_mrp_area_demo.xml
@@ -8,6 +8,10 @@
+
+
+
+
@@ -32,6 +36,10 @@
+
+
+
+
@@ -40,6 +48,14 @@
+
+
+
+
+
+
+
+
diff --git a/mrp_multi_level/demo/product_product_demo.xml b/mrp_multi_level/demo/product_product_demo.xml
index 8c0b77248..fdfb3d97c 100644
--- a/mrp_multi_level/demo/product_product_demo.xml
+++ b/mrp_multi_level/demo/product_product_demo.xml
@@ -20,6 +20,16 @@
+
+ FP-3
+
+ product
+
+
+ 3
+
+
+
@@ -43,6 +53,15 @@
3
+
+ SF-3
+
+ product
+
+
+ 3
+
+
PP-1
@@ -62,6 +81,24 @@
+
+ PP-3
+
+ product
+
+
+
+
+
+
+ PP-4
+
+ product
+
+
+
+
+
AV-11 steel
diff --git a/mrp_multi_level/demo/product_supplierinfo_demo.xml b/mrp_multi_level/demo/product_supplierinfo_demo.xml
index 2a6921240..4821b5464 100644
--- a/mrp_multi_level/demo/product_supplierinfo_demo.xml
+++ b/mrp_multi_level/demo/product_supplierinfo_demo.xml
@@ -44,4 +44,20 @@
0
100
+
+
+
+
+ 2
+ 0
+ 10
+
+
+
+
+
+ 3
+ 0
+ 80
+
diff --git a/mrp_multi_level/models/product_mrp_area.py b/mrp_multi_level/models/product_mrp_area.py
index 02c0c41c6..ec2aaf157 100644
--- a/mrp_multi_level/models/product_mrp_area.py
+++ b/mrp_multi_level/models/product_mrp_area.py
@@ -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']
diff --git a/mrp_multi_level/tests/common.py b/mrp_multi_level/tests/common.py
index 237f67907..98c85e868 100644
--- a/mrp_multi_level/tests/common.py
+++ b/mrp_multi_level/tests/common.py
@@ -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,
diff --git a/mrp_multi_level/tests/test_mrp_multi_level.py b/mrp_multi_level/tests/test_mrp_multi_level.py
index eee8de3a3..543e66d6b 100644
--- a/mrp_multi_level/tests/test_mrp_multi_level.py
+++ b/mrp_multi_level/tests/test_mrp_multi_level.py
@@ -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)
diff --git a/mrp_multi_level/wizards/mrp_multi_level.py b/mrp_multi_level/wizards/mrp_multi_level.py
index 485e26bf5..03b4a03bd 100644
--- a/mrp_multi_level/wizards/mrp_multi_level.py
+++ b/mrp_multi_level/wizards/mrp_multi_level.py
@@ -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')