[11.0][MIG] multi_level_mrp: rework procurement wizard

This commit is contained in:
Lois Rilo
2018-06-08 10:37:04 +02:00
committed by Jordi Ballester Alomar
parent 4ae6af9d9f
commit 0456d9b7e4
12 changed files with 252 additions and 133 deletions

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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