fix: prevent TypeError crashes by centralizing orphan reward line purging across POS lifecycle hooks
This commit is contained in:
parent
40ef3de37b
commit
855f770934
@ -1,9 +1,39 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { PosOrder } from "@point_of_sale/app/models/pos_order";
|
||||
import OrderPaymentValidation from "@point_of_sale/app/utils/order_payment_validation";
|
||||
import { PosStore } from "@point_of_sale/app/services/pos_store";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
/**
|
||||
* Helper: delete all reward lines in an order where reward_id or reward_id.program_id is missing.
|
||||
* This prevents TypeError crashes when the core code accesses reward.program_id.xxx
|
||||
* on lines whose loyalty program was archived, deleted, or not yet loaded into the POS cache.
|
||||
*/
|
||||
function purgeOrphanedRewardLines(order) {
|
||||
if (!order || !order.lines) {
|
||||
return;
|
||||
}
|
||||
const orphans = order.lines.filter(
|
||||
(line) =>
|
||||
line.is_reward_line &&
|
||||
(!line.reward_id || !line.reward_id.program_id)
|
||||
);
|
||||
for (const line of orphans) {
|
||||
try {
|
||||
line.delete();
|
||||
} catch (e) {
|
||||
// Ignore deletion errors — the line may already be gone
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── PosOrder patches ─────────────────────────────────────────────────────────
|
||||
|
||||
patch(PosOrder.prototype, {
|
||||
/**
|
||||
* getLoyaltyPoints reads coupon_id.id — guard against undefined coupon_id.
|
||||
*/
|
||||
getLoyaltyPoints() {
|
||||
const undefinedCouponLines = [];
|
||||
for (const line of this._get_reward_lines()) {
|
||||
@ -20,17 +50,27 @@ patch(PosOrder.prototype, {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* _updateRewardLines rebuilds reward lines — purge orphans before core touches them.
|
||||
*/
|
||||
_updateRewardLines() {
|
||||
if (this.lines) {
|
||||
const invalidRewardLines = this.lines.filter((line) => line.is_reward_line && (!line.reward_id || !line.reward_id.program_id));
|
||||
for (const line of invalidRewardLines) {
|
||||
line.delete();
|
||||
}
|
||||
}
|
||||
purgeOrphanedRewardLines(this);
|
||||
return super._updateRewardLines(...arguments);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* initState fires when an order is initialized (including from server restore).
|
||||
* Schedule a purge on the next microtask so reactive relations are fully resolved first.
|
||||
*/
|
||||
initState() {
|
||||
super.initState(...arguments);
|
||||
Promise.resolve().then(() => purgeOrphanedRewardLines(this));
|
||||
},
|
||||
});
|
||||
|
||||
// ─── OrderPaymentValidation patches ───────────────────────────────────────────
|
||||
|
||||
patch(OrderPaymentValidation.prototype, {
|
||||
async validateOrder(isForceValidate) {
|
||||
const undefinedCouponLines = [];
|
||||
@ -47,11 +87,67 @@ patch(OrderPaymentValidation.prototype, {
|
||||
line.coupon_id = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// ─── PosStore patches ─────────────────────────────────────────────────────────
|
||||
|
||||
patch(PosStore.prototype, {
|
||||
/**
|
||||
* afterProcessServerData — earliest global hook after all records are loaded.
|
||||
* Purge orphaned reward lines from ALL open orders.
|
||||
*/
|
||||
async afterProcessServerData() {
|
||||
for (const order of this.models["pos.order"].getAll()) {
|
||||
purgeOrphanedRewardLines(order);
|
||||
}
|
||||
return await super.afterProcessServerData(...arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* updateRewards — called on every order-line change.
|
||||
* Purge orphaned lines from the current order before loyalty recalculation.
|
||||
*/
|
||||
updateRewards() {
|
||||
const order = this.getOrder();
|
||||
if (order) {
|
||||
purgeOrphanedRewardLines(order);
|
||||
}
|
||||
return super.updateRewards(...arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* orderUpdateLoyaltyPrograms — called when switching tables or rescanning codes.
|
||||
* This is the path triggered by "Check Table" → `setTableFromUi` → `updateOrder`.
|
||||
*/
|
||||
async orderUpdateLoyaltyPrograms() {
|
||||
const order = this.getOrder();
|
||||
if (order) {
|
||||
purgeOrphanedRewardLines(order);
|
||||
}
|
||||
return await super.orderUpdateLoyaltyPrograms(...arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* setTableFromUi — the exact entry point for the "Check Table → blank screen" crash.
|
||||
* Purge ALL orders before switching so the target table's order is clean.
|
||||
*/
|
||||
async setTableFromUi(table, orderUuid = null) {
|
||||
for (const order of this.models["pos.order"].getAll()) {
|
||||
purgeOrphanedRewardLines(order);
|
||||
}
|
||||
return await super.setTableFromUi(...arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* postProcessLoyalty — called when pushing/validating an order.
|
||||
* Guards both reward.program_id === undefined AND coupon_id === undefined.
|
||||
*/
|
||||
async postProcessLoyalty(order) {
|
||||
// Remove lines with missing program_id (core reads reward.program_id.id)
|
||||
purgeOrphanedRewardLines(order);
|
||||
|
||||
// Also guard coupon_id=undefined (original safe_coupon behavior)
|
||||
const undefinedCouponLines = [];
|
||||
for (const line of order._get_reward_lines()) {
|
||||
if (!line.coupon_id) {
|
||||
@ -60,7 +156,7 @@ patch(PosStore.prototype, {
|
||||
}
|
||||
}
|
||||
|
||||
// Intercept data.call to strip out the dummy coupon data with key "0"
|
||||
// Strip dummy coupon key "0" from confirm_coupon_programs calls
|
||||
const originalCall = this.data.call;
|
||||
this.data.call = function (model, method, args) {
|
||||
if (model === "pos.order" && method === "confirm_coupon_programs") {
|
||||
@ -81,14 +177,4 @@ patch(PosStore.prototype, {
|
||||
}
|
||||
}
|
||||
},
|
||||
async orderUpdateLoyaltyPrograms() {
|
||||
const order = this.getOrder();
|
||||
if (order && order.lines) {
|
||||
const invalidRewardLines = order.lines.filter((line) => line.is_reward_line && (!line.reward_id || !line.reward_id.program_id));
|
||||
for (const line of invalidRewardLines) {
|
||||
line.delete();
|
||||
}
|
||||
}
|
||||
return await super.orderUpdateLoyaltyPrograms(...arguments);
|
||||
}
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user