diff --git a/setup/stock_orderpoint_route/odoo/addons/stock_orderpoint_route b/setup/stock_orderpoint_route/odoo/addons/stock_orderpoint_route new file mode 120000 index 000000000..1dfad1a50 --- /dev/null +++ b/setup/stock_orderpoint_route/odoo/addons/stock_orderpoint_route @@ -0,0 +1 @@ +../../../../stock_orderpoint_route \ No newline at end of file diff --git a/setup/stock_orderpoint_route/setup.py b/setup/stock_orderpoint_route/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/stock_orderpoint_route/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_orderpoint_route/README.rst b/stock_orderpoint_route/README.rst new file mode 100644 index 000000000..5fb6cfbbe --- /dev/null +++ b/stock_orderpoint_route/README.rst @@ -0,0 +1,82 @@ +====================== +Stock Orderpoint Route +====================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-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/11.0/stock_orderpoint_route + :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-11-0/stock-logistics-warehouse-11-0-stock_orderpoint_route + :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/11.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to restrict a specific route to be used in the +reordering rules. This route will be used instead of the default determined by +default. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Go to the reordering rule and change the route. Only the routes applicable +for this location or locations above it can be selected. + +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 `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Eficent + +Contributors +~~~~~~~~~~~~ + +* Jordi Ballester Alomar +* Lois Rilo + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +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. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_orderpoint_route/__init__.py b/stock_orderpoint_route/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/stock_orderpoint_route/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_orderpoint_route/__manifest__.py b/stock_orderpoint_route/__manifest__.py new file mode 100644 index 000000000..c4e4224d7 --- /dev/null +++ b/stock_orderpoint_route/__manifest__.py @@ -0,0 +1,14 @@ +# Copyright 2019 Eficent Business and IT Consulting Services, S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +{ + "name": "Stock Orderpoint Route", + "summary": "Allows to force a route to be used when procuring from orderpoints", + "version": "13.0.1.0.0", + "license": "LGPL-3", + "website": "https://github.com/stock-logistics-warehouse", + "author": "Eficent, Camptocamp, Odoo Community Association (OCA)", + "category": "Warehouse", + "depends": ["stock"], + "data": ["views/stock_warehouse_orderpoint_views.xml"], + "installable": True, +} diff --git a/stock_orderpoint_route/models/__init__.py b/stock_orderpoint_route/models/__init__.py new file mode 100644 index 000000000..5024fb8c7 --- /dev/null +++ b/stock_orderpoint_route/models/__init__.py @@ -0,0 +1 @@ +from . import stock_warehouse_orderpoint diff --git a/stock_orderpoint_route/models/stock_warehouse_orderpoint.py b/stock_orderpoint_route/models/stock_warehouse_orderpoint.py new file mode 100644 index 000000000..ca857510a --- /dev/null +++ b/stock_orderpoint_route/models/stock_warehouse_orderpoint.py @@ -0,0 +1,53 @@ +# Copyright 2019 Eficent Business and IT Consulting Services, S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from odoo import api, fields, models + + +class StockWarehouseOrderpoint(models.Model): + _inherit = "stock.warehouse.orderpoint" + + route_ids = fields.Many2many( + "stock.location.route", string="Allowed routes", compute="_compute_route_ids" + ) + route_id = fields.Many2one( + "stock.location.route", + string="Route", + domain="[('id', 'in', route_ids)]", + ondelete="restrict", + ) + + @api.depends("product_id", "warehouse_id", "warehouse_id.route_ids", "location_id") + def _compute_route_ids(self): + route_obj = self.env["stock.location.route"] + for record in self: + wh_routes = record.warehouse_id.route_ids + routes = route_obj.browse() + if record.product_id: + routes += record.product_id.mapped( + "route_ids" + ) | record.product_id.mapped("categ_id").mapped("total_route_ids") + if record.warehouse_id: + routes |= wh_routes + parents = record.get_parents() + record.route_ids = self._get_location_routes_of_parents(routes, parents) + + def _get_location_routes_of_parents(self, routes, parents): + return routes.filtered( + lambda route: any( + p.location_id in parents + for p in route.rule_ids.filtered( + lambda rule: rule.action in ("pull", "pull_push") + ).mapped("location_src_id") + ) + ) + + def get_parents(self): + return self.env["stock.location"].search( + [("id", "parent_of", self.location_id.id)] + ) + + def _prepare_procurement_values(self, product_qty, date=False, group=False): + res = super()._prepare_procurement_values(product_qty, date=date, group=group) + res["route_ids"] = self.route_id + return res diff --git a/stock_orderpoint_route/readme/CONTRIBUTORS.rst b/stock_orderpoint_route/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..e62cfba22 --- /dev/null +++ b/stock_orderpoint_route/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* Jordi Ballester Alomar +* Lois Rilo +* Guewen Baconnier diff --git a/stock_orderpoint_route/readme/DESCRIPTION.rst b/stock_orderpoint_route/readme/DESCRIPTION.rst new file mode 100644 index 000000000..284647648 --- /dev/null +++ b/stock_orderpoint_route/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +This module allows to restrict a specific route to be used in the +reordering rules. This route will be used instead of the default determined by +default. diff --git a/stock_orderpoint_route/readme/USAGE.rst b/stock_orderpoint_route/readme/USAGE.rst new file mode 100644 index 000000000..eb30b124e --- /dev/null +++ b/stock_orderpoint_route/readme/USAGE.rst @@ -0,0 +1,2 @@ +Go to the reordering rule and change the route. Only the routes applicable +for this location or locations above it can be selected. diff --git a/stock_orderpoint_route/static/description/icon.png b/stock_orderpoint_route/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/stock_orderpoint_route/static/description/icon.png differ diff --git a/stock_orderpoint_route/static/description/index.html b/stock_orderpoint_route/static/description/index.html new file mode 100644 index 000000000..69e16318c --- /dev/null +++ b/stock_orderpoint_route/static/description/index.html @@ -0,0 +1,406 @@ + + + + + + +Stock Orderpoint Route + + + +
+

Stock Orderpoint Route

+ + +

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

+

This module allows to restrict a specific route to be used in the +reordering rules. This route will be used instead of the default determined by +default.

+

Table of contents

+ +
+

Usage

+

Go to the reordering rule and change the route. Only the routes applicable +for this location or locations above it can be selected.

+
+
+

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.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Eficent
  • +
+
+
+

Contributors

+ +
+
+

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.

+

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

+
+
+
+ + diff --git a/stock_orderpoint_route/tests/__init__.py b/stock_orderpoint_route/tests/__init__.py new file mode 100644 index 000000000..c2355dd9a --- /dev/null +++ b/stock_orderpoint_route/tests/__init__.py @@ -0,0 +1 @@ +from . import test_stock_orderpoint_route diff --git a/stock_orderpoint_route/tests/test_stock_orderpoint_route.py b/stock_orderpoint_route/tests/test_stock_orderpoint_route.py new file mode 100644 index 000000000..f0bde822c --- /dev/null +++ b/stock_orderpoint_route/tests/test_stock_orderpoint_route.py @@ -0,0 +1,177 @@ +# Copyright 2017 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl-3.0). + +from odoo.tests import common + + +class TestStockOrderpointRoute(common.SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + + # common models + cls.orderpoint_model = cls.env["stock.warehouse.orderpoint"] + cls.procurement_group_model = cls.env["procurement.group"] + # refs + cls.stock_manager_group = cls.env.ref("stock.group_stock_manager") + cls.stock_multi_locations_group_group = cls.env.ref( + "stock.group_stock_multi_locations" + ) + cls.main_company = cls.env.ref("base.main_company") + cls.warehouse = cls.env.ref("stock.warehouse0") + cls.categ_unit = cls.env.ref("uom.product_uom_categ_unit") + cls.virtual_loc = cls.env.ref("stock.stock_location_customers") + + # common data + cls.stock_manager = cls._create_user( + "stock_manager", + [cls.stock_manager_group.id, cls.stock_multi_locations_group_group.id], + [cls.main_company.id], + ) + cls.product = cls._create_product("SH", "Shoes", False) + + cls.ressuply_loc = cls.env["stock.location"].create( + {"name": "Ressuply", "location_id": cls.warehouse.view_location_id.id} + ) + + cls.ressuply_loc2 = cls.env["stock.location"].create( + {"name": "Ressuply2", "location_id": cls.warehouse.view_location_id.id} + ) + + cls.route = cls.env["stock.location.route"].create( + { + "name": "Transfer", + "product_categ_selectable": False, + "product_selectable": True, + "company_id": cls.main_company.id, + "sequence": 10, + } + ) + cls.route2 = cls.env["stock.location.route"].create( + { + "name": "Transfer", + "product_categ_selectable": False, + "product_selectable": True, + "company_id": cls.main_company.id, + "sequence": 10, + } + ) + + cls.uom_dozen = cls.env["uom.uom"].create( + { + "name": "Test-DozenA", + "category_id": cls.categ_unit.id, + "factor_inv": 12, + "uom_type": "bigger", + "rounding": 0.001, + } + ) + + cls.env["stock.rule"].create( + { + "name": "Transfer", + "route_id": cls.route.id, + "location_src_id": cls.ressuply_loc.id, + "location_id": cls.warehouse.lot_stock_id.id, + "action": "pull", + "picking_type_id": cls.warehouse.int_type_id.id, + "procure_method": "make_to_stock", + "warehouse_id": cls.warehouse.id, + "company_id": cls.main_company.id, + } + ) + + cls.env["stock.rule"].create( + { + "name": "Transfer 2", + "route_id": cls.route2.id, + "location_src_id": cls.ressuply_loc2.id, + "location_id": cls.warehouse.lot_stock_id.id, + "action": "pull", + "picking_type_id": cls.warehouse.int_type_id.id, + "procure_method": "make_to_stock", + "warehouse_id": cls.warehouse.id, + "company_id": cls.main_company.id, + } + ) + + @classmethod + def _create_user(cls, name, group_ids, company_ids): + return ( + cls.env["res.users"] + .with_context(no_reset_password=True) + .create( + { + "name": name, + "password": "demo", + "login": name, + "email": "@".join([name, "@test.com"]), + "groups_id": [(6, 0, group_ids)], + "company_ids": [(6, 0, company_ids)], + } + ) + ) + + @classmethod + def _create_product(cls, default_code, name, company_id, **vals): + return cls.env["product.product"].create( + dict( + name=name, + default_code=default_code, + uom_id=cls.env.ref("uom.product_uom_unit").id, + company_id=company_id, + type="product", + **vals + ) + ) + + def test_orderpoint_route_01(self): + self.product.route_ids = [(6, 0, [self.route.id, self.route2.id])] + vals = { + "product_id": self.product.id, + "product_min_qty": 10.0, + "product_max_qty": 100.0, + "company_id": self.main_company.id, + "warehouse_id": self.warehouse.id, + "location_id": self.warehouse.lot_stock_id.id, + } + + orderpoint = self.orderpoint_model.with_user(self.stock_manager).create(vals) + self.assertIn(self.route, orderpoint.route_ids) + self.assertIn(self.route2, orderpoint.route_ids) + orderpoint.route_id = self.route.id + self.procurement_group_model.run_scheduler() + move = self.env["stock.move"].search( + [ + ("product_id", "=", self.product.id), + ("location_id", "=", self.ressuply_loc.id), + ], + limit=1, + ) + self.assertEqual(len(move), 1) + + def test_orderpoint_route_02(self): + self.product.route_ids = [(6, 0, [self.route.id, self.route2.id])] + vals = { + "product_id": self.product.id, + "product_min_qty": 10.0, + "product_max_qty": 100.0, + "company_id": self.main_company.id, + "warehouse_id": self.warehouse.id, + "location_id": self.warehouse.lot_stock_id.id, + } + + orderpoint = self.orderpoint_model.with_user(self.stock_manager).create(vals) + self.assertIn(self.route, orderpoint.route_ids) + self.assertIn(self.route2, orderpoint.route_ids) + orderpoint.route_id = self.route2.id + self.procurement_group_model.run_scheduler() + move = self.env["stock.move"].search( + [ + ("product_id", "=", self.product.id), + ("location_id", "=", self.ressuply_loc2.id), + ], + limit=1, + ) + self.assertEqual(len(move), 1) diff --git a/stock_orderpoint_route/views/stock_warehouse_orderpoint_views.xml b/stock_orderpoint_route/views/stock_warehouse_orderpoint_views.xml new file mode 100644 index 000000000..2d7cb9748 --- /dev/null +++ b/stock_orderpoint_route/views/stock_warehouse_orderpoint_views.xml @@ -0,0 +1,20 @@ + + + + + stock.warehouse.orderpoint.form + stock.warehouse.orderpoint + + + + + + + + +