diff --git a/addons/cx_web_refresh_from_backend/README.rst b/addons/cx_web_refresh_from_backend/README.rst deleted file mode 100644 index 6e10842..0000000 --- a/addons/cx_web_refresh_from_backend/README.rst +++ /dev/null @@ -1,127 +0,0 @@ -======================== -Web Refresh From Backend -======================== - -.. - !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! This file is generated by oca-gen-addon-readme !! - !! changes will be overwritten. !! - !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:e841ff66d3bfff0a3de22c9be5dc40f1ca539739f5487a9162fdf887fc5ac6ad - !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - -.. |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/license-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-cetmix%2Fcetmix--tower-lightgray.png?logo=github - :target: https://github.com/cetmix/cetmix-tower/tree/16.0/cx_web_refresh_from_backend - :alt: cetmix/cetmix-tower - -|badge1| |badge2| |badge3| - -Backend UI Reload Module -======================== - -This is a **technical module** that allows triggering a **UI reload** -from the backend. It enables triggering the reload action for selected -users and record IDs. - --------------- - -🔧 Helper Function: ``reload_views`` ------------------------------------- - -A special helper function ``reload_views`` is added to the ``res.users`` -model. - -**Arguments** -~~~~~~~~~~~~~ - -+----------------+--------------------------+--------------------------+ -| Argument | Type | Description | -+================+==========================+==========================+ -| **model** | ``Char`` | Model name, e.g. | -| | | ``'res.partner'`` | -+----------------+--------------------------+--------------------------+ -| **view_types** | ``List of Char`` | View types to reload, | -| | *(optional)* | e.g. | -| | | ``["form", "kanban"]``. | -| | | Leave blank to reload | -| | | all views. | -+----------------+--------------------------+--------------------------+ -| **rec_ids** | ``List of Integer`` | The view will be | -| | *(optional)* | reloaded only if a | -| | | record with an ID from | -| | | this list is present in | -| | | the view. | -+----------------+--------------------------+--------------------------+ - --------------- - -⚠️ Important Notes ------------------- - -Use this function **wisely**. - -When reloading **form views**, be aware that if a user is currently -editing a record, **their unsaved updates may be lost**. - -**Table of contents** - -.. contents:: - :local: - -Usage -===== - -🧩 Example Usage ----------------- - -Below is a code snippet showing how to use the ``reload_views`` helper -function. - -.. code:: python - - # Reload the kanban and form views for all salespeople when an opportunity is won - # Will reload views only if the current opportunity is being displayed - - group_id = self.env.ref("sales_team.group_sale_salesman").id - users_to_reload = self.env["res.users"].search([("groups_id", "in", [group_id])]) - users_to_reload.reload_views( - model="crm.lead", - view_types=["kanban", "form"], - rec_ids=[self.id], - ) - -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 - -Contributors ------------- - -- Cetmix - -Maintainers ------------ - -This module is part of the `cetmix/cetmix-tower `_ project on GitHub. - -You are welcome to contribute. diff --git a/addons/cx_web_refresh_from_backend/__init__.py b/addons/cx_web_refresh_from_backend/__init__.py deleted file mode 100644 index 961cb7d..0000000 --- a/addons/cx_web_refresh_from_backend/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright 2025 Cetmix OÜ -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). - -from . import models diff --git a/addons/cx_web_refresh_from_backend/__manifest__.py b/addons/cx_web_refresh_from_backend/__manifest__.py deleted file mode 100644 index 234a507..0000000 --- a/addons/cx_web_refresh_from_backend/__manifest__.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2025 Cetmix OÜ -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). - -# Mail is required: its ir.websocket override subscribes the partner channel to the -# bus, so users receive web.refresh_view notifications. - -{ - "name": "Web Refresh From Backend", - "summary": "Refresh frontend views from backend", - "version": "16.0.1.0.0", - "category": "Web", - "license": "LGPL-3", - "author": "Cetmix", - "website": "https://tower.cetmix.com", - "images": ["static/description/banner.png"], - "depends": ["mail"], - "assets": { - "web.assets_backend": [ - "cx_web_refresh_from_backend/static/src/views/list/list_controller_patch.esm.js", - "cx_web_refresh_from_backend/static/src/views/kanban/kanban_controller_patch.esm.js", - "cx_web_refresh_from_backend/static/src/views/form/form_controller_patch.esm.js", - ], - }, - "installable": True, - "auto_install": False, -} diff --git a/addons/cx_web_refresh_from_backend/i18n/cx_web_refresh_from_backend.pot b/addons/cx_web_refresh_from_backend/i18n/cx_web_refresh_from_backend.pot deleted file mode 100644 index 463587a..0000000 --- a/addons/cx_web_refresh_from_backend/i18n/cx_web_refresh_from_backend.pot +++ /dev/null @@ -1,97 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * cx_web_refresh_from_backend -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 16.0\n" -"Report-Msgid-Bugs-To: \n" -"Last-Translator: \n" -"Language-Team: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: \n" - -#. module: cx_web_refresh_from_backend -#. odoo-javascript -#: code:addons/cx_web_refresh_from_backend/static/src/views/form/form_controller_patch.esm.js:0 -#, python-format -msgid "All unsaved changes will be lost! Continue?" -msgstr "" - -#. module: cx_web_refresh_from_backend -#. odoo-javascript -#: code:addons/cx_web_refresh_from_backend/static/src/views/form/form_controller_patch.esm.js:0 -#: code:addons/cx_web_refresh_from_backend/static/src/views/list/list_controller_patch.esm.js:0 -#, python-format -msgid "Cancel" -msgstr "" - -#. module: cx_web_refresh_from_backend -#. odoo-javascript -#: code:addons/cx_web_refresh_from_backend/static/src/views/form/form_controller_patch.esm.js:0 -#, python-format -msgid "Continue" -msgstr "" - -#. module: cx_web_refresh_from_backend -#. odoo-javascript -#: code:addons/cx_web_refresh_from_backend/static/src/views/form/form_controller_patch.esm.js:0 -#, python-format -msgid "Could not reload form. " -msgstr "" - -#. module: cx_web_refresh_from_backend -#. odoo-javascript -#: code:addons/cx_web_refresh_from_backend/static/src/views/kanban/kanban_controller_patch.esm.js:0 -#, python-format -msgid "Could not reload kanban. " -msgstr "" - -#. module: cx_web_refresh_from_backend -#. odoo-javascript -#: code:addons/cx_web_refresh_from_backend/static/src/views/list/list_controller_patch.esm.js:0 -#, python-format -msgid "Could not reload list. " -msgstr "" - -#. module: cx_web_refresh_from_backend -#. odoo-javascript -#: code:addons/cx_web_refresh_from_backend/static/src/views/list/list_controller_patch.esm.js:0 -#, python-format -msgid "Could not save record. " -msgstr "" - -#. module: cx_web_refresh_from_backend -#. odoo-javascript -#: code:addons/cx_web_refresh_from_backend/static/src/views/form/form_controller_patch.esm.js:0 -#, python-format -msgid "Form is being refreshed from backend" -msgstr "" - -#. module: cx_web_refresh_from_backend -#. odoo-javascript -#: code:addons/cx_web_refresh_from_backend/static/src/views/list/list_controller_patch.esm.js:0 -#, python-format -msgid "List is being refreshed from backend" -msgstr "" - -#. module: cx_web_refresh_from_backend -#. odoo-javascript -#: code:addons/cx_web_refresh_from_backend/static/src/views/list/list_controller_patch.esm.js:0 -#, python-format -msgid "Save & Refresh" -msgstr "" - -#. module: cx_web_refresh_from_backend -#: model:ir.model,name:cx_web_refresh_from_backend.model_res_users -msgid "User" -msgstr "" - -#. module: cx_web_refresh_from_backend -#. odoo-javascript -#: code:addons/cx_web_refresh_from_backend/static/src/views/list/list_controller_patch.esm.js:0 -#, python-format -msgid "You have unsaved edits. Save them before refreshing?" -msgstr "" diff --git a/addons/cx_web_refresh_from_backend/models/__init__.py b/addons/cx_web_refresh_from_backend/models/__init__.py deleted file mode 100644 index 4bf0702..0000000 --- a/addons/cx_web_refresh_from_backend/models/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright 2025 Cetmix OÜ -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). - -from . import res_users diff --git a/addons/cx_web_refresh_from_backend/models/res_users.py b/addons/cx_web_refresh_from_backend/models/res_users.py deleted file mode 100644 index 0c489aa..0000000 --- a/addons/cx_web_refresh_from_backend/models/res_users.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright 2025 Cetmix OÜ -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). - -from odoo import models - - -class ResUsers(models.Model): - _inherit = "res.users" - - def reload_views(self, model, view_types=None, rec_ids=None): - """ - Trigger UI reload for selected users and record IDs. - - This method allows to reload specific views from the backend. - Be aware that when reloading form views, if a user is currently - doing some updates, those updates may be lost. - - :param model: str, Model name (e.g., 'res.partner') - :param view_types: list of str, optional, View types to reload - (e.g., ['form', 'kanban']). Leave blank to reload all views. - :param rec_ids: list of int, optional, View will be reloaded only if a record - with id from the list is present in the view. - - Example usage: - # Reload the kanban and form views for all salespeople - # when an opportunity is won. - # Will reload views only if the current opportunity is being displayed - group_id = self.env.ref("sales_team.group_sale_salesman").id - users_to_reload = self.env["res.users"].search( - [("groups_id", "in", [group_id])] - ) - users_to_reload.reload_views( - model="crm.lead", - view_types=["kanban", "form"], - rec_ids=[self.id] - ) - """ - - # Prepare the message payload - bus_message = { - "model": model, - "view_types": view_types or [], - "rec_ids": rec_ids or [], - } - - # Send notification to each user's partner - notifications = [ - [user.partner_id, "web.refresh_view", bus_message] for user in self - ] - self.env["bus.bus"]._sendmany(notifications) diff --git a/addons/cx_web_refresh_from_backend/pyproject.toml b/addons/cx_web_refresh_from_backend/pyproject.toml deleted file mode 100644 index 4231d0c..0000000 --- a/addons/cx_web_refresh_from_backend/pyproject.toml +++ /dev/null @@ -1,3 +0,0 @@ -[build-system] -requires = ["whool"] -build-backend = "whool.buildapi" diff --git a/addons/cx_web_refresh_from_backend/readme/CONTRIBUTORS.md b/addons/cx_web_refresh_from_backend/readme/CONTRIBUTORS.md deleted file mode 100644 index c2b5180..0000000 --- a/addons/cx_web_refresh_from_backend/readme/CONTRIBUTORS.md +++ /dev/null @@ -1,2 +0,0 @@ -* Cetmix - diff --git a/addons/cx_web_refresh_from_backend/readme/DESCRIPTION.md b/addons/cx_web_refresh_from_backend/readme/DESCRIPTION.md deleted file mode 100644 index 896a4d6..0000000 --- a/addons/cx_web_refresh_from_backend/readme/DESCRIPTION.md +++ /dev/null @@ -1,27 +0,0 @@ -# Backend UI Reload Module - -This is a **technical module** that allows triggering a **UI reload** from the backend. -It enables triggering the reload action for selected users and record IDs. - ---- - -## 🔧 Helper Function: `reload_views` - -A special helper function `reload_views` is added to the `res.users` model. - -### **Arguments** - -| Argument | Type | Description | -|-----------|------|-------------| -| **model** | `Char` | Model name, e.g. `'res.partner'` | -| **view_types** | `List of Char` *(optional)* | View types to reload, e.g. `["form", "kanban"]`. Leave blank to reload all views. | -| **rec_ids** | `List of Integer` *(optional)* | The view will be reloaded only if a record with an ID from this list is present in the view. | - ---- - -## ⚠️ Important Notes - -Use this function **wisely**. - -When reloading **form views**, be aware that if a user is currently editing a record, -**their unsaved updates may be lost**. diff --git a/addons/cx_web_refresh_from_backend/readme/USAGE.md b/addons/cx_web_refresh_from_backend/readme/USAGE.md deleted file mode 100644 index 9950c5f..0000000 --- a/addons/cx_web_refresh_from_backend/readme/USAGE.md +++ /dev/null @@ -1,16 +0,0 @@ -## 🧩 Example Usage - -Below is a code snippet showing how to use the `reload_views` helper function. - -```python -# Reload the kanban and form views for all salespeople when an opportunity is won -# Will reload views only if the current opportunity is being displayed - -group_id = self.env.ref("sales_team.group_sale_salesman").id -users_to_reload = self.env["res.users"].search([("groups_id", "in", [group_id])]) -users_to_reload.reload_views( - model="crm.lead", - view_types=["kanban", "form"], - rec_ids=[self.id], -) -``` diff --git a/addons/cx_web_refresh_from_backend/static/description/banner.png b/addons/cx_web_refresh_from_backend/static/description/banner.png deleted file mode 100644 index b655285..0000000 Binary files a/addons/cx_web_refresh_from_backend/static/description/banner.png and /dev/null differ diff --git a/addons/cx_web_refresh_from_backend/static/description/icon.png b/addons/cx_web_refresh_from_backend/static/description/icon.png deleted file mode 100644 index 3963e67..0000000 Binary files a/addons/cx_web_refresh_from_backend/static/description/icon.png and /dev/null differ diff --git a/addons/cx_web_refresh_from_backend/static/description/index.html b/addons/cx_web_refresh_from_backend/static/description/index.html deleted file mode 100644 index 302a2a9..0000000 --- a/addons/cx_web_refresh_from_backend/static/description/index.html +++ /dev/null @@ -1,484 +0,0 @@ - - - - - -Web Refresh From Backend - - - -
-

Web Refresh From Backend

- - -

Beta License: LGPL-3 cetmix/cetmix-tower

-
-

Backend UI Reload Module

-

This is a technical module that allows triggering a UI reload -from the backend. It enables triggering the reload action for selected -users and record IDs.

-
-
-

🔧 Helper Function: reload_views

-

A special helper function reload_views is added to the res.users -model.

-
-

Arguments

- ----- - - - - - - - - - - - - - - - - - - - - -
ArgumentTypeDescription
modelCharModel name, e.g. -'res.partner'
view_typesList of Char -(optional)View types to reload, -e.g. -["form", "kanban"]. -Leave blank to reload -all views.
rec_idsList of Integer -(optional)The view will be -reloaded only if a -record with an ID from -this list is present in -the view.
-
-
-
-
-

⚠️ Important Notes

-

Use this function wisely.

-

When reloading form views, be aware that if a user is currently -editing a record, their unsaved updates may be lost.

-

Table of contents

-
-
-
-

Usage

-
-

🧩 Example Usage

-

Below is a code snippet showing how to use the reload_views helper -function.

-
-# Reload the kanban and form views for all salespeople when an opportunity is won
-# Will reload views only if the current opportunity is being displayed
-
-group_id = self.env.ref("sales_team.group_sale_salesman").id
-users_to_reload = self.env["res.users"].search([("groups_id", "in", [group_id])])
-users_to_reload.reload_views(
-    model="crm.lead",
-    view_types=["kanban", "form"],
-    rec_ids=[self.id],
-)
-
-
-
-
-

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
  • -
-
-
-

Contributors

-
    -
  • Cetmix
  • -
-
-
-

Maintainers

-

This module is part of the cetmix/cetmix-tower project on GitHub.

-

You are welcome to contribute.

-
-
-
- - diff --git a/addons/cx_web_refresh_from_backend/static/src/views/form/form_controller_patch.esm.js b/addons/cx_web_refresh_from_backend/static/src/views/form/form_controller_patch.esm.js deleted file mode 100644 index e33c4ae..0000000 --- a/addons/cx_web_refresh_from_backend/static/src/views/form/form_controller_patch.esm.js +++ /dev/null @@ -1,181 +0,0 @@ -/** @odoo-module **/ - -import {FormController} from "@web/views/form/form_controller"; -import {patch} from "@web/core/utils/patch"; -import {useService} from "@web/core/utils/hooks"; -import {onWillUnmount} from "@odoo/owl"; -import {ConfirmationDialog} from "@web/core/confirmation_dialog/confirmation_dialog"; - -// Patch the standard FormController to react on bus notifications -patch(FormController.prototype, "cx_web_refresh_from_backend.FormController", { - setup() { - // Call original setup logic - this._super(...arguments); - - // Get core services used by this behavior - this.busService = useService("bus_service"); - this.actionService = useService("action"); - this.notificationService = useService("notification"); - - // Timestamp of last local save (used to avoid immediate auto-refresh) - this._lastLocalSave = null; - - // Bind the handler to keep reference for cleanup - this._boundBusHandler = this._onBusNotification.bind(this); - - // Subscribe to bus notifications - this.busService.addEventListener("notification", this._boundBusHandler); - - // Cleanup subscription on component unmount - onWillUnmount(() => { - if (this.busService && this._boundBusHandler) { - this.busService.removeEventListener( - "notification", - this._boundBusHandler - ); - } - }); - }, - - /** - * Handle bus notification for view refresh. - * Listens for notifications with type "web.refresh_view" and delegates - * processing to _handleViewRefresh. - * - * @param {Event} event - Bus notification event - */ - async _onBusNotification({detail: notifications}) { - // Check if component is still alive - if (!this.model || !this.model.root) { - return; - } - - for (const {payload, type} of notifications) { - if (type === "web.refresh_view") { - await this._handleViewRefresh(payload); - } - } - }, - - /** - * Handle view refresh notification. - * - * Only refreshes when: - * - model matches current form model - * - requested view types include "form" (if specified) - * - record id matches current record (if specified) - * - * @param {Object} notification - Notification payload - */ - async _handleViewRefresh(notification) { - const {model, view_types = [], rec_ids = []} = notification; - - // Check if the model matches current form model - if (this.props.resModel !== model) { - return; - } - - // Check if view_type matches (if specified in notification) - if (view_types.length > 0 && !view_types.includes("form")) { - return; - } - - // Check if record ID matches (if rec_ids is specified) - const currentResId = this.model && this.model.root && this.model.root.resId; - if (rec_ids.length > 0 && (!currentResId || !rec_ids.includes(currentResId))) { - return; - } - - // Skip refresh when form is in a dialog or when a wizard is on top of the stack. - // Refreshing in that context can leave wizard/confirmation dialogs stuck open - // (e.g. confirm="..." in wizard view). - if (this.env.inDialog) { - return; - } - const currentController = this.actionService.currentController; - const currentAction = currentController && currentController.action; - if (currentAction && currentAction.target === "new") { - return; - } - - await this.refreshForm(); - }, - - /** - * Refresh the form with actual data from server. - * - * For normal forms: - * - if record is clean: perform a soft_reload action - * - if record has unsaved changes: ask for confirmation, then reload - * - * For wizards (dialogs, target="new"): - * - reload only the current record without full action reload - * - * @returns {Promise} - */ - async refreshForm() { - // Do not refresh immediately after an explicit save (debounce window) - if (this._lastLocalSave && Date.now() - this._lastLocalSave < 1000) { - return; - } - - if (!this.model || !this.model.root) { - return; - } - - // Check if this form is opened as a wizard (dialog) - const currentController = this.actionService.currentController; - const action = currentController && currentController.action; - const isWizard = action && action.target === "new"; - - const record = this.model.root; - - if (!isWizard && record.isDirty) { - // Ask user whether to discard unsaved changes before refreshing - const confirmed = await new Promise((resolve) => { - this.dialogService.add(ConfirmationDialog, { - title: this.env._t("Form is being refreshed from backend"), - body: this.env._t("All unsaved changes will be lost! Continue?"), - confirm: () => resolve(true), - cancel: () => resolve(false), - confirmLabel: this.env._t("Continue"), - cancelLabel: this.env._t("Cancel"), - }); - }); - - if (!confirmed) { - return; - } - } - - try { - await record.load(); - } catch (error) { - const message = - (error && error.data && error.data.message) || - (error && error.message) || - String(error); - this.notificationService.add( - this.env._t("Could not reload form. ") + message, - {type: "danger"} - ); - return; - } - - // Update the view (only if component is still mounted) - if (this.model && this.model.root) { - this.render(true); - } - }, - - /** - * Override of save button handler. - * - * Stores timestamp of last local save to avoid immediate auto-refresh - * triggered by our own changes. - */ - async saveButtonClicked() { - this._lastLocalSave = Date.now(); - return await this._super(...arguments); - }, -}); diff --git a/addons/cx_web_refresh_from_backend/static/src/views/kanban/kanban_controller_patch.esm.js b/addons/cx_web_refresh_from_backend/static/src/views/kanban/kanban_controller_patch.esm.js deleted file mode 100644 index 7d6b807..0000000 --- a/addons/cx_web_refresh_from_backend/static/src/views/kanban/kanban_controller_patch.esm.js +++ /dev/null @@ -1,137 +0,0 @@ -/** @odoo-module **/ - -import {KanbanController} from "@web/views/kanban/kanban_controller"; -import {patch} from "@web/core/utils/patch"; -import {useService} from "@web/core/utils/hooks"; -import {onWillUnmount} from "@odoo/owl"; - -patch(KanbanController.prototype, "cx_web_refresh_from_backend.KanbanController", { - setup() { - this._super(...arguments); - this.busService = useService("bus_service"); - this.notificationService = useService("notification"); - - // Bind the handler to keep reference for cleanup - this._boundBusHandler = this._onBusNotification.bind(this); - - // Subscribe to bus notifications - this.busService.addEventListener("notification", this._boundBusHandler); - - // Cleanup on unmount - onWillUnmount(() => { - if (this.busService && this._boundBusHandler) { - this.busService.removeEventListener( - "notification", - this._boundBusHandler - ); - } - }); - }, - - /** - * Handle bus notification for view refresh - * @param {Event} event - Bus notification event - */ - async _onBusNotification({detail: notifications}) { - // Check if component is still alive - if (!this.model || !this.model.root) { - return; - } - - for (const {payload, type} of notifications) { - if (type === "web.refresh_view") { - await this._handleViewRefresh(payload); - } - } - }, - - /** - * Handle view refresh notification - * @param {Object} notification - Notification payload - */ - async _handleViewRefresh(notification) { - const {model, view_types = [], rec_ids = []} = notification; - - // Check if the model matches - if (this.props.resModel !== model) { - return; - } - - // Check if view_type matches (if specified) - if (view_types.length > 0 && !view_types.includes("kanban")) { - return; - } - - // Check if record ID matches (if rec_ids is specified) - if (rec_ids.length > 0) { - const loadedIds = this.getLoadedRecordIds(); - const shouldReload = loadedIds.some((id) => rec_ids.includes(id)); - - if (!shouldReload) { - return; - } - } - - await this.refreshList(); - }, - - /** - * Refresh the kanban with actual data from server - * @returns {Promise} - */ - async refreshList() { - // Safety check: component might be destroyed - if (!this.model || !this.model.root) { - return; - } - - const list = this.model.root; - - // Reload data from server - try { - await list.load(); - } catch (error) { - const message = - (error && error.data && error.data.message) || - (error && error.message) || - String(error); - this.notificationService.add( - this.env._t("Could not reload kanban. ") + message, - {type: "danger"} - ); - return; - } - - // Update the view (only if component is still mounted) - if (this.model && this.model.root) { - this.render(true); - } - }, - - /** - * Get IDs of all loaded records on the current page - * @returns {Array} Array of record IDs - */ - getLoadedRecordIds() { - const list = this.model.root; - - if (list.isGrouped) { - // For grouped kanban, collect IDs from all groups - const recordIds = []; - const collectIds = (groups) => { - for (const group of groups) { - if (group.list && group.list.records) { - recordIds.push(...group.list.records.map((r) => r.resId)); - } - if (group.groups) { - collectIds(group.groups); - } - } - }; - collectIds(list.groups); - return recordIds; - } - // For regular kanban, return IDs of all records - return list.records.map((record) => record.resId); - }, -}); diff --git a/addons/cx_web_refresh_from_backend/static/src/views/list/list_controller_patch.esm.js b/addons/cx_web_refresh_from_backend/static/src/views/list/list_controller_patch.esm.js deleted file mode 100644 index 4d17fbe..0000000 --- a/addons/cx_web_refresh_from_backend/static/src/views/list/list_controller_patch.esm.js +++ /dev/null @@ -1,177 +0,0 @@ -/** @odoo-module **/ - -import {ListController} from "@web/views/list/list_controller"; -import {patch} from "@web/core/utils/patch"; -import {useService} from "@web/core/utils/hooks"; -import {onWillUnmount} from "@odoo/owl"; -import {ConfirmationDialog} from "@web/core/confirmation_dialog/confirmation_dialog"; - -patch(ListController.prototype, "cx_web_refresh_from_backend.ListController", { - setup() { - this._super(...arguments); - this.busService = useService("bus_service"); - this.dialogService = useService("dialog"); - this.notificationService = useService("notification"); - - // Bind the handler to keep reference for cleanup - this._boundBusHandler = this._onBusNotification.bind(this); - - // Subscribe to bus notifications - this.busService.addEventListener("notification", this._boundBusHandler); - - // Cleanup on unmount - onWillUnmount(() => { - if (this.busService && this._boundBusHandler) { - this.busService.removeEventListener( - "notification", - this._boundBusHandler - ); - } - }); - }, - - /** - * Handle bus notification for view refresh - * @param {Event} event - Bus notification event - */ - async _onBusNotification({detail: notifications}) { - // Check if component is still alive - if (!this.model || !this.model.root) { - return; - } - - for (const {payload, type} of notifications) { - if (type === "web.refresh_view") { - await this._handleViewRefresh(payload); - } - } - }, - - /** - * Handle view refresh notification - * @param {Object} notification - Notification payload - */ - async _handleViewRefresh(notification) { - const {model, view_types = [], rec_ids = []} = notification; - - // Check if the model matches - if (this.props.resModel !== model) { - return; - } - - // Check if view_type matches (if specified) - if ( - view_types.length > 0 && - !view_types.includes("list") && - !view_types.includes("tree") - ) { - return; - } - - // Check if record ID matches (if rec_ids is specified) - if (rec_ids.length > 0) { - const loadedIds = this.getLoadedRecordIds(); - const shouldReload = loadedIds.some((id) => rec_ids.includes(id)); - - if (!shouldReload) { - return; - } - } - - await this.refreshList(); - }, - - /** - * Refresh the list with actual data from server. - * If there is an edited record, asks the user to save or cancel. - * - * @returns {Promise} - */ - async refreshList() { - // Safety check: component might be destroyed - if (!this.model || !this.model.root) { - return; - } - - const list = this.model.root; - - if (list.editedRecord) { - const confirmed = await new Promise((resolve) => { - this.dialogService.add(ConfirmationDialog, { - title: this.env._t("List is being refreshed from backend"), - body: this.env._t( - "You have unsaved edits. Save them before refreshing?" - ), - confirm: () => resolve(true), - cancel: () => resolve(false), - confirmLabel: this.env._t("Save & Refresh"), - cancelLabel: this.env._t("Cancel"), - }); - }); - - if (!confirmed) { - return; - } - try { - await list.editedRecord.save(); - } catch (error) { - const message = - (error && error.data && error.data.message) || - (error && error.message) || - String(error); - this.notificationService.add( - this.env._t("Could not save record. ") + message, - {type: "danger"} - ); - return; - } - } - - // Reload data from server - try { - await list.load(); - } catch (error) { - const message = - (error && error.data && error.data.message) || - (error && error.message) || - String(error); - this.notificationService.add( - this.env._t("Could not reload list. ") + message, - {type: "danger"} - ); - return; - } - - // Update the view (only if component is still mounted) - if (this.model && this.model.root) { - this.render(true); - } - }, - - /** - * Get IDs of all loaded records on the current page - * @returns {Array} Array of record IDs - */ - getLoadedRecordIds() { - const list = this.model.root; - - if (list.isGrouped) { - // For grouped list, collect IDs from all groups - const recordIds = []; - const collectIds = (groups) => { - for (const group of groups) { - if (group.list && group.list.records) { - recordIds.push(...group.list.records.map((r) => r.resId)); - } - if (group.groups) { - collectIds(group.groups); - } - } - }; - collectIds(list.groups); - return recordIds; - } - // For regular list, return IDs of all records - return list.records.map((record) => record.resId); - }, -}); diff --git a/addons/cx_web_refresh_from_backend/tests/__init__.py b/addons/cx_web_refresh_from_backend/tests/__init__.py deleted file mode 100644 index fbc9d5f..0000000 --- a/addons/cx_web_refresh_from_backend/tests/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright 2025 Cetmix OÜ -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). - -from . import test_reload_views diff --git a/addons/cx_web_refresh_from_backend/tests/test_reload_views.py b/addons/cx_web_refresh_from_backend/tests/test_reload_views.py deleted file mode 100644 index 897caff..0000000 --- a/addons/cx_web_refresh_from_backend/tests/test_reload_views.py +++ /dev/null @@ -1,95 +0,0 @@ -# Copyright 2025 Cetmix OÜ -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). - -from unittest.mock import patch - -from odoo.tests import tagged -from odoo.tests.common import TransactionCase - - -@tagged("post_install", "-at_install") -class TestReloadViews(TransactionCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.user_admin = cls.env.ref("base.user_admin") - cls.user_demo = cls.env.ref("base.user_demo") - cls.partner = cls.env["res.partner"].create( - { - "name": "Test Partner", - } - ) - - def test_reload_views_basic(self): - """Test basic reload_views call without parameters""" - with patch.object(type(self.env["bus.bus"]), "_sendmany") as mock_sendmany: - self.user_admin.reload_views(model="res.partner") - - mock_sendmany.assert_called_once() - # Get the notifications list - it's the first positional argument - notifications = mock_sendmany.call_args[0][0] - self.assertEqual(len(notifications), 1) - - partner, channel, message = notifications[0] - self.assertEqual(partner, self.user_admin.partner_id) - self.assertEqual(channel, "web.refresh_view") - self.assertEqual(message["model"], "res.partner") - self.assertEqual(message["view_types"], []) - self.assertEqual(message["rec_ids"], []) - - def test_reload_views_with_params(self): - """Test reload_views with view_types and rec_ids parameters""" - with patch.object(type(self.env["bus.bus"]), "_sendmany") as mock_sendmany: - self.user_admin.reload_views( - model="res.partner", - view_types=["form", "kanban"], - rec_ids=[self.partner.id], - ) - - notifications = mock_sendmany.call_args[0][0] - message = notifications[0][2] - self.assertEqual(message["view_types"], ["form", "kanban"]) - self.assertEqual(message["rec_ids"], [self.partner.id]) - - def test_reload_views_multiple_users(self): - """Test reload_views for multiple users at once""" - users = self.user_admin | self.user_demo - - with patch.object(type(self.env["bus.bus"]), "_sendmany") as mock_sendmany: - users.reload_views(model="res.partner") - - notifications = mock_sendmany.call_args[0][0] - self.assertEqual(len(notifications), 2) - - # Verify both users' partners are notified - notified_partners = {n[0] for n in notifications} - expected_partners = {self.user_admin.partner_id, self.user_demo.partner_id} - self.assertEqual(notified_partners, expected_partners) - - def test_reload_views_recordset(self): - """Test reload_views on a multi-record user recordset. - - Ensures that calling reload_views on a recordset still results in a - single _sendmany call, with one notification entry per user. - """ - users = self.user_admin | self.user_demo - - with patch.object(type(self.env["bus.bus"]), "_sendmany") as mock_sendmany: - users.reload_views(model="res.partner") - - # _sendmany should be called only once for the whole recordset - mock_sendmany.assert_called_once() - - notifications = mock_sendmany.call_args[0][0] - # We expect one notification tuple per user in the recordset - self.assertEqual(len(notifications), 2) - - # Verify both users' partners are notified and payload is correct - for partner, channel, message in notifications: - self.assertIn( - partner, {self.user_admin.partner_id, self.user_demo.partner_id} - ) - self.assertEqual(channel, "web.refresh_view") - self.assertEqual(message["model"], "res.partner") - self.assertEqual(message["view_types"], []) - self.assertEqual(message["rec_ids"], [])