← Back to team overview

openerp-community-reviewer team mailing list archive

[Merge] lp:~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available into lp:stock-logistic-warehouse

 

Lionel Sausin - Numérigraphe has proposed merging lp:~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available into lp:stock-logistic-warehouse.

Requested reviews:
  Stock and Logistic Core Editors (stock-logistic-core-editors)

For more details, see:
https://code.launchpad.net/~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available/+merge/220758

Add a generic module to compute the stock quantity available to promise using several implementations and make stock_available_immediately the first configurable implementation.

stock_available_immediately is rewritten to compute virtual - incoming instead of real - outgoing, which should be mostly the same except for rounding .
The field name "immediately_usable_qty" is unchanged for compatibility, but the field is now called "Available to promise" in the views and help texts (this wording seems more widespread).

By default function fields are not very modular (you need to redefine the whole field to override the method). stock_available takes care of this by making the function fields call the pool instead, so that stock_available_immediately and other future implementations need only override the function _product_available.

Module Co-authored by Loïc Bellier and your humble servant.
-- 
https://code.launchpad.net/~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available/+merge/220758
Your team Stock and Logistic Core Editors is requested to review the proposed merge of lp:~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-available into lp:stock-logistic-warehouse.
=== added directory 'stock_available'
=== added file 'stock_available/__init__.py'
--- stock_available/__init__.py	1970-01-01 00:00:00 +0000
+++ stock_available/__init__.py	2014-05-23 07:51:43 +0000
@@ -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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from . import product
+from . import res_config
+
+from .product import _product_available_fnct

=== added file 'stock_available/__openerp__.py'
--- stock_available/__openerp__.py	1970-01-01 00:00:00 +0000
+++ stock_available/__openerp__.py	2014-05-23 07:51:43 +0000
@@ -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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+{
+    'name': 'Stock available to promise',
+    'version': '2.0',
+    'author': u'Numérigraphe',
+    'category': 'Stock',
+    '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',
+    ]
+}

=== added file 'stock_available/product.py'
--- stock_available/product.py	1970-01-01 00:00:00 +0000
+++ stock_available/product.py	2014-05-23 07:51:43 +0000
@@ -0,0 +1,125 @@
+# -*- 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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+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):
+        """Dummy field for 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 with
+                context['virtual_is_immediately_usable_qty']=False
+            AND call _update_virtual_available() AFTER their own computations
+                with the context from the caller.
+
+        @param context: see _update_virtual_available()"""
+        if context is None:
+            context = {}
+        if field_names is None:
+            field_names = []
+        else:
+            # We don't want to change the caller's list
+            field_names = list(field_names)
+
+        # Load virtual_available if it's not already asked for
+        # We need it to compute immediately_usable_qty
+        if ('virtual_available' not in field_names
+                and 'immediately_usable_qty' in field_names):
+            field_names.append('virtual_available')
+        if context.get('virtual_is_immediately_usable', False):
+            # _update_virtual_available will get/set these fields
+            if 'virtual_available' not in field_names:
+                field_names.append('virtual_available')
+            if 'immediately_usable_qty' not in field_names:
+                field_names.append('immediately_usable_qty')
+
+        # 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 self.pool['product.product']._update_virtual_available(
+            cr, uid, res, context=context)
+
+    def _update_virtual_available(self, cr, uid, res, context=None):
+        """Copy immediately_usable_qty to virtual_available if context asks
+
+        @param context: If the key virtual_is_immediately_usable is True,
+                        then the virtual stock is computed as the stock
+                        available to promise. This lets existing code base
+                        their computations on the new value with a minimum of
+                        change (i.e.: warn salesmen when the stock available
+                        for sale is insufficient to honor a quotation)"""
+        if (context is None
+                or not context.get('virtual_is_immediately_usable', False)):
+            return res
+        for stock_qty in res.itervalues():
+            # _product_available makes sure both fields are loaded
+            # We're changing the caller's state but it's not be a problem
+            stock_qty['virtual_available'] = \
+                stock_qty['immediately_usable_qty']
+        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"),
+    }

=== renamed file 'stock_available_immediately/product_view.xml' => 'stock_available/product_view.xml'
--- stock_available_immediately/product_view.xml	2014-01-24 17:11:21 +0000
+++ stock_available/product_view.xml	2014-05-23 07:51:43 +0000
@@ -1,27 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  stock available_immediately for OpenERP
-  Author Guewen Baconnier. Copyright Camptocamp SA
-  Copyright (C) 2011 Akretion Sébastien BEAU <sebastien.beau@xxxxxxxxxxxx>
-  The licence is in the file __openerp__.py
--->
-
 <openerp>
     <data>
-        <record model="ir.ui.view" id="view_normal_stock_active_qty_form">
-            <field name="name">product.normal.stock.active.qty.form.inherit</field>
+        <record model="ir.ui.view" id="view_stock_available_form">
+            <field name="name">Stock available to promise (form)</field>
             <field name="model">product.product</field>
             <field name="inherit_id" ref="stock.view_normal_procurement_locations_form"/>
             <field name="arch" type="xml">
                 <field name="virtual_available" position="after">
                     <newline/>
                     <field name="immediately_usable_qty" />
-                </field>   
+                </field>
             </field>
         </record>
         
-        <record model="ir.ui.view" id="product_product_tree_view">
+        <record model="ir.ui.view" id="view_stock_available_tree">
             <field name="name">product_immediately_usable.product_product_tree_view</field>
             <field name="model">product.product</field>
             <field name="inherit_id" ref="product.product_product_tree_view"/>
@@ -30,12 +22,22 @@
                     <tree position="attributes">
                         <attribute name="colors">red:immediately_usable_qty&lt;0;blue:immediately_usable_qty&gt;=0 and state in ('draft', 'end', 'obsolete');black:immediately_usable_qty&gt;=0 and state not in ('draft', 'end', 'obsolete')</attribute>
                     </tree>
-                    <field name="virtual_available" position="replace">
+                    <field name="virtual_available" position="after">
                         <field name="immediately_usable_qty" />
                     </field>
                 </data>
             </field>
         </record>
 
+        <record model="ir.ui.view" id="view_stock_available_kanban">
+            <field name="name">Product Kanban Stock</field>
+            <field name="model">product.product</field>
+            <field name="inherit_id" ref="stock.product_kanban_stock_view"/>
+            <field name="arch" type="xml">
+                <xpath expr="//field[@name='virtual_available']/.." position="replace">
+                    <li t-if="record.type.raw_value != 'service'">Available to promise: <field name="immediately_usable_qty"/> <field name="uom_id"/></li>
+                </xpath>
+            </field>
+        </record>
     </data>
 </openerp>

=== added file 'stock_available/res_config.py'
--- stock_available/res_config.py	1970-01-01 00:00:00 +0000
+++ stock_available/res_config.py	2014-05-23 07:51:43 +0000
@@ -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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+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."),
+    }

=== added file 'stock_available/res_config_view.xml'
--- stock_available/res_config_view.xml	1970-01-01 00:00:00 +0000
+++ stock_available/res_config_view.xml	2014-05-23 07:51:43 +0000
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <record id="view_stock_configuration" model="ir.ui.view">
+            <field name="name">Stock settings: quantity available to promise</field>
+            <field name="model">stock.config.settings</field>
+            <field name="inherit_id" ref="stock.view_stock_config_settings" />
+            <field name="arch" type="xml">
+                <data>
+                    <xpath expr="//group[last()]" position="after">
+                        <group>
+                            <label for="id" string="Stock available to promise" />
+                            <div>
+                                <div>
+                                    <field name="module_stock_available_immediately" class="oe_inline" />
+                                    <label for="module_stock_available_immediately" />
+                                </div>
+                            </div>
+                        </group>
+                    </xpath>
+                </data>
+            </field>
+        </record>
+    </data>
+</openerp>
\ No newline at end of file

=== modified file 'stock_available_immediately/__openerp__.py'
--- stock_available_immediately/__openerp__.py	2014-01-24 17:11:21 +0000
+++ stock_available_immediately/__openerp__.py	2014-05-23 07:51:43 +0000
@@ -20,21 +20,26 @@
 #
 #
 
-
 {
-    "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) <sebastien.beau@xxxxxxxxxxxx>
+  * Lionel Sausin (Numérigraphe) <ls@xxxxxxxxxxxxxxxx>
 """,
-    "website": "http://tinyerp.com/module_account.html";,
-    "category": "Generic Modules/Stock",
-    "data": ["product_view.xml", 
-             ],
-    "active": False,
-    "installable": True
+    "category": "Hidden",
 }

=== modified file 'stock_available_immediately/product.py'
--- stock_available_immediately/product.py	2014-02-19 11:43:35 +0000
+++ stock_available_immediately/product.py	2014-05-23 07:51:43 +0000
@@ -19,124 +19,37 @@
 #
 ##############################################################################
 
-from openerp.addons import decimal_precision as dp
-
-from openerp.osv import orm, fields
+from openerp.osv import orm
 
 
 class product_immediately_usable(orm.Model):
-    """
-    Inherit Product in order to add an "immediately usable quantity"
-    stock field
-    Immediately usable quantity is : real stock - outgoing qty
-    """
+    """Subtract incoming qty from immediately_usable_qty
+
+    We don't need to override the function fields, the module stock_available
+    takes of it for us."""
     _inherit = 'product.product'
 
     def _product_available(self, cr, uid, ids, field_names=None,
                            arg=False, context=None):
-        """
-        Get super() _product_available and compute immediately_usable_qty
-        """
-        # We need available and outgoing quantities to compute
+        """Ignore the incoming goods in the quantity available to promise"""
+        # We need available and incoming quantities to compute
         # immediately usable quantity.
         # When immediately_usable_qty is displayed but
-        # not qty_available and outgoing_qty,
+        # not qty_available and incoming_qty,
         # they are not computed in the super method so we cannot
         # compute immediately_usable_qty.
         # To avoid this issue, we add the 2 fields in
         # field_names to compute them.
         if 'immediately_usable_qty' in field_names:
             field_names.append('qty_available')
-            field_names.append('outgoing_qty')
+            field_names.append('incoming_qty')
 
         res = super(product_immediately_usable, self)._product_available(
             cr, uid, ids, field_names, arg, 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']
-
-        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."),
-    }
+            for stock_qty in res.itervalues():
+                stock_qty['immediately_usable_qty'] -= \
+                    stock_qty['incoming_qty']
+
+        return self._update_virtual_available(cr, uid, res, context=context)


Follow ups