Private Docs

Inline-Approve Fix — ทำไม Submit บาง Flow ต้อง "เร่ง" Chain ให้จบก่อนตอบ FE

companies หายจาก /users/me ทันทีหลัง onboard เพราะ Submit→Approve เป็น async gap — สรุปสาเหตุ, ทำไม fix เป็น hardcoded whitelist ไม่ใช่ dynamic config, และ flow ไหนยังมีบั๊กเดิมอยู่

อัปเดต: 2026-08-20

เอกสารนี้อธิบายบั๊ก companies หายจาก /users/me ทันทีหลัง onboard (CUSTOMER_FIXED_ONBOARDING) — สาเหตุ, ทาง fix ที่เลือก, และทำไม fix เป็น hardcoded whitelist ไม่ใช่ dynamic config (คำถามที่เกิดขึ้นบ่อยตอนอ่าน diff)

1. อาการ

Onboard ผ่าน CUSTOMER_FIXED_ONBOARDING เสร็จ (OTP verify), FE redirect ไป /home ทันที แต่ GET /users/me ครั้งแรกคืน companies: [] — refresh 1 ครั้งค่อยเห็นถูกต้อง

2. สาเหตุ — ช่องว่างระหว่าง Submitted กับ Approved

ดู state machine ใน Flows §: Submitted → Approved (4eye OK) → Finalized (auto)สำหรับ flow ที่ “ไม่มี 4-eye” (ดูตารางด้านล่าง) ไม่มีแอดมินกด approve จริง ระบบเลยต้องมีอะไรสักอย่างปลอมตัวเป็นแอดมินกด approve ให้อัตโนมัติ — นั่นคือ AutoApproveWorker

AutoApproveWorker เป็น Service Bus consumer แยก process: Submit แค่เปลี่ยนสถานะเป็น Submitted + publish event เข้า queue แล้วตอบ FE ทันที ส่วน event ที่ publish ไปนั้น AutoApproveWorker ต้องรอคิว มารับ ค่อยสั่ง Approve จริง (ซึ่งถึงจะรัน AdvanceAutomaticAsync ที่สร้าง UserCompanyMapping ฯลฯ) — ระหว่างที่ event ยังไม่ถึง worker, instance ค้างที่ Submitted โดยที่ automatic chain (สร้าง user + ผูก company) ยังไม่รันเลย

FE redirect ทันทีที่ได้ response ของ Submit กลับมา (status Submitted) — เร็วกว่า worker เสมอ เพราะ worker ต้องรอ SB round-trip จริง จึงชนกับจังหวะที่ chain ยังไม่จบ

sequenceDiagram
  participant FE
  participant API as OnboardingV2Controller
  participant Engine as FlowEngine (Submit branch)
  participant SB as Service Bus
  participant Worker as AutoApproveWorker (แยก process)

  FE->>API: POST .../OtpVerificationStep/actions/Submit
  API->>Engine: ExecuteActionAsync(Submit)
  Engine->>Engine: TransitionToSubmitted + commit DB
  Engine-->>SB: publish flow-submitted-for-approval
  Engine-->>API: nav (status: SUBMITTED)
  API-->>FE: 200 OK
  FE->>FE: redirect /home
  FE->>API: GET /users/me
  API-->>FE: companies: [] (ยังไม่มี mapping!)
  Note over SB,Worker: async, กี่วินาทีไม่แน่นอน
  SB-->>Worker: deliver event
  Worker->>Engine: Approve → AdvanceAutomaticAsync (สร้าง mapping จริง)

3. ทำไมมีแค่บาง flow ที่เป็น (และทำไม hardcode ไม่ใช่ dynamic)

จากตาราง Flows §เปรียบเทียบสรุป:

FlowApproval (4-eye)เสี่ยงบั๊กนี้ไหมเหตุผล
NEW_REQUESTOR / RETURNING_REQUESTOR✅ มีไม่เสี่ยงแอดมินกด Approve เอง — async เป็นเรื่องปกติ (รอคนจริง) ไม่ใช่บั๊ก
RETURNING_CUSTOMERไม่มี (0 interactive)ไม่เสี่ยงไม่มี Submit เลย — Finalized ทันทีระหว่าง Start (ดู Customer Onboarding §2) จึงไม่มีช่องว่าง Submitted→Approved ให้ race
STANDARD_CUSTOMER_ONBOARDINGไม่มีเสี่ยงเหมือนกัน — 🔴 ยังไม่ได้แก้อยู่ใน AutoApproveWorker whitelist เดิมเหมือนกัน แต่ chain/latency profile ยังไม่ผ่าน safety review — ดู §5
CUSTOMER_FIXED_ONBOARDINGไม่มีเสี่ยง — ✅ แก้แล้ว (PR #2345)scope ของ fix นี้

คำตอบตรงๆ ว่าทำไม hardcode: เพราะ “เสี่ยงบั๊กนี้ไหม” ไม่ได้ตัดสินจากแค่ “มี 4-eye หรือเปล่า” (แค่ 2 บรรทัดข้างบนก็พอเดา) — แต่ต้องเช็คต่ออีกชั้นว่า flow นั้น “ปลอดภัยพอจะเร่ง chain ให้รันตอน Submit เลย” ไหม เช่น:

  • แต่ละ step handler ใน chain idempotent จริงไหม (รันซ้ำได้ไม่ error/ไม่สร้างข้อมูลซ้ำ)
  • chain มี call ออกนอกระบบ (Entra Graph) ช้าแค่ไหน — ย้ายมารันใน request เดียวกับที่ user รออยู่ เสี่ยงชน gateway timeout ไหม
  • flow นั้นมี business rule พิเศษที่ concurrent access แล้วพังไหม (STANDARD_CUSTOMER_ONBOARDING มี JuristicBinding conflict-check ที่ยังไม่ได้ตรวจกับ path นี้)

3 ข้อนี้เป็นงานตรวจเฉพาะ flow ทำอัตโนมัติ/generic ไม่ได้ — ต่อให้เปลี่ยนจาก C# whitelist เป็นคอลัมน์ config ในตาราง FlowDefinition (เช่น SubmitApprovalMode: Inline|Async) คนก็ยังต้องมานั่งตรวจ 3 ข้อนี้ทีละ flow ก่อนเปิด flag อยู่ดี — hardcode ตอนนี้ไม่ได้ทำให้งานเพิ่มขึ้น แค่ยังไม่ได้ย้ายที่เก็บ setting ไปเป็น data เท่านั้น

4. Fix ที่เลือก (CUSTOMER_FIXED_ONBOARDING เท่านั้น)

FlowEngine’s Submit branch: หลัง commit Submitted แล้ว ก่อนpublish event เข้า queue — ถ้า flow code อยู่ใน whitelist ใหม่ (InlineApproveFlowCodes, แยกจาก AutoApproveWorker.AutoApproveFlowCodes เด็ดขาด) ให้เรียก approve logic เดิม (TransitionFlowInstanceCommand) ทันทีในคำขอเดียวกัน ผ่าน DI scope ใหม่ (กัน DbContext ชนกัน) แล้ว publish event เข้า queue เหมือนเดิมเสมอเป็น fallback

sequenceDiagram
  participant FE
  participant Engine as FlowEngine (Submit branch)
  participant Inline as Inline Approve (scope ใหม่)
  participant SB as Service Bus

  Engine->>Engine: TransitionToSubmitted + commit DB
  alt flow อยู่ใน InlineApproveFlowCodes
    Engine->>Inline: Approve ทันที (AdvanceAutomaticAsync)
    Inline-->>Engine: mapping สร้างเสร็จ, commit แล้ว
  end
  Engine-->>SB: publish event เสมอ (fallback ถ้า inline ล้ม)
  Engine-->>FE: nav (status: SUBMITTED)
  Note over FE: redirect แล้ว /users/me เห็น company ครบ

เหตุผลที่ไม่ใส่ lock/timeout กันชน (คำถามที่เจอบ่อยรองลงมา): FlowInstance ไม่มี concurrency token เลย และ dedupe store มี TTL 7 วันไม่มี recovery path — lock ที่ตายกลางทางจะทำให้ instance ค้างถาวรกู้ไม่ได้ แย่กว่าของเดิม จึงใช้ลำดับ (inline เสร็จก่อน publish เสมอ) แทน lock — รายละเอียดเต็มอยู่ใน PR description

5. งานที่เหลือ — ยังไม่ได้ทำ (สำคัญ)

  • 🔴 STANDARD_CUSTOMER_ONBOARDING มีบั๊กเดียวกันน่าจะจริง แต่ยังไม่ได้แก้ — ตั้งใจตัดออกจาก scope PR นี้ เพราะ flow นี้มี JuristicBinding conflict-check ที่เป็น live code (ต่างจาก CUSTOMER_FIXED_ONBOARDING ที่ guard ไม่ทำงานเพราะไม่มี step นี้) และ chain latency ยังไม่เคยวัด — ต้อง safety review รอบใหม่แยกต่างหากก่อนเพิ่มเข้า InlineApproveFlowCodes
  • ยังไม่ได้วัด latency จริง ของ inline chain (มี Entra Graph call 1 ครั้ง) ผ่าน headed-browser E2E — ต้องทำก่อน promote ผ่าน dev (ถ้าช้าใกล้ gateway timeout ต้องพิจารณาทางอื่นแทน เช่น FE รอสถานะ)

6. อ้างอิง