diff --git a/addons/point_of_sale/static/src/app/report_service.js b/addons/point_of_sale/static/src/app/report_service.js
new file mode 100644
index 00000000000..74a1d5ffd30
--- /dev/null
+++ b/addons/point_of_sale/static/src/app/report_service.js
@@ -0,0 +1,33 @@
+/** @odoo-module */
+
+import { registry } from "@web/core/registry";
+import { downloadReport } from "@web/webclient/actions/reports/utils";
+
+export const reportService = {
+ dependencies: ["rpc", "user", "ui"],
+ start(env, { rpc, user, ui }) {
+ const reportActionsCache = {};
+ return {
+ async download(reportXmlId, active_ids) {
+ ui.block();
+ try {
+ reportActionsCache[reportXmlId] ||= rpc("/web/action/load", {
+ action_id: reportXmlId,
+ });
+ const reportAction = await reportActionsCache[reportXmlId];
+ // await instead of return because we want the ui to stay blocked
+ await downloadReport(
+ rpc,
+ { ...reportAction, context: { active_ids } },
+ "pdf",
+ user.context
+ );
+ } finally {
+ ui.unblock();
+ }
+ },
+ };
+ },
+};
+
+registry.category("services").add("report", reportService);
diff --git a/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js b/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js
index 9b71ac54f38..2873844aba1 100644
--- a/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js
@@ -23,7 +23,7 @@ export class ClosePosPopup extends AbstractAwaitablePopup {
this.pos = usePos();
this.popup = useService("popup");
this.orm = useService("orm");
- this.action = useService("action");
+ this.report = useService("report");
this.hardwareProxy = useService("hardware_proxy");
this.manualInputCashCount = false;
this.cashControl = this.pos.globalState.config.cash_control;
@@ -92,11 +92,9 @@ export class ClosePosPopup extends AbstractAwaitablePopup {
}
}
async downloadSalesReport() {
- await this.action.doAction("point_of_sale.sale_details_report", {
- additionalContext: {
- active_ids: [this.pos.globalState.pos_session.id],
- },
- });
+ return this.report.download("point_of_sale.sale_details_report", [
+ this.pos.globalState.pos_session.id,
+ ]);
}
handleInputChange(paymentId) {
let expectedAmount;
diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js
index 8584024e16c..5631e38c3b8 100644
--- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js
+++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js
@@ -31,7 +31,7 @@ export class PaymentScreen extends Component {
this.pos = usePos();
this.orm = useService("orm");
this.popup = useService("popup");
- this.action = useService("action");
+ this.report = useService("report");
this.notification = useService("pos_notification");
this.hardwareProxy = useService("hardware_proxy");
this.payment_methods_from_config = this.env.pos.payment_methods.filter((method) =>
@@ -235,11 +235,9 @@ export class PaymentScreen extends Component {
// 2. Invoice.
if (this.currentOrder.is_to_invoice()) {
if (syncOrderResult.length) {
- await this.action.doAction("account.account_invoices", {
- additionalContext: {
- active_ids: [syncOrderResult[0].account_move],
- },
- });
+ await this.report.download("account.account_invoices", [
+ syncOrderResult[0].account_move,
+ ]);
} else {
throw {
code: 401,
diff --git a/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/InvoiceButton.js b/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/InvoiceButton.js
index 2e6c84cd1ac..ab2af422bd8 100644
--- a/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/InvoiceButton.js
+++ b/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/InvoiceButton.js
@@ -15,7 +15,7 @@ export class InvoiceButton extends Component {
this.invoiceButton = useRef("invoice-button");
this.popup = useService("popup");
this.orm = useService("orm");
- this.action = useService("action");
+ this.report = useService("report");
}
get isAlreadyInvoiced() {
if (!this.props.order) {
@@ -38,12 +38,10 @@ export class InvoiceButton extends Component {
["account_move"],
{ load: false }
);
- if (orderWithInvoice && orderWithInvoice.account_move) {
- await this.action.doAction("account.account_invoices", {
- additionalContext: {
- active_ids: [orderWithInvoice.account_move],
- },
- });
+ if (orderWithInvoice?.account_move) {
+ await this.report.download("account.account_invoices", [
+ orderWithInvoice.account_move,
+ ]);
}
} catch (error) {
if (error instanceof Error) {
diff --git a/addons/web/static/src/webclient/actions/action_service.js b/addons/web/static/src/webclient/actions/action_service.js
index e2692a5f3ed..81634a11774 100644
--- a/addons/web/static/src/webclient/actions/action_service.js
+++ b/addons/web/static/src/webclient/actions/action_service.js
@@ -3,7 +3,6 @@
import { browser } from "@web/core/browser/browser";
import { makeContext } from "@web/core/context";
import { useDebugCategory } from "@web/core/debug/debug_context";
-import { download } from "@web/core/network/download";
import { evaluateExpr } from "@web/core/py_js/py";
import { registry } from "@web/core/registry";
import { KeepLast } from "@web/core/utils/concurrency";
@@ -26,6 +25,7 @@ import {
xml,
reactive,
} from "@odoo/owl";
+import { downloadReport, getReportUrl } from "./reports/utils";
const actionHandlersRegistry = registry.category("action_handlers");
const actionRegistry = registry.category("actions");
@@ -819,8 +819,9 @@ function makeActionManager(env) {
*/
function _executeActURLAction(action, options) {
let url = action.url;
- if (url && !(url.startsWith('http') || url.startsWith('/')))
- url = '/' + url;
+ if (url && !(url.startsWith("http") || url.startsWith("/"))) {
+ url = "/" + url;
+ }
if (action.target === "self") {
let willUnload = false;
const onUnload = () => {
@@ -984,92 +985,6 @@ function makeActionManager(env) {
// ir.actions.report
// ---------------------------------------------------------------------------
- // messages that might be shown to the user dependening on the state of wkhtmltopdf
- const link = '
wkhtmltopdf.org';
- const WKHTMLTOPDF_MESSAGES = {
- broken:
- env._t(
- "Your installation of Wkhtmltopdf seems to be broken. The report will be shown " +
- "in html."
- ) + link,
- install:
- env._t(
- "Unable to find Wkhtmltopdf on this system. The report will be shown in " + "html."
- ) + link,
- upgrade:
- env._t(
- "You should upgrade your version of Wkhtmltopdf to at least 0.12.0 in order to " +
- "get a correct display of headers and footers as well as support for " +
- "table-breaking between pages."
- ) + link,
- workers: env._t(
- "You need to start Odoo with at least two workers to print a pdf version of " +
- "the reports."
- ),
- };
-
- // only check the wkhtmltopdf state once, so keep the rpc promise
- let wkhtmltopdfStateProm;
-
- /**
- * Generates the report url given a report action.
- *
- * @private
- * @param {ReportAction} action
- * @param {ReportType} type
- * @returns {string}
- */
- function _getReportUrl(action, type) {
- let url = `/report/${type}/${action.report_name}`;
- const actionContext = action.context || {};
- if (action.data && JSON.stringify(action.data) !== "{}") {
- // build a query string with `action.data` (it's the place where reports
- // using a wizard to customize the output traditionally put their options)
- const options = encodeURIComponent(JSON.stringify(action.data));
- const context = encodeURIComponent(JSON.stringify(actionContext));
- url += `?options=${options}&context=${context}`;
- } else {
- if (actionContext.active_ids) {
- url += `/${actionContext.active_ids.join(",")}`;
- }
- if (type === "html") {
- const context = encodeURIComponent(JSON.stringify(env.services.user.context));
- url += `?context=${context}`;
- }
- }
- return url;
- }
-
- /**
- * Launches download action of the report
- *
- * @private
- * @param {ReportAction} action
- * @param {ActionOptions} options
- * @returns {Promise}
- */
- async function _triggerDownload(action, options, type) {
- const url = _getReportUrl(action, type);
- env.services.ui.block();
- try {
- await download({
- url: "/report/download",
- data: {
- data: JSON.stringify([url, action.report_type]),
- context: JSON.stringify(env.services.user.context),
- },
- });
- } finally {
- env.services.ui.unblock();
- }
- const onClose = options.onClose;
- if (action.close_on_report_download) {
- return doAction({ type: "ir.actions.act_window_close" }, { onClose });
- } else if (onClose) {
- onClose();
- }
- }
-
function _executeReportClientAction(action, options) {
const props = Object.assign({}, options.props, {
data: action.data,
@@ -1077,7 +992,7 @@ function makeActionManager(env) {
name: action.name,
report_file: action.report_file,
report_name: action.report_name,
- report_url: _getReportUrl(action, "html"),
+ report_url: getReportUrl(action, "html", env.services.user.context),
context: Object.assign({}, action.context),
});
@@ -1112,28 +1027,35 @@ function makeActionManager(env) {
}
if (action.report_type === "qweb-html") {
return _executeReportClientAction(action, options);
- } else if (action.report_type === "qweb-pdf") {
- // check the state of wkhtmltopdf before proceeding
- if (!wkhtmltopdfStateProm) {
- wkhtmltopdfStateProm = env.services.rpc("/report/check_wkhtmltopdf");
+ } else if (action.report_type === "qweb-pdf" || action.report_type === "qweb-text") {
+ const type = action.report_type.slice(5);
+ let success, message;
+ env.services.ui.block();
+ try {
+ ({ success, message } = await downloadReport(
+ env.services.rpc,
+ action,
+ type,
+ env.services.user.context
+ ));
+ } finally {
+ env.services.ui.unblock();
}
- const state = await wkhtmltopdfStateProm;
- // display a notification according to wkhtmltopdf's state
- if (state in WKHTMLTOPDF_MESSAGES) {
- env.services.notification.add(WKHTMLTOPDF_MESSAGES[state], {
+ if (message) {
+ env.services.notification.add(message, {
sticky: true,
title: env._t("Report"),
});
}
- if (state === "upgrade" || state === "ok") {
- // trigger the download of the PDF report
- return _triggerDownload(action, options, "pdf");
- } else {
- // open the report in the client action if generating the PDF is not possible
+ if (!success) {
return _executeReportClientAction(action, options);
}
- } else if (action.report_type === "qweb-text") {
- return _triggerDownload(action, options, "text");
+ const { onClose } = options;
+ if (action.close_on_report_download) {
+ return doAction({ type: "ir.actions.act_window_close" }, { onClose });
+ } else if (onClose) {
+ onClose();
+ }
} else {
console.error(
`The ActionManager can't handle reports of type ${action.report_type}`,
diff --git a/addons/web/static/src/webclient/actions/reports/utils.js b/addons/web/static/src/webclient/actions/reports/utils.js
new file mode 100644
index 00000000000..2d8af86e264
--- /dev/null
+++ b/addons/web/static/src/webclient/actions/reports/utils.js
@@ -0,0 +1,83 @@
+/** @odoo-module */
+
+import { _t } from "@web/core/l10n/translation";
+import { download } from "@web/core/network/download";
+
+/**
+ * Generates the report url given a report action.
+ *
+ * @param {Object} action the report action
+ * @param {"text"|"qweb"|"html"} type the type of the report
+ * @param {Object} userContext the user context
+ * @returns {string}
+ */
+export function getReportUrl(action, type, userContext) {
+ let url = `/report/${type}/${action.report_name}`;
+ const actionContext = action.context || {};
+ if (action.data && JSON.stringify(action.data) !== "{}") {
+ // build a query string with `action.data` (it's the place where reports
+ // using a wizard to customize the output traditionally put their options)
+ const options = encodeURIComponent(JSON.stringify(action.data));
+ const context = encodeURIComponent(JSON.stringify(actionContext));
+ url += `?options=${options}&context=${context}`;
+ } else {
+ if (actionContext.active_ids) {
+ url += `/${actionContext.active_ids.join(",")}`;
+ }
+ if (type === "html") {
+ const context = encodeURIComponent(JSON.stringify(userContext));
+ url += `?context=${context}`;
+ }
+ }
+ return url;
+}
+
+// messages that might be shown to the user dependening on the state of wkhtmltopdf
+const link = '
wkhtmltopdf.org'; // FIXME missing markup
+const WKHTMLTOPDF_MESSAGES = {
+ broken:
+ _t(
+ "Your installation of Wkhtmltopdf seems to be broken. The report will be shown in html."
+ ) + link,
+ install:
+ _t("Unable to find Wkhtmltopdf on this system. The report will be shown in html.") + link,
+ upgrade:
+ _t(
+ "You should upgrade your version of Wkhtmltopdf to at least 0.12.0 in order to get a correct display of headers and footers as well as support for table-breaking between pages."
+ ) + link,
+ workers: _t(
+ "You need to start Odoo with at least two workers to print a pdf version of the reports."
+ ),
+};
+
+/**
+ * Launches download action of the report
+ *
+ * @param {Function} rpc a function to perform RPCs
+ * @param {Object} action the report action
+ * @param {"pdf"|"text"} type the type of the report to download
+ * @param {Object} userContext the user context
+ * @returns {Promise<{success: boolean, message?: string}>}
+ */
+export async function downloadReport(rpc, action, type, userContext) {
+ let message;
+ if (type === "pdf") {
+ // Cache the wkhtml status on the function. In prod this means is only
+ // checked once, but we can reset it between tests to test multiple statuses.
+ downloadReport.wkhtmltopdfStatusProm ||= rpc("/report/check_wkhtmltopdf");
+ const status = await downloadReport.wkhtmltopdfStatusProm;
+ message = WKHTMLTOPDF_MESSAGES[status];
+ if (!["upgrade", "ok"].includes(status)) {
+ return { success: false, message };
+ }
+ }
+ const url = getReportUrl(action, type);
+ await download({
+ url: "/report/download",
+ data: {
+ data: JSON.stringify([url, action.report_type]),
+ context: JSON.stringify(userContext),
+ },
+ });
+ return { success: true, message };
+}
diff --git a/addons/web/static/tests/webclient/actions/reports/report_action_tests.js b/addons/web/static/tests/webclient/actions/reports/report_action_tests.js
index 0b4f101a2cc..1b4f62ef12b 100644
--- a/addons/web/static/tests/webclient/actions/reports/report_action_tests.js
+++ b/addons/web/static/tests/webclient/actions/reports/report_action_tests.js
@@ -12,6 +12,8 @@ import {
doAction,
getActionManagerServerData,
} from "@web/../tests/webclient/helpers";
+import { downloadReport } from "@web/webclient/actions/reports/utils";
+import { registerCleanup } from "../../../helpers/cleanup";
let serverData;
let target;
@@ -23,6 +25,9 @@ QUnit.module("ActionManager", (hooks) => {
serverData = getActionManagerServerData();
target = getFixture();
clearRegistryWithCleanup(registry.category("main_components"));
+ registerCleanup(() => {
+ delete downloadReport.wkhtmltopdfStatusProm;
+ });
});
QUnit.module("Report actions");
@@ -111,8 +116,8 @@ QUnit.module("ActionManager", (hooks) => {
"/web/webclient/load_menus",
"/web/action/load",
"/report/check_wkhtmltopdf",
- "notify",
"/report/download",
+ "notify",
]);
}
);