From 86278948400f28fbc17099836756a6ecfcac0c10 Mon Sep 17 00:00:00 2001 From: GabbasovDinar Date: Mon, 26 Aug 2024 17:49:56 +0500 Subject: [PATCH] [16.0][NEW] stock_picking_notify: Add new module to notify user after change picking states --- .../odoo/addons/stock_picking_notify | 1 + setup/stock_picking_notify/setup.py | 6 + stock_picking_notify/README.rst | 133 ++++++++++++++++++ stock_picking_notify/__init__.py | 1 + stock_picking_notify/__manifest__.py | 21 +++ ...ock_picking_notification_template_demo.xml | 12 ++ stock_picking_notify/models/__init__.py | 2 + stock_picking_notify/models/stock_picking.py | 28 ++++ .../stock_picking_notification_template.py | 115 +++++++++++++++ stock_picking_notify/readme/CONFIGURE.md | 15 ++ stock_picking_notify/readme/CONTEXT.md | 3 + stock_picking_notify/readme/DESCRIPTION.md | 3 + stock_picking_notify/readme/USAGE.md | 5 + .../security/ir.model.access.csv | 3 + .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 8 ++ stock_picking_notify/tests/__init__.py | 1 + .../tests/test_notification_template.py | 75 ++++++++++ stock_picking_notify/views/menuitems.xml | 12 ++ ...ock_picking_notification_template_view.xml | 98 +++++++++++++ 20 files changed, 542 insertions(+) create mode 120000 setup/stock_picking_notify/odoo/addons/stock_picking_notify create mode 100644 setup/stock_picking_notify/setup.py create mode 100644 stock_picking_notify/README.rst create mode 100644 stock_picking_notify/__init__.py create mode 100644 stock_picking_notify/__manifest__.py create mode 100644 stock_picking_notify/demo/stock_picking_notification_template_demo.xml create mode 100644 stock_picking_notify/models/__init__.py create mode 100644 stock_picking_notify/models/stock_picking.py create mode 100644 stock_picking_notify/models/stock_picking_notification_template.py create mode 100644 stock_picking_notify/readme/CONFIGURE.md create mode 100644 stock_picking_notify/readme/CONTEXT.md create mode 100644 stock_picking_notify/readme/DESCRIPTION.md create mode 100644 stock_picking_notify/readme/USAGE.md create mode 100644 stock_picking_notify/security/ir.model.access.csv create mode 100644 stock_picking_notify/static/description/icon.png create mode 100644 stock_picking_notify/static/description/index.html create mode 100644 stock_picking_notify/tests/__init__.py create mode 100644 stock_picking_notify/tests/test_notification_template.py create mode 100644 stock_picking_notify/views/menuitems.xml create mode 100644 stock_picking_notify/views/stock_picking_notification_template_view.xml diff --git a/setup/stock_picking_notify/odoo/addons/stock_picking_notify b/setup/stock_picking_notify/odoo/addons/stock_picking_notify new file mode 120000 index 000000000000..5cc9519b4d79 --- /dev/null +++ b/setup/stock_picking_notify/odoo/addons/stock_picking_notify @@ -0,0 +1 @@ +../../../../stock_picking_notify \ No newline at end of file diff --git a/setup/stock_picking_notify/setup.py b/setup/stock_picking_notify/setup.py new file mode 100644 index 000000000000..28c57bb64031 --- /dev/null +++ b/setup/stock_picking_notify/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_picking_notify/README.rst b/stock_picking_notify/README.rst new file mode 100644 index 000000000000..6c3033fa92b2 --- /dev/null +++ b/stock_picking_notify/README.rst @@ -0,0 +1,133 @@ +========================== +Notify Users about Picking +========================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:584e6579cf1f7315aeb3162a9b846dc43e8cb3d7a0f432b774fb822db18f43e1 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-AGPL--3-blue.png + :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--workflow-lightgray.png?logo=github + :target: https://github.com/OCA/stock-logistics-workflow/tree/16.0/stock_picking_notify + :alt: OCA/stock-logistics-workflow +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/stock-logistics-workflow-16-0/stock-logistics-workflow-16-0-stock_picking_notify + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/stock-logistics-workflow&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to send notifications to selected backend users when +the state of stock picking changes . For this configurable notification +rules are used. Rules are checked using flexible patterns. The first one +rule matching the pattern is triggered. + +By default notifications are sent when a picking is set to the "Waiting" +or "Ready" status. However you can enable notifications for other +pickings too. + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +Imagine you have a warehouse that is serving a retail store and online +shop. + +Each time a sale is done in the retail shop you would like to process +that order immediately. For this you need to notify selected employees +about such order so that they could switch to it **asap**. + +Configuration +============= + +To configure notification rules: + +- Go to + ``"Inventory -> Configuration -> Warehouse Management -> New Picking Notifications"`` + and create a new notification +- Put the following data in the notification form: + + - ``Priority``. Defines the order in which notification rules are + checked. The lower is the number the higher is the priority. + Default is ``10`` + - ``Operation Types``, required. Specifies the operation types for + which this notification will be triggered + - ``Notified Users``, required. List of users to be notified + - ``Source Document Pattern``, optional. Notification will be sent + if source document name matches the specified regex pattern. Leave + blank to match any source + - ``Custom Message``, optional. Custom message to be used instead of + the default one. Leave blank to use the default message + - ``Allow Notify DRAFT`` - optional. If enabled, notifications will + be sent when a picking is in the "Draft" state + - ``Allow Notify WAITING ANOTHER OPERATION`` - optional. If enabled, + notifications will be sent when a picking is in the "Waiting + Another Operation" state + - ``Allow notify WAITIN`` - optional. If enabled, notifications will + be sent when a picking is in the "WAITING" state + - ``Allow notify READY`` - optional. If enabled, notifications will + be sent when a picking is in the "READY" state. + - ``Allow notify DONE`` - optional. If enabled, notifications will + be sent when a picking is in the "Done" state. + - ``Allow notify CANCEL`` - optional. If enabled, notifications will + be sent when a picking is in the "Cancelled" state + +Usage +===== + +When a new picking is created and this picking matches some notification +rule a notification will be shown for selected users: + +- Document number in the notification title +- Message text in the notification body +- "Open document" button. Click it to open the related document + +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 to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Cetmix + +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-workflow `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_picking_notify/__init__.py b/stock_picking_notify/__init__.py new file mode 100644 index 000000000000..0650744f6bc6 --- /dev/null +++ b/stock_picking_notify/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_picking_notify/__manifest__.py b/stock_picking_notify/__manifest__.py new file mode 100644 index 000000000000..aac81a99edbc --- /dev/null +++ b/stock_picking_notify/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright (C) 2024 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ + "name": "Notify Users about Picking", + "version": "16.0.1.0.0", + "category": "Inventory/Inventory", + "summary": "Notify selected internal users of changes in picking states", + "depends": ["stock", "web_notify"], + "website": "https://github.com/OCA/stock-logistics-workflow", + "author": "Cetmix, Odoo Community Association (OCA)", + "installable": True, + "data": [ + "security/ir.model.access.csv", + "views/stock_picking_notification_template_view.xml", + "views/menuitems.xml", + ], + "demo": [ + "demo/stock_picking_notification_template_demo.xml", + ], + "license": "AGPL-3", +} diff --git a/stock_picking_notify/demo/stock_picking_notification_template_demo.xml b/stock_picking_notify/demo/stock_picking_notification_template_demo.xml new file mode 100644 index 000000000000..b3afd61a40e3 --- /dev/null +++ b/stock_picking_notify/demo/stock_picking_notification_template_demo.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/stock_picking_notify/models/__init__.py b/stock_picking_notify/models/__init__.py new file mode 100644 index 000000000000..8d84e3bdad68 --- /dev/null +++ b/stock_picking_notify/models/__init__.py @@ -0,0 +1,2 @@ +from . import stock_picking +from . import stock_picking_notification_template diff --git a/stock_picking_notify/models/stock_picking.py b/stock_picking_notify/models/stock_picking.py new file mode 100644 index 000000000000..2b791853eda0 --- /dev/null +++ b/stock_picking_notify/models/stock_picking.py @@ -0,0 +1,28 @@ +# Copyright (C) 2024 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class StockPicking(models.Model): + _inherit = "stock.picking" + + def _write(self, vals): + # We use the _write method instead of write because _write is a low-level implementation + # that bypasses certain restrictions related to computed fields. In this case, it is + # crucial to ensure that notifications are triggered correctly when the state of the + # picking changes, even if this field (state) was computed by dependent fields + res = super()._write(vals) + if "state" in vals: + self.sudo()._trigger_picking_notification() + return res + + def _trigger_picking_notification(self): + """ + Check notification rules and trigger notifications if conditions are met. + """ + notify_template_obj = self.env["stock.picking.notification.template"] + for picking in self: + template = notify_template_obj._get_matching_template(picking) + if template: + template._notify_picking_users(picking) diff --git a/stock_picking_notify/models/stock_picking_notification_template.py b/stock_picking_notify/models/stock_picking_notification_template.py new file mode 100644 index 000000000000..ef48057c6195 --- /dev/null +++ b/stock_picking_notify/models/stock_picking_notification_template.py @@ -0,0 +1,115 @@ +# Copyright (C) 2024 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import re + +from odoo import _, fields, models + + +class StockPickingNotificationTemplate(models.Model): + _name = "stock.picking.notification.template" + _description = "Picking Notification Template" + _order = "sequence, id" + _rec_name = "picking_type_id" + + sequence = fields.Integer( + default=10, + string="Priority", + ) + active = fields.Boolean( + default=True, + ) + picking_type_id = fields.Many2one( + comodel_name="stock.picking.type", + string="Operation Type", + required=True, + help="Operation types for which the notification will be triggered", + ) + user_ids = fields.Many2many( + string="Notified Users", + comodel_name="res.users", + relation="picking_notify_template_user_rel", + column1="picking_notify_template_id", + column2="user_id", + required=True, + ) + source_document_regex = fields.Char( + string="Source Document Pattern", + help="Notification will be sent if source document name matches " + "the specified regex pattern. Leave blank to match any source", + ) + allow_notify_draft = fields.Boolean( + string="Allow Notify DRAFT", + default=False, + ) + allow_notify_waiting = fields.Boolean( + string="Allow Notify WAITING ANOTHER OPERATION", + default=False, + ) + allow_notify_confirmed = fields.Boolean( + string="Allow notify WAITING", + default=True, + ) + allow_notify_assigned = fields.Boolean( + string="Allow notify READY", + default=True, + ) + allow_notify_done = fields.Boolean( + string="Allow notify DONE", + default=False, + ) + allow_notify_cancel = fields.Boolean( + string="Allow notify CANCEL", + default=False, + ) + message = fields.Text( + string="Custom Message", + help="Custom message to be used instead of the default one. " + "Leave blank to use the default message", + ) + + def _get_matching_template(self, picking): + """ + Return the first matching notification template for the given picking. + """ + templates = self.search( + [ + ("picking_type_id", "=", picking.picking_type_id.id), + (f"allow_notify_{picking.state}", "=", True), + ], + order="sequence ASC", + ) + for template in templates: + if template._matches_picking(picking): + return template + return None + + def _matches_picking(self, picking): + """ + Check if the picking matches this notification template. + """ + if self.source_document_regex and not re.match( + self.source_document_regex, picking.origin or "" + ): + return False + + return True + + def _notify_picking_users(self, picking): + """ + Send notifications to the users about the picking. + """ + self.ensure_one() + return self.user_ids.sudo().notify_info( + message=self.message or _("New picking created."), + title=picking.name, + sticky=True, + action={ + "type": "ir.actions.act_window", + "name": picking.name, + "res_model": "stock.picking", + "res_id": picking.id, + "view_mode": "form", + "view_type": "form", + "target": "current", + }, + ) diff --git a/stock_picking_notify/readme/CONFIGURE.md b/stock_picking_notify/readme/CONFIGURE.md new file mode 100644 index 000000000000..52d8b451338b --- /dev/null +++ b/stock_picking_notify/readme/CONFIGURE.md @@ -0,0 +1,15 @@ +To configure notification rules: + +* Go to `"Inventory -> Configuration -> Warehouse Management -> New Picking Notifications"` and create a new notification +* Put the following data in the notification form: + * `Priority`. Defines the order in which notification rules are checked. The lower is the number the higher is the priority. Default is `10` + * `Operation Types`, required. Specifies the operation types for which this notification will be triggered + * `Notified Users`, required. List of users to be notified + * `Source Document Pattern`, optional. Notification will be sent if source document name matches the specified regex pattern. Leave blank to match any source + * `Custom Message`, optional. Custom message to be used instead of the default one. Leave blank to use the default message + * `Allow Notify DRAFT` - optional. If enabled, notifications will be sent when a picking is in the "Draft" state + * `Allow Notify WAITING ANOTHER OPERATION` - optional. If enabled, notifications will be sent when a picking is in the "Waiting Another Operation" state + * `Allow notify WAITIN` - optional. If enabled, notifications will be sent when a picking is in the "WAITING" state + * `Allow notify READY` - optional. If enabled, notifications will be sent when a picking is in the "READY" state. + * `Allow notify DONE` - optional. If enabled, notifications will be sent when a picking is in the "Done" state. + * `Allow notify CANCEL` - optional. If enabled, notifications will be sent when a picking is in the "Cancelled" state diff --git a/stock_picking_notify/readme/CONTEXT.md b/stock_picking_notify/readme/CONTEXT.md new file mode 100644 index 000000000000..997226505f75 --- /dev/null +++ b/stock_picking_notify/readme/CONTEXT.md @@ -0,0 +1,3 @@ +Imagine you have a warehouse that is serving a retail store and online shop. + +Each time a sale is done in the retail shop you would like to process that order immediately. For this you need to notify selected employees about such order so that they could switch to it **asap**. diff --git a/stock_picking_notify/readme/DESCRIPTION.md b/stock_picking_notify/readme/DESCRIPTION.md new file mode 100644 index 000000000000..972af935a674 --- /dev/null +++ b/stock_picking_notify/readme/DESCRIPTION.md @@ -0,0 +1,3 @@ +This module allows to send notifications to selected backend users when the state of stock picking changes . For this configurable notification rules are used. Rules are checked using flexible patterns. The first one rule matching the pattern is triggered. + +By default notifications are sent when a picking is set to the "Waiting" or "Ready" status. However you can enable notifications for other pickings too. diff --git a/stock_picking_notify/readme/USAGE.md b/stock_picking_notify/readme/USAGE.md new file mode 100644 index 000000000000..aadc2fea4812 --- /dev/null +++ b/stock_picking_notify/readme/USAGE.md @@ -0,0 +1,5 @@ +When a new picking is created and this picking matches some notification rule a notification will be shown for selected users: + +* Document number in the notification title +* Message text in the notification body +* "Open document" button. Click it to open the related document diff --git a/stock_picking_notify/security/ir.model.access.csv b/stock_picking_notify/security/ir.model.access.csv new file mode 100644 index 000000000000..a68825efe76a --- /dev/null +++ b/stock_picking_notify/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_picking_notification_template_group_stock_user,access_stock_picking_notify group_stock_user,model_stock_picking_notification_template,stock.group_stock_user,1,0,0,0 +access_picking_notification_template_group_stock_manager,access_stock_picking_notify group_stock_manager,model_stock_picking_notification_template,stock.group_stock_manager,1,1,1,1 diff --git a/stock_picking_notify/static/description/icon.png b/stock_picking_notify/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/stock_picking_notify/static/description/index.html b/stock_picking_notify/static/description/index.html new file mode 100644 index 000000000000..0d8054a7a6e2 --- /dev/null +++ b/stock_picking_notify/static/description/index.html @@ -0,0 +1,8 @@ +
+
+
+

Notify Users about Picking

+

Notify selected internal users of changes in picking states.

+
+
+
diff --git a/stock_picking_notify/tests/__init__.py b/stock_picking_notify/tests/__init__.py new file mode 100644 index 000000000000..e6a82265126e --- /dev/null +++ b/stock_picking_notify/tests/__init__.py @@ -0,0 +1 @@ +from . import test_notification_template diff --git a/stock_picking_notify/tests/test_notification_template.py b/stock_picking_notify/tests/test_notification_template.py new file mode 100644 index 000000000000..6aa244d6f88b --- /dev/null +++ b/stock_picking_notify/tests/test_notification_template.py @@ -0,0 +1,75 @@ +# Copyright (C) 2024 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import json + +from odoo.tests import new_test_user +from odoo.tests.common import TransactionCase + + +class TestStockNotifyPicking(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + cls.picking_type = cls.env["stock.picking.type"].create( + { + "name": "Test Operation Type", + "sequence_code": "test", + "code": "incoming", + } + ) + cls.user = new_test_user( + cls.env, "jon", email="json.snow@westeros.test", notification_type="inbox" + ) + cls.notification_template = cls.env[ + "stock.picking.notification.template" + ].create( + {"picking_type_id": cls.picking_type.id, "user_ids": [(6, 0, cls.user.ids)]} + ) + cls.stock_location = cls.env.ref("stock.stock_location_stock") + cls.product = cls.env["product.product"].create( + { + "name": "Product", + } + ) + + def test_picking_send_notification(self): + """ + Send notification for draft picking + """ + self.notification_template.write( + { + "allow_notify_draft": True, + "allow_notify_confirmed": True, + } + ) + bus_obj = self.env["bus.bus"] + domain = [("channel", "=", self.user.notify_info_channel_name)] + existing = bus_obj.search(domain) + + picking = self.env["stock.picking"].create( + { + "location_id": self.stock_location.id, + "location_dest_id": self.stock_location.id, + "picking_type_id": self.picking_type.id, + } + ) + self.env["stock.move"].create( + { + "name": "test", + "location_id": self.stock_location.id, + "location_dest_id": self.stock_location.id, + "product_id": self.product.id, + "product_uom_qty": 1.0, + "picking_id": picking.id, + "picking_type_id": self.picking_type.id, + } + ) + picking.action_confirm() + picking.flush_model() + news = bus_obj.search(domain) - existing + self.assertEqual(1, len(news)) + payload = json.loads(news.message)["payload"][0] + self.assertEqual(payload["title"], picking.name) + self.assertEqual(payload["action"]["res_id"], picking.id) diff --git a/stock_picking_notify/views/menuitems.xml b/stock_picking_notify/views/menuitems.xml new file mode 100644 index 000000000000..4666743d76c2 --- /dev/null +++ b/stock_picking_notify/views/menuitems.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/stock_picking_notify/views/stock_picking_notification_template_view.xml b/stock_picking_notify/views/stock_picking_notification_template_view.xml new file mode 100644 index 000000000000..3c51d4631ed6 --- /dev/null +++ b/stock_picking_notify/views/stock_picking_notification_template_view.xml @@ -0,0 +1,98 @@ + + + + + stock.picking.notification.template.view.form + stock.picking.notification.template + +
+ + + + + + + + + + + + + + + + + + + +
+
+
+ + + stock.picking.notification.template.view.tree + stock.picking.notification.template + + + + + + + + + + + + + + + + + + Picking Notifications + stock.picking.notification.template + ir.actions.act_window + tree,form + +

+ No template found. Let's create one! +

+
+
+