mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
@@ -413,13 +413,15 @@ class MultiLevelMrp(models.TransientModel):
|
||||
move_data = self._prepare_mrp_move_data_from_stock_move(
|
||||
product_mrp_area, move, direction="in"
|
||||
)
|
||||
mrp_move_obj.create(move_data)
|
||||
if move_data:
|
||||
mrp_move_obj.create(move_data)
|
||||
if out_moves:
|
||||
for move in out_moves:
|
||||
move_data = self._prepare_mrp_move_data_from_stock_move(
|
||||
product_mrp_area, move, direction="out"
|
||||
)
|
||||
mrp_move_obj.create(move_data)
|
||||
if move_data:
|
||||
mrp_move_obj.create(move_data)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -10,9 +10,9 @@ MRP Multi Level Estimate
|
||||
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
|
||||
:target: https://odoo-community.org/page/development-status
|
||||
:alt: Beta
|
||||
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
|
||||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||
:alt: License: AGPL-3
|
||||
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
|
||||
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
|
||||
:alt: License: LGPL-3
|
||||
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fmanufacture-lightgray.png?logo=github
|
||||
:target: https://github.com/OCA/manufacture/tree/13.0/mrp_multi_level_estimate
|
||||
:alt: OCA/manufacture
|
||||
@@ -23,7 +23,7 @@ MRP Multi Level Estimate
|
||||
:target: https://runbot.odoo-community.org/runbot/129/13.0
|
||||
:alt: Try me on Runbot
|
||||
|
||||
|badge1| |badge2| |badge3| |badge4| |badge5|
|
||||
|badge1| |badge2| |badge3| |badge4| |badge5|
|
||||
|
||||
Integration for MRP Multi Level and `Stock Demand Estimates <https://github.com/OCA/stock-logistics-warehouse/tree/12.0/stock_demand_estimate>`_ system.
|
||||
|
||||
@@ -84,7 +84,7 @@ promote its widespread use.
|
||||
|
||||
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|
||||
|
||||
|maintainer-LoisRForgeFlow|
|
||||
|maintainer-LoisRForgeFlow|
|
||||
|
||||
This module is part of the `OCA/manufacture <https://github.com/OCA/manufacture/tree/13.0/mrp_multi_level_estimate>`_ project on GitHub.
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
# Copyright 2019-20 ForgeFlow S.L. (http://www.forgeflow.com)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/Agpl.html).
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
{
|
||||
"name": "MRP Multi Level Estimate",
|
||||
"version": "13.0.1.0.2",
|
||||
"development_status": "Beta",
|
||||
"license": "AGPL-3",
|
||||
"license": "LGPL-3",
|
||||
"author": "ForgeFlow, Odoo Community Association (OCA)",
|
||||
"maintainers": ["LoisRForgeFlow"],
|
||||
"summary": "Allows to consider demand estimates using MRP multi level.",
|
||||
"website": "https://github.com/OCA/manufacture",
|
||||
"category": "Manufacturing",
|
||||
"depends": ["mrp_multi_level", "stock_demand_estimate_matrix"],
|
||||
"data": ["views/product_mrp_area_views.xml"],
|
||||
"depends": ["mrp_multi_level", "stock_demand_estimate"],
|
||||
"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 LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.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.",
|
||||
)
|
||||
@@ -1,6 +1,6 @@
|
||||
# Copyright 2019-20 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).
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -367,7 +367,7 @@ ul.auto-toc {
|
||||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
||||
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/manufacture/tree/13.0/mrp_multi_level_estimate"><img alt="OCA/manufacture" src="https://img.shields.io/badge/github-OCA%2Fmanufacture-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/manufacture-13-0/manufacture-13-0-mrp_multi_level_estimate"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/129/13.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
|
||||
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/manufacture/tree/13.0/mrp_multi_level_estimate"><img alt="OCA/manufacture" src="https://img.shields.io/badge/github-OCA%2Fmanufacture-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/manufacture-13-0/manufacture-13-0-mrp_multi_level_estimate"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/129/13.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
|
||||
<p>Integration for MRP Multi Level and <a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/tree/12.0/stock_demand_estimate">Stock Demand Estimates</a> system.</p>
|
||||
<p><strong>Table of contents</strong></p>
|
||||
<div class="contents local topic" id="contents">
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
# Copyright 2018-20 ForgeFlow S.L. (http://www.forgeflow.com)
|
||||
# Copyright 2018-22 ForgeFlow S.L. (http://www.forgeflow.com)
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from dateutil.rrule import WEEKLY
|
||||
|
||||
from odoo.addons.mrp_multi_level.tests.common import TestMrpMultiLevelCommon
|
||||
|
||||
|
||||
@@ -39,42 +37,38 @@ class TestMrpMultiLevelEstimate(TestMrpMultiLevelCommon):
|
||||
}
|
||||
)
|
||||
|
||||
# Create Date Ranges:
|
||||
cls.dr_type = cls.env["date.range.type"].create(
|
||||
{"name": "Weeks", "company_id": False, "allow_overlap": False}
|
||||
)
|
||||
# Create 3 consecutive estimates of 1 week length each.
|
||||
today = datetime.today().replace(hour=0)
|
||||
generator = cls.env["date.range.generator"].create(
|
||||
{
|
||||
"date_start": today - timedelta(days=3),
|
||||
"name_prefix": "W-",
|
||||
"type_id": cls.dr_type.id,
|
||||
"duration_count": 1,
|
||||
"unit_of_time": str(WEEKLY),
|
||||
"count": 3,
|
||||
}
|
||||
)
|
||||
generator.action_apply()
|
||||
date_start_1 = today - timedelta(days=3)
|
||||
date_end_1 = date_start_1 + timedelta(days=6)
|
||||
date_start_2 = date_end_1 + timedelta(days=1)
|
||||
date_end_2 = date_start_2 + timedelta(days=6)
|
||||
date_start_3 = date_end_2 + timedelta(days=1)
|
||||
date_end_3 = date_start_3 + timedelta(days=6)
|
||||
start_dates = [date_start_1, date_start_2, date_start_3]
|
||||
end_dates = [date_end_1, date_end_2, date_end_3]
|
||||
|
||||
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)])
|
||||
qty = 140.0
|
||||
for dr in ranges:
|
||||
for sd, ed in zip(start_dates, end_dates):
|
||||
qty += 70.0
|
||||
cls._create_demand_estimate(cls.prod_test, cls.stock_location, dr, qty)
|
||||
cls._create_demand_estimate(cls.prod_test, cls.estimate_loc, dr, qty)
|
||||
cls._create_demand_estimate(cls.prod_test, cls.stock_location, sd, ed, qty)
|
||||
cls._create_demand_estimate(cls.prod_test, cls.estimate_loc, sd, ed, qty)
|
||||
|
||||
cls.mrp_multi_level_wiz.create({}).run_mrp_multi_level()
|
||||
|
||||
@classmethod
|
||||
def _create_demand_estimate(cls, product, location, date_range, qty):
|
||||
def _create_demand_estimate(cls, product, location, date_from, date_to, qty):
|
||||
cls.estimate_obj.create(
|
||||
{
|
||||
"product_id": product.id,
|
||||
"location_id": location.id,
|
||||
"product_uom": product.uom_id.id,
|
||||
"product_uom_qty": qty,
|
||||
"date_range_id": date_range.id,
|
||||
"manual_date_from": date_from,
|
||||
"manual_date_to": date_to,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -152,7 +146,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 +173,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 +190,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 +229,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 +250,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>
|
||||
@@ -1,6 +1,6 @@
|
||||
# Copyright 2019-20 ForgeFlow S.L. (http://www.forgeflow.com)
|
||||
# - Lois Rilo <lois.rilo@forgeflow.com>
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
@@ -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