Tier Flow Mapping — Non-Technical ↔ Technical

ไฟล์นี้ช่วย bridge ระหว่างภาษาธุรกิจ (overview) กับภาษา engineering (fullsystem)
อ่านคู่กับ legacy-tier-flow-overview.md และ legacy-fullsystem-tier-flows.md


ภาพรวม: แต่ละสถานการณ์ตรงกับ Flow ไหน?

สถานการณ์ (ภาษาคน) Flow # ชื่อ Flow (Technical) Trigger
ลูกค้าซื้อของแล้ว Tier ขึ้น Flow 1 Sales Transaction → Tier Upgrade Kafka event จาก POS
ลูกค้าคืนสินค้าแล้ว Tier ลง Flow 2 Refund / Void → Tier Downgrade Kafka event จาก POS
ระบบคำนวณยอดสะสมใหม่แบบ batch Flow 3 Batch Accum Recalculate Temporal cron (scheduled)
ครบรอบ 2 ปี — คง Tier หรือลด Tier Flow 4 Annual Maintain Tier Temporal cron (daily check)
ตรวจสอบ Tier หลังครบรอบ Flow 5 Reconcile Member Tier ต่อจาก Flow 4 อัตโนมัติ
Admin ปรับยอดด้วยมือ Flow 6 Manual Adjustment (Admin) REST API
ได้/ยกเลิกบัตร Co-Brand Flow 7 Co-Brand Card → Tier Change File import / Admin / Kafka
พนักงานออกบริษัท Flow 8 Staff Exit Import Admin CSV upload
Import สมาชิกใหม่เข้าระบบ Flow 9 Member Import Admin CSV upload

รายละเอียดแต่ละ Flow

Flow 1 — ลูกค้าซื้อของแล้ว Tier ขึ้น

ภาษาคน:

ทุกครั้งที่ลูกค้าซื้อของ ระบบจะรับข้อมูลทันที คำนวณยอดสะสม 24 เดือนใหม่ ถ้าถึง threshold ของ Tier สูงกว่า → ขึ้น Tier ทันที

Technical:

  • Trigger: Kafka event SalesTransactionCompletedDomainEvent จาก External Sales System
  • Entry point: SalesTransactionControllerSalesTransactionService.createSalesTransaction()
  • Core logic: MemberEntity.addNewSalesTransaction()calculateAccumulateSpending()calculateTierToUpgrade()
  • Mutation: updateMemberTier() → บันทึก DB + emit MemberTierUpgradedDomainEvent
  • Downstream: Engagement Service, Notification Service รับ event ต่อ

Flow 2 — ลูกค้าคืนสินค้าแล้ว Tier ลง

ภาษาคน:

เมื่อลูกค้าคืนสินค้าหรือยกเลิกรายการ ระบบหักยอดสะสมออก ถ้ายอดต่ำกว่า threshold ของ Tier ปัจจุบัน → ลด Tier ทันที (เฉพาะกรณีที่รายการนั้นเกิดขึ้นหลังจากวันที่ได้ Tier ปัจจุบัน)

Technical:

  • Trigger: Kafka event SalesTransactionRefundedDomainEvent หรือ SalesTransactionVoidedDomainEvent
  • Entry point: SalesTransactionControllerRefundSalesTransactionService.createRefundSalesTransaction()
  • Core logic: MemberEntity.addNewRefundSalesTransaction()calculateAccumulateSpending()calculateTierAdjustment()
  • เงื่อนไขพิเศษ: ตรวจว่า salesTxn.completedAt อยู่หลัง tierStartedAt ก่อนถึงจะ downgrade
  • Mutation: updateMemberTier() → emit MemberTierDowngradedDueToRefundDomainEvent

Flow 3 — ระบบคำนวณยอดสะสมใหม่แบบ batch

ภาษาคน:

ระบบมี job ที่รันตามกำหนดเวลา เพื่อคำนวณยอดสะสมของสมาชิกใหม่ทั้งหมด ป้องกันกรณีที่ยอดอาจคลาดเคลื่อนจาก real-time

Technical:

  • Trigger: Temporal Cron Schedule หรือ Admin API
  • มี 2 sub-path:
    • 3A — อัปเดตเฉพาะ accumulateSpending (rolling window boundary เปลี่ยน) ไม่เปลี่ยน Tier
    • 3B — คำนวณใหม่ทั้งหมดจาก DB aggregation (8 parallel queries) + อาจเปลี่ยน Tier
  • Entry point: updateAccumulateSpendingWorkflow / recalculateSpendingTierWorkflow (Temporal)
  • Core logic (3B): MemberEntity.processSpendingAndTierUpdate() — path แยกจาก updateMemberTier()
  • Scale: ประมวลผลเป็น chunk 500 → 100 members ต่อรอบ

Flow 4 — ครบรอบ 2 ปี: คง Tier หรือลด Tier

ภาษาคน:

ทุก Tier มีอายุ 2 ปี เมื่อครบกำหนด ระบบจะดูว่าลูกค้าซื้อของในรอบนี้มากพอไหม ถ้าพอ → คง Tier เดิม ถ้าไม่พอ → ลด Tier ลง แล้วเริ่มรอบใหม่ 2 ปี

Technical:

  • Trigger: Temporal Schedule ตรวจ tierEndedAt < now ทุกวัน
  • Entry point: maintainTierWorkflowMaintainTierService.processCycleEndedMembers()
  • Core logic: MemberEntity.maintainTier()calculateNewMinimumTier()calculateNewTierFromAccumulateMaintainSpending()
  • ใช้ accumulateMaintainSpending (ยอดในรอบ) เทียบกับ tier.maintainSpending
  • Tier ใหม่เริ่ม: tierStartedAt = Jan 1, tierEndedAt = Dec 31 ปีถัดไป
  • Scale: chunk 10,000 → 100 members ต่อรอบ
  • หลังเสร็จ: trigger Flow 5 อัตโนมัติ

Flow 5 — ตรวจสอบ Tier หลังครบรอบ (Reconcile)

ภาษาคน:

หลังจากกระบวนการต่ออายุ Tier เสร็จ ระบบจะตรวจซ้ำอีกรอบ เพื่อจับกรณีที่ลูกค้าควรได้ Tier สูงกว่าแต่ยังไม่ได้รับ แล้วปรับให้ถูกต้อง

Technical:

  • Trigger: ต่อจาก maintainTierWorkflow อัตโนมัติ
  • Entry point: reconcileMemberTierWorkflowReconcileMemberTierService.processReconciliationForMembers()
  • Core logic: MemberEntity.adjustTier()calculateTierAdjustment()calculateNewTierStartDateFromSalesTransactionPeriod()
  • ข้อสำคัญ: Flow นี้ ขึ้น Tier เท่านั้น ไม่ลง Tier
  • ถ้า Tier ปัจจุบันเป็น INVITATION type → ข้ามไป ไม่ปรับ

Flow 6 — Admin ปรับยอดด้วยมือ

ภาษาคน:

Admin สามารถเพิ่มหรือหักยอดสะสมให้ลูกค้าได้โดยตรงผ่านหน้า Backoffice ซึ่งอาจทำให้ Tier เปลี่ยนได้เช่นกัน

Technical:

  • Trigger: REST API POST /admin/members/:id/adjust-transaction
  • Entry point: AdjustSalesTransactionService.createWithTransaction()
  • ไม่มี logic ของตัวเอง — delegate ต่อ:
    • ADD → เรียก SalesTransactionService (เหมือน Flow 1 ทุกอย่าง)
    • DEDUCT → เรียก RefundSalesTransactionService (เหมือน Flow 2 ทุกอย่าง)
  • Audit: emit MemberUpdatedLogDomainEvent เพิ่มเติม

Flow 7 — ได้/ยกเลิกบัตร Co-Brand

ภาษาคน:

บัตรเครดิต Co-Brand บางใบมีสิทธิ์กำหนด “Tier ขั้นต่ำ” ให้ลูกค้า ถ้าได้บัตรที่ให้ Tier สูงกว่าปัจจุบัน → ขึ้น Tier ทันที ถ้ายกเลิกบัตร → คำนวณ Tier ใหม่จากยอดสะสม อาจลงได้

Technical:

  • มี 3 sub-path:
    • 7A — Import file จากธนาคาร (CSV): MemberCoBrandCardImportServiceMemberEntity.updateMemberLinkCoBrand()
    • 7B — Admin cancel/inactivate บัตรเดี่ยว: MemberCoBrandCardServiceMemberEntity.coBrandCardCanceled() / coBrandCardInactivated()
    • 7C — Kafka event CoBrandInactivatedDomainEvent (ระงับทั้ง brand): loop ทุก member ที่ถือบัตรนั้น
  • Core logic: calculateNewMinimumTier()calculateNewTier()updateMemberTier()
  • Events: MemberTierUpgradedCoBrandDomainEvent, MemberTierDowngradedCoBrandCanceledDomainEvent, MemberTierDowngradedCoBrandInactiveDomainEvent

Flow 8 — พนักงานออกบริษัท

ภาษาคน:

พนักงานที่ได้ Tier พิเศษ (CRYSTAL) จากสถานะพนักงาน เมื่อออกจากบริษัทจะถูกปรับ Tier ใหม่ตามยอดสะสมปกติ

Technical:

  • Trigger: Admin upload CSV → POST /admin/staff-exit/import
  • Entry point: ImportStaffExitService → Temporal importStaffExitWorkflow
  • Core logic: MemberEntity.processStaffExit() → ลบ staffProfilecalculateNewTier()updateMemberTier()
  • Validation: ตรวจว่า Tier ปัจจุบันต้องเป็น CRYSTAL ก่อน และ cross-check ชื่อ/staffId
  • Event: MemberTierAssignedDueToLeftTheCompanyDomainEvent

Flow 9 — Import สมาชิกใหม่เข้าระบบ

ภาษาคน:

Admin สามารถ import รายชื่อสมาชิกพร้อม Tier ที่กำหนดมาให้ผ่านไฟล์ CSV ระบบจะสร้างสมาชิกใหม่หรืออัปเดตสมาชิกที่มีอยู่แล้ว

Technical:

  • Trigger: Admin upload CSV → POST /admin/members/import
  • Entry point: ImportMemberService → Temporal importMemberWorkflow
  • มี 2 sub-path:
    • สมาชิกใหม่: MemberEntity.create() — ยอดสะสมเริ่มต้น = 0, Tier ตาม CSV
    • สมาชิกเดิม: MemberEntity.importUpdate() — ป้องกัน downgrade (INVITATION→NORMAL ถูก block)
  • ข้อควรระวัง: yearsToMaintain hardcoded เป็น 2 ใน import path (ไม่อ่านจาก config)
  • Events: MemberTierAssignedDomainEvent, MemberTierUpdatedDomainEvent

Core Functions ที่ทุก Flow ใช้ร่วมกัน

สำหรับคนที่อยากเข้าใจว่า “ถ้าแก้ตรงนี้ จะกระทบอะไรบ้าง”

ชื่อ Function                              ใช้ใน Flow
─────────────────────────────────────────────────────────────
calculateAccumulateSpending()              1, 2, 5, 6, 7, 8
calculateAccumulateMaintainSpending()      1, 2 (และทุก flow ที่เรียก updateMemberTier)
calculateTierToUpgrade()                   1, 6(ADD)
calculateTierAdjustment()                  2, 5, 6(DEDUCT)
calculateNewTier()                         7, 8
calculateNewTierFromAccumulateMaintainSpending()   4
calculateNewMinimumTier()                  4, 7, 9(สมาชิกเดิม)
updateMemberTier()          ← CRITICAL    1, 2, 4, 5, 6, 7, 8, 9(สมาชิกเดิม)
processSpendingAndTierUpdate()             3B (path แยก ไม่ผ่าน updateMemberTier)
create()                                   9(สมาชิกใหม่)

กฎง่ายๆ:

  • แก้ calculateAccumulateSpending() → กระทบ Flow 1, 2, 5, 6, 7, 8
  • แก้ updateMemberTier() → กระทบเกือบทุก Flow ยกเว้น 3B และ 9(ใหม่)
  • Flow 3B และ Flow 9(ใหม่) ใช้ path แยก ต้องระวังเป็นพิเศษ

Domain Events ที่ระบบ Downstream รับต่อ

Event เกิดจาก Flow ความหมาย
MemberTierAssignedDomainEvent 9 สมัครสมาชิกใหม่ / import
MemberTierUpgradedDomainEvent 1, 5 Tier ขึ้นจากยอดซื้อ
MemberTierDowngradedDueToRefundDomainEvent 2 Tier ลงจากการคืนสินค้า
MemberTierMaintainedAfterReviewedDomainEvent 4 คง Tier เดิมหลังครบรอบ
MemberTierDowngradedAfterReviewedDomainEvent 4 Tier ลงหลังครบรอบ
MemberTierUpgradedCoBrandDomainEvent 7A Tier ขึ้นจากบัตร Co-Brand
MemberTierDowngradedCoBrandCanceledDomainEvent 7B Tier ลงจากยกเลิกบัตร
MemberTierDowngradedCoBrandInactiveDomainEvent 7B, 7C Tier ลงจากระงับบัตร
MemberTierAssignedDueToLeftTheCompanyDomainEvent 8 พนักงานออกบริษัท
MemberTierUpdatedDomainEvent ทุก Flow ที่มี tier change Generic event ทุกครั้งที่ Tier เปลี่ยน

Downstream ที่รับ event: Engagement Service, Notification Service