From 8cabcc04f37b8490a6031f046ef872c74ab325ac Mon Sep 17 00:00:00 2001 From: Lionel Sausin Date: Tue, 8 Sep 2015 18:06:12 +0200 Subject: [PATCH] Revert changes to computation of _immediately_usable_qty Commit 6c16913 changed the way we compute the immediately_usable_qty: instead of using the virtual stock, we used the sum of quants without reservations. But a quant may actually be reserved and still be available (for example it may be reserved for an internal move). Fixes https://github.com/OCA/stock-logistics-warehouse/issues/79 Remove loop and use correct decorator Restore the features of stock_available_immediately The previous fix restored stock_available but then there was no way to exclude the incomming moves from the count. This belongs in stock_available_immediately, restoring it cleanly. This commit also takes care to respect the distinction between templates and variants, so it should fix https://github.com/OCA/stock-logistics-warehouse/issues/73 too. Restore the qty avail. to promise on variant treeview PEP8 --- stock_available/__init__.py | 3 +- stock_available/__openerp__.py | 7 +- stock_available/models/__init__.py | 23 ++++ .../{product.py => models/product_product.py} | 40 ++---- stock_available/models/product_template.py | 42 ++++++ stock_available/{ => models}/res_config.py | 0 stock_available/tests/test_stock_available.py | 120 ------------------ .../views/product_product_view.xml | 20 +++ .../product_template_view.xml} | 0 .../{ => views}/res_config_view.xml | 0 10 files changed, 102 insertions(+), 153 deletions(-) create mode 100644 stock_available/models/__init__.py rename stock_available/{product.py => models/product_product.py} (54%) create mode 100644 stock_available/models/product_template.py rename stock_available/{ => models}/res_config.py (100%) delete mode 100644 stock_available/tests/test_stock_available.py create mode 100644 stock_available/views/product_product_view.xml rename stock_available/{product_view.xml => views/product_template_view.xml} (100%) rename stock_available/{ => views}/res_config_view.xml (100%) diff --git a/stock_available/__init__.py b/stock_available/__init__.py index 6dff1269a..036bf665c 100644 --- a/stock_available/__init__.py +++ b/stock_available/__init__.py @@ -18,5 +18,4 @@ # ############################################################################## -from . import product -from . import res_config +from . import models diff --git a/stock_available/__openerp__.py b/stock_available/__openerp__.py index 608e6b8f4..027c5c02a 100644 --- a/stock_available/__openerp__.py +++ b/stock_available/__openerp__.py @@ -26,7 +26,8 @@ 'depends': ['stock'], 'license': 'AGPL-3', 'data': [ - 'product_view.xml', - 'res_config_view.xml', - ] + 'views/product_template_view.xml', + 'views/product_product_view.xml', + 'views/res_config_view.xml', + ], } diff --git a/stock_available/models/__init__.py b/stock_available/models/__init__.py new file mode 100644 index 000000000..bf26a289f --- /dev/null +++ b/stock_available/models/__init__.py @@ -0,0 +1,23 @@ +# -*- 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_template +from . import product_product +from . import res_config diff --git a/stock_available/product.py b/stock_available/models/product_product.py similarity index 54% rename from stock_available/product.py rename to stock_available/models/product_product.py index 2a0f6c598..7335f641a 100644 --- a/stock_available/product.py +++ b/stock_available/models/product_product.py @@ -22,45 +22,29 @@ from openerp import models, fields, api from openerp.addons import decimal_precision as dp -class ProductTemplate(models.Model): +class ProductProduct(models.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.template' + _inherit = 'product.product' - # immediately usable quantity caluculated with the quant method - @api.multi + @api.one @api.depends('virtual_available') def _immediately_usable_qty(self): - stock_location_obj = self.env['stock.location'] - internal_locations = stock_location_obj.search([ - ('usage', '=', 'internal')]) - sublocations = self.env['stock.location'] - for location in internal_locations: - sublocations += stock_location_obj.search( - [('id', 'child_of', location.id)]) - for product_template in self: - products = self.env['product.product'].search([ - ('product_tmpl_id', '=', product_template.id)]) - quant_obj = self.env['stock.quant'] - quants = quant_obj.search([ - ('location_id', 'in', sublocations.ids), - ('product_id', 'in', products.ids), - ('reservation_id', '=', False)]) - availability = 0 - if quants: - for quant in quants: - availability += quant.qty - product_template.immediately_usable_qty = availability + """No-op implementation of the stock available to promise. + + By default, available to promise = forecasted quantity. + + Must be overridden by another module that actually implement + computations.""" + self.immediately_usable_qty = self.virtual_available immediately_usable_qty = fields.Float( digits=dp.get_precision('Product Unit of Measure'), compute='_immediately_usable_qty', - string='Available to promise (quant calculation)', + 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 , this number is obtained by using the new odoo 8 " - "quants, so it gives us the actual current quants minus reserved" - "quants") + "your needs") diff --git a/stock_available/models/product_template.py b/stock_available/models/product_template.py new file mode 100644 index 000000000..fe55ecc47 --- /dev/null +++ b/stock_available/models/product_template.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 . +# +############################################################################## + +from openerp import models, fields, api +from openerp.addons import decimal_precision as dp + + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + @api.one + @api.depends('virtual_available') + def _immediately_usable_qty(self): + """Compute the quantity using all the variants""" + self.immediately_usable_qty = sum( + [v.immediately_usable_qty for v in self.product_variant_ids]) + + immediately_usable_qty = fields.Float( + digits=dp.get_precision('Product Unit of Measure'), + compute='_immediately_usable_qty', + 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/res_config.py b/stock_available/models/res_config.py similarity index 100% rename from stock_available/res_config.py rename to stock_available/models/res_config.py diff --git a/stock_available/tests/test_stock_available.py b/stock_available/tests/test_stock_available.py deleted file mode 100644 index 2c46f6460..000000000 --- a/stock_available/tests/test_stock_available.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Copyright (C) 2015 Therp BV -# -# 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.tests.common import TransactionCase - - -class testStockLogisticsWarehouse(TransactionCase): - - def test01_stock_levels(self): - """checking that immediately_usable_qty actually reflects \ -the variations in stock, both on product and template""" - moveObj = self.env['stock.move'] - productObj = self.env['product.product'] - templateObj = self.env['product.template'] - supplier_location = self.env.ref('stock.stock_location_suppliers') - stock_location = self.env.ref('stock.stock_location_stock') - customer_location = self.env.ref('stock.stock_location_customers') - uom_unit = self.env.ref('product.product_uom_unit') - - # Create product template - templateAB = templateObj.create( - {'name': 'templAB', - 'uom_id': uom_unit.id, - }) - - # Create product A and B - productA = productObj.create( - {'name': 'product A', - 'standard_price': 1, - 'type': 'product', - 'uom_id': uom_unit.id, - 'default_code': 'A', - 'product_tmpl_id': templateAB.id, - }) - - productB = productObj.create( - {'name': 'product B', - 'standard_price': 1, - 'type': 'product', - 'uom_id': uom_unit.id, - 'default_code': 'B', - 'product_tmpl_id': templateAB.id, - }) - - # Create a stock move from INCOMING to STOCK - stockMoveInA = moveObj.create( - {'location_id': supplier_location.id, - 'location_dest_id': stock_location.id, - 'name': 'MOVE INCOMING -> STOCK ', - 'product_id': productA.id, - 'product_uom': productA.uom_id.id, - 'product_uom_qty': 2, - }) - - stockMoveInB = moveObj.create( - {'location_id': supplier_location.id, - 'location_dest_id': stock_location.id, - 'name': 'MOVE INCOMING -> STOCK ', - 'product_id': productB.id, - 'product_uom': productB.uom_id.id, - 'product_uom_qty': 3, - }) - - def compare_product_usable_qty(product, value): - # Refresh, because the function field is not recalculated between - # transactions - product.refresh() - self.assertEqual(product.immediately_usable_qty, value) - - compare_product_usable_qty(productA, 0) - compare_product_usable_qty(templateAB, 0) - - stockMoveInA.action_confirm() - compare_product_usable_qty(productA, 0) - compare_product_usable_qty(templateAB, 0) - - stockMoveInA.action_assign() - compare_product_usable_qty(productA, 0) - compare_product_usable_qty(templateAB, 0) - - stockMoveInA.action_done() - compare_product_usable_qty(productA, 2) - compare_product_usable_qty(templateAB, 2) - - # will directly trigger action_done on productB - stockMoveInB.action_done() - compare_product_usable_qty(productA, 2) - compare_product_usable_qty(productB, 3) - compare_product_usable_qty(templateAB, 5) - - # Create a stock move from STOCK to CUSTOMER - stockMoveOutA = moveObj.create( - {'location_id': stock_location.id, - 'location_dest_id': customer_location.id, - 'name': ' STOCK --> CUSTOMER ', - 'product_id': productA.id, - 'product_uom': productA.uom_id.id, - 'product_uom_qty': 1, - 'state': 'confirmed', - }) - - stockMoveOutA.action_done() - compare_product_usable_qty(productA, 1) - compare_product_usable_qty(templateAB, 4) diff --git a/stock_available/views/product_product_view.xml b/stock_available/views/product_product_view.xml new file mode 100644 index 000000000..22dd36b2e --- /dev/null +++ b/stock_available/views/product_product_view.xml @@ -0,0 +1,20 @@ + + + + + Quantity available to promise (variant tree) + product.product + + + + + 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') + + + + + + + + + diff --git a/stock_available/product_view.xml b/stock_available/views/product_template_view.xml similarity index 100% rename from stock_available/product_view.xml rename to stock_available/views/product_template_view.xml diff --git a/stock_available/res_config_view.xml b/stock_available/views/res_config_view.xml similarity index 100% rename from stock_available/res_config_view.xml rename to stock_available/views/res_config_view.xml