mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
[9.0][ADD] stock_inventory_exclude_sublocation (#240)
* [ADD] stock_inventory_exclude_sublocation
This commit is contained in:
69
stock_inventory_exclude_sublocation/README.rst
Normal file
69
stock_inventory_exclude_sublocation/README.rst
Normal file
@@ -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
|
||||
<https://github.com/OCA/{project_repo}/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 <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
* Lois Rilo Antelo <lois.rilo@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.
|
||||
7
stock_inventory_exclude_sublocation/__init__.py
Normal file
7
stock_inventory_exclude_sublocation/__init__.py
Normal file
@@ -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
|
||||
21
stock_inventory_exclude_sublocation/__openerp__.py
Normal file
21
stock_inventory_exclude_sublocation/__openerp__.py
Normal file
@@ -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,
|
||||
}
|
||||
6
stock_inventory_exclude_sublocation/models/__init__.py
Normal file
6
stock_inventory_exclude_sublocation/models/__init__.py
Normal file
@@ -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
|
||||
@@ -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)
|
||||
6
stock_inventory_exclude_sublocation/tests/__init__.py
Normal file
6
stock_inventory_exclude_sublocation/tests/__init__.py
Normal file
@@ -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
|
||||
@@ -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.')
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2017 Eficent Business and IT Consulting Services S.L.
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="view_inventory_form" model="ir.ui.view">
|
||||
<field name="name">Inventory form view - cycle count extension </field>
|
||||
<field name="model">stock.inventory</field>
|
||||
<field name="inherit_id" ref="stock.view_inventory_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="filter" position="after">
|
||||
<field name="exclude_sublocation"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user