Private Docs

E2E Sequence Diagrams — ทุก Scenario

Sequence diagram ทุก scenario ของ E2E onboarding/approval (5 service) + gap analysis แยกทีม — verified จาก source code, มาร์ค ✅/❌ ทุกเส้น

อัปเดต: 2026-06-23

วันที่: 2026-06-23 verified จาก source code โดยตรงทั้ง 5 service · ทุก diagram ใช้สำหรับ discussion กับ dev team ทุกทีม

AC ของเอกสารชุดนี้:

  1. แสดง business flow ที่ถูกต้อง ของ E2E onboarding/approval — เห็นว่าจริง ๆ มีอะไรบ้าง วิ่งจากไหนไปไหน
  2. เปิดเผยว่า code ส่วนไหนยังไม่ได้ develop (gap analysis ตามทีม)

Services / Actors:

  • RQ = Requestor (ผู้ยื่นคำขอ)
  • US = UserService (Lego Engine)
  • OC = OrchestratorService
  • WF = WorkflowService
  • TS = TaskService
  • NS = NotificationService
  • MK = Maker (ผู้ตรวจสอบ — หยิบงานรอบแรก)
  • AP = Approver (ผู้อนุมัติ — หยิบงานรอบสอง)
  • AC = Maker หรือ Approver (ผู้ที่หยิบงานอยู่ — ใช้ใน scenario ที่ทำได้ทั้งสอง role)

สัญลักษณ์ใน diagram:

  • ->> / -->> = connection ที่ implement แล้ว และทำงานได้
  • --x = connection ที่ ยังไม่มี (missing/ต้อง develop)
  • rect rgb(232,248,232) (เขียว) = ส่วนที่พร้อมใช้งาน
  • rect rgb(255,243,224) (ส้ม) = ส่วนที่ทำได้บางส่วน / มี caveat
  • rect rgb(255,232,232) (แดง) = ส่วนที่ขาดหาย ต้อง develop

1. Canonical Status Model (status ที่ใช้คุยร่วมกัน)

นี่คือ status กลางที่ทุก service ต้องยึดร่วมกัน (final แล้ว) — แบ่งตามจุดที่เกิดและ role:

กลุ่มStatusความหมาย
เกิดที่ LegoDraftบันทึกร่าง
Submittedอยู่ระหว่างดำเนินการ
Role = Maker (Workflow + Task)InReviewอยู่ระหว่างตรวจสอบ (maker หยิบงานแล้ว)
Reviewedอยู่ระหว่างพิจารณา (maker ส่งต่อ approval แล้ว)
Role = Approval (Workflow + Task)InApproveกำลังพิจารณา (approval หยิบงานแล้ว)
Approvedอนุมัติ
Maker + Approval ทำได้ทั้งคู่Rejectedไม่อนุมัติ
Resubmittedแก้ไขแล้ว อยู่ระหว่างตรวจสอบ
Reworkส่งกลับแก้ไข
Cancelledยกเลิก
Closedปิดการใช้งาน

status บางตัวที่ Lego มีแต่ตัวกลางไม่มี (Finalized, Abandoned) — ปล่อยไปก่อน ไม่นำมา map

1.1 Mapping: canonical vs code จริงแต่ละ service

แยก 2 มิติ: “ประกาศ constant ไว้แล้วหรือยัง” vs “handler เขียนค่านี้ตอน transition ที่ถูกต้องหรือยัง” — ช่องว่างซ่อนอยู่ที่มิติที่สอง

CanonicalLego: ประกาศLego: set ถูกจุดWF: ประกาศWF: set ถูกจุดTask (Progress)
Draft
Submitted"Submitted"NOTSTARTED
InReview✅ (จาก progress-update)✅ (comment “หยิบงานแล้ว”)❌ เขียน "Pickup" แทนPickup
Reviewed✅ (จาก progress-update)✅ (comment “Maker Approve แล้ว”)❌ ไม่มี handler เขียน
InApproveไม่มีใน enumไม่มี constant
Approved❌ เขียน "Completed" แทนCOMPLETED
Rejected"Rejected"(ใช้ CANCEL/close)
Resubmitted✅ (ReSubmitted)❌ resubmit ไม่ set DocStatusNOTSTARTED
Rework"Rework"REWORK
Cancelled✅ (Cancelled)⚠️ ไม่มี cancel handler ที่ publishCANCEL
Closed⏳ design-undefined⏳ design-undefined

ข้อสังเกตสำคัญ:

  • WF ประกาศ InReview/Reviewed/Approved ไว้ใน DocumentStatus.cs พร้อม comment ที่ตรงกับโมเดลนี้เป๊ะ — แต่ ไม่มี handler ตัวไหนเขียนค่าพวกนี้จริง (pickup เขียน "Pickup", approve เขียน "Completed") → นี่คือ gap หลักของ AC#2
  • InApprove คือ status เดียวที่ ไม่มีเลยทุก service (ทั้ง enum/constant และ handler) → เป็น dev ใหม่ 100%

2. ภาพรวม: ทำแล้ว vs ยังไม่ทำ (จาก code จริง)

connection / capabilityสถานะหมายเหตุ
US → OC (flow-submitted-for-approval)✅ พร้อมAsbFlowApprovalEventPublisher
OC → WF (HTTP POST /instances)✅ พร้อมHttpWorkflowAdapter — ต้อง config BaseUrl
WF → TS (HTTP POST /tasks)✅ พร้อมWF สร้าง Task เองตอน PostInstances
WF → TS (PATCH /tasks/pickup)✅ พร้อมPickupHandler เก็บคนหยิบงานแล้ว
WF → OC (workflow-pickup)✅ พร้อมPickupHandlerISignalPublisher.PublishAppStatusPickupAsync
OC → US (flow-progress-update InReview/Reviewed)✅ พร้อมsaga OnPickup/OnSentToReviewer + Lego FlowProgressUpdateConsumer
WF → OC (workflow-decision REWORK)✅ พร้อมReworkHandlerPublishAppStatusReworkAsync
OC → US (flow-approval-result)✅ พร้อมAsbLegoPort.Transition() — APPROVED/REJECTED/REWORK/CANCEL
US → NS (flow-status-notification)✅ พร้อมrequestor email หลัง approve/reject/rework/cancel
US → NS (invitation-event)✅ พร้อมcustomer invite หลัง approve
NS รับ workflow-maker-notification✅ พร้อมWorkflowMakerNotificationConsumer + template maker_task_assigned
WF “Reviewed” / sent-to-reviewer (publisher)MISSINGphase cascade มีแล้ว ✅ · ขาดแค่ publish ตอน TakeAction APPROVE ปิด non-final phase (saga OnSentToReviewer รออยู่)
WF approval-pickup (รอบ 2 → InApprove)MISSINGไม่มี endpoint, ไม่มี saga state, ไม่มี constant
WF TakeAction → OC (APPROVE/REJECT)MISSINGTakeActionHandler ไม่ publish ASB เลย (มีแต่ webhook)
WF → NS notification (maker/approver)MISSINGWF ไม่เคยยิง NS · ต้องเพิ่ม client เรียก US role-lookup + NS publisher
US role→member lookup API✅ พร้อมGET .../roles/{roleId}/users (UserRoleController) — WF แค่ต้องมี client เรียก
OC saga state InApproveMISSINGsaga มีแค่ UnderReview → UnderConsideration → Approved
OC ITaskPort⚠️ no-opLoggingTaskPort — ยังไม่มี real impl
WF ForceStop (cancel cleanup)⚠️ disabledForceStopEnabled=false (W-K/W-J)

3. Scenario A: Happy Path — NEW_REQUESTOR → APPROVE

sequenceDiagram
    autonumber
    participant RQ as Requestor
    participant US as UserService (Lego)
    participant OC as OrchestratorService
    participant WF as WorkflowService
    participant TS as TaskService
    participant MK as Maker
    participant AP as Approver
    participant NS as NotificationService

    %% ── PART 1: Submit ──
    rect rgb(232,248,232)
        Note over RQ,US: PART 1 — Requestor Submit ✅
        RQ->>US: กรอกฟอร์ม + Submit
        Note over US: FlowInstance: Draft → Submitted
        US->>OC: ASB flow-submitted-for-approval<br/>{flowInstanceId, refNo, flowDefinitionCode, isResubmit=false,<br/>companyName, requestTypeId, requestTypeName, submittedDate}
    end

    %% ── PART 2: Orch fan-out + create ──
    rect rgb(232,248,232)
        Note over OC: FlowSubmittedConsumer → saga.OnFirstSubmit()<br/>outbox: TaskCreationRequested + WorkflowCreationRequested
        OC->>WF: HTTP POST /instances {WorkflowTemplateId, DocumentId=refNo,<br/>companyName, requestTypeId, requestTypeName, submittedDate}
        Note over WF: PostInstancesHandler<br/>WfInstance: Status=New, DocStatus=Submitted<br/>+ Phase/Step instances
        WF->>TS: HTTP POST /tasks {Title, RefNo, Status=NOTSTARTED}
        Note over TS: Task created (Progress=NOTSTARTED)
    end

    %% ── PART 3: notify makers (NEW) + return id ──
    rect rgb(255,232,232)
        Note over WF,NS: PART 3 — แจ้ง maker ว่ามีงานใหม่ ❌ (ของใหม่)
        WF--xUS: ❌ lookup maker-role members → GET .../roles/{roleId}/users<br/>US endpoint มีแล้ว ✅ · WF ยังไม่มี client เรียก ❌
        WF--xNS: ❌ workflow-maker-notification {MakerUserIds[], template=maker_task_assigned}<br/>NS topic+template พร้อม ✅ · WF ไม่ publish ❌
        WF-->>OC: 200 OK {wfInstanceId} ✅ WF return ให้แล้ว<br/>⚠️ แต่ OC ไม่ capture จาก response — รอจาก workflow-pickup แทน (gap OC-4)
    end

    %% ── PART 4: Maker pickup → InReview ──
    rect rgb(255,243,224)
        Note over MK,US: PART 4 — Maker หยิบงาน → InReview
        MK->>WF: POST /pickup {actionBy=maker}
        Note over WF: PickupHandler<br/>⚠️ เขียน DocStatus="Pickup" — ควรเป็น "InReview"
        WF->>TS: PATCH /tasks/pickup {AppId, ActionBy} ✅ (เก็บว่าใครหยิบงาน)
        WF->>OC: ASB workflow-pickup {refNo, wfInstanceId, actionBy} ✅
        Note over OC: WorkflowPickupConsumer → saga.OnPickup()<br/>State = UnderReview · เก็บ WfInstanceId
        OC->>US: ASB flow-progress-update {status=InReview} ✅
        Note over US: FlowInstance = InReview ✅
    end

    %% ── PART 5: Maker "Reviewed" = TakeAction APPROVE บน maker phase (non-final) ──
    rect rgb(255,232,232)
        Note over MK,NS: PART 5 — Maker กด "Reviewed" → ส่งต่อ approval
        MK->>WF: POST /instances/{id}/take-action {APPROVE} ✅ endpoint มีแล้ว
        Note over WF: ✅ phase cascade ทำงาน: maker phase → APPROVED → เปิด approver phase<br/>(DocStatus = "Pending {ApproverPhase}")<br/>❌ ควร set "Reviewed" · ❌ ไม่ publish ASB · ❌ ไม่ notify
        WF--xTS: ❌ PATCH task status = Reviewed
        WF--xUS: ❌ lookup approver-role members (US endpoint GET .../roles/{roleId}/users มีแล้ว ✅ · WF ไม่มี client ❌)
        WF--xNS: ❌ workflow-maker-notification (group noti → approver ทุกคน)
        WF--xOC: ❌ ASB sent-to-reviewer {refNo, wfInstanceId, reviewerUsername}<br/>⭐ ต้อง publish ตอน TakeAction APPROVE ปิด non-final phase (ไม่ใช่ endpoint ใหม่)
        Note over OC: ✅ saga.OnSentToReviewer() มีแล้ว: UnderReview → UnderConsideration
        OC->>US: ASB flow-progress-update {status=Reviewed} ✅ (consumer มีแล้ว)
        Note over US: FlowInstance = Reviewed ✅
    end

    %% ── PART 6: Approval pickup → InApprove (NET-NEW) ──
    rect rgb(255,232,232)
        Note over AP,US: PART 6 — Approval หยิบงาน → InApprove ❌ (ใหม่ 100%) · ไม่มี notification
        AP--xWF: ❌ POST /pickup (approval pickup รอบที่ 2)<br/>WF ไม่มี approval-pickup แยกจาก maker pickup
        Note over WF: ควร set DocStatus="InApprove" — constant ยังไม่มีด้วยซ้ำ
        WF--xTS: ❌ PATCH task (เก็บว่าใคร approve)
        WF--xOC: ❌ ASB approval-pickup {refNo, wfInstanceId, actionBy}
        Note over OC: ❌ ต้องเพิ่ม saga state ใหม่: UnderConsideration → InApprove
        OC--xUS: ❌ ASB flow-progress-update {status=InApprove}
        Note over US: ❌ FlowInstanceStatus ไม่มี "InApprove"<br/>FlowProgressUpdateConsumer terminal-guard ไม่รับ status นี้
    end

    %% ── PART 7: Approve ──
    rect rgb(255,232,232)
        Note over AP,WF: PART 7 — Approval Approve
        AP->>WF: POST /instances/{id}/take-action {action=APPROVE}
        Note over WF: TakeActionHandler → DB save<br/>⚠️ เขียน DocStatus="Completed" — ควรเป็น "Approved"<br/>❌ ไม่ publish ASB เลย (blocker ใหญ่)
        WF--xOC: ❌ workflow-decision {action=APPROVE, decisionId}<br/>OC ไม่รับ decision → saga ค้าง
    end

    %% ── PART 8: Orch → Lego → final email (✅ เมื่อ decision มาถึง) ──
    rect rgb(232,248,232)
        Note over OC,NS: PART 8 — Orch ส่งผลกลับ Lego + final email ✅ (เมื่อ decision มาถึง)
        Note over OC: WorkflowDecisionConsumer → saga.OnDecision(Approve)<br/>State = Approved · outbox: LegoTransitionRequested + TaskCompleteRequested
        OC->>US: ASB flow-approval-result {result=APPROVED, decisionId}
        Note over US: FlowInstance = Approved
        US->>US: CreateCompanyApplication → GenerateCustomerInvitations
        US->>NS: ASB invitation-event {templateKey=invitation_default}
        US->>NS: ASB flow-status-notification {status=APPROVED} (final email = Lego)
        NS-->>RQ: 📧 Email + 🔔 In-App: ใบสมัครอนุมัติ — งานเสร็จสมบูรณ์
    end

Gap สรุป Scenario A (เรียงตามจุดที่ขาด):

PartGapเจ้าของ
3WF ไม่ lookup role + ไม่ยิง maker notification ตอนสร้างงานWF (+ US lookup API)
3OC ไม่ capture wfInstanceId จาก response (WF return ให้แล้ว ✅)OC
4pickup เขียน DocStatus "Pickup" แทน "InReview"WF
5sent-to-reviewer ไม่ถูก publish ตอน maker ปิด phase (phase cascade มีแล้ว ✅) + ไม่ set Reviewed + ไม่ notify approverWF
6approval-pickup + saga state InApprove + Lego status InApproveWF + OC + US (ใหม่ 100%)
7TakeAction APPROVE ไม่ publish ASB + เขียน DocStatus "Completed" แทน "Approved"WF

4. Scenario B: Reject

Reject ทำได้จาก InReview (maker) หรือ InApprove (approver) — canonical กำหนดว่าทั้งสอง role reject ได้ saga OnDecision(Reject) รองรับการ fire จาก UnderReview หรือ UnderConsideration อยู่แล้ว

sequenceDiagram
    autonumber
    participant RQ as Requestor
    participant US as UserService (Lego)
    participant OC as OrchestratorService
    participant WF as WorkflowService
    participant TS as TaskService
    participant AC as Maker / Approver
    participant NS as NotificationService

    Note over RQ,TS: PART 1–4 เหมือน Scenario A (Submit → create → pickup → InReview/InApprove)

    rect rgb(255,232,232)
        Note over AC,WF: PART A — ตัดสิน REJECT (จาก InReview หรือ InApprove)
        AC->>WF: POST /instances/{id}/take-action {action=REJECT, remark}
        Note over WF: TakeActionHandler → OnRejectToPhaseOrder==0<br/>Status=REJECTED, DocStatus="Rejected"<br/>❌ ไม่ publish ASB
        WF--xOC: ❌ MISSING: workflow-decision {action=REJECT, decisionId, reason}<br/>⭐ แก้: เพิ่ม ISignalPublisher call ("ส่งหา orches")
        Note over OC: ⭐ ลบ dev-only driver ออก — ไม่เกี่ยวกับ business flow
    end

    rect rgb(232,248,232)
        Note over OC,NS: PART B — Orch ส่งผล Reject กลับ Lego ✅ (เมื่อ decision มาถึง)
        Note over OC: saga.OnDecision(Reject) → State=Rejected<br/>outbox: LegoTransitionRequested(Rejected) + TaskCloseRequested
        OC->>US: ASB flow-approval-result {result=REJECTED, decisionId, reason}
        Note over US: FlowInstance = Rejected
        US->>NS: ASB flow-status-notification {status=REJECTED}
        NS-->>RQ: 📧 Email + 🔔 In-App: ใบสมัครถูกปฏิเสธ
    end

Gap สรุป Scenario B:

Gapเจ้าของหมายเหตุ
TakeAction REJECT ไม่ publish ASBWFเพิ่ม workflow-decision {action=REJECT}
dev-only decision driverOCลบทิ้ง — ไม่ใช่ business flow

5. Scenario C: Rework → Resubmit → (กลับเข้า flow)

Rework flow นี้ พร้อมที่สุด เพราะ ReworkHandler publish ASB ได้จริง Rework ทำได้จาก InReview หรือ InApprove (ทั้งสอง role)

sequenceDiagram
    autonumber
    participant RQ as Requestor
    participant US as UserService (Lego)
    participant OC as OrchestratorService
    participant WF as WorkflowService
    participant TS as TaskService
    participant AC as Maker / Approver
    participant NS as NotificationService

    Note over RQ,TS: PART 1–4 เหมือน Scenario A — Rework ทำได้จาก InReview หรือ InApprove

    rect rgb(232,248,232)
        Note over AC,WF: PART A — ตัดสิน REWORK ✅
        AC->>WF: POST /instances/{id}/rework {reworkTargets, reason}
        Note over WF: ReworkHandler → Status=REWORK, DocStatus="Rework" ✅<br/>→ TaskService.ReworkAsync() ✅ → PublishAppStatusReworkAsync() ✅
        WF->>TS: PATCH /tasks/{id}/rework ✅
        WF->>OC: ASB workflow-decision {action=Rework, reason, reworkTargets} ✅
        Note over OC: saga.OnDecision(Rework) → State=ReworkRequested<br/>outbox: LegoTransitionRequested(Rework)
        OC->>US: ASB flow-approval-result {result=REWORK} ✅
        Note over US: ⭐ FlowInstance = Rework (แก้: เดิม doc เขียน CORRECTION_REQUESTED ผิด)<br/>steps unlocked
        US->>NS: ASB flow-status-notification {status=Rework} ✅
        NS-->>RQ: 📧 Email + 🔔 In-App: ต้องแก้ไขใบสมัคร
    end

    rect rgb(232,248,232)
        Note over RQ,OC: PART B — Requestor Resubmit
        RQ->>US: แก้ไขฟอร์ม + Resubmit
        Note over US: FlowInstance = Resubmitted
        US->>OC: ASB flow-submitted-for-approval {isResubmit=true,<br/>companyName, requestTypeId, requestTypeName, submittedDate}
        Note over OC: saga detects isResubmit=true → saga.OnResubmit()<br/>outbox: WorkflowResubmitRequested + TaskReworkRequested
    end

    rect rgb(255,232,232)
        Note over OC,NS: PART C — Orch → WF resubmit + แจ้ง maker คนเดิม
        OC->>WF: HTTP POST /{wfInstanceId}/resubmit {companyName, requestTypeId, requestTypeName, submittedDate}
        Note over WF: ResubmitHandler → Status=RUNNING, reset phase/steps<br/>⚠️ DocStatus ควร set "Resubmitted" (ตอนนี้คงค่าเดิม "Rework")
        WF--xNS: ❌ workflow-maker-notification → maker คนเดิม<br/>⭐ point ที่หายไป: ผู้ส่ง=workflow · resolve จาก task assignee<br/>(ส่งคนเดียว — ไม่ต้อง role lookup)
        WF-->>OC: 200 OK
        OC->>TS: TaskReworkRequested (ITaskPort = LoggingTaskPort no-op ⚠️)
    end

    Note over AC,NS: PART D — ไปต่อด้วย Maker pickup → InReview → Reviewed → InApprove → APPROVE (เหมือน Scenario A PART 4–8)

Gap สรุป Scenario C:

Gapเจ้าของหมายเหตุ
Rework path ✅ ทำงานได้ReworkHandler publish ครบ
flow-approval-result ใช้ Rework (ไม่ใช่ CORRECTION_REQUESTED)OC + USalign ชื่อ status ให้ตรง canonical
resubmit แจ้ง maker คนเดิม (WF → NS)WF⭐ point ที่หายไป — ส่งหาคนใน task
resubmit ไม่ set DocStatus=ResubmittedWF
OC ITaskPort no-op (rework signal ฝั่ง orch หาย)OCสร้าง HttpTaskAdapter

6. Scenario D: Cancel

ผู้ trigger ได้ 2 ทาง: (1) Requestor ถอนใบสมัครเอง (จาก Lego) · (2) ผู้ที่หยิบงาน (maker/approver) ยกเลิก (จาก WF) saga OnCancel() มีรออยู่แล้ว — แต่ทั้งสองทาง ยังไม่มี path ที่ publish เข้า saga จริง

sequenceDiagram
    autonumber
    participant RQ as Requestor
    participant US as UserService (Lego)
    participant OC as OrchestratorService
    participant WF as WorkflowService
    participant TS as TaskService
    participant AC as Maker / Approver (ผู้หยิบงาน)
    participant NS as NotificationService

    rect rgb(255,232,232)
        Note over RQ,OC: PART A — ทาง 1: Requestor ถอน (จาก Lego)
        RQ->>US: ขอยกเลิกใบสมัคร
        US--xOC: ❌ ASB cancel-requested {flowInstanceId, refNo, reason}<br/>ยังไม่มี topic จาก Lego → Orch
    end

    rect rgb(255,232,232)
        Note over AC,OC: PART B — ทาง 2: ผู้หยิบงานยกเลิก (จาก WF)
        AC--xWF: ❌ POST /instances/{id}/cancel<br/>WF ไม่มี cancel endpoint ที่ publish ASB
        WF--xOC: ❌ ASB workflow-decision {action=Cancel}
    end

    rect rgb(255,243,224)
        Note over OC,NS: PART C — Orch ประมวลผล Cancel (saga มี OnCancel รออยู่ ✅)
        Note over OC: saga.OnCancel() → State=Cancelled<br/>outbox: LegoTransitionRequested(Cancel) + TaskCloseRequested + WorkflowForceStopRequested
        OC--xWF: ⚠️ HTTP ForceStop (ForceStopEnabled=false → skip!) — WfInstance ยัง active
        OC->>US: ASB flow-approval-result {result=CANCEL}
        Note over US: FlowInstance = Cancelled
        US->>NS: ASB flow-status-notification {status=Cancelled}
        NS-->>RQ: 📧 Email + 🔔 In-App: ใบสมัครถูกยกเลิก
    end

Gap สรุป Scenario D:

Gapเจ้าของหมายเหตุ
Lego → OC cancel topic (cancel-requested)US + OCrequestor ถอนเอง
WF cancel endpoint + publisherWFผู้หยิบงานยกเลิก
OC รับ cancel จาก 2 ทาง → OnCancel()OCsaga handler มีแล้ว ต้องต่อ ingress
ForceStop disabled → WfInstance ไม่ถูก cleanupWFเปิด ForceStopEnabled (รอ W-K/W-J)

7. Scenario E: Customer Auto-Approve (CUSTOMER_FIXED_ONBOARDING)

Flow นี้ ไม่ผ่าน OrchestratorService เลย — auto-approve ใน Lego Engine ไม่มี maker/approver

sequenceDiagram
    autonumber
    participant CU as Customer
    participant US as UserService (Lego)
    participant NS as NotificationService

    rect rgb(232,248,232)
        Note over CU,US: ทั้ง flow อยู่ใน UserService เท่านั้น ✅
        CU->>US: คลิก invite link
        US->>US: CUSTOMER_FIXED_ONBOARDING flow start
        CU->>US: OTP → Consent → PersonalInfo → Submit
        Note over US: AutoApproveWorker → auto-approve (ไม่ต้องรอ admin)
        US->>US: CreatePlatformUser → ActivateEntraUser (stub)<br/>→ CreateUserCompanyMapping → ProvisionCustomerAccess
        US-->>CU: ✅ Onboarding สำเร็จ
        US->>NS: ASB flow-status-notification {status=Approved}
        NS-->>CU: 📧 Email: บัญชีพร้อมใช้งาน
    end

    Note over US,NS: OrchestratorService ไม่เกี่ยวกับ flow นี้เลย

8. Scenario F: Multi-Approver — ANY vs ALL Policy

เกิดภายใน WorkflowService — กำหนดว่า step จะ “closed” และ cascade decision เมื่อไหร่ ทั้งสอง policy ยังติด blocker เดิม: TakeAction APPROVE ไม่ publish ASB (PART 7 ของ Scenario A) หมายเหตุ: ก่อนถึงจุดนี้ approver หยิบงาน → InApprove (Scenario A PART 6 ก่อน) — diagram นี้โฟกัสเฉพาะ policy ANY/ALL

sequenceDiagram
    autonumber
    participant AP1 as Approver 1
    participant AP2 as Approver 2
    participant WF as WorkflowService
    participant OC as OrchestratorService

    rect rgb(255,243,224)
        Note over AP1,WF: AssignmentPolicy = ANY (คนใดคนหนึ่ง Approve ก็พอ)
        AP1->>WF: take-action APPROVE
        Note over WF: ANY → step approved ทันที → cascade phase/workflow<br/>❌ ไม่ publish ASB (blocker เดิม)
        AP2->>WF: (ไม่ต้องทำ — step closed แล้ว)
    end

    rect rgb(255,243,224)
        Note over AP1,WF: AssignmentPolicy = ALL (ทุกคนต้อง Approve)
        AP1->>WF: take-action APPROVE
        Note over WF: บันทึก Approver 1 = APPROVED · ยังรอ Approver 2
        AP2->>WF: take-action APPROVE
        Note over WF: ALL satisfied → step closed → cascade<br/>❌ ไม่ publish ASB (blocker เดิม)
    end

    Note over WF,OC: ทั้งสอง policy: TakeActionHandler ไม่ publish ASB → OC ไม่รับ decision

9. Scenario G: Resubmit หลัง Session ขาด (Orch restart)

sequenceDiagram
    autonumber
    participant US as UserService (Lego)
    participant OC as OrchestratorService
    participant WF as WorkflowService

    Note over US,WF: Scenario: Lego ส่ง isResubmit=true แต่ WfInstanceId หายจาก saga

    US->>OC: ASB flow-submitted-for-approval {isResubmit=true}
    Note over OC: FlowSubmittedConsumer → หา OrchestrationInstance by RefNo
    Note over OC: saga.OnResubmit() → WorkflowResubmitRequested (outbox)
    OC->>WF: HTTP POST /{wfInstanceId}/resubmit (ใช้ WfInstanceId ที่เก็บจาก PickupWire)
    Note over WF: WF reset phases/steps → PENDING
    WF-->>OC: 200 OK

    Note over OC,WF: ⚠️ Edge case: ถ้า PickupWire ไม่เคยมา → WfInstanceId = null<br/>→ ต้อง GET /instances/by-document/{refNo}<br/>⭐ WF return wfInstanceId ใน POST response แล้ว ✅ — ถ้า OC capture ไว้ (OC-4) ตัด dependency นี้ทิ้งได้

10. Scenario H: Closed (ปิดการใช้งาน) — ⏳ DESIGN UNDEFINED

Closed อยู่ใน canonical (กลุ่ม “Maker + Approval ทำได้ทั้งคู่”) แต่ ยังไม่ตัดสิน business flow ต้องตอบก่อน implement:

  • ใครเป็นคน trigger? (admin ปิด item ที่ approved/active แล้ว vs ปิดอัตโนมัติเมื่อ flow จบ)
  • เริ่มจาก source state ไหน?
  • กระทบ service ไหนบ้าง (น่าจะ Lego เป็นเจ้าของ lifecycle หลัง approve)

→ คงสถานะ design-undefined ไว้ใน gap list ก่อน


11. Notification Topology (ใครส่งอะไร)

เหตุการณ์ผู้ส่งปลายทางtopic / templaterole lookup?สถานะ
สร้างงานใหม่ → แจ้ง makerWFmaker ทุกคนในroleworkflow-maker-notification / maker_task_assigned✅ ต้อง lookup (group)❌ WF ไม่ยิง
Maker “Reviewed” → แจ้ง approverWFapprover ทุกคนในroleworkflow-maker-notification✅ ต้อง lookup (group)❌ WF ไม่ยิง
Approval pickup → InApprove(ไม่มี notification)— (by design)
Resubmit → แจ้ง maker คนเดิมWFmaker คนเดียว (จาก task)workflow-maker-notification❌ ไม่ต้อง (resolved แล้ว)❌ WF ไม่ยิง
Approved / Rejected / Rework / Cancelled → requestorUS (Lego)requestorflow-status-notification✅ พร้อม
Customer invite (หลัง approve)US (Lego)customerinvitation-event / invitation_default✅ พร้อม

หลักการ: NotificationService ไม่ expand role→members เอง — ผู้ส่ง (ต้นทาง) ต้อง resolve recipient list ให้เรียบร้อยก่อน publish

Role-lookup dependency: UserService มี endpoint แล้ว ✅ — GET /api/user-service/v1/companies/{companyId}/applications/{applicationId}/roles/{roleId}/users (UserRoleControllerGetUsersByRoleInCompanyQuery) คืน users ทุกคนใน role · gap อยู่ฝั่ง WF: ต้องมี client เรียก + รู้ว่า flowDefinitionCode map กับ (companyId, applicationId, roleId) ใด

  • เปิด (D5): mapping flowDefinitionCode → (companyId, applicationId, roleId) ของ maker/approver

12. Wire Contracts

12.1 มีแล้ว ✅

topicทิศทางDTO fields
flow-submitted-for-approvalUS → OCverified ✅: EventId, OccurredAt, FlowInstanceId, RefNo, FlowDefinitionCode, CompanyId, ServiceId, IsResubmit · proposed (ยังไม่มีใน code): CompanyName, RequestTypeId, RequestTypeName, SubmittedDate
workflow-pickupWF → OCRefNo, WfInstanceId, ActionBy
workflow-decision (Rework เท่านั้น)WF → OCRefNo, WfInstanceId, DecisionId, Action, Reason, ReworkTargets, ActionBy
flow-progress-updateOC → USEventId, FlowInstanceId, RefNo, ProgressStatus (InReview/Reviewed)
flow-approval-resultOC → USDecisionId, FlowInstanceId, Result, TargetSteps, CorrectionNote
flow-status-notificationUS → NSEventId, FlowInstanceId, RefNo, RequestorUserId, RequestorEmail, DeepLinkUrl, CorrectionNote
invitation-eventUS → NSTemplateKey, RecipientEmail, InvitationLink, ExpiryDate, FlowName
workflow-maker-notification(ควรเป็น) WF → NSEventId, OccurredAt, RefNo, NotificationType, TaskSummary, DeepLinkUrl, MakerUserIds[], AssigneeUserId, AssigneeEmail, EmailTemplateKey

12.2 ต้องเพิ่ม ❌

topic / changeทิศทางหมายเหตุ
workflow-decision (Action=APPROVE)WF → OCTakeActionHandler ต้อง publish + mint DecisionId
workflow-decision (Action=REJECT)WF → OCเหมือนกัน
sent-to-reviewer eventWF → OCsaga OnSentToReviewer มีแล้ว · trigger = TakeAction APPROVE ปิด non-final phase (ไม่ใช่ endpoint ใหม่) · fields: RefNo, WfInstanceId, ReviewerUsername
approval-pickup eventWF → OCใหม่ทั้งหมด — saga ต้องเพิ่ม state InApprove
flow-progress-update (ProgressStatus=InApprove)OC → USLego ต้องเพิ่ม status + map ใน consumer
cancel-requestedUS → OCrequestor ถอน (Lego → Orch)
workflow-decision (Action=Cancel)WF → OCผู้หยิบงานยกเลิก
workflow-maker-notification payloads (3 เคส)WF → NScreate / reviewed / resubmit

13. Gap Analysis ตามทีม

WorkflowService — งานหนักสุด

#GapAction
WF-1TakeAction APPROVE ที่ปิด final phase ไม่ publish ASBpublish workflow-decision {APPROVE} เมื่อ workflow COMPLETED
WF-2TakeAction REJECT ไม่ publish ASBเพิ่ม workflow-decision {REJECT}
WF-3ISignalPublisher ขาด methodเพิ่ม Approve / Reject / SentToReviewer / ApprovalPickup
WF-4sent-to-reviewer ไม่ถูก publishTakeAction APPROVE ที่ปิด non-final phase (phase cascade มีอยู่แล้ว ✅) ต้อง publish sent-to-reviewer + set DocStatus=“Reviewed” (ไม่ต้องสร้าง endpoint ใหม่)
WF-5ไม่มี approval-pickup (รอบ 2)endpoint + publisher ใหม่ → InApprove
WF-6DocStatus ไม่ถูกเขียนตาม canonicalpickup→InReview, reviewed→Reviewed, approval-pickup→InApprove (+เพิ่ม constant), approve→Approved
WF-7resubmit ไม่ set DocStatusset Resubmitted
WF-8ไม่มี notificationclient เรียก US role-lookup (GET .../roles/{roleId}/users มีแล้ว) + workflow-maker-notification publisher (create/reviewed/resubmit)
WF-9ไม่มี cancel endpointendpoint + workflow-decision {Cancel}
WF-10ForceStop disabledresolve W-K/W-J → ForceStopEnabled=true

OrchestratorService

#GapAction
OC-1saga ไม่มี state InApproveเพิ่ม state + approval-pickup consumer (UnderConsideration → InApprove → Approved)
OC-2ITaskPort = LoggingTaskPort no-opสร้าง HttpTaskAdapter + register
OC-3config WorkflowAdapter:BaseUrl + TemplateIdByFlowCode ยังไม่มีบน clusterเติมใน IaC ทุก env
OC-4orch ไม่ capture WfInstanceId จาก POST /instances response (WF return ให้แล้ว ✅) — รอจาก pickup แทนอ่าน WfInstanceId จาก response เพื่อตัด dependency กับ pickup
OC-5ไม่มี cancel ingress จาก Legoรับ cancel-requestedOnCancel()

UserService / Lego

#GapAction
US-1FlowInstanceStatus ไม่มี InApproveเพิ่ม enum + map ใน FlowProgressUpdateConsumer (ขยาย terminal-guard ให้รับ InApprove)
US-2role→member lookup API ✅ มีแล้วGET .../roles/{roleId}/users — ไม่มี gap ฝั่ง US (WF เป็นคนเรียก)
US-3ไม่มี cancel publishrequestor ถอน → publish cancel-requested ไป OC

NotificationService

#GapAction
NS-1✅ พร้อมแล้วworkflow-maker-notification + maker_task_assigned มีอยู่ · ตรวจ template content ให้ครอบคลุม 3 เคส (รอ review / รอ approve / resubmit กลับมา)

14. Decision List — ต้องตัดสินก่อน implement

#คำถามสถานะ
D1notification audience ของ group noti✅ ตัดสินแล้ว — ผู้ส่ง (WF) resolve role→members ผ่าน US lookup แล้วยิง NS
D2ใคร trigger Cancel✅ ตัดสินแล้ว — Requestor (Lego) หรือ ผู้หยิบงาน (WF)
D3Closed business flow⏳ design-undefined
D4approval-pickup ใช้ /pickup เดิม (มี role context) หรือ endpoint แยก?⏳ ต้องตัดสิน
D5mapping flowDefinitionCode → (companyId, applicationId, roleId) ของ maker/approver (US lookup endpoint มีแล้ว — ต้องรู้ param ที่เรียก)⏳ ต้องตัดสิน (กระทบ WF-8)
D6workflow-decision topic เดียว + action field หรือแยก topic ต่อ action?⏳ ต้องตัดสิน
D7Task ถูกสร้าง 2 ที่? WF PostInstancesHandler สร้าง Task แล้ว แต่ saga ก็ raise TaskCreationRequested (ITaskPort)⏳ ต้อง dedupe ใครเป็นเจ้าของ Task lifecycle