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 ของเอกสารชุดนี้:
- แสดง business flow ที่ถูกต้อง ของ E2E onboarding/approval — เห็นว่าจริง ๆ มีอะไรบ้าง วิ่งจากไหนไปไหน
- เปิดเผยว่า 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)(ส้ม) = ส่วนที่ทำได้บางส่วน / มี caveatrect rgb(255,232,232)(แดง) = ส่วนที่ขาดหาย ต้อง develop
1. Canonical Status Model (status ที่ใช้คุยร่วมกัน)
นี่คือ status กลางที่ทุก service ต้องยึดร่วมกัน (final แล้ว) — แบ่งตามจุดที่เกิดและ role:
| กลุ่ม | Status | ความหมาย |
|---|---|---|
| เกิดที่ Lego | Draft | บันทึกร่าง |
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 ที่ถูกต้องหรือยัง” — ช่องว่างซ่อนอยู่ที่มิติที่สอง
| Canonical | Lego: ประกาศ | 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 DocStatus | NOTSTARTED |
| Rework | ✅ | ✅ | ✅ | ✅ "Rework" | REWORK |
| Cancelled | ✅ | ✅ | ✅ (Cancelled) | ⚠️ ไม่มี cancel handler ที่ publish | CANCEL |
| 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) | ✅ พร้อม | PickupHandler → ISignalPublisher.PublishAppStatusPickupAsync |
OC → US (flow-progress-update InReview/Reviewed) | ✅ พร้อม | saga OnPickup/OnSentToReviewer + Lego FlowProgressUpdateConsumer |
WF → OC (workflow-decision REWORK) | ✅ พร้อม | ReworkHandler → PublishAppStatusReworkAsync |
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) | ❌ MISSING | phase cascade มีแล้ว ✅ · ขาดแค่ publish ตอน TakeAction APPROVE ปิด non-final phase (saga OnSentToReviewer รออยู่) |
| WF approval-pickup (รอบ 2 → InApprove) | ❌ MISSING | ไม่มี endpoint, ไม่มี saga state, ไม่มี constant |
| WF TakeAction → OC (APPROVE/REJECT) | ❌ MISSING | TakeActionHandler ไม่ publish ASB เลย (มีแต่ webhook) |
| WF → NS notification (maker/approver) | ❌ MISSING | WF ไม่เคยยิง NS · ต้องเพิ่ม client เรียก US role-lookup + NS publisher |
| US role→member lookup API | ✅ พร้อม | GET .../roles/{roleId}/users (UserRoleController) — WF แค่ต้องมี client เรียก |
OC saga state InApprove | ❌ MISSING | saga มีแค่ UnderReview → UnderConsideration → Approved |
OC ITaskPort | ⚠️ no-op | LoggingTaskPort — ยังไม่มี real impl |
WF ForceStop (cancel cleanup) | ⚠️ disabled | ForceStopEnabled=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 (เรียงตามจุดที่ขาด):
| Part | Gap | เจ้าของ |
|---|---|---|
| 3 | WF ไม่ lookup role + ไม่ยิง maker notification ตอนสร้างงาน | WF (+ US lookup API) |
| 3 | OC ไม่ capture wfInstanceId จาก response (WF return ให้แล้ว ✅) | OC |
| 4 | pickup เขียน DocStatus "Pickup" แทน "InReview" | WF |
| 5 | sent-to-reviewer ไม่ถูก publish ตอน maker ปิด phase (phase cascade มีแล้ว ✅) + ไม่ set Reviewed + ไม่ notify approver | WF |
| 6 | approval-pickup + saga state InApprove + Lego status InApprove | WF + OC + US (ใหม่ 100%) |
| 7 | TakeAction 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 ASB | WF | เพิ่ม workflow-decision {action=REJECT} |
| dev-only decision driver | OC | ลบทิ้ง — ไม่ใช่ business flow |
5. Scenario C: Rework → Resubmit → (กลับเข้า flow)
Rework flow นี้ พร้อมที่สุด เพราะ
ReworkHandlerpublish 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 + US | align ชื่อ status ให้ตรง canonical |
| resubmit แจ้ง maker คนเดิม (WF → NS) | WF | ⭐ point ที่หายไป — ส่งหาคนใน task |
resubmit ไม่ set DocStatus=Resubmitted | WF | |
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 + OC | requestor ถอนเอง |
| WF cancel endpoint + publisher | WF | ผู้หยิบงานยกเลิก |
OC รับ cancel จาก 2 ทาง → OnCancel() | OC | saga handler มีแล้ว ต้องต่อ ingress |
| ForceStop disabled → WfInstance ไม่ถูก cleanup | WF | เปิด 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 / template | role lookup? | สถานะ |
|---|---|---|---|---|---|
| สร้างงานใหม่ → แจ้ง maker | WF | maker ทุกคนในrole | workflow-maker-notification / maker_task_assigned | ✅ ต้อง lookup (group) | ❌ WF ไม่ยิง |
| Maker “Reviewed” → แจ้ง approver | WF | approver ทุกคนในrole | workflow-maker-notification | ✅ ต้อง lookup (group) | ❌ WF ไม่ยิง |
| Approval pickup → InApprove | — | — | (ไม่มี notification) | — | — (by design) |
| Resubmit → แจ้ง maker คนเดิม | WF | maker คนเดียว (จาก task) | workflow-maker-notification | ❌ ไม่ต้อง (resolved แล้ว) | ❌ WF ไม่ยิง |
| Approved / Rejected / Rework / Cancelled → requestor | US (Lego) | requestor | flow-status-notification | ❌ | ✅ พร้อม |
| Customer invite (หลัง approve) | US (Lego) | customer | invitation-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 (UserRoleController → GetUsersByRoleInCompanyQuery) คืน 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-approval | US → OC | verified ✅: EventId, OccurredAt, FlowInstanceId, RefNo, FlowDefinitionCode, CompanyId, ServiceId, IsResubmit · proposed (ยังไม่มีใน code): CompanyName, RequestTypeId, RequestTypeName, SubmittedDate |
workflow-pickup | WF → OC | RefNo, WfInstanceId, ActionBy |
workflow-decision (Rework เท่านั้น) | WF → OC | RefNo, WfInstanceId, DecisionId, Action, Reason, ReworkTargets, ActionBy |
flow-progress-update | OC → US | EventId, FlowInstanceId, RefNo, ProgressStatus (InReview/Reviewed) |
flow-approval-result | OC → US | DecisionId, FlowInstanceId, Result, TargetSteps, CorrectionNote |
flow-status-notification | US → NS | EventId, FlowInstanceId, RefNo, RequestorUserId, RequestorEmail, DeepLinkUrl, CorrectionNote |
invitation-event | US → NS | TemplateKey, RecipientEmail, InvitationLink, ExpiryDate, FlowName |
workflow-maker-notification | (ควรเป็น) WF → NS | EventId, OccurredAt, RefNo, NotificationType, TaskSummary, DeepLinkUrl, MakerUserIds[], AssigneeUserId, AssigneeEmail, EmailTemplateKey |
12.2 ต้องเพิ่ม ❌
| topic / change | ทิศทาง | หมายเหตุ |
|---|---|---|
workflow-decision (Action=APPROVE) | WF → OC | TakeActionHandler ต้อง publish + mint DecisionId |
workflow-decision (Action=REJECT) | WF → OC | เหมือนกัน |
sent-to-reviewer event | WF → OC | saga OnSentToReviewer มีแล้ว · trigger = TakeAction APPROVE ปิด non-final phase (ไม่ใช่ endpoint ใหม่) · fields: RefNo, WfInstanceId, ReviewerUsername |
approval-pickup event | WF → OC | ใหม่ทั้งหมด — saga ต้องเพิ่ม state InApprove |
flow-progress-update (ProgressStatus=InApprove) | OC → US | Lego ต้องเพิ่ม status + map ใน consumer |
cancel-requested | US → OC | requestor ถอน (Lego → Orch) |
workflow-decision (Action=Cancel) | WF → OC | ผู้หยิบงานยกเลิก |
workflow-maker-notification payloads (3 เคส) | WF → NS | create / reviewed / resubmit |
13. Gap Analysis ตามทีม
WorkflowService — งานหนักสุด
| # | Gap | Action |
|---|---|---|
| WF-1 | TakeAction APPROVE ที่ปิด final phase ไม่ publish ASB | publish workflow-decision {APPROVE} เมื่อ workflow COMPLETED |
| WF-2 | TakeAction REJECT ไม่ publish ASB | เพิ่ม workflow-decision {REJECT} |
| WF-3 | ISignalPublisher ขาด method | เพิ่ม Approve / Reject / SentToReviewer / ApprovalPickup |
| WF-4 | sent-to-reviewer ไม่ถูก publish | TakeAction APPROVE ที่ปิด non-final phase (phase cascade มีอยู่แล้ว ✅) ต้อง publish sent-to-reviewer + set DocStatus=“Reviewed” (ไม่ต้องสร้าง endpoint ใหม่) |
| WF-5 | ไม่มี approval-pickup (รอบ 2) | endpoint + publisher ใหม่ → InApprove |
| WF-6 | DocStatus ไม่ถูกเขียนตาม canonical | pickup→InReview, reviewed→Reviewed, approval-pickup→InApprove (+เพิ่ม constant), approve→Approved |
| WF-7 | resubmit ไม่ set DocStatus | set Resubmitted |
| WF-8 | ไม่มี notification | client เรียก US role-lookup (GET .../roles/{roleId}/users มีแล้ว) + workflow-maker-notification publisher (create/reviewed/resubmit) |
| WF-9 | ไม่มี cancel endpoint | endpoint + workflow-decision {Cancel} |
| WF-10 | ForceStop disabled | resolve W-K/W-J → ForceStopEnabled=true |
OrchestratorService
| # | Gap | Action |
|---|---|---|
| OC-1 | saga ไม่มี state InApprove | เพิ่ม state + approval-pickup consumer (UnderConsideration → InApprove → Approved) |
| OC-2 | ITaskPort = LoggingTaskPort no-op | สร้าง HttpTaskAdapter + register |
| OC-3 | config WorkflowAdapter:BaseUrl + TemplateIdByFlowCode ยังไม่มีบน cluster | เติมใน IaC ทุก env |
| OC-4 | orch ไม่ capture WfInstanceId จาก POST /instances response (WF return ให้แล้ว ✅) — รอจาก pickup แทน | อ่าน WfInstanceId จาก response เพื่อตัด dependency กับ pickup |
| OC-5 | ไม่มี cancel ingress จาก Lego | รับ cancel-requested → OnCancel() |
UserService / Lego
| # | Gap | Action |
|---|---|---|
| US-1 | FlowInstanceStatus ไม่มี InApprove | เพิ่ม enum + map ใน FlowProgressUpdateConsumer (ขยาย terminal-guard ให้รับ InApprove) |
| US-2 | role→member lookup API ✅ มีแล้ว | GET .../roles/{roleId}/users — ไม่มี gap ฝั่ง US (WF เป็นคนเรียก) |
| US-3 | ไม่มี cancel publish | requestor ถอน → publish cancel-requested ไป OC |
NotificationService
| # | Gap | Action |
|---|---|---|
| NS-1 | ✅ พร้อมแล้ว | workflow-maker-notification + maker_task_assigned มีอยู่ · ตรวจ template content ให้ครอบคลุม 3 เคส (รอ review / รอ approve / resubmit กลับมา) |
14. Decision List — ต้องตัดสินก่อน implement
| # | คำถาม | สถานะ |
|---|---|---|
| D1 | notification audience ของ group noti | ✅ ตัดสินแล้ว — ผู้ส่ง (WF) resolve role→members ผ่าน US lookup แล้วยิง NS |
| D2 | ใคร trigger Cancel | ✅ ตัดสินแล้ว — Requestor (Lego) หรือ ผู้หยิบงาน (WF) |
| D3 | Closed business flow | ⏳ design-undefined |
| D4 | approval-pickup ใช้ /pickup เดิม (มี role context) หรือ endpoint แยก? | ⏳ ต้องตัดสิน |
| D5 | mapping flowDefinitionCode → (companyId, applicationId, roleId) ของ maker/approver (US lookup endpoint มีแล้ว — ต้องรู้ param ที่เรียก) | ⏳ ต้องตัดสิน (กระทบ WF-8) |
| D6 | workflow-decision topic เดียว + action field หรือแยก topic ต่อ action? | ⏳ ต้องตัดสิน |
| D7 | Task ถูกสร้าง 2 ที่? WF PostInstancesHandler สร้าง Task แล้ว แต่ saga ก็ raise TaskCreationRequested (ITaskPort) | ⏳ ต้อง dedupe ใครเป็นเจ้าของ Task lifecycle |