From 6ffb17e78b03b5334d1bda30213f217e337ff3ef Mon Sep 17 00:00:00 2001 From: Lois Rilo Date: Thu, 9 Feb 2017 19:22:38 +0100 Subject: [PATCH] [9.0][ADD] stock_inventory_exclude_sublocation (#240) * [ADD] stock_inventory_exclude_sublocation --- .../README.rst | 69 +++++++ .../__init__.py | 7 + .../__openerp__.py | 21 ++ .../models/__init__.py | 6 + .../models/stock_inventory.py | 53 +++++ .../tests/__init__.py | 6 + .../tests/test_exclude_sublocation.py | 187 ++++++++++++++++++ .../views/stock_inventory_view.xml | 18 ++ 8 files changed, 367 insertions(+) create mode 100644 stock_inventory_exclude_sublocation/README.rst create mode 100644 stock_inventory_exclude_sublocation/__init__.py create mode 100644 stock_inventory_exclude_sublocation/__openerp__.py create mode 100644 stock_inventory_exclude_sublocation/models/__init__.py create mode 100644 stock_inventory_exclude_sublocation/models/stock_inventory.py create mode 100644 stock_inventory_exclude_sublocation/tests/__init__.py create mode 100644 stock_inventory_exclude_sublocation/tests/test_exclude_sublocation.py create mode 100644 stock_inventory_exclude_sublocation/views/stock_inventory_view.xml diff --git a/stock_inventory_exclude_sublocation/README.rst b/stock_inventory_exclude_sublocation/README.rst new file mode 100644 index 000000000000..7d18657185cd --- /dev/null +++ b/stock_inventory_exclude_sublocation/README.rst @@ -0,0 +1,69 @@ +.. 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 Inventory Exclude Sublocation +=================================== + +This module extends the functionality of Inventory Adjustment to allow you to +exclude all the sublocations when doing an inventory adjustment for a +given location. + +Sometimes we just want to make an inventory adjustment of just one shelf, or +space and forget about extra subdivisions in the location. In other cases we +do inventories of smaller locations contained in our stock, so we don't want +to count them again when doing an inventory adjustment of the parent location. +E.g. if we apply a cycle count strategy. + + +Usage +===== + +To use this module, you simply need to: + +#. Create a new inventory adjustment. +#. Select the option inventory of all products. +#. Check the box "Exclude Sublocations". + +.. 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/9.0 + + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smash it by providing detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Lois Rilo Antelo + + +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. diff --git a/stock_inventory_exclude_sublocation/__init__.py b/stock_inventory_exclude_sublocation/__init__.py new file mode 100644 index 000000000000..289aba0fe0fd --- /dev/null +++ b/stock_inventory_exclude_sublocation/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 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 tests diff --git a/stock_inventory_exclude_sublocation/__openerp__.py b/stock_inventory_exclude_sublocation/__openerp__.py new file mode 100644 index 000000000000..a6ba8022dc23 --- /dev/null +++ b/stock_inventory_exclude_sublocation/__openerp__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 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 Inventory Exclude Sublocation", + "summary": "Allow to perform inventories of a location without including " + "its child locations.", + "version": "9.0.1.0.0", + "author": "Eficent," + "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/stock-logistics-warehouse", + "category": "Warehouse Management", + "depends": ["stock"], + "data": [ + 'views/stock_inventory_view.xml' + ], + "license": "AGPL-3", + 'installable': True, + 'application': False, +} diff --git a/stock_inventory_exclude_sublocation/models/__init__.py b/stock_inventory_exclude_sublocation/models/__init__.py new file mode 100644 index 000000000000..eff36f7938b7 --- /dev/null +++ b/stock_inventory_exclude_sublocation/models/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 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_inventory diff --git a/stock_inventory_exclude_sublocation/models/stock_inventory.py b/stock_inventory_exclude_sublocation/models/stock_inventory.py new file mode 100644 index 000000000000..8421ce802966 --- /dev/null +++ b/stock_inventory_exclude_sublocation/models/stock_inventory.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 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 fields, models, api + + +class StockInventory(models.Model): + _inherit = 'stock.inventory' + + exclude_sublocation = fields.Boolean(string='Exclude Sublocations', + default=False) + + @api.model + def _get_inventory_lines(self, inventory): + if inventory.exclude_sublocation: + product_obj = self.env['product.product'] + domain = ' location_id = %s' + args = (tuple(inventory.location_id.ids)) + if inventory.partner_id: + domain += ' and owner_id = %s' + args += (inventory.partner_id.id,) + if inventory.lot_id: + domain += ' and lot_id = %s' + args += (inventory.lot_id.id,) + if inventory.product_id: + domain += ' and product_id = %s' + args += (inventory.product_id.id,) + if inventory.package_id: + domain += ' and package_id = %s' + args += (inventory.package_id.id,) + + self.env.cr.execute(''' + SELECT product_id, sum(qty) as product_qty, location_id, lot_id + as prod_lot_id, package_id, owner_id as partner_id + FROM stock_quant WHERE''' + domain + ''' + GROUP BY product_id, location_id, lot_id, package_id, partner_id + ''', args) + vals = [] + for product_line in self.env.cr.dictfetchall(): + for key, value in product_line.items(): + if not value: + product_line[key] = False + product_line['inventory_id'] = inventory.id + product_line['theoretical_qty'] = product_line['product_qty'] + if product_line['product_id']: + product = product_obj.browse(product_line['product_id']) + product_line['product_uom_id'] = product.uom_id.id + vals.append(product_line) + return vals + else: + return super(StockInventory, self)._get_inventory_lines(inventory) diff --git a/stock_inventory_exclude_sublocation/tests/__init__.py b/stock_inventory_exclude_sublocation/tests/__init__.py new file mode 100644 index 000000000000..f2e05e912553 --- /dev/null +++ b/stock_inventory_exclude_sublocation/tests/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 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 test_exclude_sublocation diff --git a/stock_inventory_exclude_sublocation/tests/test_exclude_sublocation.py b/stock_inventory_exclude_sublocation/tests/test_exclude_sublocation.py new file mode 100644 index 000000000000..8913dba79707 --- /dev/null +++ b/stock_inventory_exclude_sublocation/tests/test_exclude_sublocation.py @@ -0,0 +1,187 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 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). + +import openerp.tests.common as common + + +class TestStockInventoryExcludeSublocation(common.TransactionCase): + + def setUp(self): + super(TestStockInventoryExcludeSublocation, self).setUp() + self.inventory_model = self.env['stock.inventory'] + self.location_model = self.env['stock.location'] + self.lot_model = self.env['stock.production.lot'] + self.quant_model = self.env['stock.quant'] + self.package_model = self.env['stock.quant.package'] + self.res_users_model = self.env['res.users'] + + self.company = self.env.ref('base.main_company') + self.partner = self.ref('base.res_partner_4') + self.grp_stock_manager = self.env.ref('stock.group_stock_manager') + self.grp_tracking_owner = self.env.ref('stock.group_tracking_owner') + self.grp_production_lot = self.env.ref('stock.group_production_lot') + self.grp_tracking_lot = self.env.ref('stock.group_tracking_lot') + + self.user = self.res_users_model.create({ + 'name': 'Test Account User', + 'login': 'user_1', + 'password': 'demo', + 'email': 'example@yourcompany.com', + 'company_id': self.company.id, + 'company_ids': [(4, self.company.id)], + 'groups_id': [(6, 0, [ + self.grp_stock_manager.id, + self.grp_tracking_owner.id, + self.grp_production_lot.id, + self.grp_tracking_lot.id + ])] + }) + + self.product1 = self.env['product.product'].create({ + 'name': 'Product for parent location', + 'type': 'product', + 'default_code': 'PROD1', + }) + self.product2 = self.env['product.product'].create({ + 'name': 'Product for child location', + 'type': 'product', + 'default_code': 'PROD2', + }) + self.location = self.location_model.create({ + 'name': 'Inventory tests', + 'usage': 'internal', + }) + self.sublocation = self.location_model.create({ + 'name': 'Inventory sublocation test', + 'usage': 'internal', + 'location_id': self.location.id + }) + self.lot_a = self.lot_model.create({ + 'name': 'Lot for product1', + 'product_id': self.product1.id + }) + self.package = self.package_model.create({'name': 'PACK00TEST1'}) + + # Add a product in each location + starting_inv = self.inventory_model.create({ + 'name': 'Starting inventory', + 'filter': 'product', + 'line_ids': [ + (0, 0, { + 'product_id': self.product1.id, + 'product_uom_id': self.env.ref( + "product.product_uom_unit").id, + 'product_qty': 2.0, + 'location_id': self.location.id, + 'prod_lot_id': self.lot_a.id + }), + (0, 0, { + 'product_id': self.product2.id, + 'product_uom_id': self.env.ref( + "product.product_uom_unit").id, + 'product_qty': 4.0, + 'location_id': self.sublocation.id, + 'prod_lot_id': self.lot_a.id + }), + ], + }) + starting_inv.action_done() + + def _create_inventory_all_products(self, name, location, + exclude_sublocation): + inventory = self.inventory_model.create({ + 'name': name, + 'filter': 'none', + 'location_id': location.id, + 'exclude_sublocation': exclude_sublocation + }) + return inventory + + def test_not_excluding_sublocations(self): + '''Check if products in sublocations are included into the inventory + if the excluding sublocations option is disabled.''' + inventory_location = self._create_inventory_all_products( + 'location inventory', self.location, False) + inventory_location.prepare_inventory() + inventory_location.action_done() + lines = inventory_location.line_ids + self.assertEqual(len(lines), 2, 'Not all expected products are ' + 'included') + + def test_excluding_sublocations(self): + '''Check if products in sublocations are not included if the exclude + sublocations is enabled.''' + inventory_location = self._create_inventory_all_products( + 'location inventory', self.location, True) + inventory_sublocation = self._create_inventory_all_products( + 'sublocation inventory', self.sublocation, True) + inventory_location.prepare_inventory() + inventory_location.action_done() + inventory_sublocation.prepare_inventory() + inventory_sublocation.action_done() + lines_location = inventory_location.line_ids + lines_sublocation = inventory_sublocation.line_ids + self.assertEqual(len(lines_location), 1, + 'The products in the sublocations are not excluded') + self.assertEqual(len(lines_sublocation), 1, + 'The products in the sublocations are not excluded') + + def test_lot_excluding_sublocation(self): + '''Check if the sublocations are excluded when using lots.''' + inventory = self.inventory_model.sudo(self.user.id).create({ + 'name': 'Inventory lot', + 'filter': 'lot', + 'location_id': self.location.id, + 'lot_id': self.lot_a.id, + 'exclude_sublocation': True + }) + inventory.prepare_inventory() + inventory.action_done() + lines = inventory.line_ids + self.assertEqual(len(lines), 1, 'The products in the sublocations are ' + 'not excluded with lots.') + + def test_product_and_owner_excluding_sublocation(self): + '''Check if sublocations are excluded when filtering by owner and + product.''' + self.quant_model.create({ + 'product_id': self.product1.id, + 'location_id': self.location.id, + 'qty': 1, + 'owner_id': self.partner, + }) + inventory = self.inventory_model.sudo(self.user.id).create({ + 'name': 'Inventory lot', + 'filter': 'product_owner', + 'location_id': self.location.id, + 'product_id': self.product1.id, + 'partner_id': self.partner, + 'exclude_sublocation': True + }) + inventory.prepare_inventory() + lines = inventory.line_ids + self.assertEqual(len(lines), 1, + 'The products in the sublocations are ' + 'not excluded with product and owner filter.') + + def test_pack_excluding_sublocation(self): + '''Check if sublocations are excluded when filtering by package.''' + self.quant_model.create({ + 'product_id': self.product1.id, + 'location_id': self.location.id, + 'qty': 1, + 'package_id': self.package.id + }) + inventory = self.inventory_model.sudo(self.user.id).create({ + 'name': 'Inventory lot', + 'filter': 'pack', + 'location_id': self.location.id, + 'package_id': self.package.id, + 'exclude_sublocation': True + }) + inventory.prepare_inventory() + lines = inventory.line_ids + self.assertEqual(len(lines), 1, 'The products in the sublocations are ' + 'not excluded with package filter.') diff --git a/stock_inventory_exclude_sublocation/views/stock_inventory_view.xml b/stock_inventory_exclude_sublocation/views/stock_inventory_view.xml new file mode 100644 index 000000000000..c51b076b25f7 --- /dev/null +++ b/stock_inventory_exclude_sublocation/views/stock_inventory_view.xml @@ -0,0 +1,18 @@ + + + + + + + Inventory form view - cycle count extension + stock.inventory + + + + + + + + +