Merge PR #1273 into 17.0

Signed-off-by LoisRForgeFlow
This commit is contained in:
OCA-git-bot
2024-05-21 07:48:59 +00:00
21 changed files with 1522 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
========================
MRP Multi Level Estimate
========================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:e6d993a40522997f3b0ade9182fb5d90c4d9b889418098456b52fd010f77d5b0
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
:target: https://odoo-community.org/page/development-status
:alt: Production/Stable
.. |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/17.0/mrp_multi_level_estimate
:alt: OCA/manufacture
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/manufacture-17-0/manufacture-17-0-mrp_multi_level_estimate
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/manufacture&target_branch=17.0
:alt: Try me on Runboat
|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.
**Table of contents**
.. contents::
:local:
Configuration
=============
You can edit how to consolidate your estimates as demand at product MRP
area level using the field *Group Days of Estimates*. This number
represents the days to group your estimates as demand for the MRP, e.g:
if set to 7, you will have your estimates (regardless of the date range
used) grouped in weekly demand.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/manufacture/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/manufacture/issues/new?body=module:%20mrp_multi_level_estimate%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
-------
* ForgeFlow
Contributors
------------
- Lois Rilo <lois.rilo@forgeflow.com>
- Pimolnat Suntian<pimolnats@ecosoft.co.th>
Maintainers
-----------
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
.. |maintainer-LoisRForgeFlow| image:: https://github.com/LoisRForgeFlow.png?size=40px
:target: https://github.com/LoisRForgeFlow
:alt: LoisRForgeFlow
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-LoisRForgeFlow|
This module is part of the `OCA/manufacture <https://github.com/OCA/manufacture/tree/17.0/mrp_multi_level_estimate>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1,2 @@
from . import models
from . import wizards

View File

@@ -0,0 +1,19 @@
# Copyright 2019-23 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
{
"name": "MRP Multi Level Estimate",
"version": "17.0.1.0.0",
"development_status": "Production/Stable",
"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"],
"data": ["views/product_mrp_area_views.xml", "views/mrp_area_views.xml"],
"installable": True,
"application": False,
"auto_install": True,
}

View File

@@ -0,0 +1,99 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * mrp_multi_level_estimate
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2023-09-05 22:36+0000\n"
"Last-Translator: Ivorra78 <informatica@totmaterial.es>\n"
"Language-Team: none\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.17\n"
#. module: mrp_multi_level_estimate
#: model:ir.model.fields.selection,name:mrp_multi_level_estimate.selection__mrp_area__estimate_demand_and_other_sources_strat__all
msgid "Always consider all sources"
msgstr "Tenga siempre en cuenta todas las fuentes"
#. module: mrp_multi_level_estimate
#: model:ir.model.fields,help:mrp_multi_level_estimate.field_mrp_area__estimate_demand_and_other_sources_strat
msgid ""
"Define the strategy to follow in MRP multi level when there is acoexistence "
"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."
msgstr ""
"Definir la estrategia a seguir en MRP multinivel cuando hay acoexistencia de "
"demanda procedente de estimaciones de demanda y de otras fuentes.\n"
"* Considerar siempre todas las fuentes: nada se excluye ni se ignora.\n"
"* Ignorar otras fuentes para productos con estimaciones: Cuando hay "
"estimaciones ingresadas para un producto y están en un período presente o "
"futuro, todas las otras fuentes de demanda son ignoradas para esos "
"productos.\n"
"* Ignorar otras fuentes durante periodos con estimaciones: Cuando se crean "
"estimaciones de demanda para un periodo y producto, se ignorarán otras "
"fuentes de demanda durante ese periodo para esos productos."
#. module: mrp_multi_level_estimate
#: model:ir.model.fields,field_description:mrp_multi_level_estimate.field_mrp_area__estimate_demand_and_other_sources_strat
msgid "Demand Estimates and Other Demand Sources Strategy"
msgstr "Estimaciones de la demanda y otras fuentes de demanda Estrategia"
#. module: mrp_multi_level_estimate
#: model:ir.model.fields,field_description:mrp_multi_level_estimate.field_product_mrp_area__group_estimate_days
msgid "Group Days of Estimates"
msgstr "Grupo Días de estimación"
#. module: mrp_multi_level_estimate
#: model:ir.model.constraint,message:mrp_multi_level_estimate.constraint_product_mrp_area_group_estimate_days_check
msgid "Group Days of Estimates must be greater than or equal to zero."
msgstr "Los Días de Estimación del Grupo deben ser mayores o iguales a cero."
#. module: mrp_multi_level_estimate
#: model:ir.model.fields.selection,name:mrp_multi_level_estimate.selection__mrp_area__estimate_demand_and_other_sources_strat__ignore_overlapping
msgid "Ignore other sources during periods with estimates"
msgstr "Ignorar otras fuentes durante los períodos con estimaciones"
#. module: mrp_multi_level_estimate
#: model:ir.model.fields.selection,name:mrp_multi_level_estimate.selection__mrp_area__estimate_demand_and_other_sources_strat__ignore_others_if_estimates
msgid "Ignore other sources for products with estimates"
msgstr "Ignorar otras fuentes para productos con estimaciones"
#. module: mrp_multi_level_estimate
#: model:ir.model,name:mrp_multi_level_estimate.model_mrp_area
msgid "MRP Area"
msgstr "Area MRP"
#. module: mrp_multi_level_estimate
#: model:ir.model,name:mrp_multi_level_estimate.model_mrp_multi_level
msgid "Multi Level MRP"
msgstr "MRP Multi Nivel"
#. module: mrp_multi_level_estimate
#: model:ir.model,name:mrp_multi_level_estimate.model_product_mrp_area
msgid "Product MRP Area"
msgstr "Producto Área MRP"
#. module: mrp_multi_level_estimate
#: model:ir.model.fields,help:mrp_multi_level_estimate.field_product_mrp_area__group_estimate_days
msgid ""
"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 caseonly grouping until the total length of the date "
"range will be done."
msgstr ""
"Los días a agrupar sus estimaciones como demanda para el MRP.Puede ser "
"diferente a la longitud de los rangos de fechas que utilice en las "
"estimaciones pero no debe ser mayor, en ese caso sólo se agrupará hasta la "
"longitud total del rango de fechas."

View File

@@ -0,0 +1,109 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * mrp_multi_level_estimate
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2023-01-13 18:44+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.14.1\n"
#. module: mrp_multi_level_estimate
#: model:ir.model.fields.selection,name:mrp_multi_level_estimate.selection__mrp_area__estimate_demand_and_other_sources_strat__all
msgid "Always consider all sources"
msgstr "Considera sempre tutte le fonti"
#. module: mrp_multi_level_estimate
#: model:ir.model.fields,help:mrp_multi_level_estimate.field_mrp_area__estimate_demand_and_other_sources_strat
msgid ""
"Define the strategy to follow in MRP multi level when there is acoexistence "
"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."
msgstr ""
"Definisce la strategia da seguire con l'MRP multi livello quando c'è la "
"coesistenza di domanda dalle stime e altre fonti.\n"
"* Considera sempre tutte le fonti: niente è escluso o ignorato.\n"
"* Ignora altre fonti quando ci sono stime: quando ci sono stime per il "
"prodotto e sono per periodi presenti o futuri, tutte le altre fonti di "
"richieste vengono ignorate per il prodotto.\n"
"* Ignora altre fonti nel periodo delle stime: quando si crea una richiesta "
"da stime per un periodo e prodotto, altre fonti di richiesta verranno "
"ignorate per il periodo di quel prodotto."
#. module: mrp_multi_level_estimate
#: model:ir.model.fields,field_description:mrp_multi_level_estimate.field_mrp_area__estimate_demand_and_other_sources_strat
msgid "Demand Estimates and Other Demand Sources Strategy"
msgstr "Strategia per la domanda stimata e altre fonti di domanda"
#. module: mrp_multi_level_estimate
#: model:ir.model.fields,field_description:mrp_multi_level_estimate.field_product_mrp_area__group_estimate_days
msgid "Group Days of Estimates"
msgstr "Gruppo giorni delle stime"
#. module: mrp_multi_level_estimate
#: model:ir.model.constraint,message:mrp_multi_level_estimate.constraint_product_mrp_area_group_estimate_days_check
msgid "Group Days of Estimates must be greater than or equal to zero."
msgstr "Il gruppo dei gironi delle stime deve essere maggiore o uguale a zero."
#. module: mrp_multi_level_estimate
#: model:ir.model.fields.selection,name:mrp_multi_level_estimate.selection__mrp_area__estimate_demand_and_other_sources_strat__ignore_overlapping
msgid "Ignore other sources during periods with estimates"
msgstr "Ignora altre fonti nei periodi con stime"
#. module: mrp_multi_level_estimate
#: model:ir.model.fields.selection,name:mrp_multi_level_estimate.selection__mrp_area__estimate_demand_and_other_sources_strat__ignore_others_if_estimates
msgid "Ignore other sources for products with estimates"
msgstr "Ignora altre fonti per prodotti con stime"
#. module: mrp_multi_level_estimate
#: model:ir.model,name:mrp_multi_level_estimate.model_mrp_area
msgid "MRP Area"
msgstr "Area MRP"
#. module: mrp_multi_level_estimate
#: model:ir.model,name:mrp_multi_level_estimate.model_mrp_multi_level
msgid "Multi Level MRP"
msgstr "MRP multi livello"
#. module: mrp_multi_level_estimate
#: model:ir.model,name:mrp_multi_level_estimate.model_product_mrp_area
msgid "Product MRP Area"
msgstr "Area MRP prodotto"
#. module: mrp_multi_level_estimate
#: model:ir.model.fields,help:mrp_multi_level_estimate.field_product_mrp_area__group_estimate_days
msgid ""
"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 caseonly grouping until the total length of the date "
"range will be done."
msgstr ""
"I giorni per raggruppare le stime come domanda per l'MRP. Può essere diverso "
"dalla lunghezza del periodo delle date utilizzate nelle stime ma non "
"dovrebbe essere maggiore, in tal caso verrà eseguito solo il raggruppamento "
"fino alla lunghezza totale dell'intervallo di date."
#~ msgid ""
#~ "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 caseonly grouping until the total "
#~ "lenght of the date range will be done."
#~ msgstr ""
#~ "I giorni per raggruppare le stime come richiesta per l'MRP. Può differire "
#~ "dall'ampiezza dell'intervallo di date utilizzato per la stima ma non "
#~ "dovrebbe essere maggiore, solo in quel caso verrà eseguito il "
#~ "raggruppamento fino all'ampiezza totale dell'intervallo di date."

View File

@@ -0,0 +1,77 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * mrp_multi_level_estimate
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: mrp_multi_level_estimate
#: model:ir.model.fields.selection,name:mrp_multi_level_estimate.selection__mrp_area__estimate_demand_and_other_sources_strat__all
msgid "Always consider all sources"
msgstr ""
#. module: mrp_multi_level_estimate
#: model:ir.model.fields,help:mrp_multi_level_estimate.field_mrp_area__estimate_demand_and_other_sources_strat
msgid ""
"Define the strategy to follow in MRP multi level when there is acoexistence 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."
msgstr ""
#. module: mrp_multi_level_estimate
#: model:ir.model.fields,field_description:mrp_multi_level_estimate.field_mrp_area__estimate_demand_and_other_sources_strat
msgid "Demand Estimates and Other Demand Sources Strategy"
msgstr ""
#. module: mrp_multi_level_estimate
#: model:ir.model.fields,field_description:mrp_multi_level_estimate.field_product_mrp_area__group_estimate_days
msgid "Group Days of Estimates"
msgstr ""
#. module: mrp_multi_level_estimate
#: model:ir.model.constraint,message:mrp_multi_level_estimate.constraint_product_mrp_area_group_estimate_days_check
msgid "Group Days of Estimates must be greater than or equal to zero."
msgstr ""
#. module: mrp_multi_level_estimate
#: model:ir.model.fields.selection,name:mrp_multi_level_estimate.selection__mrp_area__estimate_demand_and_other_sources_strat__ignore_overlapping
msgid "Ignore other sources during periods with estimates"
msgstr ""
#. module: mrp_multi_level_estimate
#: model:ir.model.fields.selection,name:mrp_multi_level_estimate.selection__mrp_area__estimate_demand_and_other_sources_strat__ignore_others_if_estimates
msgid "Ignore other sources for products with estimates"
msgstr ""
#. module: mrp_multi_level_estimate
#: model:ir.model,name:mrp_multi_level_estimate.model_mrp_area
msgid "MRP Area"
msgstr ""
#. module: mrp_multi_level_estimate
#: model:ir.model,name:mrp_multi_level_estimate.model_mrp_multi_level
msgid "Multi Level MRP"
msgstr ""
#. module: mrp_multi_level_estimate
#: model:ir.model,name:mrp_multi_level_estimate.model_product_mrp_area
msgid "Product MRP Area"
msgstr ""
#. module: mrp_multi_level_estimate
#: model:ir.model.fields,help:mrp_multi_level_estimate.field_product_mrp_area__group_estimate_days
msgid ""
"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 caseonly grouping until the total length of the date"
" range will be done."
msgstr ""

View File

@@ -0,0 +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 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.",
)

View File

@@ -0,0 +1,26 @@
# Copyright 2019-20 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 ProductMRPArea(models.Model):
_inherit = "product.mrp.area"
group_estimate_days = fields.Integer(
string="Group Days of Estimates",
default=1,
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 length of the date range will be done.",
)
_sql_constraints = [
(
"group_estimate_days_check",
"CHECK( group_estimate_days >= 0 )",
"Group Days of Estimates must be greater than or equal to zero.",
),
]

View File

@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"

View File

@@ -0,0 +1,5 @@
You can edit how to consolidate your estimates as demand at product MRP
area level using the field *Group Days of Estimates*. This number
represents the days to group your estimates as demand for the MRP, e.g:
if set to 7, you will have your estimates (regardless of the date range
used) grouped in weekly demand.

View File

@@ -0,0 +1,2 @@
- Lois Rilo \<<lois.rilo@forgeflow.com>\>
- Pimolnat Suntian\<<pimolnats@ecosoft.co.th>\>

View File

@@ -0,0 +1,3 @@
Integration for MRP Multi Level and [Stock Demand
Estimates](https://github.com/OCA/stock-logistics-warehouse/tree/12.0/stock_demand_estimate)
system.

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -0,0 +1,437 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>MRP Multi Level Estimate</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="mrp-multi-level-estimate">
<h1 class="title">MRP Multi Level Estimate</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:e6d993a40522997f3b0ade9182fb5d90c4d9b889418098456b52fd010f77d5b0
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Production/Stable" src="https://img.shields.io/badge/maturity-Production%2FStable-green.png" /></a> <a class="reference external image-reference" 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 image-reference" href="https://github.com/OCA/manufacture/tree/17.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 image-reference" href="https://translation.odoo-community.org/projects/manufacture-17-0/manufacture-17-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 image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/manufacture&amp;target_branch=17.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-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">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="toc-entry-1">Configuration</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-2">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-3">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-4">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-5">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-6">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#toc-entry-1">Configuration</a></h1>
<p>You can edit how to consolidate your estimates as demand at product MRP
area level using the field <em>Group Days of Estimates</em>. This number
represents the days to group your estimates as demand for the MRP, e.g:
if set to 7, you will have your estimates (regardless of the date range
used) grouped in weekly demand.</p>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/manufacture/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/manufacture/issues/new?body=module:%20mrp_multi_level_estimate%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-3">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-4">Authors</a></h2>
<ul class="simple">
<li>ForgeFlow</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-5">Contributors</a></h2>
<ul class="simple">
<li>Lois Rilo &lt;<a class="reference external" href="mailto:lois.rilo&#64;forgeflow.com">lois.rilo&#64;forgeflow.com</a>&gt;</li>
<li>Pimolnat Suntian&lt;<a class="reference external" href="mailto:pimolnats&#64;ecosoft.co.th">pimolnats&#64;ecosoft.co.th</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external image-reference" href="https://github.com/LoisRForgeFlow"><img alt="LoisRForgeFlow" src="https://github.com/LoisRForgeFlow.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/manufacture/tree/17.0/mrp_multi_level_estimate">OCA/manufacture</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1 @@
from . import test_mrp_multi_level_estimate

View File

@@ -0,0 +1,445 @@
# 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 odoo.tests import Form
from odoo.addons.mrp_multi_level.tests.common import TestMrpMultiLevelCommon
class TestMrpMultiLevelEstimate(TestMrpMultiLevelCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.estimate_obj = cls.env["stock.demand.estimate"]
cls.uom_unit = cls.env.ref("uom.product_uom_unit")
# Create new clean area:
cls.estimate_loc = cls.loc_obj.create(
{
"name": "Test location for estimates",
"usage": "internal",
"location_id": cls.wh.view_location_id.id,
}
)
cls.estimate_area = cls.mrp_area_obj.create(
{
"name": "Test",
"warehouse_id": cls.wh.id,
"location_id": cls.estimate_loc.id,
}
)
cls.test_mrp_parameter = cls.product_mrp_area_obj.create(
{
"product_id": cls.prod_test.id,
"mrp_area_id": cls.estimate_area.id,
"mrp_nbr_days": 7,
}
)
# Create 3 consecutive estimates of 1 week length each.
today = datetime.today().replace(hour=0)
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)
qty = 140.0
for sd, ed in zip(start_dates, end_dates, strict=True):
qty += 70.0
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_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,
"manual_date_from": date_from,
"manual_date_to": date_to,
}
)
def test_01_demand_estimates(self):
"""Tests demand estimates integration."""
estimates = self.estimate_obj.search(
[
("product_id", "=", self.prod_test.id),
("location_id", "=", self.stock_location.id),
]
)
self.assertEqual(len(estimates), 3)
moves = self.mrp_move_obj.search(
[
("product_id", "=", self.prod_test.id),
("mrp_area_id", "=", self.mrp_area.id),
]
)
# 3 weeks - 3 days in the past = 18 days of valid estimates:
moves_from_estimates = moves.filtered(lambda m: m.mrp_type == "d")
self.assertEqual(len(moves_from_estimates), 18)
quantities = moves_from_estimates.mapped("mrp_qty")
self.assertIn(-30.0, quantities) # 210 a week => 30.0 dayly:
self.assertIn(-40.0, quantities) # 280 a week => 40.0 dayly:
self.assertIn(-50.0, quantities) # 350 a week => 50.0 dayly:
plans = self.planned_order_obj.search(
[
("product_id", "=", self.prod_test.id),
("mrp_area_id", "=", self.mrp_area.id),
]
)
action = list(set(plans.mapped("mrp_action")))
self.assertEqual(len(action), 1)
self.assertEqual(action[0], "buy")
self.assertEqual(len(plans), 18)
inventories = self.mrp_inventory_obj.search(
[("mrp_area_id", "=", self.estimate_area.id)]
)
self.assertEqual(len(inventories), 18)
def test_02_demand_estimates_group_plans(self):
"""Test requirement grouping functionality, `nbr_days`."""
estimates = self.estimate_obj.search(
[
("product_id", "=", self.prod_test.id),
("location_id", "=", self.estimate_loc.id),
]
)
self.assertEqual(len(estimates), 3)
moves = self.mrp_move_obj.search(
[
("product_id", "=", self.prod_test.id),
("mrp_area_id", "=", self.estimate_area.id),
]
)
supply_plans = self.planned_order_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:
moves_from_estimates = moves.filtered(lambda m: m.mrp_type == "d")
self.assertEqual(len(moves_from_estimates), 18)
# 18 days of demand / 7 nbr_days = 2.57 => 3 supply moves expected.
self.assertEqual(len(supply_plans), 3)
quantities = supply_plans.mapped("mrp_qty")
week_1_expected = sum(moves_from_estimates[0:7].mapped("mrp_qty"))
self.assertIn(abs(week_1_expected), quantities)
week_2_expected = sum(moves_from_estimates[7:14].mapped("mrp_qty"))
self.assertIn(abs(week_2_expected), quantities)
week_3_expected = sum(moves_from_estimates[14:].mapped("mrp_qty"))
self.assertIn(abs(week_3_expected), quantities)
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(
{"mrp_area_ids": [(6, 0, self.estimate_area.ids)]}
).run_mrp_multi_level()
estimates = self.estimate_obj.search(
[
("product_id", "=", self.prod_test.id),
("location_id", "=", self.estimate_loc.id),
]
)
self.assertEqual(len(estimates), 3)
moves = self.mrp_move_obj.search(
[
("product_id", "=", self.prod_test.id),
("mrp_area_id", "=", self.estimate_area.id),
]
)
# 3 weekly estimates, demand from estimates grouped in batches of 7
# days = 3 days of estimates mrp moves:
moves_from_estimates = moves.filtered(lambda m: m.mrp_type == "d")
self.assertEqual(len(moves_from_estimates), 3)
# 210 weekly -> 30 daily -> 30 * 4 days not consumed = 120
self.assertEqual(moves_from_estimates[0].mrp_qty, -120)
self.assertEqual(moves_from_estimates[1].mrp_qty, -280)
self.assertEqual(moves_from_estimates[2].mrp_qty, -350)
# 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(
{"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),
]
)
moves_from_estimates = moves.filtered(lambda m: m.mrp_type == "d")
self.assertEqual(len(moves_from_estimates), 3)
self.assertEqual(moves_from_estimates[0].mrp_qty, -120)
self.assertEqual(moves_from_estimates[1].mrp_qty, -280)
self.assertEqual(moves_from_estimates[2].mrp_qty, -350)
# 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(
{"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),
]
)
moves_from_estimates = moves.filtered(lambda m: m.mrp_type == "d")
self.assertEqual(len(moves_from_estimates), 5)
# Week 1 partially consumed, so only 4 remaining days considered.
# 30 daily x 4 days = 120
self.assertEqual(moves_from_estimates[0].mrp_qty, -120)
# Week 2 divided in 2 (40 daily) -> 5 days = 200, 2 days = 80
self.assertEqual(moves_from_estimates[1].mrp_qty, -200)
self.assertEqual(moves_from_estimates[2].mrp_qty, -80)
# Week 3 divided in 2, (50 daily) -> 5 days = 250, 2 days = 100
self.assertEqual(moves_from_estimates[3].mrp_qty, -250)
self.assertEqual(moves_from_estimates[4].mrp_qty, -100)
def test_04_group_demand_estimates_rounding(self):
"""Test demand grouping functionality, `group_estimate_days` and rounding."""
self.test_mrp_parameter.group_estimate_days = 7
self.uom_unit.rounding = 1.00
estimates = self.estimate_obj.search(
[
("product_id", "=", self.prod_test.id),
("location_id", "=", self.estimate_loc.id),
]
)
self.assertEqual(len(estimates), 3)
# Change qty of estimates to quantities that
# divided by 7 days return a decimal result
qty = 400
for estimate in estimates:
estimate.product_uom_qty = qty
qty += 100
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 weekly estimates, demand from estimates grouped in batches of 7
# days = 3 days of estimates mrp moves:
moves_from_estimates = moves.filtered(lambda m: m.mrp_type == "d")
self.assertEqual(len(moves_from_estimates), 3)
# Rounding should be done at the end of the calculation, using the daily
# quantity already rounded can lead to errors.
# 400 weekly -> 57.41 daily -> 57.41 * 4 days not consumed = 228,57 = 229
self.assertEqual(moves_from_estimates[0].mrp_qty, -229)
# 500 weekly -> 71.42 daily -> 71,42 * 7 = 500
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()
)
def test_06_estimate_and_other_sources_strat_with_mo(self):
"""
Tests demand estimates and other sources strategies with MOs.
Components demand from MOs is always indirect demand, so even if we
have estimates, we should consider that demand.
"""
# Get manufactured product, component and bom
fp_1 = self.env.ref("mrp_multi_level.product_product_fp_1")
pp_1 = self.env.ref("mrp_multi_level.product_product_pp_1")
fp_1_bom = self.env.ref("mrp_multi_level.mrp_bom_fp_1")
self.product_mrp_area_obj.create(
{"product_id": fp_1.id, "mrp_area_id": self.estimate_area.id}
)
self.product_mrp_area_obj.create(
{"product_id": pp_1.id, "mrp_area_id": self.estimate_area.id}
)
# Create 1 estimate of 1 week length for the component.
date_start = datetime.today().replace(hour=0)
date_end = date_start + timedelta(days=6)
self._create_demand_estimate(pp_1, self.estimate_loc, date_start, date_end, 7)
date_mo = date_start + timedelta(days=1)
# Create 1 MO for fp_1 that has two pp_1 in its components
mo_form = Form(self.mo_obj)
mo_form.product_id = fp_1
mo_form.bom_id = fp_1_bom
mo_form.product_qty = 10
mo_form.date_start = date_mo
mo = mo_form.save()
mo.location_src_id = (
self.estimate_loc
) # writing afterward to avoid invisible-field error in Form.
mo.action_confirm()
# Create 1 picking out that represents a Delivery Order from a sale
self._create_picking_out(pp_1, 5, date_mo, location=self.estimate_loc)
# 1. "all"
# Expected result: Consider all sources of demand
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", "=", pp_1.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), 7)
self.assertEqual(sum(demand_from_estimates.mapped("mrp_qty")), -7)
self.assertEqual(len(demand_from_other_sources), 2)
self.assertEqual(sum(demand_from_other_sources.mapped("mrp_qty")), -25)
# 2. "ignore_others_if_estimates"
# Expected result: Consider estimates and demand from MO
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", "=", pp_1.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), 7)
self.assertEqual(sum(demand_from_estimates.mapped("mrp_qty")), -7)
self.assertEqual(len(demand_from_other_sources), 1)
self.assertEqual(sum(demand_from_other_sources.mapped("mrp_qty")), -20)
# 3. "ignore_overlapping"
# Expected result: Consider estimates and demand from MO
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", "=", pp_1.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), 7)
self.assertEqual(sum(demand_from_estimates.mapped("mrp_qty")), -7)
self.assertEqual(len(demand_from_other_sources), 1)
self.assertEqual(sum(demand_from_other_sources.mapped("mrp_qty")), -20)

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

@@ -0,0 +1,14 @@
<?xml version="1.0" ?>
<odoo>
<record id="product_mrp_area_form" model="ir.ui.view">
<field name="name">product.mrp.area.form - estimates</field>
<field name="model">product.mrp.area</field>
<field name="type">form</field>
<field name="inherit_id" ref="mrp_multi_level.product_mrp_area_form" />
<field name="arch" type="xml">
<field name="mrp_nbr_days" position="after">
<field name="group_estimate_days" />
</field>
</field>
</record>
</odoo>

View File

@@ -0,0 +1 @@
from . import mrp_multi_level

View File

@@ -0,0 +1,132 @@
# Copyright 2019-22 ForgeFlow S.L. (http://www.forgeflow.com)
# - Lois Rilo <lois.rilo@forgeflow.com>
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
import logging
from datetime import timedelta
from odoo import api, fields, models
from odoo.tools.float_utils import float_round
logger = logging.getLogger(__name__)
class MultiLevelMrp(models.TransientModel):
_inherit = "mrp.multi.level"
@api.model
def _prepare_mrp_move_data_from_estimate(self, estimate, product_mrp_area, date):
mrp_type = "d"
origin = "fc"
daily_qty_unrounded = estimate.daily_qty
daily_qty = float_round(
estimate.daily_qty,
precision_rounding=product_mrp_area.product_id.uom_id.rounding,
rounding_method="HALF-UP",
)
days_consumed = 0
if product_mrp_area.group_estimate_days > 1:
start = estimate.date_from
if start < date:
days_consumed = (date - start).days
group_estimate_days = min(
product_mrp_area.group_estimate_days, estimate.duration - days_consumed
)
mrp_qty = float_round(
daily_qty_unrounded * group_estimate_days,
precision_rounding=product_mrp_area.product_id.uom_id.rounding,
rounding_method="HALF-UP",
)
return {
"mrp_area_id": product_mrp_area.mrp_area_id.id,
"product_id": product_mrp_area.product_id.id,
"product_mrp_area_id": product_mrp_area.id,
"production_id": None,
"purchase_order_id": None,
"purchase_line_id": None,
"stock_move_id": None,
"mrp_qty": -mrp_qty,
"current_qty": -daily_qty,
"mrp_date": date,
"current_date": date,
"mrp_type": mrp_type,
"mrp_origin": origin,
"mrp_order_number": None,
"parent_product_id": None,
"name": "Forecast",
"state": "confirmed",
}
@api.model
def _estimates_domain(self, product_mrp_area):
locations = product_mrp_area.mrp_area_id._get_locations()
return [
("product_id", "=", product_mrp_area.product_id.id),
("location_id", "child_of", locations.ids),
("date_to", ">=", fields.Date.today()),
]
@api.model
def _init_mrp_move_from_forecast(self, product_mrp_area):
res = super()._init_mrp_move_from_forecast(product_mrp_area)
if not product_mrp_area.group_estimate_days:
return False
today = fields.Date.today()
domain = self._estimates_domain(product_mrp_area)
estimates = self.env["stock.demand.estimate"].search(domain)
for rec in estimates:
start = rec.date_from
if start < today:
start = today
mrp_date = fields.Date.from_string(start)
date_end = fields.Date.from_string(rec.date_to)
delta = timedelta(days=product_mrp_area.group_estimate_days)
while mrp_date <= date_end:
mrp_move_data = self._prepare_mrp_move_data_from_estimate(
rec, product_mrp_area, mrp_date
)
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
)
and not res["production_id"]
):
return False
return res