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