[IMP] mrp_multi_level_estimate: Estimates and other demand sources strategy

Introduces a new concept to define the strategy to apply when estimates
coexist with other sources of demand.
This commit is contained in:
Lois Rilo
2022-12-19 13:27:35 +01:00
parent 2c301c3c5e
commit 86d8c0b8b0
7 changed files with 188 additions and 6 deletions

View File

@@ -12,7 +12,7 @@
"website": "https://github.com/OCA/manufacture",
"category": "Manufacturing",
"depends": ["mrp_multi_level", "stock_demand_estimate_matrix"],
"data": ["views/product_mrp_area_views.xml"],
"data": ["views/product_mrp_area_views.xml", "views/mrp_area_views.xml"],
"installable": True,
"application": False,
"auto_install": True,

View File

@@ -1 +1,2 @@
from . import product_mrp_area
from . import mrp_area

View File

@@ -0,0 +1,36 @@
# Copyright 2022 ForgeFlow S.L. (http://www.forgeflow.com)
# - Lois Rilo Antelo <lois.rilo@forgeflow.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import fields, models
class MRPArea(models.Model):
_inherit = "mrp.area"
estimate_demand_and_other_sources_strat = fields.Selection(
string="Demand Estimates and Other Demand Sources Strategy",
selection=[
("all", "Always consider all sources"),
(
"ignore_others_if_estimates",
"Ignore other sources for products with estimates",
),
(
"ignore_overlapping",
"Ignore other sources during periods with estimates",
),
],
default="all",
help="Define the strategy to follow in MRP multi level when there is a"
"coexistence of demand from demand estimates and other sources.\n"
"* Always consider all sources: nothing is excluded or ignored.\n"
"* Ignore other sources for products with estimates: When there "
"are estimates entered for product and they are in a present or "
"future period, all other sources of demand are ignored for those "
"products.\n"
"* Ignore other sources during periods with estimates: When "
"you create demand estimates for a period and product, "
"other sources of demand will be ignored during that period "
"for those products.",
)

View File

@@ -14,7 +14,7 @@ class ProductMRPArea(models.Model):
help="The days to group your estimates as demand for the MRP."
"It can be different from the length of the date ranges you "
"use in the estimates but it should not be greater, in that case"
"only grouping until the total lenght of the date range will be done.",
"only grouping until the total length of the date range will be done.",
)
_sql_constraints = [

View File

@@ -55,6 +55,8 @@ class TestMrpMultiLevelEstimate(TestMrpMultiLevelCommon):
}
)
generator.action_apply()
cls.date_within_ranges = today - timedelta(days=2)
cls.date_without_ranges = today + timedelta(days=150)
# Create Demand Estimates:
ranges = cls.env["date.range"].search([("type_id", "=", cls.dr_type.id)])
@@ -152,7 +154,9 @@ class TestMrpMultiLevelEstimate(TestMrpMultiLevelCommon):
def test_03_group_demand_estimates(self):
"""Test demand grouping functionality, `group_estimate_days`."""
self.test_mrp_parameter.group_estimate_days = 7
self.mrp_multi_level_wiz.create({}).run_mrp_multi_level()
self.mrp_multi_level_wiz.create(
{"mrp_area_ids": [(6, 0, self.estimate_area.ids)]}
).run_mrp_multi_level()
estimates = self.estimate_obj.search(
[
("product_id", "=", self.prod_test.id),
@@ -177,7 +181,9 @@ class TestMrpMultiLevelEstimate(TestMrpMultiLevelCommon):
# Test group_estimate_days greater than date range, it should not
# generate greater demand.
self.test_mrp_parameter.group_estimate_days = 10
self.mrp_multi_level_wiz.create({}).run_mrp_multi_level()
self.mrp_multi_level_wiz.create(
{"mrp_area_ids": [(6, 0, self.estimate_area.ids)]}
).run_mrp_multi_level()
moves = self.mrp_move_obj.search(
[
("product_id", "=", self.prod_test.id),
@@ -192,7 +198,9 @@ class TestMrpMultiLevelEstimate(TestMrpMultiLevelCommon):
# Test group_estimate_days smaller than date range, it should not
# generate greater demand.
self.test_mrp_parameter.group_estimate_days = 5
self.mrp_multi_level_wiz.create({}).run_mrp_multi_level()
self.mrp_multi_level_wiz.create(
{"mrp_area_ids": [(6, 0, self.estimate_area.ids)]}
).run_mrp_multi_level()
moves = self.mrp_move_obj.search(
[
("product_id", "=", self.prod_test.id),
@@ -229,7 +237,9 @@ class TestMrpMultiLevelEstimate(TestMrpMultiLevelCommon):
estimate.product_uom_qty = qty
qty += 100
self.mrp_multi_level_wiz.create({}).run_mrp_multi_level()
self.mrp_multi_level_wiz.create(
{"mrp_area_ids": [(6, 0, self.estimate_area.ids)]}
).run_mrp_multi_level()
moves = self.mrp_move_obj.search(
[
("product_id", "=", self.prod_test.id),
@@ -248,3 +258,86 @@ class TestMrpMultiLevelEstimate(TestMrpMultiLevelCommon):
self.assertEqual(moves_from_estimates[1].mrp_qty, -500)
# 600 weekly -> 85.71 daily -> 85.71 * 7 = 600
self.assertEqual(moves_from_estimates[2].mrp_qty, -600)
def test_05_estimate_and_other_sources_strat(self):
"""Tests demand estimates and other sources strategies."""
estimates = self.estimate_obj.search(
[
("product_id", "=", self.prod_test.id),
("location_id", "=", self.estimate_loc.id),
]
)
self.assertEqual(len(estimates), 3)
self._create_picking_out(
self.prod_test, 25, self.date_within_ranges, location=self.estimate_loc
)
self._create_picking_out(
self.prod_test, 25, self.date_without_ranges, location=self.estimate_loc
)
# 1. "all"
self.estimate_area.estimate_demand_and_other_sources_strat = "all"
self.mrp_multi_level_wiz.create(
{"mrp_area_ids": [(6, 0, self.estimate_area.ids)]}
).run_mrp_multi_level()
moves = self.mrp_move_obj.search(
[
("product_id", "=", self.prod_test.id),
("mrp_area_id", "=", self.estimate_area.id),
]
)
# 3 weeks - 3 days in the past = 18 days of valid estimates:
demand_from_estimates = moves.filtered(
lambda m: m.mrp_type == "d" and m.mrp_origin == "fc"
)
demand_from_other_sources = moves.filtered(
lambda m: m.mrp_type == "d" and m.mrp_origin != "fc"
)
self.assertEqual(len(demand_from_estimates), 18)
self.assertEqual(len(demand_from_other_sources), 2)
# 2. "ignore_others_if_estimates"
self.estimate_area.estimate_demand_and_other_sources_strat = (
"ignore_others_if_estimates"
)
self.mrp_multi_level_wiz.create(
{"mrp_area_ids": [(6, 0, self.estimate_area.ids)]}
).run_mrp_multi_level()
moves = self.mrp_move_obj.search(
[
("product_id", "=", self.prod_test.id),
("mrp_area_id", "=", self.estimate_area.id),
]
)
demand_from_estimates = moves.filtered(
lambda m: m.mrp_type == "d" and m.mrp_origin == "fc"
)
demand_from_other_sources = moves.filtered(
lambda m: m.mrp_type == "d" and m.mrp_origin != "fc"
)
self.assertEqual(len(demand_from_estimates), 18)
self.assertEqual(len(demand_from_other_sources), 0)
# 3. "ignore_overlapping"
self.estimate_area.estimate_demand_and_other_sources_strat = (
"ignore_overlapping"
)
self.mrp_multi_level_wiz.create(
{"mrp_area_ids": [(6, 0, self.estimate_area.ids)]}
).run_mrp_multi_level()
moves = self.mrp_move_obj.search(
[
("product_id", "=", self.prod_test.id),
("mrp_area_id", "=", self.estimate_area.id),
]
)
demand_from_estimates = moves.filtered(
lambda m: m.mrp_type == "d" and m.mrp_origin == "fc"
)
demand_from_other_sources = moves.filtered(
lambda m: m.mrp_type == "d" and m.mrp_origin != "fc"
)
self.assertEqual(len(demand_from_estimates), 18)
self.assertEqual(len(demand_from_other_sources), 1)
self.assertEqual(
demand_from_other_sources.mrp_date, self.date_without_ranges.date()
)

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" ?>
<odoo>
<record id="mrp_area_form" model="ir.ui.view">
<field name="name">mrp.area.form - mrp_multi_level_estimate</field>
<field name="model">mrp.area</field>
<field name="inherit_id" ref="mrp_multi_level.mrp_area_form" />
<field name="arch" type="xml">
<group name="settings" position="inside">
<field name="estimate_demand_and_other_sources_strat" />
</group>
</field>
</record>
</odoo>

View File

@@ -88,3 +88,42 @@ class MultiLevelMrp(models.TransientModel):
self.env["mrp.move"].create(mrp_move_data)
mrp_date += delta
return res
def _exclude_considering_estimate_demand_and_other_sources_strat(
self, product_mrp_area, mrp_date
):
estimate_strat = (
product_mrp_area.mrp_area_id.estimate_demand_and_other_sources_strat
)
if estimate_strat == "all":
return False
domain = self._estimates_domain(product_mrp_area)
estimates = self.env["stock.demand.estimate"].search(domain)
if not estimates:
return False
if estimate_strat == "ignore_others_if_estimates":
# Ignore
return True
if estimate_strat == "ignore_overlapping":
for estimate in estimates:
if mrp_date >= estimate.date_from and mrp_date <= estimate.date_to:
# Ignore
return True
return False
@api.model
def _prepare_mrp_move_data_from_stock_move(
self, product_mrp_area, move, direction="in"
):
res = super()._prepare_mrp_move_data_from_stock_move(
product_mrp_area, move, direction=direction
)
if direction == "out":
mrp_date = res.get("mrp_date")
if self._exclude_considering_estimate_demand_and_other_sources_strat(
product_mrp_area, mrp_date
):
return False
return res