Initial commit: OCA Technical packages (595 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:03 +02:00
commit 2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions

View file

@ -0,0 +1,50 @@
# DDMRP
Odoo addon: ddmrp
## Installation
```bash
pip install odoo-bringout-oca-ddmrp-ddmrp
```
## Dependencies
This addon depends on:
- purchase_stock
- stock_demand_estimate
- web_widget_bokeh_chart
- mrp_multi_level
- base_cron_exclusion
- stock_warehouse_calendar
- stock_helper
## Manifest Information
- **Name**: DDMRP
- **Version**: 16.0.1.12.0
- **Category**: Warehouse
- **License**: LGPL-3
- **Installable**: True
## Source
Based on [OCA/ddmrp](https://github.com/OCA/ddmrp) branch 16.0, addon `ddmrp`.
## License
This package maintains the original LGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

@ -0,0 +1,341 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association
=====
DDMRP
=====
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:f43055b32a5e8e104d8a45da35fc5716767773214054bbfe54da0c23dd8be731
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |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/license-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%2Fddmrp-lightgray.png?logo=github
:target: https://github.com/OCA/ddmrp/tree/16.0/ddmrp
:alt: OCA/ddmrp
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/ddmrp-16-0/ddmrp-16-0-ddmrp
: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/ddmrp&target_branch=16.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
Demand Driven Material Requirements Planning is a formal multi-echelon
planning and execution method developed by Ms. Carol Ptak and Mr. Chad Smith.
DDMRP combines blended aspects of Material Requirements Planning (MRP),
Distribution Requirements Planning (DRP) with the pull and visibility
emphases found in Lean and the Theory of Constraints and the variability
reduction emphasis of Six Sigma.
This method has five sequential components:
#. *Strategic Inventory Positioning*. Answers the question "Given our system
and environment, where should we place inventory to have the best
protection?" and determines where should decoupling points of inventory be
placed.
#. *Buffer Profiles and Levels*. Determine the amount of protection at those
decoupling points.
#. *Dynamic Adjustments*. Allow the company to adapt buffers to group and
individual part trait changes over time through the use of several types
of adjustments.
#. *Demand Driven Planning*. Allow to launch purchase orders (POs),
manufacturing orders (MOs) and Transfer Orders (TOs) based on the priority
dictated by the buffers.
#. *Visible and Collaborative Execution*. These POs, MOs and TOs have to be
effectively managed to synchronize with the changes that often occur within
the "execution horizon."
These five components work together to greatly dampen, if not eliminate,
the nervousness of traditional MRP systems and the bullwhip effect in
complex and challenging environments.
This approach provides real information about those parts that are
truly at risk of negatively impacting the planned availability of inventory.
DDMRP sorts the significant few items that require attention from
the many parts that are being managed. Under the DDMRP approach,
fewer planners can make better decisions more quickly. That means companies
will be better able to leverage their working and human capital.
Demand Driven Material Requirements Planning is quickly being adopted
by a wide variety of leading companies across the world.
Some of the benefits reported by the DDMRP method include:
* High fill rate performance
* Lead time reductions
* Inventory reductions, while improving customer service
* Eliminate costs related to expedite
* Planners see priorities instead of constantly fighting the conflicting
messages of MRP
It is highly recommended to read the book 'Demand Driven Material
Requirements Planning (DDMRP)' by Carol Ptak and Chad Smith.
**Table of contents**
.. contents::
:local:
Installation
============
We strongly recommend to modify the configuration of the reservation method
to manual in the outgoing operation types (so deliveries
related to Sales Orders aren't automatically reserved) and to avoid to
reserve stock for specific moves, buffers are in fact a reservation of stock.
However, while **reservation is discouraged**, it is still available to be
used, in case of reserved stock be aware that the buffer will be blind to this
transfers and stock and you are bypassing the DDMRP reordering flow.
Configuration
=============
Scheduled actions
~~~~~~~~~~~~~~~~~
* Go to *Settings > Technical*.
* 'DDMRP Buffer ADU calculation'. Computes the Average Daily Usage for all
Buffers.
* 'Reordering Rule DDMRP calculation'. Computes the Qualified Demand, Net
Flow Position, Planning and Execution priorities for all Buffers.
Decoupled Lead Time computation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The DLT is automatically computed by the system.
For manufactured products' buffers just remember to provide and
set properly the following information:
* The *Manufacturing Lead Time* for the manufactured product. It can be found
at the product form view under the tab *Sales*.
* The *Delivery Lead Time* for the preferred vendor of a product. This is
important for the products which are purchased and are components in any
Bill of Materials.
For purchased/distributed products' buffers the logic is simpler.
* In the first place the system will look if there are Vendors for the product,
if so it will use the *Delivery Lead Time* of the preferred one.
* In case of absence of vendors, the *Lead Time* at the bottom of the Buffer
form view will be used.
Usage
=====
To easily identify were are you maintaining buffers in your Bill of
Materials, you will need to first provide location information on the Bills
of Materials.
* Go to *Manufacturing / Products / Bill of Materials* and update the
'Location' in all the Bill of Materials and associated lines,
indicating where will the parts be placed/used during the manufacturing
process.
* Print the report 'BOM Structure' to display where in your BOM are you
maintaining buffers, and to identify the Lead Time (LT) of each product, and
Decouple Lead Time (DLT).
Buffers
~~~~~~~
To list the list of inventory buffers, go to one of the following:
* *Inventory / Master Data / Stock Buffer Planning*
* *Inventory / Master Data / Reordering Rules*
Buffer Profiles
~~~~~~~~~~~~~~~
Buffer profiles make maintenance of buffers easier by grouping them in
profiles. Changes applied to the profiles will be applicable in the
associated buffer calculations.
* Go to *Inventory / Configuration / Buffer Profiles*.
The Buffer Profile Lead Time Factor influences the size of the Buffer Green
zone. Items with longer lead times will usually have smaller green zones, which
will translate in more frequent supply order generation.
* Go to *Inventory / Configuration / Buffer Profile Lead Time Factor* to
chan
The Buffer Profile Variability Factor influences the size of the Buffer Red
Safety zone. Items with longer lead times will usually have smaller green
zones, which will translate in more frequent supply order generation.
* Go to *Inventory / Configuration / Buffer Profile Lead Time Factor*.
Usual factors should range from 0.2 (long lead time) to 0.8 (short lead time).
Product attributes
~~~~~~~~~~~~~~~~~~
* For manufactured products, go to *Manufacturing / Products* and
update the 'Manufacturing Lead Time' field, available in the tab *Inventory*.
* For purchased products, go to go to *Purchasing / Products* and update the
*Delivery Lead Time* for each vendor, available in tab *Purchase* and section
*Vendors*.
ADU Calculation Methods
~~~~~~~~~~~~~~~~~~~~~~~
The Average Daily Usage (ADU) defines the frequency of demand of a product in a
certain location. It can be computed in different ways, which you can configure
with ADU calculation methods as follows:
#. Go to *Inventory / Configuration / DDMRP / ADU calculation methods*.
#. Indicate a name, a calculation method (fixed, past-looking,
future-looking or blended).
#. Fill the corresponding period (past, future or both for blended method) to
specify the length of period consideration (in days).
#. Indicate the source of information: stock moves or demand estimates.
#. If you use the blended method fill also the *Past Factor* and
*Future Factor*.
If you do not have prior history of stock moves in your system, it is advised
to use fixed method or start to work on future estimates. If you have
past-history of stock moves, best use past-looking method or blended method.
The ADU is computed every day by default in a background job independently
from the other buffer fields. This computation can be done with less frequency
but it is not recommended to run it less than weekly or more than daily.
Circumstantially, If you need to force the calculation of the ADU go to
*Inventory / Configuration / DDMRP / Run DDMRP* and click on
*Run ADU calculation*.
Known issues / Roadmap
======================
The DDMRP `roadmap <https://github.com/OCA/ddmrp/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement>`_
and `known issues <https://github.com/OCA/ddmrp/issues?q=is%3Aopen+is%3Aissue+label%3Abug>`_ can
be found on GitHub.
Changelog
=========
13.0.1.1.0 (2020-07-01)
~~~~~~~~~~~~~~~~~~~~~~~
**Features**
- - New setting *Update NFP on Stock Buffers on relevant events*.
- New dedicated settings block. (`#50 <https://github.com/OCA/ddmrp/issues/50>`_)
13.0.1.0.0 (2020-06-11)
~~~~~~~~~~~~~~~~~~~~~~~
* [MIG/REF] Migration of module to v13 and refactor (added new dedicated model
for stock buffer).
11.0.1.3.0 (2019-02-21)
~~~~~~~~~~~~~~~~~~~~~~~
* [ADD] New chart that depict information about the supply and demand (
displaying also de order spike threshold and horizon) for a buffer.
(`#40 <https://github.com/OCA/ddmrp/pull/40>`_)
11.0.1.2.0 (2019-01-29)
~~~~~~~~~~~~~~~~~~~~~~~
* [IMP] Performance improvement of execution priority calculation and ADU.
(`#36 <https://github.com/OCA/ddmrp/pull/36>`_)
* [IMP] Use the minimum quantity to adjust the procure recommendation.
(`#37 <https://github.com/OCA/ddmrp/pull/37>`_)
11.0.1.1.0 (2018-08-31)
~~~~~~~~~~~~~~~~~~~~~~~
* [IMP] Implemented Blended ADU calculation method.
(`#23 <https://github.com/OCA/ddmrp/pull/23>`_)
11.0.1.0.0 (2018-07-16)
~~~~~~~~~~~~~~~~~~~~~~~
* Start of the history
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/ddmrp/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/ddmrp/issues/new?body=module:%20ddmrp%0Aversion:%2016.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
~~~~~~~~~~~~
* Jordi Ballester Alomar <jordi.ballester@forgeflow.com>
* Lois Rilo Antelo <lois.rilo@forgeflow.com>
* Guewen Baconnier <guewen.baconnier@camptocamp.com>
* Adria Gil Sorribes <adria.gil@forgeflow.com>
* Christopher Ormaza <chris.ormaza@forgeflow.com>
Other credits
~~~~~~~~~~~~~
The initial development of this module has been financially supported by:
* Aleph Objects, Inc.
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
.. |maintainer-ChrisOForgeFlow| image:: https://github.com/ChrisOForgeFlow.png?size=40px
:target: https://github.com/ChrisOForgeFlow
:alt: ChrisOForgeFlow
Current `maintainers <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-JordiBForgeFlow| |maintainer-LoisRForgeFlow| |maintainer-ChrisOForgeFlow|
This module is part of the `OCA/ddmrp <https://github.com/OCA/ddmrp/tree/16.0/ddmrp>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View file

@ -0,0 +1,3 @@
from . import models
from . import wizards
from . import report

View file

@ -0,0 +1,73 @@
# Copyright 2016-20 ForgeFlow S.L. (https://www.forgeflow.com)
# Copyright 2016 Aleph Objects, Inc. (https://www.alephobjects.com/)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
{
"name": "DDMRP",
"summary": "Demand Driven Material Requirements Planning",
"version": "16.0.1.12.0",
"license": "LGPL-3",
"development_status": "Beta",
"author": "ForgeFlow, " "Odoo Community Association (OCA)",
"maintainers": ["JordiBForgeFlow", "LoisRForgeFlow", "ChrisOForgeFlow"],
"website": "https://github.com/OCA/ddmrp",
"category": "Warehouse",
"depends": [
"purchase_stock",
"stock_demand_estimate",
"web_widget_bokeh_chart",
"mrp_multi_level",
"base_cron_exclusion",
"stock_warehouse_calendar",
"stock_helper",
],
"data": [
"data/product_adu_calculation_method_data.xml",
"data/stock_buffer_profile_variability_data.xml",
"data/stock_buffer_profile_lead_time_data.xml",
"data/stock_buffer_profile_data.xml",
"data/ir_sequence.xml",
"data/decimal_precision_data.xml",
"report/mrp_report_bom_structure.xml",
"security/ddmrp_groups.xml",
"security/ddmrp_rules.xml",
"security/ir.model.access.csv",
"wizards/make_procurement_buffer_view.xml",
"views/stock_buffer_profile_view.xml",
"views/stock_buffer_profile_variability_view.xml",
"views/stock_buffer_profile_lead_time_view.xml",
"views/product_adu_calculation_method_view.xml",
"views/stock_warehouse_views.xml",
"views/mrp_production_view.xml",
"views/purchase_order_view.xml",
"views/purchase_order_line_view.xml",
"views/product_view.xml",
"views/mrp_bom_view.xml",
"views/stock_move_views.xml",
"views/stock_buffer_view.xml",
"views/stock_picking.xml",
"data/ir_cron.xml",
"wizards/ddmrp_duplicate_buffer.xml",
"wizards/ddmrp_run_view.xml",
"wizards/mrp_bom_change_location.xml",
"wizards/res_config_settings_views.xml",
],
"demo": [
"demo/ddmrp_demo_user.xml",
"demo/res_partner_demo.xml",
"demo/product_category_demo.xml",
"demo/product_product_demo.xml",
"demo/product_supplierinfo_demo.xml",
"demo/mrp_bom_demo.xml",
"demo/stock_buffer_demo.xml",
],
"assets": {
"web.assets_backend": [
"ddmrp/static/src/**/*.js",
"ddmrp/static/src/**/*.scss",
"ddmrp/static/src/**/*.xml",
],
},
"installable": True,
"application": True,
}

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record
forcecreate="True"
id="lead_time_decimal_precision"
model="decimal.precision"
>
<field name="name">Lead Time</field>
<field name="digits" eval="0" />
</record>
<record
forcecreate="True"
id="average_daily_usage_decimal_precision"
model="decimal.precision"
>
<field name="name">Average Daily Usage</field>
<field name="digits" eval="3" />
</record>
</odoo>

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record
id="ir_cron_ddmrp_calculation_scheduler_action"
model="ir.cron"
forcecreate="True"
>
<field name="name">DDMRP Buffer calculation</field>
<field name="state">code</field>
<field name="user_id" ref="base.user_root" />
<field name="interval_number">8</field>
<field name="interval_type">hours</field>
<field name="numbercall">-1</field>
<field name="priority">6</field>
<field eval="False" name="doall" />
<field name="model_id" ref="model_stock_buffer" />
<field name="code">model.cron_ddmrp(True)</field>
</record>
<record
id="ir_cron_ddmrp_adu_calculation_scheduler_action"
model="ir.cron"
forcecreate="True"
>
<field name="name">DDMRP Buffer ADU calculation</field>
<field name="state">code</field>
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="priority">3</field>
<field
name="mutually_exclusive_cron_ids"
eval="[(6, 0, [ref('ir_cron_ddmrp_calculation_scheduler_action'),])]"
/>
<field eval="False" name="doall" />
<field name="model_id" ref="model_stock_buffer" />
<field name="code">model.cron_ddmrp_adu(True)</field>
</record>
</odoo>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="sequence_mrp_op" model="ir.sequence">
<field name="name">Stock Buffer</field>
<field name="code">stock.buffer</field>
<field name="prefix">SB/</field>
<field name="padding">5</field>
<field name="number_next">1</field>
<field name="number_increment">1</field>
<field name="company_id" eval="False" />
</record>
</odoo>

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="adu_calculation_method_fixed" model="product.adu.calculation.method">
<field name="name">Fixed</field>
<field name="method">fixed</field>
</record>
<record id="adu_calculation_method_past_120" model="product.adu.calculation.method">
<field name="name">Past (120 days)</field>
<field name="method">past</field>
<field name="source_past">actual</field>
<field name="horizon_past">120</field>
</record>
<record
id="adu_calculation_method_future_120"
model="product.adu.calculation.method"
>
<field name="name">Future (120 days)</field>
<field name="method">future</field>
<field name="source_future">estimates</field>
<field name="horizon_future">120</field>
</record>
<record
id="adu_calculation_method_future_120_estimates_mrp"
model="product.adu.calculation.method"
>
<field name="name">Future (Direct + Indirect)(120 days)</field>
<field name="method">future</field>
<field name="source_future">estimates_mrp</field>
<field name="horizon_future">120</field>
</record>
</odoo>

View file

@ -0,0 +1,894 @@
<?xml version="1.0" ?>
<odoo noupdate="1">
<record
id="stock_buffer_profile_replenish_purchased_short_low"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_replenish_manufactured_short_low"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_replenish_distributed_short_low"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_replenish_purchased_short_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_replenish_manufactured_short_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_replenish_distributed_short_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_replenish_purchased_short_high"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_replenish_manufactured_short_high"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_replenish_distributed_short_high"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_replenish_purchased_medium_low"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_replenish_manufactured_medium_low"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_replenish_distributed_medium_low"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_replenish_purchased_medium_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_replenish_manufactured_medium_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_replenish_distributed_medium_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_replenish_purchased_medium_high"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_replenish_manufactured_medium_high"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_replenish_distributed_medium_high"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_replenish_purchased_long_low"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_replenish_manufactured_long_low"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_replenish_distributed_long_low"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_replenish_purchased_long_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_replenish_manufactured_long_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_replenish_distributed_long_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_replenish_purchased_long_high"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_replenish_manufactured_long_high"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_replenish_distributed_long_high"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_min_max_purchased_short_low"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_min_max_manufactured_short_low"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_min_max_distributed_short_low"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_min_max_purchased_short_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_min_max_manufactured_short_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_min_max_distributed_short_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_min_max_purchased_short_high"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_min_max_manufactured_short_high"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_min_max_distributed_short_high"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_min_max_purchased_medium_low"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_min_max_manufactured_medium_low"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_min_max_distributed_medium_low"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_min_max_purchased_medium_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_min_max_manufactured_medium_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_min_max_distributed_medium_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_min_max_purchased_medium_high"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_min_max_manufactured_medium_high"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_min_max_distributed_medium_high"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_min_max_purchased_long_low"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_min_max_manufactured_long_low"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_min_max_distributed_long_low"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_min_max_purchased_long_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_min_max_manufactured_long_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_min_max_distributed_long_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_min_max_purchased_long_high"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_min_max_manufactured_long_high"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_min_max_distributed_long_high"
model="stock.buffer.profile"
>
<field name="replenish_method">min_max</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_replenish_override_purchased_short_low"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_replenish_override_manufactured_short_low"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_replenish_override_distributed_short_low"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_replenish_override_purchased_short_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_replenish_override_manufactured_short_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_replenish_override_distributed_short_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_replenish_override_purchased_short_high"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_replenish_override_manufactured_short_high"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_replenish_override_distributed_short_high"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_short" />
</record>
<record
id="stock_buffer_profile_replenish_override_purchased_medium_low"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_replenish_override_manufactured_medium_low"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_replenish_override_distributed_medium_low"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_replenish_override_purchased_medium_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_replenish_override_manufactured_medium_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_replenish_override_distributed_medium_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_replenish_override_purchased_medium_high"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_replenish_override_manufactured_medium_high"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_replenish_override_distributed_medium_high"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_medium" />
</record>
<record
id="stock_buffer_profile_replenish_override_purchased_long_low"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_replenish_override_manufactured_long_low"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_replenish_override_distributed_long_low"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field name="variability_id" ref="ddmrp.stock_buffer_profile_variability_low" />
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_replenish_override_purchased_long_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_replenish_override_manufactured_long_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_replenish_override_distributed_long_medium"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_medium"
/>
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_replenish_override_purchased_long_high"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">purchased</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_replenish_override_manufactured_long_high"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">manufactured</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
<record
id="stock_buffer_profile_replenish_override_distributed_long_high"
model="stock.buffer.profile"
>
<field name="replenish_method">replenish_override</field>
<field
name="variability_id"
ref="ddmrp.stock_buffer_profile_variability_high"
/>
<field name="item_type">distributed</field>
<field name="lead_time_id" ref="ddmrp.stock_buffer_profile_lead_time_long" />
</record>
</odoo>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record
id="stock_buffer_profile_lead_time_short"
model="stock.buffer.profile.lead.time"
>
<field name="name">Short</field>
<field name="factor">0.75</field>
</record>
<record
id="stock_buffer_profile_lead_time_medium"
model="stock.buffer.profile.lead.time"
>
<field name="name">Medium</field>
<field name="factor">0.5</field>
</record>
<record
id="stock_buffer_profile_lead_time_long"
model="stock.buffer.profile.lead.time"
>
<field name="name">Long</field>
<field name="factor">0.25</field>
</record>
</odoo>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record
id="stock_buffer_profile_variability_low"
model="stock.buffer.profile.variability"
>
<field name="name">Low</field>
<field name="factor">0.25</field>
</record>
<record
id="stock_buffer_profile_variability_medium"
model="stock.buffer.profile.variability"
>
<field name="name">Medium</field>
<field name="factor">0.5</field>
</record>
<record
id="stock_buffer_profile_variability_high"
model="stock.buffer.profile.variability"
>
<field name="name">High</field>
<field name="factor">0.75</field>
</record>
</odoo>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="base.user_demo" model="res.users">
<field eval="[(4, ref('group_stock_buffer_maintainer'))]" name="groups_id" />
</record>
</odoo>

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<!-- FP-01 -->
<record id="mrp_bom_fp01" model="mrp.bom">
<field name="product_tmpl_id" ref="product_product_fp01_product_template" />
<field name="product_uom_id" ref="uom.product_uom_unit" />
<field name="sequence">5</field>
</record>
<record id="mrp_bom_fp01_line_as01" model="mrp.bom.line">
<field name="product_id" ref="product_product_as01" />
<field name="product_qty">1</field>
<field name="product_uom_id" ref="uom.product_uom_unit" />
<field name="sequence">5</field>
<field name="bom_id" ref="mrp_bom_fp01" />
</record>
<!-- AS-01 -->
<record id="mrp_bom_as01" model="mrp.bom">
<field name="product_tmpl_id" ref="product_product_as01_product_template" />
<field name="product_uom_id" ref="uom.product_uom_unit" />
<field name="sequence">5</field>
</record>
<record id="mrp_bom_as01_line_rm01" model="mrp.bom.line">
<field name="product_id" ref="product_product_rm01" />
<field name="product_qty">1</field>
<field name="product_uom_id" ref="uom.product_uom_unit" />
<field name="sequence">5</field>
<field name="bom_id" ref="mrp_bom_as01" />
</record>
<record id="mrp_bom_as01_line_rm02" model="mrp.bom.line">
<field name="product_id" ref="product_product_rm02" />
<field name="product_qty">1</field>
<field name="product_uom_id" ref="uom.product_uom_unit" />
<field name="sequence">10</field>
<field name="bom_id" ref="mrp_bom_as01" />
</record>
</odoo>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="product_category_ddmrp" model="product.category">
<field name="name">DDMRP</field>
</record>
</odoo>

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="product_product_fp01" model="product.product">
<field name="name">FP-01</field>
<field name="categ_id" ref="product_category_ddmrp" />
<field name="type">product</field>
<field name="uom_id" ref="uom.product_uom_unit" />
<field name="uom_po_id" ref="uom.product_uom_unit" />
<field name="produce_delay">2</field>
<field
name="route_ids"
eval="[(6, 0, [ref('mrp.route_warehouse0_manufacture')])]"
/>
</record>
<record id="product_product_as01" model="product.product">
<field name="name">AS-01</field>
<field name="categ_id" ref="product_category_ddmrp" />
<field name="type">product</field>
<field name="uom_id" ref="uom.product_uom_unit" />
<field name="uom_po_id" ref="uom.product_uom_unit" />
<field name="produce_delay">6</field>
<field
name="route_ids"
eval="[(6, 0, [ref('mrp.route_warehouse0_manufacture')])]"
/>
</record>
<record id="product_product_rm01" model="product.product">
<field name="name">RM-01</field>
<field name="categ_id" ref="product_category_ddmrp" />
<field name="type">product</field>
<field name="uom_id" ref="uom.product_uom_unit" />
<field name="uom_po_id" ref="uom.product_uom_unit" />
<field
name="route_ids"
eval="[(6, 0, [ref('purchase_stock.route_warehouse0_buy')])]"
/>
</record>
<record id="product_product_rm02" model="product.product">
<field name="name">RM-02</field>
<field name="categ_id" ref="product_category_ddmrp" />
<field name="type">product</field>
<field name="uom_id" ref="uom.product_uom_unit" />
<field name="uom_po_id" ref="uom.product_uom_unit" />
<field
name="route_ids"
eval="[(6, 0, [ref('purchase_stock.route_warehouse0_buy')])]"
/>
</record>
</odoo>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="product_supplierinfo_rm01" model="product.supplierinfo">
<field name="product_tmpl_id" ref="product_product_rm01_product_template" />
<field name="partner_id" ref="res_partner_forgeflow" />
<field name="delay">25</field>
<field name="min_qty">50</field>
<field name="price">100</field>
</record>
<record id="product_supplierinfo_rm02" model="product.supplierinfo">
<field name="product_tmpl_id" ref="product_product_rm02_product_template" />
<field name="partner_id" ref="res_partner_forgeflow" />
<field name="delay">14</field>
<field name="min_qty">10</field>
<field name="price">100</field>
</record>
</odoo>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="res_partner_forgeflow" model="res.partner">
<field name="name">ForgeFlow</field>
<field name="is_company">1</field>
<field name="company_id" ref="base.main_company" />
<field name="email">forgeflow@yourcompany.example.com</field>
<field name="street">Rambla de Catalunya</field>
<field name="city">Barcelona</field>
<field name="zip">08008</field>
<field name="website">https://www.forgeflow.com</field>
<field name="country_id" ref="base.es" />
<field
name="image_1920"
type="base64"
file="ddmrp/static/img/res_partner_forgeflow-image.png"
/>
</record>
</odoo>

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="stock_buffer_fp01" model="stock.buffer">
<field name="product_id" ref="product_product_fp01" />
<field
name="buffer_profile_id"
ref="ddmrp.stock_buffer_profile_replenish_manufactured_medium_medium"
/>
<field name="adu_calculation_method" ref="ddmrp.adu_calculation_method_fixed" />
<field name="adu_fixed">60</field>
<field name="order_spike_horizon">15</field>
<field name="warehouse_id" ref="stock.warehouse0" />
<field name="location_id" ref="stock.stock_location_stock" />
<field name="minimum_order_quantity">5</field>
</record>
<record id="stock_buffer_rm01" model="stock.buffer">
<field name="product_id" ref="product_product_rm01" />
<field
name="buffer_profile_id"
ref="ddmrp.stock_buffer_profile_replenish_purchased_medium_medium"
/>
<field name="adu_calculation_method" ref="ddmrp.adu_calculation_method_fixed" />
<field name="adu_fixed">50</field>
<field name="order_spike_horizon">30</field>
<field name="warehouse_id" ref="stock.warehouse0" />
<field name="location_id" ref="stock.stock_location_stock" />
<field name="minimum_order_quantity">50</field>
</record>
</odoo>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,20 @@
# Copyright 2021 ForgeFlow <http://www.forgeflow.com>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import logging
_logger = logging.getLogger(__name__)
def migrate(cr, version):
_logger.info("Set buffer profile data as no-update.")
cr.execute(
"""
UPDATE ir_model_data imd
SET noupdate = true
WHERE "module" = 'ddmrp' AND (
name ILIKE 'stock_buffer_profile_replenish_%'
OR name ILIKE 'stock_buffer_profile_min_max%'
);
"""
)

View file

@ -0,0 +1,10 @@
from odoo import SUPERUSER_ID, api
def migrate(cr, version):
env = api.Environment(cr, SUPERUSER_ID, {})
buffers = env["stock.buffer"].search(
[("buffer_profile_id.item_type", "=", "distributed")]
)
if buffers:
buffers._calc_distributed_source_location()

View file

@ -0,0 +1,18 @@
from . import mrp_bom
from . import mrp_production
from . import procurement_group
from . import product_adu_calculation_method
from . import purchase_order
from . import stock_buffer_profile
from . import stock_buffer_profile_lead_time
from . import stock_buffer_profile_variability
from . import stock_rule
from . import stock_warehouse
from . import stock_buffer
from . import product_template
from . import stock_move
from . import stock_move_line
from . import stock_picking
from . import res_company
from . import product_product
from . import stock_warehouse_orderpoint

View file

@ -0,0 +1,198 @@
# Copyright 2017-24 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import logging
from odoo import _, api, fields, models
_logger = logging.getLogger(__name__)
class MrpBom(models.Model):
_inherit = "mrp.bom"
is_buffered = fields.Boolean(
string="Buffered?",
compute="_compute_is_buffered",
help="True when the product has an DDMRP buffer associated.",
)
buffer_id = fields.Many2one(
comodel_name="stock.buffer",
string="Stock Buffer",
compute="_compute_buffer",
)
dlt = fields.Float(
string="Decoupled Lead Time (days)",
compute="_compute_dlt",
)
context_location_id = fields.Many2one(
comodel_name="stock.location",
string="Stock Location",
compute="_compute_context_location",
)
# This is a legacy field that can be removed in v17
location_id = fields.Many2one(related="context_location_id")
def _get_search_buffer_domain(self):
product = self.product_id
if not product and self.product_tmpl_id.product_variant_ids:
product = self.product_tmpl_id.product_variant_ids[0]
domain = [
("product_id", "=", product.id),
("location_id", "=", self.context_location_id.id),
]
if self.company_id:
domain.append(("company_id", "=", self.company_id.id))
return domain
@api.depends("product_id", "product_tmpl_id", "context_location_id")
def _compute_buffer(self):
for record in self:
domain = record._get_search_buffer_domain()
# NOTE: It can be possible to find multiple buffers.
# For example if the BoM has no location set, and there
# are buffers with the same product_id and buffer_profile_id
# You do not know which one the search function finds.
buffer = self.env["stock.buffer"].search(domain, limit=1)
record.buffer_id = buffer
@api.depends("buffer_id")
def _compute_is_buffered(self):
for bom in self:
bom.is_buffered = True if bom.buffer_id else False
@api.depends_context("location_id")
def _compute_context_location(self):
warehouse_model = self.env["stock.warehouse"]
for rec in self:
if self.env.context.get("location_id", None):
rec.context_location_id = self.env.context.get("location_id")
elif self.env.context.get("warehouse", None):
warehouse_id = self.env.context.get("warehouse")
rec.context_location_id = warehouse_model.browse(
warehouse_id
).lot_stock_id.id
else:
company_id = rec.company_id or self.env.company
warehouse_id = warehouse_model.search(
[("company_id", "=", company_id.id)], limit=1
)
rec.context_location_id = warehouse_id.lot_stock_id.id
def _get_produce_delay(self):
self.ensure_one()
return self.product_id.produce_delay or self.product_tmpl_id.produce_delay
def _get_longest_path(self):
if not self.bom_line_ids:
return 0.0
paths = [0] * len(self.bom_line_ids)
i = 0
for line in self.bom_line_ids:
if line.is_buffered:
i += 1
elif line.product_id.bom_ids:
# If the a component is manufactured we continue exploding.
location = line.context_location_id
line_boms = line.product_id.bom_ids
bom = line_boms.filtered(
lambda bom: bom.context_location_id == location
) or line_boms.filtered(lambda bom: not bom.context_location_id)
if bom:
paths[i] += bom[0]._get_produce_delay()
paths[i] += bom[0]._get_longest_path()
else:
_logger.info(
"ddmrp (dlt): Product %s has no BOM for location "
"%s." % (line.product_id.name, location.name)
)
i += 1
else:
# assuming they are purchased,
if line.product_id.seller_ids:
paths[i] = line.product_id.seller_ids[0].delay
else:
_logger.info(
"ddmrp (dlt): Product %s has no seller set."
% line.product_id.name
)
i += 1
return max(paths)
def _get_manufactured_dlt(self):
"""Computes the Decoupled Lead Time exploding all the branches of the
BOM until a buffered position and then selecting the greatest."""
self.ensure_one()
dlt = self._get_produce_delay()
dlt += self._get_longest_path()
return dlt
@api.depends("context_location_id")
@api.depends_context("location_id")
def _compute_dlt(self):
for rec in self:
rec.dlt = rec._get_manufactured_dlt()
def action_change_context_location(self):
return {
"type": "ir.actions.act_window",
"name": _("Change MRP BoM Location"),
"res_model": "mrp.bom.change.location",
"view_mode": "form",
"target": "new",
}
class MrpBomLine(models.Model):
_inherit = "mrp.bom.line"
is_buffered = fields.Boolean(
string="Buffered?",
compute="_compute_is_buffered",
help="True when the product has an DDMRP buffer associated.",
)
buffer_id = fields.Many2one(
comodel_name="stock.buffer",
string="Stock Buffer",
compute="_compute_is_buffered",
)
dlt = fields.Float(
string="Decoupled Lead Time (days)",
compute="_compute_dlt",
)
context_location_id = fields.Many2one(related="bom_id.context_location_id")
# This is a legacy field that can be removed in v17
location_id = fields.Many2one(related="context_location_id")
def _get_search_buffer_domain(self):
product = self.product_id
if not product and self.product_tmpl_id.product_variant_ids:
product = self.product_tmpl_id.product_variant_ids[0]
domain = [
("product_id", "=", product.id),
("location_id", "=", self.context_location_id.id),
]
if self.company_id:
domain.append(("company_id", "=", self.company_id.id))
return domain
@api.depends("context_location_id")
@api.depends_context("location_id")
def _compute_is_buffered(self):
for line in self:
domain = line._get_search_buffer_domain()
buffer = self.env["stock.buffer"].search(domain, limit=1)
line.buffer_id = buffer
line.is_buffered = True if buffer else False
@api.depends("product_id")
def _compute_dlt(self):
for rec in self:
if rec.product_id.bom_ids:
rec.dlt = rec.product_id.bom_ids[0].dlt
else:
rec.dlt = (
rec.product_id.seller_ids
and rec.product_id.seller_ids[0].delay
or 0.0
)

View file

@ -0,0 +1,75 @@
# Copyright 2016-20 ForgeFlow S.L. (http://www.forgeflow.com)
# Copyright 2016 Aleph Objects, Inc. (https://www.alephobjects.com/)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import api, fields, models
from .stock_buffer import _PRIORITY_LEVEL
class MrpProduction(models.Model):
_inherit = "mrp.production"
buffer_id = fields.Many2one(
comodel_name="stock.buffer",
index=True,
string="Stock Buffer",
)
execution_priority_level = fields.Selection(
string="Buffer On-Hand Alert Level",
selection=_PRIORITY_LEVEL,
readonly=True,
)
on_hand_percent = fields.Float(
string="On Hand/TOR (%)",
)
@api.model_create_multi
def create(self, vals_list):
records = super().create(vals_list)
records._calc_execution_priority()
return records
def _calc_execution_priority(self):
"""Technical note: this method cannot be decorated with api.depends,
otherwise it would generate a infinite recursion."""
prods = self.filtered(
lambda r: r.buffer_id and r.state not in ["done", "cancel"]
)
for rec in prods:
rec.execution_priority_level = rec.buffer_id.execution_priority_level
rec.on_hand_percent = rec.buffer_id.on_hand_percent
to_update = (self - prods).filtered(
lambda r: r.execution_priority_level or r.on_hand_percent
)
if to_update:
to_update.write({"execution_priority_level": None, "on_hand_percent": None})
def _search_execution_priority(self, operator, value):
"""Search on the execution priority by evaluating on all
open manufacturing orders."""
all_records = self.search([("state", "not in", ["done", "cancel"])])
if operator == "=":
found_ids = [
a.id for a in all_records if a.execution_priority_level == value
]
elif operator == "in" and isinstance(value, list):
found_ids = [
a.id for a in all_records if a.execution_priority_level in value
]
elif operator in ("!=", "<>"):
found_ids = [
a.id for a in all_records if a.execution_priority_level != value
]
elif operator == "not in" and isinstance(value, list):
found_ids = [
a.id for a in all_records if a.execution_priority_level not in value
]
else:
raise NotImplementedError(
"Search operator {} not implemented for value {}".format(
operator, value
)
)
return [("id", "in", found_ids)]

View file

@ -0,0 +1,50 @@
# Copyright 2016-20 ForgeFlow S.L. (http://www.forgeflow.com)
# Copyright 2016 Aleph Objects, Inc. (https://www.alephobjects.com/)
# Copyright 2018 Camptocamp SA https://www.camptocamp.com
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import api, models
class ProcurementGroup(models.Model):
_inherit = "procurement.group"
# UOM: (stock_orderpoint_uom):
@api.model
def run(self, procurements, raise_user_error=True):
Proc = self.env["procurement.group"].Procurement
indexes_to_pop = []
new_procs = []
for i, procurement in enumerate(procurements):
if "buffer_id" in procurement.values:
buffer = procurement.values.get("buffer_id")
if (
buffer.procure_uom_id
and procurement.product_uom != buffer.procure_uom_id
):
new_product_qty = procurement.product_uom._compute_quantity(
procurement.product_qty, buffer.procure_uom_id
)
new_product_uom = buffer.procure_uom_id
new_procs.append(
Proc(
procurement.product_id,
new_product_qty,
new_product_uom,
procurement.location_id,
procurement.name,
procurement.origin,
procurement.company_id,
procurement.values,
)
)
indexes_to_pop.append(i)
if new_procs:
indexes_to_pop.reverse()
for index in indexes_to_pop:
procurements.pop(index)
procurements.extend(new_procs)
return super(ProcurementGroup, self).run(
procurements, raise_user_error=raise_user_error
)

View file

@ -0,0 +1,99 @@
# Copyright 2016-20 ForgeFlow S.L. (http://www.forgeflow.com)
# Copyright 2016 Aleph Objects, Inc. (https://www.alephobjects.com/)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class ProductAduCalculationMethod(models.Model):
_name = "product.adu.calculation.method"
_description = "Product Average Daily Usage calculation method"
@api.model
def _get_calculation_method(self):
return [
("fixed", _("Fixed ADU")),
("past", _("Past-looking")),
("future", _("Future-looking")),
("blended", _("Blended")),
]
@api.model
def _get_source_selection(self):
return [
("actual", "Use actual Stock Moves"),
("estimates", "Use Demand Estimates"),
("estimates_mrp", "Use Demand Estimates + Indirect Demand from MRP Moves"),
]
name = fields.Char(required=True)
method = fields.Selection(
selection="_get_calculation_method",
string="Calculation method",
)
source_past = fields.Selection(
selection="_get_source_selection",
string="Past Source",
help="Information source used for past calculation.",
)
horizon_past = fields.Float(
string="Past Horizon",
help="Length-of-period horizon in days looking past.",
)
factor_past = fields.Float(
string="Past Factor",
help="When using a blended method, this is the relative weight "
"assigned to the past part of the combination.",
default=0.5,
)
source_future = fields.Selection(
selection="_get_source_selection",
string="Future Source",
help="Information source used for future calculation.",
)
horizon_future = fields.Float(
string="Future Horizon",
help="Length-of-period horizon in days looking forward.",
)
factor_future = fields.Float(
string="Future Factor",
help="When using a blended method, this is the relative weight "
"assigned to the future part of the combination.",
default=0.5,
)
company_id = fields.Many2one(
comodel_name="res.company",
string="Company",
)
@api.constrains("method", "horizon_past", "horizon_future")
def _check_horizon(self):
for rec in self:
if rec.method in ["past", "blended"] and not rec.horizon_past:
raise ValidationError(_("Please indicate a Past Horizon."))
if rec.method in ["blended", "future"] and not rec.horizon_future:
raise ValidationError(_("Please indicate a Future Horizon."))
@api.constrains("method", "source_past", "source_future")
def _check_source(self):
for rec in self:
if rec.method in ["past", "blended"] and not rec.source_past:
raise ValidationError(_("Please indicate a Past Source."))
if rec.method in ["blended", "future"] and not rec.source_future:
raise ValidationError(_("Please indicate a Future Source."))
@api.constrains("method", "factor_past", "factor_future")
def _check_factor(self):
for rec in self.filtered(lambda r: r.method == "blended"):
if (
rec.factor_past + rec.factor_future != 1.0
or rec.factor_future < 0.0
or rec.factor_past < 0.0
):
raise ValidationError(
_(
"In blended method, past and future factors must be "
"positive and sum exactly 1,0."
)
)

View file

@ -0,0 +1,35 @@
# Copyright 2020 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import fields, models
class Product(models.Model):
_inherit = "product.product"
buffer_ids = fields.One2many(
comodel_name="stock.buffer",
string="Stock Buffers",
inverse_name="product_id",
)
buffer_count = fields.Integer(compute="_compute_buffer_count")
def write(self, values):
res = super().write(values)
if values.get("active") is False:
buffers = (
self.env["stock.buffer"].sudo().search([("product_id", "in", self.ids)])
)
buffers.write({"active": False})
return res
def _compute_buffer_count(self):
for rec in self:
rec.buffer_count = len(rec.buffer_ids)
def action_view_stock_buffers(self):
action = self.env["ir.actions.actions"]._for_xml_id("ddmrp.action_stock_buffer")
action["context"] = {}
action["domain"] = [("id", "in", self.buffer_ids.ids)]
return action

View file

@ -0,0 +1,39 @@
# Copyright 2019-20 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class ProductTemplate(models.Model):
_inherit = "product.template"
buffer_count = fields.Integer(compute="_compute_buffer_count")
def _compute_buffer_count(self):
for rec in self:
rec.buffer_count = sum(
variant.buffer_count for variant in rec.product_variant_ids
)
# UOM: (stock_orderpoint_uom):
@api.constrains("uom_id")
def _check_buffer_procure_uom(self):
for rec in self:
buffer = self.env["stock.buffer"].search(
[
("procure_uom_id.category_id", "!=", rec.uom_id.category_id.id),
("product_id", "in", rec.product_variant_ids.ids),
],
limit=1,
)
if buffer:
raise ValidationError(
_(
"At least one stock buffer for this product has a "
"different Procurement unit of measure category."
)
)
def action_view_stock_buffers(self):
return self.product_variant_ids.action_view_stock_buffers()

View file

@ -0,0 +1,107 @@
# Copyright 2017-20 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import fields, models
from .stock_buffer import _PRIORITY_LEVEL
class PurchaseOrder(models.Model):
_inherit = "purchase.order"
ddmrp_comment = fields.Text(string="Follow-up Notes")
class PurchaseOrderLine(models.Model):
_inherit = "purchase.order.line"
buffer_ids = fields.Many2many(
comodel_name="stock.buffer",
string="Stock Buffers",
copy=False,
readonly=True,
)
execution_priority_level = fields.Selection(
string="Buffer On-Hand Status Level",
selection=_PRIORITY_LEVEL,
readonly=True,
)
on_hand_percent = fields.Float(
string="On Hand/TOR (%)",
readonly=True,
)
ddmrp_comment = fields.Text(related="order_id.ddmrp_comment", readonly=False)
def create(self, vals):
record = super().create(vals)
if record.product_id:
record._find_buffer_link()
record._calc_execution_priority()
return record
def write(self, vals):
res = super().write(vals)
for rec in self:
if rec.product_id:
rec._find_buffer_link()
return res
def _product_id_change(self):
res = super()._product_id_change()
if self.product_id:
self._find_buffer_link()
return res
def _calc_execution_priority(self):
# TODO: handle serveral buffers? worst scenario, average?
to_compute = self.filtered(
lambda r: r.buffer_ids and r.state not in ["done", "cancel"]
)
for rec in to_compute:
rec.execution_priority_level = rec.buffer_ids[0].execution_priority_level
rec.on_hand_percent = rec.buffer_ids[0].on_hand_percent
(self - to_compute).write(
{"execution_priority_level": None, "on_hand_percent": None}
)
def _get_domain_buffer_link(self):
self.ensure_one()
if not self.product_id:
# Return impossible domain -> no buffer.
return [(0, "=", 1)]
return [
("product_id", "=", self.product_id.id),
("company_id", "=", self.order_id.company_id.id),
("buffer_profile_id.item_type", "=", "purchased"),
("warehouse_id", "=", self.order_id.picking_type_id.warehouse_id.id),
]
def _find_buffer_link(self):
buffer_model = self.env["stock.buffer"]
move_model = self.env["stock.move"]
for rec in self.filtered(lambda r: not r.buffer_ids):
mto_move = move_model.search(
[("created_purchase_line_id", "=", rec.id)], limit=1
)
if mto_move:
# MTO lines are not accounted in MTS stock buffers.
continue
domain = rec._get_domain_buffer_link()
buffer = buffer_model.search(domain, limit=1)
if buffer:
rec.buffer_ids = buffer
rec._calc_execution_priority()
def _prepare_purchase_order_line_from_procurement(
self, product_id, product_qty, product_uom, company_id, values, po
):
vals = super()._prepare_purchase_order_line_from_procurement(
product_id, product_qty, product_uom, company_id, values, po
)
# If the procurement was run directly by a reordering rule.
if "buffer_id" in values:
vals["buffer_ids"] = [(4, values["buffer_id"].id)]
# If the procurement was run by a stock move.
elif "buffer_ids" in values:
vals["buffer_ids"] = [(4, o.id) for o in values["buffer_ids"]]
return vals

View file

@ -0,0 +1,21 @@
# Copyright 2020 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import fields, models
class ResCompany(models.Model):
_inherit = "res.company"
ddmrp_auto_update_nfp = fields.Boolean(
string="Update NFP on Stock Buffers on relevant events.",
help="Transfer status changes can trigger the update of relevant "
"buffer's NFP.",
)
ddmrp_adu_calc_include_scrap = fields.Boolean(
string="Include scrap locations in ADU calculation",
)
ddmrp_qty_multiple_tolerance = fields.Float(
string="Qty Multiple Tolerance",
)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,77 @@
# Copyright 2016-20 ForgeFlow S.L. (http://www.forgeflow.com)
# Copyright 2016 Aleph Objects, Inc. (https://www.alephobjects.com/)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import api, fields, models
_REPLENISH_METHODS = [
("replenish", "Replenished"),
("replenish_override", "Replenished Override"),
("min_max", "Min-max"),
]
_ITEM_TYPES = [
("manufactured", "Manufactured"),
("purchased", "Purchased"),
("distributed", "Distributed"),
]
class StockBufferProfile(models.Model):
_name = "stock.buffer.profile"
_description = "Stock Buffer Profile"
@api.depends(
"item_type",
"lead_time_id",
"lead_time_id.name",
"lead_time_id.factor",
"variability_id",
"variability_id.name",
"variability_id.factor",
"distributed_reschedule_max_proc_time",
)
def _compute_name(self):
"""Get the right summary for this job."""
for rec in self:
name = "{} {}, {}({}), {}({})".format(
rec.replenish_method,
rec.item_type,
rec.lead_time_id.name,
rec.lead_time_id.factor,
rec.variability_id.name,
rec.variability_id.factor,
)
if rec.distributed_reschedule_max_proc_time > 0.0:
name += ", {}min".format(rec.distributed_reschedule_max_proc_time)
rec.name = name
name = fields.Char(compute="_compute_name", store=True)
replenish_method = fields.Selection(
string="Replenishment method", selection=_REPLENISH_METHODS, required=True
)
item_type = fields.Selection(selection=_ITEM_TYPES, required=True)
lead_time_id = fields.Many2one(
comodel_name="stock.buffer.profile.lead.time", string="Lead Time Factor"
)
variability_id = fields.Many2one(
comodel_name="stock.buffer.profile.variability", string="Variability Factor"
)
company_id = fields.Many2one(
"res.company",
"Company",
)
replenish_distributed_limit_to_free_qty = fields.Boolean(
string="Limit replenishment to free quantity",
default=False,
help="When activated, the recommended quantity will be maxed at "
"the quantity available in the replenishment source location.",
)
distributed_reschedule_max_proc_time = fields.Float(
string="Re-Schedule Procurement Max Proc. Time (minutes)",
default=0.0,
help="When you request procurement from a buffer, their scheduled"
" date is rescheduled to now + this procurement time (in minutes)."
" Their scheduled date represents the latest the transfers should"
" be done, and therefore, past this timestamp, considered late.",
)

View file

@ -0,0 +1,17 @@
# Copyright 2016-20 ForgeFlow S.L. (http://www.forgeflow.com)
# Copyright 2016 Aleph Objects, Inc. (https://www.alephobjects.com/)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import fields, models
class StockBufferProfileLeadTime(models.Model):
_name = "stock.buffer.profile.lead.time"
_description = "Stock Buffer Profile Lead Time Factor"
name = fields.Char(required=True)
factor = fields.Float(string="Lead Time Factor", required=True)
company_id = fields.Many2one(
"res.company",
"Company",
)

View file

@ -0,0 +1,17 @@
# Copyright 2016-20 ForgeFlow S.L. (http://www.forgeflow.com)
# Copyright 2016 Aleph Objects, Inc. (https://www.alephobjects.com/)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import fields, models
class StockBufferProfileVariability(models.Model):
_name = "stock.buffer.profile.variability"
_description = "Stock Buffer Profile Variability Factor"
name = fields.Char(required=True)
factor = fields.Float(string="Variability Factor", required=True)
company_id = fields.Many2one(
"res.company",
"Company",
)

View file

@ -0,0 +1,152 @@
# Copyright 2019-20 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import _, api, fields, models
class StockMove(models.Model):
_inherit = "stock.move"
buffer_ids = fields.Many2many(
comodel_name="stock.buffer",
string="Linked Stock Buffers",
)
# Add an index as '_find_buffer_link' method is using it as search criteria
created_purchase_line_id = fields.Many2one(index=True)
def _prepare_procurement_values(self):
res = super(StockMove, self)._prepare_procurement_values()
if self.buffer_ids:
res["buffer_ids"] = self.buffer_ids
return res
def _merge_moves_fields(self):
res = super(StockMove, self)._merge_moves_fields()
res["buffer_ids"] = [(4, m.id) for m in self.mapped("buffer_ids")]
return res
def write(self, vals):
res = super(StockMove, self).write(vals)
if self and self.env.company.ddmrp_auto_update_nfp:
# Stock moves changes can be triggered by users without
# access to write stock buffers, thus we do it with sudo.
if "state" in vals:
self.sudo()._update_ddmrp_nfp()
elif "location_id" in vals or "location_dest_id" in vals:
self.sudo().filtered(
lambda m: m.state
in ("confirmed", "partially_available", "assigned")
)._update_ddmrp_nfp()
return res
@api.model_create_multi
def create(self, vals_list):
moves = super(StockMove, self).create(vals_list)
# TODO should we use @api.model_create_single instead?
moves_to_update_ids = []
for vals, move in zip(vals_list, moves):
if (
"state" in vals
and move.state not in ("draft", "cancel")
and self.env.company.ddmrp_auto_update_nfp
):
moves_to_update_ids.append(move.id)
# Stock moves state changes can be triggered by users without
# access to write stock buffers, thus we do it with sudo.
if moves_to_update_ids:
self.browse(moves_to_update_ids).sudo()._update_ddmrp_nfp()
return moves
def _find_buffers_to_update_nfp(self):
# Find buffers that can be affected. `out_buffers` will see the move as
# outgoing and `in_buffers` as incoming.
out_buffers = in_buffers = self.env["stock.buffer"]
for move in self:
out_buffers |= move.mapped("product_id.buffer_ids").filtered(
lambda buffer: (
move.location_id.is_sublocation_of(buffer.location_id)
and not move.location_dest_id.is_sublocation_of(buffer.location_id)
)
)
in_buffers |= move.mapped("product_id.buffer_ids").filtered(
lambda buffer: (
not move.location_id.is_sublocation_of(buffer.location_id)
and move.location_dest_id.is_sublocation_of(buffer.location_id)
)
)
return out_buffers, in_buffers
def _update_ddmrp_nfp(self):
if self.env.context.get("no_ddmrp_auto_update_nfp"):
return True
out_buffers, in_buffers = self._find_buffers_to_update_nfp()
for buffer in out_buffers.with_context(no_ddmrp_history=True):
buffer.cron_actions(only_nfp="out")
for buffer in in_buffers.with_context(no_ddmrp_history=True):
buffer.cron_actions(only_nfp="in")
def _get_all_linked_moves(self):
"""Retrieve all linked moves both origin and destination recursively."""
def get_moves(move_set, attr):
new_moves = move_set.mapped(attr)
while new_moves:
move_set |= new_moves
new_moves = new_moves.mapped(attr)
return move_set
all_moves = (
self | get_moves(self, "move_orig_ids") | get_moves(self, "move_dest_ids")
)
return all_moves
def _get_source_field_candidates(self):
"""Extend for more source field candidates."""
return [
"sale_line_id.order_id",
"purchase_line_id.order_id",
"production_id",
"raw_material_production_id",
"unbuild_id",
"repair_id",
"rma_line_id",
"picking_id",
]
def _has_nested_field(self, field):
"""Check if an object has a nested chain of fields."""
current_object = self
try:
for field in field.split("."):
current_object = getattr(current_object, field)
return True
except AttributeError:
return False
def _get_source_record(self):
"""Find the first source record in the field candidates linked with the moves,
prioritizing the order of field candidates."""
moves = self._get_all_linked_moves()
field_candidates = self._get_source_field_candidates()
# Iterate over the prioritized list of candidate fields
for field in field_candidates:
if self._has_nested_field(field):
for move in moves:
record = move.mapped(field)
if record:
return record
return False
def action_open_stock_move_source(self):
"""Open the source record of the stock move, if it exists."""
self.ensure_one()
record = self._get_source_record()
if record:
return {
"name": getattr(record, "name", _("Stock Move Source")),
"view_mode": "form",
"res_model": record._name,
"type": "ir.actions.act_window",
"res_id": record.id,
}
return False

View file

@ -0,0 +1,12 @@
# Copyright 2021 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
from odoo import fields, models
class StockMoveLine(models.Model):
_inherit = "stock.move.line"
# Override to make '_calc_product_available_qty' method of
# 'stock.buffer' more efficient.
state = fields.Selection(index=True)

View file

@ -0,0 +1,20 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
from odoo import models
class StockPicking(models.Model):
_inherit = "stock.picking"
def action_stock_buffer_open(self):
"""Open a stock.buffer list related to products of the stock.picking."""
self.ensure_one()
domain = [
("product_id", "in", self.mapped("move_ids.product_id.id")),
("company_id", "=", self.company_id.id),
]
action = self.env["ir.actions.actions"]._for_xml_id("ddmrp.action_stock_buffer")
action["domain"] = domain
action["context"] = {"search_default_procure_recommended": 1}
return action

View file

@ -0,0 +1,92 @@
# Copyright 2018 Camptocamp (https://www.camptocamp.com)
# Copyright 2019-20 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import models
class StockRule(models.Model):
_inherit = "stock.rule"
def _prepare_mo_vals(
self,
product_id,
product_qty,
product_uom,
location_id,
name,
origin,
company_id,
values,
bom,
):
result = super(StockRule, self)._prepare_mo_vals(
product_id,
product_qty,
product_uom,
location_id,
name,
origin,
company_id,
values,
bom,
)
# TODO: stock_orderpoint_mrp_link: tests!
if "buffer_id" in values:
result["buffer_id"] = values["buffer_id"].id
elif "buffer_ids" in values:
# We take the always first value as in case of chain procurements,
# the procurements are resolved first and then the moves are
# merged. Thus here we are going to have only one buffer in
# in buffer_ids.
result["buffer_id"] = values["buffer_ids"][0].id
return result
def _run_manufacture(self, procurements):
super()._run_manufacture(procurements)
for procurement, _rule in procurements:
buffer = procurement.values.get("buffer_id")
if buffer:
buffer.sudo().cron_actions()
return True
# TODO: stock_orderpoint_move_link: tests!
def _get_stock_move_values(
self,
product_id,
product_qty,
product_uom,
location_id,
name,
origin,
company_id,
values,
):
vals = super()._get_stock_move_values(
product_id,
product_qty,
product_uom,
location_id,
name,
origin,
company_id,
values,
)
if "buffer_id" in values:
vals["buffer_ids"] = [(4, values["buffer_id"].id)]
elif "buffer_ids" in values:
vals["buffer_ids"] = [(4, o.id) for o in values["buffer_ids"]]
return vals
def _update_purchase_order_line(
self, product_id, product_qty, product_uom, company_id, values, line
):
vals = super()._update_purchase_order_line(
product_id, product_qty, product_uom, company_id, values, line
)
if "buffer_id" in values:
vals["buffer_ids"] = [(4, values["buffer_id"].id)]
# If the procurement was run by a stock move.
elif "buffer_ids" in values:
vals["buffer_ids"] = [(4, o.id) for o in values["buffer_ids"]]
return vals

View file

@ -0,0 +1,15 @@
# Copyright 2018-20 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import fields, models
class StockWarehouse(models.Model):
_inherit = "stock.warehouse"
nfp_incoming_safety_factor = fields.Float(
"Net Flow Position Incoming Safety Factor",
help="Factor used to compute the number of days to look into the "
"future for incoming shipments for the purposes of the Net "
"Flow position calculation.",
)

View file

@ -0,0 +1,30 @@
# Copyright 2024 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import api, models
class StockWarehouseOrderpoint(models.Model):
_inherit = "stock.warehouse.orderpoint"
def _get_orderpoint_action(self):
return super(
StockWarehouseOrderpoint, self.with_context(_from_get_op_action=True)
)._get_orderpoint_action()
@api.model_create_multi
def create(self, vals_list):
if self.env.context.get("_from_get_op_action"):
new_vals_list = []
for vals in vals_list:
buffer = self.env["stock.buffer"].search(
[
("product_id", "=", vals.get("product_id")),
("location_id", "=", vals.get("location_id")),
],
limit=1,
)
if not buffer:
new_vals_list.append(vals)
vals_list = new_vals_list
return super().create(vals_list)

View file

@ -0,0 +1,29 @@
Scheduled actions
~~~~~~~~~~~~~~~~~
* Go to *Settings > Technical*.
* 'DDMRP Buffer ADU calculation'. Computes the Average Daily Usage for all
Buffers.
* 'Reordering Rule DDMRP calculation'. Computes the Qualified Demand, Net
Flow Position, Planning and Execution priorities for all Buffers.
Decoupled Lead Time computation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The DLT is automatically computed by the system.
For manufactured products' buffers just remember to provide and
set properly the following information:
* The *Manufacturing Lead Time* for the manufactured product. It can be found
at the product form view under the tab *Sales*.
* The *Delivery Lead Time* for the preferred vendor of a product. This is
important for the products which are purchased and are components in any
Bill of Materials.
For purchased/distributed products' buffers the logic is simpler.
* In the first place the system will look if there are Vendors for the product,
if so it will use the *Delivery Lead Time* of the preferred one.
* In case of absence of vendors, the *Lead Time* at the bottom of the Buffer
form view will be used.

View file

@ -0,0 +1,5 @@
* Jordi Ballester Alomar <jordi.ballester@forgeflow.com>
* Lois Rilo Antelo <lois.rilo@forgeflow.com>
* Guewen Baconnier <guewen.baconnier@camptocamp.com>
* Adria Gil Sorribes <adria.gil@forgeflow.com>
* Christopher Ormaza <chris.ormaza@forgeflow.com>

View file

@ -0,0 +1,3 @@
The initial development of this module has been financially supported by:
* Aleph Objects, Inc.

View file

@ -0,0 +1,56 @@
Demand Driven Material Requirements Planning is a formal multi-echelon
planning and execution method developed by Ms. Carol Ptak and Mr. Chad Smith.
DDMRP combines blended aspects of Material Requirements Planning (MRP),
Distribution Requirements Planning (DRP) with the pull and visibility
emphases found in Lean and the Theory of Constraints and the variability
reduction emphasis of Six Sigma.
This method has five sequential components:
#. *Strategic Inventory Positioning*. Answers the question "Given our system
and environment, where should we place inventory to have the best
protection?" and determines where should decoupling points of inventory be
placed.
#. *Buffer Profiles and Levels*. Determine the amount of protection at those
decoupling points.
#. *Dynamic Adjustments*. Allow the company to adapt buffers to group and
individual part trait changes over time through the use of several types
of adjustments.
#. *Demand Driven Planning*. Allow to launch purchase orders (POs),
manufacturing orders (MOs) and Transfer Orders (TOs) based on the priority
dictated by the buffers.
#. *Visible and Collaborative Execution*. These POs, MOs and TOs have to be
effectively managed to synchronize with the changes that often occur within
the "execution horizon."
These five components work together to greatly dampen, if not eliminate,
the nervousness of traditional MRP systems and the bullwhip effect in
complex and challenging environments.
This approach provides real information about those parts that are
truly at risk of negatively impacting the planned availability of inventory.
DDMRP sorts the significant few items that require attention from
the many parts that are being managed. Under the DDMRP approach,
fewer planners can make better decisions more quickly. That means companies
will be better able to leverage their working and human capital.
Demand Driven Material Requirements Planning is quickly being adopted
by a wide variety of leading companies across the world.
Some of the benefits reported by the DDMRP method include:
* High fill rate performance
* Lead time reductions
* Inventory reductions, while improving customer service
* Eliminate costs related to expedite
* Planners see priorities instead of constantly fighting the conflicting
messages of MRP
It is highly recommended to read the book 'Demand Driven Material
Requirements Planning (DDMRP)' by Carol Ptak and Chad Smith.

View file

@ -0,0 +1,40 @@
13.0.1.1.0 (2020-07-01)
~~~~~~~~~~~~~~~~~~~~~~~
**Features**
- - New setting *Update NFP on Stock Buffers on relevant events*.
- New dedicated settings block. (`#50 <https://github.com/OCA/ddmrp/issues/50>`_)
13.0.1.0.0 (2020-06-11)
~~~~~~~~~~~~~~~~~~~~~~~
* [MIG/REF] Migration of module to v13 and refactor (added new dedicated model
for stock buffer).
11.0.1.3.0 (2019-02-21)
~~~~~~~~~~~~~~~~~~~~~~~
* [ADD] New chart that depict information about the supply and demand (
displaying also de order spike threshold and horizon) for a buffer.
(`#40 <https://github.com/OCA/ddmrp/pull/40>`_)
11.0.1.2.0 (2019-01-29)
~~~~~~~~~~~~~~~~~~~~~~~
* [IMP] Performance improvement of execution priority calculation and ADU.
(`#36 <https://github.com/OCA/ddmrp/pull/36>`_)
* [IMP] Use the minimum quantity to adjust the procure recommendation.
(`#37 <https://github.com/OCA/ddmrp/pull/37>`_)
11.0.1.1.0 (2018-08-31)
~~~~~~~~~~~~~~~~~~~~~~~
* [IMP] Implemented Blended ADU calculation method.
(`#23 <https://github.com/OCA/ddmrp/pull/23>`_)
11.0.1.0.0 (2018-07-16)
~~~~~~~~~~~~~~~~~~~~~~~
* Start of the history

View file

@ -0,0 +1,7 @@
We strongly recommend to modify the configuration of the reservation method
to manual in the outgoing operation types (so deliveries
related to Sales Orders aren't automatically reserved) and to avoid to
reserve stock for specific moves, buffers are in fact a reservation of stock.
However, while **reservation is discouraged**, it is still available to be
used, in case of reserved stock be aware that the buffer will be blind to this
transfers and stock and you are bypassing the DDMRP reordering flow.

View file

@ -0,0 +1,3 @@
The DDMRP `roadmap <https://github.com/OCA/ddmrp/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement>`_
and `known issues <https://github.com/OCA/ddmrp/issues?q=is%3Aopen+is%3Aissue+label%3Abug>`_ can
be found on GitHub.

View file

@ -0,0 +1,82 @@
To easily identify were are you maintaining buffers in your Bill of
Materials, you will need to first provide location information on the Bills
of Materials.
* Go to *Manufacturing / Products / Bill of Materials* and update the
'Location' in all the Bill of Materials and associated lines,
indicating where will the parts be placed/used during the manufacturing
process.
* Print the report 'BOM Structure' to display where in your BOM are you
maintaining buffers, and to identify the Lead Time (LT) of each product, and
Decouple Lead Time (DLT).
Buffers
~~~~~~~
To list the list of inventory buffers, go to one of the following:
* *Inventory / Master Data / Stock Buffer Planning*
* *Inventory / Master Data / Reordering Rules*
Buffer Profiles
~~~~~~~~~~~~~~~
Buffer profiles make maintenance of buffers easier by grouping them in
profiles. Changes applied to the profiles will be applicable in the
associated buffer calculations.
* Go to *Inventory / Configuration / Buffer Profiles*.
The Buffer Profile Lead Time Factor influences the size of the Buffer Green
zone. Items with longer lead times will usually have smaller green zones, which
will translate in more frequent supply order generation.
* Go to *Inventory / Configuration / Buffer Profile Lead Time Factor* to
chan
The Buffer Profile Variability Factor influences the size of the Buffer Red
Safety zone. Items with longer lead times will usually have smaller green
zones, which will translate in more frequent supply order generation.
* Go to *Inventory / Configuration / Buffer Profile Lead Time Factor*.
Usual factors should range from 0.2 (long lead time) to 0.8 (short lead time).
Product attributes
~~~~~~~~~~~~~~~~~~
* For manufactured products, go to *Manufacturing / Products* and
update the 'Manufacturing Lead Time' field, available in the tab *Inventory*.
* For purchased products, go to go to *Purchasing / Products* and update the
*Delivery Lead Time* for each vendor, available in tab *Purchase* and section
*Vendors*.
ADU Calculation Methods
~~~~~~~~~~~~~~~~~~~~~~~
The Average Daily Usage (ADU) defines the frequency of demand of a product in a
certain location. It can be computed in different ways, which you can configure
with ADU calculation methods as follows:
#. Go to *Inventory / Configuration / DDMRP / ADU calculation methods*.
#. Indicate a name, a calculation method (fixed, past-looking,
future-looking or blended).
#. Fill the corresponding period (past, future or both for blended method) to
specify the length of period consideration (in days).
#. Indicate the source of information: stock moves or demand estimates.
#. If you use the blended method fill also the *Past Factor* and
*Future Factor*.
If you do not have prior history of stock moves in your system, it is advised
to use fixed method or start to work on future estimates. If you have
past-history of stock moves, best use past-looking method or blended method.
The ADU is computed every day by default in a background job independently
from the other buffer fields. This computation can be done with less frequency
but it is not recommended to run it less than weekly or more than daily.
Circumstantially, If you need to force the calculation of the ADU go to
*Inventory / Configuration / DDMRP / Run DDMRP* and click on
*Run ADU calculation*.

View file

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

View file

@ -0,0 +1,100 @@
# Copyright 2017-23 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import api, models
class BomStructureReport(models.AbstractModel):
_inherit = "report.mrp.report_bom_structure"
def _get_pdf_doc(self, bom_id, data, quantity, product_variant_id=None):
doc = super()._get_pdf_doc(bom_id, data, quantity, product_variant_id)
doc["show_buffered"] = (
True if data and data.get("show_buffered") == "true" else False
)
return doc
@api.model
def _get_bom_data(
self,
bom,
warehouse,
product=False,
line_qty=False,
bom_line=False,
level=0,
parent_bom=False,
index=0,
product_info=False,
ignore_stock=False,
):
res = super(BomStructureReport, self)._get_bom_data(
bom,
warehouse,
product=product,
line_qty=line_qty,
bom_line=bom_line,
level=level,
parent_bom=parent_bom,
index=index,
product_info=product_info,
ignore_stock=ignore_stock,
)
res["is_buffered"] = bom.is_buffered
res["dlt"] = bom.dlt
return res
@api.model
def _get_component_data(
self,
parent_bom,
warehouse,
bom_line,
line_quantity,
level,
index,
product_info,
ignore_stock=False,
):
res = super(BomStructureReport, self)._get_component_data(
parent_bom,
warehouse,
bom_line,
line_quantity,
level,
index,
product_info,
ignore_stock=ignore_stock,
)
if bom_line.product_id.bom_ids:
lead_time = bom_line.product_id.produce_delay
else:
lead_time = (
bom_line.product_id.seller_ids
and bom_line.product_id.seller_ids[0].delay
or 0.0
)
res["is_buffered"] = bom_line.is_buffered
res["lead_time"] = lead_time or False
res["dlt"] = bom_line.dlt
return res
def _get_bom_array_lines(
self, data, level, unfolded_ids, unfolded, parent_unfolded
):
lines = super()._get_bom_array_lines(
data, level, unfolded_ids, unfolded, parent_unfolded
)
for component in data.get("components", []):
bom_line = next(
filter(
lambda line: line.get("bom_id", False) == component["bom_id"]
and line.get("name", False) == component["name"]
and line.get("level", False) == component["level"],
lines,
)
)
if bom_line:
bom_line["is_buffered"] = component["is_buffered"]
bom_line["dlt"] = component["dlt"]
return lines

View file

@ -0,0 +1,43 @@
<odoo>
<template id="report_mrp_bom_inherit_ddmrp" inherit_id="mrp.report_mrp_bom">
<xpath expr="//th[@name='th_mrp_bom_h']" position="before">
<th t-if="data['show_buffered']">Buffered</th>
</xpath>
<xpath expr="//td[@name='td_mrp_bom']" position="before">
<td t-if="data['show_buffered']">
<span t-if="data['is_buffered']">
<img
src='/ddmrp/static/img/is_buffered.png'
style="width:30px;height:30px;padding:5px"
/>
</span>
</td>
</xpath>
<xpath expr="//td[@name='td_mrp_bom_f']" position="before">
<td t-if="data['show_buffered']" />
</xpath>
<xpath expr="//td[@name='td_mrp_bom_byproducts_f']" position="before">
<td t-if="data['show_buffered']" />
</xpath>
</template>
<template
id="report_mrp_bom_pdf_line_inherit_ddmrp"
inherit_id="mrp.report_mrp_bom_pdf_line"
>
<xpath expr="//td[@name='td_mrp_code']" position="before">
<td t-if="data['show_buffered']">
<span t-if="l.get('is_buffered')">
<img
src='/ddmrp/static/img/is_buffered.png'
style="width:30px;height:30px;padding:5px"
/>
</span>
</td>
</xpath>
</template>
</odoo>

View file

@ -0,0 +1,36 @@
<?xml version="1.0" ?>
<odoo noupdate="1">
<record id="module_category_ddmrp" model="ir.module.category">
<field name="name">DDMRP</field>
<field name="sequence">40</field>
</record>
<record id="group_change_buffer_procure_qty" model="res.groups">
<field
name="name"
>Change quantity in manual procurements from Stock Buffers</field>
<field name="implied_ids" eval="[(4, ref('base.group_user'))]" />
<field name="category_id" ref="ddmrp.module_category_ddmrp" />
</record>
<record id="group_stock_buffer_maintainer" model="res.groups">
<field name="name">Stock Buffer Maintainer</field>
<field name="category_id" ref="ddmrp.module_category_ddmrp" />
<field name="implied_ids" eval="[(4, ref('stock.group_stock_user'))]" />
</record>
<record id="group_ddmrp_manager" model="res.groups">
<field name="name">DDMRP Manager</field>
<field name="category_id" ref="ddmrp.module_category_ddmrp" />
<field
name="implied_ids"
eval="[(4, ref('ddmrp.group_stock_buffer_maintainer'))]"
/>
<field
name="users"
eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"
/>
</record>
</odoo>

View file

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="0">
<record model="ir.rule" id="stock_buffer_profile_variability_comp_rule">
<field name="name">Buffer Profile Variability multi-company</field>
<field name="model_id" ref="model_stock_buffer_profile_variability" />
<field name="global" eval="True" />
<field
name="domain_force"
>['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
<record model="ir.rule" id="stock_buffer_profile_variability_lead_time_comp_rule">
<field name="name">Buffer Profile Lead Time multi-company</field>
<field name="model_id" ref="model_stock_buffer_profile_lead_time" />
<field name="global" eval="True" />
<field
name="domain_force"
>['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
<record model="ir.rule" id="stock_buffer_profile_comp_rule">
<field name="name">Buffer Profile multi-company</field>
<field name="model_id" ref="model_stock_buffer_profile" />
<field name="global" eval="True" />
<field
name="domain_force"
>['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
<record model="ir.rule" id="product_adu_calculation_method_comp_rule">
<field name="name">Product ADU calculation method multi-company</field>
<field name="model_id" ref="model_product_adu_calculation_method" />
<field name="global" eval="True" />
<field
name="domain_force"
>['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
<record model="ir.rule" id="stock_buffer_comp_rule">
<field name="name">Stock Buffer multi-company</field>
<field name="model_id" ref="model_stock_buffer" />
<field name="global" eval="True" />
<field
name="domain_force"
>['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
</odoo>

View file

@ -0,0 +1,21 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_product_adu_calculation_method,product.adu.calculation.method,model_product_adu_calculation_method,stock.group_stock_user,1,0,0,0
access_product_adu_calculation_method_system,product.adu.calculation.method system,model_product_adu_calculation_method,ddmrp.group_ddmrp_manager,1,1,1,1
access_stock_buffer_profile,stock.buffer.profile,model_stock_buffer_profile,stock.group_stock_user,1,0,0,0
access_stock_buffer_profile_system,stock.buffer.profile system,model_stock_buffer_profile,ddmrp.group_ddmrp_manager,1,1,1,1
access_stock_buffer_profile_lead_time,stock.buffer.profile.lead.time stock user,model_stock_buffer_profile_lead_time,stock.group_stock_user,1,0,0,0
access_stock_buffer_profile_lead_time_manager,stock.buffer.profile.lead.time stock user,model_stock_buffer_profile_lead_time,ddmrp.group_ddmrp_manager,1,1,1,1
access_stock_buffer_profile_variability,stock.buffer.profile.variability stock user,model_stock_buffer_profile_variability,stock.group_stock_user,1,0,0,0
access_stock_buffer_profile_variability_manager,stock.buffer.profile.variability stock manager,model_stock_buffer_profile_variability,ddmrp.group_ddmrp_manager,1,1,1,1
access_stock_buffer,stock.buffer.user,model_stock_buffer,stock.group_stock_user,1,0,0,0
access_stock_buffer_manager,stock.buffer.manager,model_stock_buffer,ddmrp.group_stock_buffer_maintainer,1,1,1,1
access_stock_buffer_procurements,stock.buffer.procurements.user,model_make_procurement_buffer,stock.group_stock_user,1,0,0,0
access_stock_buffer_procurements_manager,stock.buffer.procurements.manager,model_make_procurement_buffer,stock.group_stock_manager,1,1,1,1
access_stock_buffer_procurements_item,stock.buffer.procurements.item.user,model_make_procurement_buffer_item,stock.group_stock_user,1,0,0,0
access_stock_buffer_procurements_item_manager,stock.buffer.procurements.item.manager,model_make_procurement_buffer_item,stock.group_stock_manager,1,1,1,1
access_ddmrp_duplicate_buffer,ddmrp.duplicate.buffer,model_ddmrp_duplicate_buffer,stock.group_stock_user,1,0,0,0
access_ddmrp_duplicate_buffer_manager,ddmrp.duplicate.buffer,model_ddmrp_duplicate_buffer,ddmrp.group_stock_buffer_maintainer,1,1,1,1
access_mrp_bom_change_location_manager,mrp.bom.change.location,model_mrp_bom_change_location,stock.group_stock_user,1,1,1,1
ddmrp_access_run,ddmrp.run,model_ddmrp_run,stock.group_stock_user,1,0,0,0
ddmrp_access_run_manager,ddmrp.run.manager,model_ddmrp_run,stock.group_stock_manager,1,1,1,1
access_ddmrp_mrp_move,mrp.move ddmrp,mrp_multi_level.model_mrp_move,stock.group_stock_user,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_product_adu_calculation_method product.adu.calculation.method model_product_adu_calculation_method stock.group_stock_user 1 0 0 0
3 access_product_adu_calculation_method_system product.adu.calculation.method system model_product_adu_calculation_method ddmrp.group_ddmrp_manager 1 1 1 1
4 access_stock_buffer_profile stock.buffer.profile model_stock_buffer_profile stock.group_stock_user 1 0 0 0
5 access_stock_buffer_profile_system stock.buffer.profile system model_stock_buffer_profile ddmrp.group_ddmrp_manager 1 1 1 1
6 access_stock_buffer_profile_lead_time stock.buffer.profile.lead.time stock user model_stock_buffer_profile_lead_time stock.group_stock_user 1 0 0 0
7 access_stock_buffer_profile_lead_time_manager stock.buffer.profile.lead.time stock user model_stock_buffer_profile_lead_time ddmrp.group_ddmrp_manager 1 1 1 1
8 access_stock_buffer_profile_variability stock.buffer.profile.variability stock user model_stock_buffer_profile_variability stock.group_stock_user 1 0 0 0
9 access_stock_buffer_profile_variability_manager stock.buffer.profile.variability stock manager model_stock_buffer_profile_variability ddmrp.group_ddmrp_manager 1 1 1 1
10 access_stock_buffer stock.buffer.user model_stock_buffer stock.group_stock_user 1 0 0 0
11 access_stock_buffer_manager stock.buffer.manager model_stock_buffer ddmrp.group_stock_buffer_maintainer 1 1 1 1
12 access_stock_buffer_procurements stock.buffer.procurements.user model_make_procurement_buffer stock.group_stock_user 1 0 0 0
13 access_stock_buffer_procurements_manager stock.buffer.procurements.manager model_make_procurement_buffer stock.group_stock_manager 1 1 1 1
14 access_stock_buffer_procurements_item stock.buffer.procurements.item.user model_make_procurement_buffer_item stock.group_stock_user 1 0 0 0
15 access_stock_buffer_procurements_item_manager stock.buffer.procurements.item.manager model_make_procurement_buffer_item stock.group_stock_manager 1 1 1 1
16 access_ddmrp_duplicate_buffer ddmrp.duplicate.buffer model_ddmrp_duplicate_buffer stock.group_stock_user 1 0 0 0
17 access_ddmrp_duplicate_buffer_manager ddmrp.duplicate.buffer model_ddmrp_duplicate_buffer ddmrp.group_stock_buffer_maintainer 1 1 1 1
18 access_mrp_bom_change_location_manager mrp.bom.change.location model_mrp_bom_change_location stock.group_stock_user 1 1 1 1
19 ddmrp_access_run ddmrp.run model_ddmrp_run stock.group_stock_user 1 0 0 0
20 ddmrp_access_run_manager ddmrp.run.manager model_ddmrp_run stock.group_stock_manager 1 1 1 1
21 access_ddmrp_mrp_move mrp.move ddmrp mrp_multi_level.model_mrp_move stock.group_stock_user 1 0 0 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View file

@ -0,0 +1,690 @@
<!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>README.rst</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">
<a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
<img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
</a>
<div class="section" id="ddmrp">
<h1>DDMRP</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:f43055b32a5e8e104d8a45da35fc5716767773214054bbfe54da0c23dd8be731
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<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/license-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/ddmrp/tree/16.0/ddmrp"><img alt="OCA/ddmrp" src="https://img.shields.io/badge/github-OCA%2Fddmrp-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/ddmrp-16-0/ddmrp-16-0-ddmrp"><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/ddmrp&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>Demand Driven Material Requirements Planning is a formal multi-echelon
planning and execution method developed by Ms. Carol Ptak and Mr. Chad Smith.</p>
<p>DDMRP combines blended aspects of Material Requirements Planning (MRP),
Distribution Requirements Planning (DRP) with the pull and visibility
emphases found in Lean and the Theory of Constraints and the variability
reduction emphasis of Six Sigma.</p>
<p>This method has five sequential components:</p>
<ol class="arabic simple">
<li><em>Strategic Inventory Positioning</em>. Answers the question “Given our system
and environment, where should we place inventory to have the best
protection?” and determines where should decoupling points of inventory be
placed.</li>
<li><em>Buffer Profiles and Levels</em>. Determine the amount of protection at those
decoupling points.</li>
<li><em>Dynamic Adjustments</em>. Allow the company to adapt buffers to group and
individual part trait changes over time through the use of several types
of adjustments.</li>
<li><em>Demand Driven Planning</em>. Allow to launch purchase orders (POs),
manufacturing orders (MOs) and Transfer Orders (TOs) based on the priority
dictated by the buffers.</li>
<li><em>Visible and Collaborative Execution</em>. These POs, MOs and TOs have to be
effectively managed to synchronize with the changes that often occur within
the “execution horizon.”</li>
</ol>
<p>These five components work together to greatly dampen, if not eliminate,
the nervousness of traditional MRP systems and the bullwhip effect in
complex and challenging environments.</p>
<p>This approach provides real information about those parts that are
truly at risk of negatively impacting the planned availability of inventory.</p>
<p>DDMRP sorts the significant few items that require attention from
the many parts that are being managed. Under the DDMRP approach,
fewer planners can make better decisions more quickly. That means companies
will be better able to leverage their working and human capital.</p>
<p>Demand Driven Material Requirements Planning is quickly being adopted
by a wide variety of leading companies across the world.</p>
<p>Some of the benefits reported by the DDMRP method include:</p>
<ul class="simple">
<li>High fill rate performance</li>
<li>Lead time reductions</li>
<li>Inventory reductions, while improving customer service</li>
<li>Eliminate costs related to expedite</li>
<li>Planners see priorities instead of constantly fighting the conflicting
messages of MRP</li>
</ul>
<p>It is highly recommended to read the book Demand Driven Material
Requirements Planning (DDMRP) by Carol Ptak and Chad Smith.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#installation" id="toc-entry-1">Installation</a></li>
<li><a class="reference internal" href="#configuration" id="toc-entry-2">Configuration</a><ul>
<li><a class="reference internal" href="#scheduled-actions" id="toc-entry-3">Scheduled actions</a></li>
<li><a class="reference internal" href="#decoupled-lead-time-computation" id="toc-entry-4">Decoupled Lead Time computation</a></li>
</ul>
</li>
<li><a class="reference internal" href="#usage" id="toc-entry-5">Usage</a><ul>
<li><a class="reference internal" href="#buffers" id="toc-entry-6">Buffers</a></li>
<li><a class="reference internal" href="#buffer-profiles" id="toc-entry-7">Buffer Profiles</a></li>
<li><a class="reference internal" href="#product-attributes" id="toc-entry-8">Product attributes</a></li>
<li><a class="reference internal" href="#adu-calculation-methods" id="toc-entry-9">ADU Calculation Methods</a></li>
</ul>
</li>
<li><a class="reference internal" href="#known-issues-roadmap" id="toc-entry-10">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#changelog" id="toc-entry-11">Changelog</a><ul>
<li><a class="reference internal" href="#section-1" id="toc-entry-12">13.0.1.1.0 (2020-07-01)</a></li>
<li><a class="reference internal" href="#section-2" id="toc-entry-13">13.0.1.0.0 (2020-06-11)</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-14">11.0.1.3.0 (2019-02-21)</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-15">11.0.1.2.0 (2019-01-29)</a></li>
<li><a class="reference internal" href="#section-5" id="toc-entry-16">11.0.1.1.0 (2018-08-31)</a></li>
<li><a class="reference internal" href="#section-6" id="toc-entry-17">11.0.1.0.0 (2018-07-16)</a></li>
</ul>
</li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-18">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-19">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-20">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-21">Contributors</a></li>
<li><a class="reference internal" href="#other-credits" id="toc-entry-22">Other credits</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-23">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="installation">
<h2><a class="toc-backref" href="#toc-entry-1">Installation</a></h2>
<p>We strongly recommend to modify the configuration of the reservation method
to manual in the outgoing operation types (so deliveries
related to Sales Orders arent automatically reserved) and to avoid to
reserve stock for specific moves, buffers are in fact a reservation of stock.
However, while <strong>reservation is discouraged</strong>, it is still available to be
used, in case of reserved stock be aware that the buffer will be blind to this
transfers and stock and you are bypassing the DDMRP reordering flow.</p>
</div>
<div class="section" id="configuration">
<h2><a class="toc-backref" href="#toc-entry-2">Configuration</a></h2>
<div class="section" id="scheduled-actions">
<h3><a class="toc-backref" href="#toc-entry-3">Scheduled actions</a></h3>
<ul class="simple">
<li>Go to <em>Settings &gt; Technical</em>.</li>
<li>DDMRP Buffer ADU calculation. Computes the Average Daily Usage for all
Buffers.</li>
<li>Reordering Rule DDMRP calculation. Computes the Qualified Demand, Net
Flow Position, Planning and Execution priorities for all Buffers.</li>
</ul>
</div>
<div class="section" id="decoupled-lead-time-computation">
<h3><a class="toc-backref" href="#toc-entry-4">Decoupled Lead Time computation</a></h3>
<p>The DLT is automatically computed by the system.</p>
<p>For manufactured products buffers just remember to provide and
set properly the following information:</p>
<ul class="simple">
<li>The <em>Manufacturing Lead Time</em> for the manufactured product. It can be found
at the product form view under the tab <em>Sales</em>.</li>
<li>The <em>Delivery Lead Time</em> for the preferred vendor of a product. This is
important for the products which are purchased and are components in any
Bill of Materials.</li>
</ul>
<p>For purchased/distributed products buffers the logic is simpler.</p>
<ul class="simple">
<li>In the first place the system will look if there are Vendors for the product,
if so it will use the <em>Delivery Lead Time</em> of the preferred one.</li>
<li>In case of absence of vendors, the <em>Lead Time</em> at the bottom of the Buffer
form view will be used.</li>
</ul>
</div>
</div>
<div class="section" id="usage">
<h2><a class="toc-backref" href="#toc-entry-5">Usage</a></h2>
<p>To easily identify were are you maintaining buffers in your Bill of
Materials, you will need to first provide location information on the Bills
of Materials.</p>
<ul class="simple">
<li>Go to <em>Manufacturing / Products / Bill of Materials</em> and update the
Location in all the Bill of Materials and associated lines,
indicating where will the parts be placed/used during the manufacturing
process.</li>
<li>Print the report BOM Structure to display where in your BOM are you
maintaining buffers, and to identify the Lead Time (LT) of each product, and
Decouple Lead Time (DLT).</li>
</ul>
<div class="section" id="buffers">
<h3><a class="toc-backref" href="#toc-entry-6">Buffers</a></h3>
<p>To list the list of inventory buffers, go to one of the following:
* <em>Inventory / Master Data / Stock Buffer Planning</em>
* <em>Inventory / Master Data / Reordering Rules</em></p>
</div>
<div class="section" id="buffer-profiles">
<h3><a class="toc-backref" href="#toc-entry-7">Buffer Profiles</a></h3>
<p>Buffer profiles make maintenance of buffers easier by grouping them in
profiles. Changes applied to the profiles will be applicable in the
associated buffer calculations.</p>
<ul class="simple">
<li>Go to <em>Inventory / Configuration / Buffer Profiles</em>.</li>
</ul>
<p>The Buffer Profile Lead Time Factor influences the size of the Buffer Green
zone. Items with longer lead times will usually have smaller green zones, which
will translate in more frequent supply order generation.</p>
<ul class="simple">
<li>Go to <em>Inventory / Configuration / Buffer Profile Lead Time Factor</em> to
chan</li>
</ul>
<p>The Buffer Profile Variability Factor influences the size of the Buffer Red
Safety zone. Items with longer lead times will usually have smaller green
zones, which will translate in more frequent supply order generation.</p>
<ul class="simple">
<li>Go to <em>Inventory / Configuration / Buffer Profile Lead Time Factor</em>.</li>
</ul>
<p>Usual factors should range from 0.2 (long lead time) to 0.8 (short lead time).</p>
</div>
<div class="section" id="product-attributes">
<h3><a class="toc-backref" href="#toc-entry-8">Product attributes</a></h3>
<ul class="simple">
<li>For manufactured products, go to <em>Manufacturing / Products</em> and
update the Manufacturing Lead Time field, available in the tab <em>Inventory</em>.</li>
<li>For purchased products, go to go to <em>Purchasing / Products</em> and update the
<em>Delivery Lead Time</em> for each vendor, available in tab <em>Purchase</em> and section
<em>Vendors</em>.</li>
</ul>
</div>
<div class="section" id="adu-calculation-methods">
<h3><a class="toc-backref" href="#toc-entry-9">ADU Calculation Methods</a></h3>
<p>The Average Daily Usage (ADU) defines the frequency of demand of a product in a
certain location. It can be computed in different ways, which you can configure
with ADU calculation methods as follows:</p>
<ol class="arabic simple">
<li>Go to <em>Inventory / Configuration / DDMRP / ADU calculation methods</em>.</li>
<li>Indicate a name, a calculation method (fixed, past-looking,
future-looking or blended).</li>
<li>Fill the corresponding period (past, future or both for blended method) to
specify the length of period consideration (in days).</li>
<li>Indicate the source of information: stock moves or demand estimates.</li>
<li>If you use the blended method fill also the <em>Past Factor</em> and
<em>Future Factor</em>.</li>
</ol>
<p>If you do not have prior history of stock moves in your system, it is advised
to use fixed method or start to work on future estimates. If you have
past-history of stock moves, best use past-looking method or blended method.</p>
<p>The ADU is computed every day by default in a background job independently
from the other buffer fields. This computation can be done with less frequency
but it is not recommended to run it less than weekly or more than daily.
Circumstantially, If you need to force the calculation of the ADU go to
<em>Inventory / Configuration / DDMRP / Run DDMRP</em> and click on
<em>Run ADU calculation</em>.</p>
</div>
</div>
<div class="section" id="known-issues-roadmap">
<h2><a class="toc-backref" href="#toc-entry-10">Known issues / Roadmap</a></h2>
<p>The DDMRP <a class="reference external" href="https://github.com/OCA/ddmrp/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement">roadmap</a>
and <a class="reference external" href="https://github.com/OCA/ddmrp/issues?q=is%3Aopen+is%3Aissue+label%3Abug">known issues</a> can
be found on GitHub.</p>
</div>
<div class="section" id="changelog">
<h2><a class="toc-backref" href="#toc-entry-11">Changelog</a></h2>
<div class="section" id="section-1">
<h3><a class="toc-backref" href="#toc-entry-12">13.0.1.1.0 (2020-07-01)</a></h3>
<p><strong>Features</strong></p>
<ul class="simple">
<li><ul class="first">
<li>New setting <em>Update NFP on Stock Buffers on relevant events</em>.</li>
<li>New dedicated settings block. (<a class="reference external" href="https://github.com/OCA/ddmrp/issues/50">#50</a>)</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="section-2">
<h3><a class="toc-backref" href="#toc-entry-13">13.0.1.0.0 (2020-06-11)</a></h3>
<ul class="simple">
<li>[MIG/REF] Migration of module to v13 and refactor (added new dedicated model
for stock buffer).</li>
</ul>
</div>
<div class="section" id="section-3">
<h3><a class="toc-backref" href="#toc-entry-14">11.0.1.3.0 (2019-02-21)</a></h3>
<ul class="simple">
<li>[ADD] New chart that depict information about the supply and demand (
displaying also de order spike threshold and horizon) for a buffer.
(<a class="reference external" href="https://github.com/OCA/ddmrp/pull/40">#40</a>)</li>
</ul>
</div>
<div class="section" id="section-4">
<h3><a class="toc-backref" href="#toc-entry-15">11.0.1.2.0 (2019-01-29)</a></h3>
<ul class="simple">
<li>[IMP] Performance improvement of execution priority calculation and ADU.
(<a class="reference external" href="https://github.com/OCA/ddmrp/pull/36">#36</a>)</li>
<li>[IMP] Use the minimum quantity to adjust the procure recommendation.
(<a class="reference external" href="https://github.com/OCA/ddmrp/pull/37">#37</a>)</li>
</ul>
</div>
<div class="section" id="section-5">
<h3><a class="toc-backref" href="#toc-entry-16">11.0.1.1.0 (2018-08-31)</a></h3>
<ul class="simple">
<li>[IMP] Implemented Blended ADU calculation method.
(<a class="reference external" href="https://github.com/OCA/ddmrp/pull/23">#23</a>)</li>
</ul>
</div>
<div class="section" id="section-6">
<h3><a class="toc-backref" href="#toc-entry-17">11.0.1.0.0 (2018-07-16)</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-18">Bug Tracker</a></h2>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/ddmrp/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/ddmrp/issues/new?body=module:%20ddmrp%0Aversion:%2016.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-19">Credits</a></h2>
<div class="section" id="authors">
<h3><a class="toc-backref" href="#toc-entry-20">Authors</a></h3>
<ul class="simple">
<li>ForgeFlow</li>
</ul>
</div>
<div class="section" id="contributors">
<h3><a class="toc-backref" href="#toc-entry-21">Contributors</a></h3>
<ul class="simple">
<li>Jordi Ballester Alomar &lt;<a class="reference external" href="mailto:jordi.ballester&#64;forgeflow.com">jordi.ballester&#64;forgeflow.com</a>&gt;</li>
<li>Lois Rilo Antelo &lt;<a class="reference external" href="mailto:lois.rilo&#64;forgeflow.com">lois.rilo&#64;forgeflow.com</a>&gt;</li>
<li>Guewen Baconnier &lt;<a class="reference external" href="mailto:guewen.baconnier&#64;camptocamp.com">guewen.baconnier&#64;camptocamp.com</a>&gt;</li>
<li>Adria Gil Sorribes &lt;<a class="reference external" href="mailto:adria.gil&#64;forgeflow.com">adria.gil&#64;forgeflow.com</a>&gt;</li>
<li>Christopher Ormaza &lt;<a class="reference external" href="mailto:chris.ormaza&#64;forgeflow.com">chris.ormaza&#64;forgeflow.com</a>&gt;</li>
</ul>
</div>
<div class="section" id="other-credits">
<h3><a class="toc-backref" href="#toc-entry-22">Other credits</a></h3>
<p>The initial development of this module has been financially supported by:</p>
<ul class="simple">
<li>Aleph Objects, Inc.</li>
</ul>
</div>
<div class="section" id="maintainers">
<h3><a class="toc-backref" href="#toc-entry-23">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> <a class="reference external image-reference" href="https://github.com/ChrisOForgeFlow"><img alt="ChrisOForgeFlow" src="https://github.com/ChrisOForgeFlow.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/ddmrp/tree/16.0/ddmrp">OCA/ddmrp</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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View file

@ -0,0 +1,29 @@
/** @odoo-module **/
import {BomOverviewComponent} from "@mrp/components/bom_overview/mrp_bom_overview";
import {patch} from "@web/core/utils/patch";
patch(BomOverviewComponent.prototype, "ddmrp", {
setup() {
this._super.apply();
this.state.showOptions.is_buffered = true;
this.state.showOptions.dlt = true;
},
async getWarehouses() {
await this._super.apply();
if (this.props.action.context.warehouse_id) {
this.state.currentWarehouse = this.warehouses.filter(
(warehouse) => warehouse.id === this.props.action.context.warehouse_id
)[0];
}
},
getReportName() {
return (
this._super.apply(this, arguments) +
"&show_buffered=" +
this.state.showOptions.is_buffered
);
},
});

View file

@ -0,0 +1,21 @@
/** @odoo-module **/
import {BomOverviewDisplayFilter} from "@mrp/components/bom_overview_display_filter/mrp_bom_overview_display_filter";
import {patch} from "@web/core/utils/patch";
patch(BomOverviewDisplayFilter.prototype, "ddmrp", {
setup() {
this._super.apply();
this.displayOptions.is_buffered = this.env._t("Buffered");
},
});
patch(BomOverviewDisplayFilter, "ddmrp", {
props: {
...BomOverviewDisplayFilter.props,
showOptions: {
...BomOverviewDisplayFilter.showOptions,
is_buffered: Boolean,
},
},
});

View file

@ -0,0 +1,14 @@
/** @odoo-module **/
import {BomOverviewLine} from "@mrp/components/bom_overview_line/mrp_bom_overview_line";
import {patch} from "@web/core/utils/patch";
patch(BomOverviewLine, "ddmrp", {
props: {
...BomOverviewLine.props,
showOptions: {
...BomOverviewLine.showOptions,
is_buffered: Boolean,
},
},
});

View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t
t-name="ddmrp.BomOverviewLine"
t-inherit="mrp.BomOverviewLine"
t-inherit-mode="extension"
owl="1"
>
<xpath expr="//td[@name='td_mrp_bom']" position="before">
<td
t-if="props.showOptions.is_buffered and data.is_buffered"
class="text-center"
>
<span>
<img
src='/ddmrp/static/img/is_buffered.png'
style="width:30px;height:30px;padding:5px"
/>
</span>
</td>
<td t-if="props.showOptions.is_buffered and !data.is_buffered" />
</xpath>
<xpath expr="//td[@t-if='showLeadTimes']" position="after">
<td t-if="showLeadTimes" class="text-end">
<t
t-esc="data.dlt"
t-options='{"widget": "float", "decimal_precision": "Lead Time"}'
/> Days
</td>
</xpath>
</t>
</templates>

View file

@ -0,0 +1,14 @@
/** @odoo-module **/
import {BomOverviewSpecialLine} from "@mrp/components/bom_overview_special_line/mrp_bom_overview_special_line";
import {patch} from "@web/core/utils/patch";
patch(BomOverviewSpecialLine, "ddmrp", {
props: {
...BomOverviewSpecialLine.props,
showOptions: {
...BomOverviewSpecialLine.showOptions,
is_buffered: Boolean,
},
},
});

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t
t-name="ddmrp.BomOverviewSpecialLine"
t-inherit="mrp.BomOverviewSpecialLine"
t-inherit-mode="extension"
owl="1"
>
<xpath expr="//td[@name='td_mrp_bom']" position="before">
<td t-if="showBuffered" />
</xpath>
<xpath expr="//td[@t-if='showLeadTimes']" position="after">
<td t-if="showLeadTimes" />
</xpath>
</t>
</templates>

View file

@ -0,0 +1,22 @@
/** @odoo-module **/
import {BomOverviewTable} from "@mrp/components/bom_overview_table/mrp_bom_overview_table";
import {patch} from "@web/core/utils/patch";
patch(BomOverviewTable.prototype, "ddmrp", {
// ---- Getters ----
get showBuffered() {
return this.props.showOptions.is_buffered;
},
});
patch(BomOverviewTable, "ddmrp", {
props: {
...BomOverviewTable.props,
showOptions: {
...BomOverviewTable.showOptions,
is_buffered: Boolean,
},
},
});

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t
t-name="ddmrp.BomOverviewTable"
t-inherit="mrp.BomOverviewTable"
t-inherit-mode="extension"
owl="1"
>
<xpath expr="//th[@name='th_mrp_bom_h']" position="before">
<th t-if="showBuffered" class="text-center">Buffered</th>
</xpath>
<xpath expr="//thead/tr/th[@t-if='showLeadTimes']" position="after">
<th
t-if="showLeadTimes"
class="text-end"
title="Decoupled Lead Time of the BoM."
>DLT</th>
</xpath>
<xpath expr="//td[@name='td_mrp_bom_f']" position="before">
<td t-if="showBuffered" />
</xpath>
<xpath expr="//tfoot/tr/td[@t-if='showLeadTimes']" position="after">
<td t-if="showLeadTimes" />
</xpath>
<xpath expr="//td[@name='td_mrp_bom_b']" position="before">
<td t-if="showBuffered" />
</xpath>
<xpath expr="//tfoot/t/tr/td[@t-if='showLeadTimes']" position="after">
<td t-if="showLeadTimes" />
</xpath>
</t>
</templates>

View file

@ -0,0 +1,27 @@
.circle {
padding: 0.3em 0.7em;
border-radius: 25em;
display: inline-cell;
}
.circle_dark_red {
background-color: #8b0000;
color: white;
}
.circle_red {
background-color: #ff0000;
color: white;
}
.circle_yellow {
background-color: #ffff00;
color: black;
}
.circle_green {
background-color: #33cc33;
color: white;
}
.popover {
display: inline-table;
}
body.with-scrollbar {
overflow-y: scroll;
}

View file

@ -0,0 +1,126 @@
/** @odoo-module **/
import {FloatField} from "@web/views/fields/float/float_field";
import {loadBundle} from "@web/core/assets";
import {registry} from "@web/core/registry";
import {useService} from "@web/core/utils/hooks";
import {useUniquePopover} from "@web/core/model_field_selector/unique_popover_hook";
const {Component, markup, onWillStart} = owl;
export class StockBufferPopover extends Component {
setup() {
this.actionService = useService("action");
this.orm = useService("orm");
onWillStart(async () => {
await loadBundle({
jsLibs: [
"/web_widget_bokeh_chart/static/src/lib/bokeh/bokeh-3.1.1.min.js",
"/web_widget_bokeh_chart/static/src/lib/bokeh/bokeh-api-3.1.1.min.js",
"/web_widget_bokeh_chart/static/src/lib/bokeh/bokeh-widgets-3.1.1.min.js",
"/web_widget_bokeh_chart/static/src/lib/bokeh/bokeh-tables-3.1.1.min.js",
"/web_widget_bokeh_chart/static/src/lib/bokeh/bokeh-mathjax-3.1.1.min.js",
"/web_widget_bokeh_chart/static/src/lib/bokeh/bokeh-gl-3.1.1.min.js",
],
});
var bufferId = this.props.record.resId;
var bufferField = this.props.buffer_id;
if (bufferField && this.props.record.data[bufferField]) {
if (
("field" in this.props.record.data[bufferField] &&
this.props.record.data[bufferField].field.type ==
"many2many") ||
bufferField.endsWith("_ids")
) {
if (this.props.record.data[bufferField].records.length > 0) {
bufferId = this.props.record.data[bufferField].records[0].resId;
} else {
bufferId = 0; // Relation is blank, no buffer.
}
} else {
// Assume m2o
bufferId = this.props.record.data[bufferField][0];
}
} else if (bufferField) {
bufferId = 0; // Relation is blank, no buffer.
}
if (bufferId == 0) {
return;
}
this.bokeh_chart = await this.orm.read(
"stock.buffer",
[bufferId],
[this.props.field]
);
});
}
get json_value() {
try {
var value = JSON.parse(this.bokeh_chart[0][this.props.field]);
value.div = markup(value.div.trim());
return value;
} catch (error) {
return {};
}
}
}
StockBufferPopover.template = "ddmrp.StockBufferPopover";
export class StockBufferInfoWidget extends FloatField {
setup() {
super.setup();
this.popover = useUniquePopover();
}
get classFromDecoration() {
var decorationName = this.props.record.data[this.props.color_from];
if (decorationName !== "" && decorationName.length > 1) {
decorationName = "circle" + decorationName.slice(1);
return `${decorationName}`;
}
return "";
}
showPopup(ev) {
ev.stopPropagation();
ev.preventDefault();
this.popover.add(
ev.currentTarget,
this.constructor.components.Popover,
{
bus: this.bus,
record: this.props.record,
field: this.props.field,
color_from: this.props.color_from,
buffer_id: this.props.buffer_id,
},
{
position: "right",
}
);
}
}
StockBufferInfoWidget.components = {
...StockBufferInfoWidget.components,
Popover: StockBufferPopover,
};
StockBufferInfoWidget.template = "ddmrp.StockBufferInfoWidget";
StockBufferInfoWidget.props = {
...StockBufferInfoWidget.props,
color_from: {type: String, optional: true},
field: {type: String, optional: true},
buffer_id: {type: String, optional: true},
};
const StockBufferInfoWidgetExtractProps = StockBufferInfoWidget.extractProps;
StockBufferInfoWidget.extractProps = ({attrs, field}) => {
return Object.assign(StockBufferInfoWidgetExtractProps({attrs, field}), {
color_from: attrs.options.color_from,
field: attrs.options.field,
buffer_id: attrs.options.buffer_id,
});
};
registry.category("fields").add("stock_buffer_info", StockBufferInfoWidget);

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" ?>
<template id="template" xml:space="preserve">
<!-- TODO: rename this to QtyAtDate in master version -->
<t
t-name="ddmrp.StockBufferInfoWidget"
t-inherit="web.FloatField"
owl="1"
t-inherit-mode="primary"
>
<xpath expr="//span" position="attributes">
<attribute name="t-on-click">showPopup</attribute>
<attribute name="class">circle</attribute>
<attribute name="t-att-class">classFromDecoration</attribute>
</xpath>
</t>
<t t-name="ddmrp.StockBufferPopover" owl="1">
<div>
<t t-out="json_value.div" />
<script type="text/javascript" t-out="json_value.script" />
</div>
</t>
</template>

View file

@ -0,0 +1,3 @@
from . import test_ddmrp
from . import test_ddmrp_distributed_source_location
from . import test_distributed_max_proc_time

View file

@ -0,0 +1,549 @@
# Copyright 2016-20 ForgeFlow S.L. (http://www.forgeflow.com)
# Copyright 2016 Aleph Objects, Inc. (https://www.alephobjects.com/)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from datetime import datetime
import odoo.tests.common as common
class TestDdmrpCommon(common.TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env = cls.env(
context=dict(
cls.env.context,
tracking_disable=True,
# compatibility with ddmrp_cron_actions_as_job,
# that would delay calls to "cron_actions" in these tests
test_queue_job_no_delay=True,
)
)
# Models
cls.productModel = cls.env["product.product"]
cls.templateModel = cls.env["product.template"]
cls.bomModel = cls.env["mrp.bom"]
cls.bomlineModel = cls.env["mrp.bom.line"]
cls.bufferModel = cls.env["stock.buffer"]
cls.pickingModel = cls.env["stock.picking"]
cls.moveModel = cls.env["stock.move"]
cls.quantModel = cls.env["stock.quant"]
cls.estimateModel = cls.env["stock.demand.estimate"]
cls.aducalcmethodModel = cls.env["product.adu.calculation.method"]
cls.locationModel = cls.env["stock.location"]
cls.make_procurement_wiz = cls.env["make.procurement.buffer"]
cls.user_model = cls.env["res.users"]
cls.partner_model = cls.env["res.partner"]
cls.supinfo_model = cls.env["product.supplierinfo"]
cls.pol_model = cls.env["purchase.order.line"]
cls.wh_model = cls.env["stock.warehouse"]
cls.orderpoint_model = cls.env["stock.warehouse.orderpoint"]
# Refs
cls.main_company = cls.env.ref("base.main_company")
cls.second_company = cls.env.ref("stock.res_company_1")
cls.warehouse = cls.env.ref("stock.warehouse0")
cls.warehouse2 = cls.wh_model.create(
{
"partner_id": cls.env.ref("base.main_partner").id,
"name": "Warehouse 2",
"code": "WH2",
}
)
cls.warehouse_sc = cls.env.ref("stock.stock_warehouse_shop0")
cls.stock_location = cls.env.ref("stock.stock_location_stock")
cls.stock_location_sc = cls.warehouse_sc.lot_stock_id
cls.location_shelf1 = cls.env.ref("stock.stock_location_components")
cls.supplier_location = cls.env.ref("stock.stock_location_suppliers")
cls.customer_location = cls.env.ref("stock.stock_location_customers")
cls.inter_wh = cls.env.ref("stock.stock_location_inter_wh")
cls.inventory_location = cls.env["stock.location"].search(
[("usage", "=", "inventory"), ("company_id", "=", cls.main_company.id)],
limit=1,
)
cls.picking_type_out = cls.env.ref("stock.picking_type_out").copy(
{
"reservation_method": "manual",
"sequence_code": "DDMRP-OUT",
}
)
cls.picking_type_in = cls.env.ref("stock.picking_type_in")
cls.picking_type_internal = cls.env.ref("stock.picking_type_internal").copy(
{
"reservation_method": "manual",
"sequence_code": "DDMRP-INTERNAL",
}
)
cls.uom_unit = cls.env.ref("uom.product_uom_unit")
cls.dozen_unit = cls.env.ref("uom.product_uom_dozen")
cls.buffer_profile_pur = cls.env.ref(
"ddmrp.stock_buffer_profile_replenish_purchased_medium_medium"
)
cls.buffer_profile_mmm = cls.env.ref(
"ddmrp.stock_buffer_profile_replenish_manufactured_medium_medium"
)
cls.buffer_profile_distr = cls.env.ref(
"ddmrp.stock_buffer_profile_replenish_distributed_medium_medium"
)
cls.buffer_profile_override = cls.env.ref(
"ddmrp.stock_buffer_profile_replenish_override_purchased_short_low"
)
cls.adu_fixed = cls.env.ref("ddmrp.adu_calculation_method_fixed")
cls.group_stock_manager = cls.env.ref("stock.group_stock_manager")
cls.group_mrp_user = cls.env.ref("mrp.group_mrp_user")
cls.group_change_procure_qty = cls.env.ref(
"ddmrp.group_change_buffer_procure_qty"
)
cls.group_buffer_manager = cls.env.ref("ddmrp.group_stock_buffer_maintainer")
cls.calendar = cls.env.ref("resource.resource_calendar_std")
cls.warehouse.calendar_id = cls.calendar
# Create users
cls.user = cls._create_user(
"user_1",
[
cls.group_stock_manager,
cls.group_mrp_user,
cls.group_change_procure_qty,
cls.group_buffer_manager,
],
)
# Create Partners:
vendor = cls.partner_model.create({"name": "Test Vendor 1"})
# Create products and BoM's:
manufacture_route = cls.env.ref("mrp.route_warehouse0_manufacture")
cls.productA = cls.productModel.create(
{
"name": "product A",
"standard_price": 1,
"type": "product",
"uom_id": cls.uom_unit.id,
"default_code": "A",
"route_ids": [(6, 0, manufacture_route.ids)],
"produce_delay": 10.0,
}
)
cls.component_a1 = cls.productModel.create(
{
"name": "Component A-1",
"standard_price": 1,
"type": "product",
"uom_id": cls.uom_unit.id,
"default_code": "RM-test01",
}
)
cls.bom_a = cls.bomModel.create(
{
"product_tmpl_id": cls.productA.product_tmpl_id.id,
"product_id": cls.productA.id,
}
)
cls.bomlineModel.create(
{
"product_id": cls.component_a1.id,
"product_qty": 2.0,
"bom_id": cls.bom_a.id,
}
)
# Create locations:
cls.binA = cls.locationModel.create(
{
"usage": "internal",
"name": "Bin A",
"location_id": cls.location_shelf1.id,
"company_id": cls.main_company.id,
}
)
cls.binB = cls.locationModel.create(
{
"usage": "internal",
"name": "Bin B",
"location_id": cls.location_shelf1.id,
"company_id": cls.main_company.id,
}
)
cls.locationModel._parent_store_compute()
cls.quant = cls.quantModel.create(
{
"location_id": cls.binA.id,
"company_id": cls.main_company.id,
"product_id": cls.productA.id,
"inventory_quantity": 200.0,
}
)
cls.quant.action_apply_inventory()
# Product B (purchased):
buy_route = cls.env.ref("purchase_stock.route_warehouse0_buy")
cls.product_purchased = cls.productModel.create(
{
"name": "product Purchased",
"standard_price": 1,
"type": "product",
"uom_id": cls.uom_unit.id,
"default_code": "B",
"route_ids": [(6, 0, buy_route.ids)],
}
)
cls.product_purchased_2 = cls.productModel.create(
{
"name": "product Purchased 2",
"standard_price": 1,
"type": "product",
"uom_id": cls.uom_unit.id,
"default_code": "B",
"route_ids": [(6, 0, buy_route.ids)],
}
)
cls.supinfo_model.create(
{
"product_tmpl_id": cls.product_purchased.product_tmpl_id.id,
"partner_id": vendor.id,
"delay": 20.0,
}
)
# Product C (purchased and with variants):
cls.template_c = cls.templateModel.create(
{
"name": "Product C",
"type": "product",
"uom_id": cls.uom_unit.id,
"default_code": "C",
"route_ids": [(6, 0, buy_route.ids)],
}
)
cls.color_attribute = cls.env["product.attribute"].create(
{"name": "Color", "sequence": 1}
)
cls.color_blue = cls.env["product.attribute.value"].create(
{"name": "Blue", "attribute_id": cls.color_attribute.id, "sequence": 1}
)
cls.color_orange = cls.env["product.attribute.value"].create(
{"name": "Orange", "attribute_id": cls.color_attribute.id, "sequence": 2}
)
cls.color_green = cls.env["product.attribute.value"].create(
{"name": "Green", "attribute_id": cls.color_attribute.id, "sequence": 3}
)
cls.p_c_color_attribute_line = cls.env[
"product.template.attribute.line"
].create(
{
"product_tmpl_id": cls.template_c.id,
"attribute_id": cls.color_attribute.id,
"value_ids": [
(6, 0, [cls.color_blue.id, cls.color_orange.id, cls.color_green.id])
],
}
)
cls.product_c_blue = cls.template_c.product_variant_ids[0]
cls.product_c_orange = cls.template_c.product_variant_ids[1]
cls.product_c_green = cls.template_c.product_variant_ids[2]
cls.p_c_supinfo_blue = cls.supinfo_model.create(
{
"product_tmpl_id": cls.template_c.id,
"product_id": cls.product_c_blue.id,
"partner_id": vendor.id,
"delay": 5.0,
}
)
cls.p_c_supinfo_orange = cls.supinfo_model.create(
{
"product_tmpl_id": cls.template_c.id,
"product_id": cls.product_c_orange.id,
"partner_id": vendor.id,
"delay": 10.0,
}
)
cls.p_c_supinfo_no_variant = cls.supinfo_model.create(
{
"product_tmpl_id": cls.template_c.id,
"partner_id": vendor.id,
"delay": 8.0,
}
)
# Product D (distributed):
cls.product_distributed = cls.productModel.create(
{
"name": "product Distributed",
"standard_price": 1,
"type": "product",
"uom_id": cls.uom_unit.id,
"default_code": "D",
# TODO: "route_ids": [(6, 0, distribution_route.id)],
}
)
# Create buffers:
cls.buffer_a = cls.bufferModel.create(
{
"buffer_profile_id": cls.buffer_profile_mmm.id,
"product_id": cls.productA.id,
"location_id": cls.stock_location.id,
"warehouse_id": cls.warehouse.id,
"qty_multiple": 1.0,
"adu_calculation_method": cls.adu_fixed.id,
"adu_fixed": 4.0,
"lead_days": 10.0,
"order_spike_horizon": 10.0,
}
)
cls.buffer_purchase = cls.bufferModel.create(
{
"buffer_profile_id": cls.buffer_profile_pur.id,
"product_id": cls.product_purchased.id,
"location_id": cls.stock_location.id,
"warehouse_id": cls.warehouse.id,
"qty_multiple": 1.0,
"adu_calculation_method": cls.adu_fixed.id,
"adu_fixed": 5.0,
}
)
cls.buffer_c_blue = cls.bufferModel.create(
{
"buffer_profile_id": cls.buffer_profile_pur.id,
"product_id": cls.product_c_blue.id,
"location_id": cls.stock_location.id,
"warehouse_id": cls.warehouse.id,
"qty_multiple": 1.0,
"adu_calculation_method": cls.adu_fixed.id,
"adu_fixed": 5.0,
}
)
cls.buffer_c_orange = cls.bufferModel.create(
{
"buffer_profile_id": cls.buffer_profile_pur.id,
"product_id": cls.product_c_orange.id,
"location_id": cls.stock_location.id,
"warehouse_id": cls.warehouse.id,
"qty_multiple": 1.0,
"adu_calculation_method": cls.adu_fixed.id,
"adu_fixed": 5.0,
}
)
cls.buffer_distributed = cls.bufferModel.create(
{
"buffer_profile_id": cls.buffer_profile_distr.id,
"product_id": cls.product_distributed.id,
"location_id": cls.stock_location.id,
"warehouse_id": cls.warehouse.id,
"qty_multiple": 1.0,
"adu_calculation_method": cls.adu_fixed.id,
"adu_fixed": 5.0,
"lead_days": 20,
}
)
# dates for a period of 120 days for estimates.
cls.estimate_date_from = cls.calendar.plan_days(1, datetime.today()).date()
days = 119
dt = cls.calendar.plan_days(+1 * days + 1, datetime.today())
cls.estimate_date_to = dt.date()
# Run cron jobs:
cls.bufferModel.cron_ddmrp_adu()
cls.bufferModel.cron_ddmrp()
@classmethod
def _create_user(cls, login, groups):
"""Create a user."""
group_ids = [group.id for group in groups]
user = cls.user_model.with_context(no_reset_password=True).create(
{
"name": "Test User",
"login": login,
"password": "demo",
"email": "test@yourcompany.com",
"groups_id": [(6, 0, group_ids)],
}
)
return user
def create_pickingoutA(self, date_move, qty, uom=False, source_location=None):
if not uom:
uom = self.productA.uom_id
if not source_location:
source_location = self.binA
picking = self.pickingModel.with_user(self.user).create(
{
"picking_type_id": self.picking_type_out.id,
"location_id": source_location.id,
"location_dest_id": self.customer_location.id,
"scheduled_date": date_move,
"move_ids": [
(
0,
0,
{
"name": "Test move",
"product_id": self.productA.id,
"date": date_move,
"product_uom": uom.id,
"product_uom_qty": qty,
"location_id": source_location.id,
"location_dest_id": self.customer_location.id,
},
)
],
}
)
picking.action_confirm()
return picking
def create_pickinginA(self, date_move, qty):
picking = self.pickingModel.with_user(self.user).create(
{
"picking_type_id": self.picking_type_in.id,
"location_id": self.supplier_location.id,
"location_dest_id": self.binA.id,
"scheduled_date": date_move,
"move_ids": [
(
0,
0,
{
"name": "Test move",
"product_id": self.productA.id,
"date": date_move,
"date_deadline": date_move,
"product_uom": self.productA.uom_id.id,
"product_uom_qty": qty,
"location_id": self.supplier_location.id,
"location_dest_id": self.binA.id,
},
)
],
}
)
picking.action_confirm()
return picking
def create_pickinginternalA(self, date_move, qty):
picking = self.pickingModel.with_user(self.user).create(
{
"picking_type_id": self.picking_type_internal.id,
"location_id": self.binA.id,
"location_dest_id": self.binB.id,
"scheduled_date": date_move,
"move_ids": [
(
0,
0,
{
"name": "Test move",
"product_id": self.productA.id,
"date": date_move,
"product_uom": self.productA.uom_id.id,
"product_uom_qty": qty,
"location_id": self.binA.id,
"location_dest_id": self.binB.id,
},
)
],
}
)
picking.action_confirm()
return picking
def create_picking_out(self, product, date_move, qty, source_location=None):
if not source_location:
source_location = self.binA
picking = self.pickingModel.with_user(self.user).create(
{
"picking_type_id": self.picking_type_out.id,
"location_id": source_location.id,
"location_dest_id": self.customer_location.id,
"scheduled_date": date_move,
"move_ids": [
(
0,
0,
{
"name": "Test move",
"product_id": product.id,
"date": date_move,
"product_uom": product.uom_id.id,
"product_uom_qty": qty,
"location_id": source_location.id,
"location_dest_id": self.customer_location.id,
},
)
],
}
)
picking.action_confirm()
return picking
def create_picking_in(self, product, date_move, qty):
picking = self.pickingModel.with_user(self.user).create(
{
"picking_type_id": self.picking_type_in.id,
"location_id": self.supplier_location.id,
"location_dest_id": self.binA.id,
"scheduled_date": date_move,
"move_ids": [
(
0,
0,
{
"name": "Test move",
"product_id": product.id,
"date": date_move,
"product_uom": product.uom_id.id,
"product_uom_qty": qty,
"location_id": self.supplier_location.id,
"location_dest_id": self.binA.id,
},
)
],
}
)
picking.action_confirm()
return picking
def _do_picking(self, picking, date):
"""Do picking with only one move on the given date."""
picking.action_confirm()
picking.move_ids.quantity_done = picking.move_ids.product_uom_qty
picking._action_done()
for move in picking.move_ids:
move.date = date
def create_orderpoint_procurement(self, buffer, make_procurement=True):
"""Make Procurement from Reordering Rule"""
wizard = (
self.make_procurement_wiz.with_user(self.user)
.with_context(
active_model="stock.buffer", active_ids=buffer.ids, active_id=buffer.id
)
.create({})
)
if make_procurement:
wizard.make_procurement()
return wizard
def create_inventorylossA(self, date_move, qty):
move = self.moveModel.with_user(self.user).create(
{
"name": "Test inventory move",
"product_id": self.productA.id,
"date": date_move,
"product_uom": self.productA.uom_id.id,
"product_uom_qty": qty,
"location_id": self.binA.id,
"location_dest_id": self.inventory_location.id,
},
)
return move
def _do_move(self, move, date):
move._action_confirm()
move.move_line_ids.qty_done = move.move_line_ids.reserved_uom_qty
move._action_done()
move.date = date

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,182 @@
# Copyright 2020 Camptocamp (https://www.camptocamp.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from .common import TestDdmrpCommon
class TestDdmrpDistributedSourceLocation(TestDdmrpCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
# we store goods in "Replenish" and make pull rules to
# go through "Replenish Step" when we replenish Stock
cls.replenish_location = cls.env["stock.location"].create(
{
"name": "Replenish",
"location_id": cls.warehouse.view_location_id.id,
"usage": "internal",
"company_id": cls.main_company.id,
}
)
replenish_step_location = cls.env["stock.location"].create(
{
"name": "Replenish Step",
"location_id": cls.warehouse.view_location_id.id,
"usage": "internal",
"company_id": cls.main_company.id,
}
)
replenish_route = cls.env["stock.route"].create(
{"name": "Replenish", "sequence": 1}
)
cls.env["stock.rule"].create(
{
"name": "Replenish",
"route_id": replenish_route.id,
"location_src_id": cls.replenish_location.id,
"location_dest_id": replenish_step_location.id,
"action": "pull",
"picking_type_id": cls.warehouse.int_type_id.id,
"procure_method": "make_to_stock",
"warehouse_id": cls.warehouse.id,
"company_id": cls.main_company.id,
}
)
cls.env["stock.rule"].create(
{
"name": "Replenish Step",
"route_id": replenish_route.id,
"location_src_id": replenish_step_location.id,
"location_dest_id": cls.warehouse.lot_stock_id.id,
"action": "pull",
"picking_type_id": cls.warehouse.int_type_id.id,
"procure_method": "make_to_order",
"warehouse_id": cls.warehouse.id,
"company_id": cls.main_company.id,
}
)
# our product uses the replenishment route
cls.product_c_green.route_ids = replenish_route
cls.buffer_dist = cls.bufferModel.create(
{
"buffer_profile_id": cls.buffer_profile_distr.id,
"product_id": cls.product_c_green.id,
"location_id": cls.stock_location.id,
"warehouse_id": cls.warehouse.id,
"qty_multiple": 1.0,
"adu_calculation_method": cls.adu_fixed.id,
"adu_fixed": 5.0,
}
)
cls.buffer_profile_distr.replenish_distributed_limit_to_free_qty = True
def test_distributed_source_location_id(self):
# as we have a route to replenish from this location, we expect the
# buffer to have this location set as source
self.assertEqual(
self.buffer_dist.distributed_source_location_id, self.replenish_location
)
self.assertEqual(self.buffer_dist.distributed_source_location_qty, 0)
def test_distributed_source_location_qty(self):
self.env["stock.quant"]._update_available_quantity(
self.product_c_green, self.replenish_location, 4000
)
# Invalidate the computed quantities
self.env["product.product"].invalidate_model()
self.assertEqual(self.buffer_dist.distributed_source_location_qty, 4000)
self.env["stock.quant"]._update_reserved_quantity(
self.product_c_green, self.replenish_location, 500
)
# Invalidate the computed quantities to recompute free_qty
self.env["product.product"].invalidate_model()
self.assertEqual(self.buffer_dist.distributed_source_location_qty, 3500)
self.assertEqual(
self.env["stock.buffer"].search(
[("distributed_source_location_qty", "=", 3500)]
),
self.buffer_dist,
)
def _set_qty_and_create_replenish_wizard(
self, qty_in_replenish=4000, recommended_qty=10000
):
self.env["stock.quant"]._update_available_quantity(
self.product_c_green, self.replenish_location, 4000
)
# lie about the recommended qty (we only want to test if the limit is
# applied)
self.buffer_dist.procure_recommended_qty = 10000
return self.create_orderpoint_procurement(
self.buffer_dist, make_procurement=False
)
def test_distributed_source_limit_replenish(self):
wizard = self._set_qty_and_create_replenish_wizard()
self.assertRecordValues(
wizard.item_ids,
[
{
"recommended_qty": 10000,
# limited to the free qty
"qty": 4000,
}
],
)
def test_distributed_source_limit_replenish_with_batch_limit_max(self):
self.buffer_dist.procure_max_qty = 1200
wizard = self._set_qty_and_create_replenish_wizard()
self.assertRecordValues(
wizard.item_ids,
# we expect lines to be per batch of 1200 but maxed to the free qty
[
{"recommended_qty": 10000, "qty": 1200},
{"recommended_qty": 10000, "qty": 1200},
{"recommended_qty": 10000, "qty": 1200},
{"recommended_qty": 10000, "qty": 400},
],
)
def test_distributed_source_limit_replenish_with_batch_limit_min(self):
self.buffer_dist.procure_min_qty = 5000
wizard = self._set_qty_and_create_replenish_wizard()
self.assertRecordValues(
wizard.item_ids,
# the 4000 in stock is below the min of 5000, nothing moved
[{"recommended_qty": 10000, "qty": 0}],
)
def test_distributed_source_limit_replenish_with_batch_limit_min_and_max(self):
self.buffer_dist.procure_min_qty = 1000
self.buffer_dist.procure_max_qty = 1200
wizard = self._set_qty_and_create_replenish_wizard()
self.assertRecordValues(
wizard.item_ids,
# the last batch would be 400 which is below the min limit,
# ignore the remaining
[
{"recommended_qty": 10000, "qty": 1200},
{"recommended_qty": 10000, "qty": 1200},
{"recommended_qty": 10000, "qty": 1200},
],
)
def test_distributed_source_limit_disabled(self):
self.buffer_profile_distr.replenish_distributed_limit_to_free_qty = False
wizard = self._set_qty_and_create_replenish_wizard()
self.assertRecordValues(
wizard.item_ids,
# normal behavior, apply the recommended qty when the option is set
# to False on the profile
[{"recommended_qty": 10000, "qty": 10000}],
)

View file

@ -0,0 +1,143 @@
# Copyright 2020 Camptocamp (https://www.camptocamp.com)
# Copyright 2021 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from freezegun import freeze_time
from odoo import fields
from .common import TestDdmrpCommon
class TestDdmrpMaxProcTime(TestDdmrpCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
# we store goods in "Replenish" and make pull rules to
# go through "Replenish Step" when we replenish Stock
cls.replenish_location = cls.env["stock.location"].create(
{
"name": "Replenish",
"location_id": cls.warehouse.view_location_id.id,
"usage": "internal",
"company_id": cls.main_company.id,
}
)
replenish_step_location = cls.env["stock.location"].create(
{
"name": "Replenish Step",
"location_id": cls.warehouse.view_location_id.id,
"usage": "internal",
"company_id": cls.main_company.id,
}
)
replenish_route = cls.env["stock.route"].create(
{"name": "Replenish", "sequence": 1}
)
cls.env["stock.rule"].create(
{
"name": "Replenish",
"route_id": replenish_route.id,
"location_src_id": cls.replenish_location.id,
"location_dest_id": replenish_step_location.id,
"action": "pull",
"picking_type_id": cls.warehouse.int_type_id.id,
"procure_method": "make_to_stock",
"warehouse_id": cls.warehouse.id,
"company_id": cls.main_company.id,
}
)
cls.env["stock.rule"].create(
{
"name": "Replenish Step",
"route_id": replenish_route.id,
"location_src_id": replenish_step_location.id,
"location_dest_id": cls.warehouse.lot_stock_id.id,
"action": "pull",
"picking_type_id": cls.warehouse.int_type_id.id,
"procure_method": "make_to_order",
"warehouse_id": cls.warehouse.id,
"company_id": cls.main_company.id,
}
)
# our product uses the replenishment route
cls.product_c_green.route_ids = replenish_route
cls.buffer_dist = cls.bufferModel.create(
{
"buffer_profile_id": cls.buffer_profile_distr.id,
"product_id": cls.product_c_green.id,
"location_id": cls.stock_location.id,
"warehouse_id": cls.warehouse.id,
"qty_multiple": 1.0,
"adu_calculation_method": cls.adu_fixed.id,
"adu_fixed": 5.0,
}
)
@freeze_time("2020-12-10 10:00:00")
def test_reschedule_proc_time_no_calendar(self):
"""Reschedule moves based on the proc time"""
self.buffer_profile_distr.distributed_reschedule_max_proc_time = 180
self.warehouse.calendar_id = False
self.env["stock.quant"]._update_available_quantity(
self.product_c_green, self.replenish_location, 4000
)
# lie about the recommended qty to force creation of replenishment
self.buffer_dist.procure_recommended_qty = 10000
self.create_orderpoint_procurement(self.buffer_dist)
moves = self.env["stock.move"].search(
[("product_id", "=", self.product_c_green.id)]
)
self.assertRecordValues(
moves,
[
{"date": fields.Datetime.to_datetime("2020-12-11 13:00:00")},
{"date": fields.Datetime.to_datetime("2020-12-11 13:00:00")},
],
)
@freeze_time("2020-12-10 10:00:00")
def test_reschedule_proc_time_with_calendar(self):
"""Reschedule moves based on the proc time with calendar"""
self.buffer_profile_distr.distributed_reschedule_max_proc_time = 90
self.env["stock.quant"]._update_available_quantity(
self.product_c_green, self.replenish_location, 4000
)
# lie about the recommended qty to force creation of replenishment
self.buffer_dist.procure_recommended_qty = 10000
self.create_orderpoint_procurement(self.buffer_dist)
moves = self.env["stock.move"].search(
[("product_id", "=", self.product_c_green.id)]
)
self.assertRecordValues(
moves,
[
# the start of the working hours is 7:00 (UTC), should be 8:00
# + 1:30 hour = 8:30
{"date": fields.Datetime.to_datetime("2020-12-11 08:30:00")},
{"date": fields.Datetime.to_datetime("2020-12-11 08:30:00")},
],
)
@freeze_time("2020-12-10 23:00:00")
def test_03_reschedule_to_next_day(self):
self.buffer_profile_distr.distributed_reschedule_max_proc_time = 90
self.warehouse.calendar_id = False
buffer = self.buffer_dist
self.assertEqual(buffer.dlt, 1)
self.assertEqual(buffer.incoming_dlt_qty, 0)
# Create a picking with date planned.
date_move = buffer._get_date_planned()
self.create_picking_in(buffer.product_id, date_move, 15.0)
# Picking should be in the DLT window.
buffer.cron_actions()
self.assertEqual(buffer.incoming_dlt_qty, 15.0)

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2017-20 ForgeFlow S.L. (http://www.forgeflow.com)
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl-3.0) -->
<odoo>
<record id="mrp_bom_form_view" model="ir.ui.view">
<field name="name">mrp.bom.form - ddmrp</field>
<field name="model">mrp.bom</field>
<field name="inherit_id" ref="mrp.mrp_bom_form_view" />
<field name="arch" type="xml">
<field name="product_id" position="after">
<field name="is_buffered" />
<field name="buffer_id" />
<label for="dlt" />
<div class="o_row" style="width: 60% !important">
<field name="dlt" />
<field
name="context_location_id"
options="{'no_open': True}"
style="text-align: right;"
/>
<button
title="Change Location"
name="action_change_context_location"
icon="fa-exchange"
type="object"
/>
</div>
</field>
<xpath
expr="//field[@name='bom_line_ids']/tree/field[@name='product_uom_id']"
position="after"
>
<field name="is_buffered" />
<field name="buffer_id" />
<field name="dlt" />
<field name="context_location_id" />
</xpath>
</field>
</record>
<record id="mrp_bom_line_view_form" model="ir.ui.view">
<field name="name">mrp.bom.line.form</field>
<field name="model">mrp.bom.line</field>
<field name="inherit_id" ref="mrp.mrp_bom_line_view_form" />
<field name="arch" type="xml">
<field name="parent_product_tmpl_id" position="after">
<field name="buffer_id" />
</field>
</field>
</record>
</odoo>

View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="mrp_production_tree_view" model="ir.ui.view">
<field name="name">mrp.production.tree</field>
<field name="model">mrp.production</field>
<field name="inherit_id" ref="mrp.mrp_production_tree_view" />
<field name="arch" type="xml">
<field name="date_planned_start" position="before">
<field name="buffer_id" invisible="1" />
<field name="execution_priority_level" invisible="1" />
<field
name="on_hand_percent"
options='{"buffer_id": "buffer_id", "color_from": "execution_priority_level", "field": "ddmrp_chart_execution"}'
widget="stock_buffer_info"
/>
</field>
</field>
</record>
<record id="view_mrp_production_filter" model="ir.ui.view">
<field name="name">mrp.production.select</field>
<field name="model">mrp.production</field>
<field name="inherit_id" ref="mrp.view_mrp_production_filter" />
<field name="arch" type="xml">
<field name="product_id" position="before">
<field name="execution_priority_level" />
<group name="execution_priority" string="On Hand Alert Zones">
<filter
name="execution_priority_level_red"
string="Red"
domain="[('execution_priority_level', '=', '1_red')]"
/>
<filter
name="execution_priority_level_yellow"
string="Yellow"
domain="[('execution_priority_level', '=', '2_yellow')]"
/>
<filter
name="execution_priority_level_green"
string="Green"
domain="[('execution_priority_level', '=', '3_green')]"
/>
</group>
</field>
</field>
</record>
</odoo>

View file

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record model="ir.ui.view" id="view_product_adu_calculation_method_tree">
<field name="name">product.adu.calculation.method.tree</field>
<field name="model">product.adu.calculation.method</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
<field name="method" />
<field name="company_id" groups="base.group_multi_company" />
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_product_adu_calculation_method_form">
<field name="name">product.adu.calculation.method.form</field>
<field name="model">product.adu.calculation.method</field>
<field name="arch" type="xml">
<form>
<sheet>
<group name="name">
<field name="name" />
<field name="method" />
<field
name="company_id"
groups="base.group_multi_company"
widget="selection"
/>
</group>
<group name="configuration">
<group name="past">
<field name="source_past" />
<label for="horizon_past" />
<div name="horizon_past">
<field name="horizon_past" class="oe_inline" /> days
</div>
<field
name="factor_past"
attrs="{'invisible': [('method', '!=', 'blended')]}"
/>
</group>
<group name="future">
<field name="source_future" />
<label for="horizon_future" />
<div name="horizon_future">
<field name="horizon_future" class="oe_inline" /> days
</div>
<field
name="factor_future"
attrs="{'invisible': [('method', '!=', 'blended')]}"
/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_product_adu_calculation_method_search" model="ir.ui.view">
<field name="name">product.adu.calculation.method.search</field>
<field name="model">product.adu.calculation.method</field>
<field name="arch" type="xml">
<search string="Search ADU Calculatiion methods">
<field name="name" />
</search>
</field>
</record>
<record
model="ir.actions.act_window"
id="product_adu_calculation_method_form_action"
>
<field name="name">ADU calculation methods</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">product.adu.calculation.method</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_product_adu_calculation_method_search" />
</record>
<menuitem
id="menu_product_adu_calculation_method"
parent="ddmrp.menu_ddmrp_config"
action="product_adu_calculation_method_form_action"
/>
</odoo>

View file

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="product_normal_form_view_inherit" model="ir.ui.view">
<field name="name">product.product.form.inherit</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_normal_form_view" />
<field name="arch" type="xml">
<xpath
expr="//div[@name='button_box']/button[@name='action_view_mrp_area_parameters']"
position="after"
>
<button
type="object"
name="action_view_stock_buffers"
class="oe_stat_button"
icon="fa-flask"
attrs="{'invisible': [('buffer_count', '=', 0)]}"
>
<field
name="buffer_count"
widget="statinfo"
string="Stock Buffers"
/>
</button>
</xpath>
</field>
</record>
<record id="product_template_only_form_view_inherit" model="ir.ui.view">
<field name="name">product.template.product.form.inherit</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_only_form_view" />
<field name="arch" type="xml">
<xpath
expr="//div[@name='button_box']/button[@name='action_view_mrp_area_parameters']"
position="after"
>
<button
type="object"
name="action_view_stock_buffers"
class="oe_stat_button"
icon="fa-flask"
attrs="{'invisible': [('buffer_count', '=', 0)]}"
>
<field
name="buffer_count"
widget="statinfo"
string="Stock Buffers"
/>
</button>
</xpath>
</field>
</record>
</odoo>

View file

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2017-18 ForgeFlow S.L. (http://www.forgeflow.com)
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl-3.0) -->
<odoo>
<record id="purchase_order_line_execution_tree" model="ir.ui.view">
<field name="name">purchase.order.line.tree - ddmrp</field>
<field name="model">purchase.order.line</field>
<field name="arch" type="xml">
<tree
default_order='execution_priority_level,on_hand_percent'
create="false"
editable="top"
delete="false"
multi_edit="1"
>
<field name="order_id" widget="many2one" readonly="True" />
<field name="partner_id" string="Vendor" />
<field name="execution_priority_level" invisible="1" />
<field
name="on_hand_percent"
options='{"buffer_id": "buffer_ids", "color_from": "execution_priority_level", "field": "ddmrp_chart_execution"}'
widget="stock_buffer_info"
/>
<field name="product_qty" readonly="True" />
<field name="product_id" readonly="True" />
<field
name="buffer_ids"
string="Buffers"
widget="many2many_tags"
readonly="1"
/>
<field name="date_planned" />
<field name="state" />
<field name="ddmrp_comment" />
</tree>
</field>
</record>
<record id="purchase_order_line_search" model="ir.ui.view">
<field name="name">purchase.order.line.search - ddmrp</field>
<field name="model">purchase.order.line</field>
<field name="inherit_id" ref="purchase.purchase_order_line_search" />
<field name="arch" type="xml">
<filter name="hide_cancelled" position="after">
<filter
name="hide_no_buffer"
string="Buffered"
domain="[('buffer_ids','!=',False), ('execution_priority_level','!=',False)]"
/>
<separator />
<group name="execution_priority" string="On Hand Alert Zones">
<filter
name="execution_priority_level_red"
string="Red"
domain="[('execution_priority_level', '=', '1_red')]"
/>
<filter
name="execution_priority_level_yellow"
string="Yellow"
domain="[('execution_priority_level', '=', '2_yellow')]"
/>
<filter
name="execution_priority_level_green"
string="Green"
domain="[('execution_priority_level', '=', '3_green')]"
/>
</group>
</filter>
</field>
</record>
<record id="po_line_execution_action" model="ir.actions.act_window">
<field name="name">PO lines On-Hand Status</field>
<field name="res_model">purchase.order.line</field>
<field name="view_id" ref="purchase_order_line_execution_tree" />
<field name="domain" />
<field name="context">{"search_default_hide_no_buffer":1}</field>
<field name="view_mode">tree</field>
</record>
<menuitem
id="menu_pol_execution"
action="po_line_execution_action"
parent="purchase.menu_procurement_management"
sequence="8"
/>
</odoo>

View file

@ -0,0 +1,70 @@
<?xml version="1.0" ?>
<!-- Copyright 2017-20 ForgeFlow S.L. (http://www.forgeflow.com)
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl-3.0) -->
<odoo>
<record id="purchase_order_form" model="ir.ui.view">
<field name="name">purchase.order.form - ddmrp</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_form" />
<field name="arch" type="xml">
<xpath
expr="//field[@name='order_line']//field[@name='date_planned']"
position="after"
>
<field name="buffer_ids" invisible="1" />
<field name="execution_priority_level" invisible="1" />
<field
name="on_hand_percent"
options='{"buffer_id": "buffer_ids", "color_from": "execution_priority_level", "field": "ddmrp_chart_execution"}'
widget="stock_buffer_info"
/>
</xpath>
<xpath
expr="//page[@name='purchase_delivery_invoice']/group/group"
position="inside"
>
<field name="ddmrp_comment" />
</xpath>
<xpath expr="//field[@name='order_line']/form//notebook" position="inside">
<page
name="buffers"
string="Stock Buffers"
groups="stock.group_stock_user"
>
<field name="buffer_ids" />
</page>
</xpath>
</field>
</record>
<record id="view_purchase_order_filter" model="ir.ui.view">
<field name="name">request.quotation.select - ddmrp</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.view_purchase_order_filter" />
<field name="arch" type="xml">
<filter name="order_date" position="before">
<filter
name="execution_priority_level_red"
string="On-Hand Alert: Red"
domain="[('order_line.execution_priority_level', '=', '1_red')]"
/>
<filter
name="execution_priority_level_yellow"
string="On-Hand Alert: Yellow"
domain="[('order_line.execution_priority_level', '=', '2_yellow')]"
/>
<separator />
</filter>
</field>
</record>
<record id="purchase_order_line_form2" model="ir.ui.view">
<field name="name">purchase.order.line.form2</field>
<field name="model">purchase.order.line</field>
<field name="inherit_id" ref="purchase.purchase_order_line_form2" />
<field name="arch" type="xml">
<field name="name" position="after">
<separator string="Stock Buffers" />
<field name="buffer_ids" />
</field>
</field>
</record>
</odoo>

View file

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record model="ir.ui.view" id="view_stock_buffer_profile_lead_time_form">
<field name="name">stock.buffer.profile.lead.time.form</field>
<field name="model">stock.buffer.profile.lead.time</field>
<field name="arch" type="xml">
<form string="Buffer Profile Lead Time Factor">
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only" />
<h1>
<field name="name" class="oe_inline" />
</h1>
</div>
<group>
<group name="factor">
<field name="factor" />
</group>
<group name="company" groups="base.group_multi_company">
<field name="company_id" />
</group>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_stock_buffer_profile_lead_time_tree">
<field name="name">stock.buffer.profile.lead.time.tree</field>
<field name="model">stock.buffer.profile.lead.time</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
<field name="factor" />
<field name="company_id" groups="base.group_multi_company" />
</tree>
</field>
</record>
<record id="view_stock_buffer_profile_lead_time_search" model="ir.ui.view">
<field name="name">stock.buffer.profile.lead.time.search</field>
<field name="model">stock.buffer.profile.lead.time</field>
<field name="arch" type="xml">
<search string="Search Buffer Profile Lead Time">
<field name="name" />
<field name="factor" />
<field name="company_id" groups="base.group_multi_company" />
</search>
</field>
</record>
<record
model="ir.actions.act_window"
id="stock_buffer_profile_lead_time_form_action"
>
<field name="name">Buffer Profile Lead Time Factor</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">stock.buffer.profile.lead.time</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_stock_buffer_profile_lead_time_search" />
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to start a new buffer profile lead time factor
</p>
</field>
</record>
<menuitem
id="menu_stock_buffer_profile_lead_time"
parent="ddmrp.menu_ddmrp_config"
action="stock_buffer_profile_lead_time_form_action"
/>
</odoo>

View file

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record model="ir.ui.view" id="view_stock_buffer_profile_variability_form">
<field name="name">stock.buffer.profile.variability.form</field>
<field name="model">stock.buffer.profile.variability</field>
<field name="arch" type="xml">
<form string="Buffer Profile Variability Factor">
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only" />
<h1>
<field name="name" class="oe_inline" />
</h1>
</div>
<group>
<group name="factor">
<field name="factor" />
</group>
<group name="company" groups="base.group_multi_company">
<field name="company_id" />
</group>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_stock_buffer_profile_variability_tree">
<field name="name">stock.buffer.profile.variability.tree</field>
<field name="model">stock.buffer.profile.variability</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
<field name="factor" />
<field name="company_id" groups="base.group_multi_company" />
</tree>
</field>
</record>
<record id="view_stock_buffer_profile_variability_search" model="ir.ui.view">
<field name="name">stock.buffer.profile.variability.search</field>
<field name="model">stock.buffer.profile.variability</field>
<field name="arch" type="xml">
<search string="Search Buffer Profile Variability">
<field name="name" />
<field name="factor" />
<field name="company_id" groups="base.group_multi_company" />
</search>
</field>
</record>
<record
model="ir.actions.act_window"
id="stock_buffer_profile_variability_form_action"
>
<field name="name">Buffer Profile Variability Factor</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">stock.buffer.profile.variability</field>
<field name="view_mode">tree,form</field>
<field
name="search_view_id"
ref="view_stock_buffer_profile_variability_search"
/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to start a new buffer profile variability factor
</p>
</field>
</record>
<menuitem
id="menu_stock_buffer_profile_variability"
parent="ddmrp.menu_ddmrp_config"
action="stock_buffer_profile_variability_form_action"
/>
</odoo>

View file

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_stock_buffer_profile_form" model="ir.ui.view">
<field name="name">stock.buffer.profile.form</field>
<field name="model">stock.buffer.profile</field>
<field name="arch" type="xml">
<form string="Buffer Profile">
<sheet>
<div class="oe_edit_only">
<label for="name" class="oe_inline" />
<h1>
<field name="name" />
</h1>
</div>
<group>
<group name="replenish_method">
<field name="replenish_method" />
</group>
<group name="item_type">
<field name="item_type" />
</group>
<group name="lead_time">
<field name="lead_time_id" />
</group>
<group name="variability">
<field name="variability_id" />
</group>
<group name="organization">
<field
name="company_id"
groups="base.group_multi_company"
options="{'no_create': True}"
/>
</group>
<group
name="distributed_options"
attrs="{'invisible': [('item_type', '!=', 'distributed')]}"
>
<field name="replenish_distributed_limit_to_free_qty" />
<field name="distributed_reschedule_max_proc_time" />
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_stock_buffer_profile_tree" model="ir.ui.view">
<field name="name">stock.buffer.profile.tree</field>
<field name="model">stock.buffer.profile</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
<field name="replenish_method" />
<field name="item_type" />
<field name="lead_time_id" />
<field name="variability_id" />
<field name="company_id" groups="base.group_multi_company" />
</tree>
</field>
</record>
<record id="view_stock_buffer_profile_search" model="ir.ui.view">
<field name="name">stock.buffer.profile.search</field>
<field name="model">stock.buffer.profile</field>
<field name="arch" type="xml">
<search string="Search Buffer Profiles">
<field name="name" />
<separator />
<field name="item_type" />
<field name="replenish_method" />
<field name="lead_time_id" />
<field name="variability_id" />
<field name="company_id" groups="base.group_multi_company" />
</search>
</field>
</record>
<record id="stock_buffer_profile_form_action" model="ir.actions.act_window">
<field name="name">Buffer Profiles</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">stock.buffer.profile</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_stock_buffer_profile_search" />
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to start a new buffer profile
</p>
</field>
</record>
<menuitem
id="menu_ddmrp_config"
name="DDMRP"
parent="stock.menu_stock_config_settings"
/>
<menuitem
id="menu_stock_buffer_profile_mgt"
parent="ddmrp.menu_ddmrp_config"
action="stock_buffer_profile_form_action"
/>
</odoo>

View file

@ -0,0 +1,739 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="stock_buffer_view_tree" model="ir.ui.view">
<field name="name">stock.buffer.tree</field>
<field name="model">stock.buffer</field>
<field name="priority">50</field>
<field name="arch" type="xml">
<tree decoration-danger="procure_recommended_qty &gt; 0">
<header>
<button
name="action_request_procurement"
type="object"
string="Procure"
/>
</header>
<field name="product_id" />
<field name="product_vendor_code" optional="hide" />
<field name="warehouse_id" optional="show" />
<field name="location_id" groups="stock.group_stock_multi_locations" />
<field
name="product_uom"
string="UoM"
groups="uom.group_uom"
optional="hide"
/>
<field name="planning_priority_level" invisible="1" />
<field
name="net_flow_position_percent"
options='{"color_from": "planning_priority_level", "field": "ddmrp_chart"}'
widget="stock_buffer_info"
/>
<field
name="product_location_qty_available_not_res"
string="On-Hand"
optional="show"
/>
<field name="execution_priority_level" invisible="1" />
<field
name="on_hand_percent"
options='{"color_from": "execution_priority_level", "field": "ddmrp_chart_execution"}'
widget="stock_buffer_info"
/>
<button
title="Refresh Buffer"
name="refresh_buffer"
icon="fa-refresh"
type="object"
/>
<field name="incoming_dlt_qty" string="Incoming Within DLT" />
<field
name="incoming_outside_dlt_qty"
string="Incoming Outside DLT"
optional="hide"
/>
<field
name="incoming_total_qty"
string="Total Incoming"
optional="show"
/>
<button
title="Total Incoming"
name="action_view_supply_moves"
icon="fa-exchange"
type="object"
/>
<field name="qualified_demand" />
<field name="net_flow_position" />
<field name="procure_recommended_qty" />
<field name="procure_uom_id" groups="uom.group_uom" optional="show" />
<button
title="Create Procurement"
name="%(ddmrp.act_make_procurement_from_buffer)d"
icon="fa-cogs"
type="action"
/>
<button
title="Some incoming quantities are outside of the DLT Horizon and may require rescheduling. Press this button to display the involved supply orders"
name="action_view_supply_moves_outside_dlt_window"
icon="fa-warning"
type="object"
attrs="{'invisible':[('incoming_outside_dlt_qty', '=', 0)]}"
/>
<field name="rfq_total_qty" optional="hide" />
<field name="rfq_outside_dlt_qty" invisible="1" />
<button
title="Some RFQ quantities are outside of the DLT Horizon and may require rescheduling.
Press this button to display the involved RFQs"
name="action_view_supply_rfq_outside_dlt_window"
icon="fa-warning"
type="object"
attrs="{'invisible':[('rfq_outside_dlt_qty', '=', 0)]}"
/>
<field name="rfq_inside_dlt_qty" optional="hide" />
<button
title="Some RFQs are inside of the DLT Horizon and waiting validation to be accounted into the NFP.
Press this button to display them."
name="action_view_supply_rfq_inside_dlt_window"
icon="fa-warning"
type="object"
attrs="{'invisible':[('rfq_inside_dlt_qty', '=', 0)]}"
/>
<button
title="No stock available in source location for distributed buffer"
name="action_dummy"
icon="fa-warning"
type="object"
attrs="{'invisible':['|', ('distributed_source_location_id', '=', False), ('distributed_source_location_qty', '>', 0)]}"
/>
<field name="distributed_source_location_id" optional="hide" />
<field name="distributed_source_location_qty" optional="hide" />
<field name="item_type" optional="show" />
<field name="main_supplier_id" optional="show" />
<field name="adu" optional="hide" />
<field name="dlt" optional="hide" />
<field name="top_of_red" optional="hide" />
<field name="top_of_yellow" optional="hide" />
<field name="top_of_green" optional="hide" />
</tree>
</field>
</record>
<record id="stock_buffer_view_form" model="ir.ui.view">
<field name="name">stock.buffer.form</field>
<field name="model">stock.buffer</field>
<field name="arch" type="xml">
<form duplicate="0">
<header>
<button
string="Refresh Buffer"
name="refresh_buffer"
icon="fa-refresh"
type="object"
/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button
type="object"
name="action_view_yearly_consumption"
class="oe_stat_button"
string="Consumption"
icon="fa-fire"
/>
<button
type="object"
name="action_view_stock_demand_estimates"
class="oe_stat_button"
string="Estimates"
icon="fa-signal"
attrs="{'invisible': [('adu_calculation_method_type', '!=', 'future')]}"
/>
<button
type="object"
name="action_view_bom"
class="oe_stat_button"
string="Bill of Materials"
icon="fa-flask"
attrs="{'invisible': [('item_type', '!=', 'manufactured')]}"
/>
<button
type="object"
name="action_view_mrp_productions"
class="oe_stat_button"
string="Manufacturing Orders"
icon="fa-wrench"
attrs="{'invisible': [('item_type', '!=', 'manufactured')]}"
/>
<button
type="object"
name="action_view_purchase"
class="oe_stat_button"
string="Purchase Orders"
icon="fa-credit-card"
attrs="{'invisible': [('item_type', '!=', 'purchased')]}"
/>
<button
class="oe_stat_button"
name="action_used_in_bom"
type="object"
attrs="{'invisible':['|',('product_type', 'not in', ['product', 'consu']), ('used_in_bom_count', '=', 0)]}"
icon="fa-level-up"
>
<field
string="Used In"
name="used_in_bom_count"
widget="statinfo"
/>
</button>
</div>
<widget
name="web_ribbon"
title="Archived"
bg_color="bg-danger"
attrs="{'invisible': [('active', '=', True)]}"
/>
<group>
<group>
<field name="active" invisible="1" />
<field name="name" />
<field name="product_id" />
<field
name="product_uom"
options="{'no_open': True, 'no_create': True}"
groups="uom.group_uom"
/>
<field name="product_type" invisible="1" />
</group>
<group>
<field
name="location_id"
groups="stock.group_stock_multi_locations"
/>
<field
name="warehouse_id"
widget="selection"
groups="stock.group_stock_multi_locations"
/>
<field
name="company_id"
groups="base.group_multi_company"
options="{'no_create': True}"
/>
</group>
<group name="buffer_size_factors">
<group
name="main_buffer_size_factors"
string="Main Buffer size factors"
colspan="2"
>
<field name="buffer_profile_id" />
<field name="replenish_method" invisible="1" />
<field name="item_type" invisible="1" />
<field
name="green_override"
attrs="{'invisible': [('replenish_method', '!=', 'replenish_override')]}"
/>
<field
name="yellow_override"
attrs="{'invisible': [('replenish_method', '!=', 'replenish_override')]}"
/>
<field
name="red_override"
attrs="{'invisible': [('replenish_method', '!=', 'replenish_override')]}"
/>
<field name="adu_calculation_method" />
<field
name="adu_calculation_method_type"
invisible="1"
/>
<field
name="adu_fixed"
attrs="{'invisible': [('adu_calculation_method_type', '!=', 'fixed')]}"
/>
<label for="adu" />
<div name="adu" class="o_row">
<field
name="adu"
force_save="1"
class="oe_inline"
/>
<button
title="View ADU (Past - Direct Demand)"
name="action_view_past_adu_direct_demand"
icon="fa-search"
type="object"
attrs="{'invisible': ['|', ('adu_calculation_method_type', 'in', ['fixed', 'future']), ('adu', '=', 0)]}"
/>
<button
title="View ADU (Past - Indirect Demand)"
name="action_view_past_adu_indirect_demand"
icon="fa-search"
type="object"
attrs="{'invisible': ['|', '|', ('adu_calculation_method_type', 'in', ['fixed', 'future']), ('used_in_bom_count', '=', 0), ('adu', '=', 0)]}"
/>
<button
title="View ADU (Future - Direct Demand)"
name="action_view_future_adu_direct_demand"
icon="fa-search"
type="object"
attrs="{'invisible': ['|', ('adu_calculation_method_type', 'in', ['fixed', 'past']), ('adu', '=', 0)]}"
/>
<button
title="View ADU (Future - Indirect Demand)"
name="action_view_future_adu_indirect_demand"
icon="fa-search"
type="object"
attrs="{'invisible': ['|', '|', ('adu_calculation_method_type', 'in', ['fixed', 'past']), ('used_in_bom_count', '=', 0), ('adu', '=', 0)]}"
/>
</div>
<field
name="lead_days"
attrs="{'invisible': [('item_type', '!=', 'distributed')]}"
/>
<label for="dlt" />
<div name="dlt">
<field name="dlt" class="oe_inline" /> days
</div>
</group>
<group />
<group
name="secondary_buffer_size_factors"
string="Secondary Buffer size factors"
colspan="2"
>
<label for="extra_lead_time" />
<div name="extra_lead_time">
<field
name="extra_lead_time"
class="oe_inline"
/> days
</div>
<field name="minimum_order_quantity" />
<label for="order_cycle" />
<div name="order_cycle">
<field name="order_cycle" class="oe_inline" /> days
</div>
</group>
</group>
<group name="ddmrp_chart" string="Buffer summary">
<div colspan="2">
<field
name="ddmrp_chart"
widget="bokeh_chart"
nolabel="1"
attrs="{'invisible': [('show_execution_chart', '=', True)]}"
/>
<field
name="ddmrp_chart_execution"
widget="bokeh_chart"
nolabel="1"
attrs="{'invisible': [('show_execution_chart', '=', False)]}"
/>
</div>
<div cols="3">
<label
for="show_execution_chart"
string="Planning"
class="text-muted"
attrs="{'invisible': [('show_execution_chart', '=', False)]}"
/>
<label
for="show_execution_chart"
string="Planning"
style="font-weight: bold;"
attrs="{'invisible': [('show_execution_chart', '=', True)]}"
/>
<field
name="show_execution_chart"
widget="boolean_toggle"
nolabel="1"
options="{'autosave': False}"
/>
<label
for="show_execution_chart"
string="Execution"
class="text-muted"
attrs="{'invisible': [('show_execution_chart', '=', True)]}"
/>
<label
for="show_execution_chart"
string="Execution"
style="font-weight: bold;"
attrs="{'invisible': [('show_execution_chart', '=', False)]}"
/>
</div>
</group>
<group name="execution_settings">
<group
name="qualified_demand"
string="Qualified Demand"
colspan="2"
>
<label for="order_spike_horizon" />
<div name="order_spike_horizon">
<field
name="order_spike_horizon"
class="oe_inline"
/> days
</div>
<field name="order_spike_threshold" />
</group>
<group
name="procurement_settings"
string="Procurement Settings"
colspan="2"
>
<field name="procure_uom_id" groups="uom.group_uom" />
<field name="qty_multiple" />
<field name="procure_min_qty" />
<field name="procure_max_qty" />
<field
name="group_id"
groups="stock.group_adv_location"
/>
<field
name="distributed_source_location_id"
attrs="{'invisible': ['|', ('item_type', '!=', 'distributed'), ('distributed_source_location_id', '=', False)]}"
/>
<field
name="distributed_source_location_qty"
attrs="{'invisible': ['|', ('item_type', '!=', 'distributed'), ('distributed_source_location_id', '=', False)]}"
/>
<field
name="main_supplier_id"
attrs="{'invisible': [('item_type', '!=', 'purchased')]}"
/>
<field
name="product_vendor_code"
attrs="{'invisible': [('product_vendor_code', '=', False)]}"
/>
<field name="auto_procure" />
<field
name="auto_procure_option"
attrs="{'invisible': [('auto_procure', '=', False)]}"
/>
</group>
</group>
<group name="buffer_summary">
<group
name="buffer_information"
string="Buffer Information"
colspan="2"
>
<field name="product_location_qty_available_not_res" />
<label for="incoming_dlt_qty" />
<div name="incoming_dlt_qty" class="o_row">
<field name="incoming_dlt_qty" class="oe_inline" />
<button
title="View Incoming Moves (Within DLT)"
name="action_view_supply_moves_inside_dlt_window"
icon="fa-search"
type="object"
attrs="{'invisible': [('incoming_dlt_qty', '=', 0)]}"
/>
</div>
<label for="incoming_outside_dlt_qty" />
<div name="incoming_outside_dlt_qty" class="o_row">
<field
name="incoming_outside_dlt_qty"
class="oe_inline"
/>
<button
title="Some incoming qty is outside of the DLT Horizon and may require rescheduling.
Press this button to display the involved supply orders"
name="action_view_supply_moves_outside_dlt_window"
icon="fa-warning"
type="object"
attrs="{'invisible':[('incoming_outside_dlt_qty', '=', 0)]}"
/>
<field name="rfq_outside_dlt_qty" invisible="1" />
<button
title="Some incoming RFQs are outside of the DLT Horizon and may require rescheduling.
Press this button to display the involved supply orders"
name="action_view_supply_rfq_outside_dlt_window"
icon="fa-warning"
type="object"
attrs="{'invisible':[('rfq_outside_dlt_qty', '=', 0)]}"
/>
</div>
<label for="qualified_demand" />
<div name="qualified_demand" class="o_row">
<field name="qualified_demand" class="oe_inline" />
<field
name="qualified_demand_stock_move_ids"
invisible="1"
/>
<field
name="qualified_demand_mrp_move_ids"
invisible="1"
/>
<button
title="View Qualified Demand from Pickings"
name="action_view_qualified_demand_moves"
icon="fa-search"
type="object"
attrs="{'invisible': ['|', ('qualified_demand', '=', 0), ('qualified_demand_stock_move_ids', '=', [])]}"
/>
<button
title="View Qualified Demand from MRP"
name="action_view_qualified_demand_mrp"
icon="fa-search"
type="object"
attrs="{'invisible': ['|', ('qualified_demand', '=', 0), ('qualified_demand_mrp_move_ids', '=', [])]}"
/>
<field name="rfq_inside_dlt_qty" invisible="1" />
<button
title="Some RFQs are inside of the DLT Horizon and waiting validation to be accounted into the NFP.
Press this button to display them."
name="action_view_supply_rfq_inside_dlt_window"
icon="fa-warning"
type="object"
attrs="{'invisible':[('rfq_inside_dlt_qty', '=', 0)]}"
/>
</div>
<field name="net_flow_position" />
<label for="net_flow_position_percent" />
<div name="net_flow_position_percent">
<field
name="net_flow_position_percent"
class="oe_inline"
/> %
</div>
</group>
<group />
<group
name="buffer_target"
string="On Hand Target"
colspan="2"
>
<label for="on_hand_target_position" />
<div name="on_hand_target_position_uom" class="o_row">
<field name="on_hand_target_position" />
</div>
<label for="on_hand_target_min" />
<div name="on_hand_target_range">
<field
name="on_hand_target_min"
class="oe_inline"
/> -
<field
name="on_hand_target_max"
class="oe_inline"
/>
</div>
</group>
</group>
</group>
<notebook>
<page string="Supply &amp; Demand" name="demand_supply_bars">
<group>
<group string="Supply">
<div style="margin-top: 1em;">
<field
name="ddmrp_supply_chart"
style="height:400px"
widget="bokeh_chart"
nolabel="1"
/>
</div>
</group>
<group string="Demand">
<div style="margin-top: 1em;">
<field
name="ddmrp_demand_chart"
style="height:400px"
widget="bokeh_chart"
nolabel="1"
/>
</div>
</group>
</group>
</page>
<page name="zones_info" string="Zones Information">
<group col="3">
<group name="green_zone" string="Green zone">
<div class="no_print" colspan="2">
<p
class="oe_grey"
>The green zone determines the average order frequency and the order size. It is determined as the maximum of the following three factors: Minimum Order Cycle, Lead Time Factor and Minimum Order Quantity.</p>
</div>
<field name="green_zone_oc" />
<field name="green_zone_moq" />
<field name="green_zone_lt_factor" />
<field name="green_zone_qty" />
</group>
<group name="yellow_zone" string="Yellow zone">
<div class="no_print" colspan="2">
<p
class="oe_grey"
>The yellow zone represents the stock required to cover a full lead time.</p>
</div>
<field name="yellow_zone_qty" />
</group>
<group name="red_zone" string="Red zone">
<div class="no_print" colspan="2">
<p
class="oe_grey"
>The red zone is the embedded safety in the buffer. The larger the variability associated with the product, the larger the red zone will be. It is composed of two sub-zones: Red base and red safety.</p>
</div>
<field name="red_base_qty" />
<field name="red_safety_qty" />
<field name="red_zone_qty" />
</group>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="stock_buffer_search" model="ir.ui.view">
<field name="name">stock.buffer.search</field>
<field name="model">stock.buffer</field>
<field name="arch" type="xml">
<search>
<field
name="name"
string="Buffer"
filter_domain="[
'|', '|',
('name', 'ilike', self), ('product_id', 'ilike', self), ('location_id', 'ilike', self)]"
/>
<field name="buffer_profile_id" />
<field
name="warehouse_id"
groups="stock.group_stock_multi_warehouses"
/>
<field name="location_id" groups="stock.group_stock_multi_locations" />
<field name="main_supplier_id" />
<field name="company_id" groups="base.group_multi_company" />
<field name="product_id" />
<field name="product_categ_id" />
<field name="dlt" />
<field name="order_cycle" />
<field name="minimum_order_quantity" />
<field name="planning_priority_level" />
<field name="execution_priority_level" />
<group name="planning_priority" string="Planning Priority Zones">
<filter
name="planning_priority_level_red"
string="Red"
domain="[('planning_priority_level', '=', '1_red')]"
/>
<filter
name="planning_priority_level_yellow"
string="Yellow"
domain="[('planning_priority_level', '=', '2_yellow')]"
/>
<filter
name="planning_priority_level_green"
string="Green"
domain="[('planning_priority_level', '=', '3_green')]"
/>
</group>
<separator />
<group name="execution_priority" string="On Hand Alert Zones">
<filter
name="execution_priority_level_red"
string="Red"
domain="[('execution_priority_level', '=', '1_red')]"
/>
<filter
name="execution_priority_level_yellow"
string="Yellow"
domain="[('execution_priority_level', '=', '2_yellow')]"
/>
<filter
name="execution_priority_level_green"
string="Green"
domain="[('execution_priority_level', '=', '3_green')]"
/>
</group>
<separator />
<group name="item_type" string="Type">
<filter
name="item_type_manufactured"
string="Manufactured"
domain="[('item_type', '=', 'manufactured')]"
/>
<filter
name="item_type_purchased"
string="Purchased"
domain="[('item_type', '=', 'purchased')]"
/>
<filter
name="item_type_distributed"
string="Distributed"
domain="[('item_type', '=', 'distributed')]"
/>
</group>
<separator />
<group name="procure_recommendation" string="Procurement">
<filter
name="procure_recommended"
string="Procurement recommended"
domain="[('procure_recommended_qty', '>', 0.0)]"
/>
</group>
<separator />
<filter
name="has_long_term_supply"
string="Has Long Term Supply"
domain="['|', ('incoming_outside_dlt_qty', '>', 0), ('rfq_outside_dlt_qty', '>', 0)]"
/>
<separator />
<filter
name="has_distributed_source_location_qty"
string="Has Stock In Source Location"
domain="[('distributed_source_location_qty', '>', 0)]"
/>
<separator />
<filter
string="Archived"
name="inactive"
domain="[('active','=',False)]"
/>
<group expand="0" string="Group By">
<filter
string="Warehouse"
name="warehouse"
domain="[]"
context="{'group_by':'warehouse_id'}"
groups="stock.group_stock_multi_warehouses"
/>
<filter
string="Location"
name="location"
domain="[]"
context="{'group_by':'location_id'}"
groups="stock.group_stock_multi_locations"
/>
<filter
string="Main supplier"
name="main_supplier_group_filter"
domain="[]"
context="{'group_by':'main_supplier_id'}"
/>
<filter
string="Product Category"
name="product_categ_id"
domain="[]"
context="{'group_by': 'product_categ_id'}"
/>
</group>
</search>
</field>
</record>
<record id="action_stock_buffer" model="ir.actions.act_window">
<field name="name">Stock Buffers</field>
<field name="res_model">stock.buffer</field>
<field name="view_mode">tree,form</field>
<field name="context">{'location_id': False}</field>
</record>
<menuitem
id="menu_stock_buffer"
parent="stock.menu_stock_inventory_control"
action="action_stock_buffer"
/>
</odoo>

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2018-20 ForgeFlow S.L. (http://www.forgeflow.com)
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl-3.0) -->
<odoo>
<record id="view_move_tree" model="ir.ui.view">
<field name="name">stock.move.tree</field>
<field name="model">stock.move</field>
<field name="inherit_id" ref="stock.view_move_tree" />
<field name="arch" type="xml">
<field name="state" position="after">
<button
title="Go to Source"
name="action_open_stock_move_source"
icon="fa-arrow-right"
type="object"
/>
</field>
</field>
</record>
<record id="view_move_form" model="ir.ui.view">
<field name="name">stock.move.form</field>
<field name="model">stock.move</field>
<field name="inherit_id" ref="stock.view_move_form" />
<field name="arch" type="xml">
<field name="date" position="attributes">
<attribute name="invisible">False</attribute>
</field>
<field name="move_dest_ids" position="after">
<field name="buffer_ids" />
</field>
</field>
</record>
<record id="view_move_consumption_pivot" model="ir.ui.view">
<field name="name">stock.move.consumption.pivot</field>
<field name="model">stock.move</field>
<field name="arch" type="xml">
<pivot string="Stock Moves Yearly Consumption">
<field name="date" interval="month" type="row" />
<field name="location_dest_id" type="col" />
<field name="product_uom_qty" type="measure" />
</pivot>
</field>
</record>
<record id="stock_move_year_consumption_action" model="ir.actions.act_window">
<field name="name">Stock Move Last Year Consumption</field>
<field name="res_model">stock.move</field>
<field name="view_id" ref="ddmrp.view_move_consumption_pivot" />
<field
name="context"
>{"time_ranges": {"field": "date", "range": "last_365_days"}}</field>
<field name="domain" />
<field name="view_mode">pivot</field>
</record>
</odoo>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_picking_form" model="ir.ui.view">
<field name="name">stock.picking.form.inherit</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form" />
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button
name="action_stock_buffer_open"
type="object"
string="Product Buffers"
class="oe_stat_button"
icon="fa-tasks"
/>
</div>
</field>
</record>
</odoo>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2018 ForgeFlow S.L. (http://www.forgeflow.com)
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
<odoo>
<record id="view_warehouse" model="ir.ui.view">
<field name="name">stock.warehouse</field>
<field name="model">stock.warehouse</field>
<field name="inherit_id" ref="stock.view_warehouse" />
<field name="arch" type="xml">
<xpath expr="//page[@name='warehouse_config']/group" position="inside">
<group name="ddmrp" string="DDMRP">
<field name="nfp_incoming_safety_factor" />
</group>
</xpath>
</field>
</record>
</odoo>

View file

@ -0,0 +1,6 @@
from . import multi_level_mrp
from . import make_procurement_buffer
from . import mrp_bom_change_location
from . import ddmrp_duplicate_buffer
from . import ddmrp_run
from . import res_config_settings

View file

@ -0,0 +1,98 @@
# Copyright 2023 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class DdmrpDuplicateBuffer(models.TransientModel):
_name = "ddmrp.duplicate.buffer"
_description = "DDMRP Duplicate Buffer"
@api.model
def default_get(self, fields_list):
res = super().default_get(fields_list)
buffer_obj = self.env["stock.buffer"]
buffer_ids = self.env.context.get("active_ids", [])
active_model = self.env.context["active_model"]
if not buffer_ids:
return
assert active_model == "stock.buffer", "Bad context propagation"
if len(buffer_ids) > 1:
res.update(
{
"type": "location",
}
)
else:
buffer = buffer_obj.browse(buffer_ids)
res.update(
{
"type": "both",
"product_id": buffer.product_id.id,
"location_id": buffer.location_id.id,
}
)
return res
type = fields.Selection(
string="Duplication Type",
selection=[
("both", "Change both values"),
("product", "Change Product"),
("location", "Change Location"),
],
required=True,
)
product_id = fields.Many2one(
comodel_name="product.product",
string="New Product",
)
location_id = fields.Many2one(
comodel_name="stock.location",
string="New Location",
)
def action_duplicate_buffer(self):
buffer_obj = self.env["stock.buffer"]
buffer_ids = self.env.context["active_ids"] or []
if self.type in ["product", "both"] and not self.product_id:
raise UserError(_("Please select a New Product."))
if self.type in ["location", "both"] and not self.location_id:
raise UserError(_("Please select a New Location."))
copy_buffers = self.env["stock.buffer"]
for buffer in buffer_obj.browse(buffer_ids):
default = {}
if self.type in ["product", "both"]:
default["product_id"] = self.product_id.id
if self.type in ["location", "both"]:
default["location_id"] = self.location_id.id
default["warehouse_id"] = self.location_id.warehouse_id.id
default["company_id"] = self.location_id.company_id.id
copy_buffers |= buffer.copy(default)
if len(copy_buffers) == 1:
view_id = self.env.ref("ddmrp.stock_buffer_view_form").id
return {
"name": _("Duplicated Buffers"),
"type": "ir.actions.act_window",
"res_model": "stock.buffer",
"res_id": copy_buffers.id,
"view_mode": "form",
"view_id": view_id,
}
else:
xmlid = "ddmrp.action_stock_buffer"
action = self.env["ir.actions.act_window"]._for_xml_id(xmlid)
action.update(
{
"name": _("Duplicated Buffers"),
"res_model": "stock.buffer",
"view_mode": "tree,form",
"domain": [("id", "in", copy_buffers.ids)],
}
)
return action

View file

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="ddmrp_duplicate_buffer_wizard" model="ir.ui.view">
<field name="name">Ddmrp Duplicate Buffer Wizard</field>
<field name="model">ddmrp.duplicate.buffer</field>
<field name="arch" type="xml">
<form string="Copy Ddmrp Stock Buffer">
<group>
<group>
<field name="type" />
<field
name="product_id"
attrs="{'invisible':[('type','=', 'location')]}"
/>
<field
name="location_id"
attrs="{'invisible':[('type','=', 'product')]}"
/>
</group>
</group>
<footer>
<button
name="action_duplicate_buffer"
string="Duplicate"
type="object"
class="btn-primary"
/>
</footer>
</form>
</field>
</record>
<record id="action_server_ddmrp_duplicate_buffer" model="ir.actions.act_window">
<field name="name">Duplicate Buffer</field>
<field name="res_model">ddmrp.duplicate.buffer</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="binding_model_id" ref="ddmrp.model_stock_buffer" />
<field name="view_id" ref="ddmrp.ddmrp_duplicate_buffer_wizard" />
</record>
</odoo>

View file

@ -0,0 +1,15 @@
# Copyright 2018-20 ForgeFlow S.L. (http://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import models
class DdmrpRun(models.TransientModel):
_name = "ddmrp.run"
_description = "DDMRP Manual Run Wizard"
def run_cron_ddmrp_adu(self):
self.env["stock.buffer"].cron_ddmrp_adu(True)
def run_cron_ddmrp(self):
self.env["stock.buffer"].cron_ddmrp(True)

Some files were not shown because too many files have changed in this diff Show more