Merge PR #1202 into 13.0

Signed-off-by LoisRForgeFlow
This commit is contained in:
OCA-git-bot
2024-02-20 11:09:00 +00:00
9 changed files with 587 additions and 967 deletions

View File

@@ -1,259 +0,0 @@
===============
MRP Multi Level
===============
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:ade6783d411926bfe80610b33ea8aff7a931f7b8c3336b0b44c9d78eea162757
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |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-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
:alt: OCA/manufacture
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/manufacture-13-0/manufacture-13-0-mrp_multi_level
: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=13.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
This module allows you to calculate, based in known inventory, demand, and
supply, and based on parameters set at product variant level, the new
procurements for each product.
To do this, the calculation starts at top level of the bill of material
and explodes this down to the lowest level.
Key Features
------------
* MRP parameters set by product variant MRP area pairs.
* Cron job to calculate the MRP demand.
* Manually calculate the MRP demand.
* Confirm the calculated MRP demand and create PO's, or MO's.
* Able to see the products for which action is needed throught Planned Orders.
* Integration with `Stock Demand Estimates <https://github.com/OCA/stock-logistics-warehouse/tree/12.0/stock_demand_estimate>`_ system.
Note: You need to install `mrp_multi_level_estimate module <https://github.com/OCA/manufacture/tree/12.0/mrp_multi_level_estimate>`_.
**Table of contents**
.. contents::
:local:
Configuration
=============
MRP Areas
~~~~~~~~~
* Go to *Manufacturing > Configuration > MRP Areas* and define or edit
any existing area. You can specify the working hours for every area.
Product MRP Area Parameters
~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Go to *Manufacturing > Master Data > Product MRP Area Parameters* and set
the MRP parameters for a given product and area.
Usage
=====
To manually run the MRP scheduler:
#. Go to *Manufacturing > Operations > Run MRP Multi Level*.
#. On the wizard click *Run MRP*.
To launch replenishment orders (moves, purchases, production orders...):
#. Go to *Manufacturing > Operations > MRP Inventory*.
#. Filter with *To procure*.
#. Select multiple records and click on *Action > Procure* or click the right
hand side gears in any record.
#. On the wizard, check everything is ok and click *Execute*.
Changelog
=========
13.0.1.5.0 (2020-04-09)
~~~~~~~~~~~~~~~~~~~~~~~
**Features**
- Show *Run MRP Multi Level* menu only to a specific new security group *Run MRP Manually*. (`#492 <https://github.com/OCA/manufacture/issues/492>`_)
13.0.1.4.0 (2020-03-26)
~~~~~~~~~~~~~~~~~~~~~~~
* Add menu entry for planned orders
* Add button to navigate from planned orders to linked manufacturing orders
* Add action to convert planned orders to fixed
* When changing the due date in a planned order the release date is recomputed
13.0.1.3.0 (2020-03-02)
~~~~~~~~~~~~~~~~~~~~~~~
* [IMP] Minor changes"
(`#470 <https://github.com/OCA/manufacture/pull/470>`_).
* Planned Order release and due date become required.
* Add button to Product MRP Area to update MOQ from Supplier Info.
* Link Manufacturing Orders with Planned Orders.
* Allow Mrp Inventory Procure Wizard to be used from other models.
* Make MRP Inventory creation more extensible.
* Main Supplier computation (v13 requires explicit False definitions)
13.0.1.2.0 (2020-02-20)
~~~~~~~~~~~~~~~~~~~~~~~
* [IMP] Minor changes
(`#468 <https://github.com/OCA/manufacture/pull/468>`_).
* Planned Orders become fixed on manual creation by default
* Released Quantity becomes readonly
* Add product reference if Planned Order name is not defined on bom explosion
13.0.1.1.0 (2020-02-21)
~~~~~~~~~~~~~~~~~~~~~~~
* [FIX] Minor changes
(`#469 <https://github.com/OCA/manufacture/pull/469>`_).
* Fix Main supplier computation in multi company
* Drop Triplicated field in search view
* [IMP] Minor changes
(`#463 <https://github.com/OCA/manufacture/pull/463>`_).
* Show supply method on MRP Inventory
* Allow no-MRP users to look into Products
13.0.1.0.0 (2019-12-18)
~~~~~~~~~~~~~~~~~~~~~~~
* [MIG] Migration to v13.
12.0.1.0.0 (2019-08-05)
~~~~~~~~~~~~~~~~~~~~~~~
* [MIG] Migration to v12:
* Estimates as a forecasting mechanism is moved to a new module
(mrp_multi_level_estimate).
11.0.3.0.0 (2019-05-22)
~~~~~~~~~~~~~~~~~~~~~~~
* [REW/IMP] Rework to include Planned Orders.
(`#365 <https://github.com/OCA/manufacture/pull/365>`_).
* [IMP] Able to procure from a different location than the area's location.
11.0.2.2.0 (2019-05-02)
~~~~~~~~~~~~~~~~~~~~~~~
* [IMP] Able to run MRP only for selected areas.
(`#360 <https://github.com/OCA/manufacture/pull/360>`_).
11.0.2.1.0 (2019-04-02)
~~~~~~~~~~~~~~~~~~~~~~~
* [IMP] Implement *Nbr. Days* functionality to be able to group demand when
generating supply proposals.
(`#345 <https://github.com/OCA/manufacture/pull/345>`_).
11.0.2.0.0 (2018-11-20)
~~~~~~~~~~~~~~~~~~~~~~~
* [REW] Refactor MRP Area.
(`#322 <https://github.com/OCA/manufacture/pull/322>`_):
* MRP product concept dropped in favor of *Product MRP Area Parameters*.
This allow to set different MRP parameters for the same product in
different areas.
* Menu items reordering.
11.0.1.1.0 (2018-08-30)
~~~~~~~~~~~~~~~~~~~~~~~
* [FIX] Consider *Qty Multiple* on product to propose the quantity to procure.
(`#297 <https://github.com/OCA/manufacture/pull/297>`_)
11.0.1.0.1 (2018-08-03)
~~~~~~~~~~~~~~~~~~~~~~~
* [FIX] User and system locales doesn't break MRP calculation.
(`#290 <https://github.com/OCA/manufacture/pull/290>`_)
* [FIX] Working Hours are now defined only at Warehouse level and displayed
as a related on MRP Areas.
(`#290 <https://github.com/OCA/manufacture/pull/290>`__)
11.0.1.0.0 (2018-07-09)
~~~~~~~~~~~~~~~~~~~~~~~
* Start of the history.
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%0Aversion:%2013.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
~~~~~~~
* Ucamco
* ForgeFlow
Contributors
~~~~~~~~~~~~
* Wim Audenaert <wim.audenaert@ucamco.com>
* Jordi Ballester <jordi.ballester@forgeflow.com>
* Lois Rilo <lois.rilo@forgeflow.com>
* Héctor Villarreal <hector.villarreal@forgeflow.com>
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-JordiBForgeFlow| image:: https://github.com/JordiBForgeFlow.png?size=40px
:target: https://github.com/JordiBForgeFlow
:alt: JordiBForgeFlow
.. |maintainer-LoisRForgeFlow| image:: https://github.com/LoisRForgeFlow.png?size=40px
:target: https://github.com/LoisRForgeFlow
:alt: LoisRForgeFlow
Current `maintainers <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-JordiBForgeFlow| |maintainer-LoisRForgeFlow|
This module is part of the `OCA/manufacture <https://github.com/OCA/manufacture/tree/13.0/mrp_multi_level>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -3,7 +3,6 @@
# - Jordi Ballester Alomar <jordi.ballester@forgeflow.com>
# - Lois Rilo Antelo <lois.rilo@forgeflow.com>
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from math import ceil
from odoo import _, api, fields, models

View File

@@ -4,6 +4,8 @@ MRP Areas
* Go to *Manufacturing > Configuration > MRP Areas* and define or edit
any existing area. You can specify the working hours for every area.
Product MRP Area Parameters
~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -2,3 +2,5 @@
* Jordi Ballester <jordi.ballester@forgeflow.com>
* Lois Rilo <lois.rilo@forgeflow.com>
* Héctor Villarreal <hector.villarreal@forgeflow.com>
* Christopher Ormaza <chris.ormaza@forgeflow.com>
* Alexandre Fayolle <alexandre.fayolle@camptocamp.com>

View File

@@ -1,638 +0,0 @@
<!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</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
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: grey; } /* 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 {
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">
<h1 class="title">MRP Multi Level</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:ade6783d411926bfe80610b33ea8aff7a931f7b8c3336b0b44c9d78eea162757
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" 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 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/13.0/mrp_multi_level"><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-13-0/manufacture-13-0-mrp_multi_level"><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=13.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module allows you to calculate, based in known inventory, demand, and
supply, and based on parameters set at product variant level, the new
procurements for each product.</p>
<p>To do this, the calculation starts at top level of the bill of material
and explodes this down to the lowest level.</p>
<div class="section" id="key-features">
<h1>Key Features</h1>
<ul class="simple">
<li>MRP parameters set by product variant MRP area pairs.</li>
<li>Cron job to calculate the MRP demand.</li>
<li>Manually calculate the MRP demand.</li>
<li>Confirm the calculated MRP demand and create POs, or MOs.</li>
<li>Able to see the products for which action is needed throught Planned Orders.</li>
<li>Integration with <a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/tree/12.0/stock_demand_estimate">Stock Demand Estimates</a> system.
Note: You need to install <a class="reference external" href="https://github.com/OCA/manufacture/tree/12.0/mrp_multi_level_estimate">mrp_multi_level_estimate module</a>.</li>
</ul>
<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><ul>
<li><a class="reference internal" href="#mrp-areas" id="toc-entry-2">MRP Areas</a></li>
<li><a class="reference internal" href="#product-mrp-area-parameters" id="toc-entry-3">Product MRP Area Parameters</a></li>
</ul>
</li>
<li><a class="reference internal" href="#usage" id="toc-entry-4">Usage</a></li>
<li><a class="reference internal" href="#changelog" id="toc-entry-5">Changelog</a><ul>
<li><a class="reference internal" href="#section-1" id="toc-entry-6">13.0.1.5.0 (2020-04-09)</a></li>
<li><a class="reference internal" href="#section-2" id="toc-entry-7">13.0.1.4.0 (2020-03-26)</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-8">13.0.1.3.0 (2020-03-02)</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-9">13.0.1.2.0 (2020-02-20)</a></li>
<li><a class="reference internal" href="#section-5" id="toc-entry-10">13.0.1.1.0 (2020-02-21)</a></li>
<li><a class="reference internal" href="#section-6" id="toc-entry-11">13.0.1.0.0 (2019-12-18)</a></li>
<li><a class="reference internal" href="#section-7" id="toc-entry-12">12.0.1.0.0 (2019-08-05)</a></li>
<li><a class="reference internal" href="#section-8" id="toc-entry-13">11.0.3.0.0 (2019-05-22)</a></li>
<li><a class="reference internal" href="#section-9" id="toc-entry-14">11.0.2.2.0 (2019-05-02)</a></li>
<li><a class="reference internal" href="#section-10" id="toc-entry-15">11.0.2.1.0 (2019-04-02)</a></li>
<li><a class="reference internal" href="#section-11" id="toc-entry-16">11.0.2.0.0 (2018-11-20)</a></li>
<li><a class="reference internal" href="#section-12" id="toc-entry-17">11.0.1.1.0 (2018-08-30)</a></li>
<li><a class="reference internal" href="#section-13" id="toc-entry-18">11.0.1.0.1 (2018-08-03)</a></li>
<li><a class="reference internal" href="#section-14" id="toc-entry-19">11.0.1.0.0 (2018-07-09)</a></li>
</ul>
</li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-20">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-21">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-22">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-23">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-24">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h2><a class="toc-backref" href="#toc-entry-1">Configuration</a></h2>
<div class="section" id="mrp-areas">
<h3><a class="toc-backref" href="#toc-entry-2">MRP Areas</a></h3>
<ul class="simple">
<li>Go to <em>Manufacturing &gt; Configuration &gt; MRP Areas</em> and define or edit
any existing area. You can specify the working hours for every area.</li>
</ul>
</div>
<div class="section" id="product-mrp-area-parameters">
<h3><a class="toc-backref" href="#toc-entry-3">Product MRP Area Parameters</a></h3>
<ul class="simple">
<li>Go to <em>Manufacturing &gt; Master Data &gt; Product MRP Area Parameters</em> and set
the MRP parameters for a given product and area.</li>
</ul>
</div>
</div>
<div class="section" id="usage">
<h2><a class="toc-backref" href="#toc-entry-4">Usage</a></h2>
<p>To manually run the MRP scheduler:</p>
<ol class="arabic simple">
<li>Go to <em>Manufacturing &gt; Operations &gt; Run MRP Multi Level</em>.</li>
<li>On the wizard click <em>Run MRP</em>.</li>
</ol>
<p>To launch replenishment orders (moves, purchases, production orders…):</p>
<ol class="arabic simple">
<li>Go to <em>Manufacturing &gt; Operations &gt; MRP Inventory</em>.</li>
<li>Filter with <em>To procure</em>.</li>
<li>Select multiple records and click on <em>Action &gt; Procure</em> or click the right
hand side gears in any record.</li>
<li>On the wizard, check everything is ok and click <em>Execute</em>.</li>
</ol>
</div>
<div class="section" id="changelog">
<h2><a class="toc-backref" href="#toc-entry-5">Changelog</a></h2>
<div class="section" id="section-1">
<h3><a class="toc-backref" href="#toc-entry-6">13.0.1.5.0 (2020-04-09)</a></h3>
<p><strong>Features</strong></p>
<ul class="simple">
<li>Show <em>Run MRP Multi Level</em> menu only to a specific new security group <em>Run MRP Manually</em>. (<a class="reference external" href="https://github.com/OCA/manufacture/issues/492">#492</a>)</li>
</ul>
</div>
<div class="section" id="section-2">
<h3><a class="toc-backref" href="#toc-entry-7">13.0.1.4.0 (2020-03-26)</a></h3>
<blockquote>
<ul class="simple">
<li>Add menu entry for planned orders</li>
<li>Add button to navigate from planned orders to linked manufacturing orders</li>
<li>Add action to convert planned orders to fixed</li>
<li>When changing the due date in a planned order the release date is recomputed</li>
</ul>
</blockquote>
</div>
<div class="section" id="section-3">
<h3><a class="toc-backref" href="#toc-entry-8">13.0.1.3.0 (2020-03-02)</a></h3>
<ul class="simple">
<li>[IMP] Minor changes”
(<a class="reference external" href="https://github.com/OCA/manufacture/pull/470">#470</a>).<ul>
<li>Planned Order release and due date become required.</li>
<li>Add button to Product MRP Area to update MOQ from Supplier Info.</li>
<li>Link Manufacturing Orders with Planned Orders.</li>
<li>Allow Mrp Inventory Procure Wizard to be used from other models.</li>
<li>Make MRP Inventory creation more extensible.</li>
<li>Main Supplier computation (v13 requires explicit False definitions)</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="section-4">
<h3><a class="toc-backref" href="#toc-entry-9">13.0.1.2.0 (2020-02-20)</a></h3>
<ul class="simple">
<li>[IMP] Minor changes
(<a class="reference external" href="https://github.com/OCA/manufacture/pull/468">#468</a>).<ul>
<li>Planned Orders become fixed on manual creation by default</li>
<li>Released Quantity becomes readonly</li>
<li>Add product reference if Planned Order name is not defined on bom explosion</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="section-5">
<h3><a class="toc-backref" href="#toc-entry-10">13.0.1.1.0 (2020-02-21)</a></h3>
<ul class="simple">
<li>[FIX] Minor changes
(<a class="reference external" href="https://github.com/OCA/manufacture/pull/469">#469</a>).<ul>
<li>Fix Main supplier computation in multi company</li>
<li>Drop Triplicated field in search view</li>
</ul>
</li>
<li>[IMP] Minor changes
(<a class="reference external" href="https://github.com/OCA/manufacture/pull/463">#463</a>).<ul>
<li>Show supply method on MRP Inventory</li>
<li>Allow no-MRP users to look into Products</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="section-6">
<h3><a class="toc-backref" href="#toc-entry-11">13.0.1.0.0 (2019-12-18)</a></h3>
<ul class="simple">
<li>[MIG] Migration to v13.</li>
</ul>
</div>
<div class="section" id="section-7">
<h3><a class="toc-backref" href="#toc-entry-12">12.0.1.0.0 (2019-08-05)</a></h3>
<ul class="simple">
<li>[MIG] Migration to v12:<ul>
<li>Estimates as a forecasting mechanism is moved to a new module
(mrp_multi_level_estimate).</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="section-8">
<h3><a class="toc-backref" href="#toc-entry-13">11.0.3.0.0 (2019-05-22)</a></h3>
<ul class="simple">
<li>[REW/IMP] Rework to include Planned Orders.
(<a class="reference external" href="https://github.com/OCA/manufacture/pull/365">#365</a>).</li>
<li>[IMP] Able to procure from a different location than the areas location.</li>
</ul>
</div>
<div class="section" id="section-9">
<h3><a class="toc-backref" href="#toc-entry-14">11.0.2.2.0 (2019-05-02)</a></h3>
<ul class="simple">
<li>[IMP] Able to run MRP only for selected areas.
(<a class="reference external" href="https://github.com/OCA/manufacture/pull/360">#360</a>).</li>
</ul>
</div>
<div class="section" id="section-10">
<h3><a class="toc-backref" href="#toc-entry-15">11.0.2.1.0 (2019-04-02)</a></h3>
<ul class="simple">
<li>[IMP] Implement <em>Nbr. Days</em> functionality to be able to group demand when
generating supply proposals.
(<a class="reference external" href="https://github.com/OCA/manufacture/pull/345">#345</a>).</li>
</ul>
</div>
<div class="section" id="section-11">
<h3><a class="toc-backref" href="#toc-entry-16">11.0.2.0.0 (2018-11-20)</a></h3>
<ul class="simple">
<li>[REW] Refactor MRP Area.
(<a class="reference external" href="https://github.com/OCA/manufacture/pull/322">#322</a>):<ul>
<li>MRP product concept dropped in favor of <em>Product MRP Area Parameters</em>.
This allow to set different MRP parameters for the same product in
different areas.</li>
<li>Menu items reordering.</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="section-12">
<h3><a class="toc-backref" href="#toc-entry-17">11.0.1.1.0 (2018-08-30)</a></h3>
<ul class="simple">
<li>[FIX] Consider <em>Qty Multiple</em> on product to propose the quantity to procure.
(<a class="reference external" href="https://github.com/OCA/manufacture/pull/297">#297</a>)</li>
</ul>
</div>
<div class="section" id="section-13">
<h3><a class="toc-backref" href="#toc-entry-18">11.0.1.0.1 (2018-08-03)</a></h3>
<ul class="simple">
<li>[FIX] User and system locales doesnt break MRP calculation.
(<a class="reference external" href="https://github.com/OCA/manufacture/pull/290">#290</a>)</li>
<li>[FIX] Working Hours are now defined only at Warehouse level and displayed
as a related on MRP Areas.
(<a class="reference external" href="https://github.com/OCA/manufacture/pull/290">#290</a>)</li>
</ul>
</div>
<div class="section" id="section-14">
<h3><a class="toc-backref" href="#toc-entry-19">11.0.1.0.0 (2018-07-09)</a></h3>
<ul class="simple">
<li>Start of the history.</li>
</ul>
</div>
</div>
<div class="section" id="bug-tracker">
<h2><a class="toc-backref" href="#toc-entry-20">Bug Tracker</a></h2>
<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%0Aversion:%2013.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">
<h2><a class="toc-backref" href="#toc-entry-21">Credits</a></h2>
<div class="section" id="authors">
<h3><a class="toc-backref" href="#toc-entry-22">Authors</a></h3>
<ul class="simple">
<li>Ucamco</li>
<li>ForgeFlow</li>
</ul>
</div>
<div class="section" id="contributors">
<h3><a class="toc-backref" href="#toc-entry-23">Contributors</a></h3>
<ul class="simple">
<li>Wim Audenaert &lt;<a class="reference external" href="mailto:wim.audenaert&#64;ucamco.com">wim.audenaert&#64;ucamco.com</a>&gt;</li>
<li>Jordi Ballester &lt;<a class="reference external" href="mailto:jordi.ballester&#64;forgeflow.com">jordi.ballester&#64;forgeflow.com</a>&gt;</li>
<li>Lois Rilo &lt;<a class="reference external" href="mailto:lois.rilo&#64;forgeflow.com">lois.rilo&#64;forgeflow.com</a>&gt;</li>
<li>Héctor Villarreal &lt;<a class="reference external" href="mailto:hector.villarreal&#64;forgeflow.com">hector.villarreal&#64;forgeflow.com</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h3><a class="toc-backref" href="#toc-entry-24">Maintainers</a></h3>
<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">maintainers</a>:</p>
<p><a class="reference external image-reference" href="https://github.com/JordiBForgeFlow"><img alt="JordiBForgeFlow" src="https://github.com/JordiBForgeFlow.png?size=40px" /></a> <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/13.0/mrp_multi_level">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>
</div>
</body>
</html>

View File

@@ -15,6 +15,7 @@ class TestMrpMultiLevelCommon(SavepointCase):
cls.po_obj = cls.env["purchase.order"]
cls.product_obj = cls.env["product.product"]
cls.loc_obj = cls.env["stock.location"]
cls.quant_obj = cls.env["stock.quant"]
cls.mrp_area_obj = cls.env["mrp.area"]
cls.product_mrp_area_obj = cls.env["product.mrp.area"]
cls.partner_obj = cls.env["res.partner"]
@@ -25,6 +26,8 @@ class TestMrpMultiLevelCommon(SavepointCase):
cls.mrp_inventory_obj = cls.env["mrp.inventory"]
cls.mrp_move_obj = cls.env["mrp.move"]
cls.planned_order_obj = cls.env["mrp.planned.order"]
cls.lot_model = cls.env["stock.production.lot"]
cls.quant_model = cls.env["stock.quant"]
cls.fp_1 = cls.env.ref("mrp_multi_level.product_product_fp_1")
cls.fp_2 = cls.env.ref("mrp_multi_level.product_product_fp_2")
@@ -220,6 +223,53 @@ class TestMrpMultiLevelCommon(SavepointCase):
cls.product_mrp_area_obj.create(
{"product_id": cls.prod_uom_test.id, "mrp_area_id": cls.mrp_area.id}
)
# Product to test lots
cls.product_lots = cls.product_obj.create(
{
"name": "Product Tracked by Lots",
"type": "product",
"tracking": "lot",
"uom_id": cls.env.ref("uom.product_uom_unit").id,
"list_price": 100.0,
"produce_delay": 5.0,
"route_ids": [(6, 0, [route_buy])],
"seller_ids": [(0, 0, {"name": vendor1.id, "price": 25.0})],
}
)
cls.product_mrp_area_obj.create(
{"product_id": cls.product_lots.id, "mrp_area_id": cls.mrp_area.id}
)
cls.lot_1 = cls.lot_model.create(
{
"product_id": cls.product_lots.id,
"name": "Lot 1",
"company_id": cls.company.id,
}
)
cls.lot_2 = cls.lot_model.create(
{
"product_id": cls.product_lots.id,
"name": "Lot 2",
"company_id": cls.company.id,
}
)
cls.quant_model.sudo().create(
{
"product_id": cls.product_lots.id,
"lot_id": cls.lot_1.id,
"quantity": 100.0,
"location_id": cls.stock_location.id,
}
)
cls.quant_model.sudo().create(
{
"product_id": cls.product_lots.id,
"lot_id": cls.lot_2.id,
"quantity": 110.0,
"location_id": cls.stock_location.id,
}
)
# Product MRP Parameter to test supply method computation
cls.env.ref("stock.route_warehouse0_mto").active = True
cls.env["stock.rule"].create(
@@ -453,6 +503,9 @@ class TestMrpMultiLevelCommon(SavepointCase):
cls.create_demand_sec_loc(cls.date_20, 46.0)
cls.create_demand_sec_loc(cls.date_22, 33.0)
# Create pickings:
cls._create_picking_out(cls.product_lots, 25, today)
cls.mrp_multi_level_wiz.create({}).run_mrp_multi_level()
@classmethod

View File

@@ -1,7 +1,7 @@
# Copyright 2018-19 ForgeFlow S.L. (https://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from datetime import date, datetime
from datetime import date, datetime, timedelta
from odoo import fields
@@ -447,3 +447,398 @@ class TestMrpMultiLevel(TestMrpMultiLevelCommon):
self.fp_4.route_ids = [(4, self.env.ref("mrp.route_warehouse0_manufacture").id)]
product_mrp_area._compute_supply_method()
self.assertEqual(product_mrp_area.supply_method, "manufacture")
def test_18_priorize_safety_stock(self):
now = datetime.now()
product = self.prod_test # has Buy route
product.seller_ids[0].delay = 2 # set a purchase lead time
self.quant_obj._update_available_quantity(product, self.cases_loc, 5)
self.product_mrp_area_obj.create(
{
"product_id": product.id,
"mrp_area_id": self.cases_area.id,
"mrp_minimum_stock": 15,
"mrp_applicable": True, # needed?
}
)
self._create_picking_out(
product, 6.0, now + timedelta(days=3), location=self.cases_loc
)
self._create_picking_in(
product, 10.0, now + timedelta(days=7), location=self.cases_loc
)
self._create_picking_out(
product, 12.0, now + timedelta(days=14), location=self.cases_loc
)
self.mrp_multi_level_wiz.create(
{"mrp_area_ids": [(6, 0, self.cases_area.ids)]}
).run_mrp_multi_level()
inventory = self.mrp_inventory_obj.search(
[("mrp_area_id", "=", self.cases_area.id), ("product_id", "=", product.id)]
)
expected = [
{
"date": now.date(),
"demand_qty": 0.0,
"final_on_hand_qty": 5.0,
"initial_on_hand_qty": 5.0,
"running_availability": 15.0,
"supply_qty": 0.0,
"to_procure": 10.0,
},
{
"date": now.date() + timedelta(days=3),
"demand_qty": 6.0,
"final_on_hand_qty": -1.0,
"initial_on_hand_qty": 5.0,
"running_availability": 15.0,
"supply_qty": 0.0,
"to_procure": 6.0,
},
{
"date": now.date() + timedelta(days=7),
"demand_qty": 0.0,
"final_on_hand_qty": 9.0,
"initial_on_hand_qty": -1.0,
"running_availability": 25.0,
"supply_qty": 10.0,
"to_procure": 0.0,
},
{
"date": now.date() + timedelta(days=14),
"demand_qty": 12.0,
"final_on_hand_qty": -3.0,
"initial_on_hand_qty": 9.0,
"running_availability": 15.0,
"supply_qty": 0.0,
"to_procure": 2.0,
},
]
self.assertEqual(len(expected), len(inventory))
for test_vals, inv in zip(expected, inventory):
for key in test_vals:
self.assertEqual(
test_vals[key],
inv[key],
f"unexpected value for {key}: {inv[key]} "
f"(expected {test_vals[key]} on {inv.date})",
)
def test_19_on_hand_with_lots(self):
"""Check that on-hand is correctly computed when tracking by lots."""
lots_line_1 = self.mrp_inventory_obj.search(
[("product_mrp_area_id.product_id", "=", self.product_lots.id)]
)
self.assertEqual(len(lots_line_1), 1)
self.assertEqual(lots_line_1.initial_on_hand_qty, 210)
self.assertEqual(lots_line_1.final_on_hand_qty, 185)
def test_20_prioritize_safety_stock_grouped_1(self):
"""Test grouped demand MRP but with a short nbr days.
Safety stock should be ordered."""
now = datetime.now()
product = self.prod_test # has Buy route
product.seller_ids[0].delay = 2 # set a purchase lead time
self.quant_obj._update_available_quantity(product, self.cases_loc, 5)
self.product_mrp_area_obj.create(
{
"product_id": product.id,
"mrp_area_id": self.cases_area.id,
"mrp_minimum_stock": 15,
"mrp_nbr_days": 2,
}
)
self._create_picking_out(
product, 6.0, now + timedelta(days=3), location=self.cases_loc
)
self._create_picking_in(
product, 10.0, now + timedelta(days=7), location=self.cases_loc
)
self._create_picking_out(
product, 12.0, now + timedelta(days=14), location=self.cases_loc
)
self.mrp_multi_level_wiz.create(
{"mrp_area_ids": [(6, 0, self.cases_area.ids)]}
).run_mrp_multi_level()
inventory = self.mrp_inventory_obj.search(
[("mrp_area_id", "=", self.cases_area.id), ("product_id", "=", product.id)]
)
expected = [
{
"date": now.date(),
"demand_qty": 0.0,
"final_on_hand_qty": 5.0,
"initial_on_hand_qty": 5.0,
"running_availability": 15.0,
"supply_qty": 0.0,
"to_procure": 10.0,
},
{
"date": now.date() + timedelta(days=3),
"demand_qty": 6.0,
"final_on_hand_qty": -1.0,
"initial_on_hand_qty": 5.0,
"running_availability": 15.0,
"supply_qty": 0.0,
"to_procure": 6.0,
},
{
"date": now.date() + timedelta(days=7),
"demand_qty": 0.0,
"final_on_hand_qty": 9.0,
"initial_on_hand_qty": -1.0,
"running_availability": 25.0,
"supply_qty": 10.0,
"to_procure": 0.0,
},
{
"date": now.date() + timedelta(days=14),
"demand_qty": 12.0,
"final_on_hand_qty": -3.0,
"initial_on_hand_qty": 9.0,
"running_availability": 15.0,
"supply_qty": 0.0,
"to_procure": 2.0,
},
]
self.assertEqual(len(expected), len(inventory))
for test_vals, inv in zip(expected, inventory):
for key in test_vals:
self.assertEqual(
test_vals[key],
inv[key],
f"unexpected value for {key}: {inv[key]} "
f"(expected {test_vals[key]} on {inv.date})",
)
def test_21_prioritize_safety_stock_grouped_2(self):
"""Test grouped demand MRP but with a longer nbr days.
Safety stock should be ordered."""
now = datetime.now()
product = self.prod_test # has Buy route
product.seller_ids[0].delay = 2 # set a purchase lead time
self.quant_obj._update_available_quantity(product, self.cases_loc, 5)
self.product_mrp_area_obj.create(
{
"product_id": product.id,
"mrp_area_id": self.cases_area.id,
"mrp_minimum_stock": 15,
"mrp_nbr_days": 7,
}
)
self._create_picking_out(
product, 6.0, now + timedelta(days=3), location=self.cases_loc
)
self._create_picking_in(
product, 10.0, now + timedelta(days=7), location=self.cases_loc
)
self._create_picking_out(
product, 12.0, now + timedelta(days=12), location=self.cases_loc
)
self.mrp_multi_level_wiz.create(
{"mrp_area_ids": [(6, 0, self.cases_area.ids)]}
).run_mrp_multi_level()
inventory = self.mrp_inventory_obj.search(
[("mrp_area_id", "=", self.cases_area.id), ("product_id", "=", product.id)]
)
expected = [
{
"date": now.date(),
"demand_qty": 0.0,
"final_on_hand_qty": 5.0,
"initial_on_hand_qty": 5.0,
"running_availability": 21.0,
"supply_qty": 0.0,
"to_procure": 16.0,
},
{
"date": now.date() + timedelta(days=3),
"demand_qty": 6.0,
"final_on_hand_qty": -1.0,
"initial_on_hand_qty": 5.0,
"running_availability": 15.0,
"supply_qty": 0.0,
"to_procure": 0.0,
},
{
"date": now.date() + timedelta(days=7),
"demand_qty": 0.0,
"final_on_hand_qty": 9.0,
"initial_on_hand_qty": -1.0,
"running_availability": 27.0,
"supply_qty": 10.0,
"to_procure": 2.0,
},
{
"date": now.date() + timedelta(days=12),
"demand_qty": 12.0,
"final_on_hand_qty": -3.0,
"initial_on_hand_qty": 9.0,
"running_availability": 15.0,
"supply_qty": 0.0,
"to_procure": 0.0,
},
]
self.assertEqual(len(expected), len(inventory))
for test_vals, inv in zip(expected, inventory):
for key in test_vals:
self.assertEqual(
test_vals[key],
inv[key],
f"unexpected value for {key}: {inv[key]} "
f"(expected {test_vals[key]} on {inv.date})",
)
def test_22_prioritize_safety_stock_grouped_3(self):
"""Test grouped demand MRP but with an existing incoming supply
Safety stock should NOT be ordered."""
now = datetime.now()
product = self.prod_test # has Buy route
product.seller_ids[0].delay = 2 # set a purchase lead time
self.quant_obj._update_available_quantity(product, self.cases_loc, 5)
self.product_mrp_area_obj.create(
{
"product_id": product.id,
"mrp_area_id": self.cases_area.id,
"mrp_minimum_stock": 15,
"mrp_nbr_days": 7,
}
)
self._create_picking_in(
product, 30.0, now + timedelta(days=3), location=self.cases_loc
)
self._create_picking_out(
product, 6.0, now + timedelta(days=7), location=self.cases_loc
)
self._create_picking_out(
product, 12.0, now + timedelta(days=12), location=self.cases_loc
)
self.mrp_multi_level_wiz.create(
{"mrp_area_ids": [(6, 0, self.cases_area.ids)]}
).run_mrp_multi_level()
inventory = self.mrp_inventory_obj.search(
[("mrp_area_id", "=", self.cases_area.id), ("product_id", "=", product.id)]
)
expected = [
{
"date": now.date() + timedelta(days=3),
"demand_qty": 0.0,
"initial_on_hand_qty": 5.0,
"final_on_hand_qty": 35.0,
"running_availability": 35.0,
"supply_qty": 30.0,
"to_procure": 0.0,
},
{
"date": now.date() + timedelta(days=7),
"demand_qty": 6.0,
"initial_on_hand_qty": 35.0,
"final_on_hand_qty": 29.0,
"running_availability": 29.0,
"supply_qty": 0.0,
"to_procure": 0.0,
},
{
"date": now.date() + timedelta(days=12),
"demand_qty": 12.0,
"initial_on_hand_qty": 29.0,
"final_on_hand_qty": 17.0,
"running_availability": 17.0,
"supply_qty": 0.0,
"to_procure": 0.0,
},
]
self.assertEqual(len(expected), len(inventory))
for test_vals, inv in zip(expected, inventory):
for key in test_vals:
self.assertEqual(
test_vals[key],
inv[key],
f"unexpected value for {key}: {inv[key]} "
f"(expected {test_vals[key]} on {inv.date})",
)
def test_23_prioritize_safety_stock_with_mrp_moves_today(self):
"""Test MRP but with moves today. Safety stock should not be ordered."""
now = datetime.now()
product = self.prod_test # has Buy route
product.seller_ids[0].delay = 2 # set a purchase lead time
self.quant_obj._update_available_quantity(product, self.cases_loc, 5)
self.product_mrp_area_obj.create(
{
"product_id": product.id,
"mrp_area_id": self.cases_area.id,
"mrp_minimum_stock": 15,
}
)
self._create_picking_out(product, 10.0, now, location=self.cases_loc)
self._create_picking_in(product, 20.0, now, location=self.cases_loc)
self.mrp_multi_level_wiz.create(
{"mrp_area_ids": [(6, 0, self.cases_area.ids)]}
).run_mrp_multi_level()
inventory = self.mrp_inventory_obj.search(
[("mrp_area_id", "=", self.cases_area.id), ("product_id", "=", product.id)]
)
expected = [
{
"date": now.date(),
"demand_qty": 10.0,
"final_on_hand_qty": 15.0,
"initial_on_hand_qty": 5.0,
"running_availability": 15.0,
"supply_qty": 20.0,
"to_procure": 0.0,
},
]
self.assertEqual(len(expected), len(inventory))
for test_vals, inv in zip(expected, inventory):
for key in test_vals:
self.assertEqual(
test_vals[key],
inv[key],
f"unexpected value for {key}: {inv[key]} "
f"(expected {test_vals[key]} on {inv.date})",
)
def test_24_prioritize_safety_stock_with_mrp_moves_today_grouped(self):
"""Test grouped demand MRP but with moves today. Safety stock should not be ordered."""
now = datetime.now()
product = self.prod_test # has Buy route
product.seller_ids[0].delay = 2 # set a purchase lead time
self.quant_obj._update_available_quantity(product, self.cases_loc, 5)
self.product_mrp_area_obj.create(
{
"product_id": product.id,
"mrp_area_id": self.cases_area.id,
"mrp_minimum_stock": 15,
"mrp_nbr_days": 2,
}
)
self._create_picking_out(product, 10.0, now, location=self.cases_loc)
self._create_picking_in(product, 20.0, now, location=self.cases_loc)
self.mrp_multi_level_wiz.create(
{"mrp_area_ids": [(6, 0, self.cases_area.ids)]}
).run_mrp_multi_level()
inventory = self.mrp_inventory_obj.search(
[("mrp_area_id", "=", self.cases_area.id), ("product_id", "=", product.id)]
)
expected = [
{
"date": now.date(),
"demand_qty": 10.0,
"final_on_hand_qty": 15.0,
"initial_on_hand_qty": 5.0,
"running_availability": 15.0,
"supply_qty": 20.0,
"to_procure": 0.0,
},
]
self.assertEqual(len(expected), len(inventory))
for test_vals, inv in zip(expected, inventory):
for key in test_vals:
self.assertEqual(
test_vals[key],
inv[key],
f"unexpected value for {key}: {inv[key]} "
f"(expected {test_vals[key]} on {inv.date})",
)

View File

@@ -141,6 +141,11 @@
string="Main Supplier"
context="{'group_by':'main_supplier_id'}"
/>
<filter
name="group_date"
string="Date"
context="{'group_by':'date'}"
/>
<filter
name="group_release_date"
string="Date to Procure"

View File

@@ -23,23 +23,6 @@ class MultiLevelMrp(models.TransientModel):
help="If empty, all areas will be computed.",
)
@api.model
def _prepare_product_mrp_area_data(self, product_mrp_area):
qty_available = 0.0
product_obj = self.env["product.product"]
location_ids = product_mrp_area._get_locations()
for location in location_ids:
product_l = product_obj.with_context({"location": location.id}).browse(
product_mrp_area.product_id.id
)
qty_available += product_l.qty_available
return {
"product_mrp_area_id": product_mrp_area.id,
"mrp_qty_available": qty_available,
"mrp_llc": product_mrp_area.product_id.llc,
}
@api.model
def _prepare_mrp_move_data_from_stock_move(
self, product_mrp_area, move, direction="in"
@@ -538,13 +521,53 @@ class MultiLevelMrp(models.TransientModel):
self._init_mrp_move(product_mrp_area)
logger.info("End MRP initialisation")
def _get_qty_to_order(self, product_mrp_area, date, move_qty, onhand):
"""Compute the qty to order at a given date, for a product MRP area, given an
mrp.move quantity and an onhand quantity.
This method is an extension point, allowing a new module to change the way this
quantity should be computed.
"""
# The default rule is to resupply to rebuild the safety stock
return product_mrp_area.mrp_minimum_stock - onhand - move_qty
@api.model
def _init_mrp_move_grouped_demand(self, nbr_create, product_mrp_area):
def _init_mrp_move_grouped_demand(self, product_mrp_area):
last_date = None
last_qty = 0.00
onhand = product_mrp_area.qty_available
grouping_delta = product_mrp_area.mrp_nbr_days
demand_origin = []
if (
product_mrp_area.mrp_move_ids
and onhand < product_mrp_area.mrp_minimum_stock
):
last_date = self._get_safety_stock_target_date(product_mrp_area)
demand_origin.append("Safety Stock")
move = fields.first(product_mrp_area.mrp_move_ids)
if last_date and (
fields.Date.from_string(move.mrp_date)
>= last_date + timedelta(days=grouping_delta)
):
name = _("Safety Stock")
origin = ",".join(list({x for x in demand_origin if x}))
qtytoorder = self._get_qty_to_order(
product_mrp_area, last_date, 0, onhand
)
cm = self.create_action(
product_mrp_area_id=product_mrp_area,
mrp_date=last_date,
mrp_qty=qtytoorder,
name=name,
values=dict(origin=origin),
)
qty_ordered = cm.get("qty_ordered", 0.0)
onhand = onhand + qty_ordered
last_date = None
last_qty = 0.00
demand_origin = []
for move in product_mrp_area.mrp_move_ids:
if self._exclude_move(move):
continue
@@ -566,8 +589,10 @@ class MultiLevelMrp(models.TransientModel):
product_name=product_mrp_area.product_id.display_name,
delta_days=grouping_delta,
)
origin = ",".join(list(set(demand_origin)))
qtytoorder = product_mrp_area.mrp_minimum_stock - onhand - last_qty
origin = ",".join(list({x for x in demand_origin if x}))
qtytoorder = self._get_qty_to_order(
product_mrp_area, last_date, last_qty, onhand
)
cm = self.create_action(
product_mrp_area_id=product_mrp_area,
mrp_date=last_date,
@@ -579,13 +604,12 @@ class MultiLevelMrp(models.TransientModel):
onhand = onhand + last_qty + qty_ordered
last_date = None
last_qty = 0.00
nbr_create += 1
demand_origin = []
if (
(onhand + last_qty + move.mrp_qty) < product_mrp_area.mrp_minimum_stock
or (onhand + last_qty) < product_mrp_area.mrp_minimum_stock
):
if not last_date or last_qty == 0.0:
if not last_date:
last_date = fields.Date.from_string(move.mrp_date)
last_qty = move.mrp_qty
else:
@@ -603,8 +627,10 @@ class MultiLevelMrp(models.TransientModel):
product_name=product_mrp_area.product_id.display_name,
delta_days=grouping_delta,
)
origin = ",".join(list(set(demand_origin)))
qtytoorder = product_mrp_area.mrp_minimum_stock - onhand - last_qty
origin = ",".join(list({x for x in demand_origin if x}))
qtytoorder = self._get_qty_to_order(
product_mrp_area, last_date, last_qty, onhand
)
cm = self.create_action(
product_mrp_area_id=product_mrp_area,
mrp_date=last_date,
@@ -614,8 +640,82 @@ class MultiLevelMrp(models.TransientModel):
)
qty_ordered = cm.get("qty_ordered", 0.0)
onhand += qty_ordered
nbr_create += 1
return nbr_create
last_qty -= qty_ordered
if (onhand + last_qty) < product_mrp_area.mrp_minimum_stock:
mrp_date = self._get_safety_stock_target_date(product_mrp_area)
qtytoorder = self._get_qty_to_order(product_mrp_area, mrp_date, 0, onhand)
name = _("Safety Stock")
cm = self.create_action(
product_mrp_area_id=product_mrp_area,
mrp_date=mrp_date,
mrp_qty=qtytoorder,
name=name,
values=dict(origin=name),
)
qty_ordered = cm["qty_ordered"]
onhand += qty_ordered
def _get_safety_stock_target_date(self, product_mrp_area):
"""Get the date at which the safety stock rebuild should be targeted
This method is an extension point for modules who need to cusomize that date."""
return date.today()
@api.model
def _init_mrp_move_non_grouped_demand(self, product_mrp_area):
onhand = product_mrp_area.qty_available
for move in product_mrp_area.mrp_move_ids:
if self._exclude_move(move):
continue
# This works because mrp moves are ordered by:
# product_mrp_area_id, mrp_date, mrp_type desc, id
if onhand + move.mrp_qty < product_mrp_area.mrp_minimum_stock:
qtytoorder = self._get_qty_to_order(
product_mrp_area,
self._get_safety_stock_target_date(product_mrp_area),
0,
onhand,
)
name = _("Safety Stock")
cm = self.create_action(
product_mrp_area_id=product_mrp_area,
mrp_date=self._get_safety_stock_target_date(product_mrp_area),
mrp_qty=qtytoorder,
name=name,
values=dict(origin=name),
)
qty_ordered = cm["qty_ordered"]
onhand += qty_ordered
qtytoorder = self._get_qty_to_order(
product_mrp_area, move.mrp_date, move.mrp_qty, onhand
)
if qtytoorder > 0.0:
cm = self.create_action(
product_mrp_area_id=product_mrp_area,
mrp_date=move.mrp_date,
mrp_qty=qtytoorder,
name=move.name or "",
values=dict(origin=move.origin or ""),
)
qty_ordered = cm["qty_ordered"]
onhand += move.mrp_qty + qty_ordered
else:
onhand += move.mrp_qty
if onhand < product_mrp_area.mrp_minimum_stock:
mrp_date = self._get_safety_stock_target_date(product_mrp_area)
qtytoorder = self._get_qty_to_order(product_mrp_area, mrp_date, 0, onhand)
name = _("Safety Stock")
cm = self.create_action(
product_mrp_area_id=product_mrp_area,
mrp_date=mrp_date,
mrp_qty=qtytoorder,
name=name,
values=dict(origin=name),
)
qty_ordered = cm["qty_ordered"]
onhand += qty_ordered
@api.model
def _exclude_move(self, move):
@@ -638,47 +738,10 @@ class MultiLevelMrp(models.TransientModel):
llc += 1
for product_mrp_area in product_mrp_areas:
nbr_create = 0
onhand = product_mrp_area.qty_available
if product_mrp_area.mrp_nbr_days == 0:
for move in product_mrp_area.mrp_move_ids:
if self._exclude_move(move):
continue
qtytoorder = (
product_mrp_area.mrp_minimum_stock
- onhand
- move.mrp_qty
)
if qtytoorder > 0.0:
cm = self.create_action(
product_mrp_area_id=product_mrp_area,
mrp_date=move.mrp_date,
mrp_qty=qtytoorder,
name=move.name,
values=dict(origin=move.origin),
)
qty_ordered = cm["qty_ordered"]
onhand += move.mrp_qty + qty_ordered
nbr_create += 1
else:
onhand += move.mrp_qty
self._init_mrp_move_non_grouped_demand(product_mrp_area)
else:
nbr_create = self._init_mrp_move_grouped_demand(
nbr_create, product_mrp_area
)
if onhand < product_mrp_area.mrp_minimum_stock and nbr_create == 0:
qtytoorder = product_mrp_area.mrp_minimum_stock - onhand
name = _("Safety Stock")
cm = self.create_action(
product_mrp_area_id=product_mrp_area,
mrp_date=date.today(),
mrp_qty=qtytoorder,
name=name,
values=dict(origin=name),
)
qty_ordered = cm["qty_ordered"]
onhand += qty_ordered
self._init_mrp_move_grouped_demand(product_mrp_area)
counter += 1
log_msg = "MRP Calculation LLC {} Finished - Nbr. products: {}".format(
@@ -780,9 +843,7 @@ class MultiLevelMrp(models.TransientModel):
[("product_mrp_area_id", "=", product_mrp_area.id)], order="due_date"
).mapped("due_date")
mrp_dates = set(moves_dates + action_dates)
on_hand_qty = product_mrp_area.product_id.with_context(
location=product_mrp_area._get_locations().ids
)._product_available()[product_mrp_area.product_id.id]["qty_available"]
on_hand_qty = product_mrp_area.qty_available
running_availability = on_hand_qty
mrp_inventory_vals = []
for mdt in sorted(mrp_dates):