[11.0][REW/IMP] mrp_multi_level:

* Extract concept of planned orders from mrp.move.
* Fix error grouping demand when there is no supply for a the first day of grouping.
* Adapt tests.
This commit is contained in:
Lois Rilo
2019-05-21 12:51:37 +02:00
committed by davidborromeo
parent 26c97b4169
commit 4625b79bc9
18 changed files with 467 additions and 348 deletions

View File

@@ -35,12 +35,12 @@ and explodes this down to the lowest level.
Key Features Key Features
------------ ------------
* MRP parameters at product variant level. * MRP parameters set by product variant MRP area pairs.
* Integration with `Stock Demand Estimates <https://github.com/OCA/stock-logistics-warehouse/tree/11.0/stock_demand_estimate>`_ system. * Integration with `Stock Demand Estimates <https://github.com/OCA/stock-logistics-warehouse/tree/11.0/stock_demand_estimate>`_ system.
* Cron job to calculate the MRP demand. * Cron job to calculate the MRP demand.
* Manually calculate the MRP demand. * Manually calculate the MRP demand.
* Confirm the calculated MRP demand and create PO's, or MO's. * Confirm the calculated MRP demand and create PO's, or MO's.
* Able to see the products for which action is needed. * Able to see the products for which action is needed throught Planned Orders.
**Table of contents** **Table of contents**
@@ -81,6 +81,12 @@ To launch replenishment orders (moves, purchases, production orders...):
Changelog Changelog
========= =========
11.0.3.0.0 (2019-05-22)
~~~~~~~~~~~~~~~~~~~~~~~
* [REW/IMP] Rework to include Planned Orders.
(`#365 <https://github.com/OCA/manufacture/pull/365>`_):
11.0.2.2.0 (2019-05-02) 11.0.2.2.0 (2019-05-02)
~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -3,7 +3,7 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{ {
'name': 'MRP Multi Level', 'name': 'MRP Multi Level',
'version': '11.0.2.2.0', 'version': '11.0.3.0.0',
'development_status': 'Beta', 'development_status': 'Beta',
'license': 'AGPL-3', 'license': 'AGPL-3',
'author': 'Ucamco, ' 'author': 'Ucamco, '

View File

@@ -3,5 +3,6 @@ from . import stock_location
from . import product_product from . import product_product
from . import product_template from . import product_template
from . import mrp_move from . import mrp_move
from . import mrp_planned_order
from . import mrp_inventory from . import mrp_inventory
from . import product_mrp_area from . import product_mrp_area

View File

@@ -1,15 +1,16 @@
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com> # © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
# © 2016 Eficent Business and IT Consulting Services S.L. # © 2016-19 Eficent Business and IT Consulting Services S.L.
# - Jordi Ballester Alomar <jordi.ballester@eficent.com> # - Jordi Ballester Alomar <jordi.ballester@eficent.com>
# - Lois Rilo Antelo <lois.rilo@eficent.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import fields, models from odoo import api, fields, models
class MrpArea(models.Model): class MrpArea(models.Model):
_name = 'mrp.area' _name = 'mrp.area'
name = fields.Char('Name') name = fields.Char(required=True)
warehouse_id = fields.Many2one( warehouse_id = fields.Many2one(
comodel_name='stock.warehouse', string='Warehouse', comodel_name='stock.warehouse', string='Warehouse',
required=True, required=True,
@@ -24,3 +25,9 @@ class MrpArea(models.Model):
string='Working Hours', string='Working Hours',
related='warehouse_id.calendar_id', related='warehouse_id.calendar_id',
) )
@api.multi
def _get_locations(self):
self.ensure_one()
return self.env['stock.location'].search([
('id', 'child_of', self.location_id.id)])

View File

@@ -1,6 +1,7 @@
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com> # © 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.
# - Jordi Ballester Alomar <jordi.ballester@eficent.com> # - Jordi Ballester Alomar <jordi.ballester@eficent.com>
# - Lois Rilo Antelo <lois.rilo@eficent.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models from odoo import api, fields, models
@@ -17,7 +18,6 @@ class MrpInventory(models.Model):
# TODO: name to pass to procurements? # TODO: name to pass to procurements?
# TODO: compute procurement_date to pass to the wizard? not needed for # TODO: compute procurement_date to pass to the wizard? not needed for
# PO at least. Check for MO and moves # PO at least. Check for MO and moves
# TODO: show a LT based on the procure method?
mrp_area_id = fields.Many2one( mrp_area_id = fields.Many2one(
comodel_name='mrp.area', string='MRP Area', comodel_name='mrp.area', string='MRP Area',
@@ -41,18 +41,38 @@ class MrpInventory(models.Model):
supply_qty = fields.Float(string='Supply') supply_qty = fields.Float(string='Supply')
initial_on_hand_qty = fields.Float(string='Starting Inventory') initial_on_hand_qty = fields.Float(string='Starting Inventory')
final_on_hand_qty = fields.Float(string='Forecasted Inventory') final_on_hand_qty = fields.Float(string='Forecasted Inventory')
to_procure = fields.Float(string='To procure') to_procure = fields.Float(
string="To procure",
compute="_compute_to_procure",
store=True,
)
running_availability = fields.Float(
string="Planned Availability",
help="Theoretical inventory level if all planned orders"
"were released.",
)
order_release_date = fields.Date( order_release_date = fields.Date(
string="Order Release Date", string="Order Release Date",
compute="_compute_order_release_date", compute="_compute_order_release_date",
store=True, store=True,
) )
planned_order_ids = fields.One2many(
comodel_name="mrp.planned.order",
inverse_name="mrp_inventory_id",
readonly=True,
)
@api.multi @api.multi
def _compute_uom_id(self): def _compute_uom_id(self):
for rec in self: for rec in self:
rec.uom_id = rec.product_mrp_area_id.product_id.uom_id rec.uom_id = rec.product_mrp_area_id.product_id.uom_id
@api.depends("planned_order_ids", "planned_order_ids.qty_released")
def _compute_to_procure(self):
for rec in self:
rec.to_procure = sum(rec.planned_order_ids.mapped('mrp_qty')) - \
sum(rec.planned_order_ids.mapped('qty_released'))
@api.multi @api.multi
@api.depends('product_mrp_area_id', @api.depends('product_mrp_area_id',
'product_mrp_area_id.main_supplierinfo_id', 'product_mrp_area_id.main_supplierinfo_id',
@@ -61,12 +81,7 @@ class MrpInventory(models.Model):
def _compute_order_release_date(self): def _compute_order_release_date(self):
today = date.today() today = date.today()
for rec in self.filtered(lambda r: r.date): for rec in self.filtered(lambda r: r.date):
delay = 0 delay = rec.product_mrp_area_id.mrp_lead_time
if rec.product_mrp_area_id.supply_method == 'buy':
delay = rec.product_mrp_area_id.main_supplierinfo_id.delay
elif rec.product_mrp_area_id.supply_method == 'manufacture':
delay = rec.product_mrp_area_id.mrp_lead_time
# TODO: 'move' supply method
if delay and rec.mrp_area_id.calendar_id: if delay and rec.mrp_area_id.calendar_id:
dt_date = fields.Datetime.from_string(rec.date) dt_date = fields.Datetime.from_string(rec.date)
order_release_date = rec.mrp_area_id.calendar_id.plan_days( order_release_date = rec.mrp_area_id.calendar_id.plan_days(

View File

@@ -11,56 +11,41 @@ class MrpMove(models.Model):
# TODO: too many indexes... # TODO: too many indexes...
product_mrp_area_id = fields.Many2one(
comodel_name="product.mrp.area",
string="Product", index=True,
)
mrp_area_id = fields.Many2one( mrp_area_id = fields.Many2one(
comodel_name='mrp.area', comodel_name="mrp.area",
related='product_mrp_area_id.mrp_area_id', related="product_mrp_area_id.mrp_area_id",
string='MRP Area', string="MRP Area",
store=True, store=True,
index=True, index=True,
) )
product_id = fields.Many2one(
comodel_name='product.product',
related='product_mrp_area_id.product_id',
store=True,
)
current_date = fields.Date(string='Current Date') current_date = fields.Date(string='Current Date')
current_qty = fields.Float(string='Current Qty') current_qty = fields.Float(string='Current Qty')
# TODO: cancel is not needed I think...
mrp_action = fields.Selection(
selection=[('mo', 'Manufacturing Order'),
('po', 'Purchase Order'),
('cancel', 'Cancel'),
('none', 'None')],
string='Action',
)
mrp_action_date = fields.Date(string='MRP Action Date')
mrp_date = fields.Date(string='MRP Date') mrp_date = fields.Date(string='MRP Date')
mrp_move_down_ids = fields.Many2many( planned_order_up_ids = fields.Many2many(
comodel_name='mrp.move', comodel_name="mrp.planned.order",
relation='mrp_move_rel', relation="mrp_move_planned_order_rel",
column1='move_up_id', column1="move_down_id",
column2='move_down_id', column2="order_id",
string='MRP Move DOWN', string="Planned Orders UP",
)
mrp_move_up_ids = fields.Many2many(
comodel_name='mrp.move',
relation='mrp_move_rel',
column1='move_down_id',
column2='move_up_id',
string='MRP Move UP',
)
mrp_minimum_stock = fields.Float(
string='Minimum Stock',
related='product_mrp_area_id.mrp_minimum_stock',
) )
mrp_order_number = fields.Char(string='Order Number') mrp_order_number = fields.Char(string='Order Number')
# TODO: replace by a char origin?
mrp_origin = fields.Selection( mrp_origin = fields.Selection(
selection=[('mo', 'Manufacturing Order'), selection=[('mo', 'Manufacturing Order'),
('po', 'Purchase Order'), ('po', 'Purchase Order'),
('mv', 'Move'), ('mv', 'Move'),
('fc', 'Forecast'), ('mrp', 'MRP')], ('fc', 'Forecast'),
('mrp', 'MRP')],
string='Origin') string='Origin')
mrp_processed = fields.Boolean(string='Processed')
product_mrp_area_id = fields.Many2one(
comodel_name='product.mrp.area',
string='Product', index=True,
)
mrp_qty = fields.Float(string='MRP Quantity') mrp_qty = fields.Float(string='MRP Quantity')
mrp_type = fields.Selection( mrp_type = fields.Selection(
selection=[('s', 'Supply'), ('d', 'Demand')], selection=[('s', 'Supply'), ('d', 'Demand')],
@@ -68,12 +53,8 @@ class MrpMove(models.Model):
) )
name = fields.Char(string='Description') name = fields.Char(string='Description')
parent_product_id = fields.Many2one( parent_product_id = fields.Many2one(
comodel_name='product.product', comodel_name="product.product",
string='Parent Product', index=True, string="Parent Product", index=True,
)
product_id = fields.Many2one(
comodel_name='product.product',
string='Product', index=True,
) )
production_id = fields.Many2one( production_id = fields.Many2one(
comodel_name='mrp.production', comodel_name='mrp.production',
@@ -87,7 +68,6 @@ class MrpMove(models.Model):
comodel_name='purchase.order', comodel_name='purchase.order',
string='Purchase Order', index=True, string='Purchase Order', index=True,
) )
running_availability = fields.Float(string='Running Availability')
state = fields.Selection( state = fields.Selection(
selection=[('draft', 'Draft'), selection=[('draft', 'Draft'),
('assigned', 'Assigned'), ('assigned', 'Assigned'),

View File

@@ -0,0 +1,60 @@
# Copyright 2019 Eficent Business and IT Consulting Services S.L.
# - Lois Rilo Antelo <lois.rilo@eficent.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields
class MrpPlannedOrder(models.Model):
_name = "mrp.planned.order"
_description = "Planned Order"
_order = "due_date, id"
name = fields.Char(string="Description")
product_mrp_area_id = fields.Many2one(
comodel_name="product.mrp.area",
string="Product",
index=True,
)
mrp_area_id = fields.Many2one(
comodel_name="mrp.area",
related="product_mrp_area_id.mrp_area_id",
string="MRP Area",
store=True,
index=True,
)
product_id = fields.Many2one(
comodel_name="product.product",
related="product_mrp_area_id.product_id",
store=True,
)
order_release_date = fields.Date(
string="Release Date",
help="Order release date planned by MRP.",
)
due_date = fields.Date(
string="Due Date",
help="Date in which the supply must have been completed.",
)
qty_released = fields.Float()
fixed = fields.Boolean()
mrp_qty = fields.Float(string="Quantity")
mrp_move_down_ids = fields.Many2many(
comodel_name="mrp.move",
relation="mrp_move_planned_order_rel",
column1="order_id",
column2="move_down_id",
string="MRP Move DOWN",
)
mrp_action = fields.Selection(
selection=[("manufacture", "Manufacturing Order"),
("buy", "Purchase Order"),
("move", "Transfer"),
("none", "None")],
string="Action",
)
mrp_inventory_id = fields.Many2one(
string="Associated MRP Inventory",
comodel_name="mrp.inventory",
ondelete="set null",
)

View File

@@ -1,6 +1,9 @@
# Copyright 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com> # 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.
# - Jordi Ballester Alomar <jordi.ballester@eficent.com>
# - Lois Rilo Antelo <lois.rilo@eficent.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from math import ceil from math import ceil
from odoo import api, fields, models from odoo import api, fields, models
@@ -36,7 +39,7 @@ class ProductMRPArea(models.Model):
mrp_minimum_order_qty = fields.Float( mrp_minimum_order_qty = fields.Float(
string='Minimum Order Qty', default=0.0, string='Minimum Order Qty', default=0.0,
) )
mrp_minimum_stock = fields.Float(string='Minimum Stock') mrp_minimum_stock = fields.Float(string="Safety Stock")
mrp_nbr_days = fields.Integer( mrp_nbr_days = fields.Integer(
string='Nbr. Days', default=0, string='Nbr. Days', default=0,
help="Number of days to group demand for this product during the " help="Number of days to group demand for this product during the "
@@ -51,7 +54,7 @@ class ProductMRPArea(models.Model):
) )
mrp_lead_time = fields.Float( mrp_lead_time = fields.Float(
string='Lead Time', string='Lead Time',
related='product_id.produce_delay', compute='_compute_mrp_lead_time',
) )
main_supplier_id = fields.Many2one( main_supplier_id = fields.Many2one(
comodel_name='res.partner', string='Main Supplier', comodel_name='res.partner', string='Main Supplier',
@@ -71,13 +74,20 @@ class ProductMRPArea(models.Model):
compute='_compute_supply_method', compute='_compute_supply_method',
) )
qty_available = fields.Float('Quantity Available', qty_available = fields.Float(
compute='_compute_qty_available') string="Quantity Available",
compute="_compute_qty_available",
)
mrp_move_ids = fields.One2many( mrp_move_ids = fields.One2many(
comodel_name='mrp.move', comodel_name='mrp.move',
inverse_name='product_mrp_area_id', inverse_name='product_mrp_area_id',
readonly=True, readonly=True,
) )
planned_order_ids = fields.One2many(
comodel_name="mrp.planned.order",
inverse_name="product_mrp_area_id",
readonly=True,
)
_sql_constraints = [ _sql_constraints = [
('product_mrp_area_uniq', 'unique(product_id, mrp_area_id)', ('product_mrp_area_uniq', 'unique(product_id, mrp_area_id)',
'The product/MRP Area parameters combination must be unique.'), 'The product/MRP Area parameters combination must be unique.'),
@@ -89,10 +99,21 @@ class ProductMRPArea(models.Model):
area.mrp_area_id.name, area.mrp_area_id.name,
area.product_id.display_name)) for area in self] area.product_id.display_name)) for area in self]
@api.multi
def _compute_mrp_lead_time(self):
produced = self.filtered(lambda r: r.supply_method == "manufacture")
purchased = self.filtered(lambda r: r.supply_method == "buy")
for rec in produced:
rec.mrp_lead_time = rec.product_id.produce_delay
for rec in purchased:
rec.mrp_lead_time = rec.main_supplierinfo_id.delay
# TODO: 'move' supply method.
for rec in (self - produced - purchased):
rec.mrp_lead_time = 0
@api.multi @api.multi
def _compute_qty_available(self): def _compute_qty_available(self):
for rec in self: for rec in self:
# TODO: move mrp_qty_available computation, maybe unreserved??
rec.qty_available = rec.product_id.with_context( rec.qty_available = rec.product_id.with_context(
{'location': rec.mrp_area_id.location_id.id}).qty_available {'location': rec.mrp_area_id.location_id.id}).qty_available

View File

@@ -8,9 +8,9 @@ and explodes this down to the lowest level.
Key Features Key Features
------------ ------------
* MRP parameters at product variant level. * MRP parameters set by product variant MRP area pairs.
* Integration with `Stock Demand Estimates <https://github.com/OCA/stock-logistics-warehouse/tree/11.0/stock_demand_estimate>`_ system. * Integration with `Stock Demand Estimates <https://github.com/OCA/stock-logistics-warehouse/tree/11.0/stock_demand_estimate>`_ system.
* Cron job to calculate the MRP demand. * Cron job to calculate the MRP demand.
* Manually calculate the MRP demand. * Manually calculate the MRP demand.
* Confirm the calculated MRP demand and create PO's, or MO's. * Confirm the calculated MRP demand and create PO's, or MO's.
* Able to see the products for which action is needed. * Able to see the products for which action is needed throught Planned Orders.

View File

@@ -1,3 +1,9 @@
11.0.3.0.0 (2019-05-22)
~~~~~~~~~~~~~~~~~~~~~~~
* [REW/IMP] Rework to include Planned Orders.
(`#365 <https://github.com/OCA/manufacture/pull/365>`_):
11.0.2.2.0 (2019-05-02) 11.0.2.2.0 (2019-05-02)
~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -7,3 +7,5 @@ access_mrp_area_user,mrp.area user,model_mrp_area,mrp.group_mrp_user,1,0,0,0
access_mrp_area_manager,mrp.area manager,model_mrp_area,mrp.group_mrp_manager,1,1,1,1 access_mrp_area_manager,mrp.area manager,model_mrp_area,mrp.group_mrp_manager,1,1,1,1
access_product_mrp_area_user,product.mrp.area user,model_product_mrp_area,mrp.group_mrp_user,1,1,1,0 access_product_mrp_area_user,product.mrp.area user,model_product_mrp_area,mrp.group_mrp_user,1,1,1,0
access_product_mrp_area_manager,product.mrp.area manager,model_product_mrp_area,mrp.group_mrp_manager,1,1,1,1 access_product_mrp_area_manager,product.mrp.area manager,model_product_mrp_area,mrp.group_mrp_manager,1,1,1,1
access_mrp_planned_order_user,mrp.planned.order user,model_mrp_planned_order,mrp.group_mrp_user,1,0,0,0
access_mrp_planned_order_manager,mrp.planned.order manager,model_mrp_planned_order,mrp.group_mrp_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
7 access_mrp_area_manager mrp.area manager model_mrp_area mrp.group_mrp_manager 1 1 1 1
8 access_product_mrp_area_user product.mrp.area user model_product_mrp_area mrp.group_mrp_user 1 1 1 0
9 access_product_mrp_area_manager product.mrp.area manager model_product_mrp_area mrp.group_mrp_manager 1 1 1 1
10 access_mrp_planned_order_user mrp.planned.order user model_mrp_planned_order mrp.group_mrp_user 1 0 0 0
11 access_mrp_planned_order_manager mrp.planned.order manager model_mrp_planned_order mrp.group_mrp_manager 1 1 1 1

View File

@@ -376,51 +376,52 @@ and explodes this down to the lowest level.</p>
<div class="section" id="key-features"> <div class="section" id="key-features">
<h1>Key Features</h1> <h1>Key Features</h1>
<ul class="simple"> <ul class="simple">
<li>MRP parameters at product variant level.</li> <li>MRP parameters set by product variant MRP area pairs.</li>
<li>Integration with <a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/tree/11.0/stock_demand_estimate">Stock Demand Estimates</a> system.</li> <li>Integration with <a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/tree/11.0/stock_demand_estimate">Stock Demand Estimates</a> system.</li>
<li>Cron job to calculate the MRP demand.</li> <li>Cron job to calculate the MRP demand.</li>
<li>Manually calculate the MRP demand.</li> <li>Manually calculate the MRP demand.</li>
<li>Confirm the calculated MRP demand and create POs, or MOs.</li> <li>Confirm the calculated MRP demand and create POs, or MOs.</li>
<li>Able to see the products for which action is needed.</li> <li>Able to see the products for which action is needed throught Planned Orders.</li>
</ul> </ul>
<p><strong>Table of contents</strong></p> <p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents"> <div class="contents local topic" id="contents">
<ul class="simple"> <ul class="simple">
<li><a class="reference internal" href="#configuration" id="id12">Configuration</a><ul> <li><a class="reference internal" href="#configuration" id="id14">Configuration</a><ul>
<li><a class="reference internal" href="#mrp-areas" id="id13">MRP Areas</a></li> <li><a class="reference internal" href="#mrp-areas" id="id15">MRP Areas</a></li>
<li><a class="reference internal" href="#product-mrp-area-parameters" id="id14">Product MRP Area Parameters</a></li> <li><a class="reference internal" href="#product-mrp-area-parameters" id="id16">Product MRP Area Parameters</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#usage" id="id15">Usage</a></li> <li><a class="reference internal" href="#usage" id="id17">Usage</a></li>
<li><a class="reference internal" href="#changelog" id="id16">Changelog</a><ul> <li><a class="reference internal" href="#changelog" id="id18">Changelog</a><ul>
<li><a class="reference internal" href="#id1" id="id17">11.0.2.2.0 (2019-05-02)</a></li> <li><a class="reference internal" href="#id1" id="id19">11.0.3.0.0 (2019-05-22)</a></li>
<li><a class="reference internal" href="#id3" id="id18">11.0.2.1.0 (2019-04-02)</a></li> <li><a class="reference internal" href="#id3" id="id20">11.0.2.2.0 (2019-05-02)</a></li>
<li><a class="reference internal" href="#id5" id="id19">11.0.2.0.0 (2018-11-20)</a></li> <li><a class="reference internal" href="#id5" id="id21">11.0.2.1.0 (2019-04-02)</a></li>
<li><a class="reference internal" href="#id7" id="id20">11.0.1.1.0 (2018-08-30)</a></li> <li><a class="reference internal" href="#id7" id="id22">11.0.2.0.0 (2018-11-20)</a></li>
<li><a class="reference internal" href="#id9" id="id21">11.0.1.0.1 (2018-08-03)</a></li> <li><a class="reference internal" href="#id9" id="id23">11.0.1.1.0 (2018-08-30)</a></li>
<li><a class="reference internal" href="#id11" id="id22">11.0.1.0.0 (2018-07-09)</a></li> <li><a class="reference internal" href="#id11" id="id24">11.0.1.0.1 (2018-08-03)</a></li>
<li><a class="reference internal" href="#id13" id="id25">11.0.1.0.0 (2018-07-09)</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#bug-tracker" id="id23">Bug Tracker</a></li> <li><a class="reference internal" href="#bug-tracker" id="id26">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id24">Credits</a><ul> <li><a class="reference internal" href="#credits" id="id27">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id25">Authors</a></li> <li><a class="reference internal" href="#authors" id="id28">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id26">Contributors</a></li> <li><a class="reference internal" href="#contributors" id="id29">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id27">Maintainers</a></li> <li><a class="reference internal" href="#maintainers" id="id30">Maintainers</a></li>
</ul> </ul>
</li> </li>
</ul> </ul>
</div> </div>
<div class="section" id="configuration"> <div class="section" id="configuration">
<h2><a class="toc-backref" href="#id12">Configuration</a></h2> <h2><a class="toc-backref" href="#id14">Configuration</a></h2>
<div class="section" id="mrp-areas"> <div class="section" id="mrp-areas">
<h3><a class="toc-backref" href="#id13">MRP Areas</a></h3> <h3><a class="toc-backref" href="#id15">MRP Areas</a></h3>
<ul class="simple"> <ul class="simple">
<li>Go to <em>Manufacturing &gt; Configuration &gt; MRP Areas</em> and define or edit <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> any existing area. You can specify the working hours for every area.</li>
</ul> </ul>
</div> </div>
<div class="section" id="product-mrp-area-parameters"> <div class="section" id="product-mrp-area-parameters">
<h3><a class="toc-backref" href="#id14">Product MRP Area Parameters</a></h3> <h3><a class="toc-backref" href="#id16">Product MRP Area Parameters</a></h3>
<ul class="simple"> <ul class="simple">
<li>Go to <em>Manufacturing &gt; Master Data &gt; Product MRP Area Parameters</em> and set <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> the MRP parameters for a given product and area.</li>
@@ -428,7 +429,7 @@ the MRP parameters for a given product and area.</li>
</div> </div>
</div> </div>
<div class="section" id="usage"> <div class="section" id="usage">
<h2><a class="toc-backref" href="#id15">Usage</a></h2> <h2><a class="toc-backref" href="#id17">Usage</a></h2>
<p>To manually run the MRP scheduler:</p> <p>To manually run the MRP scheduler:</p>
<ol class="arabic simple"> <ol class="arabic simple">
<li>Go to <em>Manufacturing &gt; Operations &gt; Run MRP Multi Level</em>.</li> <li>Go to <em>Manufacturing &gt; Operations &gt; Run MRP Multi Level</em>.</li>
@@ -444,24 +445,31 @@ hand side gears in any record.</li>
</ol> </ol>
</div> </div>
<div class="section" id="changelog"> <div class="section" id="changelog">
<h2><a class="toc-backref" href="#id16">Changelog</a></h2> <h2><a class="toc-backref" href="#id18">Changelog</a></h2>
<div class="section" id="id1"> <div class="section" id="id1">
<h3><a class="toc-backref" href="#id17">11.0.2.2.0 (2019-05-02)</a></h3> <h3><a class="toc-backref" href="#id19">11.0.3.0.0 (2019-05-22)</a></h3>
<ul class="simple">
<li>[REW/IMP] Rework to include Planned Orders.
(<a class="reference external" href="https://github.com/OCA/manufacture/pull/365">#365</a>):</li>
</ul>
</div>
<div class="section" id="id3">
<h3><a class="toc-backref" href="#id20">11.0.2.2.0 (2019-05-02)</a></h3>
<ul class="simple"> <ul class="simple">
<li>[IMP] Able to run MRP only for selected areas. <li>[IMP] Able to run MRP only for selected areas.
(<a class="reference external" href="https://github.com/OCA/manufacture/pull/360">#360</a>):</li> (<a class="reference external" href="https://github.com/OCA/manufacture/pull/360">#360</a>):</li>
</ul> </ul>
</div> </div>
<div class="section" id="id3"> <div class="section" id="id5">
<h3><a class="toc-backref" href="#id18">11.0.2.1.0 (2019-04-02)</a></h3> <h3><a class="toc-backref" href="#id21">11.0.2.1.0 (2019-04-02)</a></h3>
<ul class="simple"> <ul class="simple">
<li>[IMP] Implement <em>Nbr. Days</em> functionality to be able to group demand when <li>[IMP] Implement <em>Nbr. Days</em> functionality to be able to group demand when
generating supply proposals. generating supply proposals.
(<a class="reference external" href="https://github.com/OCA/manufacture/pull/345">#345</a>):</li> (<a class="reference external" href="https://github.com/OCA/manufacture/pull/345">#345</a>):</li>
</ul> </ul>
</div> </div>
<div class="section" id="id5"> <div class="section" id="id7">
<h3><a class="toc-backref" href="#id19">11.0.2.0.0 (2018-11-20)</a></h3> <h3><a class="toc-backref" href="#id22">11.0.2.0.0 (2018-11-20)</a></h3>
<ul class="simple"> <ul class="simple">
<li>[REW] Refactor MRP Area. <li>[REW] Refactor MRP Area.
(<a class="reference external" href="https://github.com/OCA/manufacture/pull/322">#322</a>):<ul> (<a class="reference external" href="https://github.com/OCA/manufacture/pull/322">#322</a>):<ul>
@@ -473,15 +481,15 @@ different areas.</li>
</li> </li>
</ul> </ul>
</div> </div>
<div class="section" id="id7"> <div class="section" id="id9">
<h3><a class="toc-backref" href="#id20">11.0.1.1.0 (2018-08-30)</a></h3> <h3><a class="toc-backref" href="#id23">11.0.1.1.0 (2018-08-30)</a></h3>
<ul class="simple"> <ul class="simple">
<li>[FIX] Consider <em>Qty Multiple</em> on product to propose the quantity to procure. <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> (<a class="reference external" href="https://github.com/OCA/manufacture/pull/297">#297</a>)</li>
</ul> </ul>
</div> </div>
<div class="section" id="id9"> <div class="section" id="id11">
<h3><a class="toc-backref" href="#id21">11.0.1.0.1 (2018-08-03)</a></h3> <h3><a class="toc-backref" href="#id24">11.0.1.0.1 (2018-08-03)</a></h3>
<ul class="simple"> <ul class="simple">
<li>[FIX] User and system locales doesnt break MRP calculation. <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> (<a class="reference external" href="https://github.com/OCA/manufacture/pull/290">#290</a>)</li>
@@ -490,15 +498,15 @@ as a related on MRP Areas.
(<a class="reference external" href="https://github.com/OCA/manufacture/pull/290">#290</a>)</li> (<a class="reference external" href="https://github.com/OCA/manufacture/pull/290">#290</a>)</li>
</ul> </ul>
</div> </div>
<div class="section" id="id11"> <div class="section" id="id13">
<h3><a class="toc-backref" href="#id22">11.0.1.0.0 (2018-07-09)</a></h3> <h3><a class="toc-backref" href="#id25">11.0.1.0.0 (2018-07-09)</a></h3>
<ul class="simple"> <ul class="simple">
<li>Start of the history.</li> <li>Start of the history.</li>
</ul> </ul>
</div> </div>
</div> </div>
<div class="section" id="bug-tracker"> <div class="section" id="bug-tracker">
<h2><a class="toc-backref" href="#id23">Bug Tracker</a></h2> <h2><a class="toc-backref" href="#id26">Bug Tracker</a></h2>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/manufacture/issues">GitHub Issues</a>. <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. 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 If you spotted it first, help us smashing it by providing a detailed and welcomed
@@ -506,16 +514,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> <p>Do not contact contributors directly about support or help with technical issues.</p>
</div> </div>
<div class="section" id="credits"> <div class="section" id="credits">
<h2><a class="toc-backref" href="#id24">Credits</a></h2> <h2><a class="toc-backref" href="#id27">Credits</a></h2>
<div class="section" id="authors"> <div class="section" id="authors">
<h3><a class="toc-backref" href="#id25">Authors</a></h3> <h3><a class="toc-backref" href="#id28">Authors</a></h3>
<ul class="simple"> <ul class="simple">
<li>Ucamco</li> <li>Ucamco</li>
<li>Eficent</li> <li>Eficent</li>
</ul> </ul>
</div> </div>
<div class="section" id="contributors"> <div class="section" id="contributors">
<h3><a class="toc-backref" href="#id26">Contributors</a></h3> <h3><a class="toc-backref" href="#id29">Contributors</a></h3>
<ul class="simple"> <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>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> <li>Jordi Ballester &lt;<a class="reference external" href="mailto:jordi.ballester&#64;eficent.com">jordi.ballester&#64;eficent.com</a>&gt;</li>
@@ -523,7 +531,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
</ul> </ul>
</div> </div>
<div class="section" id="maintainers"> <div class="section" id="maintainers">
<h3><a class="toc-backref" href="#id27">Maintainers</a></h3> <h3><a class="toc-backref" href="#id30">Maintainers</a></h3>
<p>This module is maintained by the OCA.</p> <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> <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 <p>OCA, or the Odoo Community Association, is a nonprofit organization whose

View File

@@ -28,6 +28,7 @@ class TestMrpMultiLevel(SavepointCase):
cls.mrp_inventory_procure_wiz = cls.env['mrp.inventory.procure'] cls.mrp_inventory_procure_wiz = cls.env['mrp.inventory.procure']
cls.mrp_inventory_obj = cls.env['mrp.inventory'] cls.mrp_inventory_obj = cls.env['mrp.inventory']
cls.mrp_move_obj = cls.env['mrp.move'] cls.mrp_move_obj = cls.env['mrp.move']
cls.planned_order_obj = cls.env['mrp.planned.order']
cls.fp_1 = cls.env.ref('mrp_multi_level.product_product_fp_1') cls.fp_1 = cls.env.ref('mrp_multi_level.product_product_fp_1')
cls.fp_2 = cls.env.ref('mrp_multi_level.product_product_fp_2') cls.fp_2 = cls.env.ref('mrp_multi_level.product_product_fp_2')
@@ -322,38 +323,36 @@ class TestMrpMultiLevel(SavepointCase):
"""Tests for mrp moves generated.""" """Tests for mrp moves generated."""
moves = self.mrp_move_obj.search([ moves = self.mrp_move_obj.search([
('product_id', '=', self.pp_1.id), ('product_id', '=', self.pp_1.id),
('mrp_action', '=', 'none'),
]) ])
self.assertEqual(len(moves), 3) self.assertEqual(len(moves), 3)
self.assertNotIn('s', moves.mapped('mrp_type')) self.assertNotIn('s', moves.mapped('mrp_type'))
for move in moves: for move in moves:
self.assertTrue(move.mrp_move_up_ids) self.assertTrue(move.planned_order_up_ids)
if move.mrp_move_up_ids.product_mrp_area_id.product_id == \ if move.planned_order_up_ids.product_mrp_area_id.product_id == \
self.fp_1: self.fp_1:
# Demand coming from FP-1 # Demand coming from FP-1
self.assertEqual(move.mrp_move_up_ids.mrp_action, 'mo') self.assertEqual(
move.planned_order_up_ids.mrp_action, "manufacture")
self.assertEqual(move.mrp_qty, -200.0) self.assertEqual(move.mrp_qty, -200.0)
elif move.mrp_move_up_ids.product_mrp_area_id.product_id == \ elif move.planned_order_up_ids.product_mrp_area_id.product_id == \
self.sf_1: self.sf_1:
# Demand coming from FP-2 -> SF-1 # Demand coming from FP-2 -> SF-1
self.assertEqual(move.mrp_move_up_ids.mrp_action, 'mo') self.assertEqual(
move.planned_order_up_ids.mrp_action, "manufacture")
if move.mrp_date == self.date_5: if move.mrp_date == self.date_5:
self.assertEqual(move.mrp_qty, -90.0) self.assertEqual(move.mrp_qty, -90.0)
elif move.mrp_date == self.date_8: elif move.mrp_date == self.date_8:
self.assertEqual(move.mrp_qty, -72.0) self.assertEqual(move.mrp_qty, -72.0)
# Check actions: # Check actions:
moves = self.mrp_move_obj.search([ planned_orders = self.planned_order_obj.search([
('product_id', '=', self.pp_1.id), ('product_id', '=', self.pp_1.id),
('mrp_action', '!=', 'none'),
]) ])
self.assertEqual(len(moves), 3) self.assertEqual(len(planned_orders), 3)
for move in moves: for plan in planned_orders:
self.assertEqual(move.mrp_action, 'po') self.assertEqual(plan.mrp_action, 'buy')
self.assertEqual(move.mrp_type, 's')
# Check PP-2 PO being accounted: # Check PP-2 PO being accounted:
po_move = self.mrp_move_obj.search([ po_move = self.mrp_move_obj.search([
('product_id', '=', self.pp_2.id), ('product_id', '=', self.pp_2.id),
('mrp_action', '=', 'none'),
('mrp_type', '=', 's'), ('mrp_type', '=', 's'),
]) ])
self.assertEqual(len(po_move), 1) self.assertEqual(len(po_move), 1)
@@ -452,16 +451,15 @@ class TestMrpMultiLevel(SavepointCase):
self.assertEqual(pp_2_line_4.demand_qty, 48.0) self.assertEqual(pp_2_line_4.demand_qty, 48.0)
self.assertEqual(pp_2_line_4.to_procure, 48.0) self.assertEqual(pp_2_line_4.to_procure, 48.0)
def test_05_moves_extra_info(self): def test_05_planned_availability(self):
"""Test running availability and actions counters computation on """Test planned availability computation."""
mrp moves."""
# Running availability for PP-1: # Running availability for PP-1:
moves = self.mrp_move_obj.search([ invs = self.mrp_inventory_obj.search([
('product_id', '=', self.pp_1.id)], ('product_id', '=', self.pp_1.id)],
order='mrp_date, mrp_type desc, id') order='date')
self.assertEqual(len(moves), 6) self.assertEqual(len(invs), 2)
expected = [200.0, 290.0, 90.0, 0.0, 72.0, 0.0] expected = [0.0, 0.0] # No grouping, lot size nor safety stock.
self.assertEqual(moves.mapped('running_availability'), expected) self.assertEqual(invs.mapped('running_availability'), expected)
def test_06_demand_estimates(self): def test_06_demand_estimates(self):
"""Tests demand estimates integration.""" """Tests demand estimates integration."""
@@ -480,8 +478,14 @@ class TestMrpMultiLevel(SavepointCase):
self.assertIn(-30.0, quantities) # 210 a week => 30.0 dayly: self.assertIn(-30.0, quantities) # 210 a week => 30.0 dayly:
self.assertIn(-40.0, quantities) # 280 a week => 40.0 dayly: self.assertIn(-40.0, quantities) # 280 a week => 40.0 dayly:
self.assertIn(-50.0, quantities) # 350 a week => 50.0 dayly: self.assertIn(-50.0, quantities) # 350 a week => 50.0 dayly:
actions = moves.filtered(lambda m: m.mrp_action == 'po') plans = self.planned_order_obj.search([
self.assertEqual(len(actions), 18) ('product_id', '=', self.prod_test.id),
('mrp_area_id', '=', self.mrp_area.id),
])
action = list(set(plans.mapped("mrp_action")))
self.assertEqual(len(action), 1)
self.assertEqual(action[0], "buy")
self.assertEqual(len(plans), 18)
inventories = self.mrp_inventory_obj.search([ inventories = self.mrp_inventory_obj.search([
('mrp_area_id', '=', self.secondary_area.id)]) ('mrp_area_id', '=', self.secondary_area.id)])
self.assertEqual(len(inventories), 18) self.assertEqual(len(inventories), 18)
@@ -516,13 +520,12 @@ class TestMrpMultiLevel(SavepointCase):
mrp_inv_max = self.mrp_inventory_obj.search([ mrp_inv_max = self.mrp_inventory_obj.search([
('product_mrp_area_id.product_id', '=', self.prod_max.id)]) ('product_mrp_area_id.product_id', '=', self.prod_max.id)])
self.assertEqual(mrp_inv_max.to_procure, 150) self.assertEqual(mrp_inv_max.to_procure, 150)
moves = self.mrp_move_obj.search([ plans = self.planned_order_obj.search([
('product_id', '=', self.prod_max.id), ('product_id', '=', self.prod_max.id),
('mrp_action', '!=', 'none'),
]) ])
self.assertEqual(len(moves), 2) self.assertEqual(len(plans), 2)
self.assertIn(100.0, moves.mapped('mrp_qty')) self.assertIn(100.0, plans.mapped('mrp_qty'))
self.assertIn(50.0, moves.mapped('mrp_qty')) self.assertIn(50.0, plans.mapped('mrp_qty'))
# quantity multiple: # quantity multiple:
mrp_inv_multiple = self.mrp_inventory_obj.search([ mrp_inv_multiple = self.mrp_inventory_obj.search([
('product_mrp_area_id.product_id', '=', self.prod_multiple.id)]) ('product_mrp_area_id.product_id', '=', self.prod_multiple.id)])
@@ -538,13 +541,16 @@ class TestMrpMultiLevel(SavepointCase):
('product_id', '=', self.prod_test.id), ('product_id', '=', self.prod_test.id),
('mrp_area_id', '=', self.secondary_area.id), ('mrp_area_id', '=', self.secondary_area.id),
]) ])
supply_plans = self.planned_order_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: # 3 weeks - 3 days in the past = 18 days of valid estimates:
moves_from_estimates = moves.filtered(lambda m: m.mrp_type == 'd') moves_from_estimates = moves.filtered(lambda m: m.mrp_type == 'd')
self.assertEqual(len(moves_from_estimates), 18) self.assertEqual(len(moves_from_estimates), 18)
# 18 days of demand / 7 nbr_days = 2.57 => 3 supply moves expected. # 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_plans), 3)
self.assertEqual(len(supply_moves), 3) quantities = supply_plans.mapped('mrp_qty')
quantities = supply_moves.mapped('mrp_qty')
week_1_expected = sum(moves_from_estimates[0:7].mapped('mrp_qty')) week_1_expected = sum(moves_from_estimates[0:7].mapped('mrp_qty'))
self.assertIn(abs(week_1_expected), quantities) self.assertIn(abs(week_1_expected), quantities)
week_2_expected = sum(moves_from_estimates[7:14].mapped('mrp_qty')) week_2_expected = sum(moves_from_estimates[7:14].mapped('mrp_qty'))

View File

@@ -21,17 +21,28 @@
<field name="type">form</field> <field name="type">form</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="MRP Area"> <form string="MRP Area">
<group colspan="4" col="2"> <sheet>
<group> <div class="oe_button_box" name="button_box">
<field name="name"/> <button name="toggle_active" type="object"
<field name="active"/> class="oe_stat_button" icon="fa-archive">
<field name="active" widget="boolean_button"
options='{"terminology": "archive"}'/>
</button>
</div>
<label for="name" class="oe_edit_only"/>
<h1><field name="name"/></h1>
<group colspan="4" col="2">
<group>
<field name="warehouse_id"/>
<field name="location_id"/>
</group>
<group name="settings">
<field name="warehouse_id"/>
<field name="location_id"/>
<field name="calendar_id"/>
</group>
</group> </group>
<group> </sheet>
<field name="warehouse_id"/>
<field name="location_id"/>
<field name="calendar_id"/>
</group>
</group>
</form> </form>
</field> </field>
</record> </record>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<odoo> <odoo>
<record model="ir.ui.view" id="mrp_inventory_form"> <record id="mrp_inventory_form" model="ir.ui.view">
<field name="name">mrp.inventory.form</field> <field name="name">mrp.inventory.form</field>
<field name="model">mrp.inventory</field> <field name="model">mrp.inventory</field>
<field name="type">form</field> <field name="type">form</field>
@@ -29,7 +29,7 @@
</field> </field>
</record> </record>
<record model="ir.ui.view" id="mrp_inventory_tree"> <record id="mrp_inventory_tree" model="ir.ui.view">
<field name="name">mrp.inventory.tree</field> <field name="name">mrp.inventory.tree</field>
<field name="model">mrp.inventory</field> <field name="model">mrp.inventory</field>
<field name="type">tree</field> <field name="type">tree</field>
@@ -49,6 +49,7 @@
name="%(mrp_multi_level.act_mrp_inventory_procure)d" name="%(mrp_multi_level.act_mrp_inventory_procure)d"
icon="fa-cogs" type="action" icon="fa-cogs" type="action"
attrs="{'invisible':[('to_procure','&lt;=',0.0)]}"/> attrs="{'invisible':[('to_procure','&lt;=',0.0)]}"/>
<field name="running_availability"/>
</tree> </tree>
</field> </field>
</record> </record>
@@ -78,7 +79,7 @@
</field> </field>
</record> </record>
<record model="ir.ui.view" id="mrp_inventory_search"> <record id="mrp_inventory_search" model="ir.ui.view">
<field name="name">mrp.inventory.search</field> <field name="name">mrp.inventory.search</field>
<field name="model">mrp.inventory</field> <field name="model">mrp.inventory</field>
<field name="type">search</field> <field name="type">search</field>
@@ -108,7 +109,7 @@
</field> </field>
</record> </record>
<record model="ir.actions.act_window" id="mrp_inventory_action"> <record id="mrp_inventory_action" model="ir.actions.act_window">
<field name="name">MRP Inventory</field> <field name="name">MRP Inventory</field>
<field name="res_model">mrp.inventory</field> <field name="res_model">mrp.inventory</field>
<field name="type">ir.actions.act_window</field> <field name="type">ir.actions.act_window</field>

View File

@@ -49,13 +49,15 @@
<field name="mrp_exclude"/> <field name="mrp_exclude"/>
<field name="mrp_verified"/> <field name="mrp_verified"/>
<field name="mrp_nbr_days"/> <field name="mrp_nbr_days"/>
<field name="mrp_transit_delay"/> <!--hide delays for now-->
<field name="mrp_inspection_delay"/> <field name="mrp_transit_delay" invisible="1"/>
<field name="mrp_inspection_delay" invisible="1"/>
<field name="mrp_minimum_stock"/> <field name="mrp_minimum_stock"/>
<field name="mrp_minimum_order_qty"/> <field name="mrp_minimum_order_qty"/>
<field name="mrp_maximum_order_qty"/> <field name="mrp_maximum_order_qty"/>
<field name="mrp_qty_multiple"/> <field name="mrp_qty_multiple"/>
<field name="supply_method"/> <field name="supply_method"/>
<field name="mrp_lead_time"/>
<field name="main_supplierinfo_id"/> <field name="main_supplierinfo_id"/>
</group> </group>
</group> </group>
@@ -63,7 +65,6 @@
<page name="mrp_moves" string="MRP Moves" groups="base.group_no_one"> <page name="mrp_moves" string="MRP Moves" groups="base.group_no_one">
<field name="mrp_move_ids" nolabel="1"> <field name="mrp_move_ids" nolabel="1">
<tree> <tree>
<field name="mrp_action_date"/>
<field name="mrp_date"/> <field name="mrp_date"/>
<field name="current_date"/> <field name="current_date"/>
<field name="mrp_origin"/> <field name="mrp_origin"/>
@@ -73,12 +74,21 @@
<field name="name"/> <field name="name"/>
<field name="mrp_qty"/> <field name="mrp_qty"/>
<field name="current_qty"/> <field name="current_qty"/>
<field name="running_availability"/>
<field name="mrp_minimum_stock" />
<field name="mrp_action"/>
<field name="mrp_type"/> <field name="mrp_type"/>
<field name="mrp_move_up_ids"/> <field name="planned_order_up_ids"/>
<field name="mrp_processed"/> </tree>
</field>
</page>
<page name="planned_orders" string="Planned Orders" groups="base.group_no_one">
<field name="planned_order_ids" nolabel="1">
<tree>
<field name="order_release_date"/>
<field name="due_date"/>
<field name="name"/>
<field name="mrp_qty"/>
<field name="mrp_action"/>
<field name="qty_released"/>
<field name="fixed"/>
</tree> </tree>
</field> </field>
</page> </page>

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) # (http://www.eficent.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
@@ -17,15 +17,16 @@ class MrpInventoryProcure(models.TransientModel):
) )
@api.model @api.model
def _prepare_item(self, mrp_inventory, qty_override=0.0): def _prepare_item(self, planned_order):
return { return {
'qty': qty_override if qty_override else mrp_inventory.to_procure, 'planned_order_id': planned_order.id,
'uom_id': mrp_inventory.uom_id.id, 'qty': planned_order.mrp_qty - planned_order.qty_released,
'date_planned': mrp_inventory.date, 'uom_id': planned_order.mrp_inventory_id.uom_id.id,
'mrp_inventory_id': mrp_inventory.id, 'date_planned': planned_order.due_date,
'product_id': mrp_inventory.product_mrp_area_id.product_id.id, 'mrp_inventory_id': planned_order.mrp_inventory_id.id,
'warehouse_id': mrp_inventory.mrp_area_id.warehouse_id.id, 'product_id': planned_order.product_id.id,
'location_id': mrp_inventory.mrp_area_id.location_id.id, 'warehouse_id': planned_order.mrp_area_id.warehouse_id.id,
'location_id': planned_order.mrp_area_id.location_id.id,
} }
@api.model @api.model
@@ -56,17 +57,9 @@ class MrpInventoryProcure(models.TransientModel):
assert active_model == 'mrp.inventory', 'Bad context propagation' assert active_model == 'mrp.inventory', 'Bad context propagation'
items = item_obj = self.env['mrp.inventory.procure.item'] items = item_obj = self.env['mrp.inventory.procure.item']
for line in mrp_inventory_obj.browse(mrp_inventory_ids): for line in mrp_inventory_obj.browse(mrp_inventory_ids).mapped(
max_order = line.product_mrp_area_id.mrp_maximum_order_qty 'planned_order_ids'):
qty_to_order = line.to_procure if line.qty_released < line.mrp_qty:
if max_order and max_order < qty_to_order:
# split the procurement in batches:
while qty_to_order > 0.0:
qty = line.product_mrp_area_id._adjust_qty_to_order(
qty_to_order)
items += item_obj.create(self._prepare_item(line, qty))
qty_to_order -= qty
else:
items += item_obj.create(self._prepare_item(line)) items += item_obj.create(self._prepare_item(line))
res['item_ids'] = [(6, 0, items.ids)] res['item_ids'] = [(6, 0, items.ids)]
return res return res
@@ -90,11 +83,9 @@ class MrpInventoryProcure(models.TransientModel):
'INT: ' + str(self.env.user.login), # origin? 'INT: ' + str(self.env.user.login), # origin?
values values
) )
item.mrp_inventory_id.to_procure -= \ item.planned_order_id.qty_released += item.qty
item.uom_id._compute_quantity(
item.qty, item.product_id.uom_id)
except UserError as error: except UserError as error:
errors.append(error.name) errors.append(error.name)
if errors: if errors:
raise UserError('\n'.join(errors)) raise UserError('\n'.join(errors))
return {'type': 'ir.actions.act_window_close'} return {'type': 'ir.actions.act_window_close'}
@@ -117,6 +108,9 @@ class MrpInventoryProcureItem(models.TransientModel):
string='Mrp Inventory', string='Mrp Inventory',
comodel_name='mrp.inventory', comodel_name='mrp.inventory',
) )
planned_order_id = fields.Many2one(
comodel_name='mrp.planned.order',
)
product_id = fields.Many2one( product_id = fields.Many2one(
string='Product', string='Product',
comodel_name='product.product', comodel_name='product.product',

View File

@@ -28,8 +28,7 @@ class MultiLevelMrp(models.TransientModel):
qty_available = 0.0 qty_available = 0.0
product_obj = self.env['product.product'] product_obj = self.env['product.product']
# TODO: move mrp_qty_available computation, maybe unreserved?? # TODO: move mrp_qty_available computation, maybe unreserved??
location_ids = self.env['stock.location'].search( location_ids = product_mrp_area.mrp_area_id._get_locations()
[('id', 'child_of', product_mrp_area.mrp_area_id.location_id.id)])
for location in location_ids: for location in location_ids:
product_l = product_obj.with_context( product_l = product_obj.with_context(
{'location': location.id}).browse( {'location': location.id}).browse(
@@ -63,13 +62,10 @@ class MultiLevelMrp(models.TransientModel):
'current_qty': -daily_qty, 'current_qty': -daily_qty,
'mrp_date': date, 'mrp_date': date,
'current_date': date, 'current_date': date,
'mrp_action': 'none',
'mrp_type': mrp_type, 'mrp_type': mrp_type,
'mrp_processed': False,
'mrp_origin': origin, 'mrp_origin': origin,
'mrp_order_number': None, 'mrp_order_number': None,
'parent_product_id': None, 'parent_product_id': None,
'running_availability': 0.00,
'name': 'Forecast', 'name': 'Forecast',
'state': 'confirmed', 'state': 'confirmed',
} }
@@ -127,39 +123,26 @@ class MultiLevelMrp(models.TransientModel):
'current_qty': product_qty, 'current_qty': product_qty,
'mrp_date': mrp_date, 'mrp_date': mrp_date,
'current_date': move.date_expected, 'current_date': move.date_expected,
'mrp_action': 'none',
'mrp_type': mrp_type, 'mrp_type': mrp_type,
'mrp_processed': False,
'mrp_origin': origin, 'mrp_origin': origin,
'mrp_order_number': order_number, 'mrp_order_number': order_number,
'parent_product_id': parent_product_id, 'parent_product_id': parent_product_id,
'running_availability': 0.00,
'name': order_number, 'name': order_number,
'state': move.state, 'state': move.state,
} }
@api.model @api.model
def _prepare_mrp_move_data_supply( def _prepare_planned_order_data(
self, product_mrp_area, qty, mrp_date_supply, mrp_action_date, self, product_mrp_area, qty, mrp_date_supply,
mrp_action, name): mrp_action_date, name
):
return { return {
'product_id': product_mrp_area.product_id.id,
'product_mrp_area_id': product_mrp_area.id, 'product_mrp_area_id': product_mrp_area.id,
'production_id': None,
'purchase_order_id': None,
'purchase_line_id': None,
'stock_move_id': None,
'mrp_qty': qty, 'mrp_qty': qty,
'current_qty': None, 'due_date': mrp_date_supply,
'mrp_date': mrp_date_supply, 'order_release_date': mrp_action_date,
'mrp_action_date': mrp_action_date, 'mrp_action': product_mrp_area.supply_method,
'current_date': None, 'qty_released': 0.0,
'mrp_action': mrp_action,
'mrp_type': 's',
'mrp_processed': False,
'mrp_origin': None,
'mrp_order_number': None,
'parent_product_id': None,
'name': 'Supply: ' + name, 'name': 'Supply: ' + name,
} }
@@ -184,9 +167,7 @@ class MultiLevelMrp(models.TransientModel):
'current_qty': None, 'current_qty': None,
'mrp_date': mrp_date_demand_2, 'mrp_date': mrp_date_demand_2,
'current_date': None, 'current_date': None,
'mrp_action': 'none',
'mrp_type': 'd', 'mrp_type': 'd',
'mrp_processed': False,
'mrp_origin': 'mrp', 'mrp_origin': 'mrp',
'mrp_order_number': None, 'mrp_order_number': None,
'parent_product_id': bom.product_id.id, 'parent_product_id': bom.product_id.id,
@@ -198,82 +179,104 @@ class MultiLevelMrp(models.TransientModel):
} }
@api.model @api.model
def create_move(self, product_mrp_area_id, mrp_date, mrp_qty, name): def _get_action_and_supply_dates(self, product_mrp_area, mrp_date):
self = self.with_context(auditlog_disabled=True)
values = {}
if not isinstance(mrp_date, date): if not isinstance(mrp_date, date):
mrp_date = fields.Date.from_string(mrp_date) mrp_date = fields.Date.from_string(mrp_date)
if product_mrp_area_id.supply_method == 'buy':
# if product_mrp_area_id.purchase_requisition:
# mrp_action = 'pr'
# else:
mrp_action = 'po'
else:
# TODO: consider 'none'...
mrp_action = 'mo'
if mrp_date < date.today(): if mrp_date < date.today():
mrp_date_supply = date.today() mrp_date_supply = date.today()
else: else:
mrp_date_supply = mrp_date mrp_date_supply = mrp_date
calendar = product_mrp_area_id.mrp_area_id.calendar_id calendar = product_mrp_area.mrp_area_id.calendar_id
if calendar and product_mrp_area_id.mrp_lead_time: if calendar and product_mrp_area.mrp_lead_time:
date_str = fields.Date.to_string(mrp_date) date_str = fields.Date.to_string(mrp_date)
dt = fields.Datetime.from_string(date_str) dt = fields.Datetime.from_string(date_str)
res = calendar.plan_days( res = calendar.plan_days(
-1 * product_mrp_area_id.mrp_lead_time - 1, dt) -1 * product_mrp_area.mrp_lead_time - 1, dt)
mrp_action_date = res.date() mrp_action_date = res.date()
else: else:
mrp_action_date = mrp_date - timedelta( mrp_action_date = mrp_date - timedelta(
days=product_mrp_area_id.mrp_lead_time) days=product_mrp_area.mrp_lead_time)
return mrp_action_date, mrp_date_supply
qty_ordered = 0.00 @api.model
def explode_action(
self, product_mrp_area_id, mrp_action_date, name, qty, action
):
"""Explode requirements."""
mrp_date_demand = mrp_action_date
if mrp_date_demand < date.today():
mrp_date_demand = date.today()
if not product_mrp_area_id.product_id.bom_ids:
return False
bomcount = 0
for bom in product_mrp_area_id.product_id.bom_ids:
if not bom.active or not bom.bom_line_ids:
continue
bomcount += 1
if bomcount != 1:
continue
for bomline in bom.bom_line_ids:
if bomline.product_qty <= 0.00 or \
bomline.product_id.type != 'product':
continue
if self._exclude_from_mrp(
bomline.product_id,
product_mrp_area_id.mrp_area_id):
# Stop explosion.
continue
# TODO: review: mrp_transit_delay, mrp_inspection_delay
mrp_date_demand_2 = mrp_date_demand - timedelta(
days=(product_mrp_area_id.mrp_transit_delay +
product_mrp_area_id.mrp_inspection_delay))
move_data = \
self._prepare_mrp_move_data_bom_explosion(
product_mrp_area_id, bomline, qty,
mrp_date_demand_2,
bom, name)
mrpmove_id2 = self.env['mrp.move'].create(move_data)
if hasattr(action, "mrp_move_down_ids"):
action.mrp_move_down_ids = [(4, mrpmove_id2.id)]
return True
@api.model
def create_action(
self, product_mrp_area_id, mrp_date, mrp_qty, name, values=None,
):
if not values:
values = {}
if not isinstance(mrp_date, date):
mrp_date = fields.Date.from_string(mrp_date)
action_date, date_supply = \
self._get_action_and_supply_dates(product_mrp_area_id, mrp_date)
return self.create_planned_order(
product_mrp_area_id, mrp_qty, name, date_supply,
action_date, values=values)
@api.model
def create_planned_order(
self, product_mrp_area_id, mrp_qty,
name, mrp_date_supply, mrp_action_date, values=None,
):
self = self.with_context(auditlog_disabled=True)
qty_ordered = values.get("qty_ordered", 0.0) if values else 0.0
qty_to_order = mrp_qty qty_to_order = mrp_qty
while qty_ordered < mrp_qty: while qty_ordered < mrp_qty:
qty = product_mrp_area_id._adjust_qty_to_order(qty_to_order) qty = product_mrp_area_id._adjust_qty_to_order(qty_to_order)
qty_to_order -= qty qty_to_order -= qty
move_data = self._prepare_mrp_move_data_supply( order_data = self._prepare_planned_order_data(
product_mrp_area_id, qty, mrp_date_supply, mrp_action_date, product_mrp_area_id, qty, mrp_date_supply, mrp_action_date,
mrp_action, name) name)
mrpmove_id = self.env['mrp.move'].create(move_data) planned_order = self.env['mrp.planned.order'].create(order_data)
qty_ordered = qty_ordered + qty qty_ordered = qty_ordered + qty
if mrp_action == 'mo': if product_mrp_area_id.supply_method == 'manufacture':
mrp_date_demand = mrp_action_date self.explode_action(
if mrp_date_demand < date.today(): product_mrp_area_id, mrp_action_date,
mrp_date_demand = date.today() name, qty, planned_order)
if not product_mrp_area_id.product_id.bom_ids:
continue
bomcount = 0
for bom in product_mrp_area_id.product_id.bom_ids:
if not bom.active or not bom.bom_line_ids:
continue
bomcount += 1
if bomcount != 1:
continue
for bomline in bom.bom_line_ids:
if bomline.product_qty <= 0.00 or \
bomline.product_id.type != 'product':
continue
if self._exclude_from_mrp(
bomline.product_id,
product_mrp_area_id.mrp_area_id):
# Stop explosion.
continue
# TODO: review: mrp_transit_delay, mrp_inspection_delay
mrp_date_demand_2 = mrp_date_demand - timedelta(
days=(product_mrp_area_id.mrp_transit_delay +
product_mrp_area_id.mrp_inspection_delay))
move_data = \
self._prepare_mrp_move_data_bom_explosion(
product_mrp_area_id, bomline, qty,
mrp_date_demand_2,
bom, name)
mrpmove_id2 = self.env['mrp.move'].create(move_data)
mrpmove_id.mrp_move_down_ids = [(4, mrpmove_id2.id)]
values['qty_ordered'] = qty_ordered values['qty_ordered'] = qty_ordered
log_msg = '[%s] %s: qty_ordered = %s' % ( log_msg = '[%s] %s: qty_ordered = %s' % (
product_mrp_area_id.mrp_area_id.name, product_mrp_area_id.mrp_area_id.name,
@@ -292,6 +295,8 @@ class MultiLevelMrp(models.TransientModel):
domain += [('mrp_area_id', 'in', mrp_areas.ids)] domain += [('mrp_area_id', 'in', mrp_areas.ids)]
self.env['mrp.move'].search(domain).unlink() self.env['mrp.move'].search(domain).unlink()
self.env['mrp.inventory'].search(domain).unlink() self.env['mrp.inventory'].search(domain).unlink()
domain += [('fixed', '=', False)]
self.env['mrp.planned.order'].search(domain).unlink()
logger.info('End MRP Cleanup') logger.info('End MRP Cleanup')
return True return True
@@ -356,8 +361,7 @@ class MultiLevelMrp(models.TransientModel):
@api.model @api.model
def _init_mrp_move_from_forecast(self, product_mrp_area): def _init_mrp_move_from_forecast(self, product_mrp_area):
locations = self.env['stock.location'].search( locations = product_mrp_area.mrp_area_id._get_locations()
[('id', 'child_of', product_mrp_area.mrp_area_id.location_id.id)])
today = fields.Date.today() today = fields.Date.today()
estimates = self.env['stock.demand.estimate'].search([ estimates = self.env['stock.demand.estimate'].search([
('product_id', '=', product_mrp_area.product_id.id), ('product_id', '=', product_mrp_area.product_id.id),
@@ -383,8 +387,7 @@ class MultiLevelMrp(models.TransientModel):
# show moves with an action # show moves with an action
@api.model @api.model
def _in_stock_moves_domain(self, product_mrp_area): def _in_stock_moves_domain(self, product_mrp_area):
locations = self.env['stock.location'].search( locations = product_mrp_area.mrp_area_id._get_locations()
[('id', 'child_of', product_mrp_area.mrp_area_id.location_id.id)])
return [ return [
('product_id', '=', product_mrp_area.product_id.id), ('product_id', '=', product_mrp_area.product_id.id),
('state', 'not in', ['done', 'cancel']), ('state', 'not in', ['done', 'cancel']),
@@ -395,8 +398,7 @@ class MultiLevelMrp(models.TransientModel):
@api.model @api.model
def _out_stock_moves_domain(self, product_mrp_area): def _out_stock_moves_domain(self, product_mrp_area):
locations = self.env['stock.location'].search( locations = product_mrp_area.mrp_area_id._get_locations()
[('id', 'child_of', product_mrp_area.mrp_area_id.location_id.id)])
return [ return [
('product_id', '=', product_mrp_area.product_id.id), ('product_id', '=', product_mrp_area.product_id.id),
('state', 'not in', ['done', 'cancel']), ('state', 'not in', ['done', 'cancel']),
@@ -443,21 +445,17 @@ class MultiLevelMrp(models.TransientModel):
'current_qty': poline.product_qty, 'current_qty': poline.product_qty,
'mrp_date': mrp_date, 'mrp_date': mrp_date,
'current_date': poline.date_planned, 'current_date': poline.date_planned,
'mrp_action': 'none',
'mrp_type': 's', 'mrp_type': 's',
'mrp_processed': False,
'mrp_origin': 'po', 'mrp_origin': 'po',
'mrp_order_number': poline.order_id.name, 'mrp_order_number': poline.order_id.name,
'parent_product_id': None, 'parent_product_id': None,
'running_availability': 0.00,
'name': poline.order_id.name, 'name': poline.order_id.name,
'state': poline.order_id.state, 'state': poline.order_id.state,
} }
@api.model @api.model
def _init_mrp_move_from_purchase_order(self, product_mrp_area): def _init_mrp_move_from_purchase_order(self, product_mrp_area):
location_ids = self.env['stock.location'].search( location_ids = product_mrp_area.mrp_area_id._get_locations()
[('id', 'child_of', product_mrp_area.mrp_area_id.location_id.id)])
picking_types = self.env['stock.picking.type'].search( picking_types = self.env['stock.picking.type'].search(
[('default_location_dest_id', 'in', [('default_location_dest_id', 'in',
location_ids.ids)]) location_ids.ids)])
@@ -528,8 +526,9 @@ class MultiLevelMrp(models.TransientModel):
last_qty = 0.00 last_qty = 0.00
onhand = product_mrp_area.qty_available onhand = product_mrp_area.qty_available
grouping_delta = product_mrp_area.mrp_nbr_days grouping_delta = product_mrp_area.mrp_nbr_days
for move in product_mrp_area.mrp_move_ids.filtered( for move in product_mrp_area.mrp_move_ids:
lambda m: m.mrp_action == 'none'): if self._exclude_move(move):
continue
if last_date and ( if last_date and (
fields.Date.from_string(move.mrp_date) fields.Date.from_string(move.mrp_date)
>= last_date + timedelta(days=grouping_delta)) and ( >= last_date + timedelta(days=grouping_delta)) and (
@@ -539,7 +538,7 @@ class MultiLevelMrp(models.TransientModel):
< product_mrp_area.mrp_minimum_stock): < product_mrp_area.mrp_minimum_stock):
name = 'Grouped Demand for %d Days' % grouping_delta name = 'Grouped Demand for %d Days' % grouping_delta
qtytoorder = product_mrp_area.mrp_minimum_stock - last_qty qtytoorder = product_mrp_area.mrp_minimum_stock - last_qty
cm = self.create_move( cm = self.create_action(
product_mrp_area_id=product_mrp_area, product_mrp_area_id=product_mrp_area,
mrp_date=last_date, mrp_date=last_date,
mrp_qty=qtytoorder, mrp_qty=qtytoorder,
@@ -553,7 +552,7 @@ class MultiLevelMrp(models.TransientModel):
product_mrp_area.mrp_minimum_stock or \ product_mrp_area.mrp_minimum_stock or \
(onhand + last_qty) < \ (onhand + last_qty) < \
product_mrp_area.mrp_minimum_stock: product_mrp_area.mrp_minimum_stock:
if not last_date: if not last_date or last_qty == 0.0:
last_date = fields.Date.from_string(move.mrp_date) last_date = fields.Date.from_string(move.mrp_date)
last_qty = move.mrp_qty last_qty = move.mrp_qty
else: else:
@@ -565,7 +564,7 @@ class MultiLevelMrp(models.TransientModel):
if last_date and last_qty != 0.00: if last_date and last_qty != 0.00:
name = 'Grouped Demand for %d Days' % grouping_delta name = 'Grouped Demand for %d Days' % grouping_delta
qtytoorder = product_mrp_area.mrp_minimum_stock - onhand - last_qty qtytoorder = product_mrp_area.mrp_minimum_stock - onhand - last_qty
cm = self.create_move( cm = self.create_action(
product_mrp_area_id=product_mrp_area, mrp_date=last_date, product_mrp_area_id=product_mrp_area, mrp_date=last_date,
mrp_qty=qtytoorder, name=name) mrp_qty=qtytoorder, name=name)
qty_ordered = cm.get('qty_ordered', 0.0) qty_ordered = cm.get('qty_ordered', 0.0)
@@ -573,6 +572,11 @@ class MultiLevelMrp(models.TransientModel):
nbr_create += 1 nbr_create += 1
return nbr_create return nbr_create
@api.model
def _exclude_move(self, move):
"""Improve extensibility being able to exclude special moves."""
return False
@api.model @api.model
def _mrp_calculation(self, mrp_lowest_llc, mrp_areas): def _mrp_calculation(self, mrp_lowest_llc, mrp_areas):
logger.info('Start MRP calculation') logger.info('Start MRP calculation')
@@ -591,25 +595,22 @@ class MultiLevelMrp(models.TransientModel):
for product_mrp_area in product_mrp_areas: for product_mrp_area in product_mrp_areas:
nbr_create = 0 nbr_create = 0
onhand = product_mrp_area.qty_available onhand = product_mrp_area.qty_available
# TODO: unreserved?
if product_mrp_area.mrp_nbr_days == 0: if product_mrp_area.mrp_nbr_days == 0:
# todo: review ordering by date
for move in product_mrp_area.mrp_move_ids: for move in product_mrp_area.mrp_move_ids:
if move.mrp_action == 'none': if self._exclude_move(move):
if (onhand + move.mrp_qty) < \ continue
product_mrp_area.mrp_minimum_stock: qtytoorder = product_mrp_area.mrp_minimum_stock - \
qtytoorder = \ onhand - move.mrp_qty
product_mrp_area.mrp_minimum_stock - \ if qtytoorder > 0.0:
onhand - move.mrp_qty cm = self.create_action(
cm = self.create_move( product_mrp_area_id=product_mrp_area,
product_mrp_area_id=product_mrp_area, mrp_date=move.mrp_date,
mrp_date=move.mrp_date, mrp_qty=qtytoorder, name=move.name)
mrp_qty=qtytoorder, name=move.name) qty_ordered = cm['qty_ordered']
qty_ordered = cm['qty_ordered'] onhand += move.mrp_qty + qty_ordered
onhand += move.mrp_qty + qty_ordered nbr_create += 1
nbr_create += 1 else:
else: onhand += move.mrp_qty
onhand += move.mrp_qty
else: else:
nbr_create = self._init_mrp_move_grouped_demand( nbr_create = self._init_mrp_move_grouped_demand(
nbr_create, product_mrp_area) nbr_create, product_mrp_area)
@@ -618,7 +619,7 @@ class MultiLevelMrp(models.TransientModel):
nbr_create == 0: nbr_create == 0:
qtytoorder = \ qtytoorder = \
product_mrp_area.mrp_minimum_stock - onhand product_mrp_area.mrp_minimum_stock - onhand
cm = self.create_move( cm = self.create_action(
product_mrp_area_id=product_mrp_area, product_mrp_area_id=product_mrp_area,
mrp_date=date.today(), mrp_date=date.today(),
mrp_qty=qtytoorder, mrp_qty=qtytoorder,
@@ -654,35 +655,30 @@ class MultiLevelMrp(models.TransientModel):
FROM mrp_move FROM mrp_move
WHERE product_mrp_area_id = %(mrp_product)s WHERE product_mrp_area_id = %(mrp_product)s
AND mrp_type = 's' AND mrp_type = 's'
AND mrp_action = 'none'
GROUP BY mrp_date GROUP BY mrp_date
""" """
params = {
'mrp_product': product_mrp_area.id,
}
return query, params
@api.model
def _get_planned_order_groups(self, product_mrp_area):
query = """
SELECT due_date, sum(mrp_qty)
FROM mrp_planned_order
WHERE product_mrp_area_id = %(mrp_product)s
GROUP BY due_date
"""
params = { params = {
'mrp_product': product_mrp_area.id 'mrp_product': product_mrp_area.id
} }
return query, params return query, params
@api.model
def _get_supply_action_groups(self, product_mrp_area):
exclude_mrp_actions = ['none', 'cancel']
query = """
SELECT mrp_date, sum(mrp_qty)
FROM mrp_move
WHERE product_mrp_area_id = %(mrp_product)s
AND mrp_qty <> 0.0
AND mrp_type = 's'
AND mrp_action not in %(excluded_mrp_actions)s
GROUP BY mrp_date
"""
params = {
'mrp_product': product_mrp_area.id,
'excluded_mrp_actions': tuple(exclude_mrp_actions,)
}
return query, params
@api.model @api.model
def _init_mrp_inventory(self, product_mrp_area): def _init_mrp_inventory(self, product_mrp_area):
mrp_move_obj = self.env['mrp.move'] mrp_move_obj = self.env['mrp.move']
planned_order_obj = self.env['mrp.planned.order']
# Read Demand # Read Demand
demand_qty_by_date = {} demand_qty_by_date = {}
query, params = self._get_demand_groups(product_mrp_area) query, params = self._get_demand_groups(product_mrp_area)
@@ -695,44 +691,49 @@ class MultiLevelMrp(models.TransientModel):
self.env.cr.execute(query, params) self.env.cr.execute(query, params)
for mrp_date, qty in self.env.cr.fetchall(): for mrp_date, qty in self.env.cr.fetchall():
supply_qty_by_date[mrp_date] = qty supply_qty_by_date[mrp_date] = qty
# Read supply actions # Read planned orders:
# TODO: if we remove cancel take it into account here, planned_qty_by_date = {}
# TODO: as well as mrp_type ('r'). query, params = self._get_planned_order_groups(product_mrp_area)
supply_actions_qty_by_date = {}
query, params = self._get_supply_action_groups(product_mrp_area)
self.env.cr.execute(query, params) self.env.cr.execute(query, params)
for mrp_date, qty in self.env.cr.fetchall(): for mrp_date, qty in self.env.cr.fetchall():
supply_actions_qty_by_date[mrp_date] = qty planned_qty_by_date[mrp_date] = qty
# Dates # Dates
mrp_dates = set(mrp_move_obj.search([ moves_dates = mrp_move_obj.search([
('product_mrp_area_id', '=', product_mrp_area.id)], ('product_mrp_area_id', '=', product_mrp_area.id)],
order='mrp_date').mapped('mrp_date')) order='mrp_date').mapped('mrp_date')
action_dates = planned_order_obj.search([
('product_mrp_area_id', '=', product_mrp_area.id)],
order='due_date').mapped('due_date')
mrp_dates = set(moves_dates + action_dates)
on_hand_qty = product_mrp_area.product_id.with_context( on_hand_qty = product_mrp_area.product_id.with_context(
location=product_mrp_area.mrp_area_id.location_id.id location=product_mrp_area.mrp_area_id.location_id.id
)._product_available()[ )._product_available()[
product_mrp_area.product_id.id]['qty_available'] product_mrp_area.product_id.id]['qty_available']
# TODO: unreserved? running_availability = on_hand_qty
for mdt in sorted(mrp_dates): for mdt in sorted(mrp_dates):
mrp_inventory_data = { mrp_inventory_data = {
'product_mrp_area_id': product_mrp_area.id, 'product_mrp_area_id': product_mrp_area.id,
'date': mdt, 'date': mdt,
} }
demand_qty = 0.0 demand_qty = demand_qty_by_date.get(mdt, 0.0)
supply_qty = 0.0 mrp_inventory_data['demand_qty'] = abs(demand_qty)
if mdt in demand_qty_by_date.keys(): supply_qty = supply_qty_by_date.get(mdt, 0.0)
demand_qty = demand_qty_by_date[mdt] mrp_inventory_data['supply_qty'] = abs(supply_qty)
mrp_inventory_data['demand_qty'] = abs(demand_qty)
if mdt in supply_qty_by_date.keys():
supply_qty = supply_qty_by_date[mdt]
mrp_inventory_data['supply_qty'] = abs(supply_qty)
if mdt in supply_actions_qty_by_date.keys():
mrp_inventory_data['to_procure'] = \
supply_actions_qty_by_date[mdt]
mrp_inventory_data['initial_on_hand_qty'] = on_hand_qty mrp_inventory_data['initial_on_hand_qty'] = on_hand_qty
on_hand_qty += (supply_qty + demand_qty) on_hand_qty += (supply_qty + demand_qty)
mrp_inventory_data['final_on_hand_qty'] = on_hand_qty mrp_inventory_data['final_on_hand_qty'] = on_hand_qty
# Consider that MRP plan is followed exactly:
running_availability += supply_qty \
+ demand_qty + planned_qty_by_date.get(mdt, 0.0)
mrp_inventory_data['running_availability'] = running_availability
self.env['mrp.inventory'].create(mrp_inventory_data) inv_id = self.env['mrp.inventory'].create(mrp_inventory_data)
# attach planned orders to inventory
planned_order_obj.search([
('due_date', '=', mdt),
('product_mrp_area_id', '=', product_mrp_area.id),
]).write(
{'mrp_inventory_id': inv_id.id})
@api.model @api.model
def _mrp_final_process(self, mrp_areas): def _mrp_final_process(self, mrp_areas):
@@ -745,16 +746,6 @@ class MultiLevelMrp(models.TransientModel):
for product_mrp_area in product_mrp_area_ids: for product_mrp_area in product_mrp_area_ids:
# Build the time-phased inventory # Build the time-phased inventory
self._init_mrp_inventory(product_mrp_area) self._init_mrp_inventory(product_mrp_area)
# Complete info on mrp_move (running availability and nbr actions)
qoh = product_mrp_area.qty_available
moves = self.env['mrp.move'].search([
('product_mrp_area_id', '=', product_mrp_area.id)],
order='mrp_date, mrp_type desc, id')
for move in moves:
qoh = qoh + move.mrp_qty
move.running_availability = qoh
logger.info('End MRP final process') logger.info('End MRP final process')
@api.multi @api.multi