mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
[mig] multi_level_mrp
This commit is contained in:
75
multi_level_mrp/README.rst
Normal file
75
multi_level_mrp/README.rst
Normal file
@@ -0,0 +1,75 @@
|
||||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
|
||||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||
:alt: License: AGPL-3
|
||||
|
||||
===============
|
||||
Multi Level MRP
|
||||
===============
|
||||
|
||||
This module allows you to calculate, based in known inventory, demand, and
|
||||
supply, and based on parameters set at product variant level, the new
|
||||
procurements for each product.
|
||||
|
||||
To do this, the calculation starts at top level of the bill of material
|
||||
and explodes this down to the lowest level.
|
||||
|
||||
Key Features
|
||||
------------
|
||||
* MRP parameters at product variant level
|
||||
* Basic forecast system
|
||||
* Cron job to calculate the MRP demand
|
||||
* Manually calculate the MRP demand
|
||||
* Confirm the calculated MRP demand and create BID's, PO's, or MO's
|
||||
* View to see the products for which action is needed
|
||||
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
|
||||
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
|
||||
:alt: Try me on Runbot
|
||||
:target: https://runbot.odoo-community.org/runbot/129/11.0
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
||||
Bugs are tracked on `GitHub Issues
|
||||
<https://github.com/OCA/manufacture/issues>`_. In case of trouble, please
|
||||
check there if your issue has already been reported. If you spotted it first,
|
||||
help us smash it by providing detailed and welcomed feedback.
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
Images
|
||||
------
|
||||
|
||||
* Original Odoo icons.
|
||||
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
* Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
* Jordi Ballester <jordi.ballester@eficent.com>
|
||||
|
||||
|
||||
Maintainer
|
||||
----------
|
||||
|
||||
.. image:: https://odoo-community.org/logo.png
|
||||
:alt: Odoo Community Association
|
||||
:target: https://odoo-community.org
|
||||
|
||||
This module is maintained by the OCA.
|
||||
|
||||
OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||
mission is to support the collaborative development of Odoo features and
|
||||
promote its widespread use.
|
||||
|
||||
To contribute to this module, please visit https://odoo-community.org.
|
||||
2
multi_level_mrp/__init__.py
Normal file
2
multi_level_mrp/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import models
|
||||
from . import wizards
|
||||
31
multi_level_mrp/__manifest__.py
Normal file
31
multi_level_mrp/__manifest__.py
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
'name': 'Multi Level MRP',
|
||||
'version': '11.0.1.0.0',
|
||||
'author': 'Ucamco, '
|
||||
'Eficent, '
|
||||
'Odoo Community Association (OCA)',
|
||||
'summary': 'Adds an MRP Scheduler',
|
||||
'website': 'https://github.com/OCA/manufacture',
|
||||
'category': 'Manufacturing',
|
||||
'depends': [
|
||||
'mrp',
|
||||
'stock',
|
||||
'purchase',
|
||||
],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'views/mrp_forecast_view.xml',
|
||||
'views/mrp_area_view.xml',
|
||||
'views/product_view.xml',
|
||||
'views/stock_location_view.xml',
|
||||
'views/mrp_product_view.xml',
|
||||
'wizards/mrp_inventory_create_procurement_view.xml',
|
||||
'views/mrp_inventory_view.xml',
|
||||
'wizards/multi_level_mrp_view.xml',
|
||||
'wizards/mrp_move_create_po_view.xml',
|
||||
'views/mrp_menuitem.xml',
|
||||
'data/multi_level_mrp_cron.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
}
|
||||
19
multi_level_mrp/data/multi_level_mrp_cron.xml
Executable file
19
multi_level_mrp/data/multi_level_mrp_cron.xml
Executable file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="multi_level_mrp_cron" model="ir.cron">
|
||||
<field name="name">Multi Level MRP</field>
|
||||
<field name="active" eval="True"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="doall" eval="False"/>
|
||||
<field name="model" eval="'multi.level.mrp'"/>
|
||||
<field name="function" eval="'run_multi_level_mrp'" />
|
||||
<field name="args" eval="'(None, )'"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
7
multi_level_mrp/models/__init__.py
Normal file
7
multi_level_mrp/models/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from . import mrp_area
|
||||
from . import stock_location
|
||||
from . import mrp_forecast
|
||||
from . import product
|
||||
from . import mrp_product
|
||||
from . import mrp_move
|
||||
from . import mrp_inventory
|
||||
19
multi_level_mrp/models/mrp_area.py
Normal file
19
multi_level_mrp/models/mrp_area.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
# © 2016 Eficent Business and IT Consulting Services S.L.
|
||||
# - Jordi Ballester Alomar <jordi.ballester@eficent.com>
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class MrpArea(models.Model):
|
||||
_name = 'mrp.area'
|
||||
|
||||
name = fields.Char('Name')
|
||||
warehouse_id = fields.Many2one(
|
||||
comodel_name='stock.warehouse', string='Warehouse',
|
||||
required=True)
|
||||
location_id = fields.Many2one(
|
||||
comodel_name='stock.location', string='Location',
|
||||
required=True)
|
||||
active = fields.Boolean(default=True)
|
||||
197
multi_level_mrp/models/mrp_forecast.py
Normal file
197
multi_level_mrp/models/mrp_forecast.py
Normal file
@@ -0,0 +1,197 @@
|
||||
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
# © 2016 Eficent Business and IT Consulting Services S.L.
|
||||
# - Jordi Ballester Alomar <jordi.ballester@eficent.com>
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import api, fields, models
|
||||
from datetime import date, datetime
|
||||
|
||||
|
||||
class MrpForecastForecast(models.Model):
|
||||
_name = 'mrp.forecast.forecast'
|
||||
|
||||
date = fields.Date('Date')
|
||||
forecast_product_id = fields.Many2one('mrp.forecast.product', 'Product',
|
||||
select=True)
|
||||
name = fields.Char('Description')
|
||||
qty_forecast = fields.Float('Quantity')
|
||||
|
||||
_order = 'forecast_product_id, date'
|
||||
|
||||
|
||||
class MrpForecastProduct(models.Model):
|
||||
_name = 'mrp.forecast.product'
|
||||
|
||||
@api.one
|
||||
@api.depends('product_id')
|
||||
def _function_name(self):
|
||||
if self.product_id:
|
||||
self.name = "[%s] %s" % (self.product_id.default_code,
|
||||
self.product_id.name_template, )
|
||||
|
||||
@api.one
|
||||
@api.depends('mrp_forecast_ids')
|
||||
def _function_forecast_m0(self):
|
||||
qty = 0.00
|
||||
for fc in self.mrp_forecast_ids:
|
||||
fcmonth = (datetime.date(datetime.strptime(
|
||||
fc.date, '%Y-%m-%d')).year * 100) + \
|
||||
datetime.date(datetime.strptime(
|
||||
fc.date, '%Y-%m-%d')).month
|
||||
tmonth = (date.today().year * 100) + date.today().month
|
||||
if not(datetime.date(datetime.strptime(
|
||||
fc.date, '%Y-%m-%d')) < date.today()) \
|
||||
and fcmonth == tmonth:
|
||||
qty += fc.qty_forecast
|
||||
self.qty_forecast_m0 = qty
|
||||
|
||||
@api.one
|
||||
@api.depends('mrp_forecast_ids')
|
||||
def _function_forecast_m1(self):
|
||||
qty = 0.00
|
||||
for fc in self.mrp_forecast_ids:
|
||||
fcmonth = (datetime.date(datetime.strptime(
|
||||
fc.date, '%Y-%m-%d')).year * 100) + datetime.date(
|
||||
datetime.strptime(fc.date, '%Y-%m-%d')).month
|
||||
tdyear = date.today().year
|
||||
tdmonth = date.today().month + 1
|
||||
if tdmonth > 12:
|
||||
tdmonth -= 12
|
||||
tdyear += 1
|
||||
tmonth = (tdyear * 100) + tdmonth
|
||||
if fcmonth == tmonth:
|
||||
qty += fc.qty_forecast
|
||||
self.qty_forecast_m1 = qty
|
||||
|
||||
@api.one
|
||||
@api.depends('mrp_forecast_ids')
|
||||
def _function_forecast_m2(self):
|
||||
qty = 0.00
|
||||
for fc in self.mrp_forecast_ids:
|
||||
fcmonth = (datetime.date(datetime.strptime(
|
||||
fc.date, '%Y-%m-%d')).year * 100) + datetime.date(
|
||||
datetime.strptime(fc.date, '%Y-%m-%d')).month
|
||||
tdyear = date.today().year
|
||||
tdmonth = date.today().month + 2
|
||||
if tdmonth > 12:
|
||||
tdmonth -= 12
|
||||
tdyear += 1
|
||||
tmonth = (tdyear * 100) + tdmonth
|
||||
if fcmonth == tmonth:
|
||||
qty += fc.qty_forecast
|
||||
self.qty_forecast_m2 = qty
|
||||
|
||||
@api.one
|
||||
@api.depends('mrp_forecast_ids')
|
||||
def _function_forecast_m3(self):
|
||||
qty = 0.00
|
||||
for fc in self.mrp_forecast_ids:
|
||||
fcmonth = (datetime.date(datetime.strptime(
|
||||
fc.date, '%Y-%m-%d')).year * 100) + datetime.date(
|
||||
datetime.strptime(fc.date, '%Y-%m-%d')).month
|
||||
tdyear = date.today().year
|
||||
tdmonth = date.today().month + 3
|
||||
if tdmonth > 12:
|
||||
tdmonth -= 12
|
||||
tdyear += 1
|
||||
tmonth = (tdyear * 100) + tdmonth
|
||||
if fcmonth == tmonth:
|
||||
qty += fc.qty_forecast
|
||||
self.qty_forecast_m3 = qty
|
||||
|
||||
@api.one
|
||||
@api.depends('mrp_forecast_ids')
|
||||
def _function_forecast_m4(self):
|
||||
qty = 0.00
|
||||
for fc in self.mrp_forecast_ids:
|
||||
fcmonth = (datetime.date(datetime.strptime(
|
||||
fc.date, '%Y-%m-%d')).year * 100) + datetime.date(
|
||||
datetime.strptime(fc.date, '%Y-%m-%d')).month
|
||||
tdyear = date.today().year
|
||||
tdmonth = date.today().month + 4
|
||||
if tdmonth > 12:
|
||||
tdmonth -= 12
|
||||
tdyear += 1
|
||||
tmonth = (tdyear * 100) + tdmonth
|
||||
if fcmonth == tmonth:
|
||||
qty += fc.qty_forecast
|
||||
self.qty_forecast_m4 = qty
|
||||
|
||||
@api.one
|
||||
@api.depends('mrp_forecast_ids')
|
||||
def _function_forecast_m5(self):
|
||||
qty = 0.00
|
||||
for fc in self.mrp_forecast_ids:
|
||||
fcmonth = (datetime.date(datetime.strptime(
|
||||
fc.date, '%Y-%m-%d')).year * 100) + datetime.date(
|
||||
datetime.strptime(fc.date, '%Y-%m-%d')).month
|
||||
tdyear = date.today().year
|
||||
tdmonth = date.today().month + 5
|
||||
if tdmonth > 12:
|
||||
tdmonth -= 12
|
||||
tdyear += 1
|
||||
tmonth = (tdyear * 100) + tdmonth
|
||||
if fcmonth == tmonth:
|
||||
qty += fc.qty_forecast
|
||||
self.qty_forecast_m5 = qty
|
||||
|
||||
@api.one
|
||||
@api.depends('mrp_forecast_ids')
|
||||
def _function_forecast_m6(self):
|
||||
qty = 0.00
|
||||
for fc in self.mrp_forecast_ids:
|
||||
fcmonth = (datetime.date(datetime.strptime(
|
||||
fc.date, '%Y-%m-%d')).year * 100) + datetime.date(
|
||||
datetime.strptime(fc.date, '%Y-%m-%d')).month
|
||||
tdyear = date.today().year
|
||||
tdmonth = date.today().month + 6
|
||||
if tdmonth > 12:
|
||||
tdmonth -= 12
|
||||
tdyear += 1
|
||||
tmonth = (tdyear * 100) + tdmonth
|
||||
if fcmonth == tmonth:
|
||||
qty += fc.qty_forecast
|
||||
self.qty_forecast_m6 = qty
|
||||
|
||||
@api.one
|
||||
@api.depends('mrp_forecast_ids')
|
||||
def _function_forecast_past(self):
|
||||
qty = 0.00
|
||||
for fc in self.mrp_forecast_ids:
|
||||
if datetime.date(
|
||||
datetime.strptime(fc.date, '%Y-%m-%d')) < date.today():
|
||||
qty += fc.qty_forecast
|
||||
self.qty_forecast_past = qty
|
||||
|
||||
@api.one
|
||||
@api.depends('mrp_forecast_ids')
|
||||
def _function_forecast_total(self):
|
||||
qty = 0.00
|
||||
for fc in self.mrp_forecast_ids:
|
||||
qty += fc.qty_forecast
|
||||
self.qty_forecast_total = qty
|
||||
|
||||
mrp_forecast_ids = fields.One2many('mrp.forecast.forecast',
|
||||
'forecast_product_id',
|
||||
'Forecast')
|
||||
name = fields.Char(compute='_function_name', string='Description')
|
||||
product_id = fields.Many2one('product.product', 'Product', select=True)
|
||||
mrp_area_id = fields.Many2one('mrp.area', 'MRP Area')
|
||||
qty_forecast_m0 = fields.Float(compute='_function_forecast_m0',
|
||||
string='This Month Forecast')
|
||||
qty_forecast_m1 = fields.Float(compute='_function_forecast_m1',
|
||||
string='Month+1 Forecast')
|
||||
qty_forecast_m2 = fields.Float(compute='_function_forecast_m2',
|
||||
string='Month+2 Forecast')
|
||||
qty_forecast_m3 = fields.Float(compute='_function_forecast_m3',
|
||||
string='Month+3 Forecast')
|
||||
qty_forecast_m4 = fields.Float(compute='_function_forecast_m4',
|
||||
string='Month+4 Forecast')
|
||||
qty_forecast_m5 = fields.Float(compute='_function_forecast_m5',
|
||||
string='Month+5 Forecast')
|
||||
qty_forecast_m6 = fields.Float(compute='_function_forecast_m6',
|
||||
string='Month+6 Forecast')
|
||||
qty_forecast_past = fields.Float(compute='_function_forecast_past',
|
||||
string='Past Forecast')
|
||||
qty_forecast_total = fields.Float(compute='_function_forecast_total',
|
||||
string='Total Forecast')
|
||||
23
multi_level_mrp/models/mrp_inventory.py
Normal file
23
multi_level_mrp/models/mrp_inventory.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
# © 2016 Eficent Business and IT Consulting Services S.L.
|
||||
# - Jordi Ballester Alomar <jordi.ballester@eficent.com>
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class MrpInventory(models.Model):
|
||||
_name = 'mrp.inventory'
|
||||
|
||||
mrp_area_id = fields.Many2one('mrp.area', 'MRP Area',
|
||||
related='mrp_product_id.mrp_area_id')
|
||||
mrp_product_id = fields.Many2one('mrp.product', 'Product',
|
||||
select=True)
|
||||
date = fields.Date('Date')
|
||||
demand_qty = fields.Float('Demand')
|
||||
supply_qty = fields.Float('Supply')
|
||||
initial_on_hand_qty = fields.Float('Starting Inventory')
|
||||
final_on_hand_qty = fields.Float('Forecasted Inventory')
|
||||
to_procure = fields.Float('To procure')
|
||||
|
||||
_order = 'mrp_product_id, date'
|
||||
186
multi_level_mrp/models/mrp_move.py
Normal file
186
multi_level_mrp/models/mrp_move.py
Normal file
@@ -0,0 +1,186 @@
|
||||
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
# © 2016 Eficent Business and IT Consulting Services S.L.
|
||||
# - Jordi Ballester Alomar <jordi.ballester@eficent.com>
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo import exceptions
|
||||
|
||||
|
||||
class MrpMove(models.Model):
|
||||
_name = 'mrp.move'
|
||||
|
||||
mrp_area_id = fields.Many2one('mrp.area', 'MRP Area')
|
||||
current_date = fields.Date('Current Date')
|
||||
current_qty = fields.Float('Current Qty')
|
||||
mrp_action = fields.Selection((('mo', 'Manufacturing Order'),
|
||||
('po', 'Purchase Order'),
|
||||
('pr', 'Purchase Request'),
|
||||
('so', 'Sale Order'),
|
||||
('push', 'Push'),
|
||||
('pull', 'Pull'),
|
||||
('cancel', 'Cancel'),
|
||||
('none', 'None')),
|
||||
'Action')
|
||||
mrp_action_date = fields.Date('MRP Action Date')
|
||||
mrp_date = fields.Date('MRP Date')
|
||||
mrp_move_down_ids = fields.Many2many('mrp.move', 'mrp_move_rel',
|
||||
'move_up_id', 'move_down_id',
|
||||
'MRP Move DOWN')
|
||||
mrp_move_up_ids = fields.Many2many('mrp.move', 'mrp_move_rel',
|
||||
'move_down_id', 'move_up_id',
|
||||
'MRP Move UP')
|
||||
mrp_minimum_stock = fields.Float(string='Minimum Stock',
|
||||
related='product_id.mrp_minimum_stock')
|
||||
mrp_order_number = fields.Char('Order Number')
|
||||
mrp_origin = fields.Selection((('mo', 'Manufacturing Order'),
|
||||
('po', 'Purchase Order'),
|
||||
('pr', 'Purchase Request'),
|
||||
('so', 'Sale Order'), ('mv','Move'),
|
||||
('fc', 'Forecast'), ('mrp', 'MRP')),
|
||||
'Origin')
|
||||
mrp_processed = fields.Boolean('Processed')
|
||||
mrp_product_id = fields.Many2one('mrp.product', 'Product', select=True)
|
||||
mrp_qty = fields.Float('MRP Quantity')
|
||||
mrp_type = fields.Selection((('s', 'Supply'), ('d', 'Demand')), 'Type')
|
||||
name = fields.Char('Description')
|
||||
parent_product_id = fields.Many2one('product.product',
|
||||
'Parent Product', select=True)
|
||||
product_id = fields.Many2one('product.product',
|
||||
'Product', select=True)
|
||||
production_id = fields.Many2one('mrp.production',
|
||||
'Manufacturing Order', select=True)
|
||||
purchase_line_id = fields.Many2one('purchase.order.line',
|
||||
'Purchase Order Line', select=True)
|
||||
purchase_order_id = fields.Many2one('purchase.order',
|
||||
'Purchase Order', select=True)
|
||||
running_availability = fields.Float('Running Availability')
|
||||
sale_line_id = fields.Many2one('sale.order.line',
|
||||
'Sale Order Line', select=True)
|
||||
sale_order_id = fields.Many2one('sale.order', 'Sale Order', select=True)
|
||||
state = fields.Selection((('draft', 'Draft'),
|
||||
('assigned', 'Assigned'),
|
||||
('confirmed', 'Confirmed'),
|
||||
('waiting', 'Waiting'),
|
||||
('ready', 'Ready'),
|
||||
('in_production', 'In Production'),
|
||||
('picking_except', 'Picking Exception'),
|
||||
('sent', 'Sent'), ('approved', 'Approved'),
|
||||
('except_invoice', 'Invoice Exception')),
|
||||
'State')
|
||||
stock_move_id = fields.Many2one('stock.move', 'Stock Move', select=True)
|
||||
|
||||
_order = 'mrp_product_id, mrp_date, mrp_type desc, id'
|
||||
|
||||
@api.model
|
||||
def mrp_production_prepare(self, bom_id, routing_id):
|
||||
return {
|
||||
'product_uos_qty': 0.00,
|
||||
'product_uom': self.product_id.product_tmpl_id.uom_id.id,
|
||||
'product_qty': self.mrp_qty,
|
||||
'product_id': self.product_id.id,
|
||||
'location_src_id': 12,
|
||||
'date_planned': self.mrp_date,
|
||||
'cycle_total': 0.00,
|
||||
'company_id': 1,
|
||||
'state': 'draft',
|
||||
'hour_total': 0.00,
|
||||
'bom_id': bom_id,
|
||||
'routing_id': routing_id,
|
||||
'allow_reorder': False
|
||||
}
|
||||
|
||||
@api.model
|
||||
def mrp_process_mo(self):
|
||||
if self.mrp_action != 'mo':
|
||||
return True
|
||||
bom_id = False
|
||||
routing_id = False
|
||||
mrp_boms = self.env['mrp.bom'].search(
|
||||
[('product_id', '=', self.product_id.id),
|
||||
('type', '=', 'normal')], limit=1)
|
||||
for mrp_bom in mrp_boms:
|
||||
bom_id = mrp_bom.id
|
||||
routing_id = mrp_bom.routing_id.id
|
||||
|
||||
if self.product_id.track_production and self.mrp_qty > 1:
|
||||
raise exceptions.Warning(_('Not allowed to create '
|
||||
'manufacturing order with '
|
||||
'quantity higher than 1 '
|
||||
'for serialized product'))
|
||||
else:
|
||||
production_data = self.mrp_production_prepare(bom_id, routing_id)
|
||||
pr = self.env['mrp.production'].create(production_data)
|
||||
self.production_id = pr.id
|
||||
self.current_qty = self.mrp_qty
|
||||
self.current_date = self.mrp_date
|
||||
self.mrp_processed = True
|
||||
self.name = pr.name
|
||||
|
||||
@api.model
|
||||
def mrp_process_pr(self):
|
||||
if self.mrp_action != 'pr':
|
||||
return True
|
||||
seq = self.env['ir.sequence'].search(
|
||||
[('code', '=', 'purchase.order.requisition')])
|
||||
seqnbr = self.env['ir.sequence'].next_by_id(seq.id)
|
||||
self.env['purchase.requisition'].create({
|
||||
'origin': 'MRP - [' + self.product_id.default_code + '] ' +
|
||||
self.product_id.name_template,
|
||||
'exclusive': 'exclusive',
|
||||
'message_follower_ids': False,
|
||||
'date_end': False,
|
||||
'date_start': self.mrp_date,
|
||||
'company_id': 1,
|
||||
'warehouse_id': 1,
|
||||
'state': 'draft',
|
||||
'line_ids': [[0, False,
|
||||
{'product_uom_id':
|
||||
self.product_id.product_tmpl_id.uom_id.id,
|
||||
'product_id': self.product_id.id,
|
||||
'product_qty': self.mrp_qty,
|
||||
'name': self.product_id.name_template}]],
|
||||
'message_ids': False,
|
||||
'description': False,
|
||||
'name': seqnbr
|
||||
})
|
||||
self.current_qty = self.mrp_qty
|
||||
self.current_date = self.mrp_date
|
||||
self.mrp_processed = True
|
||||
self.name = seqnbr
|
||||
|
||||
@api.one
|
||||
def mrp_process(self):
|
||||
self.mrp_process_mo()
|
||||
self.mrp_process_pr()
|
||||
return True
|
||||
|
||||
@api.v7
|
||||
def mrp_process_po(self, cr, uid, ids, context=None):
|
||||
view_id = self.pool.get('ir.ui.view').search(
|
||||
cr, uid, [('model', '=', 'mrp.move.create.po'),
|
||||
('name', '=', 'mrp.move.create.po.form')])
|
||||
move = self.browse(cr, uid, ids)[0]
|
||||
context['move_id'] = move.id
|
||||
context['default_move_id'] = move.id
|
||||
context['default_mrp_qty'] = move.mrp_qty
|
||||
context['default_mrp_date'] = move.mrp_date
|
||||
context['default_qty_po'] = move.mrp_qty
|
||||
context['default_date_po'] = move.mrp_date
|
||||
context['default_main_supplier_id'] = \
|
||||
move.mrp_product_id.main_supplier_id.id
|
||||
context['default_product_id'] = move.mrp_product_id.product_id.id
|
||||
context['default_purchase_line_warn_msg'] = \
|
||||
move.mrp_product_id.product_id.purchase_line_warn_msg
|
||||
context['default_purchase_line_warn'] = \
|
||||
move.mrp_product_id.product_id.purchase_line_warn
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Create PO',
|
||||
'view_mode': 'form',
|
||||
'view_type': 'form',
|
||||
'view_id': view_id[0],
|
||||
'res_model': 'mrp.move.create.po',
|
||||
'target': 'new',
|
||||
'context': context,
|
||||
}
|
||||
47
multi_level_mrp/models/mrp_product.py
Normal file
47
multi_level_mrp/models/mrp_product.py
Normal file
@@ -0,0 +1,47 @@
|
||||
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
# © 2016 Eficent Business and IT Consulting Services S.L.
|
||||
# - Jordi Ballester Alomar <jordi.ballester@eficent.com>
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class MrpProduct(models.Model):
|
||||
_name = 'mrp.product'
|
||||
|
||||
mrp_area_id = fields.Many2one('mrp.area', 'MRP Area')
|
||||
current_qty_available = fields.Float(string='Current Qty Available',
|
||||
related='product_id.qty_available')
|
||||
main_supplier_id = fields.Many2one('res.partner', 'Main Supplier',
|
||||
select=True)
|
||||
mrp_inspection_delay = fields.Integer(
|
||||
string='Inspection Delay', related='product_id.mrp_inspection_delay')
|
||||
mrp_lead_time = fields.Integer(string='Lead Time',
|
||||
related='product_id.mrp_lead_time')
|
||||
mrp_llc = fields.Integer('Low Level Code', select=True)
|
||||
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('mrp.move', 'mrp_product_id', 'MRP Moves')
|
||||
mrp_nbr_days = fields.Integer(
|
||||
string='Nbr. Days', related='product_id.mrp_nbr_days')
|
||||
mrp_qty_available = fields.Float('MRP Qty Available')
|
||||
mrp_qty_multiple = fields.Float(string='Qty Multiple',
|
||||
related='product_id.mrp_qty_multiple')
|
||||
mrp_transit_delay = fields.Integer(mrp_move_ids)
|
||||
mrp_verified = fields.Boolean(string='MRP Verified',
|
||||
related='product_id.mrp_verified')
|
||||
name = fields.Char('Description')
|
||||
nbr_mrp_actions = fields.Integer('Nbr Actions', select=True)
|
||||
nbr_mrp_actions_4w = fields.Integer('Nbr Actions 4 Weeks', select=True)
|
||||
product_id = fields.Many2one('product.product', 'Product', select=True)
|
||||
product_tmpl_id = fields.Many2one('product.template', 'Product Template',
|
||||
related='product_id.product_tmpl_id')
|
||||
purchase_requisition = fields.Boolean(string='Purchase Requisition',
|
||||
related='product_id.purchase_requisition')
|
||||
supply_method = fields.Selection((('buy', 'Buy'),
|
||||
('produce', 'Produce')),
|
||||
'Supply Method')
|
||||
41
multi_level_mrp/models/product.py
Normal file
41
multi_level_mrp/models/product.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
# © 2016 Eficent Business and IT Consulting Services S.L.
|
||||
# - Jordi Ballester Alomar <jordi.ballester@eficent.com>
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class Product(models.Model):
|
||||
_inherit = 'product.product'
|
||||
|
||||
llc = fields.Integer('Low Level Code', default=0)
|
||||
manufacturing_order_ids = fields.One2many('mrp.production',
|
||||
'product_id',
|
||||
'Manufacturing Orders',
|
||||
domain=[('state', '=', 'draft')])
|
||||
mrp_applicable = fields.Boolean('MRP Applicable')
|
||||
mrp_exclude = fields.Boolean('Exclude from MRP')
|
||||
mrp_inspection_delay = fields.Integer('Inspection Delay', default=0)
|
||||
mrp_lead_time = fields.Integer('Lead Time', default=1)
|
||||
mrp_maximum_order_qty = fields.Float('Maximum Order Qty', default=0.00)
|
||||
mrp_minimum_order_qty = fields.Float('Minimum Order Qty', default=0.00)
|
||||
mrp_minimum_stock = fields.Float('Minimum Stock')
|
||||
mrp_nbr_days = fields.Integer('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('mrp.product',
|
||||
'product_id', 'MRP Product data')
|
||||
mrp_qty_multiple = fields.Float('Qty Multiple', default=1.00)
|
||||
mrp_transit_delay = fields.Integer('Transit Delay', default=0)
|
||||
mrp_verified = fields.Boolean('Verified for MRP',
|
||||
help="Identifies that this product has "
|
||||
"been verified to be valid for the "
|
||||
"MRP.")
|
||||
purchase_order_line_ids = fields.One2many('purchase.order.line',
|
||||
'product_id', 'Purchase Orders')
|
||||
purchase_requisition_ids = fields.One2many('purchase.requisition.line',
|
||||
'product_id',
|
||||
'Purchase Requisitions')
|
||||
15
multi_level_mrp/models/stock_location.py
Normal file
15
multi_level_mrp/models/stock_location.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
# © 2016 Eficent Business and IT Consulting Services S.L.
|
||||
# - Jordi Ballester Alomar <jordi.ballester@eficent.com>
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class StockLocation(models.Model):
|
||||
_inherit = 'stock.location'
|
||||
|
||||
mrp_area_id = fields.Many2one('mrp.area', string='MRP Area',
|
||||
help="Requirements for a particular MRP "
|
||||
"area are combined for the purposes "
|
||||
"of procurement by the MRP.")
|
||||
4
multi_level_mrp/security/ir.model.access.csv
Normal file
4
multi_level_mrp/security/ir.model.access.csv
Normal file
@@ -0,0 +1,4 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_mrp_inventory_user,mrp.inventory,model_mrp_inventory,mrp.group_mrp_user,1,0,0,0
|
||||
access_mrp_move_user,mrp.move,model_mrp_move,mrp.group_mrp_user,1,0,0,0
|
||||
access_mrp_product,mrp.product,model_mrp_product,base.group_user,1,0,0,0
|
||||
|
BIN
multi_level_mrp/static/src/img/icon.png
Normal file
BIN
multi_level_mrp/static/src/img/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
63
multi_level_mrp/views/mrp_area_view.xml
Normal file
63
multi_level_mrp/views/mrp_area_view.xml
Normal file
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record model="ir.ui.view" id="mrp_area_tree">
|
||||
<field name="name">mrp.area.tree</field>
|
||||
<field name="model">mrp.area</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="MRP Area">
|
||||
<field name="name"/>
|
||||
<field name="warehouse_id"/>
|
||||
<field name="location_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="mrp_area_form">
|
||||
<field name="name">mrp.area.form</field>
|
||||
<field name="model">mrp.area</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="MRP Area">
|
||||
<group colspan="4" col="2">
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="warehouse_id"/>
|
||||
<field name="location_id"/>
|
||||
</group>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="mrp_area_action">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="name">MRP Area</field>
|
||||
<field name="res_model">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="mrp_area_tree"/>
|
||||
<field name="act_window_id" ref="mrp_area_form"/>
|
||||
</record>
|
||||
<record model="ir.actions.act_window.view" id="mrp_area_form_action">
|
||||
<field name="sequence" eval="22"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="mrp_area_form"/>
|
||||
<field name="act_window_id" ref="mrp_area_action"/>
|
||||
</record>
|
||||
<record model="ir.actions.act_window.view" id="mrp_area_tree_action">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_id" ref="mrp_area_tree"/>
|
||||
<field name="act_window_id" ref="mrp_area_action"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
77
multi_level_mrp/views/mrp_forecast_view.xml
Normal file
77
multi_level_mrp/views/mrp_forecast_view.xml
Normal file
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record model="ir.ui.view" id="mrp_forecast_tree">
|
||||
<field name="name">mrp.forecast.tree</field>
|
||||
<field name="model">mrp.forecast.product</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="MRP Product Forecast">
|
||||
<field name="mrp_area_id"/>
|
||||
<field name="product_id"/>
|
||||
<field name="qty_forecast_total"/>
|
||||
<field name="qty_forecast_past"/>
|
||||
<field name="qty_forecast_m0"/>
|
||||
<field name="qty_forecast_m1"/>
|
||||
<field name="qty_forecast_m2"/>
|
||||
<field name="qty_forecast_m3"/>
|
||||
<field name="qty_forecast_m4"/>
|
||||
<field name="qty_forecast_m5"/>
|
||||
<field name="qty_forecast_m6"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="mrp_forecast_form">
|
||||
<field name="name">mrp.forecast.form</field>
|
||||
<field name="model">mrp.forecast.product</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="MRP Product Forecast">
|
||||
<group colspan="4" col="2">
|
||||
<group>
|
||||
<field name="mrp_area_id"/>
|
||||
<field name="product_id"/>
|
||||
</group>
|
||||
</group>
|
||||
<group colspan="2" col="2">
|
||||
<separator string="Forecasts"/>
|
||||
<field name="mrp_forecast_ids" nolabel="1" colspan="2" context="{'default_forecast_product_id': active_id}">
|
||||
<tree string="Forecasts" editable="bottom">
|
||||
<field name="forecast_product_id" invisible="True"/>
|
||||
<field name="date"/>
|
||||
<field name="qty_forecast"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="mrp_forecast_action">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="name">MRP Forecast</field>
|
||||
<field name="res_model">mrp.forecast.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_forecast_tree"/>
|
||||
<field name="act_window_id" ref="mrp_forecast_form"/>
|
||||
</record>
|
||||
<record model="ir.actions.act_window.view" id="mrp_forecast_form_action">
|
||||
<field name="sequence" eval="22"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="mrp_forecast_form"/>
|
||||
<field name="act_window_id" ref="mrp_forecast_action"/>
|
||||
</record>
|
||||
<record model="ir.actions.act_window.view" id="mrp_forecast_tree_action">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_id" ref="mrp_forecast_tree"/>
|
||||
<field name="act_window_id" ref="mrp_forecast_action"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
97
multi_level_mrp/views/mrp_inventory_view.xml
Normal file
97
multi_level_mrp/views/mrp_inventory_view.xml
Normal file
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record model="ir.ui.view" id="mrp_inventory_form">
|
||||
<field name="name">mrp.inventory.form</field>
|
||||
<field name="model">mrp.inventory</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="MRP Area">
|
||||
<group>
|
||||
<field name="mrp_area_id"/>
|
||||
<field name="mrp_product_id"/>
|
||||
<field name="date"/>
|
||||
<field name="initial_on_hand_qty"/>
|
||||
<field name="demand_qty"/>
|
||||
<field name="supply_qty"/>
|
||||
<field name="final_on_hand_qty"/>
|
||||
<field name="to_procure"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="mrp_inventory_tree">
|
||||
<field name="name">mrp.inventory.tree</field>
|
||||
<field name="model">mrp.inventory</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="MRP Inventory">
|
||||
<field name="mrp_area_id"/>
|
||||
<field name="mrp_product_id"/>
|
||||
<field name="date"/>
|
||||
<field name="initial_on_hand_qty"/>
|
||||
<field name="demand_qty"/>
|
||||
<field name="supply_qty"/>
|
||||
<field name="final_on_hand_qty"/>
|
||||
<field name="to_procure"/>
|
||||
<button string="Create Procurement"
|
||||
name="%(multi_level_mrp.action_mrp_inventory_create_procurement)d"
|
||||
icon="gtk-ok" type="action"
|
||||
attrs="{'invisible':[('to_procure','==',0.0)]}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="mrp_inventory_search">
|
||||
<field name="name">mrp.inventory.search</field>
|
||||
<field name="model">mrp.inventory</field>
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="MRP Inventory">
|
||||
<group name="select" expand="0" string="Selection...">
|
||||
<field name="mrp_product_id" select='1'/>
|
||||
<field name="mrp_area_id" select='1'/>
|
||||
</group>
|
||||
<separator/>
|
||||
<filter string="To Procure" name="filter_to_procure"
|
||||
domain="[['to_procure','>',0.0]]"/>
|
||||
<separator/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Product"
|
||||
context="{'group_by':'mrp_product_id'}"/>
|
||||
<filter string="MRP Area"
|
||||
context="{'group_by':'mrp_area_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="mrp_inventory_action">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="name">MRP Inventory</field>
|
||||
<field name="res_model">mrp.inventory</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_inventory_tree"/>
|
||||
<field name="search_view_id" ref="mrp_inventory_search"/>
|
||||
<field name="act_window_id" ref="mrp_inventory_form"/>
|
||||
</record>
|
||||
<record model="ir.actions.act_window.view" id="mrp_inventory_form_action">
|
||||
<field name="sequence" eval="22"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="mrp_inventory_form"/>
|
||||
<field name="act_window_id" ref="mrp_inventory_action"/>
|
||||
</record>
|
||||
<record model="ir.actions.act_window.view" id="mrp_inventory_tree_action">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_id" ref="mrp_inventory_tree"/>
|
||||
<field name="act_window_id" ref="mrp_inventory_action"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
28
multi_level_mrp/views/mrp_menuitem.xml
Normal file
28
multi_level_mrp/views/mrp_menuitem.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<menuitem name="MRP" id="menu_mrp" sequence="85"/>
|
||||
<menuitem name="MRP" id="menu_mrp_mrp" parent="menu_mrp"
|
||||
sequence="10"/>
|
||||
<menuitem name="MRP Areas" id="menu_mrp_areas"
|
||||
action="mrp_area_action" parent="menu_mrp_mrp"
|
||||
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"
|
||||
sequence="30"/>
|
||||
<menuitem name="Run Multi Level MRP"
|
||||
action="action_multi_level_mrp" id="menu_multi_level_mrp"
|
||||
parent="menu_mrp_mrp" sequence="40"/>
|
||||
<menuitem name="Forecast" id="menu_mrp_forecast"
|
||||
parent="menu_mrp" sequence="50"/>
|
||||
<menuitem name="Forecasted Products" id="menu_mrp_forecast_products"
|
||||
action="mrp_forecast_action" parent="menu_mrp_forecast"
|
||||
sequence="60"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
181
multi_level_mrp/views/mrp_product_view.xml
Normal file
181
multi_level_mrp/views/mrp_product_view.xml
Normal file
@@ -0,0 +1,181 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<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">form</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"/>
|
||||
<button name="mrp_process" icon="gtk-go-forward" type="object" attrs="{'invisible': ['|','|',('mrp_action','==', 'none'),('mrp_action','==', 'po'),('mrp_processed','==',True)]}"/>
|
||||
<button name="mrp_process_po" icon="gtk-go-forward" type="object" attrs="{'invisible': ['|',('mrp_action','!=', 'po'),('mrp_processed','==',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="sale_order_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','=','produce'],['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="sequence" eval="20"/>
|
||||
<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"/>
|
||||
<field name="act_window_id" ref="mrp_product_form"/>
|
||||
</record>
|
||||
<record model="ir.actions.act_window.view" id="mrp_product_form_action">
|
||||
<field name="sequence" eval="22"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="mrp_product_form"/>
|
||||
<field name="act_window_id" ref="mrp_product_action"/>
|
||||
</record>
|
||||
<record model="ir.actions.act_window.view" id="mrp_product_tree_action">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_id" ref="mrp_product_tree"/>
|
||||
<field name="act_window_id" ref="mrp_product_action"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
36
multi_level_mrp/views/product_view.xml
Normal file
36
multi_level_mrp/views/product_view.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<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">
|
||||
<xpath expr="//notebook/page[@string='Procurements']" position="before">
|
||||
<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_lead_time"/>
|
||||
<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>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
42
multi_level_mrp/views/stock_location_view.xml
Normal file
42
multi_level_mrp/views/stock_location_view.xml
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
|
||||
<record id="view_location_form" model="ir.ui.view">
|
||||
<field name="name">stock.location.form</field>
|
||||
<field name="model">stock.location</field>
|
||||
<field name="inherit_id" ref="stock.view_location_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="company_id" position="before">
|
||||
<field name="mrp_area_id"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_location_search" model="ir.ui.view">
|
||||
<field name="name">stock.location.search</field>
|
||||
<field name="model">stock.location</field>
|
||||
<field name="inherit_id" ref="stock.view_location_search"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="location_id" position="after">
|
||||
<field name="mrp_area_id"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="view_location_tree2" model="ir.ui.view">
|
||||
<field name="name">stock.location.tree</field>
|
||||
<field name="model">stock.location</field>
|
||||
<field name="inherit_id" ref="stock.view_location_tree2"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="company_id" position="before">
|
||||
<field name="mrp_area_id"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
3
multi_level_mrp/wizards/__init__.py
Normal file
3
multi_level_mrp/wizards/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from . import multi_level_mrp
|
||||
from . import mrp_move_create_po
|
||||
from . import mrp_inventory_create_procurement
|
||||
69
multi_level_mrp/wizards/mrp_inventory_create_procurement.py
Normal file
69
multi_level_mrp/wizards/mrp_inventory_create_procurement.py
Normal file
@@ -0,0 +1,69 @@
|
||||
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
# © 2016 Eficent Business and IT Consulting Services S.L.
|
||||
# - Jordi Ballester Alomar <jordi.ballester@eficent.com>
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import api, models, _
|
||||
|
||||
|
||||
class MrpInventoryCreateProcurement(models.TransientModel):
|
||||
_name = 'mrp.inventory.create.procurement'
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
res = super(MrpInventoryCreateProcurement, self).default_get(
|
||||
fields)
|
||||
mrp_inventory_ids = self.env.context['active_ids'] or []
|
||||
active_model = self.env.context['active_model']
|
||||
|
||||
if not mrp_inventory_ids:
|
||||
return res
|
||||
assert active_model == 'mrp.inventory', \
|
||||
'Bad context propagation'
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def _prepare_procurement_from_mrp_inventory(self, mrp_inventory):
|
||||
user = self.env['res.users'].browse(self.env.uid).login
|
||||
mrp_area = mrp_inventory.mrp_area_id or False
|
||||
warehouse = mrp_area and mrp_area.warehouse_id or False
|
||||
product = mrp_inventory.mrp_product_id and \
|
||||
mrp_inventory.mrp_product_id.product_id or False
|
||||
|
||||
return {
|
||||
'name': 'INT: '+str(user),
|
||||
'company_id': warehouse.company_id and
|
||||
warehouse.company_id.id or False,
|
||||
'date_planned': mrp_inventory.date,
|
||||
'product_id': product and product.id or False,
|
||||
'product_qty': mrp_inventory.to_order,
|
||||
'product_uom': product and
|
||||
product.uom_id and product.uom_id.id or False,
|
||||
'location_id': mrp_area.location_id and
|
||||
mrp_area.location_id.id or False,
|
||||
'warehouse_id': warehouse and warehouse.id or False
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def make_procurement_order(self):
|
||||
res = []
|
||||
procurement_obj = self.env['procurement.order']
|
||||
mrp_inventory_obj = self.env['mrp.inventory']
|
||||
mrp_inventory_ids = self.env.context['active_ids']
|
||||
|
||||
for mrp_inventory in mrp_inventory_obj.browse(mrp_inventory_ids):
|
||||
procurement_data = self._prepare_procurement_from_mrp_inventory(
|
||||
mrp_inventory)
|
||||
procurement = procurement_obj.create(procurement_data)
|
||||
res.append(procurement.id)
|
||||
|
||||
return {
|
||||
'domain': "[('id','in', ["+','.join(map(str, res))+"])]",
|
||||
'name': _('Procurement Order'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'tree,form',
|
||||
'res_model': 'procurement.order',
|
||||
'view_id': False,
|
||||
'context': False,
|
||||
'type': 'ir.actions.act_window'
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="view_mrp_inventory_create_procurement" model="ir.ui.view">
|
||||
<field name="name">Create Procurement Order</field>
|
||||
<field name="model">mrp.inventory.create.procurement</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Create Procurement Order">
|
||||
<group colspan="2">
|
||||
<button name="make_procurement_order"
|
||||
string="Create Procurement Order"
|
||||
type="object"
|
||||
class="oe_highlight"/>
|
||||
<button special="cancel" string="Cancel" class="oe_link"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_mrp_inventory_create_procurement" model="ir.actions.act_window">
|
||||
<field name="name">Create Procurement Order</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">mrp.inventory.create.procurement</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="view_mrp_inventory_create_procurement"/>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.values" id="mrp_inventory_create_procurement">
|
||||
<field name="model_id" ref="model_mrp_inventory" />
|
||||
<field name="name">Create RFQ</field>
|
||||
<field name="key2">client_action_multi</field>
|
||||
<field name="value" eval="'ir.actions.act_window,' + str(ref('action_mrp_inventory_create_procurement'))" />
|
||||
<field name="key">action</field>
|
||||
<field name="model">mrp.inventory</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
139
multi_level_mrp/wizards/mrp_move_create_po.py
Normal file
139
multi_level_mrp/wizards/mrp_move_create_po.py
Normal file
@@ -0,0 +1,139 @@
|
||||
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
# © 2016 Eficent Business and IT Consulting Services S.L.
|
||||
# - Jordi Ballester Alomar <jordi.ballester@eficent.com>
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import api, fields, models
|
||||
from datetime import date
|
||||
|
||||
|
||||
class MrpMoveCreatePo(models.TransientModel):
|
||||
_name = 'mrp.move.create.po'
|
||||
|
||||
move_id = fields.Many2one('mrp.move', 'Move', select=True)
|
||||
product_id = fields.Many2one('product.product', 'Product', select=True)
|
||||
main_supplier_id = fields.Many2one('res.partner', 'Main Supplier',
|
||||
select=True)
|
||||
partner_id = fields.Many2one('res.partner', 'Partner', select=True)
|
||||
mrp_qty = fields.Float('Quantity')
|
||||
mrp_date = fields.Date('Planned Date')
|
||||
qty_po = fields.Float('Quantity PO')
|
||||
date_po = fields.Date('Date PO')
|
||||
other_partner = fields.Boolean('Select Other Partner', default=False)
|
||||
purchase_line_warn = fields.Char('Purchase Warning')
|
||||
purchase_line_warn_msg = fields.Text('Purchase Warning')
|
||||
|
||||
@api.multi
|
||||
def create_po(self):
|
||||
for move in self:
|
||||
if move.other_partner and move.partner_id:
|
||||
partner_selected = move.partner_id.id
|
||||
pricelist = move.partner_id.property_product_pricelist_purchase.id
|
||||
fiscalposition = move.partner_id.property_account_position.id
|
||||
currency_id = \
|
||||
move.partner_id.property_product_pricelist_purchase.\
|
||||
currency_id.id
|
||||
else:
|
||||
partner_selected = move.main_supplier_id.id
|
||||
pricelist = move.main_supplier_id.\
|
||||
property_product_pricelist_purchase.id
|
||||
fiscalposition = \
|
||||
move.main_supplier_id.property_account_position.id
|
||||
currency_id = move.main_supplier_id.\
|
||||
property_product_pricelist_purchase.currency_id.id
|
||||
|
||||
order_id = 0
|
||||
sql_stat = "SELECT id FROM purchase_order WHERE " \
|
||||
"purchase_order.partner_id = %d AND state = 'draft' " \
|
||||
"ORDER BY date_order DESC LIMIT 1" % \
|
||||
(partner_selected, )
|
||||
self.env.cr.execute(sql_stat)
|
||||
sql_res = self.env.cr.dictfetchone()
|
||||
if sql_res:
|
||||
if sql_res['id']:
|
||||
order_id = sql_res['id']
|
||||
|
||||
unit_price = 0
|
||||
sql_stat = "SELECT price FROM product_supplierinfo, " \
|
||||
"pricelist_partnerinfo WHERE " \
|
||||
"product_supplierinfo.name = %d AND product_tmpl_id " \
|
||||
"= %d AND product_supplierinfo.id = suppinfo_id " \
|
||||
"LIMIT 1" % (partner_selected,
|
||||
move.product_id.product_tmpl_id.id)
|
||||
self.env.cr.execute(sql_stat)
|
||||
sql_res = self.env.cr.dictfetchone()
|
||||
if sql_res:
|
||||
if sql_res['price']:
|
||||
unit_price = sql_res['price']
|
||||
|
||||
if order_id == 0:
|
||||
po = self.env['purchase.order']
|
||||
po_id = po.create({
|
||||
'shipped': False,
|
||||
'warehouse_id': 1,
|
||||
'date_order': date.today(),
|
||||
'location_id': 12,
|
||||
'amount_untaxed': 0.00,
|
||||
'partner_id': partner_selected,
|
||||
'company_id': 1,
|
||||
'amount_tax': 0.00,
|
||||
'invoice_method': 'picking',
|
||||
'state': 'draft',
|
||||
'pricelist_id': pricelist,
|
||||
'amount_total': 0.00,
|
||||
'journal_id': 2,
|
||||
'accept_amount': False,
|
||||
'fiscal_position': fiscalposition,
|
||||
'currency_id': currency_id,
|
||||
})
|
||||
order_id = po_id
|
||||
|
||||
if order_id != 0:
|
||||
pr = self.env['purchase.order.line']
|
||||
account_fiscal_position = \
|
||||
self.env['account.fiscal.position']
|
||||
account_tax = self.env['account.tax']
|
||||
fpos = fiscalposition and account_fiscal_position.browse(
|
||||
fiscalposition) or False
|
||||
taxes = account_tax.browse(map(lambda x: x.id,
|
||||
move.product_id.supplier_taxes_id))
|
||||
taxes_ids = account_fiscal_position.map_tax(fpos, taxes)
|
||||
pr_id = pr.create({
|
||||
'product_id': move.product_id.id,
|
||||
'product_uom': move.product_id.product_tmpl_id.uom_id.id,
|
||||
'date_planned': move.date_po,
|
||||
'order_id': order_id,
|
||||
'name': '[' + move.product_id.default_code + '] ' +
|
||||
move.product_id.name_template,
|
||||
'price_unit': unit_price,
|
||||
'product_qty': move.qty_po,
|
||||
'supplier_uom_id':
|
||||
move.product_id.product_tmpl_id.uom_id.id,
|
||||
'supplier_price_unit': unit_price,
|
||||
'supplier_qty': move.qty_po,
|
||||
'conversion_coeff': 1,
|
||||
'taxes_id': [[6, False, taxes_ids]],
|
||||
'description': False,
|
||||
})
|
||||
|
||||
order_number = None
|
||||
sql_stat = "SELECT name FROM purchase_order WHERE id = %d" % (
|
||||
order_id, )
|
||||
self.env.cr.execute(sql_stat)
|
||||
sql_res = self.env.cr.dictfetchone()
|
||||
if sql_res:
|
||||
if sql_res['name']:
|
||||
order_number = sql_res['name']
|
||||
|
||||
if 'move_id' in self.env.context and self.env.context['move_id']:
|
||||
move_obj = self.env['mrp.move']
|
||||
move_obj.write(self.env.context['move_id'], {
|
||||
'purchase_order_id': order_id,
|
||||
'purchase_order_line_id': pr_id,
|
||||
'mrp_processed': True,
|
||||
'current_qty': move.qty_po,
|
||||
'current_date': move.date_po,
|
||||
'name': order_number,
|
||||
})
|
||||
|
||||
return True
|
||||
36
multi_level_mrp/wizards/mrp_move_create_po_view.xml
Normal file
36
multi_level_mrp/wizards/mrp_move_create_po_view.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record model="ir.ui.view" id="mrp_move_create_po_form">
|
||||
<field name="name">mrp.move.create.po.form</field>
|
||||
<field name="model">mrp.move.create.po</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Create PO">
|
||||
<group colspan="4">
|
||||
<group>
|
||||
<field name="move_id" invisible="True"/>
|
||||
<field name="product_id" readonly="True"/>
|
||||
<field name="mrp_qty" readonly="True"/>
|
||||
<field name="qty_po" attrs="{'invisible':[('purchase_line_warn','==','block')]}"/>
|
||||
<field name="mrp_date" readonly="True"/>
|
||||
<field name="date_po" attrs="{'invisible':[('purchase_line_warn','==','block')]}"/>
|
||||
<field name="main_supplier_id" readonly="True"/>
|
||||
<field name="other_partner" attrs="{'invisible':[('purchase_line_warn','==','block')]}"/>
|
||||
<field name="partner_id" attrs="{'invisible':[('other_partner','==',False)]}" domain="[('supplier','=',True),('is_company','=',True)]"/>
|
||||
<field name="purchase_line_warn" invisible="True"/>
|
||||
<field name="purchase_line_warn_msg" readonly="True" attrs="{'invisible':[('purchase_line_warn','==','no-message')]}"/>
|
||||
</group>
|
||||
</group>
|
||||
<group colspan="4" col="4">
|
||||
<button string="Create PO" name="create_po" icon="gtk-ok" type="object" attrs="{'invisible':[('purchase_line_warn','==','block')]}"/>
|
||||
<button special="cancel" string="Cancel" icon="gtk-cancel"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
983
multi_level_mrp/wizards/multi_level_mrp.py
Normal file
983
multi_level_mrp/wizards/multi_level_mrp.py
Normal file
@@ -0,0 +1,983 @@
|
||||
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
# © 2016 Eficent Business and IT Consulting Services S.L.
|
||||
# - Jordi Ballester Alomar <jordi.ballester@eficent.com>
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import api, models, exceptions, _
|
||||
from datetime import date, datetime, timedelta
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MultiLevelMrp(models.TransientModel):
|
||||
_name = 'multi.level.mrp'
|
||||
|
||||
@api.model
|
||||
def _prepare_mrp_product_data(self, product, mrp_area):
|
||||
main_supplier_id = False
|
||||
sequence = 9999
|
||||
# TODO: All this should not be really needed, as at the time
|
||||
# of procurement you will figure out these details.
|
||||
for supplier in product.product_tmpl_id.seller_ids:
|
||||
if supplier.sequence < sequence:
|
||||
sequence = supplier.sequence
|
||||
main_supplier_id = supplier.name.id
|
||||
supply_method = 'produce'
|
||||
for route in product.product_tmpl_id.route_ids:
|
||||
if route.name == 'Buy':
|
||||
supply_method = 'buy'
|
||||
qty_available = 0.0
|
||||
product_obj = self.env['product.product']
|
||||
location_ids = self.env['stock.location'].search(
|
||||
[('id', 'child_of', mrp_area.location_id.id)])
|
||||
for location in location_ids:
|
||||
product_l = product_obj.with_context(
|
||||
{'location': location.id}).browse(product.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,
|
||||
'nbr_mrp_actions': 0,
|
||||
'nbr_mrp_actions_4w': 0,
|
||||
'name': product.name_template,
|
||||
'supply_method': supply_method,
|
||||
'main_supplier_id': main_supplier_id,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _prepare_mrp_move_data_from_forecast(self, fc, fc_id, mrpproduct):
|
||||
mrp_type = 'd'
|
||||
origin = 'fc'
|
||||
mrp_date = date.today()
|
||||
if datetime.date(datetime.strptime(fc_id.date,
|
||||
'%Y-%m-%d')) > date.today():
|
||||
mrp_date = datetime.date(datetime.strptime(
|
||||
fc_id.date, '%Y-%m-%d'))
|
||||
return {
|
||||
'mrp_area_id': fc.mrp_area_id.id,
|
||||
'product_id': fc.product_id.id,
|
||||
'mrp_product_id': mrpproduct.id,
|
||||
'production_id': None,
|
||||
'purchase_order_id': None,
|
||||
'purchase_line_id': None,
|
||||
'sale_order_id': None,
|
||||
'sale_line_id': None,
|
||||
'stock_move_id': None,
|
||||
'mrp_qty': -fc_id.qty_forecast,
|
||||
'current_qty': -fc_id.qty_forecast,
|
||||
'mrp_date': mrp_date,
|
||||
'current_date': mrp_date,
|
||||
'mrp_action': 'none',
|
||||
'mrp_type': mrp_type,
|
||||
'mrp_processed': False,
|
||||
'mrp_origin': origin,
|
||||
'mrp_order_number': None,
|
||||
'parent_product_id': None,
|
||||
'running_availability': 0.00,
|
||||
'name': 'Forecast',
|
||||
'state': 'confirmed',
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _prepare_mrp_move_data_from_stock_move(self, mrp_product, move):
|
||||
# TODO: Clean up to reduce dependencies
|
||||
if (move.location_id.usage == 'internal' and
|
||||
move.location_dest_id.usage != 'internal') \
|
||||
or (move.location_id.usage != 'internal' and
|
||||
move.location_dest_id.usage == 'internal'):
|
||||
if move.location_id.usage == 'internal':
|
||||
mrp_type = 'd'
|
||||
productqty = -move.product_qty
|
||||
else:
|
||||
mrp_type = 's'
|
||||
productqty = move.product_qty
|
||||
po = None
|
||||
po_line = None
|
||||
so = None
|
||||
so_line = None
|
||||
mo = None
|
||||
origin = None
|
||||
order_number = None
|
||||
parent_product_id = None
|
||||
if move.purchase_line_id:
|
||||
order_number = move.purchase_line_id.order_id.name
|
||||
origin = 'po'
|
||||
po = move.purchase_line_id.order_id.id
|
||||
po_line = move.purchase_line_id.id
|
||||
if move.production_id:
|
||||
order_number = move.production_id.name
|
||||
origin = 'mo'
|
||||
mo = move.production_id.id
|
||||
else:
|
||||
if move.move_dest_id:
|
||||
if move.move_dest_id.production_id:
|
||||
order_number = \
|
||||
move.move_dest_id.production_id.name
|
||||
origin = 'mo'
|
||||
mo = move.move_dest_id.production_id.id
|
||||
if move.move_dest_id.production_id.product_id:
|
||||
parent_product_id = \
|
||||
move.move_dest_id.production_id.product_id.id
|
||||
else:
|
||||
parent_product_id = \
|
||||
move.move_dest_id.product_id.id
|
||||
if order_number is None:
|
||||
order_number = move.name
|
||||
mrp_date = date.today()
|
||||
if datetime.date(datetime.strptime(
|
||||
move.date, '%Y-%m-%d %H:%M:%S')) > date.today():
|
||||
mrp_date = datetime.date(datetime.strptime(
|
||||
move.date, '%Y-%m-%d %H:%M:%S'))
|
||||
return {
|
||||
'mrp_area_id': mrp_product.mrp_area_id.id,
|
||||
'product_id': move.product_id.id,
|
||||
'mrp_product_id': mrp_product.id,
|
||||
'production_id': mo,
|
||||
'purchase_order_id': po,
|
||||
'purchase_line_id': po_line,
|
||||
'sale_order_id': so,
|
||||
'sale_line_id': so_line,
|
||||
'stock_move_id': move.id,
|
||||
'mrp_qty': productqty,
|
||||
'current_qty': productqty,
|
||||
'mrp_date': mrp_date,
|
||||
'current_date': move.date,
|
||||
'mrp_action': 'none',
|
||||
'mrp_type': mrp_type,
|
||||
'mrp_processed': False,
|
||||
'mrp_origin': origin,
|
||||
'mrp_order_number': order_number,
|
||||
'parent_product_id': parent_product_id,
|
||||
'running_availability': 0.00,
|
||||
'name': order_number,
|
||||
'state': move.state,
|
||||
}
|
||||
return {}
|
||||
|
||||
@api.model
|
||||
def _prepare_mrp_move_data_supply(self, product, qty, mrp_date_supply,
|
||||
mrp_action_date, mrp_action, name):
|
||||
return {
|
||||
'mrp_area_id': product.mrp_area_id.id,
|
||||
'product_id': product.product_id.id,
|
||||
'mrp_product_id': product.id,
|
||||
'production_id': None,
|
||||
'purchase_order_id': None,
|
||||
'purchase_line_id': None,
|
||||
'sale_order_id': None,
|
||||
'sale_line_id': None,
|
||||
'stock_move_id': None,
|
||||
'mrp_qty': qty,
|
||||
'current_qty': None,
|
||||
'mrp_date': mrp_date_supply,
|
||||
'mrp_action_date': mrp_action_date,
|
||||
'current_date': None,
|
||||
'mrp_action': mrp_action,
|
||||
'mrp_type': 's',
|
||||
'mrp_processed': False,
|
||||
'mrp_origin': None,
|
||||
'mrp_order_number': None,
|
||||
'parent_product_id': None,
|
||||
'name': 'Supply: ' + name,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _prepare_mrp_move_data_bom_explosion(self, product, bomline, qty,
|
||||
mrp_date_demand_2, bom, name):
|
||||
mrp_products = self.env['mrp.product'].search(
|
||||
[('mrp_area_id', '=', product.mrp_area_id.id),
|
||||
('product_id', '=', bomline.product_id.id)], limit=1)
|
||||
if not mrp_products:
|
||||
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_products[0].id,
|
||||
'production_id': None,
|
||||
'purchase_order_id': None,
|
||||
'purchase_line_id': None,
|
||||
'sale_order_id': None,
|
||||
'sale_line_id': None,
|
||||
'stock_move_id': None,
|
||||
'mrp_qty': -(qty * bomline.product_qty),
|
||||
'current_qty': None,
|
||||
'mrp_date': mrp_date_demand_2,
|
||||
'current_date': None,
|
||||
'mrp_action': 'none',
|
||||
'mrp_type': 'd',
|
||||
'mrp_processed': False,
|
||||
'mrp_origin': 'mrp',
|
||||
'mrp_order_number': None,
|
||||
'parent_product_id': bom.product_id.id,
|
||||
'name':
|
||||
('Demand Bom Explosion: ' + name).replace(
|
||||
'Demand Bom Explosion: Demand Bom '
|
||||
'Explosion: ',
|
||||
'Demand Bom Explosion: '),
|
||||
}
|
||||
|
||||
@api.model
|
||||
def create_move(self, mrp_product_id, mrp_date, mrp_qty, name):
|
||||
self = self.with_context(auditlog_disabled=True)
|
||||
|
||||
values = {}
|
||||
if not isinstance(mrp_date, date):
|
||||
mrp_date = datetime.date(datetime.strptime(mrp_date, '%Y-%m-%d'))
|
||||
|
||||
qty_ordered = 0.00
|
||||
products = self.env['mrp.product'].search([('id', '=',
|
||||
mrp_product_id)])
|
||||
for product in products:
|
||||
if product.supply_method == 'buy':
|
||||
if product.purchase_requisition:
|
||||
mrp_action = 'pr'
|
||||
else:
|
||||
mrp_action = 'po'
|
||||
else:
|
||||
mrp_action = 'mo'
|
||||
|
||||
if mrp_date < date.today():
|
||||
mrp_date_supply = date.today()
|
||||
else:
|
||||
mrp_date_supply = mrp_date
|
||||
|
||||
mrp_action_date = mrp_date-timedelta(days=product.mrp_lead_time)
|
||||
|
||||
qty_ordered = 0.00
|
||||
qty_to_order = mrp_qty
|
||||
while qty_ordered < mrp_qty:
|
||||
qty = 0.00
|
||||
if product.mrp_maximum_order_qty == 0.00 and \
|
||||
product.mrp_minimum_order_qty == 0.00:
|
||||
qty = qty_to_order
|
||||
else:
|
||||
if qty_to_order < product.mrp_minimum_order_qty:
|
||||
qty = product.mrp_minimum_order_qty
|
||||
else:
|
||||
if product.mrp_maximum_order_qty and qty_to_order > \
|
||||
product.mrp_maximum_order_qty:
|
||||
qty = product.mrp_maximum_order_qty
|
||||
else:
|
||||
qty = qty_to_order
|
||||
qty_to_order -= qty
|
||||
|
||||
move_data = self._prepare_mrp_move_data_supply(product, qty,
|
||||
mrp_date_supply,
|
||||
mrp_action_date,
|
||||
mrp_action,
|
||||
name)
|
||||
mrpmove_id = self.env['mrp.move'].create(move_data)
|
||||
qty_ordered = qty_ordered + qty
|
||||
|
||||
if mrp_action == 'mo':
|
||||
mrp_date_demand = mrp_date-timedelta(days=product.mrp_lead_time)
|
||||
if mrp_date_demand < date.today():
|
||||
mrp_date_demand = date.today()
|
||||
if not product.product_id.bom_ids:
|
||||
continue
|
||||
bomcount = 0
|
||||
for bom in product.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:
|
||||
continue
|
||||
if bomline.date_start and datetime.date(
|
||||
datetime.strptime(
|
||||
bomline.date_start, '%Y-%m-%d')) > \
|
||||
mrp_date_demand:
|
||||
continue
|
||||
if bomline.date_stop and datetime.date(
|
||||
datetime.strptime(
|
||||
bomline.date_stop, '%Y-%m-%d')) < \
|
||||
mrp_date_demand:
|
||||
continue
|
||||
|
||||
mrp_date_demand_2 = mrp_date_demand-timedelta(
|
||||
days=(product.mrp_transit_delay+product.
|
||||
mrp_inspection_delay))
|
||||
move_data = \
|
||||
self._prepare_mrp_move_data_bom_explosion(
|
||||
product, bomline, qty,
|
||||
mrp_date_demand_2,
|
||||
bom, name)
|
||||
mrpmove_id2 = self.env['mrp.move'].create(
|
||||
move_data)
|
||||
sql_stat = "INSERT INTO mrp_move_rel (" \
|
||||
"move_up_id, " \
|
||||
"move_down_id) values (%d, %d)" % \
|
||||
(mrpmove_id, mrpmove_id2, )
|
||||
self.env.cr.execute(sql_stat)
|
||||
values['qty_ordered'] = qty_ordered
|
||||
log_msg = '%s' % qty_ordered
|
||||
logger.info(log_msg)
|
||||
return values
|
||||
|
||||
@api.model
|
||||
def _mrp_cleanup(self):
|
||||
# Some part of the code with the new API is replaced by
|
||||
# sql statements due to performance issues when the auditlog is
|
||||
# installed
|
||||
logger.info('START MRP CLEANUP')
|
||||
self.env['mrp.move'].search([('id', '!=', 0)]).unlink()
|
||||
self.env['mrp.product'].search([('id', '!=', 0)]).unlink()
|
||||
logger.info('END MRP CLEANUP')
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def _low_level_code_calculation(self):
|
||||
logger.info('START LOW LEVEL CODE CALCULATION')
|
||||
counter = 999999
|
||||
sql_stat = 'update product_product set llc = 0'
|
||||
self.env.cr.execute(sql_stat)
|
||||
sql_stat = 'SELECT count(id) AS counter FROM product_product WHERE ' \
|
||||
'llc = %d' % (0, )
|
||||
self.env.cr.execute(sql_stat)
|
||||
sql_res = self.env.cr.dictfetchone()
|
||||
if sql_res:
|
||||
counter = sql_res['counter']
|
||||
log_msg = 'LOW LEVEL CODE 0 FINISHED - NBR PRODUCTS: %s' % counter
|
||||
logger.info(log_msg)
|
||||
|
||||
llc = 0
|
||||
# TODO: possibly replace condition to while counter != 0
|
||||
while counter != 999999:
|
||||
self.env.cr.commit()
|
||||
llc += 1
|
||||
sql_stat = """
|
||||
UPDATE product_product AS child_product
|
||||
SET child_product.llc = %d
|
||||
FROM mrp_bom_line AS bom_line,
|
||||
mrp_bom AS bom,
|
||||
product_product AS parent_product
|
||||
WHERE
|
||||
child_product.llc = (%d - 1)
|
||||
AND child_product.id = bom_line.product_id
|
||||
AND bom_line.bom_id = bom.id
|
||||
AND parent_product.product_tmpl_id = bom.product_tmpl_id
|
||||
AND parent_product.llc = (%d - 1)"""
|
||||
self.env.cr.execute(sql_stat, (llc, llc, llc, ))
|
||||
sql_stat = 'SELECT count(id) AS counter FROM product_product ' \
|
||||
'WHERE llc = %d' % (llc, )
|
||||
self.env.cr.execute(sql_stat)
|
||||
sql_res = self.env.cr.dictfetchone()
|
||||
if sql_res:
|
||||
counter = sql_res['counter']
|
||||
log_msg = 'LOW LEVEL CODE %s FINISHED - NBR PRODUCTS: %s' % (
|
||||
llc, counter)
|
||||
logger.info(log_msg)
|
||||
if counter == 0:
|
||||
counter = 999999
|
||||
mrp_lowest_llc = llc
|
||||
self.env.cr.commit()
|
||||
logger.info('END LOW LEVEL CODE CALCULATION')
|
||||
return mrp_lowest_llc
|
||||
|
||||
@api.model
|
||||
def _calculate_mrp_applicable(self):
|
||||
logger.info('CALCULATE MRP APPLICABLE')
|
||||
sql_stat = '''UPDATE product_product SET mrp_applicable = False;'''
|
||||
self.env.cr.execute(sql_stat)
|
||||
|
||||
sql_stat = '''
|
||||
UPDATE product_product SET mrp_applicable=True
|
||||
FROM product_template
|
||||
WHERE product_tmpl_id = product_template.id
|
||||
AND product_template.active = True
|
||||
AND product_template.type = 'product'
|
||||
AND mrp_minimum_stock > (SELECT sum(qty) FROM stock_quant, stock_location
|
||||
WHERE stock_quant.product_id = product_product.id
|
||||
AND stock_quant.location_id = stock_location.id
|
||||
AND stock_location.usage = 'internal');'''
|
||||
self.env.cr.execute(sql_stat)
|
||||
|
||||
sql_stat = '''
|
||||
UPDATE product_product SET mrp_applicable=True
|
||||
FROM product_template
|
||||
WHERE product_tmpl_id = product_template.id
|
||||
AND product_template.active = True
|
||||
AND product_template.type = 'product'
|
||||
AND product_product.id in (SELECT distinct product_id FROM stock_move WHERE
|
||||
state <> 'draft' AND state <> 'cancel');'''
|
||||
self.env.cr.execute(sql_stat)
|
||||
|
||||
sql_stat = '''
|
||||
UPDATE product_product SET mrp_applicable=True
|
||||
FROM product_template
|
||||
WHERE product_tmpl_id = product_template.id
|
||||
AND product_template.active = True
|
||||
AND product_template.type = 'product'
|
||||
AND llc > 0;'''
|
||||
self.env.cr.execute(sql_stat)
|
||||
|
||||
sql_stat = '''
|
||||
UPDATE product_product SET mrp_applicable=True
|
||||
FROM mrp_forecast_product
|
||||
WHERE product_product.id = mrp_forecast_product.product_id;'''
|
||||
self.env.cr.execute(sql_stat)
|
||||
|
||||
self.env.cr.commit()
|
||||
counter = 0
|
||||
sql_stat = 'SELECT count(id) AS counter FROM product_product WHERE ' \
|
||||
'mrp_applicable = True'
|
||||
self.env.cr.execute(sql_stat)
|
||||
sql_res = self.env.cr.dictfetchone()
|
||||
if sql_res:
|
||||
counter = sql_res['counter']
|
||||
log_msg = 'END CALCULATE MRP APPLICABLE: %s' % counter
|
||||
logger.info(log_msg)
|
||||
|
||||
@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):
|
||||
forecast = self.env['mrp.forecast.product'].search(
|
||||
[('product_id', '=', mrp_product.product_id.id),
|
||||
('mrp_area_id', '=', mrp_product.mrp_area_id.id)])
|
||||
for fc in forecast:
|
||||
for fc_id in fc.mrp_forecast_ids:
|
||||
mrp_move_data = \
|
||||
self._prepare_mrp_move_data_from_forecast(
|
||||
fc, fc_id, mrp_product)
|
||||
self.env['mrp.move'].create(mrp_move_data)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def _init_mrp_move_from_stock_move(self, mrp_product):
|
||||
# TODO: Should we exclude the quantity done from the moves?
|
||||
move_obj = self.env['stock.move']
|
||||
mrp_move_obj = self.env['mrp.move']
|
||||
location_ids = self.env['stock.location'].search(
|
||||
[('id', 'child_of', mrp_product.mrp_area_id.location_id.id)])
|
||||
in_moves = move_obj.search(
|
||||
[('product_id', '=', mrp_product.product_id.id),
|
||||
('state', '!=', 'done'),
|
||||
('state', '!=', 'cancel'),
|
||||
('product_qty', '>', 0.00),
|
||||
('location_id', 'in', location_ids.ids)])
|
||||
out_moves = move_obj.search(
|
||||
[('product_id', '=', mrp_product.product_id.id),
|
||||
('state', '!=', 'done'),
|
||||
('state', '!=', 'cancel'),
|
||||
('product_qty', '>', 0.00),
|
||||
('location_dest_id', 'in', location_ids.ids)])
|
||||
moves = in_moves + out_moves
|
||||
for move in moves:
|
||||
move_data = self._prepare_mrp_move_data_from_stock_move(
|
||||
mrp_product, move)
|
||||
mrp_move_obj.create(move_data)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def _prepare_mrp_move_data_from_purchase_requisition(self, preql,
|
||||
mrp_product):
|
||||
mrp_date = date.today()
|
||||
if preql.requisition_id.schedule_date and \
|
||||
datetime.date(datetime.strptime(
|
||||
preql.requisition_id.schedule_date,
|
||||
'%Y-%m-%d %H:%M:%S')) > date.today():
|
||||
mrp_date = datetime.date(datetime.strptime(
|
||||
preql.requisition_id.schedule_date,
|
||||
'%Y-%m-%d %H:%M:%S'))
|
||||
return {
|
||||
'product_id': preql.product_id.id,
|
||||
'mrp_product_id': mrp_product.id,
|
||||
'production_id': None,
|
||||
'purchase_order_id': None,
|
||||
'purchase_line_id': None,
|
||||
'sale_order_id': None,
|
||||
'sale_line_id': None,
|
||||
'stock_move_id': None,
|
||||
'mrp_qty': preql.product_qty,
|
||||
'current_qty': preql.product_qty,
|
||||
'mrp_date': mrp_date,
|
||||
'current_date': preql.requisition_id.schedule_date,
|
||||
'mrp_action': 'none',
|
||||
'mrp_type': 's',
|
||||
'mrp_processed': False,
|
||||
'mrp_origin': 'pr',
|
||||
'mrp_order_number': preql.requisition_id.name,
|
||||
'parent_product_id': None,
|
||||
'running_availability': 0.00,
|
||||
'name': preql.requisition_id.name,
|
||||
'state': preql.requisition_id.state,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _init_mrp_move_from_purchase_requisition(self, mrp_product):
|
||||
location_ids = self.env['stock.location'].search(
|
||||
[('id', 'child_of', mrp_product.mrp_area_id.location_id.id)])
|
||||
picking_types = self.env['stock.picking.type'].search(
|
||||
[('default_location_dest_id', 'in', location_ids.ids)])
|
||||
picking_type_ids = [ptype.id for ptype in picking_types]
|
||||
requisitions = self.env['purchase.requisition'].search(
|
||||
[('picking_type_id', 'in', picking_type_ids),
|
||||
('state', '=', 'draft')])
|
||||
req_lines = self.env['purchase.requisition.line'].search(
|
||||
[('requisition_id', 'in', requisitions.ids),
|
||||
('product_qty', '>', 0.0),
|
||||
('product_id', '=', mrp_product.product_id.id)])
|
||||
|
||||
for preql in req_lines:
|
||||
mrp_move_data = \
|
||||
self._prepare_mrp_move_data_from_purchase_requisition(
|
||||
preql, mrp_product)
|
||||
self.env['mrp.move'].create(mrp_move_data)
|
||||
|
||||
@api.model
|
||||
def _prepare_mrp_move_data_from_purchase_order(self, poline, mrp_product):
|
||||
mrp_date = date.today()
|
||||
if datetime.date(datetime.strptime(
|
||||
poline.date_planned, '%Y-%m-%d')) > date.today():
|
||||
mrp_date = datetime.date(datetime.strptime(
|
||||
poline.date_planned, '%Y-%m-%d'))
|
||||
return {
|
||||
'product_id': poline.product_id.id,
|
||||
'mrp_product_id': mrp_product.id,
|
||||
'production_id': None,
|
||||
'purchase_order_id': poline.order_id.id,
|
||||
'purchase_line_id': poline.id,
|
||||
'sale_order_id': None,
|
||||
'sale_line_id': None,
|
||||
'stock_move_id': None,
|
||||
'mrp_qty': poline.product_qty,
|
||||
'current_qty': poline.product_qty,
|
||||
'mrp_date': mrp_date,
|
||||
'current_date': poline.date_planned,
|
||||
'mrp_action': 'none',
|
||||
'mrp_type': 's',
|
||||
'mrp_processed': False,
|
||||
'mrp_origin': 'po',
|
||||
'mrp_order_number': poline.order_id.name,
|
||||
'parent_product_id': None,
|
||||
'running_availability': 0.00,
|
||||
'name': poline.order_id.name,
|
||||
'state': poline.order_id.state,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _init_mrp_move_from_purchase_order(self, mrp_product):
|
||||
location_ids = self.env['stock.location'].search(
|
||||
[('id', 'child_of', mrp_product.mrp_area_id.location_id.id)])
|
||||
picking_types = self.env['stock.picking.type'].search(
|
||||
[('default_location_dest_id', 'in',
|
||||
location_ids.ids)])
|
||||
picking_type_ids = [ptype.id for ptype in picking_types]
|
||||
orders = self.env['purchase.order'].search(
|
||||
[('picking_type_id', 'in', picking_type_ids),
|
||||
('state', 'in', ['draft', 'confirmed'])])
|
||||
po_lines = self.env['purchase.order.line'].search(
|
||||
[('order_id', 'in', orders.ids),
|
||||
('product_qty', '>', 0.0),
|
||||
('product_id', '=', mrp_product.product_id.id)])
|
||||
|
||||
for poline in po_lines:
|
||||
mrp_move_data = \
|
||||
self._prepare_mrp_move_data_from_purchase_order(
|
||||
poline, mrp_product)
|
||||
self.env['mrp.move'].create(mrp_move_data)
|
||||
|
||||
@api.model
|
||||
def _prepare_mrp_move_data_from_mrp_production(self, mo, mrp_product):
|
||||
mrp_date = date.today()
|
||||
if datetime.date(datetime.strptime(
|
||||
mo.date_planned, '%Y-%m-%d %H:%M:%S')) > date.today():
|
||||
mrp_date = datetime.date(datetime.strptime(
|
||||
mo.date_planned, '%Y-%m-%d %H:%M:%S'))
|
||||
return {
|
||||
'mrp_area_id': mrp_product.mrp_area_id.id,
|
||||
'product_id': mo.product_id.id,
|
||||
'mrp_product_id': mrp_product.id,
|
||||
'production_id': mo.id,
|
||||
'purchase_order_id': None,
|
||||
'purchase_line_id': None,
|
||||
'sale_order_id': None,
|
||||
'sale_line_id': None,
|
||||
'stock_move_id': None,
|
||||
'mrp_qty': mo.product_qty,
|
||||
'current_qty': mo.product_qty,
|
||||
'mrp_date': mrp_date,
|
||||
'current_date': mo.date_planned,
|
||||
'mrp_action': 'none',
|
||||
'mrp_type': 's',
|
||||
'mrp_processed': False,
|
||||
'mrp_origin': 'mo',
|
||||
'mrp_order_number': mo.name,
|
||||
'parent_product_id': None,
|
||||
'running_availability': 0.00,
|
||||
'name': mo.name,
|
||||
'state': mo.state,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _prepare_mrp_move_data_from_mrp_production_bom(self, mo, bomline,
|
||||
mrp_date_demand,
|
||||
mrp_product):
|
||||
return {
|
||||
'mrp_area_id': mrp_product.mrp_area_id.id,
|
||||
'product_id': bomline.product_id.id,
|
||||
'mrp_product_id':
|
||||
bomline.product_id.mrp_product_id.id,
|
||||
'production_id': mo.id,
|
||||
'purchase_order_id': None,
|
||||
'purchase_line_id': None,
|
||||
'sale_order_id': None,
|
||||
'sale_line_id': None,
|
||||
'stock_move_id': None,
|
||||
'mrp_qty': -(mo.product_qty * bomline.product_qty),
|
||||
'current_qty': None,
|
||||
'mrp_date': mrp_date_demand,
|
||||
'current_date': None,
|
||||
'mrp_action': 'none',
|
||||
'mrp_type': 'd',
|
||||
'mrp_processed': False,
|
||||
'mrp_origin': 'mo',
|
||||
'mrp_order_number': mo.name,
|
||||
'parent_product_id': mo.product_id.id,
|
||||
'name': ('Demand Bom Explosion: ' + mo.name).replace(
|
||||
'Demand Bom Explosion: ',
|
||||
'Demand Bom Explosion: ',
|
||||
'Demand Bom Explosion: '),
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _init_mrp_move_from_mrp_production_bom(self, mo, mrp_product):
|
||||
mrp_date = date.today()
|
||||
mrp_date_demand = mrp_date-timedelta(
|
||||
days=mrp_product.product_id.mrp_lead_time)
|
||||
if mrp_date_demand < date.today():
|
||||
mrp_date_demand = date.today()
|
||||
if mo.bom_id and mo.bom_id.bom_line_ids:
|
||||
for bomline in mo.bom_id.bom_line_ids:
|
||||
if bomline.product_qty <= 0.00:
|
||||
continue
|
||||
if (bomline.date_start and datetime.date(
|
||||
datetime.strptime(bomline.date_start, '%Y-%m-%d')) >=
|
||||
mrp_date_demand):
|
||||
continue
|
||||
if (bomline.date_stop and datetime.date(
|
||||
datetime.strptime(bomline.date_stop, '%Y-%m-%d')) <=
|
||||
mrp_date_demand):
|
||||
continue
|
||||
mrp_move_data = \
|
||||
self._prepare_mrp_move_data_from_mrp_production_bom(
|
||||
mo, bomline, mrp_date_demand, mrp_product)
|
||||
self.env['mrp.move'].create(mrp_move_data)
|
||||
|
||||
@api.model
|
||||
def _init_mrp_move_from_mrp_production(self, mrp_product):
|
||||
location_ids = self.env['stock.location'].search(
|
||||
[('id', 'child_of', mrp_product.mrp_area_id.location_id.id)])
|
||||
production_orders = self.env['mrp.production'].search(
|
||||
[('location_dest_id', 'in', location_ids.ids),
|
||||
('product_qty', '>', 0.0),
|
||||
('state', '=', 'draft')])
|
||||
for mo in production_orders:
|
||||
mrp_move_data = \
|
||||
self._prepare_mrp_move_data_from_mrp_production(
|
||||
mo, mrp_product)
|
||||
self.env['mrp.move'].create(mrp_move_data)
|
||||
self._init_mrp_move_from_mrp_production_bom(mo, mrp_product)
|
||||
|
||||
@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_requisition(mrp_product)
|
||||
self._init_mrp_move_from_purchase_order(mrp_product)
|
||||
self._init_mrp_move_from_mrp_production(mrp_product)
|
||||
|
||||
@api.model
|
||||
def _exclude_from_mrp(self, mrp_area, product):
|
||||
""" To extend with various logic where needed. """
|
||||
return product.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([('mrp_applicable',
|
||||
'=', True)])
|
||||
init_counter = 0
|
||||
for mrp_area in mrp_areas:
|
||||
for product in products:
|
||||
if self._exclude_from_mrp(mrp_area, product):
|
||||
continue
|
||||
init_counter += 1
|
||||
log_msg = 'MRP INIT: %s - %s ' % (
|
||||
init_counter, product.default_code)
|
||||
logger.info(log_msg)
|
||||
mrp_product = self._init_mrp_product(product, mrp_area)
|
||||
self._init_mrp_move(mrp_product)
|
||||
self.env.cr.commit()
|
||||
logger.info('END MRP INITIALISATION')
|
||||
|
||||
@api.model
|
||||
def _init_mrp_move_grouped_demand(self, nbr_create, mrp_product):
|
||||
last_date = None
|
||||
last_qty = 0.00
|
||||
move_ids = []
|
||||
for move in mrp_product.mrp_move_ids:
|
||||
move_ids.append(move.id)
|
||||
for move_id in move_ids:
|
||||
move_rec = self.env['mrp.move'].search(
|
||||
[('id', '=', move_id)])
|
||||
for move in move_rec:
|
||||
if move.mrp_action == 'none':
|
||||
if last_date is not None:
|
||||
if datetime.date(
|
||||
datetime.strptime(
|
||||
move.mrp_date, '%Y-%m-%d')) \
|
||||
> last_date+timedelta(
|
||||
days=mrp_product.mrp_nbr_days):
|
||||
if (onhand + last_qty + move.mrp_qty) \
|
||||
< mrp_product.mrp_minimum_stock \
|
||||
or (onhand + last_qty) \
|
||||
< mrp_product.mrp_minimum_stock:
|
||||
name = 'Grouped Demand for ' \
|
||||
'%d Days' % (
|
||||
mrp_product.mrp_nbr_days, )
|
||||
qtytoorder = \
|
||||
mrp_product.mrp_minimum_stock - \
|
||||
mrp_product - last_qty
|
||||
cm = self.create_move(
|
||||
mrp_product_id=mrp_product.id,
|
||||
mrp_date=last_date,
|
||||
mrp_qty=qtytoorder,
|
||||
name=name)
|
||||
qty_ordered = cm['qty_ordered']
|
||||
onhand = onhand + last_qty + qty_ordered
|
||||
last_date = None
|
||||
last_qty = 0.00
|
||||
nbr_create += 1
|
||||
if (onhand + last_qty + move.mrp_qty) < \
|
||||
mrp_product.mrp_minimum_stock or \
|
||||
(onhand + last_qty) < \
|
||||
mrp_product.mrp_minimum_stock:
|
||||
if last_date is None:
|
||||
last_date = datetime.date(
|
||||
datetime.strptime(move.mrp_date,
|
||||
'%Y-%m-%d'))
|
||||
last_qty = move.mrp_qty
|
||||
else:
|
||||
last_qty = last_qty + move.mrp_qty
|
||||
else:
|
||||
last_date = datetime.date(
|
||||
datetime.strptime(move.mrp_date,
|
||||
'%Y-%m-%d'))
|
||||
onhand = onhand + move.mrp_qty
|
||||
|
||||
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
|
||||
cm = self.create_move(
|
||||
mrp_product_id=mrp_product.id, mrp_date=last_date,
|
||||
mrp_qty=qtytoorder, name=name)
|
||||
qty_ordered = cm['qty_ordered']
|
||||
onhand += qty_ordered
|
||||
nbr_create += 1
|
||||
return nbr_create
|
||||
|
||||
@api.model
|
||||
def _mrp_calculation(self, mrp_lowest_llc):
|
||||
logger.info('START MRP CALCULATION')
|
||||
mrp_product_obj = self.env['mrp.product']
|
||||
counter = 0
|
||||
for mrp_area in self.env['mrp.area'].search([]):
|
||||
llc = 0
|
||||
while mrp_lowest_llc > llc:
|
||||
self.env.cr.commit()
|
||||
mrp_products = mrp_product_obj.search(
|
||||
[('mrp_llc', '=', llc),
|
||||
('mrp_area_id', '=', mrp_area.id)])
|
||||
llc += 1
|
||||
|
||||
for mrp_product in mrp_products:
|
||||
nbr_create = 0
|
||||
onhand = mrp_product.mrp_qty_available
|
||||
if mrp_product.mrp_nbr_days == 0:
|
||||
# todo: review ordering by date
|
||||
for move in mrp_product.mrp_move_ids:
|
||||
if move.mrp_action == 'none':
|
||||
if (onhand + move.mrp_qty) < \
|
||||
mrp_product.mrp_minimum_stock:
|
||||
name = move.name
|
||||
qtytoorder = \
|
||||
mrp_product.mrp_minimum_stock - \
|
||||
onhand - move.mrp_qty
|
||||
cm = self.create_move(
|
||||
mrp_product_id=mrp_product.id,
|
||||
mrp_date=move.mrp_date,
|
||||
mrp_qty=qtytoorder, name=name)
|
||||
qty_ordered = cm['qty_ordered']
|
||||
onhand += move.mrp_qty + qty_ordered
|
||||
nbr_create += 1
|
||||
else:
|
||||
onhand += move.mrp_qty
|
||||
else:
|
||||
nbr_create = self._init_mrp_move_grouped_demand(
|
||||
nbr_create, mrp_product)
|
||||
|
||||
if onhand < mrp_product.mrp_minimum_stock and \
|
||||
nbr_create == 0:
|
||||
name = 'Minimum Stock'
|
||||
qtytoorder = mrp_product.mrp_minimum_stock - onhand
|
||||
cm = self.create_move(mrp_product_id=mrp_product.id,
|
||||
mrp_date=date.today(),
|
||||
mrp_qty=qtytoorder, name=name)
|
||||
qty_ordered = cm['qty_ordered']
|
||||
onhand += qty_ordered
|
||||
counter += 1
|
||||
self.env.cr.commit()
|
||||
log_msg = 'MRP CALCULATION LLC %s FINISHED - NBR PRODUCTS: %s' %(
|
||||
llc - 1, counter)
|
||||
logger.info(log_msg)
|
||||
if llc < 0:
|
||||
counter = 999999
|
||||
|
||||
self.env.cr.commit()
|
||||
logger.info('END MRP CALCULATION')
|
||||
|
||||
@api.model
|
||||
def _init_mrp_inventory(self, mrp_product):
|
||||
# Read Demand
|
||||
demand_qty_by_date = {}
|
||||
sql_stat = '''SELECT mrp_date, sum(mrp_qty) as demand_qty
|
||||
FROM mrp_move
|
||||
WHERE mrp_product_id = %d
|
||||
AND mrp_type = 'd'
|
||||
GROUP BY mrp_date''' % (mrp_product.id, )
|
||||
self.env.cr.execute(sql_stat)
|
||||
for sql_res in self.env.cr.dictfetchall():
|
||||
demand_qty_by_date[sql_res['mrp_date']] = sql_res['demand_qty']
|
||||
|
||||
|
||||
# Read Supply
|
||||
supply_qty_by_date = {}
|
||||
|
||||
sql_stat = '''SELECT mrp_date, sum(mrp_qty) as supply_qty
|
||||
FROM mrp_move
|
||||
WHERE mrp_product_id = %d
|
||||
AND mrp_type = 's'
|
||||
AND mrp_action = 'none'
|
||||
GROUP BY mrp_date''' % (mrp_product.id, )
|
||||
self.env.cr.execute(sql_stat)
|
||||
for sql_res in self.env.cr.dictfetchall():
|
||||
supply_qty_by_date[sql_res['mrp_date']] = sql_res['supply_qty']
|
||||
|
||||
# Read supply actions
|
||||
supply_actions_qty_by_date = {}
|
||||
mrp_type = 's'
|
||||
exclude_mrp_actions = ['none', 'cancel']
|
||||
sql_stat = '''SELECT mrp_date, sum(mrp_qty) as actions_qty
|
||||
FROM mrp_move
|
||||
WHERE mrp_product_id = %d
|
||||
AND mrp_qty <> 0.0
|
||||
AND mrp_type = 's'
|
||||
AND mrp_action not in %s
|
||||
GROUP BY mrp_date''' % (mrp_product.id,
|
||||
tuple(exclude_mrp_actions))
|
||||
self.env.cr.execute(sql_stat)
|
||||
for sql_res in self.env.cr.dictfetchall():
|
||||
supply_actions_qty_by_date[sql_res['mrp_date']] = \
|
||||
sql_res['actions_qty']
|
||||
|
||||
# Dates
|
||||
sql_stat = '''SELECT mrp_date
|
||||
FROM mrp_move
|
||||
WHERE mrp_product_id = %d
|
||||
GROUP BY mrp_date
|
||||
ORDER BY mrp_date''' % (mrp_product.id, )
|
||||
self.env.cr.execute(sql_stat)
|
||||
on_hand_qty = mrp_product.current_qty_available
|
||||
for sql_res in self.env.cr.dictfetchall():
|
||||
mdt = sql_res['mrp_date']
|
||||
mrp_inventory_data = {
|
||||
'mrp_product_id': mrp_product.id,
|
||||
'date': mdt,
|
||||
}
|
||||
demand_qty = 0.0
|
||||
supply_qty = 0.0
|
||||
if mdt in demand_qty_by_date.keys():
|
||||
demand_qty = demand_qty_by_date[mdt]
|
||||
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
|
||||
on_hand_qty += (supply_qty + demand_qty)
|
||||
mrp_inventory_data['final_on_hand_qty'] = on_hand_qty
|
||||
|
||||
self.env['mrp.inventory'].create(mrp_inventory_data)
|
||||
|
||||
@api.model
|
||||
def _mrp_final_process(self):
|
||||
logger.info('START MRP FINAL PROCESS')
|
||||
mrp_areas = self.env['mrp.area'].search([])
|
||||
mrp_product_ids = self.env['mrp.product'].search(
|
||||
[('mrp_llc', '<', 9999),
|
||||
('mrp_area_id', 'in', mrp_areas.ids)])
|
||||
|
||||
for mrp_product in mrp_product_ids:
|
||||
# Build the time-phased inventory
|
||||
self._init_mrp_inventory(mrp_product)
|
||||
|
||||
# Complete the info on mrp_move
|
||||
qoh = mrp_product.mrp_qty_available
|
||||
nbr_actions = 0
|
||||
nbr_actions_4w = 0
|
||||
sql_stat = 'SELECT id, mrp_date, mrp_qty, mrp_action FROM ' \
|
||||
'mrp_move WHERE mrp_product_id = %d ' \
|
||||
'ORDER BY ' \
|
||||
'mrp_date, ' \
|
||||
'mrp_type desc, id' % (mrp_product.id, )
|
||||
self.env.cr.execute(sql_stat)
|
||||
for sql_res in self.env.cr.dictfetchall():
|
||||
qoh = qoh + sql_res['mrp_qty']
|
||||
self.env['mrp.move'].search(
|
||||
[('id', '=', sql_res['id'])]).write(
|
||||
{'running_availability': qoh})
|
||||
|
||||
for move in mrp_product.mrp_move_ids:
|
||||
if move.mrp_action != 'none':
|
||||
nbr_actions += 1
|
||||
if move.mrp_date:
|
||||
if move.mrp_action != 'none' and \
|
||||
datetime.date(datetime.strptime(
|
||||
move.mrp_action_date, '%Y-%m-%d')) < \
|
||||
date.today()+timedelta(days=29):
|
||||
nbr_actions_4w += 1
|
||||
if nbr_actions > 0:
|
||||
self.env['mrp.product'].search(
|
||||
[('id', '=', mrp_product.id)]).write(
|
||||
{'nbr_mrp_actions': nbr_actions,
|
||||
'nbr_mrp_actions_4w': nbr_actions_4w})
|
||||
self.env.cr.commit()
|
||||
logger.info('END MRP FINAL PROCESS')
|
||||
|
||||
@api.one
|
||||
def run_multi_level_mrp(self):
|
||||
self._mrp_cleanup()
|
||||
mrp_lowest_llc = self._low_level_code_calculation()
|
||||
self._calculate_mrp_applicable()
|
||||
self._mrp_initialisation()
|
||||
self._mrp_calculation(mrp_lowest_llc)
|
||||
self._mrp_final_process()
|
||||
29
multi_level_mrp/wizards/multi_level_mrp_view.xml
Normal file
29
multi_level_mrp/wizards/multi_level_mrp_view.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="view_run_multi_level_mrp_wizard" model="ir.ui.view">
|
||||
<field name="name">Run MRP</field>
|
||||
<field name="model">multi.level.mrp</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Run Multi Level MRP" version="7.0">
|
||||
<footer>
|
||||
<button name="run_multi_level_mrp" string="Run MRP" type="object" class="oe_highlight" />
|
||||
or
|
||||
<button string="Cancel" class="oe_link" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<act_window name="Run MRP"
|
||||
res_model="multi.level.mrp"
|
||||
src_model="multi.level.mrp"
|
||||
view_mode="form"
|
||||
target="new"
|
||||
key2="client_action_multi"
|
||||
id="action_multi_level_mrp"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
Reference in New Issue
Block a user