mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
[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:
@@ -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,
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
from . import product_mrp_area
|
||||
from . import mrp_area
|
||||
|
||||
36
mrp_multi_level_estimate/models/mrp_area.py
Normal file
36
mrp_multi_level_estimate/models/mrp_area.py
Normal 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.",
|
||||
)
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
|
||||
13
mrp_multi_level_estimate/views/mrp_area_views.xml
Normal file
13
mrp_multi_level_estimate/views/mrp_area_views.xml
Normal 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>
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user