From 86ca74629e13e973d24a37cce04311cdc2251ca7 Mon Sep 17 00:00:00 2001 From: Sergio Teruel Date: Tue, 11 Feb 2020 22:39:51 +0100 Subject: [PATCH] [MIG] stock_inventory_preparation_filter: Migration to v13.0 --- stock_inventory_preparation_filter/README.rst | 22 +- .../__manifest__.py | 4 +- .../models/stock_inventory.py | 152 ++++---------- .../readme/CONTRIBUTORS.rst | 1 + .../readme/DESCRIPTION.rst | 4 - .../readme/ROADMAP.rst | 3 + .../security/ir.model.access.csv | 3 - .../static/description/index.html | 39 ++-- ...test_stock_inventory_preparation_filter.py | 195 ++++++------------ .../views/stock_inventory_view.xml | 90 ++++---- 10 files changed, 186 insertions(+), 327 deletions(-) create mode 100644 stock_inventory_preparation_filter/readme/ROADMAP.rst delete mode 100644 stock_inventory_preparation_filter/security/ir.model.access.csv diff --git a/stock_inventory_preparation_filter/README.rst b/stock_inventory_preparation_filter/README.rst index 5c824d859..e6784f253 100644 --- a/stock_inventory_preparation_filter/README.rst +++ b/stock_inventory_preparation_filter/README.rst @@ -14,39 +14,42 @@ Extended Inventory Preparation Filters :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstock--logistics--warehouse-lightgray.png?logo=github - :target: https://github.com/OCA/stock-logistics-warehouse/tree/12.0/stock_inventory_preparation_filter + :target: https://github.com/OCA/stock-logistics-warehouse/tree/13.0/stock_inventory_preparation_filter :alt: OCA/stock-logistics-warehouse .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-12-0/stock-logistics-warehouse-12-0-stock_inventory_preparation_filter + :target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-13-0/stock-logistics-warehouse-13-0-stock_inventory_preparation_filter :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/153/12.0 + :target: https://runbot.odoo-community.org/runbot/153/13.0 :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| Includes more options for making an inventory out of: -* Multiple products. * Products filtered by a domain. * Products of a category. * Multiple lots -It also allows to make an inventory based on scanned products, adding a line -with product code and quantity. - **Table of contents** .. contents:: :local: +Known issues / Roadmap +====================== + +* The widget domain is not displayed correctly, but this issue is related to + Odoo. To avoid this malfunction, use the keyboard arrows to properly work + with the domain option. + 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 smashing it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -67,6 +70,7 @@ Contributors * Pedro M. Baeza * David Vidal + * Sergio Teruel * Xavier Jimenez @@ -83,6 +87,6 @@ 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. -This module is part of the `OCA/stock-logistics-warehouse `_ project on GitHub. +This module is part of the `OCA/stock-logistics-warehouse `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_inventory_preparation_filter/__manifest__.py b/stock_inventory_preparation_filter/__manifest__.py index af798d608..c24294b77 100644 --- a/stock_inventory_preparation_filter/__manifest__.py +++ b/stock_inventory_preparation_filter/__manifest__.py @@ -5,13 +5,13 @@ { "name": "Extended Inventory Preparation Filters", - "version": "12.0.1.1.0", + "version": "13.0.1.0.0", "depends": ["stock"], "author": "AvanzOSC," "Tecnativa," "Odoo Community Association (OCA)", "category": "Inventory, Logistic, Storage", "website": "http://github.com/OCA/stock-logistics-warehouse", "summary": "More filters for inventory adjustments", - "data": ["views/stock_inventory_view.xml", "security/ir.model.access.csv"], + "data": ["views/stock_inventory_view.xml"], "installable": True, "license": "AGPL-3", } diff --git a/stock_inventory_preparation_filter/models/stock_inventory.py b/stock_inventory_preparation_filter/models/stock_inventory.py index adb19e4ba..3128207dd 100644 --- a/stock_inventory_preparation_filter/models/stock_inventory.py +++ b/stock_inventory_preparation_filter/models/stock_inventory.py @@ -1,53 +1,40 @@ # Copyright 2015 AvanzOSC - Oihane Crucelaegi # Copyright 2015 Tecnativa - Pedro M. Baeza +# Copyright 2020 Sergio Teruel # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import _, api, fields, models from odoo.tools.safe_eval import safe_eval -from odoo.addons import decimal_precision as dp - - -class StockInventoryEmptyLines(models.Model): - _name = "stock.inventory.line.empty" - _description = "Inventory Line Empty" - - product_code = fields.Char(string="Product Code", required=True) - product_qty = fields.Float( - string="Quantity", - required=True, - default=1.0, - digits=dp.get_precision("Product Unit of Measure"), - ) - inventory_id = fields.Many2one( - comodel_name="stock.inventory", - string="Inventory", - required=True, - ondelete="cascade", - ) - class StockInventory(models.Model): _inherit = "stock.inventory" @api.model def _selection_filter(self): - """This function will return the list of filters allowed according to - the options checked in 'Settings/Warehouse'. - - :return: list of tuple - """ - res_filters = super(StockInventory, self)._selection_filter() - res_filters.append(("categories", _("Selected Categories"))) - res_filters.append(("products", _("Selected Products"))) - res_filters.append(("domain", _("Filtered Products"))) - for res_filter in res_filters: - if res_filter[0] == "lot": - res_filters.append(("lots", _("Selected Lots"))) - break - res_filters.append(("empty", _("Empty list"))) - return res_filters + """ Get the list of filter allowed according to the options checked + in 'Settings / Warehouse'. """ + res_filter = [ + ("products", _("All products")), + ("categories", _("Selected Categories")), + ("domain", _("Filtered Products")), + ] + if self.user_has_groups("stock.group_production_lot"): + res_filter.append(("lots", _("Selected Lots"))) + return res_filter + filter = fields.Selection( + string="Inventory of", + selection="_selection_filter", + required=True, + default="products", + help="If you do an entire inventory, you can choose 'All Products' and " + "it will prefill the inventory with the current stock. If you " + "only do some products (e.g. Cycle Counting) you can choose " + "'Manual Selection of Products' and the system won't propose " + "anything. You can also let the system propose for a single " + "product / lot /... ", + ) categ_ids = fields.Many2many( comodel_name="product.category", relation="rel_inventories_categories", @@ -55,13 +42,6 @@ class StockInventory(models.Model): column2="category_id", string="Categories", ) - product_ids = fields.Many2many( - comodel_name="product.product", - relation="rel_inventories_products", - column1="inventory_id", - column2="product_id", - string="Products", - ) lot_ids = fields.Many2many( comodel_name="stock.production.lot", relation="rel_inventories_lots", @@ -69,75 +49,21 @@ class StockInventory(models.Model): column2="lot_id", string="Lots", ) - empty_line_ids = fields.One2many( - comodel_name="stock.inventory.line.empty", - inverse_name="inventory_id", - string="Capture Lines", - ) product_domain = fields.Char("Domain", default=[("name", "ilike", "")]) - @api.model - def _get_inventory_lines_values(self): - self.ensure_one() - vals = [] - product_obj = self.env["product.product"] - inventory = self.new(self._convert_to_write(self.read()[0])) - if self.filter in ("categories", "products"): - if self.filter == "categories": - products = product_obj.search( - [("product_tmpl_id.categ_id", "in", self.categ_ids.ids)] - ) - else: # filter = 'products' - products = self.product_ids - inventory.filter = "product" - for product in products: - inventory.product_id = product - vals += super(StockInventory, inventory)._get_inventory_lines_values() - elif self.filter == "lots": - inventory.filter = "lot" - for lot in self.lot_ids: - inventory.lot_id = lot - vals += super(StockInventory, inventory)._get_inventory_lines_values() - elif self.filter == "domain": - domain = safe_eval(self.product_domain) - products = self.env["product.product"].search(domain) - inventory.filter = "product" - for product in products: - inventory.product_id = product - vals += super(StockInventory, inventory)._get_inventory_lines_values() - elif self.filter == "empty": - tmp_lines = {} - for line in self.empty_line_ids: - tmp_lines.setdefault(line.product_code, 0) - tmp_lines[line.product_code] += line.product_qty - self.empty_line_ids.unlink() - inventory.filter = "product" - # HACK: Make sure location is preserved - inventory.location_id = self.location_id - for product_code in tmp_lines.keys(): - product = product_obj.search( - [ - "|", - ("default_code", "=", product_code), - ("barcode", "=", product_code), - ], - limit=1, - ) - if not product: - continue - inventory.product_id = product - values = super(StockInventory, inventory)._get_inventory_lines_values() - if values: - values[0]["product_qty"] = tmp_lines[product_code] - else: - vals += [ - { - "product_id": product.id, - "product_qty": tmp_lines[product_code], - "location_id": self.location_id.id, - } - ] - vals += values - else: - vals = super()._get_inventory_lines_values() - return vals + def _action_start(self): + Product = self.env["product.product"] + for inventory in self: + products = Product.browse() + if inventory.state != "draft": + continue + if inventory.filter == "categories": + products = Product.search([("categ_id", "in", inventory.categ_ids.ids)]) + if inventory.filter == "lots": + products = inventory.lot_ids.mapped("product_id") + if inventory.filter == "domain": + domain = safe_eval(inventory.product_domain) + products = Product.search(domain) + if products: + inventory.product_ids = [(6, 0, products.ids)] + return super()._action_start() diff --git a/stock_inventory_preparation_filter/readme/CONTRIBUTORS.rst b/stock_inventory_preparation_filter/readme/CONTRIBUTORS.rst index 74d17e3f1..73ee8a368 100644 --- a/stock_inventory_preparation_filter/readme/CONTRIBUTORS.rst +++ b/stock_inventory_preparation_filter/readme/CONTRIBUTORS.rst @@ -3,5 +3,6 @@ * Pedro M. Baeza * David Vidal + * Sergio Teruel * Xavier Jimenez diff --git a/stock_inventory_preparation_filter/readme/DESCRIPTION.rst b/stock_inventory_preparation_filter/readme/DESCRIPTION.rst index 33779664f..62cead8b8 100644 --- a/stock_inventory_preparation_filter/readme/DESCRIPTION.rst +++ b/stock_inventory_preparation_filter/readme/DESCRIPTION.rst @@ -1,9 +1,5 @@ Includes more options for making an inventory out of: -* Multiple products. * Products filtered by a domain. * Products of a category. * Multiple lots - -It also allows to make an inventory based on scanned products, adding a line -with product code and quantity. diff --git a/stock_inventory_preparation_filter/readme/ROADMAP.rst b/stock_inventory_preparation_filter/readme/ROADMAP.rst new file mode 100644 index 000000000..a4e3ae486 --- /dev/null +++ b/stock_inventory_preparation_filter/readme/ROADMAP.rst @@ -0,0 +1,3 @@ +* The widget domain is not displayed correctly, but this issue is related to + Odoo. To avoid this malfunction, use the keyboard arrows to properly work + with the domain option. diff --git a/stock_inventory_preparation_filter/security/ir.model.access.csv b/stock_inventory_preparation_filter/security/ir.model.access.csv deleted file mode 100644 index 723cb2057..000000000 --- a/stock_inventory_preparation_filter/security/ir.model.access.csv +++ /dev/null @@ -1,3 +0,0 @@ -id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_stock_inventory_line_empty_user,access_stock_inventory_line_empty_user,model_stock_inventory_line_empty,stock.group_stock_user,1,0,0,0 -access_stock_inventory_line_empty_manager,access_stock_inventory_line_empty_manager,model_stock_inventory_line_empty,stock.group_stock_manager,1,1,1,1 diff --git a/stock_inventory_preparation_filter/static/description/index.html b/stock_inventory_preparation_filter/static/description/index.html index 039f44545..2d7b90ae1 100644 --- a/stock_inventory_preparation_filter/static/description/index.html +++ b/stock_inventory_preparation_filter/static/description/index.html @@ -367,65 +367,72 @@ ul.auto-toc { !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/stock-logistics-warehouse Translate me on Weblate Try me on Runbot

+

Beta License: AGPL-3 OCA/stock-logistics-warehouse Translate me on Weblate Try me on Runbot

Includes more options for making an inventory out of:

    -
  • Multiple products.
  • Products filtered by a domain.
  • Products of a category.
  • Multiple lots
-

It also allows to make an inventory based on scanned products, adding a line -with product code and quantity.

Table of contents

+
+

Known issues / Roadmap

+
    +
  • The widget domain is not displayed correctly, but this issue is related to +Odoo. To avoid this malfunction, use the keyboard arrows to properly work +with the domain option.
  • +
+
-

Bug Tracker

+

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 smashing it by providing a detailed and welcomed -feedback.

+feedback.

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • AvanzOSC
  • Tecnativa
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association

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.

-

This module is part of the OCA/stock-logistics-warehouse project on GitHub.

+

This module is part of the OCA/stock-logistics-warehouse project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

diff --git a/stock_inventory_preparation_filter/tests/test_stock_inventory_preparation_filter.py b/stock_inventory_preparation_filter/tests/test_stock_inventory_preparation_filter.py index 99d3d27c0..42370160b 100644 --- a/stock_inventory_preparation_filter/tests/test_stock_inventory_preparation_filter.py +++ b/stock_inventory_preparation_filter/tests/test_stock_inventory_preparation_filter.py @@ -11,15 +11,14 @@ class TestStockInventoryPreparationFilterCategories(common.TransactionCase): def setUp(self): super(TestStockInventoryPreparationFilterCategories, self).setUp() self.inventory_model = self.env["stock.inventory"] + self.location = self.env.ref("stock.stock_location_stock") + self.Product = self.env["product.product"] + self.Category = self.env["product.category"] # Create some categories - self.category = self.env["product.category"].create( - {"name": "Category for inventory"} - ) - self.category2 = self.env["product.category"].create( - {"name": "Category for inventory 2"} - ) + self.category = self.Category.create({"name": "Category for inventory"}) + self.category2 = self.Category.create({"name": "Category for inventory 2"}) # Create some products in the category - self.product1 = self.env["product.product"].create( + self.product1 = self.Product.create( { "name": "Product for inventory 1", "type": "product", @@ -27,7 +26,7 @@ class TestStockInventoryPreparationFilterCategories(common.TransactionCase): "default_code": "PROD1-TEST", } ) - self.product2 = self.env["product.product"].create( + self.product2 = self.Product.create( { "name": "Product for inventory 2", "type": "product", @@ -35,7 +34,7 @@ class TestStockInventoryPreparationFilterCategories(common.TransactionCase): "default_code": "PROD2-TEST", } ) - self.product3 = self.env["product.product"].create( + self.product3 = self.Product.create( { "name": "Product for inventory 3", "type": "product", @@ -43,7 +42,7 @@ class TestStockInventoryPreparationFilterCategories(common.TransactionCase): "default_code": "PROD3-TEST", } ) - self.product_lot = self.env["product.product"].create( + self.product_lot = self.Product.create( { "name": "Product for inventory with lots", "type": "product", @@ -51,7 +50,41 @@ class TestStockInventoryPreparationFilterCategories(common.TransactionCase): } ) self.lot = self.env["stock.production.lot"].create( - {"name": "Lot test", "product_id": self.product_lot.id} + { + "name": "Lot test", + "product_id": self.product_lot.id, + "company_id": self.env.user.company_id.id, + } + ) + # Add quants for products to ensure that inventory lines are created + self.env["stock.quant"].create( + [ + { + "product_id": self.product1.id, + "product_uom_id": self.product1.uom_id.id, + "location_id": self.location.id, + "quantity": 2.0, + }, + { + "product_id": self.product2.id, + "product_uom_id": self.product2.uom_id.id, + "location_id": self.location.id, + "quantity": 2.0, + }, + { + "product_id": self.product3.id, + "product_uom_id": self.product3.uom_id.id, + "location_id": self.location.id, + "quantity": 2.0, + }, + { + "product_id": self.product_lot.id, + "product_uom_id": self.product_lot.uom_id.id, + "location_id": self.location.id, + "quantity": 2.0, + "lot_id": self.lot.id, + }, + ] ) # Add user to lot tracking group self.env.user.groups_id = [(4, self.env.ref("stock.group_production_lot").id)] @@ -59,93 +92,35 @@ class TestStockInventoryPreparationFilterCategories(common.TransactionCase): self.location = self.env["stock.location"].create( {"name": "Inventory tests", "usage": "internal"} ) - inventory = self.inventory_model.create( - { - "name": "Product1 inventory", - "filter": "product", - "line_ids": [ - ( - 0, - 0, - { - "product_id": self.product1.id, - "product_uom_id": self.env.ref("uom.product_uom_unit").id, - "product_qty": 2.0, - "location_id": self.location.id, - }, - ), - ( - 0, - 0, - { - "product_id": self.product2.id, - "product_uom_id": self.env.ref("uom.product_uom_unit").id, - "product_qty": 4.0, - "location_id": self.location.id, - }, - ), - ( - 0, - 0, - { - "product_id": self.product_lot.id, - "product_uom_id": self.env.ref("uom.product_uom_unit").id, - "product_qty": 6.0, - "location_id": self.location.id, - "prod_lot_id": self.lot.id, - }, - ), - ], - } + self.test_products = ( + self.product1 + self.product2 + self.product3 + self.product_lot ) - inventory._action_done() - def test_inventory_category_filter(self): + def test_inventory_filter(self): + # Filter all products inventory = self.inventory_model.create( - { - "name": "Category inventory", - "filter": "categories", - "location_id": self.location.id, - "categ_ids": [(6, 0, [self.category.id])], - } + {"name": "Inventory test", "filter": "products"} ) inventory.action_start() - self.assertEqual(len(inventory.line_ids), 2) - line1 = inventory.line_ids[0] - self.assertEqual(line1.product_id, self.product1) - self.assertEqual(line1.theoretical_qty, 2.0) - self.assertEqual(line1.location_id, self.location) - line2 = inventory.line_ids[1] - self.assertEqual(line2.product_id, self.product2) - self.assertEqual(line2.theoretical_qty, 4.0) - self.assertEqual(line2.location_id, self.location) - - def test_inventory_products_filter(self): - inventory = self.inventory_model.create( - { - "name": "Products inventory", - "filter": "products", - "location_id": self.location.id, - "product_ids": [(6, 0, [self.product1.id, self.product2.id])], - } + self.assertTrue(self.test_products <= inventory.line_ids.mapped("product_id")) + # Filter by categories + inventory.action_cancel_draft() + inventory.update( + {"filter": "categories", "categ_ids": [(6, 0, [self.category.id])]} ) inventory.action_start() - self.assertEqual(len(inventory.line_ids), 2) - line1 = inventory.line_ids[0] - self.assertEqual(line1.product_id, self.product1) - self.assertEqual(line1.theoretical_qty, 2.0) - self.assertEqual(line1.location_id, self.location) - line2 = inventory.line_ids[1] - self.assertEqual(line2.product_id, self.product2) - self.assertEqual(line2.theoretical_qty, 4.0) - self.assertEqual(line2.location_id, self.location) + self.assertEqual(len(inventory.line_ids), 3) + # Filter by lots + inventory.action_cancel_draft() + inventory.update({"filter": "lots", "lot_ids": [(6, 0, self.lot.ids)]}) + inventory.action_start() + self.assertEqual(len(inventory.line_ids), 1) def test_inventory_domain_filter(self): inventory = self.inventory_model.create( { "name": "Domain inventory", "filter": "domain", - "location_id": self.location.id, "product_domain": [("id", "=", self.product1.id)], } ) @@ -154,53 +129,3 @@ class TestStockInventoryPreparationFilterCategories(common.TransactionCase): line1 = inventory.line_ids[0] self.assertEqual(line1.product_id, self.product1) self.assertEqual(line1.theoretical_qty, 2.0) - self.assertEqual(line1.location_id, self.location) - - def test_inventory_lots_filter(self): - inventory = self.inventory_model.create( - { - "name": "Products inventory", - "filter": "lots", - "location_id": self.location.id, - "lot_ids": [(6, 0, [self.lot.id])], - } - ) - inventory.action_start() - self.assertEqual(len(inventory.line_ids), 1) - line1 = inventory.line_ids[0] - self.assertEqual(line1.product_id, self.product_lot) - self.assertEqual(line1.prod_lot_id, self.lot) - self.assertEqual(line1.theoretical_qty, 6.0) - self.assertEqual(line1.location_id, self.location) - - def test_inventory_empty_filter(self): - inventory = self.inventory_model.create( - { - "name": "Products inventory", - "filter": "empty", - "location_id": self.location.id, - "empty_line_ids": [ - (0, 0, {"product_code": "PROD1-TEST", "product_qty": 3.0}), - (0, 0, {"product_code": "PROD2-TEST", "product_qty": 7.0}), - (0, 0, {"product_code": "PROD3-TEST", "product_qty": 5.0}), - (0, 0, {"product_code": "UNEXISTING-CODE", "product_qty": 0.0}), - ], - } - ) - inventory.action_start() - self.assertEqual(len(inventory.line_ids), 3) - line1 = inventory.line_ids[0] - self.assertEqual(line1.product_id, self.product1) - self.assertEqual(line1.theoretical_qty, 2.0) - self.assertEqual(line1.product_qty, 3.0) - self.assertEqual(line1.location_id, self.location) - line2 = inventory.line_ids[1] - self.assertEqual(line2.product_id, self.product2) - self.assertEqual(line2.theoretical_qty, 4.0) - self.assertEqual(line2.product_qty, 7.0) - self.assertEqual(line2.location_id, self.location) - line3 = inventory.line_ids[2] - self.assertEqual(line3.product_id, self.product3) - self.assertEqual(line3.theoretical_qty, 0.0) - self.assertEqual(line3.product_qty, 5.0) - self.assertEqual(line3.location_id, self.location) diff --git a/stock_inventory_preparation_filter/views/stock_inventory_view.xml b/stock_inventory_preparation_filter/views/stock_inventory_view.xml index 4077f15d5..49e76ab7a 100644 --- a/stock_inventory_preparation_filter/views/stock_inventory_view.xml +++ b/stock_inventory_preparation_filter/views/stock_inventory_view.xml @@ -1,48 +1,48 @@ - + - - - stock.inventory.form - stock.inventory - - - - - - - - {'invisible':[('state','=','draft')]} - - - - - - - - - - - - - - - - - - - - - - - - + + stock.inventory + + + + {'invisible': [('filter', '!=', 'products')]} - - + + + + + + + + + + + + + + + + + +