Files
lcbp3/2_Backend_Plan_V1_4_4.md
2025-11-28 17:12:05 +07:00

2121 lines
75 KiB
Markdown

# 📋 **แผนการพัฒนา Backend (NestJS) - LCBP3-DMS v1.4.4 (ฉบับปรับปรุง)**
**สถานะ:** FINAL GUIDELINE Rev.04
**วันที่:** 2025-11-26
**อ้างอิง:** Requirements v1.4.3 & FullStackJS Guidelines v1.4.3
**Classification:** Internal Technical Documentation
**การแก้ไข:** เพิ่ม T2.5.1-T2.5.9, T3.1.1-T3.1.8,
---
## 🎯 **ภาพรวมโครงการ**
พัฒนา Backend สำหรับระบบบริหารจัดการเอกสารโครงการ (Document Management System) ที่มีความปลอดภัยสูง รองรับการทำงานพร้อมกัน (Concurrency) ได้อย่างถูกต้องแม่นยำ มีสถาปัตยกรรมที่ยืดหยุ่นต่อการขยายตัว และรองรับการจัดการเอกสารที่ซับซ้อน มีระบบ Workflow การอนุมัติ และการควบคุมสิทธิ์แบบ RBAC 4 ระดับ พร้อมมาตรการความปลอดภัยที่ทันสมัย
---
## 📐 **สถาปัตยกรรมระบบ**
### **Technology Stack (Updated)**
- **Framework:** NestJS (TypeScript, ESM)
- **Database:** MariaDB 10.11 (ใช้ Virtual Columns)
- **ORM:** TypeORM (ใช้ Optimistic Locking)
- **Authentication:** JWT + Passport
- **Authorization:** CASL (RBAC 4-level)
- **File Upload:** Multer + Virus Scanning (ClamAV) + Two-Phase Storage
- **Search:** Elasticsearch
- **Notification:** Nodemailer + n8n (Line Integration) + BullMQ Queue
- **Caching/Locking:** Redis (Redlock) สำหรับ Distributed Locking
- **Queue:** BullMQ (Redis) สำหรับ Notification Batching และ Async Jobs
- **Resilience:** Circuit Breaker, Retry Patterns
- **Security:** Helmet, CSRF Protection, Rate Limiting, Idempotency
- **Monitoring:** Winston, Health Checks, Metrics
- **Scheduling:** @nestjs/schedule (Cron Jobs)
- **Documentation:** Swagger
- **Validation:** Zod / Class-validator
### **โครงสร้างโมดูล (Domain-Driven)**
```
📁backend
├── .editorconfig
├── .env
├── .gitignore
├── .prettierrc
├── docker-compose.override.yml.example
├── docker-compose.yml
├── eslint.config.mjs
├── Infrastructure Setup.yml
├── nest-cli.json
├── package-lock.json
├── package.json
├── pnpm-lock.yaml
├── README.md
├── tsconfig.build.json
├── tsconfig.json
├── 📁scripts
│ ├── debug-db.ts
│ └── verify-workflow.ts
├── 📁test
│ ├── app.e2e-spec.ts
│ ├── jest-e2e.json
│ ├── phase3-workflow.e2e-spec.ts
│ └── simple.e2e-spec.ts
├── 📁uploads
│ └── 📁temp
│ ├── 5a6d4c26-84b2-4c8a-b177-9fa267651a93.pdf
│ └── d60d9807-a22d-4ca0-b99a-5d5d8b81b3e8.pdf
└── 📁src
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
├── main.ts
├── redlock.d.ts
├── 📁common
│ ├── 📁auth
│ │ ├── 📁dto
│ │ │ ├── login.dto.ts
│ │ │ └── register.dto.ts
│ │ ├── 📁strategies
│ │ │ ├── jwt-refresh.strategy.ts
│ │ │ └── jwt.strategy.ts
│ │ ├── auth.controller.spec.ts
│ │ ├── auth.controller.ts
│ │ ├── auth.module.ts
│ │ ├── auth.service.spec.ts
│ │ └── auth.service.ts
│ ├── 📁config
│ │ ├── env.validation.ts
│ │ └── redis.config.ts
│ ├── 📁decorators
│ │ ├── audit.decorator.ts
│ │ ├── bypass-maintenance.decorator.ts
│ │ ├── circuit-breaker.decorator.ts
│ │ ├── current-user.decorator.ts
│ │ ├── idempotency.decorator.ts
│ │ ├── require-permission.decorator.ts
│ │ └── retry.decorator.ts
│ ├── 📁entities
│ │ ├── audit-log.entity.ts
│ │ └── base.entity.ts
│ ├── 📁exceptions
│ │ └── http-exception.filter.ts
│ ├── 📁file-storage
│ │ ├── 📁entities
│ │ │ └── attachment.entity.ts
│ │ ├── file-cleanup.service.ts
│ │ ├── file-storage.controller.spec.ts
│ │ ├── file-storage.controller.ts
│ │ ├── file-storage.module.ts
│ │ ├── file-storage.service.spec.ts
│ │ └── file-storage.service.ts
│ ├── 📁guards
│ │ ├── jwt-auth.guard.ts
│ │ ├── jwt-refresh.guard.ts
│ │ ├── maintenance-mode.guard.ts
│ │ └── rbac.guard.ts
│ ├── 📁idempotency
│ │ ├── idempotency.interceptor.ts
│ │ └── performance.interceptor.ts
│ ├── 📁interceptors
│ │ ├── audit-log.interceptor.ts
│ │ ├── idempotency.interceptor.ts
│ │ ├── performance.interceptor.ts
│ │ └── transform.interceptor.ts
│ ├── 📁maintenance
│ ├── 📁resilience
│ │ └── resilience.module.ts
│ └── 📁security
│ ├── 📁services
│ │ ├── crypto.service.ts
│ │ └── request-context.service.ts
│ └── common.module.ts
├── 📁database
│ ├── 📁migrations
│ └── 📁seeds
│ └── workflow-definitions.seed.ts
└── 📁modules
├── 📁circulation
│ ├── 📁dto
│ │ ├── create-circulation.dto.ts
│ │ ├── search-circulation.dto.ts
│ │ └── update-circulation-routing.dto.ts
│ ├── 📁entities
│ │ ├── circulation-routing.entity.ts
│ │ ├── circulation-status-code.entity.ts
│ │ └── circulation.entity.ts
│ ├── circulation.controller.ts
│ ├── circulation.module.ts
│ └── circulation.service.ts
├── 📁correspondence
│ ├── 📁dto
│ │ ├── add-reference.dto.ts
│ │ ├── create-correspondence.dto.ts
│ │ ├── search-correspondence.dto.ts
│ │ ├── submit-correspondence.dto.ts
│ │ └── workflow-action.dto.ts
│ ├── 📁entities
│ │ ├── correspondence-reference.entity.ts
│ │ ├── correspondence-revision.entity.ts
│ │ ├── correspondence-routing.entity.ts
│ │ ├── correspondence-status.entity.ts
│ │ ├── correspondence-sub-type.entity.ts
│ │ ├── correspondence-type.entity.ts
│ │ ├── correspondence.entity.ts
│ │ ├── routing-template-step.entity.ts
│ │ └── routing-template.entity.ts
│ ├── correspondence.controller.spec.ts
│ ├── correspondence.controller.ts
│ ├── correspondence.module.ts
│ ├── correspondence.service.spec.ts
│ └── correspondence.service.ts
├── 📁document-numbering
│ ├── 📁entities
│ │ ├── document-number-counter.entity.ts
│ │ └── document-number-format.entity.ts
│ ├── 📁interfaces
│ │ └── document-numbering.interface.ts
│ ├── document-numbering.module.ts
│ ├── document-numbering.service.spec.ts
│ └── document-numbering.service.ts
├── 📁drawing
│ ├── 📁dto
│ │ ├── create-contract-drawing.dto.ts
│ │ ├── create-shop-drawing-revision.dto.ts
│ │ ├── create-shop-drawing.dto.ts
│ │ ├── search-contract-drawing.dto.ts
│ │ ├── search-shop-drawing.dto.ts
│ │ └── update-contract-drawing.dto.ts
│ ├── 📁entities
│ │ ├── contract-drawing-sub-category.entity.ts
│ │ ├── contract-drawing-volume.entity.ts
│ │ ├── contract-drawing.entity.ts
│ │ ├── shop-drawing-main-category.entity.ts
│ │ ├── shop-drawing-revision.entity.ts
│ │ ├── shop-drawing-sub-category.entity.ts
│ │ └── shop-drawing.entity.ts
│ ├── contract-drawing.controller.ts
│ ├── contract-drawing.service.ts
│ ├── drawing-master-data.controller.ts
│ ├── drawing-master-data.service.ts
│ ├── drawing.module.ts
│ ├── shop-drawing.controller.ts
│ └── shop-drawing.service.ts
├── 📁json-schema
│ ├── 📁dto
│ │ ├── create-json-schema.dto.ts
│ │ ├── search-json-schema.dto.ts
│ │ └── update-json-schema.dto.ts
│ ├── 📁entities
│ │ └── json-schema.entity.ts
│ ├── json-schema.controller.spec.ts
│ ├── json-schema.controller.ts
│ ├── json-schema.module.ts
│ ├── json-schema.service.spec.ts
│ └── json-schema.service.ts
├── 📁master
│ ├── 📁dto
│ │ ├── create-discipline.dto.ts
│ │ ├── create-sub-type.dto.ts
│ │ ├── create-tag.dto.ts
│ │ ├── save-number-format.dto.ts
│ │ ├── search-tag.dto.ts
│ │ └── update-tag.dto.ts
│ ├── 📁entities
│ │ ├── discipline.entity.ts
│ │ └── tag.entity.ts
│ ├── master.controller.ts
│ ├── master.module.ts
│ └── master.service.ts
├── 📁monitoring
│ ├── 📁controllers
│ │ └── health.controller.ts
│ ├── 📁dto
│ │ └── set-maintenance.dto.ts
│ ├── 📁logger
│ │ └── winston.config.ts
│ ├── 📁services
│ │ └── metrics.service.ts
│ ├── monitoring.controller.ts
│ ├── monitoring.module.ts
│ └── monitoring.service.ts
├── 📁notification
│ ├── 📁dto
│ │ ├── create-notification.dto.ts
│ │ └── search-notification.dto.ts
│ ├── 📁entities
│ │ └── notification.entity.ts
│ ├── notification-cleanup.service.ts
│ ├── notification.controller.ts
│ ├── notification.gateway.ts
│ ├── notification.module.ts
│ ├── notification.processor.ts
│ └── notification.service.ts
├── 📁project
│ ├── 📁dto
│ │ ├── create-project.dto.ts
│ │ ├── search-project.dto.ts
│ │ └── update-project.dto.ts
│ ├── 📁entities
│ │ ├── contract-organization.entity.ts
│ │ ├── contract.entity.ts
│ │ ├── organization.entity.ts
│ │ ├── project-organization.entity.ts
│ │ └── project.entity.ts
│ ├── project.controller.spec.ts
│ ├── project.controller.ts
│ ├── project.module.ts
│ ├── project.service.spec.ts
│ └── project.service.ts
├── 📁rfa
│ ├── 📁dto
│ │ ├── create-rfa.dto.ts
│ │ ├── search-rfa.dto.ts
│ │ └── update-rfa.dto.ts
│ ├── 📁entities
│ │ ├── rfa-approve-code.entity.ts
│ │ ├── rfa-item.entity.ts
│ │ ├── rfa-revision.entity.ts
│ │ ├── rfa-status-code.entity.ts
│ │ ├── rfa-type.entity.ts
│ │ ├── rfa-workflow-template-step.entity.ts
│ │ ├── rfa-workflow-template.entity.ts
│ │ ├── rfa-workflow.entity.ts
│ │ └── rfa.entity.ts
│ ├── rfa.controller.ts
│ ├── rfa.module.ts
│ └── rfa.service.ts
├── 📁search
│ ├── 📁dto
│ │ └── search-query.dto.ts
│ ├── search.controller.ts
│ ├── search.module.ts
│ └── search.service.ts
├── 📁transmittal
│ ├── 📁dto
│ │ ├── create-transmittal.dto.ts
│ │ ├── search-transmittal.dto.ts
│ │ └── update-transmittal.dto.ts
│ ├── 📁entities
│ │ ├── transmittal-item.entity.ts
│ │ └── transmittal.entity.ts
│ ├── transmittal.controller.ts
│ ├── transmittal.module.ts
│ └── transmittal.service.ts
├── 📁user
│ ├── 📁dto
│ │ ├── assign-role.dto.ts
│ │ ├── create-user.dto.ts
│ │ ├── update-preference.dto.ts
│ │ └── update-user.dto.ts
│ ├── 📁entities
│ │ ├── permission.entity.ts
│ │ ├── role.entity.ts
│ │ ├── user-assignment.entity.ts
│ │ ├── user-preference.entity.ts
│ │ └── user.entity.ts
│ ├── user-assignment.service.ts
│ ├── user-preference.service.ts
│ ├── user.controller.ts
│ ├── user.module.ts
│ ├── user.service.spec.ts
│ └── user.service.ts
└── 📁workflow-engine
├── 📁dto
│ ├── create-workflow-definition.dto.ts
│ ├── evaluate-workflow.dto.ts
│ ├── get-available-actions.dto.ts
│ └── update-workflow-definition.dto.ts
├── 📁entities
│ └── workflow-definition.entity.ts
├── 📁interfaces
│ └── workflow.interface.ts
├── workflow-dsl.service.ts
├── workflow-engine.controller.ts
├── workflow-engine.module.ts
├── workflow-engine.service.spec.ts
└── workflow-engine.service.ts
```
---
## 🗓️ **แผนการพัฒนาแบบ Phase-Based**
- _(Dependency Diagram ถูกละไว้เพื่อประหยัดพื้นที่ เนื่องจากมีการอ้างอิงจากแผนเดิม)_
## **Phase 0: Infrastructure & Configuration (สัปดาห์ที่ 1)**
**Milestone:** โครงสร้างพื้นฐานพร้อม รองรับ Secrets ที่ปลอดภัย และ Redis พร้อมใช้งาน
### **Phase 0: Tasks**
- **[ ] T0.1 Secure Configuration Setup**
- [ ] ปรับปรุง `ConfigModule` ให้รองรับการอ่านค่าจาก Environment Variables
- [ ] สร้าง Template `docker-compose.override.yml.example` สำหรับ Dev
- [ ] Validate Config ด้วย Joi/Zod ตอน Start App (Throw error ถ้าขาด Secrets)
- [ ] **Security:** Setup network segmentation และ firewall rules
- [ ] **Deliverable:** Configuration Management พร้อมใช้งานอย่างปลอดภัย
- [ ] **Dependencies:** None (Task เริ่มต้น)
- **[ ] T0.2 Redis & Queue Infrastructure**
- [ ] Setup Redis Container
- [ ] Setup BullMQ Module ใน NestJS สำหรับจัดการ Background Jobs
- [ ] Setup Redis Client สำหรับ Distributed Lock (Redlock)
- [ ] **Security:** Setup Redis authentication และ encryption
- [ ] **Deliverable:** Redis และ Queue System พร้อมใช้งาน
- [ ] **Dependencies:** T0.1
- **[ ] T0.3 Setup Database Connection**
- [ ] Import SQL Schema v1.4.2 เข้า MariaDB
- [ ] Run Seed Data (organizations, users, roles, permissions)
- [ ] Configure TypeORM ใน AppModule
- [ ] **Security:** Setup database connection encryption
- [ ] ทดสอบ Connection
- [ ] **Deliverable:** Database พร้อมใช้งาน, มี Seed Data
- [ ] **Dependencies:** T0.1
- **[ ] T0.4 Setup Git Repository**
- [ ] สร้าง Repository ใน Gitea (git.np-dms.work)
- [ ] Setup .gitignore, README.md, SECURITY.md
- [ ] Commit Initial Project
- [ ] **Deliverable:** Code อยู่ใน Version Control
- [ ] **Dependencies:** T0.1, T0.2, T0.3
---
## **Phase 1: Core Foundation & Security (สัปดาห์ที่ 2-3)**
**Milestone:** ระบบ Authentication, Authorization, Idempotency พื้นฐาน และ Security Baseline
### **Phase 1: Tasks**
- **[ ] T1.1 CommonModule - Base Infrastructure**
- [ ] สร้าง Base Entity (id, created_at, updated_at, deleted_at)
- [ ] สร้าง Global Exception Filter (ไม่เปิดเผย sensitive information)
- [ ] สร้าง Response Transform Interceptor
- [ ] สร้าง Audit Log Interceptor
- [ ] **Idempotency Interceptor:** ตรวจสอบ Header `Idempotency-Key` และ Cache Response เดิมใน Redis
- [ ] **Maintenance Mode Middleware:** ตรวจสอบ Flag ใน **Redis Key** เพื่อ Block API ระหว่างปรับปรุงระบบ **(Admin ใช้ Redis/Admin UI ในการ Toggle สถานะ)**
- [ ] สร้าง RequestContextService - สำหรับเก็บข้อมูลระหว่าง Request
- [ ] สร้าง ConfigService - Centralized configuration management
- [ ] สร้าง CryptoService - สำหรับ encryption/decryption
- [ ] **Security:** Implement input validation pipeline
- [ ] **Deliverable:** Common Services พร้อมใช้ รวมถึง Idempotency และ Maintenance Mode
- [ ] **Dependencies:** T0.2, T0.3
- **[ ] T1.2 AuthModule - JWT Authentication**
- [ ] สร้าง Entity: User
- [ ] สร้าง AuthService:
- [ ] login(username, password) → JWT Token
- [ ] validateUser(username, password) → User | null
- [ ] Password Hashing (bcrypt) + salt
- [ ] สร้าง JWT Strategy (Passport)
- [ ] สร้าง JwtAuthGuard
- [ ] สร้าง Controllers:
- [ ] POST /auth/login → { access_token, refresh_token }
- [ ] POST /auth/register → Create User (Admin only)
- [ ] POST /auth/refresh → Refresh token
- [ ] POST /auth/logout → Revoke token
- [ ] GET /auth/profile (Protected)
- [ ] **Security:** Implement rate limiting สำหรับ authentication endpoints
- [ ] **Deliverable:** ล็อกอิน/ล็อกเอาต์ทำงานได้อย่างปลอดภัย
- [ ] **Dependencies:** T1.1, T0.3
- **[ ] T1.3 UserModule - User Management**
- [ ] สร้าง Entities: User, Role, Permission, UserRole, UserAssignment, **UserPreference**
- [ ] สร้าง UserService CRUD (พร้อม soft delete)
- [ ] สร้าง RoleService CRUD
- [ ] สร้าง PermissionService (Read-Only, จาก Seed)
- [ ] สร้าง UserAssignmentService - สำหรับจัดการ user assignments ตาม scope
- [ ] สร้าง **UserPreferenceService** - สำหรับจัดการการตั้งค่า Notification และ UI
- [ ] สร้าง Controllers:
- [ ] GET /users → List Users (Paginated)
- [ ] GET /users/:id → User Detail
- [ ] POST /users → Create User (ต้องบังคับเปลี่ยน password ครั้งแรก)
- [ ] PUT /users/:id → Update User
- [ ] DELETE /users/:id → Soft Delete
- [ ] GET /roles → List Roles
- [ ] POST /roles → Create Role (Admin)
- [ ] PUT /roles/:id/permissions → Assign Permissions
- [ ] **Security:** Implement permission checks สำหรับ user management
- [ ] **Deliverable:** จัดการผู้ใช้, Role, และ Preferences ได้
- [ ] **Dependencies:** T1.1, T1.2
- **[ ] T1.4 RBAC Guard - 4-Level Authorization**
- [ ] สร้าง @RequirePermission() Decorator
- [ ] สร้าง RbacGuard ที่ตรวจสอบ 4 ระดับ:
- [ ] Global Permissions
- [ ] Organization Permissions
- [ ] Project Permissions
- [ ] Contract Permissions
- [ ] Permission Hierarchy Logic
- [ ] Integration กับ CASL
- [ ] **Security:** Implement audit logging สำหรับ permission checks
- [ ] **Deliverable:** ระบบสิทธิ์ทำงานได้ทั้ง 4 ระดับ
- [ ] **Dependencies:** T1.1, T1.3
- **[ ] T1.5 ProjectModule - Base Structures**
- [ ] สร้าง Entities:
- [ ] Organization
- [ ] Project
- [ ] Contract
- [ ] ProjectOrganization (Junction)
- [ ] ContractOrganization (Junction)
- [ ] สร้าง Services & Controllers:
- [ ] GET /organizations → List
- [ ] POST /projects → Create (Superadmin)
- [ ] GET /projects/:id/contracts → List Contracts
- [ ] POST /projects/:id/contracts → Create Contract
- [ ] **Security:** Implement data isolation ระหว่าง organizations
- [ ] **Deliverable:** จัดการโครงสร้างโปรเจกต์ได้
- [ ] **Dependencies:** T1.1, T1.2, T0.3
---
## **Phase 2: High-Integrity Data & File Management (สัปดาห์ที่ 4)**
**Milestone:** Master Data, ระบบจัดการไฟล์แบบ Transactional, Document Numbering ที่ไม่มี Race Condition, JSON details system พร้อมใช้งาน
### **Phase 2: Tasks**
- **[ ] T2.1 Virtual Columns for JSON**
- [ ] ออกแบบ Migration Script สำหรับตารางที่มี JSON Details
- [ ] เพิ่ม **Generated Columns (Virtual)** สำหรับฟิลด์ที่ใช้ Search บ่อยๆ (เช่น `project_id`, `type`) พร้อม Index
- [ ] **Security:** Implement admin-only access สำหรับ master data
- [ ] **Deliverable:** JSON Data Search Performance ดีขึ้น
- [ ] **Dependencies:** T0.3, T1.1, T1.5
- **[ ] T2.2 FileStorageService - Two-Phase Storage**
- [ ] สร้าง Attachment Entity
- [ ] สร้าง FileStorageService:
- [ ] **Phase 1 (Upload):** API รับไฟล์ → Scan Virus → Save ลง `temp/` → Return `temp_id`
- [ ] **Phase 2 (Commit):** Method `commitFiles(tempIds[])` → ย้ายจาก `temp/` ไป `permanent/{YYYY}/{MM}/` → Update DB
- [ ] File type validation (white-list: PDF, DWG, DOCX, XLSX, PPTX, ZIP)
- [ ] File size check (max 50MB)
- [ ] Generate checksum (SHA-256)
- [ ] **Cleanup Job:** สร้าง Cron Job ลบไฟล์ใน `temp/` ที่ค้างเกิน 24 ชม. **โดยตรวจสอบจากคอลัมน์ `expires_at` ในตาราง `attachments`**
- [ ] สร้าง Controller:
- [ ] POST /files/upload → { temp_id } (Protected)
- [ ] POST /files/commit → { attachment_id, url } (Protected)
- [ ] GET /files/:id/download → File Stream (Protected + Expiration)
- [ ] **Security:** Access Control - ตรวจสอบสิทธิ์ผ่าน Junction Table
- [ ] **Deliverable:** อัปโหลด/ดาวน์โหลดไฟล์ได้อย่างปลอดภัย แบบ Transactional
- [ ] **Dependencies:** T1.1, T1.4
- **[ ] T2.3 DocumentNumberingModule - Double-Lock Mechanism**
- [ ] สร้าง Entities:
- [ ] DocumentNumberFormat
- [ ] DocumentNumberCounter
- [ ] สร้าง DocumentNumberingService:
- [ ] generateNextNumber(projectId, orgId, typeId, year) → string
- [ ] ใช้ **Double-Lock Mechanism**: 1. Acquire **Redis Lock** (Key: `doc_num:{project}:{type}`) 2. Read DB & Calculate Next Number 3. Update DB with **Optimistic Lock** Check (ใช้ `@VersionColumn()`) 4. Release Redis Lock 5. Retry on Failure ด้วย exponential backoff
- [ ] Fallback mechanism เมื่อการขอเลขล้มเหลว
- [ ] Format ตาม Template: {ORG_CODE}-{TYPE_CODE}-{YEAR_SHORT}-{SEQ:4}
- **ไม่มี Controller** (Internal Service เท่านั้น)
- [ ] **Security:** Implement audit log ทุกครั้งที่มีการ generate เลขที่
- [ ] **Deliverable:** Service สร้างเลขที่เอกสารได้ถูกต้องและปลอดภัย ไม่มี Race Condition
- [ ] **Dependencies:** T1.1, T0.3
* **[ ] T2.3 DocumentNumberingModule - Token-Based & Double-Lock** (Updated)
- [ ] Update Entity: `DocumentNumberCounter` (Add `discipline_id` to PK)
- [ ] Implement Token Parser & Replacer Logic (`{DISCIPLINE}`, `{SUBTYPE_NUM}`)
- [ ] Update `generateNextNumber` to handle optional keys (Discipline/SubType)
- [ ] **Deliverable:** Flexible Numbering System
- **[ ] T2.4 SecurityModule - Enhanced Security**
- [ ] สร้าง Input Validation Service:
- [ ] XSS Prevention
- [ ] SQL Injection Prevention
- [ ] CSRF Protection
- [ ] สร้าง RateLimitGuard:
- [ ] Implement rate limiting ตาม strategy (anonymous: 100/hr, authenticated: 500-5000/hr)
- [ ] Different limits สำหรับ endpoints ต่างๆ
- [ ] สร้าง Security Headers Middleware
- [ ] **Security:** Implement content security policy (CSP)
- [ ] **Deliverable:** Security layers ทำงานได้
- [ ] **Dependencies:** T1.1
- **[ ] T2.5 JSON Details & Schema Management**
- [ ] T2.5.1 JsonSchemaModule - Schema Management: สร้าง Service สำหรับ Validate, get, register JSON schemas
- [ ] T2.5.2 DetailsService - Data Processing: สร้าง Service สำหรับ sanitize, transform, compress/decompress JSON
- [ ] T2.5.3 JSON Security & Validation: Implement security checks และ validation rules
- [ ] **Deliverable:** JSON schema system ทำงานได้
- [ ] **Dependencies:** T1.1
### 🚀 **T2.5 JSON Details & Schema Management - Enhanced Implementation Plan**
#### 📋 **Overview**
สร้างระบบจัดการ JSON Schema ที่ครอบคลุมสำหรับ dynamic document details, validation, transformation และ performance optimization
---
#### 🎯 **Enhanced Task Breakdown for T2.5**
##### **[ ] T2.5.1 JSON Schema Registry & Versioning System**
- [ ] **Schema Entity Design** สำหรับเก็บ JSON schemas ทุกประเภท
- [ ] **Version Control System** สำหรับ schema evolution
- [ ] **Migration Strategy** สำหรับ backward-compatible changes
- [ ] **Schema Inheritance** สำหรับ shared field definitions
```typescript
// Schema Entity Design
@Entity()
export class JsonSchema {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string; // 'CORRESPONDENCE_GENERIC', 'RFA_DWG', 'CIRCULATION_INTERNAL'
@Column()
entity_type: string; // 'correspondence', 'rfa', 'circulation'
@Column()
version: number;
@Column('json')
schema_definition: any; // AJV JSON Schema
@Column('json')
ui_schema: any; // UI configuration for form generation
@Column({ default: true })
is_active: boolean;
@Column('json', { nullable: true })
migration_script: any; // Data transformation rules
@CreateDateColumn()
created_at: Date;
@UpdateDateColumn()
updated_at: Date;
// Virtual columns configuration for performance
@Column('json', { nullable: true })
virtual_columns: VirtualColumnConfig[];
}
interface VirtualColumnConfig {
json_path: string; // '$.projectId'
column_name: string; // 'ref_project_id'
data_type: 'INT' | 'VARCHAR' | 'BOOLEAN' | 'DATE';
index_type?: 'INDEX' | 'UNIQUE' | 'FULLTEXT';
is_required: boolean;
}
```
##### **[ ] T2.5.2 Schema Validation & Transformation Engine**
- [ ] **AJV Integration** สำหรับ high-performance JSON validation
- [ ] **Custom Validators** สำหรับ business rule validation
- [ ] **Data Transformation** สำหรับ schema version migration
- [ ] **Sanitization Service** สำหรับ data cleansing
```typescript
@Injectable()
export class JsonSchemaService {
private ajv: Ajv;
constructor() {
this.ajv = new Ajv({
allErrors: true,
coerceTypes: true,
useDefaults: true,
removeAdditional: true,
formats: {
'date-time': true,
email: true,
uri: true,
'document-number': this.documentNumberFormat,
},
});
// Register custom formats and keywords
this.registerCustomValidators();
}
async validateData(
schemaName: string,
data: any,
options: ValidationOptions = {}
): Promise<ValidationResult> {
const schema = await this.getSchema(schemaName);
const validate = this.ajv.compile(schema);
const isValid = validate(data);
if (!isValid) {
return {
isValid: false,
errors: validate.errors,
sanitizedData: null,
};
}
// Apply data transformation if needed
const sanitizedData = await this.sanitizeData(data, schema, options);
return {
isValid: true,
errors: [],
sanitizedData,
};
}
private async sanitizeData(
data: any,
schema: any,
options: ValidationOptions
): Promise<any> {
const sanitized = { ...data };
// Remove unknown properties if not allowed
if (options.removeAdditional !== false) {
const allowedProperties = this.extractPropertyNames(schema);
Object.keys(sanitized).forEach((key) => {
if (!allowedProperties.includes(key)) {
delete sanitized[key];
}
});
}
// Apply custom sanitizers based on field type
await this.applyFieldSanitizers(sanitized, schema);
return sanitized;
}
private registerCustomValidators(): void {
// Custom format for document numbers
this.ajv.addFormat('document-number', {
type: 'string',
validate: (value: string) => {
return /^[A-Z]{3,5}-[A-Z]{2,4}-\d{4}-\d{3,5}$/.test(value);
},
});
// Custom keyword for role-based access
this.ajv.addKeyword({
keyword: 'requiredRole',
type: 'string',
compile: (requiredRole: string) => {
return (data: any, dataPath: string, parentData: any) => {
// Check if user has required role for this field
const userContext = this.getUserContext();
return userContext.roles.includes(requiredRole);
};
},
});
}
}
```
##### **[ ] T2.5.3 Virtual Columns & Performance Optimization**
- [ ] **Virtual Column Generator** สำหรับ JSON field indexing
- [ ] **Migration Scripts** สำหรับสร้าง generated columns
- [ ] **Query Optimizer** สำหรับใช้ virtual columns ใน search
- [ ] **Performance Monitoring** สำหรับ JSON query performance
```typescript
@Injectable()
export class VirtualColumnService {
constructor(
private dataSource: DataSource,
private configService: ConfigService
) {}
async setupVirtualColumns(
tableName: string,
schemaConfig: VirtualColumnConfig[]
): Promise<void> {
const connection = this.dataSource.manager.connection;
for (const config of schemaConfig) {
await this.createVirtualColumn(tableName, config);
}
}
private async createVirtualColumn(
tableName: string,
config: VirtualColumnConfig
): Promise<void> {
const columnDefinition = this.generateColumnDefinition(config);
const sql = `
ALTER TABLE ${tableName}
ADD COLUMN ${config.column_name} ${columnDefinition}
`;
await this.dataSource.query(sql);
// Create index if specified
if (config.index_type) {
await this.createIndex(tableName, config);
}
}
private generateColumnDefinition(config: VirtualColumnConfig): string {
const dataType = this.mapDataType(config.data_type);
const jsonPath = this.escapeJsonPath(config.json_path);
return `${dataType} GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(details, '${jsonPath}'))) VIRTUAL`;
}
private async createIndex(
tableName: string,
config: VirtualColumnConfig
): Promise<void> {
const indexName = `idx_${tableName}_${config.column_name}`;
const sql = `
CREATE ${config.index_type} INDEX ${indexName}
ON ${tableName} (${config.column_name})
`;
await this.dataSource.query(sql);
}
}
// Example virtual column configuration for correspondence
const correspondenceVirtualColumns: VirtualColumnConfig[] = [
{
json_path: '$.projectId',
column_name: 'ref_project_id',
data_type: 'INT',
index_type: 'INDEX',
is_required: true,
},
{
json_path: '$.priority',
column_name: 'ref_priority',
data_type: 'VARCHAR',
index_type: 'INDEX',
is_required: false,
},
{
json_path: '$.dueDate',
column_name: 'ref_due_date',
data_type: 'DATE',
index_type: 'INDEX',
is_required: false,
},
];
```
##### **[ ] T2.5.4 Dynamic Form Schema Management**
- [ ] **UI Schema Definition** สำหรับ frontend form generation
- [ ] **Field Dependency System** สำหรับ conditional fields
- [ ] **Validation Rule Sync** ระหว่าง backend-frontend
- [ ] **Form Template Registry** สำหรับ reusable form patterns
```typescript
interface UiSchema {
type: 'object';
properties: {
[key: string]: UiSchemaField;
};
required?: string[];
layout?: {
type: 'tabs' | 'sections' | 'steps';
groups: LayoutGroup[];
};
}
interface UiSchemaField {
type: 'string' | 'number' | 'boolean' | 'array' | 'object';
widget?: 'text' | 'textarea' | 'select' | 'radio' | 'checkbox' | 'date';
title: string;
description?: string;
placeholder?: string;
enum?: string[];
enumNames?: string[];
dependencies?: FieldDependency[];
conditions?: FieldCondition[];
properties?: { [key: string]: UiSchemaField }; // for nested objects
items?: UiSchemaField; // for arrays
}
interface FieldDependency {
field: string;
condition: {
operator: 'equals' | 'notEquals' | 'contains' | 'greaterThan';
value: any;
};
actions: {
visibility?: boolean;
required?: boolean;
options?: string[];
};
}
// Example: RFA Drawing Schema
const rfaDwgSchema: UiSchema = {
type: 'object',
layout: {
type: 'tabs',
groups: [
{
title: 'Basic Information',
fields: ['title', 'description', 'discipline'],
},
{
title: 'Drawing Details',
fields: ['drawingReferences', 'revision', 'approvalType'],
},
{
title: 'Technical Specifications',
fields: ['materials', 'dimensions', 'tolerances'],
},
],
},
properties: {
title: {
type: 'string',
widget: 'text',
title: 'Drawing Title',
placeholder: 'Enter drawing title...',
required: true,
},
discipline: {
type: 'string',
widget: 'select',
title: 'Discipline',
enum: ['CIVIL', 'STRUCTURAL', 'MECHANICAL', 'ELECTRICAL', 'PLUMBING'],
enumNames: [
'Civil',
'Structural',
'Mechanical',
'Electrical',
'Plumbing',
],
},
drawingReferences: {
type: 'array',
title: 'Related Contract Drawings',
items: {
type: 'string',
widget: 'select',
title: 'Drawing Number',
},
},
approvalType: {
type: 'string',
widget: 'radio',
title: 'Approval Type',
enum: ['FULL_APPROVAL', 'PARTIAL_APPROVAL', 'COMMENTS_ONLY'],
enumNames: ['Full Approval', 'Partial Approval', 'Comments Only'],
},
},
required: ['title', 'discipline', 'approvalType'],
};
```
##### **[ ] T2.5.5 Data Migration & Version Compatibility**
- [ ] **Schema Migration Service** สำหรับอัพเกรด data ระหว่าง versions
- [ ] **Data Transformation Pipeline** สำหรับ backward compatibility
- [ ] **Version Rollback Mechanism** สำหรับ emergency situations
- [ ] **Migration Testing Framework** สำหรับ 确保 data integrity
```typescript
@Injectable()
export class SchemaMigrationService {
async migrateData(
entityType: string,
entityId: string,
targetVersion: number
): Promise<MigrationResult> {
const currentData = await this.getCurrentData(entityType, entityId);
const currentVersion = await this.getCurrentSchemaVersion(
entityType,
entityId
);
const migrationPath = await this.findMigrationPath(
currentVersion,
targetVersion
);
let migratedData = currentData;
for (const migrationStep of migrationPath) {
migratedData = await this.applyMigrationStep(migrationStep, migratedData);
}
// Validate migrated data against target schema
const validationResult = await this.validateAgainstSchema(
migratedData,
targetVersion
);
if (!validationResult.isValid) {
throw new MigrationError(
'MIGRATION_VALIDATION_FAILED',
validationResult.errors
);
}
await this.saveMigratedData(
entityType,
entityId,
migratedData,
targetVersion
);
return {
success: true,
fromVersion: currentVersion,
toVersion: targetVersion,
migratedFields: this.getMigratedFields(currentData, migratedData),
};
}
private async applyMigrationStep(
step: MigrationStep,
data: any
): Promise<any> {
switch (step.type) {
case 'FIELD_RENAME':
return this.renameField(data, step.config);
case 'FIELD_TRANSFORM':
return this.transformField(data, step.config);
case 'FIELD_ADD':
return this.addField(data, step.config);
case 'FIELD_REMOVE':
return this.removeField(data, step.config);
case 'STRUCTURE_CHANGE':
return this.restructureData(data, step.config);
default:
throw new MigrationError('UNKNOWN_MIGRATION_TYPE');
}
}
}
// Example migration configuration
const migrationSteps = [
{
from_version: 1,
to_version: 2,
type: 'FIELD_RENAME',
config: {
old_field: 'project_id',
new_field: 'ref_project_id',
},
},
{
from_version: 2,
to_version: 3,
type: 'FIELD_TRANSFORM',
config: {
field: 'priority',
transform: 'MAP_VALUES',
mapping: {
HIGH: 'URGENT',
MEDIUM: 'NORMAL',
LOW: 'LOW',
},
},
},
];
```
##### **[ ] T2.5.6 Security & Access Control for JSON Data**
- [ ] **Field-level Security** 基于 user roles
- [ ] **Data Encryption** สำหรับ sensitive fields
- [ ] **Audit Logging** สำหรับ JSON data changes
- [ ] **Input Sanitization** สำหรับป้องกัน XSS และ injection
```typescript
@Injectable()
export class JsonSecurityService {
async applyFieldLevelSecurity(
data: any,
schema: any,
userContext: UserContext
): Promise<any> {
const securedData = { ...data };
const securityRules = await this.getSecurityRules(schema.name);
for (const [fieldPath, fieldConfig] of Object.entries(schema.properties)) {
const fieldRules = securityRules[fieldPath];
if (fieldRules && !this.hasFieldAccess(fieldRules, userContext)) {
// Remove or mask field based on security rules
if (fieldRules.on_deny === 'REMOVE') {
this.deleteField(securedData, fieldPath);
} else if (fieldRules.on_deny === 'MASK') {
this.maskField(securedData, fieldPath, fieldRules.mask_pattern);
}
}
}
return securedData;
}
async encryptSensitiveFields(data: any, schema: any): Promise<any> {
const encryptedData = { ...data };
const sensitiveFields = this.getSensitiveFields(schema);
for (const fieldPath of sensitiveFields) {
const fieldValue = this.getFieldValue(data, fieldPath);
if (fieldValue) {
const encrypted = await this.cryptoService.encrypt(
fieldValue,
'field-level'
);
this.setFieldValue(encryptedData, fieldPath, encrypted);
}
}
return encryptedData;
}
private getSensitiveFields(schema: any): string[] {
const sensitiveFields: string[] = [];
const traverseSchema = (obj: any, path: string = '') => {
if (obj.properties) {
for (const [key, value] of Object.entries(obj.properties)) {
const currentPath = path ? `${path}.${key}` : key;
if (value.sensitive) {
sensitiveFields.push(currentPath);
}
if (value.properties || value.items) {
traverseSchema(value, currentPath);
}
}
}
};
traverseSchema(schema);
return sensitiveFields;
}
}
```
##### **[ ] T2.5.7 API Design & Integration**
- [ ] **Schema Management API** สำหรับ CRUD operations
- [ ] **Validation API** สำหรับ validate data against schemas
- [ ] **Migration API** สำหรับ manage data migrations
- [ ] **Integration Hooks** สำหรับ other modules
```typescript
@Controller('json-schema')
export class JsonSchemaController {
@Post('validate/:schemaName')
@RequirePermission('schema.validate')
async validateData(
@Param('schemaName') schemaName: string,
@Body() dto: ValidateDataDto
): Promise<ValidationResult> {
return this.jsonSchemaService.validateData(
schemaName,
dto.data,
dto.options
);
}
@Post('schemas')
@RequirePermission('schema.manage')
async createSchema(@Body() dto: CreateSchemaDto): Promise<JsonSchema> {
return this.jsonSchemaService.createSchema(dto);
}
@Post('migrate/:entityType/:entityId')
@RequirePermission('data.migrate')
async migrateData(
@Param('entityType') entityType: string,
@Param('entityId') entityId: string,
@Body() dto: MigrateDataDto
): Promise<MigrationResult> {
return this.migrationService.migrateData(
entityType,
entityId,
dto.targetVersion
);
}
@Get('ui-schema/:schemaName')
@RequirePermission('schema.view')
async getUiSchema(
@Param('schemaName') schemaName: string
): Promise<UiSchema> {
return this.schemaService.getUiSchema(schemaName);
}
}
```
### **[ ] T2.5.8 Integration with Document Modules**
- [ ] **Correspondence Module Integration**
- [ ] **RFA Module Integration**
- [ ] **Circulation Module Integration**
- [ ] **Drawing Module Integration**
```typescript
// Example: Correspondence Service Integration
@Injectable()
export class CorrespondenceService {
constructor(
private jsonSchemaService: JsonSchemaService,
private detailsService: DetailsService
) {}
async createCorrespondence(
dto: CreateCorrespondenceDto
): Promise<Correspondence> {
// 1. Validate details against schema
const validationResult = await this.jsonSchemaService.validateData(
`CORRESPONDENCE_${dto.type}`,
dto.details
);
if (!validationResult.isValid) {
throw new ValidationError('INVALID_DETAILS', validationResult.errors);
}
// 2. Apply security and sanitization
const secureDetails = await this.detailsService.sanitizeDetails(
validationResult.sanitizedData,
dto.type
);
// 3. Create correspondence entity
const correspondence = this.correspondenceRepository.create({
...dto,
details: secureDetails,
schema_version: await this.getCurrentSchemaVersion(
`CORRESPONDENCE_${dto.type}`
),
});
// 4. Setup virtual columns for performance
await this.setupVirtualColumns(correspondence);
return this.correspondenceRepository.save(correspondence);
}
async searchCorrespondences(
filters: SearchFilters
): Promise<Correspondence[]> {
// Use virtual columns for efficient filtering
const query = this.correspondenceRepository.createQueryBuilder('c');
if (filters.projectId) {
query.andWhere('c.ref_project_id = :projectId', {
projectId: filters.projectId,
});
}
if (filters.priority) {
query.andWhere('c.ref_priority = :priority', {
priority: filters.priority,
});
}
return query.getMany();
}
}
```
##### **[ ] T2.5.9 Testing Strategy**
- [ ] **Unit Tests** สำหรับ schema validation และ transformation
- [ ] **Integration Tests** สำหรับ end-to-end data flow
- [ ] **Performance Tests** สำหรับ virtual columns และ large datasets
- [ ] **Security Tests** สำหรับ field-level security
```typescript
describe('JsonSchemaService', () => {
describe('validateData', () => {
it('should validate correct RFA_DWG data successfully', async () => {
const testData = {
title: 'Structural Beam Details',
discipline: 'STRUCTURAL',
drawingReferences: ['CD-STR-001', 'CD-STR-002'],
approvalType: 'FULL_APPROVAL',
materials: ['STEEL_A36', 'CONCRETE_40MPA'],
};
const result = await jsonSchemaService.validateData('RFA_DWG', testData);
expect(result.isValid).toBe(true);
expect(result.errors).toHaveLength(0);
});
it('should reject invalid discipline value', async () => {
const testData = {
title: 'Test Drawing',
discipline: 'INVALID_DISCIPLINE', // Not in enum
approvalType: 'FULL_APPROVAL',
};
const result = await jsonSchemaService.validateData('RFA_DWG', testData);
expect(result.isValid).toBe(false);
expect(result.errors[0].message).toContain('discipline');
});
});
});
describe('VirtualColumnService', () => {
it('should improve search performance with virtual columns', async () => {
// Create test data with JSON details
await createTestCorrespondences(1000);
// Search without virtual column (JSON_EXTRACT)
const startTime1 = Date.now();
const result1 = await correspondenceRepository
.createQueryBuilder('c')
.where("JSON_EXTRACT(c.details, '$.projectId') = :projectId", {
projectId: 123,
})
.getMany();
const time1 = Date.now() - startTime1;
// Search with virtual column
const startTime2 = Date.now();
const result2 = await correspondenceRepository
.createQueryBuilder('c')
.where('c.ref_project_id = :projectId', { projectId: 123 })
.getMany();
const time2 = Date.now() - startTime2;
expect(time2).toBeLessThan(time1 * 0.5); // At least 2x faster
expect(result1.length).toEqual(result2.length);
});
});
```
#### 🔗 **Critical Dependencies**
- **T1.1** (Common Module) - สำหรับ base entities และ shared services
- **T1.4** (RBAC Guard) - สำหรับ field-level security
- **T0.3** (Database) - สำหรับ virtual columns implementation
- **T3.2** (Correspondence) - สำหรับ integration testing
#### 🎯 **Success Metrics**
- ✅ JSON validation performance: < 10ms ต่อ request
- ✅ Virtual columns improve search performance 5x+
- ✅ Support schema evolution without data loss
- ✅ Field-level security enforced across all modules
- ✅ 100% test coverage สำหรับ core validation logic
* **[ ] T2.6 MasterModule - Advanced Data (Req 6B)** (New)
- [ ] Update Entities: `Discipline`, `CorrespondenceSubType`
- [ ] Create Services/Controllers for CRUD Operations (Admin Panel Support)
- [ ] Implement Seeding Logic for initial 6B data
- [ ] **Deliverable:** API for managing Disciplines and Sub-types
- [ ] **Dependencies:** T1.1, T0.3
---
## **Phase 3: Unified Workflow Engine (สัปดาห์ที่ 5-6)**
**Milestone:** ระบบ Workflow กลางที่รองรับทั้ง Routing ปกติ และ RFA
### **Phase 3: Tasks**
- **[ ] T3.1 WorkflowEngineModule (New)**
- [ ] ออกแบบ Generic Schema สำหรับ Workflow State Machine
- [ ] Implement Service: `initializeWorkflow()`, `processAction()`, `getNextStep()`
- [ ] รองรับ Logic การ "ข้ามขั้นตอน" และ "ส่งกลับ" ภายใน Engine เดียว
- [ ] **Security:** Implement audit logging สำหรับ workflow actions
- [ ] **Deliverable:** Unified Workflow Engine พร้อมใช้งาน
- [ ] **Dependencies:** T1.1
- **[ ] T3.1.1 Workflow DSL Specification & Grammar**
- [ ] Define EBNF Grammar สำหรับ Workflow DSL
- [ ] Create YAML Schema สำหรับ human-friendly workflow definitions
- [ ] Design JSON Schema สำหรับ compiled workflow representations
```yaml
# ตัวอย่าง DSL Structure
workflow: RFA_APPROVAL
version: 1.0
description: "RFA Approval Workflow with Parallel Reviews"
states:
- name: DRAFT
initial: true
metadata:
color: "gray"
icon: "draft"
on:
SUBMIT:
to: TECHNICAL_REVIEW
conditions: - expression: "user.hasRole('ENGINEER')"
requirements: - role: "ENGINEER"
events: - type: "notify"
target: "reviewers"
template: "NEW_RFA_SUBMITTED" - type: "assign"
target: "technical_lead"
- name: TECHNICAL_REVIEW
metadata:
color: "blue"
icon: "review"
on:
APPROVE:
to: MANAGERIAL_REVIEW
conditions: - expression: "user.department === context.document.department"
REQUEST_CHANGES:
to: DRAFT
events: - type: "notify"
target: "creator"
template: "CHANGES_REQUESTED"
ESCALATE:
to: ESCALATED_REVIEW
conditions: - expression: "document.priority === 'HIGH'"
- name: PARALLEL_APPROVAL
parallel: true
branches:
- MANAGERIAL_REVIEW
- FINANCIAL_REVIEW
on:
ALL_APPROVED:
to: APPROVED
ANY_REJECTED:
to: REJECTED
- name: APPROVED
terminal: true
metadata:
color: "green"
icon: "approved"
Versioning Strategy สำหรับ workflow definitions
```
- **[ ] T3.1.2 Workflow Core Entities & Database Schema**
- [ ] WorkflowDefinition Entity
- [ ] WorkflowInstance Entity
- [ ] WorkflowHistory Entity
- [ ] WorkflowTransition Entity
```typescript
// Core Entities Design
@Entity()
export class WorkflowDefinition {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string;
@Column()
version: number;
@Column('json')
dsl_raw: any; // YAML/JSON DSL
@Column('json')
compiled_schema: any; // Normalized JSON
@Column({ default: true })
is_active: boolean;
@CreateDateColumn()
created_at: Date;
@VersionColumn()
version: number;
}
@Entity()
export class WorkflowInstance {
@PrimaryGeneratedColumn('uuid')
id: string;
@ManyToOne(() => WorkflowDefinition)
definition: WorkflowDefinition;
@Column()
entity_type: string; // 'correspondence', 'rfa', 'circulation'
@Column()
entity_id: string;
@Column()
current_state: string;
@Column('json')
context: any; // Workflow-specific data
@Column('json')
history: any[]; // State transition history
@CreateDateColumn()
created_at: Date;
@UpdateDateColumn()
updated_at: Date;
}
```
- **[ ] T3.1.3 DSL Parser & Compiler Service**
- [ ] YAML Parser สำหรับอ่าน DSL definitions
- [ ] Syntax Validator สำหรับ compile-time validation
- [ ] Schema Compiler สำหรับแปลง DSL → Normalized JSON
- [ ] Version Migration สำหรับอัพเกรด workflow definitions
```typescript
@Injectable()
export class WorkflowDslService {
async parseAndValidate(dslContent: string): Promise<CompiledWorkflow> {
// 1. Parse YAML
const rawDefinition = yaml.parse(dslContent);
// 2. Validate Syntax
await this.validateSyntax(rawDefinition);
// 3. Compile to Normalized JSON
const compiled = await this.compileDefinition(rawDefinition);
// 4. Validate Business Rules
await this.validateBusinessRules(compiled);
return compiled;
}
private async validateSyntax(definition: any): Promise<void> {
const rules = [
// ต้องมีอย่างน้อย 1 initial state
() => definition.states.some((s) => s.initial),
// Terminal states ต้องไม่มี transitions
() => !definition.states.filter((s) => s.terminal).some((s) => s.on),
// State names must be unique
() =>
new Set(definition.states.map((s) => s.name)).size ===
definition.states.length,
// Transition targets must exist
() => this.validateTransitionTargets(definition),
];
for (const rule of rules) {
if (!rule()) {
throw new WorkflowValidationError('DSL_VALIDATION_FAILED');
}
}
}
}
```
- **[ ] T3.1.4 Workflow Runtime Engine**
- [ ] State Machine Engine สำหรับจัดการ state transitions
- [ ] Condition Evaluator สำหรับประเมิน conditional transitions
- [ ] Permission Checker สำหรับตรวจสอบสิทธิ์ในการเปลี่ยนสถานะ
- [ ] Event Dispatcher สำหรับจัดการ workflow events
```typescript
****@Injectable()
export class WorkflowEngineService {
async processTransition(
instanceId: string,
action: string,
context: WorkflowContext
): Promise<TransitionResult> {
// 1. Load Workflow Instance
const instance = await this.findInstance(instanceId);
const definition = await this.getCompiledDefinition(instance.definition_id);
// 2. Validate Current State & Action
const currentState = definition.states[instance.current_state];
const transition = currentState.transitions[action];
if (!transition) {
throw new WorkflowError('INVALID_TRANSITION');
}
// 3. Check Permissions & Conditions
await this.validatePermissions(transition, context);
await this.validateConditions(transition, context);
// 4. Execute Pre-Transition Hooks
await this.executeHooks('pre_transition', instance, transition, context);
// 5. Perform State Transition
const previousState = instance.current_state;
instance.current_state = transition.to;
// 6. Record History
await this.recordTransitionHistory(instance, {
from: previousState,
to: transition.to,
action,
user: context.userId,
timestamp: new Date(),
metadata: context.metadata
});
// 7. Execute Post-Transition Events
await this.executeEvents(transition.events, instance, context);
// 8. Execute Post-Transition Hooks
await this.executeHooks('post_transition', instance, transition, context);
// 9. Save Instance
await this.saveInstance(instance);
return {
success: true,
previousState,
newState: transition.to,
instanceId: instance.id
};
}
async getAvailableActions(
instanceId: string,
context: WorkflowContext
): Promise<string[]> {
const instance = await this.findInstance(instanceId);
const definition = await this.getCompiledDefinition(instance.definition_id);
const currentState = definition.states[instance.current_state];
return Object.keys(currentState.transitions).filter(action => {
const transition = currentState.transitions[action];
return this.isActionAvailable(transition, context);
});
}
}
```
- **[ ] T3.1.5 Advanced Feature Implementation**
- [ ] Parallel Approval Flows สำหรับ multi-department approvals
- [ ] Conditional Transitions ด้วย expression evaluation
- [ ] Timeout & Escalation สำหรับขั้นตอนที่เกินกำหนด
- [ ] Rollback & Compensation สำหรับย้อนกลับสถานะ
```typescript
// Parallel Workflow Support
interface ParallelState {
parallel: true;
branches: string[];
completion_policy: 'ALL' | 'ANY' | 'MAJORITY';
on: {
[completionType: string]: {
to: string;
conditions?: Condition[];
};
};
}
// Conditional Transition Support
interface ConditionalTransition {
to: string;
conditions: Array<{
expression: string; // "user.role === 'MANAGER' && document.value > 10000"
evaluator?: 'javascript' | 'jsonlogic';
}>;
requirements?: PermissionRequirement[];
}
// Timeout & Escalation
interface StateWithTimeout {
timeout_seconds: number;
on_timeout: {
action: string;
escalate_to?: string;
notify?: string[];
};
}
```
- **[ ] T3.1.6 Event System & Integration**
- [ ] Event Types (notify, assign, webhook, auto_action)
- [ ] Template Engine สำหรับ dynamic messages
- [ ] Webhook Support สำหรับ external integrations
- [ ] Notification Service Integration
```typescript
@Injectable()
export class WorkflowEventService {
async executeEvents(
events: WorkflowEvent[],
instance: WorkflowInstance,
context: WorkflowContext
): Promise<void> {
for (const event of events) {
switch (event.type) {
case 'notify':
await this.handleNotifyEvent(event, instance, context);
break;
case 'assign':
await this.handleAssignEvent(event, instance, context);
break;
case 'webhook':
await this.handleWebhookEvent(event, instance, context);
break;
case 'auto_action':
await this.handleAutoActionEvent(event, instance, context);
break;
}
}
}
private async handleNotifyEvent(
event: NotifyEvent,
instance: WorkflowInstance,
context: WorkflowContext
): Promise<void> {
const recipients = await this.resolveRecipients(
event.target,
instance,
context
);
const message = await this.renderTemplate(
event.template,
instance,
context
);
await this.notificationService.send({
type: 'workflow',
recipients,
subject: message.subject,
body: message.body,
metadata: {
workflow_instance_id: instance.id,
state: instance.current_state,
action: context.action,
},
});
}
}
```
- **[ ] T3.1.7 API Design & Controllers**
- [ ] REST API Endpoints สำหรับ workflow management
- [ ] WebSocket Support สำหรับ real-time updates
- [ ] Admin API สำหรับ workflow definition management
- [ ] Integration Hooks สำหรับ external systems
```typescript
@Controller('workflow')
export class WorkflowEngineController {
@Post('instances/:id/transition')
@RequirePermission('workflow.execute')
async processTransition(
@Param('id') instanceId: string,
@Body() dto: WorkflowTransitionDto
): Promise<TransitionResult> {
return this.workflowEngine.processTransition(
instanceId,
dto.action,
dto.context
);
}
@Get('instances/:id/actions')
@RequirePermission('workflow.view')
async getAvailableActions(
@Param('id') instanceId: string,
@Query() context: WorkflowContext
): Promise<string[]> {
return this.workflowEngine.getAvailableActions(instanceId, context);
}
@Post('definitions')
@RequirePermission('workflow.manage')
async createWorkflowDefinition(
@Body() dto: CreateWorkflowDefinitionDto
): Promise<WorkflowDefinition> {
return this.workflowDslService.compileAndSave(dto.dslContent);
}
@Get('instances/:id/history')
@RequirePermission('workflow.view')
async getWorkflowHistory(
@Param('id') instanceId: string
): Promise<WorkflowHistory[]> {
return this.workflowHistoryService.getHistory(instanceId);
}
}
```
- **[ ] T3.1.8 Integration with Existing Modules**
- [ ] Correspondence Module Integration
- [ ] RFA Module Integration
- [ ] Circulation Module Integration
- [ ] Notification Module Integration
```typescript
// Correspondence Integration Example
@Injectable()
export class CorrespondenceWorkflowService {
constructor(
private workflowEngine: WorkflowEngineService,
private correspondenceService: CorrespondenceService
) {}
async submitCorrespondence(
correspondenceId: string,
userId: string
): Promise<void> {
const correspondence = await this.correspondenceService.findById(
correspondenceId
);
// Create workflow instance
const instance = await this.workflowEngine.createInstance({
definition: 'CORRESPONDENCE_ROUTING',
entity_type: 'correspondence',
entity_id: correspondenceId,
context: {
document: correspondence,
user: userId,
},
});
// Process initial transition
await this.workflowEngine.processTransition(instance.id, 'SUBMIT', {
userId,
metadata: { correspondenceId },
});
}
}
```
- **[ ] T3.1.9 Testing Strategy**
- [ ] Unit Tests สำหรับ DSL parser และ state machine
- [ ] Integration Tests สำหรับ end-to-end workflow execution
- [ ] Performance Tests สำหรับ high-concurrency scenarios
- [ ] Security Tests สำหรับ permission validation.
```typescript
describe('WorkflowEngineService', () => {
describe('processTransition', () => {
it('should successfully transition state with valid permissions', async () => {
// Arrange
const instance = await createTestInstance();
const context = { userId: 'user1', roles: ['APPROVER'] };
// Act
const result = await workflowEngine.processTransition(
instance.id,
'APPROVE',
context
);
// Assert
expect(result.success).toBe(true);
expect(result.newState).toBe('APPROVED');
});
it('should reject transition without required permissions', async () => {
// Arrange
const instance = await createTestInstance();
const context = { userId: 'user2', roles: ['VIEWER'] };
// Act & Assert
await expect(
workflowEngine.processTransition(instance.id, 'APPROVE', context)
).rejects.toThrow(WorkflowError);
});
});
});
```
- **🔗 Critical Dependencies of T3.1.1-T3.1.8**
- T1.1 (Common Module) - สำหรับ base entities และ shared services
- T1.4 (RBAC Guard) - สำหรับ permission checking
- T2.5 (JSON Schema) - สำหรับ DSL validation
- T6.2 (Notification) - สำหรับ event handling
- **🎯 Success Metrics**
- ✅ Support ทั้ง Correspondence Routing และ RFA Workflow
- ✅ DSL ที่ human-readable และ editable โดยไม่ต้องแก้โค้ด
- ✅ Performance: < 50ms ต่อ state transition
- ✅ 100% test coverage สำหรับ core workflow logic
- ✅ Complete audit trail สำหรับทุก workflow instance
- **[ ] T3.2 CorrespondenceModule - Basic CRUD**
- [ ] สร้าง Entities (Correspondence, Revision, Recipient, Tag, Reference, Attachment)
- [ ] สร้าง CorrespondenceService (Create with Document Numbering, Update with new Revision, Soft Delete)
- [ ] สร้าง Controllers (POST/GET/PUT/DELETE /correspondences)
- [ ] Implement Impersonation Logic: ตรวจสอบ originatorId ใน DTO หากมีการส่งมา ต้องเช็คว่า User ปัจจุบันมีสิทธิ์กระทำการแทนหรือไม่ (Superadmin)
- [ ] **Security:** Implement permission checks สำหรับ document access
- [ ] **Deliverable:** สร้าง/แก้ไข/ดูเอกสารได้
- [ ] **Dependencies:** T1.1, T1.2, T1.3, T1.4, T1.5, T2.3, T2.2, T2.5
- **[ ] T3.3 CorrespondenceModule - Advanced Features**
- [ ] Implement Status Transitions (DRAFT → SUBMITTED)
- [ ] Implement References (Link Documents)
- [ ] Implement Search (Basic)
- [ ] **Security:** Implement state transition validation
- [ ] **Deliverable:** Workflow พื้นฐานทำงานได้
- [ ] **Dependencies:** T3.2
- **[ ] T3.4 Correspondence Integration with Workflow**
- [ ] เชื่อมต่อ `CorrespondenceService` เข้ากับ `WorkflowEngineModule`
- [ ] ย้าย Logic การ Routing เดิมมาใช้ Engine ใหม่
- [ ] สร้าง API endpoints สำหรับ Frontend (Templates, Pending Tasks, Bulk Action)
- [ ] **Security:** Implement permission checks สำหรับ workflow operations
- [ ] **Deliverable:** ระบบส่งต่อเอกสารทำงานได้สมบูรณ์ด้วย Unified Engine
- [ ] **Dependencies:** T3.1, T3.2
---
## **Phase 4: Drawing & Advanced Workflows (สัปดาห์ที่ 7-8)**
**Milestone:** การจัดการแบบและ RFA โดยใช้ Unified Engine
### **Phase 4: Tasks**
- **[ ] T4.1 DrawingModule - Contract Drawings**
- [ ] สร้าง Entities (ContractDrawing, Volume, Category, SubCategory, Attachment)
- [ ] สร้าง ContractDrawingService CRUD
- [ ] สร้าง Controllers (GET/POST /drawings/contract)
- [ ] **Security:** Implement access control สำหรับ contract drawings
- [ ] **Deliverable:** จัดการ Contract Drawings ได้
- [ ] **Dependencies:** T1.1, T1.2, T1.4, T1.5, T2.2
- **[ ] T4.2 DrawingModule - Shop Drawings**
- [ ] สร้าง Entities (ShopDrawing, Revision, Main/SubCategory, ContractRef, RevisionAttachment)
- [ ] สร้าง ShopDrawingService CRUD (รวมการสร้าง Revision)
- [ ] สร้าง Controllers (GET/POST /drawings/shop, /drawings/shop/:id/revisions)
- [ ] Link Shop Drawing Revision → Contract Drawings
- [ ] **Security:** Implement virus scanning สำหรับ drawing files
- [ ] **Deliverable:** จัดการ Shop Drawings และ Revisions ได้
- [ ] **Dependencies:** T4.1
- **[ ] T5.1 RfaModule with Unified Workflow**
- [ ] สร้าง Entities (Rfa, RfaRevision, RfaItem, RfaWorkflowTemplate/Step)
- [ ] สร้าง RfaService (Create RFA, Link Shop Drawings)
- [ ] Implement RFA Workflow โดยใช้ Configuration ของ `WorkflowEngineModule`
- [ ] สร้าง Controllers (POST/GET /rfas, POST /rfas/:id/workflow/...)
- [ ] **Resilience:** Implement circuit breaker สำหรับ notification services
- [ ] **Deliverable:** RFA Workflow ทำงานได้ด้วย Unified Engine
- [ ] **Dependencies:** T3.2, T4.2, T2.5, T6.2
---
## **Phase 5: Workflow Systems & Resilience (สัปดาห์ที่ 8-9)**
**Milestone:** ระบบ Workflow ทั้งหมดพร้อม Resilience Patterns
### **Phase 5: Tasks**
- **[ ] T5.2 CirculationModule - Internal Routing**
- [ ] สร้าง Entities (Circulation, Template, Routing, Attachment)
- [ ] สร้าง CirculationService (Create 1:1 with Correspondence, Assign User, Complete/Close Step)
- [ ] สร้าง Controllers (POST/GET /circulations, POST /circulations/:id/steps/...)
- [ ] **Resilience:** Implement retry mechanism สำหรับ assignment notifications
- [ ] **Deliverable:** ใบเวียนภายในองค์กรทำงานได้
- [ ] **Dependencies:** T3.2, T2.5, T6.2
- **[ ] T5.3 TransmittalModule - Document Forwarding**
- [ ] สร้าง Entities (Transmittal, TransmittalItem)
- [ ] สร้าง TransmittalService (Create Correspondence + Transmittal, Link Multiple Correspondences)
- [ ] สร้าง Controllers (POST/GET /transmittals)
- [ ] **Security:** Implement access control สำหรับ transmittal items
- [ ] **Deliverable:** สร้าง Transmittal ได้
- [ ] **Dependencies:** T3.2
---
## **Phase 6: Notification & Resilience (สัปดาห์ที่ 9)**
**Milestone:** ระบบแจ้งเตือนแบบ Digest และการจัดการข้อมูลขนาดใหญ่
### **Phase 6: Tasks**
- **[ ] T6.1 SearchModule - Elasticsearch Integration**
- [ ] Setup Elasticsearch Container
- [ ] สร้าง SearchService (index/update/delete documents, search)
- [ ] Index ทุก Document Type
- [ ] สร้าง Controllers (GET /search)
- [ ] **Resilience:** Implement circuit breaker สำหรับ Elasticsearch
- [ ] **Deliverable:** ค้นหาขั้นสูงทำงานได้
- [ ] **Dependencies:** T3.2, T5.1, T4.2, T5.2, T5.3
- **[ ] T6.2 Notification Queue & Digest**
- [ ] สร้าง NotificationService (sendEmail/Line/System)
- [ ] **Producer:** Push Event ลง BullMQ Queue
- [ ] **Consumer:** จัดกลุ่ม Notification (Digest Message) และส่งผ่าน Email/Line
- [ ] Integrate กับ Workflow Events (แจ้ง Recipients, Assignees, Deadline)
- [ ] สร้าง Controllers (GET /notifications, PUT /notifications/:id/read)
- [ ] **Resilience:** Implement retry mechanism ด้วย exponential backoff
- [ ] **Deliverable:** ระบบแจ้งเตือนทำงานได้แบบ Digest
- [ ] **Dependencies:** T1.1, T6.4
- **[ ] T6.3 MonitoringModule - Observability**
- [ ] สร้าง Health Check Controller (GET /health)
- [ ] สร้าง Metrics Service (API response times, Error rates)
- [ ] สร้าง Performance Interceptor (Track request duration)
- [ ] สร้าง Logging Service (Structured logging)
- [ ] **Deliverable:** Monitoring system ทำงานได้
- [ ] **Dependencies:** T1.1
- **[ ] T6.4 ResilienceModule - Circuit Breaker & Retry**
- [ ] สร้าง Circuit Breaker Service (@CircuitBreaker() decorator)
- [ ] สร้าง Retry Service (@Retry() decorator)
- [ ] สร้าง Fallback Strategies
- [ ] Implement สำหรับ Email, LINE, Elasticsearch, Virus Scanning
- [ ] **Deliverable:** Resilience patterns ทำงานได้
- [ ] **Dependencies:** T1.1
- **[ ] T6.5 Data Partitioning Strategy**
- [ ] ออกแบบ Table Partitioning สำหรับ `audit_logs` และ `notifications` (แบ่งตาม Range: Year)
- [ ] เขียน Raw SQL Migration สำหรับสร้าง Partition Table
- [ ] **Deliverable:** Database Performance และ Scalability ดีขึ้น
- [ ] **Dependencies:** T0.3
---
## **Phase 7: Testing & Hardening (สัปดาห์ที่ 10-12)**
**Milestone:** ทดสอบความทนทานต่อ Race Condition, Security, และ Performance
### **Phase 7: Tasks**
- **[ ] T7.1 Concurrency Testing**
- [ ] เขียน Test Scenarios ยิง Request ขอเลขที่เอกสารพร้อมกัน 100 Request (ต้องไม่ซ้ำและไม่ข้าม)
- [ ] ทดสอบ Optimistic Lock ทำงานถูกต้องเมื่อ Redis ถูกปิด
- [ ] ทดสอบ File Upload พร้อมกันหลายไฟล์
- [ ] **Deliverable:** ระบบทนทานต่อ Concurrency Issues
- **[ ] T7.2 Transaction Integrity Testing**
- [ ] ทดสอบ Upload ไฟล์แล้ว Kill Process ก่อน Commit
- [ ] ทดสอบ Two-Phase File Storage ทำงานถูกต้อง
- [ ] ทดสอบ Database Transaction Rollback Scenarios
- [ ] **Deliverable:** Data Integrity รับประกันได้
- **[ ] T7.3 Security & Idempotency Test**
- [ ] ทดสอบ Replay Attack โดยใช้ `Idempotency-Key` ซ้ำ
- [ ] ทดสอบ Maintenance Mode Block API ได้จริง
- [ ] ทดสอบ RBAC 4-Level ทำงานถูกต้อง 100%
- [ ] **Deliverable:** Security และ Idempotency ทำงานได้ตาม 设计要求
- **[ ] T7.4 Unit Testing (80% Coverage)**
- **[ ] T7.5 Integration Testing**
- **[ ] T7.6 E2E Testing**
- **[ ] T7.7 Performance Testing**
- [ ] Load Testing: 100 concurrent users
- [ ] **(สำคัญ)** การจูนและทดสอบ Load Test จะต้องทำในสภาพแวดล้อมที่จำลอง Spec ของ QNAP Server (TS-473A, AMD Ryzen V1500B) เพื่อให้ได้ค่า Response Time และ Connection Pool ที่เที่ยงตรง
- [ ] Stress Testing
- [ ] Endurance Testing
- [ ] **Deliverable:** Performance targets บรรลุ
- **[ ] T7.8 Security Testing**
- [ ] Penetration Testing (OWASP Top 10)
- [ ] Security Audit (Code review, Dependency scanning)
- [ ] File Upload Security Testing
- [ ] **Deliverable:** Security tests ผ่าน
- **[ ] T7.9 Performance Optimization**
- [ ] Implement Caching (Master Data, User Permissions, Search Results)
- [ ] Database Optimization (Review Indexes, Query Optimization, Pagination)
- [ ] **Deliverable:** Response Time < 200ms (90th percentile)
---
## **Phase 8: Documentation & Deployment (สัปดาห์ที่ 14)**
**Milestone:** เอกสารและ Deploy สู่ Production พร้อม Security Hardening
### **Phase 8: Tasks**
- **[ ] T8.1 API Documentation (Swagger)**
- **[ ] T8.2 Technical Documentation**
- **[ ] T8.3 Security Hardening**
- **[ ] T8.4 Deployment Preparation (QNAP Setup, Nginx Proxy Manager)**
- **[ ] T8.5 Production Deployment**
- **[ ] T8.6 Handover to Frontend Team**
---
## 📊 **สรุป Timeline**
| Phase | ระยะเวลา | จำนวนงาน | Output หลัก |
| :------ | :------------- | :----------- | :--------------------------------------------- |
| Phase 0 | 1 สัปดาห์ | 4 | Infrastructure Ready + Security Base |
| Phase 1 | 2 สัปดาห์ | 5 | Auth & User Management + RBAC + Idempotency |
| Phase 2 | 1 สัปดาห์ | 5 | High-Integrity Data & File Management |
| Phase 3 | 2 สัปดาห์ | 4 | Unified Workflow Engine + Correspondence |
| Phase 4 | 2 สัปดาห์ | 3 | Drawing Management + RFA with Unified Workflow |
| Phase 5 | 2 สัปดาห์ | 2 | Workflow Systems + Resilience |
| Phase 6 | 1 สัปดาห์ | 5 | Notification & Resilience + Data Partitioning |
| Phase 7 | 3 สัปดาห์ | 9 | Testing & Hardening |
| Phase 8 | 1 สัปดาห์ | 6 | Documentation & Deploy |
| **รวม** | **15 สัปดาห์** | **39 Tasks** | **Production-Ready Backend v1.4.2** |
## **Document Control:**
- **Document:** Backend Development Plan v1.4.4
- **Version:** 1.4
- **Date:** 2025-11-28
- **Author:** NAP LCBP3-DMS & Gemini
- **Status:** FINAL-Rev.04
- **Classification:** Internal Technical Documentation
- **Approved By:** Nattanin
---
`End of Backend Development Plan v1.4.4`
```
```
```
```