mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
[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:
@@ -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)
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|||||||
@@ -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, '
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)])
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
60
mrp_multi_level/models/mrp_planned_order.py
Normal file
60
mrp_multi_level/models/mrp_planned_order.py
Normal 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",
|
||||||
|
)
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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)
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
|
@@ -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 PO’s, or MO’s.</li>
|
<li>Confirm the calculated MRP demand and create PO’s, or MO’s.</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 > Configuration > MRP Areas</em> and define or edit
|
<li>Go to <em>Manufacturing > Configuration > 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 > Master Data > Product MRP Area Parameters</em> and set
|
<li>Go to <em>Manufacturing > Master Data > 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 > Operations > Run MRP Multi Level</em>.</li>
|
<li>Go to <em>Manufacturing > Operations > 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 doesn’t break MRP calculation.
|
<li>[FIX] User and system locales doesn’t 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 <<a class="reference external" href="mailto:wim.audenaert@ucamco.com">wim.audenaert@ucamco.com</a>></li>
|
<li>Wim Audenaert <<a class="reference external" href="mailto:wim.audenaert@ucamco.com">wim.audenaert@ucamco.com</a>></li>
|
||||||
<li>Jordi Ballester <<a class="reference external" href="mailto:jordi.ballester@eficent.com">jordi.ballester@eficent.com</a>></li>
|
<li>Jordi Ballester <<a class="reference external" href="mailto:jordi.ballester@eficent.com">jordi.ballester@eficent.com</a>></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
|
||||||
|
|||||||
@@ -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'))
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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','<=',0.0)]}"/>
|
attrs="{'invisible':[('to_procure','<=',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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user