New Down-Tier Logic
Tier ยอดสะสม
💙 NAVY → ยอดสะสม 0 – 49,999 บาท
❤️ SCARLET → ยอดสะสม 50,000 – 299,999 บาท
👑 CROWN → ยอดสะสม 300,000 – 1,999,999 บาท
⭐ VEGA → ยอดสะสม 2,000,000 บาท ขึ้นไป
💎 CRYSTAL / VVIP → ได้รับเชิญพิเศษเท่านั้น (ไม่ขึ้นกับยอดซื้อ)
ความแตกต่างจาก Legacy
กฎ 6 ข้อนี้เปลี่ยนเฉพาะ ลง Tier (down-tier) จาก void/refund เท่านั้น
การขึ้น Tier ยังใช้ logic เดิมทุกอย่าง ไม่เปลี่ยนเหตุผล: ทุกข้อพูดถึง “เมื่อเกิด void/refund” — ไม่มีข้อไหนเกี่ยวกับ “เมื่อซื้อของ”
ปัญหาอยู่ที่ “ลง Tier ง่ายเกินไป” ไม่ใช่ “ขึ้น Tier”
| ขึ้น Tier (upgrade) | ลง Tier (downgrade จาก void/refund) | |
|---|---|---|
| Logic | เดิม — ไม่เปลี่ยน | ใหม่ — ตามกฎ 6 ข้อด้านล่าง |
| ใช้ยอดสะสม | accumulateSpending (rolling 24 เดือน) |
Accum Tier Logic (window พิเศษ) |
| Function | calculateTierToUpgrade() — ไม่แก้ |
addNewRefundSalesTransaction() — แก้ |
| Trigger | ซื้อของ (Flow 1) | คืนสินค้า / ยกเลิกรายการ (Flow 2) |
| Legacy | New | |
|---|---|---|
| Trigger downgrade | refund ลด accumulateSpending ต่ำกว่า tier | refund เป็น transaction ที่ทำให้ up-tier หรือก่อนหน้า |
| Window คำนวณ | rolling 24 เดือนจากปัจจุบัน | 24 เดือนย้อนหลังจาก up-tier date + ทุก transaction หลัง up-tier |
| Transaction หลัง up-tier | นับรวมใน accumulateSpending | ไม่ทำให้ down-tier (ข้อ 2) |
| Maintain spending | นับจาก tierStartedAt | นับจาก transaction ถัดจาก up-tier transaction |
หลักการ
Accum (ปัจจุบัน — Legacy)
Accum = SUM ย้อนหลัง 24 เดือนจากวันปัจจุบัน
Accum Tier Logic (New — ใช้ตัดสิน down-tier)
Accum Tier Logic = SUM 24 เดือนย้อนหลังจาก up-tier date
+ SUM ทุก transaction หลัง up-tier จนถึงปัจจุบัน
ตัวอย่าง Timeline:
ต.ค. 2023 ซื้อ 10,000 ─┐
พ.ย. 2023 ซื้อ 10,000 │ 24 เดือนย้อนหลังจาก up-tier date
ธ.ค. 2023 ซื้อ 10,000 │
ม.ค. 2024 ซื้อ 10,000 ─┤
ซื้อ 10,000 ──┼── up-tier date (01 Jan 2024) → ขึ้น SCARLET (50,000)
ก.พ. 2024 ซื้อ 10,000 ─┐
มี.ค. 2024 ซื้อ 10,000 │ transactions หลัง up-tier
เม.ย. 2024 ซื้อ 10,000 │
พ.ค. 2024 ซื้อ 10,000 │
มิ.ย. 2024 ซื้อ 10,000 │
ก.ค. 2024 ซื้อ 10,000 ─┘ ← ปัจจุบัน
Accum (Legacy) = rolling 24 เดือนจากปัจจุบัน = 80,000 บาท
Accum Tier Logic = (10K×4 ก่อน up-tier) + (10K×1 up-tier) + (10K×6 หลัง up-tier)
= 40,000 + 10,000 + 60,000 = 110,000 บาท
กฎ 6 ข้อ
ข้อ 1 — Transaction Expired ไม่ทำให้ Down-tier
Transaction ที่หลุดออกจาก rolling 24 เดือน (expired) ไม่ทำให้ down-tier
ตัวอย่าง:
up-tier เมื่อ 01 Jan 2024 ด้วย accum = 50,000 บาท (SCARLET)
วันนี้ 01 Feb 2026 → transaction ก่อน 01 Feb 2024 หลุด rolling window ปกติ
Legacy: accum ลดลง → อาจ down-tier
New: ไม่ down-tier เพราะ transaction expired ไม่ใช่ void/refund ✅
ข้อ 2 — Void/Refund หลัง Up-tier ไม่ทำให้ Down-tier
Void หรือ Refund ที่เกิดหลัง up-tier date ไม่ทำให้ down-tier
ตัวอย่าง:
up-tier วันที่ 01 Jan 2024 (ขึ้น SCARLET)
ซื้อเพิ่ม 10,000 บาท เมื่อ 15 Mar 2024 ← หลัง up-tier
void/refund 10,000 บาท นั้นเมื่อ 20 Apr 2024
Legacy: accum ลดลง → อาจ down-tier
New: void/refund เกิดหลัง up-tier date (Mar 2024 > Jan 2024)
→ ไม่ down-tier ✅
ข้อ 3 — Void/Refund ก่อนหรือตอน Up-tier → ดูผลลัพธ์ Accum Tier Logic
Void หรือ Refund ที่เกิดก่อนหรือตอน up-tier date → คำนวณยอดใหม่ด้วย:
Accum Tier Logic ใหม่ = Accum Tier Logic ก่อน void/refund - void/refund amount
แล้วดูว่ายังถึง tier ไหน:
| ผลลัพธ์ | การดำเนินการ |
|---|---|
| ยังถึง tier ปัจจุบัน | คง tier เดิม (ข้อ 3.1) |
| ข้าม tier (accum อยู่ระหว่าง tier ปัจจุบันกับ tier ก่อนหน้า) | Down เป็น tier ก่อนหน้า (ข้อ 3.2) |
| ต่ำกว่า tier ก่อนหน้า | คำนวณใหม่ทั้งหมด + หา tier dates (ข้อ 3.3) |
3.1 — ยังถึง tier ปัจจุบัน → คง tier
หมายเหตุ (จาก code): เมื่อ tier ไม่เปลี่ยน (
newTier.id === currentTierId) code จะ ไม่เข้า block down-tier เลย
ดังนั้น maintain spending จะถูกคำนวณจากcalculateAccumulateMaintainSpending(tierStartedAt, tierEndedAt)ตามปกติ (ไม่ใช่กฎ 4)
แต่ถ้า refund ทำให้ qualifying transaction เปลี่ยน (เช่น up-tier txn ถูก refund แต่ txn อื่นรวมกันยังถึง tier) → maintain จะถูก recalculate จาก txns ที่ completedAt อยู่ในช่วง tierStartedAt-tierEndedAt ซึ่งอาจเป็น 0 ถ้า up-tier txn ถูก refund หมด
ตัวอย่าง:
สถานะ: CROWN (minimumSpending = 300,000)
Accum Tier Logic ก่อน void/refund = 350,000 บาท
void/refund 50,000 บาท (ก่อน up-tier)
→ Accum Tier Logic ใหม่ = 350,000 - 50,000 = 300,000 บาท
300,000 >= 300,000 (CROWN) ✅
→ คง CROWN
3.2 — ต่ำกว่า tier ปัจจุบัน แต่ถึง tier ก่อนหน้า → ดู validity ของ tier ก่อนหน้า
ดูว่า tier ก่อนหน้านั้น valid ไหม (tierEndedAt ยังไม่หมดอายุ):
| ผลลัพธ์ | การดำเนินการ |
|---|---|
| Valid (ยังไม่หมดอายุ) | Down เป็น tier ก่อนหน้า + ใช้ tier dates จากประวัติ |
| ไม่ valid (หมดอายุแล้ว) | Fallback → ไปใช้ logic เดียวกับ ข้อ 3.3 |
ตัวอย่าง (valid):
สถานะ: CROWN (minimumSpending = 300,000)
ประวัติ: SCARLET มี tierEndedAt = Dec 2026 (ยัง valid)
Accum Tier Logic ก่อน void/refund = 320,000 บาท
void/refund 100,000 บาท (ก่อน up-tier)
→ Accum Tier Logic ใหม่ = 320,000 - 100,000 = 220,000 บาท
220,000 < 300,000 (CROWN) ❌
220,000 >= 50,000 (SCARLET) ✅
SCARLET ยัง valid (tierEndedAt ยังไม่หมดอายุ) ✅
→ Down เป็น SCARLET + tierStartedAt/tierEndedAt จากประวัติ
ตัวอย่าง (ไม่ valid):
สถานะ: CROWN (minimumSpending = 300,000)
ประวัติ: SCARLET มี tierEndedAt = Dec 2023 (หมดอายุแล้ว → ไม่ valid)
Accum Tier Logic ก่อน void/refund = 320,000 บาท
void/refund 100,000 บาท (ก่อน up-tier)
→ Accum Tier Logic ใหม่ = 320,000 - 100,000 = 220,000 บาท
220,000 >= 50,000 (SCARLET) ✅ แต่ SCARLET ไม่ valid แล้ว
→ Fallback → scan หา qualifying transaction ภายใน 24 เดือนก่อน up-tier date + หลัง up-tier จนถึงปัจจุบัน (หักลบ refund แล้ว)
หรือถ้าไม่มี → NAVY มี tierStartedAt = วันนี้
3.3 — ต่ำกว่า tier ก่อนหน้า → คำนวณใหม่ทั้งหมด + หา tier dates
Fall back ไปใช้ logic เดียวกัน คือ: scan หา qualifying transaction ภายใน 24 เดือนก่อน up-tier date เพื่อหา tier ปลายทาง
| กรณี | ผลลัพธ์ |
|---|---|
| มี qualifying transaction ภายใน 24 เดือนก่อน up-tier date + หลัง up-tier จนถึงปัจจุบัน ที่ทำให้ถึง tier ปลายทาง (หักลบ refund แล้ว) | tierStartedAt = completedAt ของ transaction นั้น (3.3.1) |
| ไม่มี qualifying transaction ใน window นั้น | Down เป็น NAVY (3.3.2) |
Maintain คำนวณใหม่ตาม ข้อ 4
3.3.1 — มี qualifying transaction ใน 24 เดือนก่อน up-tier + หลัง up-tier จนถึงปัจจุบัน → tierStartedAt = completedAt ของ transaction นั้น
scan หา transaction แรกที่ทำให้ accum สะสม (หักลบ refund แล้ว) ถึง threshold
window = 24 เดือนก่อน up-tier date + หลัง up-tier จนถึงปัจจุบัน
ตัวอย่าง:
ประวัติ tier:
SCARLET → Jan 2023
CROWN → Jul 2023
VEGA → Jan 2024 ← current
สถานะ: VEGA (minimumSpending = 2,000,000)
Accum Tier Logic ก่อน void/refund = 2,100,000 บาท
void/refund 1,800,000 บาท (ก่อน up-tier)
→ Accum Tier Logic หลัง void/refund = 300,000 บาท
300,000 < 2,000,000 (VEGA) ❌
300,000 < 300,000 (CROWN) ❌
300,000 >= 50,000 (SCARLET) ✅
→ Down จาก VEGA → SCARLET (ข้าม CROWN)
หา qualifying transaction ภายใน 24 เดือนก่อน up-tier date + หลัง up-tier จนถึงปัจจุบัน (หักลบ refund แล้ว)
→ สมมติ transaction ที่ทำให้ accum ถึง SCARLET (50,000) อยู่ใน window เกิดเมื่อ Jan 2023
→ tierStartedAt = Jan 2023 ← ใช้ completedAt ของ transaction นั้น
→ tierEndedAt = endOf(Jan 2023 + 2 ปี) = Dec 2025
→ maintain คำนวณใหม่ตาม ข้อ 4
3.3.2 — ไม่มี qualifying transaction ใน 24 เดือนก่อน up-tier → Down เป็น NAVY
ตัวอย่าง:
สถานะ: VEGA (minimumSpending = 2,000,000)
Accum Tier Logic ก่อน void/refund = 2,100,000 บาท (มาจาก transaction ที่ทำให้ up-tier เพียงตัวเดียว)
void/refund transaction นั้น 2,000,000 บาท
→ Accum Tier Logic หลัง void/refund = 100,000 บาท
100,000 < 2,000,000 (VEGA) ❌
100,000 < 300,000 (CROWN) ❌
100,000 < 50,000 (SCARLET) ❌
→ Fallback → scan หา qualifying transaction ภายใน 24 เดือนก่อน up-tier date + หลัง up-tier จนถึงปัจจุบัน (หักลบ refund แล้ว)
→ ไม่มี qualifying transaction ใน window นั้น
→ Down เป็น NAVY มี tierStartedAt = วันนี้
ข้อ 4 — Maintain Spending คำนวณใหม่เมื่อ Down-tier
เมื่อเกิด down-tier ยอด maintain ไม่ reset เป็น 0 แต่คำนวณใหม่จาก:
maintainSpending = SUM transactions ถัดจาก up-tier transaction จนถึงปัจจุบัน
(ไม่รวม up-tier transaction เอง)
ตัวอย่าง:
up-tier transaction: 10,000 บาท (01 Jan 2024) ← ไม่นับ
transactions หลัง up-tier:
ก.พ. 2024 10,000
มี.ค. 2024 10,000
เม.ย. 2024 10,000
พ.ค. 2024 10,000
มิ.ย. 2024 10,000
ก.ค. 2024 10,000
รวม = 60,000 บาท
Legacy: maintainSpending = 0 (reset)
New: maintainSpending = 60,000 บาท ✅
ข้อ 5 — tierStartedAt / tierEndedAt เมื่อ Down-tier
5.1 — มี transaction ทำให้เกิด tier ก่อนหน้า
เมื่อ down-tier แล้ว tier ที่ได้เป็น tier ที่มี transaction ทำให้เกิด (มีประวัติ)
→ ดูว่า tier dates จากประวัติยัง valid ไหม (tierEndedAt ยังไม่หมดอายุ):
| ผลลัพธ์ | การดำเนินการ |
|---|---|
| Valid (ยังไม่หมดอายุ) | Down เป็น tier นั้น + tierStartedAt/tierEndedAt จากประวัติ |
| ไม่ valid (หมดอายุแล้ว) | Fallback → ไปใช้ logic เดียวกับ ข้อ 5.2 |
ตัวอย่าง (valid):
ประวัติ tier:
NAVY → Jan 2022
SCARLET → Jun 2023 ← มี transaction ทำให้เกิด SCARLET
CROWN → Jan 2024
void/refund ทำให้ down จาก CROWN → SCARLET
→ SCARLET มี transaction ทำให้เกิด → ใช้ข้อ 5.1
→ ตรวจ validity: tierEndedAt = Dec 2025 (ยัง valid)
→ ผลลัพธ์: SCARLET มี tierStartedAt = Jun 2023, tierEndedAt = Dec 2025 ✅
ตัวอย่าง (ไม่ valid):
ประวัติ tier:
NAVY → Jan 2022
SCARLET → Jun 2021 ← มี transaction ทำให้เกิด SCARLET
CROWN → Jan 2024
void/refund ทำให้ down จาก CROWN → SCARLET
→ SCARLET มี transaction ทำให้เกิด → ใช้ข้อ 5.1
→ ตรวจ validity: tierEndedAt = Dec 2023 (หมดอายุแล้ว → ไม่ valid)
→ Fallback → ไปใช้ logic เดียวกับ ข้อ 5.2
→ คำนวณใหม่จาก scratch → ผลลัพธ์: SCARLET มี tierStartedAt = วันนี้
5.2 — transaction เดียวถึง tier ปัจจุบันเลย (ไม่มี transaction ทำให้ tier ก่อนหน้า)
เมื่อ down-tier แล้ว tier ที่ได้เป็น tier ที่ไม่เคยมี transaction ทำให้เกิด (transaction เดียวถึงเลย)
→ ต้องคำนวณใหม่จาก scratch แล้วดูว่า Accum ที่เหลือไปลงที่ tier อะไร:
| ผลลัพธ์ | tierStartedAt |
|---|---|
| ไปลง NAVY | วันนี้ |
| ไปลง SCARLET/CROWN/VEGA | วันนี้ |
MT คำนวณใหม่ตาม ข้อ 4
ตัวอย่าง:
ประวัติ tier:
NAVY → Jan 2023
CROWN → Jul 2023 ← ข้าม SCARLET เพราะ transaction เดียวถึง CROWN
CROWN → Jan 2024
ก่อน refund:
CROWN (ACC: 2.1M, MT: 0, START: 1 JAN 24, END: 31 DEC 26)
Refund 1.8M (ก่อน up-tier จริง Jul 2023)
→ Accum ที่เหลือ = 300K = CROWN (ไม่ถึง VEGA แต่ถึง CROWN)
→ CROWN ไม่เคยมี transaction ทำให้เกิด → ใช้ข้อ 5.2
→ คำนวณใหม่จาก scratch → ผลลัพธ์: CROWN มี tierStartedAt = วันนี้
5.2.3 — tier ไม่เปลี่ยน
ตัวอย่าง:
ประวัติ tier:
NAVY → Jan 2023 (START: 1 JAN 23, END: 31 DEC 25)
SCARLET → Jan 2024 (START: 1 JAN 24, END: 31 DEC 26) ← up-tier + current
ก่อน refund:
SCARLET (ACC: 65K, MT: 20K, START: 1 JAN 24, END: 31 DEC 26)
Refund 10K (ก่อน up-tier)
→ Accum ที่เหลือ = 55K = SCARLET (ยังถึง tier ปัจจุบัน)
→ tier ไม่เปลี่ยน → ไม่ต้องหา tierStartedAt ใหม่
→ MT คำนวณใหม่ตามข้อ 4
ตัวอย่างเต็ม: Down-tier จาก Refund
Timeline ของคุณ A:
ต.ค. 2023 ซื้อ 10,000 → accum = 10,000
พ.ย. 2023 ซื้อ 10,000 → accum = 20,000
ธ.ค. 2023 ซื้อ 10,000 → accum = 30,000
ม.ค. 2024 ซื้อ 10,000 → accum = 40,000
ซื้อ 10,000 → accum = 50,000 ✅ ขึ้น SCARLET ← up-tier txn
ก.พ.–ก.ค. 2024 ซื้อ 10,000 × 6 = 60,000 (หลัง up-tier)
Accum Tier Logic = 40,000 + 10,000 + 60,000 = 110,000 บาท
เกิด Refund: คืนสินค้าที่ซื้อ ธ.ค. 2023 (ก่อน up-tier) มูลค่า 70,000 บาท
Accum Tier Logic ใหม่ = 110,000 - 70,000 = 40,000 บาท
CROWN.minimumSpending = 300,000 → 40,000 < 300,000 ❌
SCARLET.minimumSpending = 50,000 → 40,000 < 50,000 ❌
NAVY.minimumSpending = 0 → 40,000 >= 0 ✅ → Down เป็น NAVY
maintainSpending ใหม่ = SUM txns หลัง up-tier = 60,000 บาท (ไม่ reset เป็น 0)
NAVY ไม่เคยมี transaction ทำให้เกิด → ใช้ข้อ 5.2
→ scan หา qualifying transaction ภายใน 24 เดือนก่อน up-tier + หลัง up-tier จนถึงปัจจุบัน (หักลบ refund แล้ว)
→ ไม่มี qualifying transaction ใน window
→ ผลลัพธ์: NAVY มี tierStartedAt = วันนี้
ข้อ 6 — Adjust จาก Admin
Adjust คือ Admin หักยอดสมาชิกด้วยมือ (DEDUCT) — ใช้ logic เดียวกับ Void/Refund ทุกประการ คือ:
- ถ้า adjust เกิดหลัง up-tier date → ไม่ down-tier (ข้อ 2)
- ถ้า adjust เกิดก่อนหรือตอน up-tier date → ใช้ Accum Tier Logic (ข้อ 3)
- Maintain spending คำนวณใหม่ (ข้อ 4)
- tierStartedAt/tierEndedAt ตาม 5.1 / 5.2
สิ่งที่ต่างจาก Refund: sequence การบวกยอดใช้ลำดับตาม purchase date ไม่ใช่วันที่ปรับ
ตัวอย่าง:
ประวัติ tier:
NAVY → Jan 2023
SCARLET → May 2024
Adjust ณ วันที่ 1 Apr ปรับ transaction ที่ซื้อ 1 Feb จำนวน 20K
Sequence การบวกคือ 1+2+4+3 ตาม purchase date
= 60K = SCARLET → tier ไม่เปลี่ยน (ยังถึง tier ปัจจุบัน)
Maintain = 30K
ตัวอย่าง (กรณี adjust ทำให้ down-tier):
ประวัติ tier:
NAVY → Jan 2023
SCARLET → May 2024
Adjust ณ วันที่ 1 Apr ปรับยอดลง 30K (ก่อน up-tier)
→ Accum ที่เหลือ = 40K → SCARLET (50K) → คง SCARLET
Maintain = 30K
สรุปเปรียบเทียบ Legacy vs New
| Scenario | Legacy | New |
|---|---|---|
| Void/Refund/Adjust ก่อนหรือตอน up-tier | อาจ down-tier | down-tier ตามข้อ 3 (ดูผลลัพธ์ Accum Tier Logic) |
| Void/Refund/Adjust หลัง up-tier | อาจ down-tier | ไม่ down-tier (ข้อ 2) |
| Transaction expired (หลุด 24 เดือน) | อาจ down-tier | ไม่ down-tier (ข้อ 1) |
| Maintain หลัง down-tier | reset = 0 | คำนวณจาก transactions หลัง up-tier (ข้อ 4) |
| tierStartedAt หลัง down-tier | now | ขึ้นกับ 5.1 / 5.2 |
Implementation Details (จาก Codebase)
Section นี้ document behavior จริงของ code ที่ implement แล้ว
Guard Condition (กฎ 2)
// member.entity.ts → addNewRefundSalesTransaction()
const isRefundBeforeOrAtUpTier = !DateTimeHelper.isAfter(
salesTransaction?.completedAt ?? now,
this.tierStartedAt,
);
isAfter(a, b)= true ถ้า a > b (strict)- ดังนั้น
!isAfter(completedAt, tierStartedAt)= true ถ้าcompletedAt <= tierStartedAt - ผลลัพธ์: refund ของ txn ที่
completedAt > tierStartedAt→ ไม่เข้า down-tier logic
Accum Tier Logic (กฎ 3)
// member.entity.ts → calculateAccumTierLogicSpending()
const windowStart = startOf(subtractTime(upTierDate, 24, "month"), "month");
const bufferedUpTierDate = addTime(upTierDate, 10, "minute"); // inclusive up-tier txn
const bufferedNow = addTime(now, 10, "minute");
// window 1: [windowStart, bufferedUpTierDate) — รวม up-tier txn เอง
const preUpTierAccum = calculateAccumulate(windowStart, bufferedUpTierDate);
// window 2: (bufferedUpTierDate, bufferedNow) — ไม่รวม up-tier txn
const postUpTierAccum = calculateAccumulate(bufferedUpTierDate, bufferedNow);
return preUpTierAccum + postUpTierAccum;
หมายเหตุ: ใช้ isBetween (exclusive ทั้งสองด้าน) ดังนั้น buffer +10 นาทีเพื่อให้ txn ที่ตรงขอบถูกรวม
Tier Decision + Cap
// member.entity.ts → calculateTierAdjustmentWithNewLogic()
// 1. หา tier สูงสุดที่ accumTierLogic >= minimumSpending
// 2. apply co-brand floor (minimumTier) ถ้ามี
// 3. cap ไม่ให้สูงกว่า tier ปัจจุบัน (capToCurrentTier)
// member.entity.ts → capToCurrentTier()
private capToCurrentTier(tier, current): TierEntity | undefined {
if (tier && tier.minimumSpending > current.minimumSpending) {
return current; // ไม่ให้สูงกว่า current
}
return tier ?? this.tier;
}
Tier Dates (กฎ 5)
// กฎ 5.1: findPreviousTierInfo(newTierId)
// - หา MemberTierHistory ล่าสุดที่ toTierId === newTierId
// - ตรวจว่า tierEndedAt ยังไม่หมดอายุ (now < tierEndedAt)
// - ถ้า valid → return { tierStartedAt, tierEndedAt } จาก history
// กฎ 5.2: findQualifyingTransactionDate(threshold)
// - ถ้า threshold <= 0 (เช่น NAVY) → return null ทันที (base tier ไม่ต้องหา qualifying)
// - window = [24mo ก่อน up-tier, now+10min]
// - scan txns จากเก่า→ใหม่ สะสมยอด (หักลบ refund)
// - หยุดเมื่อ running >= threshold → return completedAt ของ txn นั้น
// - ถ้าไม่มี → return null → tierStartedAt = now
Maintain Spending (กฎ 4)
เมื่อ Down-tier (newTier.id !== currentTierId)
// member.entity.ts → addNewRefundSalesTransaction()
// หลัง updateMemberTier แล้ว:
this.setAccumulateMaintainSpending(
this.calculateAccumulateMaintainSpending(newTierStartedAt, newTierEndedAt),
);
Logic: maintain = SUM txns ที่ completedAt อยู่ในช่วง (newTierStartedAt, newTierEndedAt) (exclusive ทั้งสองด้านจาก isBetween)
เมื่อ Tier ไม่เปลี่ยน (newTier.id === currentTierId)
// member.entity.ts → addNewRefundSalesTransaction()
const qualifyingDate = this.findQualifyingTransactionDate(
memberCurrentTier.minimumSpending ?? 0,
);
if (qualifyingDate) {
// maintain = SUM txns หลัง qualifying txn (strict >)
this.setAccumulateMaintainSpending(
this.calculatePostUpTierMaintainSpending(qualifyingDate),
);
} else {
this.setAccumulateMaintainSpending(0);
}
Logic: หา qualifying txn ใหม่ (txn ที่ทำให้ accum สะสมถึง tier threshold หลังหักลบ refund) แล้ว maintain = SUM txns ที่ completedAt > qualifyingDate
calculatePostUpTierMaintainSpending
// member.entity.ts → calculatePostUpTierMaintainSpending(upTierDate)
// - SUM sales ที่ completedAt > upTierDate (strict >)
// - SUM refunds ที่ original sale completedAt > upTierDate
// - return max(0, salesSum + refundSum)
สำคัญ: ใช้ isAfter (strict >) ดังนั้น txn ที่ completedAt === upTierDate ไม่ถูกนับ
กรณี tier ไม่เปลี่ยน (newTier.id === currentTierId)
เมื่อ Accum Tier Logic ยังถึง tier ปัจจุบัน:
- เข้า
elseblock → หา qualifying txn ใหม่ - maintain = SUM txns หลัง qualifying txn
- ถ้าไม่มี qualifying txn → maintain = 0
- Guard: ทำเฉพาะ tier type = NORMAL (invitation/import ไม่เข้า)
Non-NORMAL Tier (Invitation / Import)
// calculateTierAdjustmentWithNewLogic()
if (!memberCurrentTier || memberCurrentTier.type !== TierType.NORMAL) {
return this.tier; // ไม่ down-tier
}
VVIP, VEGA (invitation), Co-brand (import) ที่ type !== NORMAL → return tier เดิมเสมอ → ไม่ down-tier
QA Test Case Discrepancy (TC 10, 11, 26)
Test case 10, 11, 26 ยังมีตัวเลข maintain ที่ต้อง verify:
TC10: Accum Tier Logic = 40,000 — ถ้า SCARLET threshold > 40,000 → code จะ down tier แต่ expected ไม่ down
TC11: qualifying txn logic อาจให้ maintain ไม่ตรง expected (7,000)
TC26: expected maintain = 2,000 แต่ txn ที่ให้ค่านี้ไม่อยู่ในช่วง newTierStartedAt
สาเหตุที่เป็นไปได้:
- SCARLET threshold ใน test env ≠ 50,000 (อาจเป็น 40,000 หรือ 45,000)
- Test case มี txn/data ที่ไม่ได้แสดงใน pre-condition
- Business rule เพิ่มเติมที่ยังไม่ได้ document
Status: ต้อง verify กับ QA/BA