[11.0][IMP] mrp_multi_level: be able to group demand when generating supply proposals

This commit is contained in:
Lois Rilo
2019-04-02 11:09:19 +02:00
committed by JasminSForgeFlow
parent 8472d4f750
commit f9cd5f733a
7 changed files with 149 additions and 107 deletions

View File

@@ -78,15 +78,16 @@ To launch replenishment orders (moves, purchases, production orders...):
hand side gears in any record.
#. On the wizard, check everything is ok and click *Execute*.
Known issues / Roadmap
======================
* The functionality related to field *Nbr. Days* in products is not
functional for the time being. Please, stay tuned to future updates.
Changelog
=========
11.0.2.1.0 (2019-04-02)
~~~~~~~~~~~~~~~~~~~~~~~
* [IMP] Implement *Nbr. Days* functionality to be able to group demand when
generating supply proposals.
(`#345 <https://github.com/OCA/manufacture/pull/345>`_):
11.0.2.0.0 (2018-11-20)
~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -1,9 +1,9 @@
# Copyright 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
# Copyright 2016-18 Eficent Business and IT Consulting Services S.L.
# Copyright 2016-19 Eficent Business and IT Consulting Services S.L.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{
'name': 'MRP Multi Level',
'version': '11.0.2.0.0',
'version': '11.0.2.1.0',
'development_status': 'Beta',
'license': 'AGPL-3',
'author': 'Ucamco, '

View File

@@ -1,3 +1,10 @@
11.0.2.1.0 (2019-04-02)
~~~~~~~~~~~~~~~~~~~~~~~
* [IMP] Implement *Nbr. Days* functionality to be able to group demand when
generating supply proposals.
(`#345 <https://github.com/OCA/manufacture/pull/345>`_):
11.0.2.0.0 (2018-11-20)
~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -1,2 +0,0 @@
* The functionality related to field *Nbr. Days* in products is not
functional for the time being. Please, stay tuned to future updates.

View File

@@ -386,40 +386,40 @@ and explodes this down to the lowest level.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="id8">Configuration</a><ul>
<li><a class="reference internal" href="#mrp-areas" id="id9">MRP Areas</a></li>
<li><a class="reference internal" href="#product-mrp-area-parameters" id="id10">Product MRP Area Parameters</a></li>
<li><a class="reference internal" href="#configuration" id="id10">Configuration</a><ul>
<li><a class="reference internal" href="#mrp-areas" id="id11">MRP Areas</a></li>
<li><a class="reference internal" href="#product-mrp-area-parameters" id="id12">Product MRP Area Parameters</a></li>
</ul>
</li>
<li><a class="reference internal" href="#usage" id="id11">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="id12">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#changelog" id="id13">Changelog</a><ul>
<li><a class="reference internal" href="#id1" id="id14">11.0.2.0.0 (2018-11-20)</a></li>
<li><a class="reference internal" href="#id3" id="id15">11.0.1.1.0 (2018-08-30)</a></li>
<li><a class="reference internal" href="#id5" id="id16">11.0.1.0.1 (2018-08-03)</a></li>
<li><a class="reference internal" href="#id7" id="id17">11.0.1.0.0 (2018-07-09)</a></li>
<li><a class="reference internal" href="#usage" id="id13">Usage</a></li>
<li><a class="reference internal" href="#changelog" id="id14">Changelog</a><ul>
<li><a class="reference internal" href="#id1" id="id15">11.0.2.1.0 (2019-04-02)</a></li>
<li><a class="reference internal" href="#id3" id="id16">11.0.2.0.0 (2018-11-20)</a></li>
<li><a class="reference internal" href="#id5" id="id17">11.0.1.1.0 (2018-08-30)</a></li>
<li><a class="reference internal" href="#id7" id="id18">11.0.1.0.1 (2018-08-03)</a></li>
<li><a class="reference internal" href="#id9" id="id19">11.0.1.0.0 (2018-07-09)</a></li>
</ul>
</li>
<li><a class="reference internal" href="#bug-tracker" id="id18">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id19">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id20">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id21">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id22">Maintainers</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id20">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id21">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id22">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id23">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id24">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h2><a class="toc-backref" href="#id8">Configuration</a></h2>
<h2><a class="toc-backref" href="#id10">Configuration</a></h2>
<div class="section" id="mrp-areas">
<h3><a class="toc-backref" href="#id9">MRP Areas</a></h3>
<h3><a class="toc-backref" href="#id11">MRP Areas</a></h3>
<ul class="simple">
<li>Go to <em>Manufacturing &gt; Configuration &gt; MRP Areas</em> and define or edit
any existing area. You can specify the working hours for every area.</li>
</ul>
</div>
<div class="section" id="product-mrp-area-parameters">
<h3><a class="toc-backref" href="#id10">Product MRP Area Parameters</a></h3>
<h3><a class="toc-backref" href="#id12">Product MRP Area Parameters</a></h3>
<ul class="simple">
<li>Go to <em>Manufacturing &gt; Master Data &gt; Product MRP Area Parameters</em> and set
the MRP parameters for a given product and area.</li>
@@ -427,7 +427,7 @@ the MRP parameters for a given product and area.</li>
</div>
</div>
<div class="section" id="usage">
<h2><a class="toc-backref" href="#id11">Usage</a></h2>
<h2><a class="toc-backref" href="#id13">Usage</a></h2>
<p>To manually run the MRP scheduler:</p>
<ol class="arabic simple">
<li>Go to <em>Manufacturing &gt; Operations &gt; Run MRP Multi Level</em>.</li>
@@ -442,17 +442,18 @@ hand side gears in any record.</li>
<li>On the wizard, check everything is ok and click <em>Execute</em>.</li>
</ol>
</div>
<div class="section" id="known-issues-roadmap">
<h2><a class="toc-backref" href="#id12">Known issues / Roadmap</a></h2>
<div class="section" id="changelog">
<h2><a class="toc-backref" href="#id14">Changelog</a></h2>
<div class="section" id="id1">
<h3><a class="toc-backref" href="#id15">11.0.2.1.0 (2019-04-02)</a></h3>
<ul class="simple">
<li>The functionality related to field <em>Nbr. Days</em> in products is not
functional for the time being. Please, stay tuned to future updates.</li>
<li>[IMP] Implement <em>Nbr. Days</em> functionality to be able to group demand when
generating supply proposals.
(<a class="reference external" href="https://github.com/OCA/manufacture/pull/345">#345</a>):</li>
</ul>
</div>
<div class="section" id="changelog">
<h2><a class="toc-backref" href="#id13">Changelog</a></h2>
<div class="section" id="id1">
<h3><a class="toc-backref" href="#id14">11.0.2.0.0 (2018-11-20)</a></h3>
<div class="section" id="id3">
<h3><a class="toc-backref" href="#id16">11.0.2.0.0 (2018-11-20)</a></h3>
<ul class="simple">
<li>[REW] Refactor MRP Area.
(<a class="reference external" href="https://github.com/OCA/manufacture/pull/322">#322</a>):<ul>
@@ -464,15 +465,15 @@ different areas.</li>
</li>
</ul>
</div>
<div class="section" id="id3">
<h3><a class="toc-backref" href="#id15">11.0.1.1.0 (2018-08-30)</a></h3>
<div class="section" id="id5">
<h3><a class="toc-backref" href="#id17">11.0.1.1.0 (2018-08-30)</a></h3>
<ul class="simple">
<li>[FIX] Consider <em>Qty Multiple</em> on product to propose the quantity to procure.
(<a class="reference external" href="https://github.com/OCA/manufacture/pull/297">#297</a>)</li>
</ul>
</div>
<div class="section" id="id5">
<h3><a class="toc-backref" href="#id16">11.0.1.0.1 (2018-08-03)</a></h3>
<div class="section" id="id7">
<h3><a class="toc-backref" href="#id18">11.0.1.0.1 (2018-08-03)</a></h3>
<ul class="simple">
<li>[FIX] User and system locales doesnt break MRP calculation.
(<a class="reference external" href="https://github.com/OCA/manufacture/pull/290">#290</a>)</li>
@@ -481,15 +482,15 @@ as a related on MRP Areas.
(<a class="reference external" href="https://github.com/OCA/manufacture/pull/290">#290</a>)</li>
</ul>
</div>
<div class="section" id="id7">
<h3><a class="toc-backref" href="#id17">11.0.1.0.0 (2018-07-09)</a></h3>
<div class="section" id="id9">
<h3><a class="toc-backref" href="#id19">11.0.1.0.0 (2018-07-09)</a></h3>
<ul class="simple">
<li>Start of the history.</li>
</ul>
</div>
</div>
<div class="section" id="bug-tracker">
<h2><a class="toc-backref" href="#id18">Bug Tracker</a></h2>
<h2><a class="toc-backref" href="#id20">Bug Tracker</a></h2>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/manufacture/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
@@ -497,16 +498,16 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
<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="#id19">Credits</a></h2>
<h2><a class="toc-backref" href="#id21">Credits</a></h2>
<div class="section" id="authors">
<h3><a class="toc-backref" href="#id20">Authors</a></h3>
<h3><a class="toc-backref" href="#id22">Authors</a></h3>
<ul class="simple">
<li>Ucamco</li>
<li>Eficent</li>
</ul>
</div>
<div class="section" id="contributors">
<h3><a class="toc-backref" href="#id21">Contributors</a></h3>
<h3><a class="toc-backref" href="#id23">Contributors</a></h3>
<ul class="simple">
<li>Wim Audenaert &lt;<a class="reference external" href="mailto:wim.audenaert&#64;ucamco.com">wim.audenaert&#64;ucamco.com</a>&gt;</li>
<li>Jordi Ballester &lt;<a class="reference external" href="mailto:jordi.ballester&#64;eficent.com">jordi.ballester&#64;eficent.com</a>&gt;</li>
@@ -514,7 +515,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
</ul>
</div>
<div class="section" id="maintainers">
<h3><a class="toc-backref" href="#id22">Maintainers</a></h3>
<h3><a class="toc-backref" href="#id24">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

View File

@@ -1,4 +1,4 @@
# Copyright 2018 Eficent Business and IT Consulting Services S.L.
# Copyright 2018-19 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
@@ -17,6 +17,8 @@ class TestMrpMultiLevel(SavepointCase):
cls.mo_obj = cls.env['mrp.production']
cls.po_obj = cls.env['purchase.order']
cls.product_obj = cls.env['product.product']
cls.loc_obj = cls.env['stock.location']
cls.mrp_area_obj = cls.env['mrp.area']
cls.product_mrp_area_obj = cls.env['product.mrp.area']
cls.partner_obj = cls.env['res.partner']
cls.stock_picking_obj = cls.env['stock.picking']
@@ -45,6 +47,18 @@ class TestMrpMultiLevel(SavepointCase):
# Partner:
vendor1 = cls.partner_obj.create({'name': 'Vendor 1'})
# Create secondary location and MRP Area:
cls.sec_loc = cls.loc_obj.create({
'name': 'Test location',
'usage': 'internal',
'location_id': cls.wh.view_location_id.id,
})
cls.secondary_area = cls.mrp_area_obj.create({
'name': 'Test',
'warehouse_id': cls.wh.id,
'location_id': cls.sec_loc.id,
})
# Create products:
route_buy = cls.env.ref('purchase.route_warehouse0_buy').id
cls.prod_test = cls.product_obj.create({
@@ -59,6 +73,12 @@ class TestMrpMultiLevel(SavepointCase):
'product_id': cls.prod_test.id,
'mrp_area_id': cls.mrp_area.id,
})
# Parameters in secondary area with nbr_days set.
cls.product_mrp_area_obj.create({
'product_id': cls.prod_test.id,
'mrp_area_id': cls.secondary_area.id,
'mrp_nbr_days': 7,
})
cls.prod_min = cls.product_obj.create({
'name': 'Product with minimum order qty',
'type': 'product',
@@ -239,6 +259,8 @@ class TestMrpMultiLevel(SavepointCase):
qty += 70.0
cls._create_demand_estimate(
cls.prod_test, cls.stock_location, dr, qty)
cls._create_demand_estimate(
cls.prod_test, cls.sec_loc, dr, qty)
cls.mrp_multi_level_wiz.create({}).run_mrp_multi_level()
@@ -425,11 +447,13 @@ class TestMrpMultiLevel(SavepointCase):
def test_06_demand_estimates(self):
"""Tests demand estimates integration."""
estimates = self.estimate_obj.search(
[('product_id', '=', self.prod_test.id)])
estimates = self.estimate_obj.search([
('product_id', '=', self.prod_test.id),
('location_id', '=', self.stock_location.id)])
self.assertEqual(len(estimates), 3)
moves = self.mrp_move_obj.search([
('product_id', '=', self.prod_test.id),
('mrp_area_id', '=', self.mrp_area.id),
])
# 3 weeks - 3 days in the past = 18 days of valid estimates:
moves_from_estimates = moves.filtered(lambda m: m.mrp_type == 'd')
@@ -440,6 +464,9 @@ class TestMrpMultiLevel(SavepointCase):
self.assertIn(-50.0, quantities) # 350 a week => 50.0 dayly:
actions = moves.filtered(lambda m: m.mrp_action == 'po')
self.assertEqual(len(actions), 18)
inventories = self.mrp_inventory_obj.search([
('mrp_area_id', '=', self.secondary_area.id)])
self.assertEqual(len(inventories), 18)
def test_07_procure_mo(self):
"""Test procurement wizard with MOs."""
@@ -483,5 +510,29 @@ class TestMrpMultiLevel(SavepointCase):
('product_mrp_area_id.product_id', '=', self.prod_multiple.id)])
self.assertEqual(mrp_inv_multiple.to_procure, 125)
def test_09_group_demand(self):
"""Test demand grouping functionality, `nbr_days`."""
estimates = self.estimate_obj.search([
('product_id', '=', self.prod_test.id),
('location_id', '=', self.sec_loc.id)])
self.assertEqual(len(estimates), 3)
moves = self.mrp_move_obj.search([
('product_id', '=', self.prod_test.id),
('mrp_area_id', '=', self.secondary_area.id),
])
# 3 weeks - 3 days in the past = 18 days of valid estimates:
moves_from_estimates = moves.filtered(lambda m: m.mrp_type == 'd')
self.assertEqual(len(moves_from_estimates), 18)
# 18 days of demand / 7 nbr_days = 2.57 => 3 supply moves expected.
supply_moves = moves.filtered(lambda m: m.mrp_type == 's')
self.assertEqual(len(supply_moves), 3)
quantities = supply_moves.mapped('mrp_qty')
week_1_expected = sum(moves_from_estimates[0:7].mapped('mrp_qty'))
self.assertIn(abs(week_1_expected), quantities)
week_2_expected = sum(moves_from_estimates[7:14].mapped('mrp_qty'))
self.assertIn(abs(week_2_expected), quantities)
week_3_expected = sum(moves_from_estimates[14:].mapped('mrp_qty'))
self.assertIn(abs(week_3_expected), quantities)
# TODO: test procure wizard: pos, multiple...
# TODO: test multiple destination IDS:...

View File

@@ -1,6 +1,7 @@
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
# Copyright 2016-18 Eficent Business and IT Consulting Services S.L.
# Copyright 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
# Copyright 2016-19 Eficent Business and IT Consulting Services S.L.
# - Jordi Ballester Alomar <jordi.ballester@eficent.com>
# - Lois Rilo <lois.rilo@eficent.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models, exceptions, _
@@ -507,64 +508,48 @@ class MultiLevelMrp(models.TransientModel):
last_date = None
last_qty = 0.00
onhand = product_mrp_area.qty_available
move_ids = []
for move in product_mrp_area.mrp_move_ids:
move_ids.append(move.id)
for move_id in move_ids:
move_rec = self.env['mrp.move'].search(
[('id', '=', move_id)])
for move in move_rec:
if move.mrp_action == 'none':
if last_date is not None:
if datetime.date(
datetime.strptime(
move.mrp_date, '%Y-%m-%d')) \
> last_date+timedelta(
days=product_mrp_area.mrp_nbr_days):
if (onhand + last_qty + move.mrp_qty) \
< product_mrp_area.mrp_minimum_stock \
or (onhand + last_qty) \
< product_mrp_area.mrp_minimum_stock:
name = 'Grouped Demand for %d Days' % \
product_mrp_area.mrp_nbr_days
qtytoorder = \
product_mrp_area.mrp_minimum_stock - \
product_mrp_area - last_qty
cm = self.create_move(
product_mrp_area_id=product_mrp_area,
mrp_date=last_date,
mrp_qty=qtytoorder,
name=name)
qty_ordered = cm['qty_ordered']
onhand = onhand + last_qty + qty_ordered
last_date = None
last_qty = 0.00
nbr_create += 1
if (onhand + last_qty + move.mrp_qty) < \
product_mrp_area.mrp_minimum_stock or \
(onhand + last_qty) < \
product_mrp_area.mrp_minimum_stock:
if last_date is None:
last_date = datetime.date(
datetime.strptime(move.mrp_date,
'%Y-%m-%d'))
last_qty = move.mrp_qty
else:
last_qty = last_qty + move.mrp_qty
else:
last_date = datetime.date(
datetime.strptime(move.mrp_date,
'%Y-%m-%d'))
onhand = onhand + move.mrp_qty
grouping_delta = product_mrp_area.mrp_nbr_days
for move in product_mrp_area.mrp_move_ids.filtered(
lambda m: m.mrp_action == 'none'):
if last_date and (
fields.Date.from_string(move.mrp_date)
>= last_date + timedelta(days=grouping_delta)) and (
(onhand + last_qty + move.mrp_qty)
< product_mrp_area.mrp_minimum_stock
or (onhand + last_qty)
< product_mrp_area.mrp_minimum_stock):
name = 'Grouped Demand for %d Days' % grouping_delta
qtytoorder = product_mrp_area.mrp_minimum_stock - last_qty
cm = self.create_move(
product_mrp_area_id=product_mrp_area,
mrp_date=last_date,
mrp_qty=qtytoorder,
name=name)
qty_ordered = cm.get('qty_ordered', 0.0)
onhand = onhand + last_qty + qty_ordered
last_date = None
last_qty = 0.00
nbr_create += 1
if (onhand + last_qty + move.mrp_qty) < \
product_mrp_area.mrp_minimum_stock or \
(onhand + last_qty) < \
product_mrp_area.mrp_minimum_stock:
if not last_date:
last_date = fields.Date.from_string(move.mrp_date)
last_qty = move.mrp_qty
else:
last_qty += move.mrp_qty
else:
last_date = fields.Date.from_string(move.mrp_date)
onhand += move.mrp_qty
if last_date is not None and last_qty != 0.00:
name = 'Grouped Demand for %d Days' % \
(product_mrp_area.mrp_nbr_days, )
if last_date and last_qty != 0.00:
name = 'Grouped Demand for %d Days' % grouping_delta
qtytoorder = product_mrp_area.mrp_minimum_stock - onhand - last_qty
cm = self.create_move(
product_mrp_area_id=product_mrp_area, mrp_date=last_date,
mrp_qty=qtytoorder, name=name)
qty_ordered = cm['qty_ordered']
qty_ordered = cm.get('qty_ordered', 0.0)
onhand += qty_ordered
nbr_create += 1
return nbr_create
@@ -605,7 +590,6 @@ class MultiLevelMrp(models.TransientModel):
else:
onhand += move.mrp_qty
else:
# TODO: review this
nbr_create = self._init_mrp_move_grouped_demand(
nbr_create, product_mrp_area)