193 lines
9.6 KiB
Markdown
193 lines
9.6 KiB
Markdown
# Implementation Plan: ADR-021 Integrated Workflow Context & Step-specific Attachments
|
|
|
|
**Branch**: `200-fullstacks/202-adr-021-integrated-workflow-context` | **Date**: 2026-04-12 | **Spec**: [spec.md](./spec.md)
|
|
**Location**: `specs/200-fullstacks/202-adr-021-integrated-workflow-context/`
|
|
**Input**: Feature specification from ADR-021
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
ปรับปรุง Workflow Engine ให้รองรับ (1) **Integrated Banner** ที่ยุบรวม Metadata + Status + Actions ไว้ด้วยกัน (2) **Vertical Timeline Lifecycle** พร้อม Active Step Highlighting และ (3) **Step-specific Attachments** ที่เชื่อมโยงไฟล์แนบกับ `workflow_history` ของแต่ละขั้นตอนโดยตรง
|
|
|
|
แนวทางเทคนิค: ขยาย `workflow_histories` ด้วย FK ใน `attachments` (Nullable) + ขยาย `WorkflowTransitionDto` รับ `attachmentPublicIds` (pre-uploaded UUIDv7 list) + สร้าง Frontend components ใหม่ 4 ชิ้น
|
|
|
|
---
|
|
|
|
## Technical Context
|
|
|
|
**Language/Version**: TypeScript 5.x (strict mode), Node.js 20+
|
|
**Primary Dependencies**:
|
|
- Backend: NestJS 10, TypeORM 0.3, MariaDB 10.6+, Redis (Redlock), BullMQ
|
|
- Frontend: Next.js 14 (App Router), TailwindCSS 3.4, shadcn/ui, TanStack Query v5, React Hook Form + Zod
|
|
**Storage**: MariaDB (schema via SQL delta — ADR-009), MinIO / Local FS via `StorageService`
|
|
**Testing**: Jest (backend unit + e2e), Vitest (frontend)
|
|
**Target Platform**: QNAP Container Station (Docker), Browser (Chrome/Edge latest)
|
|
**Project Type**: Web application (backend/ + frontend/ monorepo)
|
|
**Performance Goals**: (1) Workflow history + attachment join query < 200ms p95 (mitigated by Redis Cache TTL 1h); (2) `POST /instances/:id/transition` (พร้อม file) P95 ≤ 5 วินาที สำหรับ file ≤ 10MB (รวม ClamAV + Redlock + DB transaction)
|
|
**Constraints**: No TypeORM migrations (ADR-009); UUID via `publicId` only (ADR-019); ClamAV scan mandatory (ADR-016); BullMQ for all async jobs (ADR-008)
|
|
**Scale/Scope**: ~50 concurrent users, documents in hundreds per project
|
|
|
|
---
|
|
|
|
## Constitution Check
|
|
|
|
_GATE: Checked against `.windsurfrules` before Phase 0. Re-verified after Phase 1._
|
|
|
|
| Gate | Status | Notes |
|
|
|------|--------|-------|
|
|
| **🔴 UUID Pattern (ADR-019)** | ✅ PASS | All attachment references via `publicId` (UUIDv7 string). `workflow_history_id` FK value is CHAR(36) UUID from `workflow_histories.id`. No `parseInt` usage. |
|
|
| **🔴 Schema via SQL Delta (ADR-009)** | ✅ PASS | Delta file `04-add-workflow-history-id-to-attachments.sql` — no TypeORM migration |
|
|
| **🔴 Two-Phase Upload (ADR-016)** | ✅ PASS | Files uploaded via existing Two-Phase endpoint first; `publicId`s referenced in transition DTO |
|
|
| **🔴 ClamAV Scan (ADR-016)** | ✅ PASS | ClamAV scan runs during Phase 1 of file upload (before transition) |
|
|
| **🔴 CASL Guard (ADR-016)** | ✅ PASS | New `WorkflowTransitionGuard` implements 4-Level RBAC |
|
|
| **🔴 Idempotency-Key (Security Rule #1)** | ✅ PASS | `POST /instances/:id/transition` validates `Idempotency-Key` header |
|
|
| **🔴 BullMQ Async (ADR-008)** | ✅ PASS | Notifications dispatched via `WorkflowEventService` (existing BullMQ pattern) |
|
|
| **🔴 No `any` types** | ✅ PASS | All new types fully typed |
|
|
| **🟡 Thin Controller** | ✅ PASS | Controller delegates to Service; Guard handles RBAC |
|
|
| **🟡 Test Coverage 80% business logic** | ⚠️ REQUIRED | See testing plan in Phase 3 |
|
|
| **🔴 Redis Redlock (ADR-002)** | ✅ PASS | Redlock applied to `instanceId` during `processTransition()` — Fail-closed: Retry 3x (500ms exponential backoff) → HTTP 503 if Redis unavailable |
|
|
| **🔴 Upload State Restriction** | ✅ PASS | Step-attachment upload permitted only in `PENDING_REVIEW`/`PENDING_APPROVAL`; Terminal states (`APPROVED`,`REJECTED`,`CLOSED`) → HTTP 409 |
|
|
|
|
---
|
|
|
|
## Project Structure
|
|
|
|
### Documentation (this feature)
|
|
|
|
```text
|
|
specs/feat/adr-021-integrated-workflow-context/
|
|
├── spec.md # Feature specification
|
|
├── plan.md # This file
|
|
├── tasks.md # Generated by speckit-tasks
|
|
└── checklists/ # Quality checklists
|
|
```
|
|
|
|
### Source Code (impacted files)
|
|
|
|
```text
|
|
# 🔴 Backend — DB & Entities
|
|
specs/03-Data-and-Storage/deltas/
|
|
└── 04-add-workflow-history-id-to-attachments.sql [NEW]
|
|
|
|
backend/src/common/file-storage/entities/
|
|
└── attachment.entity.ts [MODIFY — add workflowHistoryId + relation]
|
|
|
|
backend/src/modules/workflow-engine/entities/
|
|
└── workflow-history.entity.ts [MODIFY — add OneToMany attachments]
|
|
|
|
# 🔴 Backend — API & Guards
|
|
backend/src/modules/workflow-engine/dto/
|
|
└── workflow-transition.dto.ts [MODIFY — add attachmentPublicIds]
|
|
|
|
backend/src/modules/workflow-engine/guards/
|
|
└── workflow-transition.guard.ts [NEW — 4-Level RBAC]
|
|
|
|
backend/src/modules/workflow-engine/
|
|
├── workflow-engine.service.ts [MODIFY — extend processTransition()]
|
|
├── workflow-engine.controller.ts [MODIFY — add idempotency header, guard]
|
|
└── workflow-engine.module.ts [MODIFY — register guard]
|
|
|
|
# 🟡 Frontend — Types
|
|
frontend/types/
|
|
└── workflow.ts [MODIFY — add attachments to WorkflowHistoryStep]
|
|
|
|
frontend/types/dto/workflow-engine/
|
|
└── workflow-engine.dto.ts [MODIFY — add WorkflowTransitionWithAttachmentsDto]
|
|
|
|
# 🟡 Frontend — New Components
|
|
frontend/components/workflow/
|
|
├── integrated-banner.tsx [NEW — Status + Metadata + Action bar]
|
|
└── workflow-lifecycle.tsx [NEW — Vertical timeline with Indigo active step]
|
|
|
|
frontend/components/common/
|
|
└── file-preview-modal.tsx [NEW — PDF/Image inline preview]
|
|
|
|
# 🟡 Frontend — New Hook
|
|
frontend/hooks/
|
|
└── use-workflow-action.ts [NEW — upload + transition orchestration]
|
|
|
|
# 🟡 Frontend — Page Refactors (use new components)
|
|
frontend/app/(dashboard)/rfas/[uuid]/page.tsx [MODIFY — integrate IntegratedBanner + WorkflowLifecycle]
|
|
frontend/app/(dashboard)/transmittals/[uuid]/page.tsx [MODIFY — same as RFA]
|
|
frontend/app/(dashboard)/circulation/[uuid]/page.tsx [MODIFY — same as RFA]
|
|
frontend/app/(dashboard)/correspondences/[uuid]/page.tsx [MODIFY — same as RFA]
|
|
```
|
|
|
|
---
|
|
|
|
## Complexity Tracking
|
|
|
|
_No constitution violations. Architecture is additive (Nullable FK, extended DTO, new components)._
|
|
|
|
---
|
|
|
|
## Design Decisions
|
|
|
|
### Data Model
|
|
|
|
- `attachments.workflow_history_id` = `CHAR(36) NULL` FK → `workflow_histories.id`
|
|
- `ON DELETE SET NULL` (preserve attachment records if history row deleted)
|
|
- Composite index: `INDEX idx_att_wfhist_created (workflow_history_id, created_at)`
|
|
- `WorkflowHistory` gains `@OneToMany(() => Attachment, a => a.workflowHistory)` — **lazy-loaded only**
|
|
|
|
### API Contract
|
|
|
|
**Extended `POST /workflow-engine/instances/:instanceId/transition`:**
|
|
```
|
|
Header: Idempotency-Key: <UUIDv7>
|
|
Body: {
|
|
action: string // existing
|
|
comment?: string // existing
|
|
payload?: Record // existing
|
|
attachmentPublicIds?: string[] // NEW — UUIDv7 list of pre-uploaded attachments
|
|
}
|
|
```
|
|
|
|
**New `GET /workflow-engine/instances/:instanceId/history`:**
|
|
```
|
|
Response: WorkflowHistoryItem[] with nested attachments[] per step
|
|
```
|
|
|
|
### Frontend Architecture
|
|
|
|
3 new components follow **compound pattern**:
|
|
- `<IntegratedBanner>` — Status + Metadata + Action bar
|
|
- `<WorkflowLifecycle>` — Vertical timeline, Indigo active step (pulse animation)
|
|
- `<FilePreviewModal>` — PDF iframe / Image viewer
|
|
|
|
**`use-workflow-action` hook responsibilities:**
|
|
1. Validate `Idempotency-Key` (generate UUIDv7 once per action intent)
|
|
2. Guard: Check `currentState ∈ {PENDING_REVIEW, PENDING_APPROVAL}` before transition
|
|
3. Ensure all `attachmentPublicIds` are committed (not temp) before transition
|
|
4. Call `POST /instances/:id/transition` with `Idempotency-Key` header
|
|
5. Handle HTTP 503 → toast "ระบบยุ่ง กรุณาลองใหม่"
|
|
6. Invalidate TanStack Query cache for the document + workflow instance
|
|
|
|
**Modules in scope (v1.8.6):** RFA, Transmittal, Circulation, Correspondence (4 modules)
|
|
|
|
---
|
|
|
|
## Risk Register
|
|
|
|
| Risk | Likelihood | Impact | Mitigation |
|
|
|------|-----------|--------|------------|
|
|
| N+1 query on history + attachments join | Medium | High | Eager-load only when explicitly querying history; Redis cache TTL 1h |
|
|
| Race condition: 2 users upload to same step simultaneously | Low | High | Redis Redlock on `instanceId` — only 1 transition allowed at a time |
|
|
| Attachment linked to wrong history record | Low | High | `processTransition()` creates history row first, then links attachments in same transaction |
|
|
| ClamAV timeout during upload | Low | Medium | Upload endpoint has its own timeout; transition is decoupled |
|
|
| Frontend: stale workflow state after transition | Medium | Medium | `use-workflow-action` hook invalidates TanStack Query cache on success |
|
|
|
|
---
|
|
|
|
## Dependencies Map
|
|
|
|
```
|
|
ADR-021
|
|
├── ADR-001 (Workflow Engine DSL) — extends processTransition()
|
|
├── ADR-002 (Redis Redlock) — existing lock pattern applied to transition
|
|
├── ADR-016 (Security) — Two-Phase upload, ClamAV, CASL Guard
|
|
├── ADR-019 (UUID) — publicId for all attachment references
|
|
└── ADR-008 (BullMQ) — notification dispatch (unchanged, existing pattern)
|
|
```
|