stock_orderpoint_manual_procurement (#210)

[ADD] stock_orderpoint_procure_uom
This commit is contained in:
Jordi Ballester Alomar
2016-12-22 04:51:59 +01:00
committed by Joan Sisquella
parent 6d41d7a9b9
commit d389b4bdce
14 changed files with 564 additions and 0 deletions

View File

@@ -0,0 +1,67 @@
.. 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
===================================
Stock Orderpoint Manual Procurement
===================================
This module allows users to manually start procurements from the list of
reordering rules, based on the quantity that is recommended to be procured.
Usage
=====
Go to 'Configuration / Reordering Rules' and review the quantity recommended
to be procured. You can now start the procurement for a single or a list of
reordering rules.
The recommended quantity to procure is adjusted to the procurement unit of
measure indicated in the reordering rule.
If you want users to be able to change the recommended quantity to procure,
you should assign them to the security group 'Change quantity in manual
procurements from reordering rules', under 'Settings / Users / Users'.
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/153/8.0
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/stock-logistics-warehouse/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.
Credits
=======
Images
------
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
Contributors
------------
* Jordi Ballester Alomar <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,7 @@
# -*- coding: utf-8 -*-
# Copyright 2016 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 . import models
from . import wizards

View File

@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Copyright 2016 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).
{
"name": "Stock Orderpoint manual procurement",
"summary": "Allows to create procurement orders from orderpoints instead "
"of relying only on the scheduler",
"version": "8.0.1.0.0",
"author": "Eficent Business and IT Consulting Services S.L,"
"Odoo Community Association (OCA)",
"website": "https://www.odoo-community.org",
"category": "Warehouse Management",
"depends": ["stock",
"stock_orderpoint_uom"],
"data": ["security/stock_orderpoint_manual_procurement_security.xml",
"wizards/make_procurement_orderpoint_view.xml",
"views/procurement_order_view.xml",
"views/stock_warehouse_orderpoint_view.xml"
],
"license": "AGPL-3",
'installable': True,
'application': True,
}

View File

@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2016 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 . import stock_warehouse_orderpoint

View File

@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
# Copyright 2016 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 openerp import api, fields, models
from datetime import datetime
from openerp.addons import decimal_precision as dp
from openerp.tools import float_compare, float_round
UNIT = dp.get_precision('Product Unit of Measure')
class StockWarehouseOrderpoint(models.Model):
_inherit = 'stock.warehouse.orderpoint'
@api.multi
@api.depends("product_min_qty", "product_id", "qty_multiple")
def _compute_procure_recommended(self):
procurement_model = self.env['procurement.order']
for op in self:
op.procure_recommended_date = \
procurement_model._get_orderpoint_date_planned(
op, datetime.today())
procure_recommended_qty = 0.0
prods = procurement_model._product_virtual_get(op)
if prods is None:
continue
if float_compare(prods, op.product_min_qty,
precision_rounding=op.product_uom.rounding) < 0:
qty = max(op.product_min_qty, op.product_max_qty) - prods
reste = op.qty_multiple > 0 and qty % op.qty_multiple or 0.0
if float_compare(
reste, 0.0,
precision_rounding=op.product_uom.rounding) > 0:
qty += op.qty_multiple - reste
if float_compare(
qty, 0.0,
precision_rounding=op.product_uom.rounding) <= 0:
continue
qty -= op.subtract_procurements(op)
qty_rounded = float_round(
qty, precision_rounding=op.product_uom.rounding)
if qty_rounded > 0:
procure_recommended_qty = qty_rounded
if op.procure_uom_id:
product_qty = op.procure_uom_id._compute_qty(
op.product_id.uom_id.id, procure_recommended_qty,
op.procure_uom_id.id)
else:
product_qty = procure_recommended_qty
op.procure_recommended_qty = product_qty
procure_recommended_qty = fields.Float(
string='Procure recommendation',
compute="_compute_procure_recommended",
digits=UNIT)
procure_recommended_date = fields.Date(
string='Request Date',
compute="_compute_procure_recommended")

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<record id="group_change_orderpoint_procure_qty" model="res.groups">
<field name="name">Change quantity in manual procurements from reordering rules</field>
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
<field name="category_id" ref="base.module_category_warehouse_management"/>
</record>
</data>
</openerp>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright 2016 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# Copyright 2016 Serpent Consulting Services Pvt. Ltd.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from . import test_stock_orderpoint_manual_procurement

View File

@@ -0,0 +1,133 @@
# -*- coding: utf-8 -*-
# Copyright 2016 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# Copyright 2016 Serpent Consulting Services Pvt. Ltd.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from openerp.tests import common
class TestStockWarehouseOrderpoint(common.TransactionCase):
def setUp(self):
super(TestStockWarehouseOrderpoint, self).setUp()
# Refs
self.group_stock_manager = self.env.ref('stock.group_stock_manager')
self.group_change_procure_qty = self.env.ref(
'stock_orderpoint_manual_procurement.'
'group_change_orderpoint_procure_qty')
self.company1 = self.env.ref('base.main_company')
# Get required Model
self.reordering_rule_model = self.env['stock.warehouse.orderpoint']
self.product_model = self.env['product.product']
self.user_model = self.env['res.users']
self.product_ctg_model = self.env['product.category']
self.stock_change_model = self.env['stock.change.product.qty']
self.make_procurement_orderpoint_model =\
self.env['make.procurement.orderpoint']
# Create users
self.user = self._create_user('user_1',
[self.group_stock_manager,
self.group_change_procure_qty],
self.company1)
# Get required Model data
self.product_uom = self.env.ref('product.product_uom_unit')
self.location = self.env.ref('stock.stock_location_stock')
self.product = self.env.ref('product.product_product_7')
# Create Product category and Product
self.product_ctg = self._create_product_category()
self.product = self._create_product()
# Add default quantity
quantity = 20.00
self._update_product_qty(self.product, self.location, quantity)
# Create Reordering Rule
self.reorder = self.create_orderpoint()
def _create_user(self, login, groups, company):
""" Create a user."""
group_ids = [group.id for group in groups]
user = \
self.user_model.with_context({'no_reset_password': True}).create({
'name': 'Test User',
'login': login,
'password': 'demo',
'email': 'test@yourcompany.com',
'company_id': company.id,
'company_ids': [(4, company.id)],
'groups_id': [(6, 0, group_ids)]
})
return user
def _create_product_category(self):
"""Create a Product Category."""
product_ctg = self.product_ctg_model.create({
'name': 'test_product_ctg',
'type': 'normal',
})
return product_ctg
def _create_product(self):
"""Create a Product."""
product = self.product_model.create({
'name': 'Test Product',
'categ_id': self.product_ctg.id,
'type': 'product',
'uom_id': self.product_uom.id,
})
return product
def _update_product_qty(self, product, location, quantity):
"""Update Product quantity."""
change_product_qty = self.stock_change_model.create({
'location_id': location.id,
'product_id': product.id,
'new_quantity': quantity,
})
change_product_qty.change_product_qty()
return change_product_qty
def create_orderpoint(self):
"""Create a Reordering Rule"""
reorder = self.reordering_rule_model.sudo(self.user).create({
'name': 'Order-point',
'product_id': self.product.id,
'product_min_qty': '100',
'product_max_qty': '500',
'qty_multiple': '1'
})
return reorder
def create_orderpoint_procurement(self):
"""Make Procurement from Reordering Rule"""
context = {
'active_model': 'stock.warehouse.orderpoint',
'active_ids': self.reorder.ids,
'active_id': self.reorder.id
}
wizard = self.make_procurement_orderpoint_model.sudo(self.user).\
with_context(context).create({})
wizard.make_procurement()
return wizard
def test_security(self):
"""Test Manual Procurement created from Order-Point"""
# Create Manual Procurement from order-point procured quantity
self.create_orderpoint_procurement()
# Assert that Procurement is created with the desired quantity
self.assertTrue(self.reorder.procurement_ids)
self.assertEqual(self.reorder.product_id.id,
self.reorder.procurement_ids.product_id.id)
self.assertEqual(self.reorder.name,
self.reorder.procurement_ids.origin)
self.assertNotEqual(self.reorder.procure_recommended_qty,
self.reorder.procurement_ids.product_qty)
self.assertEqual(self.reorder.procurement_ids.product_qty,
480.0)

View File

@@ -0,0 +1,19 @@
<?xml version="1.0"?>
<openerp>
<data>
<record id="view_procurement_filter" model="ir.ui.view">
<field name="name">procurement.order.select</field>
<field name="model">procurement.order</field>
<field name="inherit_id"
ref="procurement.view_procurement_filter"/>
<field name="arch" type="xml">
<field name="origin" position="after">
<field name="orderpoint_id"/>
</field>
</field>
</record>
</data>
</openerp>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0"?>
<openerp>
<data>
<record id="view_warehouse_orderpoint_tree" model="ir.ui.view">
<field name="name">stock.warehouse.orderpoint.tree</field>
<field name="model">stock.warehouse.orderpoint</field>
<field name="inherit_id"
ref="stock.view_warehouse_orderpoint_tree"/>
<field name="arch" type="xml">
<field name="product_uom" position="after">
<field name="procurement_ids" invisible="1"/>
<field name="procure_recommended_qty"/>
<field name="procure_recommended_date"/>
<button string="Create Procurement"
name="%(stock_orderpoint_manual_procurement.act_make_procurement_from_orderpoint)d"
icon="gtk-execute" type="action"/>
<button string="Procurements"
name="%(procurement.procurement_action)d"
attrs="{'invisible':[('procurement_ids', '=', False)]}"
icon="gtk-open" type="action"
domain="[('orderpoint_id','=', active_id)]"
context="{'search_default_orderpoint_id': [active_id]}"/>
</field>
</field>
</record>
</data>
</openerp>

View File

@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2016 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 . import make_procurement_orderpoint

View File

@@ -0,0 +1,126 @@
# -*- coding: utf-8 -*-
# Copyright 2016 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 openerp import api, fields, models
class MakeProcurementOrderpoint(models.TransientModel):
_name = 'make.procurement.orderpoint'
_description = 'Make Procurements from Orderpoints'
item_ids = fields.One2many(
'make.procurement.orderpoint.item',
'wiz_id', string='Items')
@api.model
def _prepare_item(self, orderpoint):
if orderpoint.procure_uom_id:
product_uom = orderpoint.procure_uom_id
else:
product_uom = orderpoint.product_uom
return {
'qty': orderpoint.procure_recommended_qty,
'uom_id': product_uom.id,
'date_planned': orderpoint.procure_recommended_date,
'orderpoint_id': orderpoint.id,
'product_id': orderpoint.product_id.id,
'warehouse_id': orderpoint.warehouse_id.id,
'location_id': orderpoint.location_id.id
}
@api.model
def default_get(self, fields):
res = super(MakeProcurementOrderpoint, self).default_get(
fields)
orderpoint_obj = self.env['stock.warehouse.orderpoint']
orderpoint_ids = self.env.context['active_ids'] or []
active_model = self.env.context['active_model']
if not orderpoint_ids:
return res
assert active_model == 'stock.warehouse.orderpoint', \
'Bad context propagation'
items = []
for line in orderpoint_obj.browse(orderpoint_ids):
items.append([0, 0, self._prepare_item(line)])
res['item_ids'] = items
return res
@api.multi
def make_procurement(self):
self.ensure_one()
res = []
for item in self.item_ids:
data = item._prepare_procurement()
procurement = self.env['procurement.order'].create(data)
procurement.signal_workflow('button_confirm')
procurement.run()
res.append(procurement.id)
return {
'domain': "[('id','in', ["+','.join(map(str, res))+"])]",
'view_type': 'form',
'view_mode': 'tree,form',
'res_model': 'procurement.order',
'type': 'ir.actions.act_window',
}
class MakeProcurementOrderpointItem(models.TransientModel):
_name = 'make.procurement.orderpoint.item'
_description = 'Make Procurements from Orderpoint Item'
wiz_id = fields.Many2one(
'make.procurement.orderpoint', string='Wizard', required=True,
ondelete='cascade', readonly=True)
qty = fields.Float(string='Quantity', required=True)
uom_id = fields.Many2one(string='Unit of Measure',
comodel_name='product.uom', required=True)
date_planned = fields.Date(string='Planned Date', required=True)
orderpoint_id = fields.Many2one(string='Reordering rule',
comodel_name='stock.warehouse.orderpoint',
required=True, readonly=True)
product_id = fields.Many2one(string='Product',
comodel_name='product.product',
required=True, readonly=True)
warehouse_id = fields.Many2one(string='Warehouse',
comodel_name='stock.warehouse',
readonly=True)
location_id = fields.Many2one(string='Location',
comodel_name='stock.location',
readonly=True)
@api.multi
def _prepare_procurement(self):
return {
'name': self.orderpoint_id.name,
'date_planned': self.date_planned,
'product_id': self.product_id.id,
'product_qty': self.qty,
'product_uom': self.uom_id.id,
'warehouse_id': self.warehouse_id.id,
'location_id': self.location_id.id,
'company_id': self.orderpoint_id.company_id.id,
'orderpoint_id': self.orderpoint_id.id,
'origin': self.orderpoint_id.name,
'group_id': self.orderpoint_id.group_id.id,
}
@api.multi
@api.onchange('uom_id')
def onchange_uom_id(self):
for rec in self:
uom = rec.orderpoint_id.procure_uom_id or \
rec.orderpoint_id.product_uom
rec.qty = rec.uom_id._compute_qty(
uom.id,
rec.orderpoint_id.procure_recommended_qty,
rec.uom_id.id)

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Make Procurement -->
<record id="view_make_procurment_buffer_wizard" model="ir.ui.view">
<field name="name">Request Procurement</field>
<field name="model">make.procurement.orderpoint</field>
<field name="arch" type="xml">
<form string="Procurement Request">
<p class="oe_gray">
Use this assistant to generate a procurement request for this
stock buffer. 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="orderpoint_id" invisible="True"/>
<field name="warehouse_id" groups="stock.group_locations"/>
<field name="location_id" groups="stock.group_locations"/>
<field name="product_id"/>
<field name="qty" groups="stock_orderpoint_manual_procurement.group_change_orderpoint_procure_qty"/>
<field name="uom_id" groups="product.group_uom"/>
<field name="date_planned"/>
</tree>
</field>
</group>
<footer>
<button name="make_procurement" string="Execute"
type="object" class="oe_highlight" />
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record model="ir.actions.act_window"
id="act_make_procurement_from_orderpoint">
<field name="name">Request Procurement</field>
<field name="res_model">make.procurement.orderpoint</field>
<field name="src_model">stock.warehouse.orderpoint</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<record model="ir.values"
id="stock_warehouse_orderpoint_make_procurement">
<field name="model_id"
ref="stock.model_stock_warehouse_orderpoint" />
<field name="name">Request Procurement</field>
<field name="key2">client_action_multi</field>
<field name="value" eval="'ir.actions.act_window,' + str(ref('act_make_procurement_from_orderpoint'))" />
<field name="key">action</field>
<field name="model">stock.warehouse.orderpoint</field>
</record>
</data>
</openerp>