[REF] point_of_sale, web: factor report downloads out of action service
Previously, downloading reports was only possible by going through the action service. In a few places, we want to download reports from the PoS, but it doesn't make sense to load the action service and all its dependencies in the PoS. In order to allow reports to be downloaded without the action services, some functions are extracted out of the action service itself and into report utils, which the action service calls. These utils can now be used from the PoS without requiring the action service and its dependencies be included in the PoS assets. Part-of: odoo/odoo#120070
This commit is contained in:
@@ -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);
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
+5
-7
@@ -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) {
|
||||
|
||||
@@ -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 = '<br><br><a href="http://wkhtmltopdf.org/" target="_blank">wkhtmltopdf.org</a>';
|
||||
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}`,
|
||||
|
||||
@@ -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 = '<br><br><a href="http://wkhtmltopdf.org/" target="_blank">wkhtmltopdf.org</a>'; // 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 };
|
||||
}
|
||||
@@ -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",
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user