690414:1113 Update README.md /.agents/skills, /.windsurf/workflows

This commit is contained in:
2026-04-14 11:13:42 +07:00
parent 02400fd88c
commit 6d45bdaeb5
194 changed files with 12708 additions and 8762 deletions
@@ -0,0 +1,355 @@
openapi: "3.0.3"
info:
title: "ADR-021: Workflow Transition with Step-specific Attachments"
version: "1.8.6"
description: |
Extended Workflow Engine API contracts for ADR-021.
- POST /instances/:id/transition — extended with attachmentPublicIds
- GET /instances/:id/history — new endpoint returning history with attachments per step
servers:
- url: "/api/workflow-engine"
description: "NAP-DMS Backend API"
security:
- BearerAuth: []
components:
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
headers:
IdempotencyKey:
description: |
UUIDv7 idempotency token. If the same key+userId is received within 24h,
the cached response is returned without re-processing. (ADR-021 §5.1)
schema:
type: string
format: uuid
required: true
schemas:
WorkflowTransitionRequest:
type: object
required:
- action
properties:
action:
type: string
description: "Action ที่ต้องการทำ — ต้องตรงกับ DSL ของ Workflow นี้"
example: "APPROVE"
enum: [APPROVE, REJECT, RETURN, ACKNOWLEDGE]
comment:
type: string
description: "ความเห็นประกอบการอนุมัติ"
example: "อนุมัติแล้ว ดำเนินการต่อได้เลย"
nullable: true
payload:
type: object
description: "ข้อมูลเพิ่มเติมสำหรับ DSL Context"
additionalProperties: true
nullable: true
example:
urgent: true
attachmentPublicIds:
type: array
description: |
รายการ publicId (UUIDv7) ของไฟล์แนบที่ต้องการผูกกับขั้นตอนนี้.
ไฟล์ต้องผ่าน Two-Phase Upload ก่อน (is_temporary = false, ClamAV passed).
ADR-019: ใช้ publicId เท่านั้น — ห้ามใช้ INT id.
items:
type: string
format: uuid
example: "019505a1-7c3e-7000-8000-abc123def456"
maxItems: 20
nullable: true
WorkflowTransitionResponse:
type: object
properties:
success:
type: boolean
example: true
nextState:
type: string
description: "สถานะใหม่หลัง Transition"
example: "APPROVED"
historyId:
type: string
format: uuid
description: "ID ของ WorkflowHistory record ที่เพิ่งสร้าง"
example: "b8a2e3c1-4567-4abc-8def-0123456789ab"
isCompleted:
type: boolean
description: "true ถ้า Workflow สิ้นสุดแล้ว (Terminal State)"
example: false
attachmentsLinked:
type: integer
description: "จำนวนไฟล์แนบที่ถูก link กับ History record นี้"
example: 2
AttachmentSummary:
type: object
properties:
publicId:
type: string
format: uuid
description: "UUIDv7 identifier (ADR-019)"
example: "019505a1-7c3e-7000-8000-abc123def456"
originalFilename:
type: string
example: "drawing-rev-A.pdf"
mimeType:
type: string
example: "application/pdf"
fileSize:
type: integer
description: "ขนาดไฟล์ (bytes)"
example: 2048000
createdAt:
type: string
format: date-time
example: "2026-04-12T10:30:00Z"
WorkflowHistoryItem:
type: object
properties:
id:
type: string
format: uuid
description: "UUID ของ history record (PK โดยตรง)"
example: "b8a2e3c1-4567-4abc-8def-0123456789ab"
fromState:
type: string
example: "PENDING_REVIEW"
toState:
type: string
example: "APPROVED"
action:
type: string
example: "APPROVE"
actorName:
type: string
description: "ชื่อผู้ดำเนินการ (populated via user join)"
example: "สมชาย ใจดี"
nullable: true
comment:
type: string
nullable: true
example: "อนุมัติแล้ว"
createdAt:
type: string
format: date-time
example: "2026-04-12T10:30:00Z"
attachments:
type: array
description: "ไฟล์แนบที่อัปโหลดพร้อมขั้นตอนนี้ (Step-specific)"
items:
$ref: "#/components/schemas/AttachmentSummary"
ErrorResponse:
type: object
properties:
statusCode:
type: integer
message:
type: string
description: "Technical error message (for developers)"
userMessage:
type: string
description: "User-friendly message (Thai, for display)"
recoveryAction:
type: string
description: "คำแนะนำสำหรับผู้ใช้ในการแก้ปัญหา"
nullable: true
errorCode:
type: string
nullable: true
paths:
/instances/{instanceId}/transition:
post:
operationId: processWorkflowTransition
summary: "ดำเนินการเปลี่ยนสถานะ Workflow พร้อมแนบไฟล์ประจำขั้นตอน"
description: |
Executes a workflow state transition. Optionally links pre-uploaded attachments
to this workflow history step.
**Security:**
- Requires `workflow.action_review` permission
- 4-Level RBAC: Superadmin > Org Admin > Assigned Handler > Read-only
- Idempotency-Key header REQUIRED (prevents duplicate submissions)
**File Attachment:**
- Files must be pre-uploaded via Two-Phase upload endpoint
- Files must be committed (is_temporary = false, ClamAV passed)
- Max 20 attachments per transition
**Concurrency:**
- Redis Redlock applied to instanceId
- Only 1 concurrent transition allowed per instance
parameters:
- name: instanceId
in: path
required: true
schema:
type: string
format: uuid
description: "Workflow Instance ID (UUID)"
- name: Idempotency-Key
in: header
required: true
schema:
type: string
format: uuid
description: "UUIDv7 idempotency token (TTL: 24h). Re-use returns cached response."
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/WorkflowTransitionRequest"
examples:
approve_with_attachments:
summary: "อนุมัติพร้อมไฟล์แนบ"
value:
action: "APPROVE"
comment: "อนุมัติแล้ว เอกสารครบถ้วน"
attachmentPublicIds:
- "019505a1-7c3e-7000-8000-abc123def456"
- "019505b2-8d4f-7000-9000-def456abc789"
reject_no_attachments:
summary: "ปฏิเสธโดยไม่มีไฟล์แนบ"
value:
action: "REJECT"
comment: "เอกสารไม่ครบถ้วน กรุณาแก้ไขและส่งใหม่"
responses:
"200":
description: "Transition executed successfully"
content:
application/json:
schema:
type: object
properties:
data:
$ref: "#/components/schemas/WorkflowTransitionResponse"
"200_idempotent":
description: "Idempotent response — duplicate key detected, cached result returned"
headers:
X-Idempotent-Replay:
schema:
type: string
enum: ["true"]
content:
application/json:
schema:
type: object
properties:
data:
$ref: "#/components/schemas/WorkflowTransitionResponse"
"400":
description: |
Bad Request — possible causes:
- Missing Idempotency-Key header
- Invalid action for current state
- attachmentPublicIds contains non-committed or non-existent UUIDs
- Workflow not in ACTIVE status
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"403":
description: "Forbidden — user does not have permission for this transition"
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
statusCode: 403
userMessage: "คุณไม่มีสิทธิ์ดำเนินการในขั้นตอนนี้"
recoveryAction: "ติดต่อผู้รับผิดชอบหรือ Admin หากคิดว่านี่เป็นข้อผิดพลาด"
"404":
description: "Workflow Instance not found"
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"409":
description: "Conflict — Concurrent transition in progress (Redlock busy)"
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
statusCode: 409
userMessage: "มีการดำเนินการอื่นอยู่ระหว่างดำเนินการ กรุณารอสักครู่แล้วลองใหม่"
recoveryAction: "รอ 5 วินาทีแล้วลองใหม่อีกครั้ง"
/instances/{instanceId}/history:
get:
operationId: getWorkflowHistory
summary: "ดึงประวัติการเปลี่ยนสถานะพร้อมไฟล์แนบประจำแต่ละขั้นตอน"
description: |
Returns the complete workflow history for an instance, including
step-specific attachments for each transition.
**Caching:** Redis cache TTL 1h, invalidated on state change.
**Security:** Requires `document.view` permission.
parameters:
- name: instanceId
in: path
required: true
schema:
type: string
format: uuid
description: "Workflow Instance ID (UUID)"
responses:
"200":
description: "History with step attachments"
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/WorkflowHistoryItem"
example:
data:
- id: "b8a2e3c1-4567-4abc-8def-0123456789ab"
fromState: "DRAFT"
toState: "PENDING_REVIEW"
action: "SUBMIT"
actorName: "สมชาย ใจดี"
comment: "ส่งเพื่อตรวจสอบ"
createdAt: "2026-04-10T09:00:00Z"
attachments: []
- id: "c9b3f4d2-5678-5bcd-9ef0-1234567890bc"
fromState: "PENDING_REVIEW"
toState: "APPROVED"
action: "APPROVE"
actorName: "วิชัย รักดี"
comment: "อนุมัติแล้ว"
createdAt: "2026-04-12T10:30:00Z"
attachments:
- publicId: "019505a1-7c3e-7000-8000-abc123def456"
originalFilename: "drawing-rev-A.pdf"
mimeType: "application/pdf"
fileSize: 2048000
createdAt: "2026-04-12T10:25:00Z"
"403":
description: "Forbidden"
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"404":
description: "Instance not found"
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"