From 870420204ea6627d521c98f75a827a3397a431d7 Mon Sep 17 00:00:00 2001 From: admin Date: Sun, 3 May 2026 10:36:34 +0700 Subject: [PATCH] 690503:1036 Update workflow #02 --- .../workflow-engine/workflow-engine.service.spec.ts | 8 +++++--- lcbp3.code-workspace | 2 +- specs/03-Data-and-Storage/03-01-data-dictionary.md | 9 +++++---- .../lcbp3-v1.8.0-schema-02-tables.sql | 5 +++++ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/backend/src/modules/workflow-engine/workflow-engine.service.spec.ts b/backend/src/modules/workflow-engine/workflow-engine.service.spec.ts index 76f5bd1..fd6d553 100644 --- a/backend/src/modules/workflow-engine/workflow-engine.service.spec.ts +++ b/backend/src/modules/workflow-engine/workflow-engine.service.spec.ts @@ -127,7 +127,7 @@ describe('WorkflowEngineService', () => { provide: getRepositoryToken(Attachment), useValue: { find: jest.fn(), - update: jest.fn(), + update: jest.fn().mockResolvedValue({ affected: 0 }), }, }, { provide: WorkflowDslService, useValue: mockDslService }, @@ -727,7 +727,9 @@ describe('WorkflowEngineService', () => { it('T024d: should rollback attachments to temp when DB transaction fails (FR-019)', async () => { // Arrange: commit ล้มเหลว — คาดว่า attachments จะถูก revert กลับเป็น temp - (instanceRepo.findOne as jest.Mock).mockResolvedValue(null); // no pre-check needed (no attachment state) + (instanceRepo.findOne as jest.Mock).mockResolvedValue({ + currentState: 'PENDING_REVIEW', + }); // ผ่าน pre-check, transaction จะ fail ที่ commit mockQueryRunner.manager.findOne.mockResolvedValue({ ...baseInstance, versionNo: 5, @@ -759,7 +761,7 @@ describe('WorkflowEngineService', () => { // FR-019: attachmentRepo.update ต้องถูกเรียกเพื่อ revert ไฟล์กลับเป็น temp expect(attachmentRepo.update).toHaveBeenCalledWith( expect.objectContaining({ - publicId: ['att-rollback-1', 'att-rollback-2'], + publicId: In(['att-rollback-1', 'att-rollback-2']), }), expect.objectContaining({ isTemporary: true }) ); diff --git a/lcbp3.code-workspace b/lcbp3.code-workspace index 927aaed..ab2631f 100644 --- a/lcbp3.code-workspace +++ b/lcbp3.code-workspace @@ -22,7 +22,7 @@ // EDITOR SETTINGS // ======================================== - "editor.fontSize": 19, + "editor.fontSize": 18, "editor.tabSize": 2, "editor.lineHeight": 1.6, "editor.rulers": [80, 120], diff --git a/specs/03-Data-and-Storage/03-01-data-dictionary.md b/specs/03-Data-and-Storage/03-01-data-dictionary.md index 2bc2060..fdfdf86 100644 --- a/specs/03-Data-and-Storage/03-01-data-dictionary.md +++ b/specs/03-Data-and-Storage/03-01-data-dictionary.md @@ -1724,6 +1724,7 @@ erDiagram | current_state | VARCHAR(50) | NOT NULL | สถานะปัจจุบัน | | status | ENUM | DEFAULT 'ACTIVE' | ACTIVE, COMPLETED, CANCELLED, TERMINATED | | context | JSON | NULL | ตัวแปร Context สำหรับตัดสินใจ (รวม contractId, projectId, initiatorId เป็นต้น) | +| **version_no** | **INT** | **NOT NULL, DEFAULT 1** | **[ADR-001 v1.1 FR-002] Optimistic lock counter — incremented on every successful transition. Client sends current value; server rejects with 409 if mismatch.** | | created_at | TIMESTAMP | DEFAULT NOW | เวลาที่สร้าง | | updated_at | TIMESTAMP | ON UPDATE | เวลาที่อัปเดตล่าสุด | @@ -1737,12 +1738,11 @@ erDiagram - PRIMARY KEY (id) - FOREIGN KEY (definition_id) REFERENCES workflow_definitions(id) ON DELETE CASCADE -- **FOREIGN KEY (contract_id) REFERENCES contracts(id) ON DELETE SET NULL** [delta-07] +- FOREIGN KEY (contract_id) REFERENCES contracts(id) ON DELETE SET NULL - INDEX (entity_type, entity_id) - INDEX (current_state) -- **INDEX (contract_id, entity_type, status)** — `idx_wf_inst_contract` [delta-07] - ---- +- INDEX (contract_id, entity_type, status) +- INDEX (id, version_no) — `idx_wf_inst_version` [ADR-001 v1.1 FR-002] — Supports CAS check: WHERE id = ? AND version_no = ? ### 10.3 workflow_histories @@ -1756,6 +1756,7 @@ erDiagram | to_state | VARCHAR(50) | NOT NULL | สถานะปลายทาง | | action | VARCHAR(50) | NOT NULL | Action ที่กระทำ | | action_by_user_id | INT | FK, NULL | User ID ผู้กระทำ | +| **action_by_user_uuid** | **VARCHAR(36)** | **NULL** | **[ADR-019 FR-003] UUID ของ User ผู้ดำเนินการ — ใช้ใน API Response แทน INT FK. NULL = System Action หรือ Pre-migration record** | | comment | TEXT | NULL | ความเห็น | | metadata | JSON | NULL | Snapshot ข้อมูล ณ ขณะนั้น | | created_at | TIMESTAMP | DEFAULT NOW | เวลาที่กระทำ | diff --git a/specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql b/specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql index 8f25611..f3d4fc9 100644 --- a/specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql +++ b/specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql @@ -1381,6 +1381,7 @@ CREATE TABLE workflow_instances ( 'TERMINATED' ) DEFAULT 'ACTIVE' COMMENT 'สถานะภาพรวม', context JSON NULL COMMENT 'ตัวแปร Context สำหรับตัดสินใจ', + version_no INT NOT NULL DEFAULT 1 COMMENT 'Optimistic lock counter — incremented on every successful transition (ADR-001 v1.1 FR-002). Client sends current value; server rejects with 409 if mismatch.', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_wf_inst_def FOREIGN KEY (definition_id) REFERENCES workflow_definitions (id) ON DELETE CASCADE, @@ -1394,6 +1395,9 @@ CREATE INDEX idx_wf_inst_contract ON workflow_instances (contract_id, entity_typ CREATE INDEX idx_wf_inst_state ON workflow_instances (current_state); +-- Index เพื่อรองรับ CAS check: WHERE id = ? AND version_no = ? +CREATE INDEX idx_wf_inst_version ON workflow_instances (id, version_no); + -- 3. ตารางเก็บประวัติ (Audit Log / History) CREATE TABLE workflow_histories ( id CHAR(36) NOT NULL PRIMARY KEY COMMENT 'UUID', @@ -1402,6 +1406,7 @@ CREATE TABLE workflow_histories ( to_state VARCHAR(50) NOT NULL COMMENT 'สถานะปลายทาง', ACTION VARCHAR(50) NOT NULL COMMENT 'Action ที่กระทำ', action_by_user_id INT NULL COMMENT 'User ID ผู้กระทำ', + action_by_user_uuid VARCHAR(36) NULL COMMENT 'UUID ของ User ผู้ดำเนินการ — ใช้ใน API Response แทน INT FK (ADR-019). NULL = System Action หรือ Pre-migration record', COMMENT TEXT NULL COMMENT 'ความเห็น', metadata JSON NULL COMMENT 'Snapshot ข้อมูล ณ ขณะนั้น', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,