feat: patch PosOrder to exclude temporary loyalty cards from applicability and rewards calculations

This commit is contained in:
Suherdy Yacob 2026-05-22 22:27:10 +07:00
parent ccdc2f95df
commit c71acbf7cb
3 changed files with 57 additions and 1 deletions

View File

@ -23,6 +23,10 @@ This custom Odoo module enhances the POS Loyalty interface and partner creation
- Solves a core Odoo 19 multi-company operational bug where cashiers in branch companies encounter "Access Errors" when validating orders for customers whose loyalty cards belong to the parent company. - Solves a core Odoo 19 multi-company operational bug where cashiers in branch companies encounter "Access Errors" when validating orders for customers whose loyalty cards belong to the parent company.
- Bypasses company-specific record rules during POS loyalty audit logging using standard and safe `sudo` environment execution. - Bypasses company-specific record rules during POS loyalty audit logging using standard and safe `sudo` environment execution.
5. **Loyalty Reward and Point Access Control:**
- Restricts loyalty program reward eligibility, point balance displays, and program applicability in the POS UI strictly to customers who have a persisted, active card in the database for that specific program.
- Prevents unauthorized claim/access to zero-point rewards (e.g., 100% discount rewards in executive/exclusive programs) by non-enrolled members.
## Technical Details ## Technical Details
- **JS OWL Patching:** Overrides POS frontend partner management safely using clean, standard owl-level class overrides. - **JS OWL Patching:** Overrides POS frontend partner management and order processing (`PosOrder`) safely using clean, standard owl-level class overrides to filter program eligibility, claimable rewards, and loyalty point lists based on card status.
- **Python Inheritance:** Customizes backend models (`res.partner`, `pos.order`) to clean loading domains and securely provision cross-company data. - **Python Inheritance:** Customizes backend models (`res.partner`, `pos.order`) to clean loading domains and securely provision cross-company data.

View File

@ -24,6 +24,7 @@ Custom POS Loyalty and Membership management features:
'assets': { 'assets': {
'point_of_sale._assets_pos': [ 'point_of_sale._assets_pos': [
'pos_loyalty_member_custom/static/src/app/screens/partner_list_patch.js', 'pos_loyalty_member_custom/static/src/app/screens/partner_list_patch.js',
'pos_loyalty_member_custom/static/src/app/models/pos_order_patch.js',
] ]
}, },
'installable': True, 'installable': True,

View File

@ -0,0 +1,51 @@
/** @odoo-module **/
import { PosOrder } from "@point_of_sale/app/models/pos_order";
import { patch } from "@web/core/utils/patch";
patch(PosOrder.prototype, {
_programIsApplicable(program) {
const isApplicable = super._programIsApplicable(...arguments);
if (!isApplicable) {
return false;
}
if (program.program_type === 'loyalty') {
const partner = this.getPartner();
if (partner) {
// Check if a card exists in cache for this program and partner
const card = this.models['loyalty.card'].find(
(c) => c.partner_id?.id === partner.id && c.program_id?.id === program.id
);
// If a card exists and is temporary (ID < 0), the program is not applicable
if (card && card.id < 0) {
return false;
}
}
}
return true;
},
getClaimableRewards(coupon_id = false, program_id = false, auto = false) {
const claimable = super.getClaimableRewards(...arguments);
return claimable.filter((item) => {
const card = this.models['loyalty.card'].get(item.coupon_id);
if (card && card.program_id?.program_type === 'loyalty' && card.id < 0) {
return false;
}
return true;
});
},
getLoyaltyPoints() {
const points = super.getLoyaltyPoints(...arguments);
return points.filter((item) => {
const couponId = parseInt(item.couponId);
if (couponId < 0 && item.program?.program_type === 'loyalty') {
return false;
}
return true;
});
}
});