Solution A: Tier Flow Mapping — Non-Technical ↔ Technical

Solution A: แก้เฉพาะ addNewRefundSalesTransaction() ใน Member Entity — ไม่มี schema change


Flow ที่เปลี่ยน vs ไม่เปลี่ยน

Flow เปลี่ยน? หมายเหตุ
Flow 1 — ซื้อของ → Tier ขึ้น ไม่เปลี่ยน 100%
Flow 2 — คืนสินค้า → Tier ลง เปลี่ยน guard condition + down-tier logic ทั้งหมด
Flow 3A — Batch Accum ไม่เปลี่ยน
Flow 3B — Batch Recalc ไม่เปลี่ยน (ใช้ path แยก)
Flow 4 — Maintain Tier ไม่เปลี่ยน (ใช้ path แยก)
Flow 5 — Reconcile ไม่เปลี่ยน (ใช้ path แยก)
Flow 6 — Admin ADD delegate ไป Flow 1 → ไม่เปลี่ยน
Flow 6 — Admin DEDUCT ⚠️ delegate ไป Flow 2 → ได้รับผลทางอ้อม
Flow 7 — Co-Brand ไม่เปลี่ยน (ใช้ path แยก)
Flow 8 — Staff Exit ไม่เปลี่ยน (ใช้ path แยก)
Flow 9 — Member Import ไม่เปลี่ยน (ใช้ path แยก)

DB Schema

ไม่มี schema change — คำนวณสดจาก transactions ใน memory ทุกครั้ง


Flow 2 — รายละเอียดที่เปลี่ยน

ภาษาคน (ใหม่):

เมื่อคืนสินค้าที่ซื้อก่อนหรือตอนขึ้น Tier → ใช้ Accum Tier Logic คำนวณ window พิเศษ → ตัดสิน down-tier

Technical (ใหม่):

addNewRefundSalesTransaction():
  1. calculateAccumulateSpending()         // เหมือนเดิม
  2. calculateAccumulateMaintainSpending() // เหมือนเดิม

  3. Guard: originalSale.completedAt > tierStartedAt?
     → ใช่: จบเลย ไม่ check downgrade (กฎ 2)

  4. calculateAccumTierLogicSpending()     // NEW: window พิเศษ
  5. calculateTierAdjustmentWithNewLogic() // NEW: ตัดสิน down-tier

  6. ถ้า downgrade:
     - findPreviousTierInfo()              // NEW: หา tier dates จาก history
     - findQualifyingTransactionDate()     // NEW: scan qualifying txn
     - calculatePostUpTierMaintainSpending() // NEW: maintain ไม่ reset = 0
     - updateMemberTier(...)

Functions ใหม่ที่ต้องเพิ่ม

flowchart LR subgraph NEW ["Functions ใหม่"] ATL["calculateAccumTierLogicSpending()\nwindow พิเศษ"] CTA2["calculateTierAdjustmentWithNewLogic()\nตัดสิน down-tier"] MDT["calculatePostUpTierMaintainSpending()\nไม่ reset = 0"] FPT["findPreviousTierInfo()\nหา tier dates จาก history"] FQT["findQualifyingTransactionDate()\nscan qualifying txn\n(หักลบ refund, window ถึงปัจจุบัน)"] end subgraph CHANGED ["Functions ที่เปลี่ยน"] ARF["addNewRefundSalesTransaction()\nเปลี่ยน down-tier logic ทั้งหมด"] end subgraph UNCHANGED ["Functions ที่ไม่เปลี่ยน"] CAS["calculateAccumulateSpending()"] CMS["calculateAccumulateMaintainSpending()"] CTA["calculateTierAdjustment()"] UMT["updateMemberTier()"] end ARF --> ATL ARF --> CTA2 ARF --> MDT ARF --> FPT ARF --> FQT ARF --> UMT

Functions ที่ไม่เปลี่ยน

  • calculateAccumulateSpending() — ยังใช้ rolling 24 เดือนเหมือนเดิม
  • calculateAccumulateMaintainSpending() — ยังใช้เหมือนเดิม
  • calculateTierAdjustment() — ยังใช้เหมือนเดิม (แต่ไม่ใช้ใน down-tier path ใหม่)
  • updateMemberTier() — ไม่เปลี่ยน
  • calculateTierToUpgrade() — ไม่เปลี่ยน (upgrade path ไม่แตะ)
  • ทุก function ใน Flow 3, 4, 5, 7, 8, 9

กฎ 6 ข้อ: implement อย่างไร

กฎ Legacy Solution A
1: Transaction expired ไม่ down-tier อาจ down-tier ✅ ไม่ trigger เพราะไม่มี refund event
2: Void/Refund หลัง up-tier ไม่ down-tier อาจ down-tier ✅ Guard: if (originalSale.completedAt > tierStartedAt) return;
3: Void/Refund ก่อน/ตอน up-tier → Accum Tier Logic rolling 24mo calculateAccumTierLogicSpending()
3.1: ยังถึง tier ปัจจุบัน คง tier ✅ คง tier เดิม
3.2: ข้าม tier → ดู validity ไม่มี findPreviousTierInfo() → check tierEndedAt > now
3.3: ต่ำกว่า tier ก่อนหน้า → scan ไม่มี findQualifyingTransactionDate() scan 24 เดือนก่อน up-tier + หลัง up-tier จนถึงปัจจุบัน (หักลบ refund)
4: Maintain ไม่ reset reset = 0 calculatePostUpTierMaintainSpending()
5.1: tierDates จาก valid history tierStartedAt = now ✅ ใช้จาก MemberTierHistory
5.2: tierDates คำนวณใหม่ tierStartedAt = now findQualifyingTransactionDate() หรือ fallback = now
6: Admin DEDUCT ใช้ logic เดียวกับ Refund ผ่าน refund service ✅ อัตโนมัติ เพราะ delegate ไป Flow 2

Domain Events

ไม่เพิ่ม event ใหม่ — ยังใช้ MemberTierDowngradedDueToRefundDomainEvent เหมือนเดิม
แต่ payload เปลี่ยน (tierStartedAt / accumulateMaintainSpending ต่างจากเดิม)