first commit
This commit is contained in:
commit
e567862552
15
README.md
Normal file
15
README.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# POS Loyalty Safe Coupon Patch
|
||||||
|
|
||||||
|
This custom Odoo module resolves a core Odoo 19 bug in the POS Loyalty module where standard Odoo code makes unsafe direct reads on `coupon_id.id` of reward lines.
|
||||||
|
|
||||||
|
When an order is restored (e.g., during table switching in a restaurant) and references a coupon that is not yet fully loaded in the local client registry, Odoo leaves `coupon_id` as `undefined`. Accessing `.id` on this undefined object crashes the POS UI.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
Without modifying any core Odoo code, this module dynamically patches:
|
||||||
|
1. `PosOrder.getLoyaltyPoints()`
|
||||||
|
2. `OrderPaymentValidation.validateOrder()`
|
||||||
|
3. `PosStore.postProcessLoyalty()`
|
||||||
|
|
||||||
|
During their respective execution, it temporarily mocks any missing `coupon_id` references with `{ id: 0 }`. It also wraps `this.data.call` during `postProcessLoyalty` to ensure dummy coupon records with ID `0` are filtered out before being communicated back to the Odoo backend server. After execution, the original `undefined` state of the coupon is safely restored.
|
||||||
|
|
||||||
|
This ensures zero side effects, maximum stability, and clean compatibility with standard/custom addons.
|
||||||
2
__init__.py
Normal file
2
__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
21
__manifest__.py
Normal file
21
__manifest__.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': 'POS Loyalty Safe Coupon Patch',
|
||||||
|
'version': '1.0',
|
||||||
|
'category': 'Point of Sale',
|
||||||
|
'summary': 'Fixes TypeError on undefined coupon_id in Odoo POS Loyalty',
|
||||||
|
'description': """
|
||||||
|
POS Loyalty Safe Coupon Patch
|
||||||
|
=============================
|
||||||
|
This module overrides POS models and helper methods inside the POS loyalty app to safely handle undefined coupon_id structures, preventing POS interface crashes (e.g. when changing tables or updating loyalty).
|
||||||
|
""",
|
||||||
|
'author': 'Antigravity',
|
||||||
|
'depends': ['pos_loyalty'],
|
||||||
|
'assets': {
|
||||||
|
'point_of_sale._assets_pos': [
|
||||||
|
'pos_loyalty_safe_coupon/static/src/app/pos_loyalty_safe_coupon_patch.js',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'installable': True,
|
||||||
|
'license': 'LGPL-3',
|
||||||
|
}
|
||||||
75
static/src/app/pos_loyalty_safe_coupon_patch.js
Normal file
75
static/src/app/pos_loyalty_safe_coupon_patch.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
patch(PosOrder.prototype, {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
patch(PosStore.prototype, {
|
||||||
|
async postProcessLoyalty(order) {
|
||||||
|
const undefinedCouponLines = [];
|
||||||
|
for (const line of order._get_reward_lines()) {
|
||||||
|
if (!line.coupon_id) {
|
||||||
|
line.coupon_id = { id: 0 };
|
||||||
|
undefinedCouponLines.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intercept data.call to strip out the dummy coupon data with key "0"
|
||||||
|
const originalCall = this.data.call;
|
||||||
|
this.data.call = function (model, method, args) {
|
||||||
|
if (model === "pos.order" && method === "confirm_coupon_programs") {
|
||||||
|
const couponData = args[1];
|
||||||
|
if (couponData && "0" in couponData) {
|
||||||
|
delete couponData["0"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return originalCall.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await super.postProcessLoyalty(...arguments);
|
||||||
|
} finally {
|
||||||
|
this.data.call = originalCall;
|
||||||
|
for (const line of undefinedCouponLines) {
|
||||||
|
line.coupon_id = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user