a.1 Solution A: ภาพรวม

แนวทาง: แก้เฉพาะ Flow 2 (Refund/Void) — ไม่เพิ่ม schema, ไม่แตะ Flow อื่น


สรุปแบบภาษาคน

ระบบเดิมเมื่อมีการคืนสินค้า จะคำนวณยอดสะสมใหม่โดยดูย้อนหลัง 24 เดือนจาก “วันนี้” ทำให้ลง Tier ง่ายเกินไป แม้แต่ refund ที่เกิดหลังจากขึ้น Tier ก็ยังทำให้ลงได้

Solution A แก้เฉพาะ การลง Tier จาก void/refund โดยใช้ window การคำนวณใหม่ที่ยึดจาก “วันที่ขึ้น Tier” แทน “วันนี้” และเพิ่ม guard ว่า refund ที่เกิดหลัง up-tier ไม่ทำให้ลง Tier

การขึ้น Tier ยังใช้ logic เดิมทุกอย่าง ไม่เปลี่ยน


Scope: ขึ้น Tier vs ลง Tier

ขึ้น Tier (upgrade) ลง Tier (downgrade จาก void/refund)
Logic เดิม — ไม่เปลี่ยน ใหม่ — ตามกฎ 6 ข้อ
ใช้ยอดสะสม accumulateSpending (rolling 24 เดือน) Accum Tier Logic (window พิเศษ)
Function calculateTierToUpgrade() — ไม่แก้ addNewRefundSalesTransaction() — แก้
Trigger ซื้อของ (Flow 1) คืนสินค้า / ยกเลิกรายการ (Flow 2)

Accum Tier Logic (window ใหม่)

Accum Tier Logic = SUM 24 เดือนย้อนหลังจาก up-tier date
                 + SUM ทุก transaction หลัง up-tier จนถึงปัจจุบัน

ต่างจาก Legacy ที่ใช้ rolling 24 เดือนจากวันนี้ ซึ่งทำให้ยอดลดลงเรื่อยๆ ตามเวลา


กฎ 6 ข้อ (สรุป)

ข้อ กฎ ผลลัพธ์
1 Transaction expired ไม่ทำให้ down-tier ไม่ down-tier เพราะไม่มี refund event
2 Void/Refund หลัง up-tier ไม่ทำให้ down-tier Guard: originalSale.completedAt > upTierDate → return
3 Void/Refund ก่อน/ตอน up-tier → ดู Accum Tier Logic คำนวณใหม่ด้วย window พิเศษ แล้วดูว่าถึง tier ไหน
4 Maintain spending คำนวณใหม่เมื่อ down-tier ไม่ reset = 0 แต่ SUM txns หลัง up-tier
5 tierStartedAt/tierEndedAt ตาม history ดู validity ของ tier ก่อนหน้า (5.1) หรือคำนวณใหม่ (5.2)
6 Admin DEDUCT ใช้ logic เดียวกับ Refund delegate ไป Flow 2 อัตโนมัติ

สิ่งที่เปลี่ยน vs ไม่เปลี่ยน

Flow เปลี่ยน? หมายเหตุ
Flow 1 — Sales ไม่เปลี่ยน 100%
Flow 2 — Refund/Void เปลี่ยน guard + down-tier logic ทั้งหมด
Flow 3A — Batch Accum ไม่เปลี่ยน
Flow 3B — Batch Recalc ไม่เปลี่ยน
Flow 4 — Maintain Tier ไม่เปลี่ยน
Flow 5 — Reconcile ไม่เปลี่ยน
Flow 6 — Admin ADD delegate ไป Flow 1 → ไม่เปลี่ยน
Flow 6 — Admin DEDUCT ⚠️ delegate ไป Flow 2 → ได้รับผลทางอ้อม
Flow 7 — Co-Brand ไม่เปลี่ยน
Flow 8 — Staff Exit ไม่เปลี่ยน
Flow 9 — Member Import ไม่เปลี่ยน

Key Methods ใหม่

Method วัตถุประสงค์
calculateAccumTierLogicSpending() คำนวณ Accum Tier Logic = SUM(24mo ก่อน up-tier) + SUM(ทุก txn หลัง up-tier)
calculateTierAdjustmentWithNewLogic() ตัดสิน down-tier จาก Accum Tier Logic แทน rolling 24mo + apply co-brand floor + cap
capToCurrentTier() cap ไม่ให้ tier ปลายทางสูงกว่า tier ปัจจุบัน (flow นี้เป็น down-tier เท่านั้น)
calculatePostUpTierMaintainSpending() คำนวณ maintain จาก txns หลัง up-tier (ไม่รวม up-tier txn) — ใช้ทั้ง down-tier และ tier-ไม่เปลี่ยน
findPreviousTierInfo() หา tier dates จาก MemberTierHistory ที่ยัง valid (tierEndedAt ยังไม่หมดอายุ)
findQualifyingTransactionDate() scan หา txn ที่ทำให้ accum (หักลบ refund) ข้าม threshold ใน window [24mo ก่อน up-tier, now]; return null ถ้า threshold ≤ 0

ทำไม Solution A ถึง impact น้อยสุด

Aspect Solution A
Files ที่แก้ 2 files (member.entity.ts, refund-sales-transaction.service.ts)
Functions ที่แก้ 1 existing (addNewRefundSalesTransaction) + 5 new private methods
Schema change ไม่มี
Flows ที่กระทบ แค่ Flow 2
Regression risk ต่ำมาก

อ่านรายละเอียด sequence diagram แต่ละ flow ใน solution-a-sequence-diagrams.md
อ่าน implementation plan ใน solution-a-implementation-plan.md