Solution A: Tier Flow Mapping — Non-Technical ↔ Technical
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 ต่างจากเดิม)