From 1f85ab68bca32e5e540c7c04894c7146b805fcec Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Thu, 4 Jun 2026 10:37:26 +0700 Subject: [PATCH] feat: implement membership validation logic to distinguish between manual and auto-tier loyalty programs in POS orders --- static/src/app/models/pos_order.js | 54 +++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/static/src/app/models/pos_order.js b/static/src/app/models/pos_order.js index ae808af..2c1d73d 100644 --- a/static/src/app/models/pos_order.js +++ b/static/src/app/models/pos_order.js @@ -37,6 +37,37 @@ patch(PosOrder.prototype, { // Custom Logic for Multi-Level Membership. // Guard against recursive calls triggered when filtering all programs below. if (program.multi_level_membership && !_checkingMultiLevel) { + const partner = this.getPartner(); + const membershipId = (partner && partner.membership_level_id) + ? resolveManyToOneId(partner.membership_level_id) + : null; + + // ── Case 1: Manual membership program (e.g. Membership Direksi, subscriptions) ── + // These programs are never part of auto-tier selection. + // Only allow them when the partner actually holds an active loyalty card + // for this program — whether it's a replacement tier (Direksi) or + // an add-on subscription (e.g. Makan Pagi Gratis). + if (program.manual_membership) { + if (!partner) return false; + const allCards = this.models['loyalty.card']?.getAll() || []; + return allCards.some((card) => { + const cardPartnerId = resolveManyToOneId(card.partner_id); + const cardProgramId = resolveManyToOneId(card.program_id); + return cardPartnerId === partner.id && cardProgramId === program.id; + }); + } + + // ── Case 2: Auto-tier program (Silver / Gold / Platinum) ── + // If the partner holds a manual membership level (e.g. Direksi), + // block all auto-tier rewards — they use their own program's rewards. + if (membershipId) { + const allPrograms = this.models['loyalty.program'].getAll(); + const memberProgram = allPrograms.find((p) => p.id === membershipId); + if (memberProgram && memberProgram.manual_membership) { + return false; + } + } + // Retrieve all loyalty programs const allPrograms = this.models['loyalty.program'].getAll(); @@ -44,9 +75,10 @@ patch(PosOrder.prototype, { _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. + // Filter programs that have the multi-level flag (non-manual) and + // pass the base applicability check. With the guard set, + // _programIsApplicable(p) will skip this custom block, + // effectively calling only the base logic for each candidate. multiLevelPrograms = allPrograms.filter( (p) => p.multi_level_membership && !p.manual_membership && this._programIsApplicable(p) ); @@ -54,24 +86,19 @@ patch(PosOrder.prototype, { _checkingMultiLevel = false; } - // If there are no applicable multi-level programs, block all of them. + // If there are no applicable auto-tier 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; - } + // If the partner has a membership level, try to match it. + if (membershipId) { + bestProgram = multiLevelPrograms.find((p) => p.id === membershipId) || null; } - // Fallback: pick the program with the smallest minimum_spend + // Fallback: pick the program with the smallest minimum_spend (Silver) if (!bestProgram) { bestProgram = multiLevelPrograms.reduce((prev, curr) => { const prevSpend = prev.minimum_spend || 0; @@ -89,3 +116,4 @@ patch(PosOrder.prototype, { return true; }, }); +