mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
[11.0][REN] multi_level_mrp -> mrp_multi_level
This commit is contained in:
committed by
Jordi Ballester Alomar
parent
cad67a0231
commit
7bc6533e98
6
mrp_multi_level/models/__init__.py
Normal file
6
mrp_multi_level/models/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from . import mrp_area
|
||||
from . import stock_location
|
||||
from . import product
|
||||
from . import mrp_product
|
||||
from . import mrp_move
|
||||
from . import mrp_inventory
|
||||
19
mrp_multi_level/models/mrp_area.py
Normal file
19
mrp_multi_level/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)
|
||||
44
mrp_multi_level/models/mrp_inventory.py
Normal file
44
mrp_multi_level/models/mrp_inventory.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
# Copyright 2016-18 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
|
||||
|
||||
|
||||
class MrpInventory(models.Model):
|
||||
_name = 'mrp.inventory'
|
||||
_order = 'mrp_product_id, date'
|
||||
_description = 'MRP inventory projections'
|
||||
_rec_name = 'mrp_product_id'
|
||||
|
||||
# TODO: uom??
|
||||
# TODO: name to pass to procurements?
|
||||
# TODO: compute procurement_date to pass to the wizard? not needed for PO at least. Check for MO and moves
|
||||
# TODO: substract qty already procured.
|
||||
# TODO: show a LT based on the procure method?
|
||||
# TODO: add to_procure_date
|
||||
|
||||
mrp_area_id = fields.Many2one(
|
||||
comodel_name='mrp.area', string='MRP Area',
|
||||
related='mrp_product_id.mrp_area_id',
|
||||
)
|
||||
mrp_product_id = fields.Many2one(
|
||||
comodel_name='mrp.product', string='Product',
|
||||
index=True,
|
||||
)
|
||||
uom_id = fields.Many2one(
|
||||
comodel_name='product.uom', string='Product UoM',
|
||||
compute='_compute_uom_id',
|
||||
)
|
||||
date = fields.Date(string='Date')
|
||||
demand_qty = fields.Float(string='Demand')
|
||||
supply_qty = fields.Float(string='Supply')
|
||||
initial_on_hand_qty = fields.Float(string='Starting Inventory')
|
||||
final_on_hand_qty = fields.Float(string='Forecasted Inventory')
|
||||
to_procure = fields.Float(string='To procure')
|
||||
|
||||
@api.multi
|
||||
def _compute_uom_id(self):
|
||||
for rec in self:
|
||||
rec.uom_id = rec.mrp_product_id.product_id.uom_id
|
||||
173
mrp_multi_level/models/mrp_move.py
Normal file
173
mrp_multi_level/models/mrp_move.py
Normal file
@@ -0,0 +1,173 @@
|
||||
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
# © 2016-18 Eficent Business and IT Consulting Services S.L.
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo import exceptions
|
||||
|
||||
|
||||
class MrpMove(models.Model):
|
||||
_name = 'mrp.move'
|
||||
_order = 'mrp_product_id, mrp_date, mrp_type desc, id'
|
||||
|
||||
# TODO: too many indexes...
|
||||
|
||||
mrp_area_id = fields.Many2one('mrp.area', 'MRP Area')
|
||||
current_date = fields.Date('Current Date')
|
||||
current_qty = fields.Float('Current Qty')
|
||||
# TODO: remove purchase request and move to other module?
|
||||
# TODO: cancel is not needed I think...
|
||||
mrp_action = fields.Selection(
|
||||
selection=[('mo', 'Manufacturing Order'),
|
||||
('po', 'Purchase Order'),
|
||||
('pr', 'Purchase Request'),
|
||||
('so', 'Sale Order'),
|
||||
('cancel', 'Cancel'),
|
||||
('none', 'None')],
|
||||
string='Action',
|
||||
)
|
||||
mrp_action_date = fields.Date('MRP Action Date')
|
||||
mrp_date = fields.Date('MRP Date')
|
||||
mrp_move_down_ids = fields.Many2many(
|
||||
comodel_name='mrp.move',
|
||||
relation='mrp_move_rel',
|
||||
column1='move_up_id',
|
||||
column2='move_down_id',
|
||||
string='MRP Move DOWN',
|
||||
)
|
||||
mrp_move_up_ids = fields.Many2many(
|
||||
comodel_name='mrp.move',
|
||||
relation='mrp_move_rel',
|
||||
column1='move_down_id',
|
||||
column2='move_up_id',
|
||||
string='MRP Move UP',
|
||||
)
|
||||
mrp_minimum_stock = fields.Float(
|
||||
string='Minimum Stock',
|
||||
related='product_id.mrp_minimum_stock',
|
||||
)
|
||||
mrp_order_number = fields.Char('Order Number')
|
||||
# TODO: move purchase request to another module
|
||||
mrp_origin = fields.Selection(
|
||||
selection=[('mo', 'Manufacturing Order'),
|
||||
('po', 'Purchase Order'),
|
||||
('pr', 'Purchase Request'),
|
||||
('so', 'Sale Order'),
|
||||
('mv', 'Move'),
|
||||
('fc', 'Forecast'), ('mrp', 'MRP')],
|
||||
string='Origin')
|
||||
mrp_processed = fields.Boolean('Processed')
|
||||
mrp_product_id = fields.Many2one('mrp.product', 'Product', index=True)
|
||||
mrp_qty = fields.Float('MRP Quantity')
|
||||
mrp_type = fields.Selection(
|
||||
selection=[('s', 'Supply'), ('d', 'Demand')],
|
||||
string='Type',
|
||||
)
|
||||
name = fields.Char('Description')
|
||||
parent_product_id = fields.Many2one(
|
||||
comodel_name='product.product',
|
||||
string='Parent Product', index=True,
|
||||
)
|
||||
product_id = fields.Many2one('product.product',
|
||||
'Product', index=True)
|
||||
production_id = fields.Many2one('mrp.production',
|
||||
'Manufacturing Order', index=True)
|
||||
purchase_line_id = fields.Many2one('purchase.order.line',
|
||||
'Purchase Order Line', index=True)
|
||||
purchase_order_id = fields.Many2one('purchase.order',
|
||||
'Purchase Order', index=True)
|
||||
running_availability = fields.Float('Running Availability')
|
||||
sale_line_id = fields.Many2one('sale.order.line',
|
||||
'Sale Order Line', index=True)
|
||||
sale_order_id = fields.Many2one('sale.order', 'Sale Order', index=True)
|
||||
state = fields.Selection(
|
||||
selection=[('draft', 'Draft'),
|
||||
('assigned', 'Assigned'),
|
||||
('confirmed', 'Confirmed'),
|
||||
('waiting', 'Waiting'),
|
||||
('partially_available', 'Partially Available'),
|
||||
('ready', 'Ready'),
|
||||
('sent', 'Sent'),
|
||||
('to approve', 'To Approve'),
|
||||
('approved', 'Approved')],
|
||||
string='State',
|
||||
)
|
||||
stock_move_id = fields.Many2one('stock.move', 'Stock Move', index=True)
|
||||
|
||||
@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
|
||||
|
||||
# TODO: extension to purchase requisition in other module?
|
||||
@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,
|
||||
'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}]],
|
||||
'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
|
||||
119
mrp_multi_level/models/mrp_product.py
Normal file
119
mrp_multi_level/models/mrp_product.py
Normal file
@@ -0,0 +1,119 @@
|
||||
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
# © 2016-18 Eficent Business and IT Consulting Services S.L.
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class MrpProduct(models.Model):
|
||||
_name = 'mrp.product'
|
||||
|
||||
mrp_area_id = fields.Many2one(
|
||||
comodel_name='mrp.area', string='MRP Area',
|
||||
)
|
||||
current_qty_available = fields.Float(
|
||||
string='Current Qty Available',
|
||||
related='product_id.qty_available',
|
||||
)
|
||||
main_supplier_id = fields.Many2one(
|
||||
comodel_name='res.partner', string='Main Supplier',
|
||||
compute='_compute_main_supplier', store=True,
|
||||
index=True,
|
||||
)
|
||||
mrp_inspection_delay = fields.Integer(
|
||||
string='Inspection Delay', related='product_id.mrp_inspection_delay')
|
||||
mrp_lead_time = fields.Float(
|
||||
string='Lead Time',
|
||||
related='product_id.produce_delay',
|
||||
)
|
||||
mrp_llc = fields.Integer(
|
||||
string='Low Level Code', index=True, readonly=True,
|
||||
)
|
||||
# TODO: minimun stock and max/min order qty assigned by area?
|
||||
mrp_maximum_order_qty = fields.Float(
|
||||
string='Maximum Order Qty', related='product_id.mrp_maximum_order_qty',
|
||||
)
|
||||
mrp_minimum_order_qty = fields.Float(
|
||||
string='Minimum Order Qty', related='product_id.mrp_minimum_order_qty',
|
||||
)
|
||||
mrp_minimum_stock = fields.Float(
|
||||
string='Minimum Stock',
|
||||
related='product_id.mrp_minimum_stock',
|
||||
)
|
||||
mrp_move_ids = fields.One2many(
|
||||
comodel_name='mrp.move', inverse_name='mrp_product_id',
|
||||
string='MRP Moves',
|
||||
)
|
||||
mrp_nbr_days = fields.Integer(
|
||||
string='Nbr. Days', related='product_id.mrp_nbr_days')
|
||||
mrp_qty_available = fields.Float('MRP Qty Available')
|
||||
mrp_qty_multiple = fields.Float(string='Qty Multiple',
|
||||
related='product_id.mrp_qty_multiple')
|
||||
# TODO: this was: mrp_transit_delay = fields.Integer(mrp_move_ids) ??¿?¿¿?
|
||||
mrp_transit_delay = fields.Integer(related='product_id.mrp_transit_delay')
|
||||
mrp_verified = fields.Boolean(string='MRP Verified',
|
||||
related='product_id.mrp_verified')
|
||||
name = fields.Char('Description')
|
||||
# TODO: rename to mrp_action_count?
|
||||
nbr_mrp_actions = fields.Integer(
|
||||
string='Nbr Actions', index=True,
|
||||
)
|
||||
nbr_mrp_actions_4w = fields.Integer(
|
||||
string='Nbr Actions 4 Weeks', index=True,
|
||||
)
|
||||
product_id = fields.Many2one(
|
||||
comodel_name='product.product', string='Product',
|
||||
index=True,
|
||||
)
|
||||
product_tmpl_id = fields.Many2one('product.template', 'Product Template',
|
||||
related='product_id.product_tmpl_id')
|
||||
# TODO: extension to purchase requisition in other module?
|
||||
# purchase_requisition = fields.Boolean(string='Purchase Requisition',
|
||||
# related='product_id.purchase_requisition')
|
||||
supply_method = fields.Selection(
|
||||
selection=[('buy', 'Buy'),
|
||||
('none', 'Undefined'),
|
||||
('manufacture', 'Produce'),
|
||||
('move', 'Transfer')],
|
||||
string='Supply Method',
|
||||
compute='_compute_supply_method', store=True,
|
||||
)
|
||||
|
||||
@api.multi
|
||||
@api.depends('mrp_area_id')
|
||||
def _compute_supply_method(self):
|
||||
group_obj = self.env['procurement.group']
|
||||
for rec in self:
|
||||
values = {
|
||||
'warehouse_id': rec.mrp_area_id.warehouse_id,
|
||||
'company_id': self.env.user.company_id.id, # TODO: better way to get company
|
||||
}
|
||||
rule = group_obj._get_rule(
|
||||
rec.product_id, rec.mrp_area_id.location_id, values)
|
||||
rec.supply_method = rule.action if rule else 'none'
|
||||
|
||||
@api.multi
|
||||
@api.depends('supply_method')
|
||||
def _compute_main_supplier(self):
|
||||
"""Simplified and similar to procurement.rule logic."""
|
||||
for rec in self.filtered(lambda r: r.supply_method == 'buy'):
|
||||
suppliers = rec.product_id.seller_ids.filtered(
|
||||
lambda r: (not r.product_id or r.product_id == rec.product_id))
|
||||
if not suppliers:
|
||||
continue
|
||||
rec.main_supplier_id = suppliers[0].name
|
||||
|
||||
@api.multi
|
||||
def _adjust_qty_to_order(self, qty_to_order):
|
||||
# TODO: consider mrp_qty_multiple?
|
||||
self.ensure_one()
|
||||
if not self.mrp_maximum_order_qty and not self.mrp_minimum_order_qty:
|
||||
return qty_to_order
|
||||
if qty_to_order < self.mrp_minimum_order_qty:
|
||||
return self.mrp_minimum_order_qty
|
||||
if self.mrp_maximum_order_qty and qty_to_order > \
|
||||
self.mrp_maximum_order_qty:
|
||||
qty = self.mrp_maximum_order_qty
|
||||
else:
|
||||
qty = qty_to_order
|
||||
return qty
|
||||
47
mrp_multi_level/models/product.py
Normal file
47
mrp_multi_level/models/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 Product(models.Model):
|
||||
_inherit = 'product.product'
|
||||
|
||||
llc = fields.Integer('Low Level Code', default=0)
|
||||
manufacturing_order_ids = fields.One2many(
|
||||
comodel_name='mrp.production',
|
||||
inverse_name='product_id',
|
||||
string='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_maximum_order_qty = fields.Float(
|
||||
string='Maximum Order Qty', default=0.0,
|
||||
)
|
||||
mrp_minimum_order_qty = fields.Float(
|
||||
string='Minimum Order Qty', default=0.0,
|
||||
)
|
||||
mrp_minimum_stock = fields.Float('Minimum Stock')
|
||||
mrp_nbr_days = fields.Integer(
|
||||
string='Nbr. Days', default=0,
|
||||
help="Number of days to group demand for this product during the "
|
||||
"MRP run, in order to determine the quantity to order.",
|
||||
)
|
||||
mrp_product_ids = fields.One2many('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')
|
||||
# TODO: extension to purchase requisition in other module?
|
||||
# purchase_requisition_ids = fields.One2many('purchase.requisition.line',
|
||||
# 'product_id',
|
||||
# 'Purchase Requisitions')
|
||||
16
mrp_multi_level/models/stock_location.py
Normal file
16
mrp_multi_level/models/stock_location.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# © 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(
|
||||
comodel_name='mrp.area', string='MRP Area',
|
||||
help="Requirements for a particular MRP area are combined for the "
|
||||
"purposes of procurement by the MRP.",
|
||||
)
|
||||
Reference in New Issue
Block a user