diff --git a/static/src/app/pos_loyalty_safe_coupon_patch.js b/static/src/app/pos_loyalty_safe_coupon_patch.js index f2b7d1a..a76a487 100644 --- a/static/src/app/pos_loyalty_safe_coupon_patch.js +++ b/static/src/app/pos_loyalty_safe_coupon_patch.js @@ -74,25 +74,6 @@ patch(PosOrder.prototype, { }); }, - /** - * getLoyaltyPoints reads coupon_id.id — guard undefined coupon_id. - */ - getLoyaltyPoints() { - const undefinedCouponLines = []; - for (const line of this._get_reward_lines()) { - if (!line.coupon_id) { - line.coupon_id = { id: 0 }; - undefinedCouponLines.push(line); - } - } - try { - return super.getLoyaltyPoints(...arguments); - } finally { - for (const line of undefinedCouponLines) { - line.coupon_id = undefined; - } - } - }, /** * _updateRewardLines — purge orphans before core re-processes them. @@ -104,7 +85,7 @@ patch(PosOrder.prototype, { /** * _validForPointsCorrection — core accesses reward.program_id.id without null check. - * Guard it here. + * Guard it here. This also guards the lines accessed in _getPointsCorrection. */ _validForPointsCorrection(reward, line, rule) { if (!reward || !reward.program_id || !rule.program_id) { @@ -113,28 +94,21 @@ patch(PosOrder.prototype, { return super._validForPointsCorrection(...arguments); }, - _getPointsCorrection(program) { - if (!program || !program.rule_ids) { - return 0; - } - const originalLines = this.lines; - this.lines = this.lines.filter( - (line) => !(line.is_reward_line && (!line.reward_id || !line.reward_id.program_id)) - ); - try { - return super._getPointsCorrection(...arguments); - } finally { - this.lines = originalLines; - } - }, - + /** + * _get_reward_lines — exclude orphaned reward lines (no valid reward_id.program_id). + * This also protects _getPointsCorrection which iterates this.lines.filter(is_reward_line). + * NOTE: Do NOT reassign this.lines — that goes through the ORM setter and permanently + * mutates/destroys lines. Instead, filter at the method level. + */ _get_reward_lines() { const rewardLines = super._get_reward_lines(...arguments); if (!rewardLines) { return []; } + // Exclude orphaned lines with missing reward_id.program_id OR missing coupon_id. + // getLoyaltyPoints accesses line.coupon_id.id which crashes if coupon_id is undefined. return rewardLines.filter( - (line) => line.reward_id && line.reward_id.program_id + (line) => line.reward_id && line.reward_id.program_id && line.coupon_id ); }, @@ -169,20 +143,12 @@ patch(PosOrder.prototype, { patch(OrderPaymentValidation.prototype, { async validateOrder(isForceValidate) { - const undefinedCouponLines = []; - for (const line of this.order._get_reward_lines()) { - if (!line.coupon_id) { - line.coupon_id = { id: 0 }; - undefinedCouponLines.push(line); - } - } - try { - return await super.validateOrder(...arguments); - } finally { - for (const line of undefinedCouponLines) { - line.coupon_id = undefined; - } + // Purge any orphaned reward lines before validation to prevent crashes + // in confirm_coupon_programs when it iterates reward lines. + if (this.order) { + purgeOrphanedRewardLines(this.order); } + return await super.validateOrder(...arguments); }, }); @@ -237,14 +203,6 @@ patch(PosStore.prototype, { async postProcessLoyalty(order) { purgeOrphanedRewardLines(order); - const undefinedCouponLines = []; - for (const line of order._get_reward_lines()) { - if (!line.coupon_id) { - line.coupon_id = { id: 0 }; - undefinedCouponLines.push(line); - } - } - const originalCall = this.data.call; this.data.call = function (model, method, args) { if (model === "pos.order" && method === "confirm_coupon_programs") { @@ -260,9 +218,6 @@ patch(PosStore.prototype, { return await super.postProcessLoyalty(...arguments); } finally { this.data.call = originalCall; - for (const line of undefinedCouponLines) { - line.coupon_id = undefined; - } } }, });