[mig] multi_level_mrp

This commit is contained in:
Jordi Ballester Alomar
2018-06-07 06:41:52 +02:00
parent 5ff5b06fd9
commit 424e013614
28 changed files with 2491 additions and 0 deletions

View 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.

View File

@@ -0,0 +1,2 @@
from . import models
from . import wizards

View 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,
}

View 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>

View 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

View 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)

View 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')

View 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'

View 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,
}

View 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')

View 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')

View 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.")

View 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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_mrp_inventory_user mrp.inventory model_mrp_inventory mrp.group_mrp_user 1 0 0 0
3 access_mrp_move_user mrp.move model_mrp_move mrp.group_mrp_user 1 0 0 0
4 access_mrp_product mrp.product model_mrp_product base.group_user 1 0 0 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -0,0 +1,3 @@
from . import multi_level_mrp
from . import mrp_move_create_po
from . import mrp_inventory_create_procurement

View 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'
}

View File

@@ -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>

View 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

View 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>

View 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()

View 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>