From cb186fe1790fdb322ca14e3aa1f144e9bf78df44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lionel=20Sausin=20=28Num=C3=A9rigraphe=29?= Date: Fri, 27 Feb 2015 13:43:56 +0100 Subject: [PATCH] [ADD] stock_available Generic module to compute the stock quantity available to promise using several implementations. stock_available_immediatly is changed to become the first optional implementation. Cherry pick of commit 0b060f619fa5d60f9fb343afe1154acd5c730148 from the v7 branch --- stock_available/__init__.py | 24 ++++ stock_available/__openerp__.py | 42 ++++++ stock_available/i18n/fr.po | 98 ++++++++++++++ stock_available/i18n/stock_available.pot | 92 +++++++++++++ stock_available/product.py | 100 ++++++++++++++ .../product_view.xml | 34 +++-- stock_available/res_config.py | 34 +++++ stock_available/res_config_view.xml | 25 ++++ stock_available_immediately/__openerp__.py | 30 +++-- stock_available_immediately/product.py | 123 +++++------------- 10 files changed, 478 insertions(+), 124 deletions(-) create mode 100644 stock_available/__init__.py create mode 100644 stock_available/__openerp__.py create mode 100644 stock_available/i18n/fr.po create mode 100644 stock_available/i18n/stock_available.pot create mode 100644 stock_available/product.py rename {stock_available_immediately => stock_available}/product_view.xml (54%) create mode 100644 stock_available/res_config.py create mode 100644 stock_available/res_config_view.xml diff --git a/stock_available/__init__.py b/stock_available/__init__.py new file mode 100644 index 000000000..6755b49d2 --- /dev/null +++ b/stock_available/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from . import product +from . import res_config + +from .product import _product_available_fnct diff --git a/stock_available/__openerp__.py b/stock_available/__openerp__.py new file mode 100644 index 000000000..16ea5f8ab --- /dev/null +++ b/stock_available/__openerp__.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +{ + 'name': 'Stock available to promise', + 'version': '2.0', + 'author': u'Numérigraphe', + 'category': 'Warehouse', + 'depends': ['stock'], + 'description': """ +Stock available to promise +========================== +This module proposes several options to compute the quantity available to +promise for each product. +This quantity is based on the projected stock and, depending on the +configuration, it can account for various data such as sales quotations or +immediate production capacity. +This can be configured in the menu Settings > Configuration > Warehouse. +""", + 'license': 'AGPL-3', + 'data': [ + 'product_view.xml', + 'res_config_view.xml', + ] +} diff --git a/stock_available/i18n/fr.po b/stock_available/i18n/fr.po new file mode 100644 index 000000000..6c8e4c244 --- /dev/null +++ b/stock_available/i18n/fr.po @@ -0,0 +1,98 @@ +# Translation of OpenERP Server. +# This file contains the translation of the following modules: +# * stock_available +# +msgid "" +msgstr "" +"Project-Id-Version: OpenERP Server 7.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-07-30 16:42+0000\n" +"PO-Revision-Date: 2014-07-30 16:42+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: stock_available +#: field:product.product,immediately_usable_qty:0 +msgid "Available to promise" +msgstr "Disponible à la vente" + +#. module: stock_available +#: view:product.product:0 +msgid "Available to promise:" +msgstr "Disponible à la vente:" + +#. module: stock_available +#: field:stock.config.settings,module_stock_available_sale:0 +msgid "Exclude goods already in sale quotations" +msgstr "Exclure les marchandises qui sont déjà dans les devis" + +#. module: stock_available +#: field:stock.config.settings,module_stock_available_immediately:0 +msgid "Exclude incoming goods" +msgstr "Exclure les receptions attendues" + +#. module: stock_available +#: field:stock.config.settings,module_stock_available_mrp:0 +msgid "Include the production potential" +msgstr "Inclure la production potentielle" + +#. module: stock_available +#: code:_description:0 +#: model:ir.model,name:stock_available.model_product_product +#, python-format +msgid "Product" +msgstr "Article" + +#. module: stock_available +#: view:stock.config.settings:0 +msgid "Stock available to promise" +msgstr "Stock disponible à la vente" + +#. module: stock_available +#: help:product.product,immediately_usable_qty:0 +msgid "Stock for this Product that can be safely proposed for sale to Customers.\n" +"The definition of this value can be configured to suit your needs" +msgstr "Stock de cet article qui peut sans risque être proposé à la vente aux clients.\n" +"La définition de cette valeur est paramétrable selon vos besoins" + +#. module: stock_available +#: help:stock.config.settings,module_stock_available_mrp:0 +msgid "This will add the quantities of goods that can be immediately manufactured, to the quantities available to promise.\n" +"This installs the module stock_available_mrp.\n" +"If the module mrp is not installed, this will install it too" +msgstr "Ceci ajoute les quantités de marchandises qui peuvent être immédiatement fabriquées, aux quantitiés disponibles à la vente.\n" +"Ceci installe le module stock_available_mrp.\n" +"Si le module mrp n'est pas encore installé, il le sera aussi" + +#. module: stock_available +#: help:stock.config.settings,module_stock_available_immediately:0 +msgid "This will subtract incoming quantities from the quantities available to promise.\n" +"This installs the module stock_available_immediately." +msgstr "Ceci soustrait les réceptions attendues des quantitiés disponibles à la vente.\n" +"Ceci installe le module stock_available_immediately." + +#. module: stock_available +#: help:stock.config.settings,module_stock_available_sale:0 +msgid "This will subtract quantities from the sale quotations from the quantities available to promise.\n" +"This installs the modules stock_available_sale.\n" +"If the modules sale and sale_delivery_date are not installed, this will install them too" +msgstr "Ceci soustrait les quantités des devis de vente des quantitiés disponibles à la vente.\n" +"Ceci installe le modules stock_available_sale.\n" +"Si les modules sale et sale_delivery_date ne sont pas encore installés, ils le seront également" + +#. module: stock_available +#: view:product.product:0 +msgid "red:immediately_usable_qty<0;blue:immediately_usable_qty>=0 and state in ('draft', 'end', 'obsolete');black:immediately_usable_qty>=0 and state not in ('draft', 'end', 'obsolete')" +msgstr "red:immediately_usable_qty<0;blue:immediately_usable_qty>=0 and state in ('draft', 'end', 'obsolete');black:immediately_usable_qty>=0 and state not in ('draft', 'end', 'obsolete')" + +#. module: stock_available +#: code:_description:0 +#: model:ir.model,name:stock_available.model_stock_config_settings +#, python-format +msgid "stock.config.settings" +msgstr "stock.config.settings" + diff --git a/stock_available/i18n/stock_available.pot b/stock_available/i18n/stock_available.pot new file mode 100644 index 000000000..6eed1c952 --- /dev/null +++ b/stock_available/i18n/stock_available.pot @@ -0,0 +1,92 @@ +# Translation of OpenERP Server. +# This file contains the translation of the following modules: +# * stock_available +# +msgid "" +msgstr "" +"Project-Id-Version: OpenERP Server 7.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-07-30 16:48+0000\n" +"PO-Revision-Date: 2014-07-30 16:48+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: stock_available +#: field:product.product,immediately_usable_qty:0 +msgid "Available to promise" +msgstr "" + +#. module: stock_available +#: view:product.product:0 +msgid "Available to promise:" +msgstr "" + +#. module: stock_available +#: field:stock.config.settings,module_stock_available_sale:0 +msgid "Exclude goods already in sale quotations" +msgstr "" + +#. module: stock_available +#: field:stock.config.settings,module_stock_available_immediately:0 +msgid "Exclude incoming goods" +msgstr "" + +#. module: stock_available +#: field:stock.config.settings,module_stock_available_mrp:0 +msgid "Include the production potential" +msgstr "" + +#. module: stock_available +#: code:_description:0 +#: model:ir.model,name:stock_available.model_product_product +#, python-format +msgid "Product" +msgstr "" + +#. module: stock_available +#: view:stock.config.settings:0 +msgid "Stock available to promise" +msgstr "" + +#. module: stock_available +#: help:product.product,immediately_usable_qty:0 +msgid "Stock for this Product that can be safely proposed for sale to Customers.\n" +"The definition of this value can be configured to suit your needs" +msgstr "" + +#. module: stock_available +#: help:stock.config.settings,module_stock_available_mrp:0 +msgid "This will add the quantities of goods that can be immediately manufactured, to the quantities available to promise.\n" +"This installs the module stock_available_mrp.\n" +"If the module mrp is not installed, this will install it too" +msgstr "" + +#. module: stock_available +#: help:stock.config.settings,module_stock_available_immediately:0 +msgid "This will subtract incoming quantities from the quantities available to promise.\n" +"This installs the module stock_available_immediately." +msgstr "" + +#. module: stock_available +#: help:stock.config.settings,module_stock_available_sale:0 +msgid "This will subtract quantities from the sale quotations from the quantities available to promise.\n" +"This installs the modules stock_available_sale.\n" +"If the modules sale and sale_delivery_date are not installed, this will install them too" +msgstr "" + +#. module: stock_available +#: view:product.product:0 +msgid "red:immediately_usable_qty<0;blue:immediately_usable_qty>=0 and state in ('draft', 'end', 'obsolete');black:immediately_usable_qty>=0 and state not in ('draft', 'end', 'obsolete')" +msgstr "" + +#. module: stock_available +#: code:_description:0 +#: model:ir.model,name:stock_available.model_stock_config_settings +#, python-format +msgid "stock.config.settings" +msgstr "" + diff --git a/stock_available/product.py b/stock_available/product.py new file mode 100644 index 000000000..a4a528eb7 --- /dev/null +++ b/stock_available/product.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp.osv import orm, fields +import openerp.addons.decimal_precision as dp + + +# Expose the method as a function, like when the fields are defined, +# and use the pool to call the method from the other modules too. +def _product_available_fnct(self, cr, uid, ids, field_names=None, arg=False, + context=None): + return self.pool['product.product']._product_available( + cr, uid, ids, field_names=field_names, arg=arg, context=context) + + +class ProductProduct(orm.Model): + """Add a field for the stock available to promise. + + Useful implementations need to be installed through the Settings menu or by + installing one of the modules stock_available_* + """ + _inherit = 'product.product' + + def __init__(self, pool, cr): + """Use _product_available_fnct to compute all the quantities.""" + # Doing this lets us change the function and not redefine fields + super(ProductProduct, self).__init__(pool, cr) + for coldef in self._columns.values(): + if (isinstance(coldef, fields.function) + and coldef._multi == 'qty_available'): + coldef._fnct = _product_available_fnct + + def _product_available(self, cr, uid, ids, field_names=None, arg=False, + context=None): + """No-op implementation of the stock available to promise. + + Must be overridden by another module that actually implement + computations. + The sub-modules MUST call super()._product_available BEFORE their own + computations + + Side-effect warning: This method may change the list passed as the + field_names parameter, which will then alter the caller's state.""" + # If we didn't get a field_names list, there's nothing to do + if field_names is None: + return super(ProductProduct, self)._product_available( + cr, uid, ids, field_names=field_names, arg=arg, + context=context) + + if context is None: + context = {} + + # Load virtual_available if it's not already asked for + # We need it to compute immediately_usable_qty + # We DO want to change the caller's list so we're NOT going to + # work on a copy of field_names. + if ('virtual_available' not in field_names + and 'immediately_usable_qty' in field_names): + field_names.append('virtual_available') + + # Compute the core quantities + res = super(ProductProduct, self)._product_available( + cr, uid, ids, field_names=field_names, arg=arg, context=context) + + # By default, available to promise = forecasted quantity + if ('immediately_usable_qty' in field_names): + for stock_qty in res.itervalues(): + stock_qty['immediately_usable_qty'] = \ + stock_qty['virtual_available'] + + return res + + _columns = { + 'immediately_usable_qty': fields.function( + _product_available_fnct, multi='qty_available', + type='float', + digits_compute=dp.get_precision('Product Unit of Measure'), + string='Available to promise', + help="Stock for this Product that can be safely proposed " + "for sale to Customers.\n" + "The definition of this value can be configured to suit " + "your needs"), + } diff --git a/stock_available_immediately/product_view.xml b/stock_available/product_view.xml similarity index 54% rename from stock_available_immediately/product_view.xml rename to stock_available/product_view.xml index bdd7873f9..d05270f75 100644 --- a/stock_available_immediately/product_view.xml +++ b/stock_available/product_view.xml @@ -1,32 +1,20 @@ - - - - - product.normal.stock.active.qty.form.inherit + + Quantity available to promise (form) product.template - - - - - - - product_immediately_usable.product_product_tree_view + + + Quantity available to promise (tree) product.template @@ -34,12 +22,22 @@ red:immediately_usable_qty<0;blue:immediately_usable_qty>=0 and state in ('draft', 'end', 'obsolete');black:immediately_usable_qty>=0 and state not in ('draft', 'end', 'obsolete') - + + + Quantity available to promise (kanban) + product.template + + + +
  • Available to promise:
  • +
    +
    +
    diff --git a/stock_available/res_config.py b/stock_available/res_config.py new file mode 100644 index 000000000..535f58460 --- /dev/null +++ b/stock_available/res_config.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp.osv import orm, fields + + +class StockConfig(orm.TransientModel): + """Add options to easily install the submodules""" + _inherit = 'stock.config.settings' + + _columns = { + 'module_stock_available_immediately': fields.boolean( + 'Exclude incoming goods', + help="This will subtract incoming quantities from the quantities " + "available to promise.\n" + "This installs the module stock_available_immediately."), + } diff --git a/stock_available/res_config_view.xml b/stock_available/res_config_view.xml new file mode 100644 index 000000000..15e45c031 --- /dev/null +++ b/stock_available/res_config_view.xml @@ -0,0 +1,25 @@ + + + + + Stock settings: quantity available to promise + stock.config.settings + + + + + + + + + + + + \ No newline at end of file diff --git a/stock_available_immediately/__openerp__.py b/stock_available_immediately/__openerp__.py index 1cedaa84f..838f9db4c 100644 --- a/stock_available_immediately/__openerp__.py +++ b/stock_available_immediately/__openerp__.py @@ -20,21 +20,27 @@ # # - { - "name": "Immediately Usable Stock Quantity", - "version": "1.0", - "depends": ["product", "stock", ], + "name": "Ignore planned receptions in quantity available to promise", + "version": "2.0", + "depends": ["stock_available"], "author": "Camptocamp", "license": "AGPL-3", - "description": """ -Compute the immediately usable stock. -Immediately usable is computed : Quantity on Hand - Outgoing Stock. + "description": u""" +Ignore planned receptions in quantity available to promise +---------------------------------------------------------- + +Normally the quantity available to promise is based on the virtual stock, +which includes both planned outgoing and incoming goods. +This module will subtract the planned receptions from the quantity available to +promise. + +Contributors +------------ + * Author: Guewen Baconnier (Camptocamp SA) + * Sébastien BEAU (Akretion) + * Lionel Sausin (Numérigraphe) """, - "website": "http://tinyerp.com/module_account.html", - "category": "Generic Modules/Stock", - "data": ["product_view.xml", - ], - "active": False, + "category": "Hidden", 'installable': True } diff --git a/stock_available_immediately/product.py b/stock_available_immediately/product.py index 8e6a7dc95..939bbbb9d 100644 --- a/stock_available_immediately/product.py +++ b/stock_available_immediately/product.py @@ -19,106 +19,41 @@ # ############################################################################## -from openerp.addons import decimal_precision as dp +from openerp.osv import orm + from openerp.osv import orm, fields +class product_immediately_usable(orm.Model): + """Subtract incoming qty from immediately_usable_qty -class ProductTemplate(orm.Model): - """ - Immediately usable quantity is : real stock - outgoing qty - """ - _inherit = 'product.template' + We don't need to override the function fields, the module stock_available + takes of it for us. + + Side-effect warning: This method may change the list passed as the + field_names parameter, which will then alter the caller's state.""" + _inherit = 'product.product' def _product_available(self, cr, uid, ids, field_names=None, arg=False, context=None): - res = super(ProductTemplate, self)._product_available( - cr, uid, ids, field_names, arg, context) + """Ignore the incoming goods in the quantity available to promise""" + # If we didn't get a field_names list, there's nothing to do + if field_names is None or 'immediately_usable_qty' not in field_names: + return super(product_immediately_usable, self)._product_available( + cr, uid, ids, field_names=field_names, arg=arg, + context=context) - if 'immediately_usable_qty' in field_names: - for product_id, stock_qty in res.iteritems(): - res[product_id]['immediately_usable_qty'] = \ - stock_qty['qty_available'] - stock_qty['outgoing_qty'] + # We need available and incoming quantities to compute + # immediately usable quantity. + # We DO want to change the caller's list so we're NOT going to + # work on a copy of field_names. + field_names.append('qty_available') + field_names.append('incoming_qty') + + res = super(product_immediately_usable, self)._product_available( + cr, uid, ids, field_names=field_names, arg=arg, context=context) + + for stock_qty in res.itervalues(): + stock_qty['immediately_usable_qty'] -= \ + stock_qty['incoming_qty'] return res - - _columns = { - 'qty_available': fields.function( - _product_available, - multi='qty_available', - type='float', - digits_compute=dp.get_precision('Product UoM'), - string='Quantity On Hand', - help="Current quantity of products.\n" - "In a context with a single Stock Location, this includes " - "goods stored at this Location, or any of its children.\n" - "In a context with a single Warehouse, this includes " - "goods stored in the Stock Location of this Warehouse, " - "or any " - "of its children.\n" - "In a context with a single Shop, this includes goods " - "stored in the Stock Location of the Warehouse of this Shop, " - "or any of its children.\n" - "Otherwise, this includes goods stored in any Stock Location " - "typed as 'internal'."), - 'virtual_available': fields.function( - _product_available, - multi='qty_available', - type='float', - digits_compute=dp.get_precision('Product UoM'), - string='Quantity Available', - help="Forecast quantity (computed as Quantity On Hand " - "- Outgoing + Incoming)\n" - "In a context with a single Stock Location, this includes " - "goods stored at this Location, or any of its children.\n" - "In a context with a single Warehouse, this includes " - "goods stored in the Stock Location of this Warehouse, " - "or any " - "of its children.\n" - "In a context with a single Shop, this includes goods " - "stored in the Stock Location of the Warehouse of this Shop, " - "or any of its children.\n" - "Otherwise, this includes goods stored in any Stock Location " - "typed as 'internal'."), - 'incoming_qty': fields.function( - _product_available, - multi='qty_available', - type='float', - digits_compute=dp.get_precision('Product UoM'), - string='Incoming', - help="Quantity of products that are planned to arrive.\n" - "In a context with a single Stock Location, this includes " - "goods arriving to this Location, or any of its children.\n" - "In a context with a single Warehouse, this includes " - "goods arriving to the Stock Location of this Warehouse, or " - "any of its children.\n" - "In a context with a single Shop, this includes goods " - "arriving to the Stock Location of the Warehouse of this " - "Shop, or any of its children.\n" - "Otherwise, this includes goods arriving to any Stock " - "Location typed as 'internal'."), - 'outgoing_qty': fields.function( - _product_available, - multi='qty_available', - type='float', - digits_compute=dp.get_precision('Product UoM'), - string='Outgoing', - help="Quantity of products that are planned to leave.\n" - "In a context with a single Stock Location, this includes " - "goods leaving from this Location, or any of its children.\n" - "In a context with a single Warehouse, this includes " - "goods leaving from the Stock Location of this Warehouse, or " - "any of its children.\n" - "In a context with a single Shop, this includes goods " - "leaving from the Stock Location of the Warehouse of this " - "Shop, or any of its children.\n" - "Otherwise, this includes goods leaving from any Stock " - "Location typed as 'internal'."), - 'immediately_usable_qty': fields.function( - _product_available, - digits_compute=dp.get_precision('Product UoM'), - type='float', - string='Immediately Usable', - multi='qty_available', - help="Quantity of products really available for sale." - "Computed as: Quantity On Hand - Outgoing."), - }