Tower: upload cx_web_refresh_from_backend 16.0.1.0.0 (via marketplace)
This commit is contained in:
@@ -0,0 +1,181 @@
|
|||||||
|
/** @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<void>}
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user