mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
[11.0][MIG] multi_level_mrp: rework procurement wizard
This commit is contained in:
committed by
Jordi Ballester Alomar
parent
4ae6af9d9f
commit
0456d9b7e4
@@ -1,5 +1,5 @@
|
||||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
|
||||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||
:target: https://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||
:alt: License: AGPL-3
|
||||
|
||||
===============
|
||||
@@ -57,6 +57,7 @@ Contributors
|
||||
|
||||
* Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
* Jordi Ballester <jordi.ballester@eficent.com>
|
||||
* Lois Rilo <lois.rilo@eficent.com>
|
||||
|
||||
|
||||
Maintainer
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
'purchase',
|
||||
],
|
||||
'data': [
|
||||
'security/multi_level_mrp_security.xml',
|
||||
'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',
|
||||
'wizards/mrp_inventory_procure_view.xml',
|
||||
'views/mrp_inventory_view.xml',
|
||||
'wizards/multi_level_mrp_view.xml',
|
||||
'wizards/mrp_move_create_po_view.xml',
|
||||
|
||||
@@ -9,18 +9,18 @@ from datetime import date, datetime
|
||||
|
||||
class MrpForecastForecast(models.Model):
|
||||
_name = 'mrp.forecast.forecast'
|
||||
_order = 'forecast_product_id, date'
|
||||
|
||||
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'
|
||||
# TODO: adapt to demand_estimate?? or at least to date_range??
|
||||
|
||||
@api.one
|
||||
@api.depends('product_id')
|
||||
|
||||
@@ -1,23 +1,43 @@
|
||||
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
# © 2016 Eficent Business and IT Consulting Services S.L.
|
||||
# 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 fields, models
|
||||
from odoo import api, 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'
|
||||
_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?
|
||||
|
||||
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',
|
||||
select=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
|
||||
|
||||
10
multi_level_mrp/security/multi_level_mrp_security.xml
Normal file
10
multi_level_mrp/security/multi_level_mrp_security.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo noupdate="1">
|
||||
|
||||
<record id="group_change_mrp_procure_qty" model="res.groups">
|
||||
<field name="name">Change procure quantity in MRP</field>
|
||||
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
|
||||
<field name="category_id" ref="base.module_category_hidden"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -35,9 +35,10 @@
|
||||
<field name="supply_qty"/>
|
||||
<field name="final_on_hand_qty"/>
|
||||
<field name="to_procure"/>
|
||||
<!--TODO: use same procurement wizard as ddmrp?-->
|
||||
<button string="Create Procurement"
|
||||
name="%(multi_level_mrp.action_mrp_inventory_create_procurement)d"
|
||||
icon="gtk-ok" type="action"
|
||||
name="%(multi_level_mrp.act_mrp_inventory_procure)d"
|
||||
icon="fa-cogs" type="action"
|
||||
attrs="{'invisible':[('to_procure','==',0.0)]}"/>
|
||||
</tree>
|
||||
</field>
|
||||
@@ -78,6 +79,8 @@
|
||||
<field name="search_view_id" ref="mrp_inventory_search"/>
|
||||
<field name="act_window_id" ref="mrp_inventory_form"/>
|
||||
</record>
|
||||
|
||||
<!--TODO: remove?-->
|
||||
<record model="ir.actions.act_window.view" id="mrp_inventory_form_action">
|
||||
<field name="sequence" eval="22"/>
|
||||
<field name="view_mode">form</field>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
from . import multi_level_mrp
|
||||
from . import mrp_move_create_po
|
||||
from . import mrp_inventory_create_procurement
|
||||
from . import mrp_inventory_procure
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
# © 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'
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<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">Procure</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>
|
||||
<field name="binding_model_id" ref="multi_level_mrp.model_mrp_inventory"/>
|
||||
</record>
|
||||
|
||||
<!--todo: REMOVE-->
|
||||
<!--<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>-->
|
||||
|
||||
</odoo>
|
||||
133
multi_level_mrp/wizards/mrp_inventory_procure.py
Normal file
133
multi_level_mrp/wizards/mrp_inventory_procure.py
Normal file
@@ -0,0 +1,133 @@
|
||||
# Copyright 2018 Eficent Business and IT Consulting Services S.L.
|
||||
# (http://www.eficent.com)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
|
||||
class MrpInventoryProcure(models.TransientModel):
|
||||
_name = 'mrp.inventory.procure'
|
||||
_description = 'Make Procurements from MRP inventory projections'
|
||||
|
||||
item_ids = fields.One2many(
|
||||
comodel_name='mrp.inventory.procure.item',
|
||||
inverse_name='wiz_id',
|
||||
string='Items',
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _prepare_item(self, mrp_inventory):
|
||||
return {
|
||||
'qty': mrp_inventory.to_procure,
|
||||
'uom_id': mrp_inventory.uom_id.id,
|
||||
'date_planned': mrp_inventory.date,
|
||||
'mrp_inventory_id': mrp_inventory.id,
|
||||
'product_id': mrp_inventory.mrp_product_id.product_id.id,
|
||||
'warehouse_id': mrp_inventory.mrp_area_id.warehouse_id.id,
|
||||
'location_id': mrp_inventory.mrp_area_id.location_id.id,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def fields_view_get(self, view_id=None, view_type='form', toolbar=False,
|
||||
submenu=False):
|
||||
if self.user_has_groups(
|
||||
"multi_level_mrp.group_change_mrp_procure_qty"):
|
||||
view_id = self.env.ref(
|
||||
'multi_level_mrp.'
|
||||
'view_mrp_inventory_procure_wizard').id
|
||||
else:
|
||||
view_id = self.env.ref(
|
||||
'multi_level_mrp.'
|
||||
'view_mrp_inventory_procure_without_security').id
|
||||
return super(MrpInventoryProcure, self).fields_view_get(
|
||||
view_id=view_id, view_type=view_type, toolbar=toolbar,
|
||||
submenu=submenu)
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
res = super(MrpInventoryProcure, self).default_get(fields)
|
||||
mrp_inventory_obj = self.env['mrp.inventory']
|
||||
mrp_inventory_ids = self.env.context['active_ids'] or []
|
||||
active_model = self.env.context['active_model']
|
||||
if not mrp_inventory_ids or 'item_ids' not in fields:
|
||||
return res
|
||||
|
||||
assert active_model == 'mrp.inventory', 'Bad context propagation'
|
||||
|
||||
items = item_obj = self.env['mrp.inventory.procure.item']
|
||||
for line in mrp_inventory_obj.browse(mrp_inventory_ids):
|
||||
items += item_obj.create(self._prepare_item(line))
|
||||
res['item_ids'] = [(6, 0, items.ids)]
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def make_procurement(self):
|
||||
self.ensure_one()
|
||||
errors = []
|
||||
for item in self.item_ids:
|
||||
if not item.qty:
|
||||
raise ValidationError(_("Quantity must be positive."))
|
||||
values = item._prepare_procurement_values()
|
||||
# Run procurement
|
||||
try:
|
||||
self.env['procurement.group'].run(
|
||||
item.product_id,
|
||||
item.qty,
|
||||
item.uom_id,
|
||||
item.location_id,
|
||||
'INT: ' + str(self.env.user.login), # name?
|
||||
'INT: ' + str(self.env.user.login), # origin?
|
||||
values
|
||||
)
|
||||
except UserError as error:
|
||||
errors.append(error.name)
|
||||
if errors:
|
||||
raise UserError('\n'.join(errors))
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
|
||||
class MrpInventoryProcureItem(models.TransientModel):
|
||||
_name = 'mrp.inventory.procure.item'
|
||||
|
||||
wiz_id = fields.Many2one(
|
||||
comodel_name='mrp.inventory.procure', string='Wizard',
|
||||
ondelete='cascade', readonly=True,
|
||||
)
|
||||
qty = fields.Float(string='Quantity')
|
||||
uom_id = fields.Many2one(
|
||||
string='Unit of Measure',
|
||||
comodel_name='product.uom',
|
||||
)
|
||||
date_planned = fields.Date(string='Planned Date', required=False)
|
||||
mrp_inventory_id = fields.Many2one(
|
||||
string='Mrp Inventory',
|
||||
comodel_name='mrp.inventory',
|
||||
)
|
||||
product_id = fields.Many2one(
|
||||
string='Product',
|
||||
comodel_name='product.product',
|
||||
)
|
||||
warehouse_id = fields.Many2one(
|
||||
string='Warehouse',
|
||||
comodel_name='stock.warehouse',
|
||||
)
|
||||
location_id = fields.Many2one(
|
||||
string='Location',
|
||||
comodel_name='stock.location',
|
||||
)
|
||||
|
||||
def _prepare_procurement_values(self, group=False):
|
||||
return {
|
||||
'date_planned': self.date_planned, # TODO: play with this...
|
||||
'warehouse_id': self.warehouse_id,
|
||||
# 'company_id': self.company_id, # TODO: consider company
|
||||
'group_id': group,
|
||||
}
|
||||
|
||||
@api.multi
|
||||
@api.onchange('uom_id')
|
||||
def onchange_uom_id(self):
|
||||
for rec in self:
|
||||
rec.qty = rec.mrp_inventory_id.uom_id._compute_quantity(
|
||||
rec.mrp_inventory_id.to_procure, rec.uom_id)
|
||||
59
multi_level_mrp/wizards/mrp_inventory_procure_view.xml
Normal file
59
multi_level_mrp/wizards/mrp_inventory_procure_view.xml
Normal file
@@ -0,0 +1,59 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
|
||||
<!-- Make Procurement with security access right -->
|
||||
<record id="view_mrp_inventory_procure_wizard" model="ir.ui.view">
|
||||
<field name="name">mrp.inventory.procure.form</field>
|
||||
<field name="model">mrp.inventory.procure</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Procurement Request">
|
||||
<p class="oe_gray">
|
||||
Use this assistant to procure for this product and date.
|
||||
According to the product configuration,
|
||||
this may trigger a draft purchase order, a manufacturing
|
||||
order or a transfer picking.
|
||||
</p>
|
||||
<group name="items" string="Items">
|
||||
<field name="item_ids" nolabel="1">
|
||||
<tree string="Items" nocreate="1" editable="top">
|
||||
<field name="mrp_inventory_id" invisible="True"/>
|
||||
<field name="warehouse_id" groups="stock.group_stock_multi_locations" readonly="1"/>
|
||||
<field name="location_id" groups="stock.group_stock_multi_locations" readonly="1"/>
|
||||
<field name="product_id" readonly="1"/>
|
||||
<field name="qty"/>
|
||||
<field name="uom_id" groups="product.group_uom"/>
|
||||
<field name="date_planned"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Execute" name="make_procurement" type="object" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-default" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Make Procurement without security access right -->
|
||||
<record id="view_mrp_inventory_procure_without_security" model="ir.ui.view">
|
||||
<field name="name">mrp.inventory.procure.form - readonly qty</field>
|
||||
<field name="model">mrp.inventory.procure</field>
|
||||
<field name="inherit_id" ref="view_mrp_inventory_procure_wizard"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="qty" position="attributes">
|
||||
<attribute name="readonly">1</attribute>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="act_mrp_inventory_procure" model="ir.actions.act_window">
|
||||
<field name="name">Procure</field>
|
||||
<field name="res_model">mrp.inventory.procure</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<field name="binding_model_id"
|
||||
ref="multi_level_mrp.model_mrp_inventory"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -1,5 +1,5 @@
|
||||
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
# © 2016 Eficent Business and IT Consulting Services S.L.
|
||||
# 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).
|
||||
|
||||
@@ -13,6 +13,9 @@ logger = logging.getLogger(__name__)
|
||||
class MultiLevelMrp(models.TransientModel):
|
||||
_name = 'multi.level.mrp'
|
||||
|
||||
# TODO: fix supply_method calculation.
|
||||
# TODO: dates are not being correctly computed for supply...
|
||||
|
||||
@api.model
|
||||
def _prepare_mrp_product_data(self, product, mrp_area):
|
||||
main_supplier_id = False
|
||||
|
||||
Reference in New Issue
Block a user