/** @odoo-module * * PaymentScreen patch — minimal laundry behaviors: * 1. Settlement orders: hide pay-later methods, custom validateOrder * that calls settle_laundry_dues_rpc and bypasses normal sync. * 2. Legacy laundry orders: trigger order-type popup at payment time * (NOT on add-line). Block payment if type is required and missing. * * STRICT CONTRACT: * - Does NOT touch order.lines, pricing, taxes * - Does NOT override PosOrder.setup * - Selections stored as primitives only (handled in pos_store_patch.js) * - For non-laundry orders, super.validateOrder runs unchanged. */ import { patch } from "@web/core/utils/patch"; import { _t } from "@web/core/l10n/translation"; import { PaymentScreen } from "@point_of_sale/app/screens/payment_screen/payment_screen"; import { AlertDialog } from "@web/core/confirmation_dialog/confirmation_dialog"; import { LaundrySettlementReceipt } from "@laundry_management/js/settlement_receipt"; function isSettlementOrder(order) { return ( order && order.lines && order.lines.some( (line) => line.product_id?.product_tmpl_id?.is_laundry_settlement ) ); } patch(PaymentScreen.prototype, { setup() { super.setup(...arguments); // For settlement orders, hide Customer Account / pay-later methods. if (isSettlementOrder(this.currentOrder)) { this.payment_methods_from_config = this.payment_methods_from_config.filter( (pm) => !pm.split_transactions ); } }, /** * Pre-payment gate for the legacy laundry order-type flow. * - true → continue with payment * - false → block payment * * Skipped for: feature disabled, settlement orders, non-laundry orders, * and orders that already have a type. If required + missing, opens the * popup; blocks if the user closes/cancels. */ async beforePaymentFlow(order) { const pos = this.pos; const config = pos.config; if (!order || !config?.enable_laundry_order_type) { return true; } if (isSettlementOrder(order)) { return true; } if (!pos.orderHasLaundryServiceLine(order)) { return true; } if (order.laundry_order_type_id) { return true; } await pos.runLaundryOrderTypeFlow(order); if (config.require_laundry_order_type && !order.laundry_order_type_id) { pos.notification.add( _t("Order type is required to proceed to payment."), { type: "warning" } ); return false; } return true; }, async validateOrder(isForceValidate) { const order = this.currentOrder; // ── Settlement validation — bypass normal POS sync ── if (isSettlementOrder(order)) { return this._validateSettlementOrder(order); } // ── Legacy laundry order-type gate ── const allowed = await this.beforePaymentFlow(order); if (!allowed) { return false; } return super.validateOrder(isForceValidate); }, async _validateSettlementOrder(order) { const paymentLines = order.payment_ids .filter((pl) => pl.getAmount() > 0) .map((pl) => ({ pos_payment_method_id: pl.payment_method_id.id, amount: pl.getAmount(), })); if (paymentLines.length === 0) { this.notification.add( _t("Please add at least one payment."), { type: "warning" } ); return false; } const partnerId = order.partner_id?.id; if (!partnerId) { this.notification.add(_t("No customer selected."), { type: "danger", }); return false; } const sessionId = this.pos.session?.id || null; let response; try { this.ui.block(); response = await this.pos.data.call( "res.partner", "settle_laundry_dues_rpc", [partnerId, paymentLines, sessionId] ); } catch (err) { this.dialog.add(AlertDialog, { title: _t("Settlement Failed"), body: err?.data?.message || err?.message || _t("Unknown error."), }); return false; } finally { this.ui.unblock(); } if ( order.payment_ids.some( (pl) => pl.payment_method_id.is_cash_count && pl.getAmount() > 0 ) ) { this.pos.hardwareProxy.openCashbox(); } this.dialog.add(LaundrySettlementReceipt, { partnerName: order.partner_id.name, settledTotal: response.settled_total || 0, remainingDue: response.remaining_due || 0, payments: response.payments || [], settledOrders: response.settled_orders || [], }); this.pos._lastSettlementMethodId = paymentLines[0]?.pos_payment_method_id || null; this.pos.removeOrder(order, false); this.pos.addNewOrder(); this.pos.navigate("ProductScreen"); return true; }, });