mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
[11.0][mrp_multi_level] refactor mrp area
This commit is contained in:
committed by
davidborromeo
parent
dc2f428538
commit
f89873ca61
@@ -3,7 +3,7 @@
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
{
|
||||
'name': 'MRP Multi Level',
|
||||
'version': '11.0.1.1.0',
|
||||
'version': '11.0.2.0.0',
|
||||
'development_status': 'Beta',
|
||||
'license': 'AGPL-3',
|
||||
'author': 'Ucamco, '
|
||||
@@ -23,14 +23,14 @@
|
||||
'data': [
|
||||
'security/mrp_multi_level_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/mrp_area_view.xml',
|
||||
'views/product_product_view.xml',
|
||||
'views/product_template_view.xml',
|
||||
'views/stock_location_view.xml',
|
||||
'views/mrp_product_view.xml',
|
||||
'wizards/mrp_inventory_procure_view.xml',
|
||||
'views/mrp_inventory_view.xml',
|
||||
'wizards/mrp_multi_level_view.xml',
|
||||
'views/mrp_area_views.xml',
|
||||
'views/product_product_views.xml',
|
||||
'views/product_template_views.xml',
|
||||
'views/product_mrp_area_views.xml',
|
||||
'views/stock_location_views.xml',
|
||||
'wizards/mrp_inventory_procure_views.xml',
|
||||
'views/mrp_inventory_views.xml',
|
||||
'wizards/mrp_multi_level_views.xml',
|
||||
'views/mrp_menuitem.xml',
|
||||
'data/mrp_multi_level_cron.xml',
|
||||
'data/mrp_area_data.xml',
|
||||
@@ -38,7 +38,8 @@
|
||||
'demo': [
|
||||
'demo/product_category_demo.xml',
|
||||
'demo/product_product_demo.xml',
|
||||
'demo/res_partner_demo.xml',
|
||||
'demo/product_product_demo.xml',
|
||||
'demo/product_mrp_area_demo.xml',
|
||||
'demo/product_supplierinfo_demo.xml',
|
||||
'demo/mrp_bom_demo.xml',
|
||||
'demo/initial_on_hand_demo.xml',
|
||||
|
||||
27
mrp_multi_level/demo/product_mrp_area_demo.xml
Normal file
27
mrp_multi_level/demo/product_mrp_area_demo.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo noupdate="1">
|
||||
<record id="product_mrp_area_fp_1" model="product.mrp.area">
|
||||
<field name="product_id" ref="product_product_fp_1"/>
|
||||
<field name="mrp_area_id" ref="mrp_area_stock_wh0"/>
|
||||
</record>
|
||||
<record id="product_mrp_area_fp_2" model="product.mrp.area">
|
||||
<field name="product_id" ref="product_product_fp_2"/>
|
||||
<field name="mrp_area_id" ref="mrp_area_stock_wh0"/>
|
||||
</record>
|
||||
<record id="product_mrp_area_sf_1" model="product.mrp.area">
|
||||
<field name="product_id" ref="product_product_sf_1"/>
|
||||
<field name="mrp_area_id" ref="mrp_area_stock_wh0"/>
|
||||
</record>
|
||||
<record id="product_mrp_area_sf_2" model="product.mrp.area">
|
||||
<field name="product_id" ref="product_product_sf_2"/>
|
||||
<field name="mrp_area_id" ref="mrp_area_stock_wh0"/>
|
||||
</record>
|
||||
<record id="product_mrp_area_pp_1" model="product.mrp.area">
|
||||
<field name="product_id" ref="product_product_pp_1"/>
|
||||
<field name="mrp_area_id" ref="mrp_area_stock_wh0"/>
|
||||
</record>
|
||||
<record id="product_mrp_area_pp_2" model="product.mrp.area">
|
||||
<field name="product_id" ref="product_product_pp_2"/>
|
||||
<field name="mrp_area_id" ref="mrp_area_stock_wh0"/>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -2,6 +2,6 @@ from . import mrp_area
|
||||
from . import stock_location
|
||||
from . import product_product
|
||||
from . import product_template
|
||||
from . import mrp_product
|
||||
from . import mrp_move
|
||||
from . import mrp_inventory
|
||||
from . import product_mrp_area
|
||||
|
||||
@@ -10,9 +10,9 @@ from datetime import timedelta, date
|
||||
|
||||
class MrpInventory(models.Model):
|
||||
_name = 'mrp.inventory'
|
||||
_order = 'mrp_product_id, date'
|
||||
_order = 'product_mrp_area_id, date'
|
||||
_description = 'MRP inventory projections'
|
||||
_rec_name = 'mrp_product_id'
|
||||
_rec_name = 'product_mrp_area_id'
|
||||
|
||||
# TODO: name to pass to procurements?
|
||||
# TODO: compute procurement_date to pass to the wizard? not needed for
|
||||
@@ -22,10 +22,10 @@ class MrpInventory(models.Model):
|
||||
|
||||
mrp_area_id = fields.Many2one(
|
||||
comodel_name='mrp.area', string='MRP Area',
|
||||
related='mrp_product_id.mrp_area_id', store=True,
|
||||
related='product_mrp_area_id.mrp_area_id', store=True,
|
||||
)
|
||||
mrp_product_id = fields.Many2one(
|
||||
comodel_name='mrp.product', string='Product',
|
||||
product_mrp_area_id = fields.Many2one(
|
||||
comodel_name='product.mrp.area', string='Product',
|
||||
index=True,
|
||||
)
|
||||
uom_id = fields.Many2one(
|
||||
@@ -47,20 +47,21 @@ class MrpInventory(models.Model):
|
||||
@api.multi
|
||||
def _compute_uom_id(self):
|
||||
for rec in self:
|
||||
rec.uom_id = rec.mrp_product_id.product_id.uom_id
|
||||
rec.uom_id = rec.product_mrp_area_id.product_id.uom_id
|
||||
|
||||
@api.multi
|
||||
@api.depends('mrp_product_id', 'mrp_product_id.main_supplierinfo_id',
|
||||
'mrp_product_id.mrp_lead_time',
|
||||
'mrp_product_id.mrp_area_id.calendar_id')
|
||||
@api.depends('product_mrp_area_id',
|
||||
'product_mrp_area_id.main_supplierinfo_id',
|
||||
'product_mrp_area_id.mrp_lead_time',
|
||||
'product_mrp_area_id.mrp_area_id.calendar_id')
|
||||
def _compute_order_release_date(self):
|
||||
today = date.today()
|
||||
for rec in self.filtered(lambda r: r.date):
|
||||
delay = 0
|
||||
if rec.mrp_product_id.supply_method == 'buy':
|
||||
delay = rec.mrp_product_id.main_supplierinfo_id.delay
|
||||
elif rec.mrp_product_id.supply_method == 'manufacture':
|
||||
delay = rec.mrp_product_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:
|
||||
dt_date = fields.Datetime.from_string(rec.date)
|
||||
|
||||
@@ -7,13 +7,16 @@ from odoo import models, fields
|
||||
|
||||
class MrpMove(models.Model):
|
||||
_name = 'mrp.move'
|
||||
_order = 'mrp_product_id, mrp_date, mrp_type desc, id'
|
||||
_order = 'product_mrp_area_id, mrp_date, mrp_type desc, id'
|
||||
|
||||
# TODO: too many indexes...
|
||||
|
||||
mrp_area_id = fields.Many2one(
|
||||
comodel_name='mrp.area',
|
||||
related='product_mrp_area_id.mrp_area_id',
|
||||
string='MRP Area',
|
||||
store=True,
|
||||
index=True,
|
||||
)
|
||||
current_date = fields.Date(string='Current Date')
|
||||
current_qty = fields.Float(string='Current Qty')
|
||||
@@ -43,7 +46,7 @@ class MrpMove(models.Model):
|
||||
)
|
||||
mrp_minimum_stock = fields.Float(
|
||||
string='Minimum Stock',
|
||||
related='product_id.mrp_minimum_stock',
|
||||
related='product_mrp_area_id.mrp_minimum_stock',
|
||||
)
|
||||
mrp_order_number = fields.Char(string='Order Number')
|
||||
# TODO: replace by a char origin?
|
||||
@@ -54,8 +57,8 @@ class MrpMove(models.Model):
|
||||
('fc', 'Forecast'), ('mrp', 'MRP')],
|
||||
string='Origin')
|
||||
mrp_processed = fields.Boolean(string='Processed')
|
||||
mrp_product_id = fields.Many2one(
|
||||
comodel_name='mrp.product',
|
||||
product_mrp_area_id = fields.Many2one(
|
||||
comodel_name='product.mrp.area',
|
||||
string='Product', index=True,
|
||||
)
|
||||
mrp_qty = fields.Float(string='MRP Quantity')
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
# © 2016-18 Eficent Business and IT Consulting Services S.L.
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from math import ceil
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class MrpProduct(models.Model):
|
||||
_name = 'mrp.product'
|
||||
|
||||
mrp_area_id = fields.Many2one(
|
||||
comodel_name='mrp.area', string='MRP Area',
|
||||
)
|
||||
current_qty_available = fields.Float(
|
||||
string='Current Qty Available',
|
||||
related='product_id.qty_available',
|
||||
)
|
||||
main_supplier_id = fields.Many2one(
|
||||
comodel_name='res.partner', string='Main Supplier',
|
||||
compute='_compute_main_supplier', store=True,
|
||||
index=True,
|
||||
)
|
||||
main_supplierinfo_id = fields.Many2one(
|
||||
comodel_name='product.supplierinfo', string='Supplier Info',
|
||||
compute='_compute_main_supplier', store=True,
|
||||
)
|
||||
mrp_inspection_delay = fields.Integer(
|
||||
string='Inspection Delay',
|
||||
related='product_id.mrp_inspection_delay')
|
||||
mrp_lead_time = fields.Float(
|
||||
string='Lead Time',
|
||||
related='product_id.produce_delay',
|
||||
)
|
||||
mrp_llc = fields.Integer(
|
||||
string='Low Level Code', index=True, readonly=True,
|
||||
)
|
||||
# TODO: minimun stock and max/min order qty assigned by area?
|
||||
mrp_maximum_order_qty = fields.Float(
|
||||
string='Maximum Order Qty',
|
||||
related='product_id.mrp_maximum_order_qty',
|
||||
)
|
||||
mrp_minimum_order_qty = fields.Float(
|
||||
string='Minimum Order Qty',
|
||||
related='product_id.mrp_minimum_order_qty',
|
||||
)
|
||||
mrp_minimum_stock = fields.Float(
|
||||
string='Minimum Stock',
|
||||
related='product_id.mrp_minimum_stock',
|
||||
)
|
||||
mrp_move_ids = fields.One2many(
|
||||
comodel_name='mrp.move', inverse_name='mrp_product_id',
|
||||
string='MRP Moves',
|
||||
)
|
||||
mrp_nbr_days = fields.Integer(
|
||||
string='Nbr. Days', related='product_id.mrp_nbr_days')
|
||||
mrp_qty_available = fields.Float(
|
||||
string='MRP Qty Available')
|
||||
mrp_qty_multiple = fields.Float(
|
||||
string='Qty Multiple',
|
||||
related='product_id.mrp_qty_multiple',
|
||||
)
|
||||
mrp_transit_delay = fields.Integer(related='product_id.mrp_transit_delay')
|
||||
mrp_verified = fields.Boolean(
|
||||
string='MRP Verified',
|
||||
related='product_id.mrp_verified',
|
||||
)
|
||||
name = fields.Char(string='Description')
|
||||
# TODO: rename to mrp_action_count?
|
||||
nbr_mrp_actions = fields.Integer(
|
||||
string='Nbr Actions', index=True,
|
||||
)
|
||||
nbr_mrp_actions_4w = fields.Integer(
|
||||
string='Nbr Actions 4 Weeks', index=True,
|
||||
)
|
||||
product_id = fields.Many2one(
|
||||
comodel_name='product.product', string='Product',
|
||||
index=True,
|
||||
)
|
||||
product_tmpl_id = fields.Many2one(
|
||||
comodel_name='product.template',
|
||||
string='Product Template',
|
||||
related='product_id.product_tmpl_id',
|
||||
)
|
||||
supply_method = fields.Selection(
|
||||
selection=[('buy', 'Buy'),
|
||||
('none', 'Undefined'),
|
||||
('manufacture', 'Produce'),
|
||||
('move', 'Transfer')],
|
||||
string='Supply Method',
|
||||
compute='_compute_supply_method', store=True,
|
||||
)
|
||||
|
||||
@api.multi
|
||||
@api.depends('mrp_area_id')
|
||||
def _compute_supply_method(self):
|
||||
group_obj = self.env['procurement.group']
|
||||
for rec in self:
|
||||
values = {
|
||||
'warehouse_id': rec.mrp_area_id.warehouse_id,
|
||||
'company_id': self.env.user.company_id.id,
|
||||
# TODO: better way to get company
|
||||
}
|
||||
rule = group_obj._get_rule(
|
||||
rec.product_id, rec.mrp_area_id.location_id, values)
|
||||
rec.supply_method = rule.action if rule else 'none'
|
||||
|
||||
@api.multi
|
||||
@api.depends('supply_method')
|
||||
def _compute_main_supplier(self):
|
||||
"""Simplified and similar to procurement.rule logic."""
|
||||
for rec in self.filtered(lambda r: r.supply_method == 'buy'):
|
||||
suppliers = rec.product_id.seller_ids.filtered(
|
||||
lambda r: (not r.product_id or r.product_id == rec.product_id))
|
||||
if not suppliers:
|
||||
continue
|
||||
rec.main_supplierinfo_id = suppliers[0]
|
||||
rec.main_supplier_id = suppliers[0].name
|
||||
|
||||
@api.multi
|
||||
def _adjust_qty_to_order(self, qty_to_order):
|
||||
self.ensure_one()
|
||||
if (not self.mrp_maximum_order_qty and not
|
||||
self.mrp_minimum_order_qty and self.mrp_qty_multiple == 1.0):
|
||||
return qty_to_order
|
||||
if qty_to_order < self.mrp_minimum_order_qty:
|
||||
return self.mrp_minimum_order_qty
|
||||
if self.mrp_qty_multiple:
|
||||
multiplier = ceil(qty_to_order / self.mrp_qty_multiple)
|
||||
qty_to_order = multiplier * self.mrp_qty_multiple
|
||||
if self.mrp_maximum_order_qty and qty_to_order > \
|
||||
self.mrp_maximum_order_qty:
|
||||
return self.mrp_maximum_order_qty
|
||||
return qty_to_order
|
||||
143
mrp_multi_level/models/product_mrp_area.py
Normal file
143
mrp_multi_level/models/product_mrp_area.py
Normal file
@@ -0,0 +1,143 @@
|
||||
# Copyright 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
# Copyright 2016-18 Eficent Business and IT Consulting Services S.L.
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
from math import ceil
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class ProductMRPArea(models.Model):
|
||||
_name = 'product.mrp.area'
|
||||
_description = 'Product MRP Area'
|
||||
|
||||
active = fields.Boolean(default=True)
|
||||
mrp_area_id = fields.Many2one('mrp.area',
|
||||
required=True,
|
||||
)
|
||||
product_id = fields.Many2one('product.product',
|
||||
required=True,
|
||||
string='Product',
|
||||
)
|
||||
product_tmpl_id = fields.Many2one('product.template',
|
||||
readonly=True,
|
||||
related='product_id.product_tmpl_id',
|
||||
store=True,
|
||||
)
|
||||
|
||||
# TODO: applicable and exclude... redundant??
|
||||
mrp_applicable = fields.Boolean(string='MRP Applicable')
|
||||
mrp_exclude = fields.Boolean(string='Exclude from MRP')
|
||||
mrp_inspection_delay = fields.Integer(string='Inspection Delay')
|
||||
mrp_maximum_order_qty = fields.Float(
|
||||
string='Maximum Order Qty', default=0.0,
|
||||
)
|
||||
mrp_minimum_order_qty = fields.Float(
|
||||
string='Minimum Order Qty', default=0.0,
|
||||
)
|
||||
mrp_minimum_stock = fields.Float(string='Minimum Stock')
|
||||
mrp_nbr_days = fields.Integer(
|
||||
string='Nbr. Days', default=0,
|
||||
help="Number of days to group demand for this product during the "
|
||||
"MRP run, in order to determine the quantity to order.",
|
||||
)
|
||||
mrp_qty_multiple = fields.Float(string='Qty Multiple', default=1.00)
|
||||
mrp_transit_delay = fields.Integer(string='Transit Delay', default=0)
|
||||
mrp_verified = fields.Boolean(
|
||||
string='Verified for MRP',
|
||||
help="Identifies that this product has been verified "
|
||||
"to be valid for the MRP.",
|
||||
)
|
||||
mrp_lead_time = fields.Float(
|
||||
string='Lead Time',
|
||||
related='product_id.produce_delay',
|
||||
)
|
||||
main_supplier_id = fields.Many2one(
|
||||
comodel_name='res.partner', string='Main Supplier',
|
||||
compute='_compute_main_supplier', store=True,
|
||||
index=True,
|
||||
)
|
||||
main_supplierinfo_id = fields.Many2one(
|
||||
comodel_name='product.supplierinfo', string='Supplier Info',
|
||||
compute='_compute_main_supplier', store=True,
|
||||
)
|
||||
supply_method = fields.Selection(
|
||||
selection=[('buy', 'Buy'),
|
||||
('none', 'Undefined'),
|
||||
('manufacture', 'Produce'),
|
||||
('move', 'Transfer')],
|
||||
string='Supply Method',
|
||||
compute='_compute_supply_method',
|
||||
)
|
||||
|
||||
qty_available = fields.Float('Quantity Available',
|
||||
compute='_compute_qty_available')
|
||||
mrp_move_ids = fields.One2many(comodel_name='mrp.move',
|
||||
inverse_name='product_mrp_area_id',
|
||||
readonly=True,
|
||||
)
|
||||
_sql_constraints = [
|
||||
('product_mrp_area_uniq', 'unique(product_id, mrp_area_id)',
|
||||
'The product/MRP Area parameters combination must be unique.'),
|
||||
]
|
||||
|
||||
@api.multi
|
||||
def name_get(self):
|
||||
return [(area.id, '[%s] %s' % (
|
||||
area.mrp_area_id.name,
|
||||
area.product_id.display_name)) for area in self]
|
||||
|
||||
@api.multi
|
||||
def _compute_qty_available(self):
|
||||
for rec in self:
|
||||
qty_available = 0.0
|
||||
product_obj = self.env['product.product']
|
||||
# TODO: move mrp_qty_available computation, maybe unreserved??
|
||||
location_ids = self.env['stock.location'].search(
|
||||
[('id', 'child_of',
|
||||
rec.mrp_area_id.location_id.id)])
|
||||
for location in location_ids:
|
||||
product_l = product_obj.with_context(
|
||||
{'location': location.id}).browse(rec.product_id.id)
|
||||
qty_available += product_l.qty_available
|
||||
rec.qty_available = qty_available
|
||||
|
||||
@api.multi
|
||||
def _compute_supply_method(self):
|
||||
group_obj = self.env['procurement.group']
|
||||
for rec in self:
|
||||
values = {
|
||||
'warehouse_id': rec.mrp_area_id.warehouse_id,
|
||||
'company_id': self.env.user.company_id.id,
|
||||
# TODO: better way to get company
|
||||
}
|
||||
rule = group_obj._get_rule(
|
||||
rec.product_id, rec.mrp_area_id.location_id, values)
|
||||
rec.supply_method = rule.action if rule else 'none'
|
||||
|
||||
@api.multi
|
||||
@api.depends('supply_method')
|
||||
def _compute_main_supplier(self):
|
||||
"""Simplified and similar to procurement.rule logic."""
|
||||
for rec in self.filtered(lambda r: r.supply_method == 'buy'):
|
||||
suppliers = rec.product_id.seller_ids.filtered(
|
||||
lambda r: (not r.product_id or r.product_id == rec.product_id))
|
||||
if not suppliers:
|
||||
continue
|
||||
rec.main_supplierinfo_id = suppliers[0]
|
||||
rec.main_supplier_id = suppliers[0].name
|
||||
|
||||
@api.multi
|
||||
def _adjust_qty_to_order(self, qty_to_order):
|
||||
self.ensure_one()
|
||||
if (not self.mrp_maximum_order_qty and not
|
||||
self.mrp_minimum_order_qty and self.mrp_qty_multiple == 1.0):
|
||||
return qty_to_order
|
||||
if qty_to_order < self.mrp_minimum_order_qty:
|
||||
return self.mrp_minimum_order_qty
|
||||
if self.mrp_qty_multiple:
|
||||
multiplier = ceil(qty_to_order / self.mrp_qty_multiple)
|
||||
qty_to_order = multiplier * self.mrp_qty_multiple
|
||||
if self.mrp_maximum_order_qty and qty_to_order > \
|
||||
self.mrp_maximum_order_qty:
|
||||
return self.mrp_maximum_order_qty
|
||||
return qty_to_order
|
||||
@@ -2,7 +2,7 @@
|
||||
# Copyright 2016-18 Eficent Business and IT Consulting Services S.L.
|
||||
# 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 Product(models.Model):
|
||||
@@ -15,36 +15,37 @@ class Product(models.Model):
|
||||
string='Manufacturing Orders',
|
||||
domain=[('state', '=', 'draft')],
|
||||
)
|
||||
# TODO: applicable and exclude... redundant??
|
||||
mrp_applicable = fields.Boolean(string='MRP Applicable')
|
||||
mrp_exclude = fields.Boolean(string='Exclude from MRP')
|
||||
mrp_inspection_delay = fields.Integer(string='Inspection Delay')
|
||||
mrp_maximum_order_qty = fields.Float(
|
||||
string='Maximum Order Qty', default=0.0,
|
||||
)
|
||||
mrp_minimum_order_qty = fields.Float(
|
||||
string='Minimum Order Qty', default=0.0,
|
||||
)
|
||||
mrp_minimum_stock = fields.Float(string='Minimum Stock')
|
||||
mrp_nbr_days = fields.Integer(
|
||||
string='Nbr. Days', default=0,
|
||||
help="Number of days to group demand for this product during the "
|
||||
"MRP run, in order to determine the quantity to order.",
|
||||
)
|
||||
mrp_product_ids = fields.One2many(
|
||||
comodel_name='mrp.product',
|
||||
inverse_name='product_id',
|
||||
string='MRP Product data',
|
||||
)
|
||||
mrp_qty_multiple = fields.Float(string='Qty Multiple', default=1.00)
|
||||
mrp_transit_delay = fields.Integer(string='Transit Delay', default=0)
|
||||
mrp_verified = fields.Boolean(
|
||||
string='Verified for MRP',
|
||||
help="Identifies that this product has been verified "
|
||||
"to be valid for the MRP.",
|
||||
)
|
||||
purchase_order_line_ids = fields.One2many(
|
||||
comodel_name='purchase.order.line',
|
||||
inverse_name='product_id',
|
||||
string='Purchase Orders',
|
||||
)
|
||||
mrp_area_ids = fields.One2many(
|
||||
comodel_name='product.mrp.area',
|
||||
inverse_name='product_id',
|
||||
string='MRP Area parameters'
|
||||
)
|
||||
mrp_area_count = fields.Integer(
|
||||
string='MRP Area Parameter Count',
|
||||
readonly=True,
|
||||
compute='_compute_mrp_area_count')
|
||||
|
||||
@api.multi
|
||||
def _compute_mrp_area_count(self):
|
||||
for rec in self:
|
||||
rec.mrp_area_count = len(rec.mrp_area_ids)
|
||||
|
||||
@api.multi
|
||||
def action_view_mrp_area_parameters(self):
|
||||
self.ensure_one()
|
||||
action = self.env.ref('mrp_multi_level.product_mrp_area_action')
|
||||
result = action.read()[0]
|
||||
product_ids = self.ids
|
||||
if len(product_ids) > 1:
|
||||
result['domain'] = [('product_id', 'in', product_ids)]
|
||||
else:
|
||||
res = self.env.ref('mrp_multi_level.product_mrp_area_form', False)
|
||||
result['views'] = [(res and res.id or False, 'form')]
|
||||
result['res_id'] = product_ids[0]
|
||||
result['context'] = {'default_product_id': product_ids[0]}
|
||||
return result
|
||||
|
||||
@@ -7,222 +7,34 @@ from odoo import api, fields, models
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = 'product.template'
|
||||
|
||||
mrp_applicable = fields.Boolean(string='MRP Applicable',
|
||||
compute='_compute_mrp_applicable',
|
||||
inverse='_set_mrp_applicable', store=True
|
||||
)
|
||||
mrp_exclude = fields.Boolean(string='Exclude from MRP',
|
||||
compute='_compute_mrp_exclude',
|
||||
inverse='_set_mrp_exclude', store=True
|
||||
)
|
||||
mrp_inspection_delay = fields.Integer(
|
||||
string='Inspection Delay',
|
||||
compute='_compute_mrp_inspection_delay',
|
||||
inverse='_set_mrp_inspection_delay',
|
||||
store=True
|
||||
)
|
||||
mrp_maximum_order_qty = fields.Float(
|
||||
string='Maximum Order Qty',
|
||||
compute='_compute_mrp_maximum_order_qty',
|
||||
inverse='_set_mrp_maximum_order_qty', store=True
|
||||
)
|
||||
mrp_minimum_order_qty = fields.Float(
|
||||
string='Minimum Order Qty',
|
||||
compute='_compute_mrp_minimum_order_qty',
|
||||
inverse='_set_mrp_minimum_order_qty', store=True
|
||||
)
|
||||
mrp_minimum_stock = fields.Float(
|
||||
string='Minimum Stock',
|
||||
compute='_compute_mrp_minimum_stock',
|
||||
inverse='_set_mrp_minimum_stock', store=True
|
||||
)
|
||||
mrp_nbr_days = fields.Integer(
|
||||
string='Nbr. Days',
|
||||
compute='_compute_mrp_nbr_days',
|
||||
inverse='_set_mrp_nbr_days', store=True,
|
||||
help="Number of days to group demand for this product during the "
|
||||
"MRP run, in order to determine the quantity to order.",
|
||||
)
|
||||
mrp_qty_multiple = fields.Float(
|
||||
string='Qty Multiple', default=1.00,
|
||||
compute='_compute_mrp_qty_multiple',
|
||||
inverse='_set_mrp_qty_multiple', store=True
|
||||
)
|
||||
mrp_transit_delay = fields.Integer(
|
||||
string='Transit Delay', default=0,
|
||||
compute='_compute_mrp_transit_delay',
|
||||
inverse='_set_mrp_transit_delay', store=True
|
||||
)
|
||||
mrp_verified = fields.Boolean(
|
||||
string='Verified for MRP',
|
||||
compute='_compute_mrp_verified',
|
||||
inverse='_set_mrp_verified', store=True,
|
||||
help="Identifies that this product has been verified "
|
||||
"to be valid for the MRP.",
|
||||
mrp_area_ids = fields.One2many(
|
||||
comodel_name='product.mrp.area',
|
||||
inverse_name='product_tmpl_id',
|
||||
string='MRP Area parameters',
|
||||
)
|
||||
mrp_area_count = fields.Integer(
|
||||
string='MRP Area Parameter Count',
|
||||
readonly=True,
|
||||
compute='_compute_mrp_area_count')
|
||||
|
||||
@api.depends('product_variant_ids', 'product_variant_ids.mrp_applicable')
|
||||
def _compute_mrp_applicable(self):
|
||||
unique_variants = self.filtered(
|
||||
lambda template: len(template.product_variant_ids) == 1)
|
||||
for template in unique_variants:
|
||||
template.mrp_applicable = \
|
||||
template.product_variant_ids.mrp_applicable
|
||||
for template in (self - unique_variants):
|
||||
template.mrp_applicable = False
|
||||
@api.multi
|
||||
def _compute_mrp_area_count(self):
|
||||
for rec in self:
|
||||
rec.mrp_area_count = len(rec.mrp_area_ids)
|
||||
|
||||
@api.one
|
||||
def _set_mrp_applicable(self):
|
||||
if len(self.product_variant_ids) == 1:
|
||||
self.product_variant_ids.mrp_applicable = self.mrp_applicable
|
||||
|
||||
@api.depends('product_variant_ids', 'product_variant_ids.mrp_exclude')
|
||||
def _compute_mrp_exclude(self):
|
||||
unique_variants = self.filtered(
|
||||
lambda template: len(template.product_variant_ids) == 1)
|
||||
for template in unique_variants:
|
||||
template.mrp_exclude = \
|
||||
template.product_variant_ids.mrp_exclude
|
||||
for template in (self - unique_variants):
|
||||
template.mrp_exclude = False
|
||||
|
||||
@api.one
|
||||
def _set_mrp_exclude(self):
|
||||
if len(self.product_variant_ids) == 1:
|
||||
self.product_variant_ids.mrp_exclude = self.mrp_exclude
|
||||
|
||||
@api.depends('product_variant_ids',
|
||||
'product_variant_ids.mrp_inspection_delay')
|
||||
def _compute_mrp_inspection_delay(self):
|
||||
unique_variants = self.filtered(
|
||||
lambda template: len(template.product_variant_ids) == 1)
|
||||
for template in unique_variants:
|
||||
template.mrp_inspection_delay = \
|
||||
template.product_variant_ids.mrp_inspection_delay
|
||||
for template in (self - unique_variants):
|
||||
template.mrp_inspection_delay = 0
|
||||
|
||||
@api.one
|
||||
def _set_mrp_inspection_delay(self):
|
||||
if len(self.product_variant_ids) == 1:
|
||||
self.product_variant_ids.mrp_inspection_delay = \
|
||||
self.mrp_inspection_delay
|
||||
|
||||
@api.depends('product_variant_ids',
|
||||
'product_variant_ids.mrp_maximum_order_qty')
|
||||
def _compute_mrp_maximum_order_qty(self):
|
||||
unique_variants = self.filtered(
|
||||
lambda template: len(template.product_variant_ids) == 1)
|
||||
for template in unique_variants:
|
||||
template.mrp_maximum_order_qty = \
|
||||
template.product_variant_ids.mrp_maximum_order_qty
|
||||
for template in (self - unique_variants):
|
||||
template.mrp_maximum_order_qty = 0.0
|
||||
|
||||
@api.one
|
||||
def _set_mrp_maximum_order_qty(self):
|
||||
if len(self.product_variant_ids) == 1:
|
||||
self.product_variant_ids.mrp_maximum_order_qty = \
|
||||
self.mrp_maximum_order_qty
|
||||
|
||||
@api.depends('product_variant_ids',
|
||||
'product_variant_ids.mrp_minimum_order_qty')
|
||||
def _compute_mrp_minimum_order_qty(self):
|
||||
unique_variants = self.filtered(
|
||||
lambda template: len(template.product_variant_ids) == 1)
|
||||
for template in unique_variants:
|
||||
template.mrp_minimum_order_qty = \
|
||||
template.product_variant_ids.mrp_minimum_order_qty
|
||||
for template in (self - unique_variants):
|
||||
template.mrp_minimum_order_qty = 0.0
|
||||
|
||||
@api.one
|
||||
def _set_mrp_minimum_order_qty(self):
|
||||
if len(self.product_variant_ids) == 1:
|
||||
self.product_variant_ids.mrp_minimum_order_qty = \
|
||||
self.mrp_minimum_order_qty
|
||||
|
||||
@api.depends('product_variant_ids',
|
||||
'product_variant_ids.mrp_minimum_stock')
|
||||
def _compute_mrp_minimum_stock(self):
|
||||
unique_variants = self.filtered(
|
||||
lambda template: len(template.product_variant_ids) == 1)
|
||||
for template in unique_variants:
|
||||
template.mrp_minimum_stock = \
|
||||
template.product_variant_ids.mrp_minimum_stock
|
||||
for template in (self - unique_variants):
|
||||
template.mrp_minimum_stock = 0.0
|
||||
|
||||
@api.one
|
||||
def _set_mrp_minimum_stock(self):
|
||||
if len(self.product_variant_ids) == 1:
|
||||
self.product_variant_ids.mrp_minimum_stock = \
|
||||
self.mrp_minimum_stock
|
||||
|
||||
@api.depends('product_variant_ids',
|
||||
'product_variant_ids.mrp_nbr_days')
|
||||
def _compute_mrp_nbr_days(self):
|
||||
unique_variants = self.filtered(
|
||||
lambda template: len(template.product_variant_ids) == 1)
|
||||
for template in unique_variants:
|
||||
template.mrp_nbr_days = \
|
||||
template.product_variant_ids.mrp_nbr_days
|
||||
for template in (self - unique_variants):
|
||||
template.mrp_nbr_days = 0.0
|
||||
|
||||
@api.one
|
||||
def _set_mrp_nbr_days(self):
|
||||
if len(self.product_variant_ids) == 1:
|
||||
self.product_variant_ids.mrp_nbr_days = \
|
||||
self.mrp_nbr_days
|
||||
|
||||
@api.depends('product_variant_ids',
|
||||
'product_variant_ids.mrp_qty_multiple')
|
||||
def _compute_mrp_qty_multiple(self):
|
||||
unique_variants = self.filtered(
|
||||
lambda template: len(template.product_variant_ids) == 1)
|
||||
for template in unique_variants:
|
||||
template.mrp_qty_multiple = \
|
||||
template.product_variant_ids.mrp_qty_multiple
|
||||
for template in (self - unique_variants):
|
||||
template.mrp_qty_multiple = 1
|
||||
|
||||
@api.one
|
||||
def _set_mrp_qty_multiple(self):
|
||||
if len(self.product_variant_ids) == 1:
|
||||
self.product_variant_ids.mrp_qty_multiple = \
|
||||
self.mrp_qty_multiple
|
||||
|
||||
@api.depends('product_variant_ids',
|
||||
'product_variant_ids.mrp_transit_delay')
|
||||
def _compute_mrp_transit_delay(self):
|
||||
unique_variants = self.filtered(
|
||||
lambda template: len(template.product_variant_ids) == 1)
|
||||
for template in unique_variants:
|
||||
template.mrp_transit_delay = \
|
||||
template.product_variant_ids.mrp_transit_delay
|
||||
for template in (self - unique_variants):
|
||||
template.mrp_transit_delay = 0.0
|
||||
|
||||
@api.one
|
||||
def _set_mrp_transit_delay(self):
|
||||
if len(self.product_variant_ids) == 1:
|
||||
self.product_variant_ids.mrp_transit_delay = \
|
||||
self.mrp_transit_delay
|
||||
|
||||
@api.depends('product_variant_ids',
|
||||
'product_variant_ids.mrp_verified')
|
||||
def _compute_mrp_verified(self):
|
||||
unique_variants = self.filtered(
|
||||
lambda template: len(template.product_variant_ids) == 1)
|
||||
for template in unique_variants:
|
||||
template.mrp_verified = \
|
||||
template.product_variant_ids.mrp_verified
|
||||
for template in (self - unique_variants):
|
||||
template.mrp_verified = 0.0
|
||||
|
||||
@api.one
|
||||
def _set_mrp_verified(self):
|
||||
if len(self.product_variant_ids) == 1:
|
||||
self.product_variant_ids.mrp_verified = \
|
||||
self.mrp_verified
|
||||
@api.multi
|
||||
def action_view_mrp_area_parameters(self):
|
||||
self.ensure_one()
|
||||
action = self.env.ref('mrp_multi_level.product_mrp_area_action')
|
||||
result = action.read()[0]
|
||||
mrp_area_ids = self.with_context(
|
||||
active_test=False).mrp_area_ids.ids
|
||||
if len(mrp_area_ids) > 1:
|
||||
result['domain'] = [('id', 'in', mrp_area_ids)]
|
||||
else:
|
||||
res = self.env.ref('mrp_multi_level.product_mrp_area_form', False)
|
||||
result['views'] = [(res and res.id or False, 'form')]
|
||||
result['res_id'] = mrp_area_ids[0]
|
||||
result['context'] = {
|
||||
'default_product_id': self.product_variant_ids[0].id}
|
||||
return result
|
||||
|
||||
@@ -3,7 +3,7 @@ access_mrp_inventory_user,mrp.inventory user,model_mrp_inventory,mrp.group_mrp_u
|
||||
access_mrp_inventory_manager,mrp.inventory manager,model_mrp_inventory,mrp.group_mrp_manager,1,1,1,1
|
||||
access_mrp_move_user,mrp.move user,model_mrp_move,mrp.group_mrp_user,1,0,0,0
|
||||
access_mrp_move_manager,mrp.move manager,model_mrp_move,mrp.group_mrp_manager,1,1,1,1
|
||||
access_mrp_product_user,mrp.product user,model_mrp_product,base.group_user,1,0,0,0
|
||||
access_mrp_product_manager,mrp.product manager,model_mrp_product,mrp.group_mrp_manager,1,1,1,1
|
||||
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_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
|
||||
|
||||
|
@@ -17,13 +17,13 @@ class TestMrpMultiLevel(SavepointCase):
|
||||
cls.mo_obj = cls.env['mrp.production']
|
||||
cls.po_obj = cls.env['purchase.order']
|
||||
cls.product_obj = cls.env['product.product']
|
||||
cls.product_mrp_area_obj = cls.env['product.mrp.area']
|
||||
cls.partner_obj = cls.env['res.partner']
|
||||
cls.stock_picking_obj = cls.env['stock.picking']
|
||||
cls.estimate_obj = cls.env['stock.demand.estimate']
|
||||
cls.mrp_multi_level_wiz = cls.env['mrp.multi.level']
|
||||
cls.mrp_inventory_procure_wiz = cls.env['mrp.inventory.procure']
|
||||
cls.mrp_inventory_obj = cls.env['mrp.inventory']
|
||||
cls.mrp_product_obj = cls.env['mrp.product']
|
||||
cls.mrp_inventory_obj = cls.env['mrp.inventory']
|
||||
cls.mrp_move_obj = cls.env['mrp.move']
|
||||
|
||||
cls.fp_1 = cls.env.ref('mrp_multi_level.product_product_fp_1')
|
||||
@@ -32,6 +32,7 @@ class TestMrpMultiLevel(SavepointCase):
|
||||
cls.sf_2 = cls.env.ref('mrp_multi_level.product_product_sf_2')
|
||||
cls.pp_1 = cls.env.ref('mrp_multi_level.product_product_pp_1')
|
||||
cls.pp_2 = cls.env.ref('mrp_multi_level.product_product_pp_2')
|
||||
cls.mrp_area = cls.env.ref('mrp_multi_level.mrp_area_stock_wh0')
|
||||
cls.vendor = cls.env.ref('mrp_multi_level.res_partner_lazer_tech')
|
||||
cls.wh = cls.env.ref('stock.warehouse0')
|
||||
cls.stock_location = cls.wh.lot_stock_id
|
||||
@@ -54,22 +55,35 @@ class TestMrpMultiLevel(SavepointCase):
|
||||
'route_ids': [(6, 0, [route_buy])],
|
||||
'seller_ids': [(0, 0, {'name': vendor1.id, 'price': 20.0})],
|
||||
})
|
||||
cls.product_mrp_area_obj.create({
|
||||
'product_id': cls.prod_test.id,
|
||||
'mrp_area_id': cls.mrp_area.id,
|
||||
})
|
||||
cls.prod_min = cls.product_obj.create({
|
||||
'name': 'Product with minimum order qty',
|
||||
'type': 'product',
|
||||
'list_price': 50.0,
|
||||
'route_ids': [(6, 0, [route_buy])],
|
||||
'seller_ids': [(0, 0, {'name': vendor1.id, 'price': 10.0})],
|
||||
})
|
||||
cls.product_mrp_area_obj.create({
|
||||
'product_id': cls.prod_min.id,
|
||||
'mrp_area_id': cls.mrp_area.id,
|
||||
'mrp_minimum_order_qty': 50.0,
|
||||
'mrp_maximum_order_qty': 0.0,
|
||||
'mrp_qty_multiple': 1.0,
|
||||
})
|
||||
|
||||
cls.prod_max = cls.product_obj.create({
|
||||
'name': 'Product with maximum order qty',
|
||||
'type': 'product',
|
||||
'list_price': 50.0,
|
||||
'route_ids': [(6, 0, [route_buy])],
|
||||
'seller_ids': [(0, 0, {'name': vendor1.id, 'price': 10.0})],
|
||||
})
|
||||
cls.product_mrp_area_obj.create({
|
||||
'product_id': cls.prod_max.id,
|
||||
'mrp_area_id': cls.mrp_area.id,
|
||||
'mrp_minimum_order_qty': 50.0,
|
||||
'mrp_maximum_order_qty': 100.0,
|
||||
'mrp_qty_multiple': 1.0,
|
||||
@@ -80,11 +94,14 @@ class TestMrpMultiLevel(SavepointCase):
|
||||
'list_price': 50.0,
|
||||
'route_ids': [(6, 0, [route_buy])],
|
||||
'seller_ids': [(0, 0, {'name': vendor1.id, 'price': 10.0})],
|
||||
})
|
||||
cls.product_mrp_area_obj.create({
|
||||
'product_id': cls.prod_multiple.id,
|
||||
'mrp_area_id': cls.mrp_area.id,
|
||||
'mrp_minimum_order_qty': 50.0,
|
||||
'mrp_maximum_order_qty': 500.0,
|
||||
'mrp_qty_multiple': 25.0,
|
||||
})
|
||||
|
||||
# Create test picking for FP-1 and FP-2:
|
||||
res = cls.calendar.plan_days(7+1, datetime.today())
|
||||
date_move = res.date()
|
||||
@@ -244,16 +261,16 @@ class TestMrpMultiLevel(SavepointCase):
|
||||
self.assertEqual(self.pp_1.llc, 2)
|
||||
self.assertEqual(self.pp_2.llc, 2)
|
||||
|
||||
def test_02_mrp_product(self):
|
||||
def test_02_product_mrp_area(self):
|
||||
"""Tests that mrp products are generated correctly."""
|
||||
mrp_product = self.mrp_product_obj.search([
|
||||
product_mrp_area = self.product_mrp_area_obj.search([
|
||||
('product_id', '=', self.pp_1.id)])
|
||||
self.assertEqual(mrp_product.supply_method, 'buy')
|
||||
self.assertEqual(mrp_product.main_supplier_id, self.vendor)
|
||||
self.assertEqual(mrp_product.mrp_qty_available, 10.0)
|
||||
mrp_product = self.mrp_product_obj.search([
|
||||
self.assertEqual(product_mrp_area.supply_method, 'buy')
|
||||
self.assertEqual(product_mrp_area.main_supplier_id, self.vendor)
|
||||
self.assertEqual(product_mrp_area.qty_available, 10.0)
|
||||
product_mrp_area = self.product_mrp_area_obj.search([
|
||||
('product_id', '=', self.sf_1.id)])
|
||||
self.assertEqual(mrp_product.supply_method, 'manufacture')
|
||||
self.assertEqual(product_mrp_area.supply_method, 'manufacture')
|
||||
|
||||
def test_03_mrp_moves(self):
|
||||
"""Tests for mrp moves generated."""
|
||||
@@ -265,11 +282,13 @@ class TestMrpMultiLevel(SavepointCase):
|
||||
self.assertNotIn('s', moves.mapped('mrp_type'))
|
||||
for move in moves:
|
||||
self.assertTrue(move.mrp_move_up_ids)
|
||||
if move.mrp_move_up_ids.mrp_product_id.product_id == self.fp_1:
|
||||
if move.mrp_move_up_ids.product_mrp_area_id.product_id == \
|
||||
self.fp_1:
|
||||
# Demand coming from FP-1
|
||||
self.assertEqual(move.mrp_move_up_ids.mrp_action, 'mo')
|
||||
self.assertEqual(move.mrp_qty, -200.0)
|
||||
elif move.mrp_move_up_ids.mrp_product_id.product_id == self.sf_1:
|
||||
elif move.mrp_move_up_ids.product_mrp_area_id.product_id == \
|
||||
self.sf_1:
|
||||
# Demand coming from FP-2 -> SF-1
|
||||
self.assertEqual(move.mrp_move_up_ids.mrp_action, 'mo')
|
||||
if move.mrp_date == self.date_5:
|
||||
@@ -299,21 +318,21 @@ class TestMrpMultiLevel(SavepointCase):
|
||||
"""Tests MRP inventories created."""
|
||||
# FP-1
|
||||
fp_1_inventory_lines = self.mrp_inventory_obj.search(
|
||||
[('mrp_product_id.product_id', '=', self.fp_1.id)])
|
||||
[('product_mrp_area_id.product_id', '=', self.fp_1.id)])
|
||||
self.assertEqual(len(fp_1_inventory_lines), 1)
|
||||
self.assertEqual(fp_1_inventory_lines.date, self.date_7)
|
||||
self.assertEqual(fp_1_inventory_lines.demand_qty, 100.0)
|
||||
self.assertEqual(fp_1_inventory_lines.to_procure, 100.0)
|
||||
# FP-2
|
||||
fp_2_line_1 = self.mrp_inventory_obj.search([
|
||||
('mrp_product_id.product_id', '=', self.fp_2.id),
|
||||
('product_mrp_area_id.product_id', '=', self.fp_2.id),
|
||||
('date', '=', self.date_7)])
|
||||
self.assertEqual(len(fp_2_line_1), 1)
|
||||
self.assertEqual(fp_2_line_1.demand_qty, 15.0)
|
||||
self.assertEqual(fp_2_line_1.to_procure, 15.0)
|
||||
# TODO: ask odoo to fix it... should be date10
|
||||
fp_2_line_2 = self.mrp_inventory_obj.search([
|
||||
('mrp_product_id.product_id', '=', self.fp_2.id),
|
||||
('product_mrp_area_id.product_id', '=', self.fp_2.id),
|
||||
('date', '=', self.date_9)])
|
||||
self.assertEqual(len(fp_2_line_2), 1)
|
||||
self.assertEqual(fp_2_line_2.demand_qty, 0.0)
|
||||
@@ -322,26 +341,26 @@ class TestMrpMultiLevel(SavepointCase):
|
||||
|
||||
# SF-1
|
||||
sf_1_line_1 = self.mrp_inventory_obj.search([
|
||||
('mrp_product_id.product_id', '=', self.sf_1.id),
|
||||
('product_mrp_area_id.product_id', '=', self.sf_1.id),
|
||||
('date', '=', self.date_6)])
|
||||
self.assertEqual(len(sf_1_line_1), 1)
|
||||
self.assertEqual(sf_1_line_1.demand_qty, 30.0)
|
||||
self.assertEqual(sf_1_line_1.to_procure, 30.0)
|
||||
sf_1_line_2 = self.mrp_inventory_obj.search([
|
||||
('mrp_product_id.product_id', '=', self.sf_1.id),
|
||||
('product_mrp_area_id.product_id', '=', self.sf_1.id),
|
||||
('date', '=', self.date_9)])
|
||||
self.assertEqual(len(sf_1_line_2), 1)
|
||||
self.assertEqual(sf_1_line_2.demand_qty, 24.0)
|
||||
self.assertEqual(sf_1_line_2.to_procure, 24.0)
|
||||
# SF-2
|
||||
sf_2_line_1 = self.mrp_inventory_obj.search([
|
||||
('mrp_product_id.product_id', '=', self.sf_2.id),
|
||||
('product_mrp_area_id.product_id', '=', self.sf_2.id),
|
||||
('date', '=', self.date_6)])
|
||||
self.assertEqual(len(sf_2_line_1), 1)
|
||||
self.assertEqual(sf_2_line_1.demand_qty, 45.0)
|
||||
self.assertEqual(sf_2_line_1.to_procure, 30.0)
|
||||
sf_2_line_2 = self.mrp_inventory_obj.search([
|
||||
('mrp_product_id.product_id', '=', self.sf_2.id),
|
||||
('product_mrp_area_id.product_id', '=', self.sf_2.id),
|
||||
('date', '=', self.date_9)])
|
||||
self.assertEqual(len(sf_2_line_2), 1)
|
||||
self.assertEqual(sf_2_line_2.demand_qty, 36.0)
|
||||
@@ -349,39 +368,39 @@ class TestMrpMultiLevel(SavepointCase):
|
||||
|
||||
# PP-1
|
||||
pp_1_line_1 = self.mrp_inventory_obj.search([
|
||||
('mrp_product_id.product_id', '=', self.pp_1.id),
|
||||
('product_mrp_area_id.product_id', '=', self.pp_1.id),
|
||||
('date', '=', self.date_5)])
|
||||
self.assertEqual(len(pp_1_line_1), 1)
|
||||
self.assertEqual(pp_1_line_1.demand_qty, 290.0)
|
||||
self.assertEqual(pp_1_line_1.to_procure, 280.0)
|
||||
pp_1_line_2 = self.mrp_inventory_obj.search([
|
||||
('mrp_product_id.product_id', '=', self.pp_1.id),
|
||||
('product_mrp_area_id.product_id', '=', self.pp_1.id),
|
||||
('date', '=', self.date_8)])
|
||||
self.assertEqual(len(pp_1_line_2), 1)
|
||||
self.assertEqual(pp_1_line_2.demand_qty, 72.0)
|
||||
self.assertEqual(pp_1_line_2.to_procure, 72.0)
|
||||
# PP-2
|
||||
pp_2_line_1 = self.mrp_inventory_obj.search([
|
||||
('mrp_product_id.product_id', '=', self.pp_2.id),
|
||||
('product_mrp_area_id.product_id', '=', self.pp_2.id),
|
||||
('date', '=', self.date_3)])
|
||||
self.assertEqual(len(pp_2_line_1), 1)
|
||||
self.assertEqual(pp_2_line_1.demand_qty, 90.0)
|
||||
# 90.0 demand - 20.0 on hand - 5.0 on PO = 65.0
|
||||
self.assertEqual(pp_2_line_1.to_procure, 65.0)
|
||||
pp_2_line_2 = self.mrp_inventory_obj.search([
|
||||
('mrp_product_id.product_id', '=', self.pp_2.id),
|
||||
('product_mrp_area_id.product_id', '=', self.pp_2.id),
|
||||
('date', '=', self.date_5)])
|
||||
self.assertEqual(len(pp_2_line_2), 1)
|
||||
self.assertEqual(pp_2_line_2.demand_qty, 360.0)
|
||||
self.assertEqual(pp_2_line_2.to_procure, 360.0)
|
||||
pp_2_line_3 = self.mrp_inventory_obj.search([
|
||||
('mrp_product_id.product_id', '=', self.pp_2.id),
|
||||
('product_mrp_area_id.product_id', '=', self.pp_2.id),
|
||||
('date', '=', self.date_6)])
|
||||
self.assertEqual(len(pp_2_line_3), 1)
|
||||
self.assertEqual(pp_2_line_3.demand_qty, 108.0)
|
||||
self.assertEqual(pp_2_line_3.to_procure, 108.0)
|
||||
pp_2_line_4 = self.mrp_inventory_obj.search([
|
||||
('mrp_product_id.product_id', '=', self.pp_2.id),
|
||||
('product_mrp_area_id.product_id', '=', self.pp_2.id),
|
||||
('date', '=', self.date_8)])
|
||||
self.assertEqual(len(pp_2_line_4), 1)
|
||||
self.assertEqual(pp_2_line_4.demand_qty, 48.0)
|
||||
@@ -398,11 +417,11 @@ class TestMrpMultiLevel(SavepointCase):
|
||||
expected = [200.0, 290.0, 90.0, 0.0, 72.0, 0.0]
|
||||
self.assertEqual(moves.mapped('running_availability'), expected)
|
||||
# Actions counters for PP-1:
|
||||
mrp_product = self.mrp_product_obj.search([
|
||||
product_mrp_area = self.product_mrp_area_obj.search([
|
||||
('product_id', '=', self.pp_1.id)
|
||||
])
|
||||
self.assertEqual(mrp_product.nbr_mrp_actions, 3)
|
||||
self.assertEqual(mrp_product.nbr_mrp_actions_4w, 3)
|
||||
# self.assertEqual(product_mrp_area.nbr_mrp_actions, 3) # TODO
|
||||
# self.assertEqual(product_mrp_area.nbr_mrp_actions_4w, 3) # TODO
|
||||
|
||||
def test_06_demand_estimates(self):
|
||||
"""Tests demand estimates integration."""
|
||||
@@ -428,7 +447,7 @@ class TestMrpMultiLevel(SavepointCase):
|
||||
('product_id', '=', self.fp_1.id)])
|
||||
self.assertFalse(mos)
|
||||
mrp_inv = self.mrp_inventory_obj.search([
|
||||
('mrp_product_id.product_id', '=', self.fp_1.id)])
|
||||
('product_mrp_area_id.product_id', '=', self.fp_1.id)])
|
||||
self.mrp_inventory_procure_wiz.with_context({
|
||||
'active_model': 'mrp.inventory',
|
||||
'active_ids': mrp_inv.ids,
|
||||
@@ -446,11 +465,11 @@ class TestMrpMultiLevel(SavepointCase):
|
||||
maximum order quantities and quantity multiple are set."""
|
||||
# minimum order quantity:
|
||||
mrp_inv_min = self.mrp_inventory_obj.search([
|
||||
('mrp_product_id.product_id', '=', self.prod_min.id)])
|
||||
('product_mrp_area_id.product_id', '=', self.prod_min.id)])
|
||||
self.assertEqual(mrp_inv_min.to_procure, 50.0)
|
||||
# maximum order quantity:
|
||||
mrp_inv_max = self.mrp_inventory_obj.search([
|
||||
('mrp_product_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)
|
||||
moves = self.mrp_move_obj.search([
|
||||
('product_id', '=', self.prod_max.id),
|
||||
@@ -461,7 +480,7 @@ class TestMrpMultiLevel(SavepointCase):
|
||||
self.assertIn(50.0, moves.mapped('mrp_qty'))
|
||||
# quantity multiple:
|
||||
mrp_inv_multiple = self.mrp_inventory_obj.search([
|
||||
('mrp_product_id.product_id', '=', self.prod_multiple.id)])
|
||||
('product_mrp_area_id.product_id', '=', self.prod_multiple.id)])
|
||||
self.assertEqual(mrp_inv_multiple.to_procure, 125)
|
||||
|
||||
# TODO: test procure wizard: pos, multiple...
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<group>
|
||||
<group>
|
||||
<field name="mrp_area_id"/>
|
||||
<field name="mrp_product_id"/>
|
||||
<field name="product_mrp_area_id"/>
|
||||
<field name="date"/>
|
||||
</group>
|
||||
<group>
|
||||
@@ -35,7 +35,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<tree string="MRP Inventory" create="false">
|
||||
<field name="mrp_area_id"/>
|
||||
<field name="mrp_product_id"/>
|
||||
<field name="product_mrp_area_id"/>
|
||||
<field name="date"/>
|
||||
<field name="uom_id" groups="product.group_uom"/>
|
||||
<field name="initial_on_hand_qty"/>
|
||||
@@ -59,7 +59,7 @@
|
||||
<pivot string="MRP Inventory">
|
||||
<field name="final_on_hand_qty" type="measure"/>
|
||||
<field name="mrp_area_id" type="row"/>
|
||||
<field name="mrp_product_id" type="row"/>
|
||||
<field name="product_mrp_area_id" type="row"/>
|
||||
<field name="date" interval="day" type="col"/>
|
||||
</pivot>
|
||||
</field>
|
||||
@@ -72,7 +72,7 @@
|
||||
<graph string="MRP Inventory" type="line">
|
||||
<field name="final_on_hand_qty" type="measure"/>
|
||||
<field name="date" interval="day" type="row"/>
|
||||
<field name="mrp_product_id" type="row"/>
|
||||
<field name="product_mrp_area_id" type="row"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
@@ -84,7 +84,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<search string="MRP Inventory">
|
||||
<group name="select" expand="0" string="Selection...">
|
||||
<field name="mrp_product_id"/>
|
||||
<field name="product_mrp_area_id"/>
|
||||
<field name="mrp_area_id"/>
|
||||
</group>
|
||||
<separator/>
|
||||
@@ -93,7 +93,7 @@
|
||||
<separator/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Product"
|
||||
context="{'group_by':'mrp_product_id'}"/>
|
||||
context="{'group_by':'product_mrp_area_id'}"/>
|
||||
<filter string="MRP Area"
|
||||
context="{'group_by':'mrp_area_id'}"/>
|
||||
<filter string="Date to Procure (By Day)"
|
||||
@@ -5,29 +5,26 @@
|
||||
id="menu_mrp_mrp"
|
||||
parent="mrp.menu_mrp_root"
|
||||
sequence="22"/>
|
||||
|
||||
<menuitem name="MRP Areas"
|
||||
id="menu_mrp_areas"
|
||||
action="mrp_area_action"
|
||||
parent="menu_mrp_mrp"
|
||||
parent="mrp.menu_mrp_configuration"
|
||||
sequence="50"/>
|
||||
<menuitem name="Product MRP Area Parameters"
|
||||
id="menu_product_mrp_area_parameters"
|
||||
action="product_mrp_area_action"
|
||||
parent="mrp.menu_mrp_bom"
|
||||
sequence="10"/>
|
||||
|
||||
<menuitem name="MRP Products"
|
||||
id="menu_mrp_products"
|
||||
action="mrp_product_action"
|
||||
parent="menu_mrp_mrp"
|
||||
sequence="20"/>
|
||||
|
||||
<menuitem name="MRP Inventory"
|
||||
id="menu_mrp_inventory"
|
||||
action="mrp_inventory_action"
|
||||
parent="menu_mrp_mrp"
|
||||
parent="mrp.menu_mrp_manufacturing"
|
||||
sequence="30"/>
|
||||
|
||||
<menuitem name="Run MRP Multi Level"
|
||||
id="menu_mrp_multi_level"
|
||||
action="action_mrp_multi_level"
|
||||
parent="menu_mrp_mrp"
|
||||
parent="mrp.menu_mrp_manufacturing"
|
||||
groups="mrp.group_mrp_manager"
|
||||
sequence="40"/>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="mrp_product_tree">
|
||||
<field name="name">mrp.product.tree</field>
|
||||
<field name="model">mrp.product</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="MRP Products">
|
||||
<field name="mrp_area_id"/>
|
||||
<field name="product_id"/>
|
||||
<field name="mrp_verified"/>
|
||||
<field name="mrp_move_ids"/>
|
||||
<field name="nbr_mrp_actions_4w"/>
|
||||
<field name="nbr_mrp_actions"/>
|
||||
<field name="supply_method"/>
|
||||
<field name="main_supplier_id"/>
|
||||
<field name="mrp_llc" invisible="True"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="mrp_product_form">
|
||||
<field name="name">mrp.product.form</field>
|
||||
<field name="model">mrp.product</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="MRP Product">
|
||||
<group colspan="4" col="2">
|
||||
<group>
|
||||
<field name="mrp_area_id" readonly="True"/>
|
||||
<field name="product_id" readonly="True"/>
|
||||
<field name="product_tmpl_id" readonly="True"/>
|
||||
<field name="mrp_verified" readonly="True"/>
|
||||
<field name="supply_method" readonly="True"/>
|
||||
<!--<field name="purchase_requisition" readonly="True"/>-->
|
||||
<field name="main_supplier_id" readonly="True"/>
|
||||
<field name="mrp_lead_time" readonly="True"/>
|
||||
<field name="mrp_nbr_days" readonly="True"/>
|
||||
<field name="mrp_transit_delay" readonly="True"/>
|
||||
<field name="mrp_inspection_delay" readonly="True"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="mrp_llc" readonly="True"/>
|
||||
<field name="mrp_qty_available" readonly="True"/>
|
||||
<field name="current_qty_available" readonly="True"/>
|
||||
<field name="mrp_minimum_stock" readonly="True"/>
|
||||
<field name="mrp_minimum_order_qty" readonly="True"/>
|
||||
<field name="mrp_maximum_order_qty" readonly="True"/>
|
||||
<field name="mrp_qty_multiple" readonly="True"/>
|
||||
</group>
|
||||
</group>
|
||||
<group colspan="2" col="2">
|
||||
<separator string="Moves"/>
|
||||
<field name="mrp_move_ids" nolabel="1" colspan="2" context="{'default_mrp_product_id': active_id}" readonly="True">
|
||||
<tree string="Moves" colors="red:running_availability > mrp_minimum_stock">
|
||||
<field name="mrp_area_id" invisible="True"/>
|
||||
<field name="mrp_product_id" invisible="True"/>
|
||||
<field name="mrp_action_date" readonly="True"/>
|
||||
<field name="mrp_date" readonly="True"/>
|
||||
<field name="current_date" readonly="True"/>
|
||||
<field name="mrp_origin" readonly="True"/>
|
||||
<field name="state" readonly="True"/>
|
||||
<field name="mrp_order_number" readonly="True"/>
|
||||
<field name="parent_product_id" readonly="True"/>
|
||||
<field name="name" readonly="True"/>
|
||||
<field name="mrp_qty" readonly="True"/>
|
||||
<field name="current_qty" readonly="True"/>
|
||||
<field name="running_availability" readonly="True"/>
|
||||
<field name="mrp_minimum_stock" invisible="True"/>
|
||||
<field name="mrp_action" readonly="True"/>
|
||||
<field name="mrp_type" readonly="True"/>
|
||||
<field name="mrp_move_up_ids" readonly="True"/>
|
||||
<field name="mrp_processed" invisible="True"/>
|
||||
</tree>
|
||||
<form string="Moves">
|
||||
<group colspan="4" col="2">
|
||||
<group>
|
||||
<field name="mrp_area_id"
|
||||
invisible="True"/>
|
||||
<field name="mrp_product_id" invisible="True"/>
|
||||
<field name="mrp_qty" readonly="True"/>
|
||||
<field name="current_qty" readonly="True"/>
|
||||
<field name="mrp_date" readonly="True"/>
|
||||
<field name="current_date" readonly="True"/>
|
||||
<field name="mrp_action" readonly="True"/>
|
||||
<field name="mrp_type" readonly="True"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="production_id" readonly="True"/>
|
||||
<field name="purchase_order_id" readonly="True"/>
|
||||
<field name="stock_move_id" readonly="True"/>
|
||||
<field name="mrp_processed" invisible="True"/>
|
||||
</group>
|
||||
</group>
|
||||
<group colspan="2" col="2">
|
||||
<field name="mrp_move_up_ids">
|
||||
<tree string="Moves">
|
||||
<field name="mrp_area_id"
|
||||
invisible="True"/>
|
||||
<field name="mrp_product_id" readonly="True"/>
|
||||
<field name="mrp_date" readonly="True"/>
|
||||
<field name="current_date" readonly="True"/>
|
||||
<field name="mrp_origin" readonly="True"/>
|
||||
<field name="state" readonly="True"/>
|
||||
<field name="mrp_order_number" readonly="True"/>
|
||||
<field name="parent_product_id" readonly="True"/>
|
||||
<field name="name" readonly="True"/>
|
||||
<field name="mrp_qty" readonly="True"/>
|
||||
<field name="current_qty" readonly="True"/>
|
||||
<field name="running_availability" readonly="True"/>
|
||||
<field name="mrp_action" readonly="True"/>
|
||||
<field name="mrp_type" readonly="True"/>
|
||||
<field name="mrp_move_up_ids" readonly="True"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="mrp_search_form">
|
||||
<field name="name">mrp.filter.form</field>
|
||||
<field name="model">mrp.product</field>
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Product">
|
||||
<group expand="0" string="Selection...">
|
||||
<field name="product_id" select='1'/>
|
||||
<field name="mrp_area_id"/>
|
||||
</group>
|
||||
<separator/>
|
||||
<filter string="With Moves" name="moves" domain="[['mrp_move_ids','!=',False]]"/>
|
||||
<filter string="With Actions in Coming 4 Weeks" name="actions4w" domain="[['nbr_mrp_actions_4w','!=',0]]"/>
|
||||
<filter string="With Actions" name="actions" domain="[['nbr_mrp_actions','!=',0]]"/>
|
||||
<filter string="Purchase Actions" domain="[['supply_method','=','buy'],['nbr_mrp_actions','!=',0]]"/>
|
||||
<filter string="Manufacture Actions" domain="[['supply_method','=','manufacture'],['nbr_mrp_actions','!=',0]]"/>
|
||||
<separator/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Supply Method" context="{'group_by':'supply_method'}"/>
|
||||
<filter string="Main Supplier" context="{'group_by':'main_supplier_id'}"/>
|
||||
<filter string="Low Level Code" context="{'group_by':'mrp_llc'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="mrp_product_action">
|
||||
<field name="name">MRP Products</field>
|
||||
<field name="res_model">mrp.product</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="mrp_product_tree"/>
|
||||
<field name="search_view_id" ref="mrp_search_form"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
92
mrp_multi_level/views/product_mrp_area_views.xml
Normal file
92
mrp_multi_level/views/product_mrp_area_views.xml
Normal file
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="product_mrp_area_tree">
|
||||
<field name="name">product.mrp.area.tree</field>
|
||||
<field name="model">product.mrp.area</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Product MRP Area parameters">
|
||||
<field name="mrp_area_id"/>
|
||||
<field name="product_tmpl_id"/>
|
||||
<field name="product_id" groups="product.group_product_variant"/>
|
||||
<field name="mrp_exclude"/>
|
||||
<field name="mrp_verified"/>
|
||||
<field name="mrp_nbr_days"/>
|
||||
<field name="mrp_transit_delay"/>
|
||||
<field name="mrp_inspection_delay"/>
|
||||
<field name="mrp_minimum_stock"/>
|
||||
<field name="mrp_minimum_order_qty"/>
|
||||
<field name="mrp_maximum_order_qty"/>
|
||||
<field name="mrp_qty_multiple"/>
|
||||
<field name="supply_method"/>
|
||||
<field name="main_supplierinfo_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="product_mrp_area_form">
|
||||
<field name="name">product.mrp.area.form</field>
|
||||
<field name="model">product.mrp.area</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Product MRP Area parameters">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="toggle_active" type="object"
|
||||
class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button"
|
||||
options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
</div>
|
||||
<group colspan="4" col="2">
|
||||
<group>
|
||||
<field name="mrp_area_id"/>
|
||||
<field name="product_tmpl_id" invisible="1"/>
|
||||
<field name="product_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="mrp_exclude"/>
|
||||
<field name="mrp_verified"/>
|
||||
<field name="mrp_nbr_days"/>
|
||||
<field name="mrp_transit_delay"/>
|
||||
<field name="mrp_inspection_delay"/>
|
||||
<field name="mrp_minimum_stock"/>
|
||||
<field name="mrp_minimum_order_qty"/>
|
||||
<field name="mrp_maximum_order_qty"/>
|
||||
<field name="mrp_qty_multiple"/>
|
||||
<field name="supply_method"/>
|
||||
<field name="main_supplierinfo_id"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="product_mrp_area_search">
|
||||
<field name="name">product.mrp.area.search</field>
|
||||
<field name="model">product.mrp.area</field>
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Product MRP Area parameters">
|
||||
<group expand="0" string="Selection...">
|
||||
<field name="product_id" select='1'/>
|
||||
<field name="mrp_area_id"/>
|
||||
</group>
|
||||
<separator/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="product_mrp_area_action">
|
||||
<field name="name">Product MRP Area Parameters</field>
|
||||
<field name="res_model">product.mrp.area</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="product_mrp_area_tree"/>
|
||||
<field name="search_view_id" ref="product_mrp_area_search"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -1,32 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="view_mrp_product_product_form">
|
||||
<field name="name">view.mrp.product.product.form</field>
|
||||
<field name="model">product.product</field>
|
||||
<field name="inherit_id" ref="product.product_normal_form_view"/>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<notebook position="inside">
|
||||
<page string="MRP">
|
||||
<group colspan="4" col="2">
|
||||
<group>
|
||||
<field name="mrp_exclude"/>
|
||||
<field name="mrp_verified"/>
|
||||
<field name="mrp_nbr_days"/>
|
||||
<field name="mrp_transit_delay"/>
|
||||
<field name="mrp_inspection_delay"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="mrp_minimum_stock"/>
|
||||
<field name="mrp_minimum_order_qty"/>
|
||||
<field name="mrp_maximum_order_qty"/>
|
||||
<field name="mrp_qty_multiple"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
22
mrp_multi_level/views/product_product_views.xml
Normal file
22
mrp_multi_level/views/product_product_views.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="view_product_mrp_area_product_form">
|
||||
<field name="name">view.product.mrp.area.product.form</field>
|
||||
<field name="model">product.product</field>
|
||||
<field name="inherit_id" ref="product.product_normal_form_view"/>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<div name="button_box" position="inside">
|
||||
<button type="object"
|
||||
name="action_view_mrp_area_parameters"
|
||||
class="oe_stat_button"
|
||||
icon="fa-eject"
|
||||
groups="mrp.group_mrp_user,rma.group_mrp_manager">
|
||||
<field name="mrp_area_count" widget="statinfo"
|
||||
string="MRP Areas"/>
|
||||
</button>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -1,31 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
|
||||
<record id="product_template_only_form_view_mrp" model="ir.ui.view">
|
||||
<field name="name">product.template.product.form.mrp</field>
|
||||
<field name="model">product.template</field>
|
||||
<field name="inherit_id" ref="product.product_template_only_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<notebook position="inside">
|
||||
<page name="mrp_multi_level" string="MRP" attrs="{'invisible': [('product_variant_count', '>', 1)]}">
|
||||
<group colspan="4" col="2">
|
||||
<group>
|
||||
<field name="mrp_exclude"/>
|
||||
<field name="mrp_verified"/>
|
||||
<field name="mrp_nbr_days"/>
|
||||
<field name="mrp_transit_delay"/>
|
||||
<field name="mrp_inspection_delay"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="mrp_minimum_stock"/>
|
||||
<field name="mrp_minimum_order_qty"/>
|
||||
<field name="mrp_maximum_order_qty"/>
|
||||
<field name="mrp_qty_multiple"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
22
mrp_multi_level/views/product_template_views.xml
Normal file
22
mrp_multi_level/views/product_template_views.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
|
||||
<record id="product_template_only_form_view_mrp" model="ir.ui.view">
|
||||
<field name="name">product.template.product.form.mrp</field>
|
||||
<field name="model">product.template</field>
|
||||
<field name="inherit_id" ref="product.product_template_only_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<div name="button_box" position="inside">
|
||||
<button type="object"
|
||||
name="action_view_mrp_area_parameters"
|
||||
class="oe_stat_button"
|
||||
icon="fa-eject"
|
||||
groups="mrp.group_mrp_user,rma.group_mrp_manager">
|
||||
<field name="mrp_area_count" widget="statinfo"
|
||||
string="MRP Areas"/>
|
||||
</button>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -23,7 +23,7 @@ class MrpInventoryProcure(models.TransientModel):
|
||||
'uom_id': mrp_inventory.uom_id.id,
|
||||
'date_planned': mrp_inventory.date,
|
||||
'mrp_inventory_id': mrp_inventory.id,
|
||||
'product_id': mrp_inventory.mrp_product_id.product_id.id,
|
||||
'product_id': mrp_inventory.product_mrp_area_id.product_id.id,
|
||||
'warehouse_id': mrp_inventory.mrp_area_id.warehouse_id.id,
|
||||
'location_id': mrp_inventory.mrp_area_id.location_id.id,
|
||||
}
|
||||
@@ -57,12 +57,12 @@ class MrpInventoryProcure(models.TransientModel):
|
||||
|
||||
items = item_obj = self.env['mrp.inventory.procure.item']
|
||||
for line in mrp_inventory_obj.browse(mrp_inventory_ids):
|
||||
max_order = line.mrp_product_id.mrp_maximum_order_qty
|
||||
max_order = line.product_mrp_area_id.mrp_maximum_order_qty
|
||||
qty_to_order = line.to_procure
|
||||
if max_order and max_order < qty_to_order:
|
||||
# split the procurement in batches:
|
||||
while qty_to_order > 0.0:
|
||||
qty = line.mrp_product_id._adjust_qty_to_order(
|
||||
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
|
||||
|
||||
@@ -17,38 +17,37 @@ class MultiLevelMrp(models.TransientModel):
|
||||
# TODO: dates are not being correctly computed for supply...
|
||||
|
||||
@api.model
|
||||
def _prepare_mrp_product_data(self, product, mrp_area):
|
||||
def _prepare_product_mrp_area_data(self, product_mrp_area):
|
||||
qty_available = 0.0
|
||||
product_obj = self.env['product.product']
|
||||
# TODO: move mrp_qty_available computation, maybe unreserved??
|
||||
location_ids = self.env['stock.location'].search(
|
||||
[('id', 'child_of', mrp_area.location_id.id)])
|
||||
[('id', 'child_of', product_mrp_area.mrp_area_id.location_id.id)])
|
||||
for location in location_ids:
|
||||
product_l = product_obj.with_context(
|
||||
{'location': location.id}).browse(product.id)
|
||||
{'location': location.id}).browse(
|
||||
product_mrp_area.product_id.id)
|
||||
qty_available += product_l.qty_available
|
||||
|
||||
return {
|
||||
'mrp_area_id': mrp_area.id,
|
||||
'product_id': product.id,
|
||||
'mrp_qty_available': product.qty_available,
|
||||
'mrp_llc': product.llc,
|
||||
'name': '[%s] %s' % (mrp_area.name, product.display_name),
|
||||
'product_mrp_area_id': product_mrp_area.id,
|
||||
'mrp_qty_available': qty_available,
|
||||
'mrp_llc': product_mrp_area.product_id.llc,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _prepare_mrp_move_data_from_forecast(
|
||||
self, estimate, mrp_product, date):
|
||||
self, estimate, product_mrp_area, date):
|
||||
mrp_type = 'd'
|
||||
origin = 'fc'
|
||||
daily_qty = float_round(
|
||||
estimate.daily_qty,
|
||||
precision_rounding=mrp_product.product_id.uom_id.rounding,
|
||||
precision_rounding=product_mrp_area.product_id.uom_id.rounding,
|
||||
rounding_method='HALF-UP')
|
||||
return {
|
||||
'mrp_area_id': mrp_product.mrp_area_id.id,
|
||||
'product_id': mrp_product.product_id.id,
|
||||
'mrp_product_id': mrp_product.id,
|
||||
'mrp_area_id': product_mrp_area.mrp_area_id.id,
|
||||
'product_id': product_mrp_area.product_id.id,
|
||||
'product_mrp_area_id': product_mrp_area.id,
|
||||
'production_id': None,
|
||||
'purchase_order_id': None,
|
||||
'purchase_line_id': None,
|
||||
@@ -70,7 +69,7 @@ class MultiLevelMrp(models.TransientModel):
|
||||
|
||||
@api.model
|
||||
def _prepare_mrp_move_data_from_stock_move(
|
||||
self, mrp_product, move, direction='in'):
|
||||
self, product_mrp_area, move, direction='in'):
|
||||
if not((move.location_id.usage == 'internal' and
|
||||
move.location_dest_id.usage != 'internal')
|
||||
or (move.location_id.usage != 'internal' and
|
||||
@@ -117,9 +116,8 @@ class MultiLevelMrp(models.TransientModel):
|
||||
mrp_date = datetime.date(datetime.strptime(
|
||||
move.date_expected, DEFAULT_SERVER_DATETIME_FORMAT))
|
||||
return {
|
||||
'mrp_area_id': mrp_product.mrp_area_id.id,
|
||||
'product_id': move.product_id.id,
|
||||
'mrp_product_id': mrp_product.id,
|
||||
'product_mrp_area_id': product_mrp_area.id,
|
||||
'production_id': mo,
|
||||
'purchase_order_id': po,
|
||||
'purchase_line_id': po_line,
|
||||
@@ -141,12 +139,11 @@ class MultiLevelMrp(models.TransientModel):
|
||||
|
||||
@api.model
|
||||
def _prepare_mrp_move_data_supply(
|
||||
self, mrp_product, qty, mrp_date_supply, mrp_action_date,
|
||||
self, product_mrp_area, qty, mrp_date_supply, mrp_action_date,
|
||||
mrp_action, name):
|
||||
return {
|
||||
'mrp_area_id': mrp_product.mrp_area_id.id,
|
||||
'product_id': mrp_product.product_id.id,
|
||||
'mrp_product_id': mrp_product.id,
|
||||
'product_id': product_mrp_area.product_id.id,
|
||||
'product_mrp_area_id': product_mrp_area.id,
|
||||
'production_id': None,
|
||||
'purchase_order_id': None,
|
||||
'purchase_line_id': None,
|
||||
@@ -168,16 +165,16 @@ class MultiLevelMrp(models.TransientModel):
|
||||
@api.model
|
||||
def _prepare_mrp_move_data_bom_explosion(
|
||||
self, product, bomline, qty, mrp_date_demand_2, bom, name):
|
||||
mrp_product = self._get_mrp_product_from_product_and_area(
|
||||
product_mrp_area = self._get_product_mrp_area_from_product_and_area(
|
||||
bomline.product_id, product.mrp_area_id)
|
||||
if not mrp_product:
|
||||
if not product_mrp_area:
|
||||
raise exceptions.Warning(
|
||||
_("No MRP product found"))
|
||||
|
||||
return {
|
||||
'mrp_area_id': product.mrp_area_id.id,
|
||||
'product_id': bomline.product_id.id,
|
||||
'mrp_product_id': mrp_product.id,
|
||||
'product_mrp_area_id': product_mrp_area.id,
|
||||
'production_id': None,
|
||||
'purchase_order_id': None,
|
||||
'purchase_line_id': None,
|
||||
@@ -200,15 +197,15 @@ class MultiLevelMrp(models.TransientModel):
|
||||
}
|
||||
|
||||
@api.model
|
||||
def create_move(self, mrp_product_id, mrp_date, mrp_qty, name):
|
||||
def create_move(self, product_mrp_area_id, mrp_date, mrp_qty, name):
|
||||
self = self.with_context(auditlog_disabled=True)
|
||||
|
||||
values = {}
|
||||
if not isinstance(mrp_date, date):
|
||||
mrp_date = fields.Date.from_string(mrp_date)
|
||||
|
||||
if mrp_product_id.supply_method == 'buy':
|
||||
# if mrp_product_id.purchase_requisition:
|
||||
if product_mrp_area_id.supply_method == 'buy':
|
||||
# if product_mrp_area_id.purchase_requisition:
|
||||
# mrp_action = 'pr'
|
||||
# else:
|
||||
mrp_action = 'po'
|
||||
@@ -221,24 +218,24 @@ class MultiLevelMrp(models.TransientModel):
|
||||
else:
|
||||
mrp_date_supply = mrp_date
|
||||
|
||||
calendar = mrp_product_id.mrp_area_id.calendar_id
|
||||
if calendar and mrp_product_id.mrp_lead_time:
|
||||
calendar = product_mrp_area_id.mrp_area_id.calendar_id
|
||||
if calendar and product_mrp_area_id.mrp_lead_time:
|
||||
date_str = fields.Date.to_string(mrp_date)
|
||||
dt = fields.Datetime.from_string(date_str)
|
||||
res = calendar.plan_days(
|
||||
-1 * mrp_product_id.mrp_lead_time - 1, dt)
|
||||
-1 * product_mrp_area_id.mrp_lead_time - 1, dt)
|
||||
mrp_action_date = res.date()
|
||||
else:
|
||||
mrp_action_date = mrp_date - timedelta(
|
||||
days=mrp_product_id.mrp_lead_time)
|
||||
days=product_mrp_area_id.mrp_lead_time)
|
||||
|
||||
qty_ordered = 0.00
|
||||
qty_to_order = mrp_qty
|
||||
while qty_ordered < mrp_qty:
|
||||
qty = mrp_product_id._adjust_qty_to_order(qty_to_order)
|
||||
qty = product_mrp_area_id._adjust_qty_to_order(qty_to_order)
|
||||
qty_to_order -= qty
|
||||
move_data = self._prepare_mrp_move_data_supply(
|
||||
mrp_product_id, qty, mrp_date_supply, mrp_action_date,
|
||||
product_mrp_area_id, qty, mrp_date_supply, mrp_action_date,
|
||||
mrp_action, name)
|
||||
mrpmove_id = self.env['mrp.move'].create(move_data)
|
||||
qty_ordered = qty_ordered + qty
|
||||
@@ -247,10 +244,10 @@ class MultiLevelMrp(models.TransientModel):
|
||||
mrp_date_demand = mrp_action_date
|
||||
if mrp_date_demand < date.today():
|
||||
mrp_date_demand = date.today()
|
||||
if not mrp_product_id.product_id.bom_ids:
|
||||
if not product_mrp_area_id.product_id.bom_ids:
|
||||
continue
|
||||
bomcount = 0
|
||||
for bom in mrp_product_id.product_id.bom_ids:
|
||||
for bom in product_mrp_area_id.product_id.bom_ids:
|
||||
if not bom.active or not bom.bom_line_ids:
|
||||
continue
|
||||
bomcount += 1
|
||||
@@ -261,17 +258,17 @@ class MultiLevelMrp(models.TransientModel):
|
||||
bomline.product_id.type != 'product':
|
||||
continue
|
||||
if self._exclude_from_mrp(
|
||||
mrp_product_id.mrp_area_id,
|
||||
bomline.product_id):
|
||||
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=(mrp_product_id.mrp_transit_delay +
|
||||
mrp_product_id.mrp_inspection_delay))
|
||||
days=(product_mrp_area_id.mrp_transit_delay +
|
||||
product_mrp_area_id.mrp_inspection_delay))
|
||||
move_data = \
|
||||
self._prepare_mrp_move_data_bom_explosion(
|
||||
mrp_product_id, bomline, qty,
|
||||
product_mrp_area_id, bomline, qty,
|
||||
mrp_date_demand_2,
|
||||
bom, name)
|
||||
mrpmove_id2 = self.env['mrp.move'].create(move_data)
|
||||
@@ -288,7 +285,6 @@ class MultiLevelMrp(models.TransientModel):
|
||||
# installed
|
||||
logger.info('START MRP CLEANUP')
|
||||
self.env['mrp.move'].search([]).unlink()
|
||||
self.env['mrp.product'].search([]).unlink()
|
||||
self.env['mrp.inventory'].search([]).unlink()
|
||||
logger.info('END MRP CLEANUP')
|
||||
return True
|
||||
@@ -335,31 +331,25 @@ class MultiLevelMrp(models.TransientModel):
|
||||
@api.model
|
||||
def _calculate_mrp_applicable(self):
|
||||
logger.info('CALCULATE MRP APPLICABLE')
|
||||
self.env['product.product'].search([]).write({'mrp_applicable': False})
|
||||
self.env['product.product'].search([
|
||||
('type', '=', 'product'),
|
||||
self.env['product.mrp.area'].search([]).write(
|
||||
{'mrp_applicable': False})
|
||||
self.env['product.mrp.area'].search([
|
||||
('product_id.type', '=', 'product'),
|
||||
]).write({'mrp_applicable': True})
|
||||
self._adjust_mrp_applicable()
|
||||
counter = self.env['product.product'].search([
|
||||
counter = self.env['product.mrp.area'].search([
|
||||
('mrp_applicable', '=', True)], count=True)
|
||||
log_msg = 'END CALCULATE MRP APPLICABLE: %s' % counter
|
||||
logger.info(log_msg)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def _init_mrp_product(self, product, mrp_area):
|
||||
|
||||
mrp_product_data = self._prepare_mrp_product_data(
|
||||
product, mrp_area)
|
||||
return self.env['mrp.product'].create(mrp_product_data)
|
||||
|
||||
@api.model
|
||||
def _init_mrp_move_from_forecast(self, mrp_product):
|
||||
def _init_mrp_move_from_forecast(self, product_mrp_area):
|
||||
locations = self.env['stock.location'].search(
|
||||
[('id', 'child_of', mrp_product.mrp_area_id.location_id.id)])
|
||||
[('id', 'child_of', product_mrp_area.mrp_area_id.location_id.id)])
|
||||
today = fields.Date.today()
|
||||
estimates = self.env['stock.demand.estimate'].search([
|
||||
('product_id', '=', mrp_product.product_id.id),
|
||||
('product_id', '=', product_mrp_area.product_id.id),
|
||||
('location_id', 'in', locations.ids),
|
||||
('date_range_id.date_end', '>=', today)
|
||||
])
|
||||
@@ -373,19 +363,19 @@ class MultiLevelMrp(models.TransientModel):
|
||||
while mrp_date <= date_end:
|
||||
mrp_move_data = \
|
||||
self._prepare_mrp_move_data_from_forecast(
|
||||
rec, mrp_product, mrp_date)
|
||||
rec, product_mrp_area, mrp_date)
|
||||
self.env['mrp.move'].create(mrp_move_data)
|
||||
mrp_date += delta
|
||||
return True
|
||||
|
||||
# TODO: move this methods to mrp_product?? to be able to
|
||||
# TODO: move this methods to product_mrp_area?? to be able to
|
||||
# show moves with an action
|
||||
@api.model
|
||||
def _in_stock_moves_domain(self, mrp_product):
|
||||
def _in_stock_moves_domain(self, product_mrp_area):
|
||||
locations = self.env['stock.location'].search(
|
||||
[('id', 'child_of', mrp_product.mrp_area_id.location_id.id)])
|
||||
[('id', 'child_of', product_mrp_area.mrp_area_id.location_id.id)])
|
||||
return [
|
||||
('product_id', '=', mrp_product.product_id.id),
|
||||
('product_id', '=', product_mrp_area.product_id.id),
|
||||
('state', 'not in', ['done', 'cancel']),
|
||||
('product_qty', '>', 0.00),
|
||||
('location_id', 'not in', locations.ids),
|
||||
@@ -393,11 +383,11 @@ class MultiLevelMrp(models.TransientModel):
|
||||
]
|
||||
|
||||
@api.model
|
||||
def _out_stock_moves_domain(self, mrp_product):
|
||||
def _out_stock_moves_domain(self, product_mrp_area):
|
||||
locations = self.env['stock.location'].search(
|
||||
[('id', 'child_of', mrp_product.mrp_area_id.location_id.id)])
|
||||
[('id', 'child_of', product_mrp_area.mrp_area_id.location_id.id)])
|
||||
return [
|
||||
('product_id', '=', mrp_product.product_id.id),
|
||||
('product_id', '=', product_mrp_area.product_id.id),
|
||||
('state', 'not in', ['done', 'cancel']),
|
||||
('product_qty', '>', 0.00),
|
||||
('location_id', 'in', locations.ids),
|
||||
@@ -405,34 +395,34 @@ class MultiLevelMrp(models.TransientModel):
|
||||
]
|
||||
|
||||
@api.model
|
||||
def _init_mrp_move_from_stock_move(self, mrp_product):
|
||||
def _init_mrp_move_from_stock_move(self, product_mrp_area):
|
||||
# TODO: Should we exclude the quantity done from the moves?
|
||||
move_obj = self.env['stock.move']
|
||||
mrp_move_obj = self.env['mrp.move']
|
||||
in_domain = self._in_stock_moves_domain(mrp_product)
|
||||
in_domain = self._in_stock_moves_domain(product_mrp_area)
|
||||
in_moves = move_obj.search(in_domain)
|
||||
out_domain = self._out_stock_moves_domain(mrp_product)
|
||||
out_domain = self._out_stock_moves_domain(product_mrp_area)
|
||||
out_moves = move_obj.search(out_domain)
|
||||
if in_moves:
|
||||
for move in in_moves:
|
||||
move_data = self._prepare_mrp_move_data_from_stock_move(
|
||||
mrp_product, move, direction='in')
|
||||
product_mrp_area, move, direction='in')
|
||||
mrp_move_obj.create(move_data)
|
||||
if out_moves:
|
||||
for move in out_moves:
|
||||
move_data = self._prepare_mrp_move_data_from_stock_move(
|
||||
mrp_product, move, direction='out')
|
||||
product_mrp_area, move, direction='out')
|
||||
mrp_move_obj.create(move_data)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def _prepare_mrp_move_data_from_purchase_order(self, poline, mrp_product):
|
||||
def _prepare_mrp_move_data_from_purchase_order(self, poline, product_mrp_area):
|
||||
mrp_date = date.today()
|
||||
if fields.Date.from_string(poline.date_planned) > date.today():
|
||||
mrp_date = fields.Date.from_string(poline.date_planned)
|
||||
return {
|
||||
'product_id': poline.product_id.id,
|
||||
'mrp_product_id': mrp_product.id,
|
||||
'product_mrp_area_id': product_mrp_area.id,
|
||||
'production_id': None,
|
||||
'purchase_order_id': poline.order_id.id,
|
||||
'purchase_line_id': poline.id,
|
||||
@@ -453,9 +443,9 @@ class MultiLevelMrp(models.TransientModel):
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _init_mrp_move_from_purchase_order(self, mrp_product):
|
||||
def _init_mrp_move_from_purchase_order(self, product_mrp_area):
|
||||
location_ids = self.env['stock.location'].search(
|
||||
[('id', 'child_of', mrp_product.mrp_area_id.location_id.id)])
|
||||
[('id', 'child_of', product_mrp_area.mrp_area_id.location_id.id)])
|
||||
picking_types = self.env['stock.picking.type'].search(
|
||||
[('default_location_dest_id', 'in',
|
||||
location_ids.ids)])
|
||||
@@ -466,58 +456,63 @@ class MultiLevelMrp(models.TransientModel):
|
||||
po_lines = self.env['purchase.order.line'].search(
|
||||
[('order_id', 'in', orders.ids),
|
||||
('product_qty', '>', 0.0),
|
||||
('product_id', '=', mrp_product.product_id.id)])
|
||||
('product_id', '=', product_mrp_area.product_id.id)])
|
||||
|
||||
for line in po_lines:
|
||||
mrp_move_data = \
|
||||
self._prepare_mrp_move_data_from_purchase_order(
|
||||
line, mrp_product)
|
||||
line, product_mrp_area)
|
||||
self.env['mrp.move'].create(mrp_move_data)
|
||||
|
||||
@api.model
|
||||
def _get_mrp_product_from_product_and_area(self, product, mrp_area):
|
||||
return self.env['mrp.product'].search([
|
||||
def _get_product_mrp_area_from_product_and_area(self, product, mrp_area):
|
||||
return self.env['product.mrp.area'].search([
|
||||
('product_id', '=', product.id),
|
||||
('mrp_area_id', '=', mrp_area.id),
|
||||
], limit=1)
|
||||
|
||||
@api.model
|
||||
def _init_mrp_move(self, mrp_product):
|
||||
self._init_mrp_move_from_forecast(mrp_product)
|
||||
self._init_mrp_move_from_stock_move(mrp_product)
|
||||
self._init_mrp_move_from_purchase_order(mrp_product)
|
||||
def _init_mrp_move(self, product_mrp_area):
|
||||
self._init_mrp_move_from_forecast(product_mrp_area)
|
||||
self._init_mrp_move_from_stock_move(product_mrp_area)
|
||||
self._init_mrp_move_from_purchase_order(product_mrp_area)
|
||||
|
||||
@api.model
|
||||
def _exclude_from_mrp(self, mrp_area, product):
|
||||
def _exclude_from_mrp(self, product, mrp_area):
|
||||
""" To extend with various logic where needed. """
|
||||
return product.mrp_exclude
|
||||
product_mrp_area = self.env['product.mrp.area'].search(
|
||||
[('product_id', '=', product.id),
|
||||
('mrp_area_id', '=', mrp_area.id)], limit=1)
|
||||
if not product_mrp_area:
|
||||
return True
|
||||
return product_mrp_area.mrp_exclude
|
||||
|
||||
@api.model
|
||||
def _mrp_initialisation(self):
|
||||
logger.info('START MRP INITIALISATION')
|
||||
mrp_areas = self.env['mrp.area'].search([])
|
||||
products = self.env['product.product'].search([
|
||||
product_mrp_areas = self.env['product.mrp.area'].search([
|
||||
('mrp_applicable', '=', True)])
|
||||
init_counter = 0
|
||||
for mrp_area in mrp_areas:
|
||||
for product in products:
|
||||
if self._exclude_from_mrp(mrp_area, product):
|
||||
for product_mrp_area in product_mrp_areas.filtered(
|
||||
lambda a: a.mrp_area_id == mrp_area):
|
||||
if product_mrp_area.mrp_exclude:
|
||||
continue
|
||||
init_counter += 1
|
||||
log_msg = 'MRP INIT: %s - %s ' % (
|
||||
init_counter, product.default_code)
|
||||
init_counter, product_mrp_area.display_name)
|
||||
logger.info(log_msg)
|
||||
mrp_product = self._init_mrp_product(product, mrp_area)
|
||||
self._init_mrp_move(mrp_product)
|
||||
self._init_mrp_move(product_mrp_area)
|
||||
logger.info('END MRP INITIALISATION')
|
||||
|
||||
@api.model
|
||||
def _init_mrp_move_grouped_demand(self, nbr_create, mrp_product):
|
||||
def _init_mrp_move_grouped_demand(self, nbr_create, product_mrp_area):
|
||||
last_date = None
|
||||
last_qty = 0.00
|
||||
onhand = mrp_product.mrp_qty_available
|
||||
onhand = product_mrp_area.qty_available
|
||||
move_ids = []
|
||||
for move in mrp_product.mrp_move_ids:
|
||||
for move in product_mrp_area.mrp_move_ids:
|
||||
move_ids.append(move.id)
|
||||
for move_id in move_ids:
|
||||
move_rec = self.env['mrp.move'].search(
|
||||
@@ -529,18 +524,18 @@ class MultiLevelMrp(models.TransientModel):
|
||||
datetime.strptime(
|
||||
move.mrp_date, '%Y-%m-%d')) \
|
||||
> last_date+timedelta(
|
||||
days=mrp_product.mrp_nbr_days):
|
||||
days=product_mrp_area.mrp_nbr_days):
|
||||
if (onhand + last_qty + move.mrp_qty) \
|
||||
< mrp_product.mrp_minimum_stock \
|
||||
< product_mrp_area.mrp_minimum_stock \
|
||||
or (onhand + last_qty) \
|
||||
< mrp_product.mrp_minimum_stock:
|
||||
< product_mrp_area.mrp_minimum_stock:
|
||||
name = 'Grouped Demand for %d Days' % \
|
||||
mrp_product.mrp_nbr_days
|
||||
product_mrp_area.mrp_nbr_days
|
||||
qtytoorder = \
|
||||
mrp_product.mrp_minimum_stock - \
|
||||
mrp_product - last_qty
|
||||
product_mrp_area.mrp_minimum_stock - \
|
||||
product_mrp_area - last_qty
|
||||
cm = self.create_move(
|
||||
mrp_product_id=mrp_product.id,
|
||||
product_mrp_area_id=product_mrp_area.id,
|
||||
mrp_date=last_date,
|
||||
mrp_qty=qtytoorder,
|
||||
name=name)
|
||||
@@ -550,9 +545,9 @@ class MultiLevelMrp(models.TransientModel):
|
||||
last_qty = 0.00
|
||||
nbr_create += 1
|
||||
if (onhand + last_qty + move.mrp_qty) < \
|
||||
mrp_product.mrp_minimum_stock or \
|
||||
product_mrp_area.mrp_minimum_stock or \
|
||||
(onhand + last_qty) < \
|
||||
mrp_product.mrp_minimum_stock:
|
||||
product_mrp_area.mrp_minimum_stock:
|
||||
if last_date is None:
|
||||
last_date = datetime.date(
|
||||
datetime.strptime(move.mrp_date,
|
||||
@@ -568,10 +563,10 @@ class MultiLevelMrp(models.TransientModel):
|
||||
|
||||
if last_date is not None and last_qty != 0.00:
|
||||
name = 'Grouped Demand for %d Days' % \
|
||||
(mrp_product.mrp_nbr_days, )
|
||||
qtytoorder = mrp_product.mrp_minimum_stock - onhand - last_qty
|
||||
(product_mrp_area.mrp_nbr_days, )
|
||||
qtytoorder = product_mrp_area.mrp_minimum_stock - onhand - last_qty
|
||||
cm = self.create_move(
|
||||
mrp_product_id=mrp_product.id, mrp_date=last_date,
|
||||
product_mrp_area_id=product_mrp_area.id, mrp_date=last_date,
|
||||
mrp_qty=qtytoorder, name=name)
|
||||
qty_ordered = cm['qty_ordered']
|
||||
onhand += qty_ordered
|
||||
@@ -581,30 +576,31 @@ class MultiLevelMrp(models.TransientModel):
|
||||
@api.model
|
||||
def _mrp_calculation(self, mrp_lowest_llc):
|
||||
logger.info('START MRP CALCULATION')
|
||||
mrp_product_obj = self.env['mrp.product']
|
||||
product_mrp_area_obj = self.env['product.mrp.area']
|
||||
counter = 0
|
||||
for mrp_area in self.env['mrp.area'].search([]):
|
||||
llc = 0
|
||||
while mrp_lowest_llc > llc:
|
||||
mrp_products = mrp_product_obj.search(
|
||||
[('mrp_llc', '=', llc),
|
||||
product_mrp_areas = product_mrp_area_obj.search(
|
||||
[('product_id.llc', '=', llc),
|
||||
('mrp_area_id', '=', mrp_area.id)])
|
||||
llc += 1
|
||||
|
||||
for mrp_product in mrp_products:
|
||||
for product_mrp_area in product_mrp_areas:
|
||||
nbr_create = 0
|
||||
onhand = mrp_product.mrp_qty_available # TODO: unreserved?
|
||||
if mrp_product.mrp_nbr_days == 0:
|
||||
onhand = product_mrp_area.qty_available
|
||||
# TODO: unreserved?
|
||||
if product_mrp_area.mrp_nbr_days == 0:
|
||||
# todo: review ordering by date
|
||||
for move in mrp_product.mrp_move_ids:
|
||||
for move in product_mrp_area.mrp_move_ids:
|
||||
if move.mrp_action == 'none':
|
||||
if (onhand + move.mrp_qty) < \
|
||||
mrp_product.mrp_minimum_stock:
|
||||
product_mrp_area.mrp_minimum_stock:
|
||||
qtytoorder = \
|
||||
mrp_product.mrp_minimum_stock - \
|
||||
product_mrp_area.mrp_minimum_stock - \
|
||||
onhand - move.mrp_qty
|
||||
cm = self.create_move(
|
||||
mrp_product_id=mrp_product,
|
||||
product_mrp_area_id=product_mrp_area,
|
||||
mrp_date=move.mrp_date,
|
||||
mrp_qty=qtytoorder, name=move.name)
|
||||
qty_ordered = cm['qty_ordered']
|
||||
@@ -615,13 +611,13 @@ class MultiLevelMrp(models.TransientModel):
|
||||
else:
|
||||
# TODO: review this
|
||||
nbr_create = self._init_mrp_move_grouped_demand(
|
||||
nbr_create, mrp_product)
|
||||
nbr_create, product_mrp_area)
|
||||
|
||||
if onhand < mrp_product.mrp_minimum_stock and \
|
||||
if onhand < product_mrp_area.mrp_minimum_stock and \
|
||||
nbr_create == 0:
|
||||
qtytoorder = mrp_product.mrp_minimum_stock - onhand
|
||||
qtytoorder = product_mrp_area.mrp_minimum_stock - onhand
|
||||
cm = self.create_move(
|
||||
mrp_product_id=mrp_product,
|
||||
product_mrp_area_id=product_mrp_area,
|
||||
mrp_date=date.today(),
|
||||
mrp_qty=qtytoorder,
|
||||
name='Minimum Stock')
|
||||
@@ -636,64 +632,64 @@ class MultiLevelMrp(models.TransientModel):
|
||||
logger.info('END MRP CALCULATION')
|
||||
|
||||
@api.model
|
||||
def _get_demand_groups(self, mrp_product):
|
||||
def _get_demand_groups(self, product_mrp_area):
|
||||
query = """
|
||||
SELECT mrp_date, sum(mrp_qty)
|
||||
FROM mrp_move
|
||||
WHERE mrp_product_id = %(mrp_product)s
|
||||
WHERE product_mrp_area_id = %(mrp_product)s
|
||||
AND mrp_type = 'd'
|
||||
GROUP BY mrp_date
|
||||
"""
|
||||
params = {
|
||||
'mrp_product': mrp_product.id
|
||||
'mrp_product': product_mrp_area.id
|
||||
}
|
||||
return query, params
|
||||
|
||||
@api.model
|
||||
def _get_supply_groups(self, mrp_product):
|
||||
def _get_supply_groups(self, product_mrp_area):
|
||||
query = """
|
||||
SELECT mrp_date, sum(mrp_qty)
|
||||
FROM mrp_move
|
||||
WHERE mrp_product_id = %(mrp_product)s
|
||||
WHERE product_mrp_area_id = %(mrp_product)s
|
||||
AND mrp_type = 's'
|
||||
AND mrp_action = 'none'
|
||||
GROUP BY mrp_date
|
||||
"""
|
||||
params = {
|
||||
'mrp_product': mrp_product.id
|
||||
'mrp_product': product_mrp_area.id
|
||||
}
|
||||
return query, params
|
||||
|
||||
@api.model
|
||||
def _get_supply_action_groups(self, mrp_product):
|
||||
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 mrp_product_id = %(mrp_product)s
|
||||
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': mrp_product.id,
|
||||
'mrp_product': product_mrp_area.id,
|
||||
'excluded_mrp_actions': tuple(exclude_mrp_actions,)
|
||||
}
|
||||
return query, params
|
||||
|
||||
@api.model
|
||||
def _init_mrp_inventory(self, mrp_product):
|
||||
def _init_mrp_inventory(self, product_mrp_area):
|
||||
mrp_move_obj = self.env['mrp.move']
|
||||
# Read Demand
|
||||
demand_qty_by_date = {}
|
||||
query, params = self._get_demand_groups(mrp_product)
|
||||
query, params = self._get_demand_groups(product_mrp_area)
|
||||
self.env.cr.execute(query, params)
|
||||
for mrp_date, qty in self.env.cr.fetchall():
|
||||
demand_qty_by_date[mrp_date] = qty
|
||||
# Read Supply
|
||||
supply_qty_by_date = {}
|
||||
query, params = self._get_supply_groups(mrp_product)
|
||||
query, params = self._get_supply_groups(product_mrp_area)
|
||||
self.env.cr.execute(query, params)
|
||||
for mrp_date, qty in self.env.cr.fetchall():
|
||||
supply_qty_by_date[mrp_date] = qty
|
||||
@@ -701,19 +697,22 @@ class MultiLevelMrp(models.TransientModel):
|
||||
# TODO: if we remove cancel take it into account here,
|
||||
# TODO: as well as mrp_type ('r').
|
||||
supply_actions_qty_by_date = {}
|
||||
query, params = self._get_supply_action_groups(mrp_product)
|
||||
query, params = self._get_supply_action_groups(product_mrp_area)
|
||||
self.env.cr.execute(query, params)
|
||||
for mrp_date, qty in self.env.cr.fetchall():
|
||||
supply_actions_qty_by_date[mrp_date] = qty
|
||||
# Dates
|
||||
mrp_dates = set(mrp_move_obj.search([
|
||||
('mrp_product_id', '=', mrp_product.id)],
|
||||
('product_mrp_area_id', '=', product_mrp_area.id)],
|
||||
order='mrp_date').mapped('mrp_date'))
|
||||
|
||||
on_hand_qty = mrp_product.current_qty_available # TODO: unreserved?
|
||||
on_hand_qty = product_mrp_area.product_id.with_context(
|
||||
location=product_mrp_area.mrp_area_id.location_id.id
|
||||
)._product_available()[
|
||||
product_mrp_area.product_id.id]['qty_available']
|
||||
# TODO: unreserved?
|
||||
for mdt in sorted(mrp_dates):
|
||||
mrp_inventory_data = {
|
||||
'mrp_product_id': mrp_product.id,
|
||||
'product_mrp_area_id': product_mrp_area.id,
|
||||
'date': mdt,
|
||||
}
|
||||
demand_qty = 0.0
|
||||
@@ -736,32 +735,31 @@ class MultiLevelMrp(models.TransientModel):
|
||||
@api.model
|
||||
def _mrp_final_process(self):
|
||||
logger.info('START MRP FINAL PROCESS')
|
||||
mrp_product_ids = self.env['mrp.product'].search([
|
||||
('mrp_llc', '<', 9999),
|
||||
('mrp_area_id', '!=', False)])
|
||||
product_mrp_area_ids = self.env['product.mrp.area'].search([
|
||||
('product_id.llc', '<', 9999)])
|
||||
|
||||
for mrp_product in mrp_product_ids:
|
||||
for product_mrp_area in product_mrp_area_ids:
|
||||
# Build the time-phased inventory
|
||||
self._init_mrp_inventory(mrp_product)
|
||||
self._init_mrp_inventory(product_mrp_area)
|
||||
|
||||
# Complete info on mrp_move (running availability and nbr actions)
|
||||
qoh = mrp_product.mrp_qty_available
|
||||
qoh = product_mrp_area.qty_available
|
||||
|
||||
moves = self.env['mrp.move'].search([
|
||||
('mrp_product_id', '=', mrp_product.id)],
|
||||
('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
|
||||
|
||||
nbr_actions = mrp_product.mrp_move_ids.filtered(
|
||||
nbr_actions = product_mrp_area.mrp_move_ids.filtered(
|
||||
lambda m: m.mrp_action != 'none')
|
||||
horizon_4w = fields.Date.to_string(
|
||||
date.today() + timedelta(weeks=4))
|
||||
nbr_actions_4w = nbr_actions.filtered(
|
||||
lambda m: m.mrp_action_date < horizon_4w)
|
||||
if nbr_actions:
|
||||
mrp_product.write({
|
||||
product_mrp_area.write({
|
||||
'nbr_mrp_actions': len(nbr_actions),
|
||||
'nbr_mrp_actions_4w': len(nbr_actions_4w),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user