feat: patch PosOrder to implement multi-level membership program selection logic
This commit is contained in:
parent
9768a3795c
commit
af99678406
@ -0,0 +1,91 @@
|
|||||||
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
import { PosOrder } from "@point_of_sale/app/models/pos_order";
|
||||||
|
import { patch } from "@web/core/utils/patch";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely extract a numeric ID from a Many2One field value.
|
||||||
|
* Handles three forms Odoo may return:
|
||||||
|
* - raw integer: 5
|
||||||
|
* - [id, name] tuple: [5, "Gold"]
|
||||||
|
* - record object: { id: 5, name: "Gold", ... }
|
||||||
|
*/
|
||||||
|
function resolveManyToOneId(value) {
|
||||||
|
if (!value && value !== 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return parseInt(value[0], 10);
|
||||||
|
}
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
return parseInt(value.id, 10);
|
||||||
|
}
|
||||||
|
return parseInt(value, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track recursion depth to prevent infinite loops when filtering multiLevelPrograms
|
||||||
|
let _checkingMultiLevel = false;
|
||||||
|
|
||||||
|
patch(PosOrder.prototype, {
|
||||||
|
_programIsApplicable(program) {
|
||||||
|
// Evaluate base program applicability first.
|
||||||
|
const isApplicable = super._programIsApplicable(...arguments);
|
||||||
|
if (!isApplicable) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom Logic for Multi-Level Membership.
|
||||||
|
// Guard against recursive calls triggered when filtering all programs below.
|
||||||
|
if (program.multi_level_membership && !_checkingMultiLevel) {
|
||||||
|
// Retrieve all loyalty programs
|
||||||
|
const allPrograms = this.models['loyalty.program'].getAll();
|
||||||
|
|
||||||
|
// Set guard BEFORE filtering to prevent re-entry into this block
|
||||||
|
_checkingMultiLevel = true;
|
||||||
|
let multiLevelPrograms;
|
||||||
|
try {
|
||||||
|
// Filter programs that have the multi-level flag and pass the base applicability check.
|
||||||
|
// With the guard set, this._programIsApplicable(p) will skip the custom block,
|
||||||
|
// effectively calling only the base logic for each candidate program.
|
||||||
|
multiLevelPrograms = allPrograms.filter(
|
||||||
|
(p) => p.multi_level_membership && this._programIsApplicable(p)
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
_checkingMultiLevel = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are no applicable multi-level programs, block all of them.
|
||||||
|
if (multiLevelPrograms.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bestProgram = null;
|
||||||
|
const partner = this.getPartner();
|
||||||
|
|
||||||
|
// If the partner is set and has a membership level, try to match it.
|
||||||
|
if (partner && partner.membership_level_id) {
|
||||||
|
const membershipId = resolveManyToOneId(partner.membership_level_id);
|
||||||
|
if (membershipId) {
|
||||||
|
// Find the matching program among applicable multi-level programs
|
||||||
|
bestProgram = multiLevelPrograms.find((p) => p.id === membershipId) || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: pick the program with the smallest minimum_spend
|
||||||
|
if (!bestProgram) {
|
||||||
|
bestProgram = multiLevelPrograms.reduce((prev, curr) => {
|
||||||
|
const prevSpend = prev.minimum_spend || 0;
|
||||||
|
const currSpend = curr.minimum_spend || 0;
|
||||||
|
return currSpend < prevSpend ? curr : prev;
|
||||||
|
}, multiLevelPrograms[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only permit the chosen program to be active
|
||||||
|
if (program.id !== bestProgram.id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user