251129:1700 update to 1.4.5

This commit is contained in:
admin
2025-11-29 16:50:34 +07:00
parent f7a43600a3
commit a78c9941be
55 changed files with 14641 additions and 2090 deletions

View File

@@ -0,0 +1,20 @@
---
trigger: always_on
---
---
description: Control which shell commands the agent may run automatically.
allowAuto: ["pnpm test:watch", "pnpm test:debug", "pnpm test:e2e", "git status"]
denyAuto: ["rm -rf", "Remove-Item", "git push --force", "curl | bash"]
alwaysReview: true
scopes: ["backend/src/**", "backend/test/**", "frontend/app/**"]
---
# Execution Rules
- Only auto-execute commands that are explicitly listed in `allowAuto`.
- Commands in denyAuto must always be blocked, even if manually requested.
- All shell operations that create, modify, or delete files in `backend/src/` or `backend/test/` or `frontend/app/`require human review.
- Alert if environment variables related to DB connection or secrets would be displayed or logged.

View File

@@ -0,0 +1,11 @@
---
trigger: always_on
---
# Basic data:
- For Application Requirements, refer to 0_Requirements_V1_4_4.md
- For detailed Full Stack JS, refer to 1_FullStackJS_V1_4_4.md
- For detailed Backend Development Plan, refer to 2_Backend_Plan_V1_4_4.md, 2_Backend_Plan_V1_4_4.Phase_Addition.md, 2_Backend_Plan_Phase6A_V1_4_3.md
- For detailed Frontend Development Plan, refer to 3_Frontend_Plan_V1_4_4.md
- For detailed DB schema, refer to 4_Data_Dictionary_V1_4_4.md, 8_lcbp3_v1_4_4.sql, 8_lcbp3_v1_4_4_seed.sql

View File

@@ -1,6 +1,11 @@
---
trigger: always_on
glob:
description:
---
# 📏 Coding Standards (Strict Rules)
1. **Language:** - Code logic & Naming: **English**.
- Comments & Documentation: **Thai Language (ภาษาไทย)**.
2. **File Structure:** Follow `kebab-case` for files (e.g., `user-service.ts`).
3. **Comments:** Add `// File: path/to/file` at the top of every file.
4. **Secrets:** NEVER hardcode secrets. Use `process.env` and assume `docker-compose.override.yml` is used for local dev.

803
0_Requirements_V1_4_5.md Normal file
View File

@@ -0,0 +1,803 @@
# 📝 **Documents Management System Version 1.4.5: Application Requirements Specification**
**สถานะ:** FINAL-Rev.05
**วันที่:** 2025-11-29
**อ้างอิงพื้นฐาน:** v1.4.4
**Classification:** Internal Technical Documentation
## 📌 **1. วัตถุประสงค์**
สร้างเว็บแอปพลิเคชันสำหรับระบบบริหารจัดการเอกสารโครงการ (Document Management System - DMS) แบบครบวงจร ที่เน้นความปลอดภัยสูงสุด ความถูกต้องของข้อมูล (Data Integrity) และรองรับการขยายตัวในอนาคต (Scalability) โดยแก้ไขปัญหา Race Condition และเพิ่มความเสถียรในการจัดการไฟล์ และใช้ Unified Workflow Engine ในการจัดการกระบวนการอนุมัติทั้งหมดเพื่อความยืดหยุ่น
- มีฟังก์ชันหลักในการอัปโหลด จัดเก็บ ค้นหา แชร์ และควบคุมสิทธิ์การเข้าถึงเอกสาร
- ช่วยลดการใช้เอกสารกระดาษ เพิ่มความปลอดภัยในการจัดเก็บข้อมูล
- เพิ่มความสะดวกในการทำงานร่วมกันระหว่างองกรณ์
- **เสริม:** ปรับปรุงความปลอดภัยของระบบด้วยมาตรการป้องกันที่ทันสมัย
- **เสริม:** เพิ่มความทนทานของระบบด้วยกลไก resilience patterns
- **เสริม:** สร้างระบบ monitoring และ observability ที่ครอบคลุม
## 🛠️ **2. สถาปัตยกรรมและเทคโนโลยี (System Architecture & Technology Stack)**
ใช้สถาปัตยกรรมแบบ Headless/API-First ที่ทันสมัย ทำงานทั้งหมดบน QNAP Server ผ่าน Container Station เพื่อความสะดวกในการจัดการและบำรุงรักษา
**Domain:** `np-dms.work`, `www.np-dms.work`
**IP:** 159.192.126.103
**Docker Network:** ทุก Service จะเชื่อมต่อผ่านเครือข่ายกลางชื่อ `lcbp3` เพื่อให้สามารถสื่อสารกันได้
### **2.1 Infrastructure & Environment:**
- **Server:** QNAP (Model: TS-473A, RAM: 32GB, CPU: AMD Ryzen V1500B)
- **Containerization:** Container Station (Docker & Docker Compose) ใช้ UI ของ Container Station เป็นหลัก ในการ configuration และการรัน docker command
- **Development Environment:** VS Code/Cursor on Windows 11
- **Data Storage:** `/share/dms-data` บน QNAP
- **ข้อจำกัด:** ไม่สามารถใช้ .env ในการกำหนดตัวแปรภายนอกได้ ต้องกำหนดใน docker-compose.yml เท่านั้น
### **2.2 การจัดการ Configuration (ปรับปรุง):**
- ใช้ `docker-compose.yml` สำหรับ environment variables ตามข้อจำกัดของ QNAP
- **Secrets Management (ใหม่):**
- ห้ามระบุ Sensitive Secrets (Password, Keys) ใน `docker-compose.yml` หลัก
- ต้องใช้ไฟล์ `docker-compose.override.yml` (ที่ถูก gitignore) สำหรับ Inject Environment Variables ที่เป็นความลับในแต่ละ Environment (Dev/Prod)
- ไฟล์ `docker-compose.yml` หลักให้ใส่ค่า Dummy หรือว่างไว้
- **แต่ต้องมี mechanism สำหรับจัดการ sensitive secrets อย่างปลอดภัย** โดยใช้:
- Docker secrets (ถ้ารองรับ)
- External secret management (Hashicorp Vault) หรือ
- Encrypted environment variables
- Development environment ยังใช้ .env ได้ แต่ต้องไม่ commit เข้า version control
- ต้องมี configuration validation during application startup
- ต้องแยก configuration ตาม environment (development, staging, production)
### **2.3 Core Services:**
- **Code Hosting:** Gitea (Self-hosted on QNAP)
- Application name: git
- Service name: gitea
- Domain: `git.np-dms.work`
- หน้าที่: เป็นศูนย์กลางในการเก็บและจัดการเวอร์ชันของโค้ด (Source Code) สำหรับทุกส่วน
- **Backend / Data Platform:** NestJS
- Application name: lcbp3-backend
- Service name: backend
- Domain: `backend.np-dms.work`
- Framework: NestJS (Node.js, TypeScript, ESM)
- หน้าที่: จัดการโครงสร้างข้อมูล (Data Models), สร้าง API, จัดการสิทธิ์ผู้ใช้ (Roles & Permissions), และสร้าง Workflow ทั้งหมดของระบบ
- **Database:** MariaDB 10.11
- Application name: lcbp3-db
- Service name: mariadb
- Domain: `db.np-dms.work`
- หน้าที่: ฐานข้อมูลหลักสำหรับเก็บข้อมูลทั้งหมด
- Tooling: DBeaver (Community Edition), phpmyadmin สำหรับการออกแบบและจัดการฐานข้อมูล
- **Database Management:** phpMyAdmin
- Application name: lcbp3-db
- Service: phpmyadmin:5-apache
- Service name: pma
- Domain: `pma.np-dms.work`
- หน้าที่: จัดการฐานข้อมูล mariadb ผ่าน Web UI
- **Frontend:** Next.js
- Application name: lcbp3-frontend
- Service name: frontend
- Domain: `lcbp3.np-dms.work`
- Framework: Next.js (App Router, React, TypeScript, ESM)
- Styling: Tailwind CSS + PostCSS
- Component Library: shadcn/ui
- หน้าที่: สร้างหน้าตาเว็บแอปพลิเคชันสำหรับให้ผู้ใช้งานเข้ามาดู Dashboard, จัดการเอกสาร, และติดตามงาน โดยจะสื่อสารกับ Backend ผ่าน API
- **Workflow Automation:** n8n
- Application name: lcbp3-n8n
- Service: n8nio/n8n:latest
- Service name: n8n
- Domain: `n8n.np-dms.work`
- หน้าที่: จัดการ workflow ระหว่าง Backend และ Line
- **Reverse Proxy:** Nginx Proxy Manager
- Application name: lcbp3-npm
- Service: Nginx Proxy Manager (nginx-proxy-manage: latest)
- Service name: npm
- Domain: `npm.np-dms.work`
- หน้าที่: เป็นด่านหน้าในการรับ-ส่งข้อมูล จัดการโดเมนทั้งหมด, ทำหน้าที่เป็น Proxy ชี้ไปยัง Service ที่ถูกต้อง, และจัดการ SSL Certificate (HTTPS) ให้อัตโนมัติ
- **Search Engine:** Elasticsearch
- **Cache:** Redis
### **2.4 Business Logic & Consistency (ปรับปรุง):**
- **2.4.1 Unified Workflow Engine (หลัก):** ระบบการเดินเอกสารทั้งหมด (Correspondence, RFA, Circulation) ต้อง ใช้ Engine กลางเดียวกัน โดยกำหนด Logic ผ่าน Workflow DSL (JSON Configuration) แทนการเขียน Hard-coded ลงในตาราง
- **2.4.2 Separation of Concerns:** Module ต่างๆ (RFA, Correspondence) จะเก็บเฉพาะข้อมูลของเอกสาร (Data) ส่วนสถานะและการเปลี่ยนสถานะ (State Transition) จะถูกจัดการโดย Workflow Engine
- **2.4.3 Idempotency & Locking:** ใช้กลไกเดิมในการป้องกันการทำรายการซ้ำ
- **2.4.4 Optimistic Locking (ใหม่):** ใช้ Version Column ใน Database ควบคู่กับ Redis Lock สำหรับการสร้างเลขที่เอกสาร เพื่อเป็น Safety Net ชั้นสุดท้าย
- **2.4.5** **จะไม่มีการใช้ SQL Triggers** เพื่อป้องกันตรรกะซ่อนเร้น (Hidden Logic) และความซับซ้อนในการดีบัก
### **2.5 Data Migration และ Schema Versioning:**
- ต้องมี database migration scripts สำหรับทุก schema change โดยใช้ TypeORM migrations
- ต้องรองรับ rollback ของ migration ได้
- ต้องมี data seeding strategy สำหรับ environment ต่างๆ (development, staging, production)
- ต้องมี version compatibility between schema versions
- Migration scripts ต้องผ่านการทดสอบใน staging environment ก่อน production
- ต้องมี database backup ก่อนทำ migration ใน production
### **2.6 กลยุทธ์ความทนทานและการจัดการข้อผิดพลาด (Resilience & Error Handling Strategy)**
- 2.6.1 Circuit Breaker Pattern: ใช้สำหรับ external service calls (Email, LINE, Elasticsearch)
- 2.6.2 Retry Mechanism: ด้วย exponential backoff สำหรับ transient failures
- 2.6.3 Fallback Strategies: Graceful degradation เมื่อบริการภายนอกล้มเหลว
- 2.6.4 Error Handling: Error messages ต้องไม่เปิดเผยข้อมูล sensitive
- 2.6.5 Monitoring: Centralized error monitoring และ alerting system
## **📦 3. ข้อกำหนดด้านฟังก์ชันการทำงาน (Functional Requirements)**
### **3.1. การจัดการโครงสร้างโครงการและองค์กร**
- 3.1.1. โครงการ (Projects): ระบบต้องสามารถจัดการเอกสารภายในหลายโครงการได้ (ปัจจุบันมี 4 โครงการ และจะเพิ่มขึ้นในอนาคต)
- 3.1.2. สัญญา (Contracts): ระบบต้องสามารถจัดการเอกสารภายในแต่ละสัญญาได้ ในแต่ละโครงการ มีได้หลายสัญญา หรืออย่างน้อย 1 สัญญา
- 3.1.3. องค์กร (Organizations):
- มีหลายองค์กรในโครงการ องค์กรณ์ที่เป็น Owner, Designer และ Consultant สามารถอยู่ในหลายโครงการและหลายสัญญาได้
- Contractor จะถือ 1 สัญญา และอยู่ใน 1 โครงการเท่านั้น
### **3.2. การจัดการเอกสารโต้ตอบ (Correspondence Management)**
- 3.2.1. วัตถุประสงค์: เอกสารโต้ตอบ (correspondences) ระหว่างองกรณื-องกรณ์ ภายใน โครงการ (Projects) และระหว่าง องค์กร-องค์กร ภายนอก โครงการ (Projects), รองรับ To (ผู้รับหลัก) และ CC (ผู้รับสำเนา) หลายองค์กร
- 3.2.2. ประเภทเอกสาร: ระบบต้องรองรับเอกสารรูปแบบ ไฟล์ PDF หลายประเภท (Types) เช่น จดหมาย (Letter), อีเมล์ (Email), Request for Information (RFI), และสามารถเพิ่มประเภทใหม่ได้ในภายหลัง
- 3.2.3. การสร้างเอกสาร (Correspondence):
- ผู้ใช้ที่มีสิทธิ์ (เช่น Document Control) สามารถสร้างเอกสารรอไว้ในสถานะ ฉบับร่าง" (Draft) ได้ ซึ่งผู้ใช้งานต่างองค์กรจะมองไม่เห็น
- เมื่อกด "Submitted" แล้ว การแก้ไข, ถอนเอกสารกลับไปสถานะ Draft, หรือยกเลิก (Cancel) จะต้องทำโดยผู้ใช้ระดับ Admin ขึ้นไป พร้อมระบุเหตุผล
- 3.2.4. การอ้างอิงและจัดกลุ่ม:
- เอกสารสามารถอ้างถึง (Reference) เอกสารฉบับก่อนหน้าได้หลายฉบับ
- สามารถกำหนด Tag ได้หลาย Tag เพื่อจัดกลุ่มและใช้ในการค้นหาขั้นสูง
- 3.2.5. Correspondence Routing & Workflow
- 3.2.5.1 Routing Templates (แม่แบบการส่งต่อ)
- ผู้ดูแลระบบต้องสามารถสร้างแม่แบบการส่งต่อได้
- แม่แบบสามารถเป็นแบบทั่วไป (ใช้ได้ทุกโครงการ) หรือเฉพาะโครงการ
- แต่ละแม่แบบประกอบด้วยลำดับขั้นตอนการส่งต่อ
- การส่งจาก Originator -> Organization 1 -> Organization 2 -> Organization 3 แล้วส่งผลกลับตามลำดับเดิม (โดยถ้า องกรณ์ใดใน Wouting ให้ส่งกลับ ก็สามารถส่งผลกลับตามลำดับเดิมโดยไม่ต้องรอให้ถึง องกรณืในลำดับถัดไป)
- 3.2.5.2 Routing Steps (ขั้นตอนการส่งต่อ) แต่ละขั้นตอนในแม่แบบต้องกำหนด:
- **ลำดับขั้นตอน** (Sequence)
- **องค์กรผู้รับ** (To Organization)
- **วัตถุประสงค์** (Purpose): เพื่ออนุมัติ (FOR_APPROVAL), เพื่อตรวจสอบ (FOR_REVIEW), เพื่อทราบ (FOR_INFORMATION), เพื่อดำเนินการ (FOR_ACTION)
- **ระยะเวลาที่คาดหวัง** (Expected Duration)
- 3.2.5.3 Actual Routing Execution (การส่งต่อจริง) เมื่อสร้างเอกสารและเลือกใช้แม่แบบ ระบบต้อง:
- สร้างลำดับการส่งต่อตามแม่แบบ
- ติดตามสถานะของแต่ละขั้นตอน: ส่งแล้ว (SENT), กำลังดำเนินการ (IN_PROGRESS), ดำเนินการแล้ว (ACTIONED), ส่งต่อแล้ว (FORWARDED), ตอบกลับแล้ว (REPLIED)
- ระบุวันครบกำหนด (Due Date) สำหรับแต่ละขั้นตอน
- บันทึกผู้ดำเนินการและเวลาที่ดำเนินการ
- 3.2.5.4 Routing Flexibility (ความยืดหยุ่น)
- สามารถข้ามขั้นตอนได้ในกรณีพิเศษ (โดยผู้มีสิทธิ์)
- สามารถส่งกลับขั้นตอนก่อนหน้าได้
- สามารถเพิ่มความคิดเห็นในแต่ละขั้นตอน
- แจ้งเตือนอัตโนมัติเมื่อถึงขั้นตอนใหม่หรือใกล้ครบกำหนด
- 3.2.6. การจัดการ: มีการจัดการอย่างน้อยดังนี้
- สามารถกำหนดวันแล้วเสร็จ (Deadline) สำหรับผู้รับผิดชอบของ องกรณ์ ที่เป็นผู้รับได้
- มีระบบแจ้งเตือน ให้ผู้รับผิดชอบขององกรณ์ที่เป็น ผู้รับ/ผู้ส่ง ทราบ เมื่อมีเอกสารใหม่ หรือมีการเปลี่ยนสถานะ
### **3.3. การจัดกาแบบคู่สัญญา (Contract Drawing)**
- 3.3.1. วัตถุประสงค์: แบบคู่สัญญา (Contract Drawing) ใช้เพื่ออ้างอิงและใช้ในการตรวจสอบ
- 3.3.2. ประเภทเอกสาร: ไฟล์ PDF
- 3.3.3. การสร้างเอกสาร: ผู้ใช้ที่มีสิทธิ์ สามารถสร้างและแก้ไขได้
- 3.3.4. การอ้างอิงและจัดกลุ่ม: ใช้สำหรับอ้างอิง ใน Shop Drawings, มีการจัดหมวดหมู่ของ Contract Drawing
### **3.4. การจัดกาแบบก่อสร้าง (Shop Drawing)**
- 3.4.1. วัตถุประสงค์: แบบก่อสร้าง (Shop Drawing) ใช้เในการตรวจสอบ โดยจัดส่งด้วย Request for Approval (RFA)
- 3.4.2. ประเภทเอกสาร: ไฟล์ PDF
- 3.4.3. การสร้างเอกสาร: ผู้ใช้ที่มีสิทธิ์ สามารถสร้างและแก้ไขได้
- 3.4.4. การอ้างอิงและจัดกลุ่ม: ช้สำหรับอ้างอิง ใน Shop Drawings, มีการจัดหมวดหมู่ของ Shop Drawings
### **3.5. การจัดการ Workflow (Unified Workflow)**
- 3.5.1 Workflow Definition:
- Admin ต้องสามารถสร้าง/แก้ไข Workflow Rule ได้ผ่านหน้าจอ UI (DSL Editor) ร
- องรับการกำหนด State, Transition, Required Role, Condition (JS Expression)
- 3.5.2 Workflow Execution:
- ระบบต้องรองรับการสร้าง Instance ของ Workflow ผูกกับเอกสาร (Polymorphic)
- รองรับการเปลี่ยนสถานะ (Action) เช่น Approve, Reject, Comment, Return
- Auto-Action: รองรับการเปลี่ยนสถานะอัตโนมัติเมื่อครบเงื่อนไข (เช่น Review ครบทุกคน)
- 3.5.3 Flexibility:
- รองรับ Parallel Review (ส่งให้หลายคนตรวจพร้อมกัน)
- รองรับ Conditional Flow (เช่น ถ้ายอดเงิน > X ให้เพิ่มผู้อนุมัติ)
- 3.5.4 Workflow การอนุมัติ:
- ต้องรองรับกระบวนการอนุมัติที่ซับซ้อนและเป็นลำดับ เช่น ส่งจาก Originator -> Organization 1 -> Organization 2 -> Organization 3 แล้วส่งผลกลับตามลำดับเดิม (โดยถ้า องกรณ์ใดใน Workflow ให้ส่งกลับ ก็สามารถส่งผลกลับตามลำดับเดิมโดยไม่ต้องรอให้ถึง องกรณืในลำดับถัดไป)
- 3.5.5 การจัดการ:
- สามารถกำหนดวันแล้วเสร็จ (Deadline) สำหรับผู้รับผิดชอบของ องกรณ์ ที่อยู่ใน Workflow ได้
- มีระบบแจ้งเตือน ให้ผู้รับผิดชอบของ องกรณ์ ที่อยู่ใน Workflow ทราบ เมื่อมี RFA ใหม่ หรือมีการเปลี่ยนสถานะ
### **3.6.การจัดการเอกสารนำส่ง (Transmittals)**
- 3.6.1. วัตถุประสงค์: เอกสารนำส่ง ใช้สำหรับ นำส่ง Request for Approval (RFAS) หลายฉบับ ไปยังองค์กรอื่น
- 3.6.2. ประเภทเอกสาร: ไฟล์ PDF
- 3.6.3. การสร้างเอกสาร: ผู้ใช้ที่มีสิทธิ์ สามารถสร้างและแก้ไขได้
- 3.6.4. การอ้างอิงและจัดกลุ่ม: เอกสารนำส่ง เป็นส่วนหนึ่งใน Correspondence
### **3.7. ใบเวียนเอกสาร (Circulation Sheet)**
- 3.7.1. วัตถุประสงค์: การสื่อสาร เอกสาร (Correspondence) ทุกฉบับ จะมีใบเวียนเอกสารเพื่อควบคุมและมอบหมายงานภายในองค์กร (สามารถดูและแก้ไขได้เฉพาะคนในองค์กร)
- 3.7.2. ประเภทเอกสาร: ไฟล์ PDF
- 3.7.3. การสร้างเอกสาร: ผู้ใช้ที่มีสิทธิ์ในองค์กรนั้น สามารถสร้างและแก้ไขได้
- 3.7.4. การอ้างอิงและจัดกลุ่ม: การระบุผู้รับผิดชอบ:
- ผู้รับผิดชอบหลัก (Main): มีได้หลายคน
- ผู้ร่วมปฏิบัติงาน (Action): มีได้หลายคน
- ผู้ที่ต้องรับทราบ (Information): มีได้หลายคน
- 3.7.5. การติดตามงาน:
- สามารถกำหนดวันแล้วเสร็จ (Deadline) สำหรับผู้รับผิดชอบประเภท Main และ Action ได้
- มีระบบแจ้งเตือนเมื่อมี Circulation ใหม่ และแจ้งเตือนล่วงหน้าก่อนถึงวันแล้วเสร็จ
- สามารถปิด Circulation ได้เมื่อดำเนินการตอบกลับไปยังองค์กรผู้ส่ง (Originator) แล้ว หรือ รับทราบแล้ว (For Information)
### **3.8. ประวัติการแก้ไข (Revisions):** ระบบจะเก็บประวัติการสร้างและแก้ไข เอกสารทั้งหมด
### **3.9. การจัดเก็บไฟล์ (File Handling - ปรับปรุงใหญ่)**
- **3.9.1 Two-Phase Storage Strategy:**
1. **Phase 1 (Upload):** ไฟล์ถูกอัปโหลดเข้าโฟลเดอร์ `temp/` และได้รับ `temp_id`
2. **Phase 2 (Commit):** เมื่อ User กด Submit ฟอร์มสำเร็จ ระบบจะย้ายไฟล์จาก `temp/` ไปยัง `permanent/{YYYY}/{MM}/` และบันทึกลง Database ภายใน Transaction เดียวกัน
3. **Cleanup:** มี Cron Job ลบไฟล์ใน `temp/` ที่ค้างเกิน 24 ชม. (Orphan Files)
- **3.9.2 Security:**
- Virus Scan (ClamAV) ก่อนย้ายเข้า Permanent
- Whitelist File Types: PDF, DWG, DOCX, XLSX, ZIP
- Max Size: 50MB
- Access Control: ตรวจสอบสิทธิ์ผ่าน Junction Table ก่อนให้ Download Link
- **3.9.3 ความปลอดภัยของการจัดเก็บไฟล์:**
- ต้องมีการ scan virus สำหรับไฟล์ที่อัปโหลดทั้งหมด โดยใช้ ClamAV หรือบริการ third-party
- จำกัดประเภทไฟล์ที่อนุญาต: PDF, DWG, DOCX, XLSX, ZIP (ต้องระบุรายการที่ชัดเจน)
- ขนาดไฟล์สูงสุด: 50MB ต่อไฟล์
- ไฟล์ต้องถูกเก็บนอก web root และเข้าถึงได้ผ่าน authenticated endpoint เท่านั้น
- ต้องมี file integrity check (checksum) เพื่อป้องกันการแก้ไขไฟล์
- Download links ต้องมี expiration time (default: 24 ชั่วโมง)
- ต้องบันทึก audit log ทุกครั้งที่มีการดาวน์โหลดไฟล์สำคัญ
### **3.10. การจัดการเลขที่เอกสาร (Document Numbering - ปรับปรุง)**
- 3.10.1. ระบบต้องสามารถสร้างเลขที่เอกสาร (Running Number) ได้โดยอัตโนมัติและยืดหยุ่นสูง
- 3.10.2. **Logic การนับเลข (Counter Logic):** การนับเลขจะต้องรองรับการแยกตาม Key ที่ซับซ้อนขึ้น:
- **Project** + **Originator** + **Type** + **Discipline** (ถ้ามี) + **Sub-Type** (ถ้ามี) + **Year**
- 3.10.3. **Format Template:** รองรับการกำหนดรูปแบบด้วย **Token Replacement** เช่น:
- `{ORG}-{TYPE}-{DISCIPLINE}-{SEQ:4}-{REV}` -> `TEAM-RFA-STR-0001-A`
- รองรับ Token: `{PROJECT}`, `{ORG}`, `{TYPE}`, `{DISCIPLINE}`, `{SUBTYPE}`, `{SUBTYPE_NUM}`, `{YEAR}`, `{YEAR_SHORT}`, `{SEQ:n}`
- 3.10.4. **Transmittal Logic:** รองรับเงื่อนไขพิเศษสำหรับ Transmittal ที่เลขอาจเปลี่ยนตามผู้รับ (To Owner vs To Contractor)
- 3.10.5. **กลไกความปลอดภัย:** ยังคงใช้ Redis Distributed Lock และ Optimistic Locking เพื่อป้องกันเลขซ้ำหรือข้าม
- 3.10.6. ต้องมี retry mechanism และ fallback strategy เมื่อการ generate เลขที่เอกสารล้มเหลว
### **3.11 การจัดการ JSON Details (JSON & Performance - ปรับปรุง)**
- **3.11.1 วัตถุประสงค์**
- จัดเก็บข้อมูลแบบไดนามิกที่เฉพาะเจาะจงกับแต่ละประเภทของเอกสาร
- รองรับการขยายตัวของระบบโดยไม่ต้องเปลี่ยนแปลง database schema
- จัดการ metadata และข้อมูลประกอบสำหรับ correspondence, routing, และ workflows
- **3.11.2 โครงสร้าง JSON Schema**
ระบบต้องมี predefined JSON schemas สำหรับประเภทเอกสารต่างๆ:
- **3.11.2.1 Correspondence Types**
- **GENERIC**: ข้อมูลพื้นฐานสำหรับเอกสารทั่วไป
- **RFI**: รายละเอียดคำถามและข้อมูลทางเทคนิค
- **RFA**: ข้อมูลการขออนุมัติแบบและวัสดุ
- **TRANSMITTAL**: รายการเอกสารที่ส่งต่อ
- **LETTER**: ข้อมูลจดหมายทางการ
- **EMAIL**: ข้อมูลอีเมล
- **3.11.2.2 Rworkflow Types**
- **workflow_definitions**: กฎและเงื่อนไขการส่งต่อ
- **workflow_histories**: สถานะและประวัติการส่งต่อ
- **workflow_instances**: การดำเนินการในแต่ละขั้นตอน
- **3.11.2.3 Audit Types**
- **AUDIT_LOG**: ข้อมูลการตรวจสอบ
- **SECURITY_SCAN**: ผลการตรวจสอบความปลอดภัย
- **3.11.3 Virtual Columns (ใหม่):** สำหรับ Field ใน JSON ที่ต้องใช้ในการค้นหา (Search) หรือจัดเรียง (Sort) บ่อยๆ **ต้องสร้าง Generated Column (Virtual Column)** ใน Database และทำ Index ไว้ เพื่อประสิทธิภาพสูงสุด
- **3.11.4 Validation Rules**
- ต้องมี JSON schema validation สำหรับแต่ละประเภท
- ต้องรองรับ versioning ของ schema
- ต้องมี default values สำหรับ field ที่ไม่บังคับ
- ต้องตรวจสอบ data types และ format ให้ถูกต้อง
- **3.11.5 Performance Requirements**
- JSON field ต้องมีขนาดไม่เกิน 50KB
- ต้องรองรับ indexing สำหรับ field ที่ใช้ค้นหาบ่อย
- ต้องมี compression สำหรับ JSON ขนาดใหญ่
- **3.11.6 Security Requirements**
- ต้อง sanitize JSON input เพื่อป้องกัน injection attacks
- ต้อง validate JSON structure ก่อนบันทึก
- ต้อง encrypt sensitive data ใน JSON fields
### **3.12 ข้อกำหนดพิเศษ**
- **ผู้ใช้งานที่มีสิทธิ์ระดับสูง (Global) หรือผู้ได้รับอนุญาตเป็นกรณีพิเศษ**
- สามารถเลือก **สร้างในนามองค์กร (Create on behalf of)** ได้ เพื่อให้สามารถออกเลขที่เอกสาร (Running Number) ขององค์กรอื่นได้โดยไม่ต้องล็อกอินใหม่
- สามารถทำงานแทนผู้ใช้งานอื่นได้ Routing & Workflow ของ Correspondence, RFA, Circulation Sheet
### 3.13. การจัดการข้อมูลหลักขั้นสูง (Admin Panel for Master Data)
- 3.13.1. **Disciplines Management:** Admin ต้องสามารถ เพิ่ม/ลบ/แก้ไข สาขางาน (Disciplines) แยกตามสัญญา (Contract) ได้
- 3.13.2. **Sub-Type Mapping:** Admin ต้องสามารถกำหนด Correspondence Sub-types และ Mapping รหัสตัวเลข (เช่น MAT = 11) ได้
- 3.13.3. **Numbering Format Configuration:** Admin ต้องมี UI สำหรับตั้งค่า Format Template ของแต่ละ Project/Type ได้โดยไม่ต้องแก้โค้ด
## **🔐 4. ข้อกำหนดด้านสิทธิ์และการเข้าถึง (Access Control Requirements)**
### **4.1. ภาพรวม:** ผู้ใช้และองค์กรสามารถดูและแก้ไขเอกสารได้ตามสิทธิ์ที่ได้รับ โดยระบบสิทธิ์จะเป็นแบบ Role-Based Access Control (RBAC)
### **4.2. ลำดับชั้นของสิทธิ์ (Permission Hierarchy)**
- Global: สิทธิ์สูงสุดของระบบ
- Organization: สิทธิ์ภายในองค์กร เป็นสิทธิ์พื้นฐานของผู้ใช้
- Project: สิทธิ์เฉพาะในโครงการ จะถูกพิจารณาเมื่อผู้ใช้อยู่ในโครงการนั้น
- Contract: สิทธิ์เฉพาะในสัญญา จะถูกพิจารณาเมื่อผู้ใช้อยู่ในสัญญานั้น (สัญญาเป็นส่วนหนึ่งของโครงการ)
กฎการบังคับใช้: เมื่อตรวจสอบสิทธิ์ ระบบจะพิจารณาสิทธิ์จากทุกระดับที่ผู้ใช้มี และใช้ สิทธิ์ที่มากที่สุด (Most Permissive) เป็นตัวตัดสิน
ตัวอย่าง: ผู้ใช้ A เป็น Viewer ในองค์กร แต่ถูกมอบหมายเป็น Editor ในโครงการ X เมื่ออยู่ในโครงการ X ผู้ใช้ A จะมีสิทธิ์แก้ไขได้
### **4.3. การกำหนดบทบาท (Roles) และขอบเขต (Scope)**
| บทบาท (Role) | ขอบเขต (Scope) | คำอธิบาย | สิทธิ์หลัก (Key Permissions) |
| :------------------- | :------------- | :---------------------- | :------------------------------------------------------------------------------------- |
| **Superadmin** | Global | ผู้ดูแลระบบสูงสุด | ทำทุกอย่างในระบบ, จัดการองค์กร, จัดการข้อมูลหลักระดับ Global |
| **Org Admin** | Organization | ผู้ดูแลองค์กร | จัดการผู้ใช้ในองค์กร, จัดการบทบาท/สิทธิ์ภายในองค์กร, ดูรายงานขององค์กร |
| **Document Control** | Organization | ควบคุมเอกสารขององค์กร | เพิ่ม/แก้ไข/ลบเอกสาร, กำหนดสิทธิ์เอกสารภายในองค์กร |
| **Editor** | Organization | ผู้แก้ไขเอกสารขององค์กร | เพิ่ม/แก้ไขเอกสารที่ได้รับมอบหมาย |
| **Viewer** | Organization | ผู้ดูเอกสารขององค์กร | ดูเอกสารที่มีสิทธิ์เข้าถึง |
| **Project Manager** | Project | ผู้จัดการโครงการ | จัดการสมาชิกในโครงการ (เพิ่ม/ลบ/มอบบทบาท), สร้าง/จัดการสัญญาในโครงการ, ดูรายงานโครงการ |
| **Contract Admin** | Contract | ผู้ดูแลสัญญา | จัดการสมาชิกในสัญญา, สร้าง/จัดการข้อมูลหลักเฉพาะสัญญา (ถ้ามี), อนุมัติเอกสารในสัญญา |
### **4.4. Token Management (ปรับปรุง)**
- **Payload Optimization:** ใน JWT Access Token ให้เก็บเฉพาะ `userId` และ `scope` ปัจจุบันเท่านั้น
- **Permission Caching:** สิทธิ์ละเอียด (Permissions List) ให้เก็บใน **Redis** และดึงมาตรวจสอบเมื่อ Request เข้ามา เพื่อลดขนาด Token และเพิ่มความเร็ว
### **4.5. กระบวนการเริ่มต้นใช้งาน (Onboarding Workflow) ที่สมบูรณ์**
- **4.5.1. สร้างองค์กร (Organization)**
- **Superadmin** สร้างองค์กรใหม่ (เช่น บริษัท A)
- **Superadmin** แต่งตั้งผู้ใช้อย่างน้อย 1 คนให้เป็น **Org Admin** หรือ **Document Control** ของบริษัท A
- **4.5.2. เพิ่มผู้ใช้ในองค์กร**
- **Org Admin** ของบริษัท A เพิ่มผู้ใช้อื่นๆ (Editor, Viewer) เข้ามาในองค์กรของตน
- **4.5.3. มอบหมายผู้ใช้ให้กับโครงการ (Project)**
- **Project Manager** ของโครงการ X (ซึ่งอาจมาจากบริษัท A หรือบริษัทอื่น) ทำการ "เชิญ" หรือ "มอบหมาย" ผู้ใช้จากองค์กรต่างๆ ที่เกี่ยวข้องเข้ามาในโครงการ X
- ในขั้นตอนนี้ **Project Manager** จะกำหนด **บทบาทระดับโครงการ** (เช่น Project Member, หรืออาจไม่มีบทบาทพิเศษ ให้ใช้สิทธิ์จากระดับองค์กรไปก่อน)
- **4.5.4. เมอบหมายผู้ใช้ให้กับสัญญา (Contract)**
- **Contract Admin** ของสัญญา Y (ซึ่งเป็นส่วนหนึ่งของโครงการ X) ทำการเลือกผู้ใช้ที่อยู่ในโครงการ X แล้ว มอบหมายให้เข้ามาในสัญญา Y
- ในขั้นตอนนี้ **Contract Admin** จะกำหนด **บทบาทระดับสัญญา** (เช่น Contract Member) และสิทธิ์เฉพาะที่จำเป็น
- **4.5.5 Security Onboarding:**
- ต้องบังคับเปลี่ยน password ครั้งแรกสำหรับผู้ใช้ใหม่
- ต้องมี security awareness training สำหรับผู้ใช้ที่มีสิทธิ์สูง
- ต้องมี process สำหรับการรีเซ็ต password ที่ปลอดภัย
- ต้องบันทึก audit log ทุกครั้งที่มีการเปลี่ยนแปลง permissions
### **4.6. การจัดการข้อมูลหลัก (Master Data Management) ที่แบ่งตามระดับ**
| ข้อมูลหลัก | ผู้มีสิทธิ์จัดการ | ระดับ |
| :---------------------------------- | :------------------------------ | :--------------------------------- |
| ประเภทเอกสาร (Correspondence, RFA) | **Superadmin** | Global |
| สถานะเอกสาร (Draft, Approved, etc.) | **Superadmin** | Global |
| หมวดหมู่แบบ (Shop Drawing) | **Project Manager** | Project (สร้างใหม่ได้ภายในโครงการ) |
| Tags | **Org Admin / Project Manager** | Organization / Project |
| บทบาทและสิทธิ์ (Custom Roles) | **Superadmin / Org Admin** | Global / Organization |
| Document Numbering Formats | **Superadmin / Admin** | Global / Organization |
## **👥 5. ข้อกำหนดด้านผู้ใช้งาน (User Interface & Experience)**
### **5.1. Layout หลัก:** หน้าเว็บใช้รูปแบบ App Shell ที่ประกอบด้วย
- Navbar (ส่วนบน): แสดงชื่อระบบ, เมนูผู้ใช้ (Profile), เมนูสำหรับ Document Control/เมนูสำหรับ Admin/Superadmin (จัดการผู้ใช้, จัดการสิทธิ์), และปุ่ม Login/Logout
- Sidebar (ด้านข้าง): เป็นเมนูหลักสำหรับเข้าถึงส่วนที่เกี่ยวข้องกับเอกสารทั้งหมด เช่น Dashboard, Correspondences, RFA, Drawings
- Main Content Area: พื้นที่สำหรับแสดงเนื้อหาหลักของหน้าที่เลือก
### **5.2. หน้า Landing Page:** เป็นหน้าแรกที่แสดงข้อมูลบางส่วนของโครงการสำหรับผู้ใช้ที่ยังไม่ได้ล็อกอิน
### **5.3. หน้า Dashboard:** เป็นหน้าแรกหลังจากล็อกอิน ประกอบด้วย
- การ์ดสรุปภาพรวม (KPI Cards): แสดงข้อมูลสรุปที่สำคัญขององค์กร เช่น จำนวนเอกสาร, งานที่เกินกำหนด
- ตาราง "งานของฉัน" (My Tasks Table): แสดงรายการงานทั้งหมดจาก Circulation ที่ผู้ใช้ต้องดำเนินการ
- Security Metrics: แสดงจำนวน files scanned, security incidents, failed login attempts
### **5.4. การติดตามสถานะ:** องค์กรสามารถติดตามสถานะเอกสารทั้งของตนเอง (Originator) และสถานะเอกสารที่ส่งมาถึงตนเอง (Recipient)
### **5.5. การจัดการข้อมูลส่วนตัว (Profile Page):** ผู้ใช้สามารถจัดการข้อมูลส่วนตัวและเปลี่ยนรหัสผ่านของตนเองได้
### **5.6. การจัดการเอกสารทางเทคนิค (RFA & Workflow):** ผู้ใช้สามารถดู RFA ในรูปแบบ Workflow ทั้งหมดได้ในหน้าเดียว, ขั้นตอนที่ยังไม่ถึงหรือผ่านไปแล้วจะเป็นรูปแบบ diable, สามารถดำเนินการได้เฉพาะในขั้นตอนที่ได้รับมอบหมายงาน (active) เช่น ตรวจสอบแล้ว เพื่อไปยังขั้นตอนต่อไป, สิทธิ์ Document Control ขึ้นไป สามรถกด ไปยังขั้นตอนต่อไป ได้ทุกขั้นตอน, การย้อนกลับ ไปขั้นตอนก่อนหน้า สามารถทำได้โดย สิทธิ์ Document Control ขึ้นไป
### **5.7. การจัดการใบเวียนเอกสาร (Circulation):** ผู้ใช้สามารถดู Circulation ในรูปแบบ Workflow ทั้งหมดได้ในหน้าเดียว,ขั้นตอนที่ยังไม่ถึงหรือผ่านไปแล้วจะเป็นรูปแบบ diable, สามารถดำเนินการได้เฉพาะในขั้นตอนที่ได้รับมอบหมายงาน (active) เช่น ตรวจสอบแล้ว เพื่อไปยังขั้นตอนต่อไป, สิทธิ์ Document Control ขึ้นไป สามรถกด ไปยังขั้นตอนต่อไป ได้ทุกขั้นตอน, การย้อนกลับ ไปขั้นตอนก่อนหน้า สามารถทำได้โดย สิทธิ์ Document Control ขึ้นไป
### **5.8. การจัดการเอกสารนำส่ง (Transmittals):** ผู้ใช้สามารถดู Transmittals ในรูปแบบรายการทั้งหมดได้ในหน้าเดียว
### **5.9. ข้อกำหนด UI/UX การแนบไฟล์ (File Attachment UX):**
- ระบบต้องรองรับการอัปโหลดไฟล์หลายไฟล์พร้อมกัน (Multi-file upload) เช่น การลากและวาง (Drag-and-Drop)
- ในหน้าอัปโหลด (เช่น สร้าง RFA หรือ Correspondence) ผู้ใช้ต้องสามารถกำหนดได้ว่าไฟล์ใดเป็น "เอกสารหลัก" (Main Document เช่น PDF) และไฟล์ใดเป็น "เอกสารแนบประกอบ" (Supporting Attachments เช่น .dwg, .docx, .zip)
- **Security Feedback:** แสดง security warnings สำหรับ file types ที่เสี่ยงหรือ files ที่ fail virus scan
- **File Type Indicators:** แสดง file type icons และ security status
### **5.10 Form & Interaction (ใหม่)**
- **Dynamic Form Generator:** ใช้ Component กลางที่รับ JSON Schema แล้ว Render Form ออกมาอัตโนมัติ เพื่อลดความซ้ำซ้อนของโค้ดหน้าบ้าน และรองรับเอกสารประเภทใหม่ๆ ได้ทันที
- **Optimistic Updates:** การเปลี่ยนสถานะ (เช่น กด Approve, กด Read) ให้ UI เปลี่ยนสถานะทันทีให้ผู้ใช้เห็นก่อนรอ API Response (Rollback ถ้า Failed)
### **5.11 Mobile Responsiveness (ใหม่)**
- **Table Visualization:** บนหน้าจอมือถือ ตารางข้อมูลที่มีหลาย Column (เช่น Correspondence List) ต้องเปลี่ยนการแสดงผลเป็นแบบ **Card View** อัตโนมัติ
- **Navigation:** Sidebar ต้องเป็นแบบ Collapsible Drawer
### **5.12 Resilience & Offline Support (ใหม่)**
- **Auto-Save Draft:** ระบบต้องบันทึกข้อมูลฟอร์มที่กำลังกรอกลง **LocalStorage** อัตโนมัติ เพื่อป้องกันข้อมูลหายกรณีเน็ตหลุดหรือปิด Browser โดยไม่ได้ตั้งใจ
- **Graceful Degradation:** หาก Service รอง (เช่น Search, Notification) ล่ม ระบบหลัก (CRUD) ต้องยังทำงานต่อได้
## **🛡️ 6. ข้อกำหนดที่ไม่ใช่ฟังก์ชันการทำงาน (Non-Functional Requirements)**
### **6.1. การบันทึกการกระทำ (Audit Log):** ทุกการกระทำที่สำคัญของผู้ใช้ (สร้าง, แก้ไข, ลบ, ส่ง) จะถูกบันทึกไว้ใน audit_logs เพื่อการตรวจสอบย้อนหลัง
- **6.1.1 ขอบเขตการบันทึก Audit Log:**
- ทุกการสร้าง/แก้ไข/ลบ ข้อมูลสำคัญ (correspondences, RFAs, drawings, users, permissions)
- ทุกการเข้าถึงข้อมูล sensitive (user data, financial information)
- ทุกการเปลี่ยนสถานะ workflow (status transitions)
- ทุกการดาวน์โหลดไฟล์สำคัญ (contract documents, financial reports)
- ทุกการเปลี่ยนแปลง permission และ role assignment
- ทุกการล็อกอินที่สำเร็จและล้มเหลว
- ทุกการส่งคำขอ API ที่สำคัญ
- **6.1.2 ข้อมูลที่ต้องบันทึกใน Audit Log:**
- ผู้ใช้งาน (user_id)
- การกระทำ (action)
- ชนิดของ entity (entity_type)
- ID ของ entity (entity_id)
- ข้อมูลก่อนการเปลี่ยนแปลง (old_values) - สำหรับ update operations
- ข้อมูลหลังการเปลี่ยนแปลง (new_values) - สำหรับ update operations
- IP address
- User agent
- Timestamp
- Request ID สำหรับ tracing
### **6.2. Data Archiving & Partitioning (ใหม่)**
- สำหรับตารางที่มีขนาดใหญ่และโตเร็ว (เช่น `audit_logs`, `notifications`, `correspondence_revisions`) ต้องออกแบบโดยรองรับ **Table Partitioning** (แบ่งตาม Range วันที่ หรือ List) เพื่อประสิทธิภาพในระยะยาว
### **6.3. การค้นหา (Search):** ระบบต้องมีฟังก์ชันการค้นหาขั้นสูง ที่สามารถค้นหาเอกสาร **correspondence**, **rfa**, **shop_drawing**, **contract-drawing**, **transmittal** และ **ใบเวียน (Circulations)** จากหลายเงื่อนไขพร้อมกันได้ เช่น ค้นหาจากชื่อเรื่อง, ประเภท, วันที่, และ Tag
### **6.4. การทำรายงาน (Reporting):** สามารถจัดทำรายงานสรุปแยกประเภทของ Correspondence ประจำวัน, สัปดาห์, เดือน, และปีได้
### **6.5. ประสิทธิภาพ (Performance):** มีการใช้ Caching กับข้อมูลที่เรียกใช้บ่อย และใช้ Pagination ในตารางข้อมูลเพื่อจัดการข้อมูลจำนวนมาก
- **6.5.1 ตัวชี้วัดประสิทธิภาพ:**
- **API Response Time:** < 200ms (90th percentile) สำหรับ operation ทั่วไป
- **Search Query Performance:** < 500ms สำหรับการค้นหาขั้นสูง
- **File Upload Performance:** < 30 seconds สำหรับไฟล์ขนาด 50MB
- **Concurrent Users:** รองรับผู้ใช้พร้อมกันอย่างน้อย 100 คน
- **Database Connection Pool:** ขนาดเหมาะสมกับ workload (default: min 5, max 20 connections)
- **Cache Hit Ratio:** > 80% สำหรับ cached data
- **Application Startup Time:** < 30 seconds
- **6.5.2 Caching Strategy:**
- **Master Data Cache:** Roles, Permissions, Organizations, Project metadata (TTL: 1 hour)
- **User Session Cache:** User permissions และ profile data (TTL: 30 minutes)
- **Search Result Cache:** Frequently searched queries (TTL: 15 minutes)
- **File Metadata Cache:** Attachment metadata (TTL: 1 hour)
- **Document Cache:** Frequently accessed document metadata (TTL: 30 minutes)
- **ต้องมี cache invalidation strategy ที่ชัดเจน:**
- Invalidate on update/delete operations
- Time-based expiration
- Manual cache clearance สำหรับ admin operations
- ใช้ Redis เป็น distributed cache backend
- ต้องมี cache monitoring (hit/miss ratios)
### **6.6. ความปลอดภัย (Security):**
- มีระบบ Rate Limiting เพื่อป้องกันการโจมตีแบบ Brute-force
- การจัดการ Secret (เช่น รหัสผ่าน DB, JWT Secret) จะต้องทำผ่าน Environment Variable ของ Docker เพื่อความปลอดภัยสูงสุด
- **6.6.1 Rate Limiting Strategy:**
- **Anonymous Endpoints:** 100 requests/hour ต่อ IP address
- **Authenticated Endpoints:**
- Viewer: 500 requests/hour
- Editor: 1000 requests/hour
- Document Control: 2000 requests/hour
- Admin/Superadmin: 5000 requests/hour
- **File Upload Endpoints:** 50 requests/hour ต่อ user
- **Search Endpoints:** 500 requests/hour ต่อ user
- **Authentication Endpoints:** 10 requests/minute ต่อ IP address
- **ต้องมี mechanism สำหรับยกเว้น rate limiting สำหรับ trusted services**
- ต้องบันทึก log เมื่อมีการ trigger rate limiting
- **6.6.2 Error Handling และ Resilience:**
- ต้องมี circuit breaker pattern สำหรับ external service calls
- ต้องมี retry mechanism ด้วย exponential backoff
- ต้องมี graceful degradation เมื่อบริการภายนอกล้มเหลว
- Error messages ต้องไม่เปิดเผยข้อมูล sensitive
- **6.6.3 Input Validation:**
- ต้องมี input validation ทั้งฝั่ง client และ server (defense in depth)
- ต้องป้องกัน OWASP Top 10 vulnerabilities:
- SQL Injection (ใช้ parameterized queries ผ่าน ORM)
- XSS (input sanitization และ output encoding)
- CSRF (CSRF tokens สำหรับ state-changing operations)
- ต้อง validate file uploads:
- File type (white-list approach)
- File size
- File content (magic number verification)
- ต้อง sanitize user inputs ก่อนแสดงผลใน UI
- ต้องใช้ content security policy (CSP) headers
- ต้องมี request size limits เพื่อป้องกัน DoS attacks
- **6.6.4 Session และ Token Management:**
- **JWT token expiration:** 8 hours สำหรับ access token
- **Refresh token expiration:** 7 days
- **Refresh token mechanism:** ต้องรองรับ token rotation และ revocation
- **Token revocation on logout:** ต้องบันทึก revoked tokens จนกว่าจะ expire
- **Concurrent session management:**
- จำกัดจำนวน session พร้อมกันได้ (default: 5 devices)
- ต้องแจ้งเตือนเมื่อมี login จาก device/location ใหม่
- **Device fingerprinting:** สำหรับ security และ audit purposes
- **Password policy:**
- ความยาวขั้นต่ำ: 8 characters
- ต้องมี uppercase, lowercase, number, special character
- ต้องเปลี่ยน password ทุก 90 วัน
- ต้องป้องกันการใช้ password ที่เคยใช้มาแล้ว 5 ครั้งล่าสุด
### **6.7. การสำรองข้อมูลและการกู้คืน (Backup & Recovery):**
- ระบบจะต้องมีกลไกการสำรองข้อมูลอัตโนมัติสำหรับฐานข้อมูล MariaDB [cite: 2.4] และไฟล์เอกสารทั้งหมดใน /share/dms-data [cite: 2.1] (เช่น ใช้ HBS 3 ของ QNAP หรือสคริปต์สำรองข้อมูล) อย่างน้อยวันละ 1 ครั้ง
- ต้องมีแผนการกู้คืนระบบ (Disaster Recovery Plan) ในกรณีที่ Server หลัก (QNAP) ใช้งานไม่ได้
- **6.7.1 ขั้นตอนการกู้คืน:**
- **Database Restoration Procedure:**
- สร้างจาก full backup ล่าสุด
- Apply transaction logs ถึง point-in-time ที่ต้องการ
- Verify data integrity post-restoration
- **File Storage Restoration Procedure:**
- Restore จาก QNAP snapshot หรือ backup
- Verify file integrity และ permissions
- **Application Redeployment Procedure:**
- Deploy จาก version ล่าสุดที่รู้ว่าทำงานได้
- Verify application health
- **Data Integrity Verification Post-Recovery:**
- Run data consistency checks
- Verify critical business data
- **Recovery Time Objective (RTO):** < 4 ชั่วโมง
- **Recovery Point Objective (RPO):** < 1 ชั่วโมง
### **6.8. กลยุทธ์การแจ้งเตือน (Notification Strategy - ปรับปรุง):**
- **6.8.1 ระบบจะส่งการแจ้งเตือน (ผ่าน Email หรือ Line [cite: 2.7]) เมื่อมีการกระทำที่สำคัญ** ดังนี้:
1. เมื่อมีเอกสารใหม่ (Correspondence, RFA) ถูกส่งมาถึงองค์กรณ์ของเรา
2. เมื่อมีใบเวียน (Circulation) ใหม่ มอบหมายงานมาที่เรา
3. (ทางเลือก) เมื่อเอกสารที่เราส่งไป ถูกดำเนินการ (เช่น อนุมัติ/ปฏิเสธ)
4. (ทางเลือก) เมื่อใกล้ถึงวันครบกำหนด (Deadline) [cite: 3.2.5, 3.6.6, 3.7.5]
- **6.8.2 Grouping/Digest (ใหม่):** กรณีมีการแจ้งเตือนประเภทเดียวกันจำนวนมากในช่วงเวลาสั้นๆ (เช่น Approve เอกสาร 10 ฉบับรวด) ระบบต้อง **รวม (Batch)** เป็น 1 Email/Line Notification เพื่อไม่ให้รบกวนผู้ใช้ (Spamming)
- **6.8.3 Notification Delivery Guarantees:**
- **At-least-once delivery:** สำหรับ important notifications
- **Retry mechanism:** ด้วย exponential backoff (max 3 reties)
- **Dead letter queue:** สำหรับ notifications ที่ส่งไม่สำเร็จหลังจาก retries
- **Delivery status tracking:** ต้องบันทึกสถานะการส่ง notifications
- **Fallback channels:** ถ้า Email ล้มเหลว ให้ส่งผ่าน SYSTEM notification
- **Notification preferences:** ผู้ใช้ต้องสามารถกำหนด channel preferences ได้
### **6.9. Maintenance Mode (ใหม่)**
- ระบบต้องมีกลไก **Maintenance Mode** ที่ Admin สามารถเปิดใช้งานได้
- เมื่อเปิด: ผู้ใช้ทั่วไปจะเห็นหน้า "ปิดปรับปรุง" และไม่สามารถเรียก API ได้ (ยกเว้น Admin)
- ใช้สำหรับช่วง Deploy Version ใหม่ หรือ Database Migration
### **6.10. Monitoring และ Observability**
- **6.10.1 Application Monitoring:**
- **Health checks:** /health endpoint สำหรับ load balancer
- **Metrics collection:** Response times, error rates, throughput
- **Distributed tracing:** สำหรับ request tracing across services
- **Log aggregation:** Structured logging ด้วย JSON format
- **Alerting:** สำหรับ critical errors และ performance degradation
- **6.10.2 Business Metrics:**
- จำนวน documents created ต่อวัน
- Workflow completion rates
- User activity metrics
- System utilization rates
- Search query performance
- **6.10.3 Security Monitoring:**
- Failed login attempts
- Rate limiting triggers
- Virus scan results
- File download activities
- Permission changes
### **6.11 JSON Processing & Validation**
- **6.11.1 JSON Schema Management**
- ต้องมี centralized JSON schema registry
- ต้องรองรับ schema versioning และ migration
- ต้องมี schema validation during runtime
- **6.11.2 Performance Optimization**
- **Caching:** Cache parsed JSON structures
- **Compression:** ใช้ compression สำหรับ JSON ขนาดใหญ่
- **Indexing:** Support JSON path indexing สำหรับ query
- **6.11.3 Error Handling**
- ต้องมี graceful degradation เมื่อ JSON validation ล้มเหลว
- ต้องมี default fallback values
- ต้องบันทึก error logs สำหรับ validation failures
## **🧪 7. ข้อกำหนดด้านการทดสอบ (Testing Requirements)**
### **7.1. Unit Testing:**
- ต้องมี unit tests สำหรับ business logic ทั้งหมด
- Code coverage อย่างน้อย 70% สำหรับ backend services
- ต้องทดสอบ RBAC permission logic ทุกระดับ
### **7.2. Integration Testing:**
- ทดสอบการทำงานร่วมกันของ modules
- ทดสอบ database migrations และ data integrity
- ทดสอบ API endpoints ด้วย realistic data
### **7.3. End-to-End Testing:**
- ทดสอบ complete user workflows
- ทดสอบ document lifecycle จาก creation ถึง archival
- ทดสอบ cross-module integrations
### **7.4. Security Testing:**
- **Penetration Testing:** ทดสอบ OWASP Top 10 vulnerabilities
- **Security Audit:** Review code สำหรับ security flaws
- **Virus Scanning Test:** ทดสอบ file upload security
- **Rate Limiting Test:** ทดสอบ rate limiting functionality
### **7.5. Performance Testing:**
- **Load Testing:** ทดสอบด้วย realistic workloads
- **Stress Testing:** หา breaking points ของระบบ
- **Endurance Testing:** ทดสอบการทำงานต่อเนื่องเป็นเวลานาน
### **7.6. Disaster Recovery Testing:**
- ทดสอบ backup และ restoration procedures
- ทดสอบ failover mechanisms
- ทดสอบ data integrity หลังการ recovery
### **7.7 Specific Scenario Testing (เพิ่ม)**
- **Race Condition Test:** ทดสอบยิง Request ขอเลขที่เอกสารพร้อมกัน 100 Request
- **Transaction Test:** ทดสอบปิดเน็ตระหว่าง Upload ไฟล์ (ตรวจสอบว่าไม่มี Orphan File หรือ Broken Link)
- **Permission Test:** ทดสอบ CASL Integration ทั้งฝั่ง Backend และ Frontend ให้ตรงกัน
## **8. ข้อกำหนดด้านการบำรุงรักษา (Maintenance Requirements)**
### **8.1. Log Retention:**
- Audit logs: 7 ปี
- Application logs: 1 ปี
- Performance metrics: 2 ปี
### **8.2. Monitoring และ Alerting:**
- ต้องมี proactive monitoring สำหรับ critical systems
- ต้องมี alerting สำหรับ security incidents
- ต้องมี performance degradation alerts
### **8.3. Patch Management:**
- ต้องมี process สำหรับ security patches
- ต้องทดสอบ patches ใน staging environment
- ต้องมี rollback plan สำหรับ failed updates
### **8.4. Capacity Planning:**
- ต้อง monitor resource utilization
- ต้องมี scaling strategy สำหรับ growth
- ต้องมี performance baselines และ trending
## **9. ข้อกำหนดด้านการปฏิบัติตามกฎระเบียบ (Compliance Requirements)**
### **9.1. Data Privacy:**
- ต้องปฏิบัติตามกฎหมายคุ้มครองข้อมูลส่วนบุคคล
- ต้องมี data retention policies
- ต้องมี data deletion procedures
### **9.2. Audit Compliance:**
- ต้องรองรับ internal และ external audits
- ต้องมี comprehensive audit trails
- ต้องมี reporting capabilities สำหรับ compliance
### **9.3. Security Standards:**
- ต้องปฏิบัติตาม organizational security policies
- ต้องมี security incident response plan
- ต้องมี regular security assessments
## **📋 สรุปการปรับปรุงจากเวอร์ชันก่อนหน้า**
### **Security Enhancements:**
1. **File Upload Security** - Virus scanning, file type validation, access controls
2. **Input Validation** - OWASP Top 10 protection, XSS/CSRF prevention
3. **Rate Limiting** - Comprehensive rate limiting strategy
4. **Secrets Management** - Secure handling of sensitive configuration
### **Architecture Improvements:**
1. **Document Numbering** - Changed from Stored Procedure to Application-level Locking with Optimistic Locking safety net
2. **Resilience Patterns** - Circuit breaker, retry mechanisms, fallback strategies
3. **Monitoring & Observability** - Health checks, metrics, distributed tracing
4. **Caching Strategy** - Comprehensive caching with proper invalidation
5. **Two-Phase File Storage** - Temp -> Permanent storage with transaction safety
6. **Unified Workflow Engine** - Consolidated routing logic for better maintainability
### **Performance Targets:**
1. **API Response Time** - < 200ms (90th percentile)
2. **Search Performance** - < 500ms
3. **File Upload** - < 30 seconds for 50MB files
4. **Cache Hit Ratio** - > 80%
### **Operational Excellence:**
1. **Disaster Recovery** - RTO < 4 hours, RPO < 1 hour
2. **Backup Procedures** - Comprehensive backup and restoration
3. **Security Testing** - Penetration testing and security audits
4. **Performance Testing** - Load testing with realistic workloads
5. **Maintenance Mode** - Graceful system maintenance capabilities
### **User Experience Improvements:**
1. **Dynamic Form Generator** - Reduced code duplication and better schema support
2. **Mobile Responsiveness** - Card view for tables on mobile devices
3. **Auto-Save Draft** - LocalStorage integration for form resilience
4. **Notification Digest** - Reduced notification spam
### **Data Management:**
1. **Virtual Columns** - Improved JSON field search performance
2. **Table Partitioning** - Support for large-scale data growth
3. **Idempotency Keys** - Prevention of duplicate transactions
เอกสารนี้สะท้อนถึงความมุ่งมั่นในการสร้างระบบที่มีความปลอดภัย, มีความทนทาน, และมีประสิทธิภาพสูง พร้อมรองรับการเติบโตในอนาคตและความต้องการทางธุรกิจที่เปลี่ยนแปลงไป
**หมายเหตุ:** Requirements นี้จะถูกทบทวนและปรับปรุงเป็นระยะตาม feedback จากทีมพัฒนาและความต้องการทางธุรกิจที่เปลี่ยนแปลงไป
## **Document Control:**
- **Document:** Application Requirements Specification v1.4.5
- **Version:** 1.4
- **Date:** 2025-11-29
- **Author:** NAP LCBP3-DMS & Gemini
- **Status:** FINAL-Rev.05
- **Classification:** Internal Technical Documentation
- **Approved By:** Nattanin
---
`End of Requirements Specification v1.4.5`

962
1_FullStackJS_V1_4_5.md Normal file
View File

@@ -0,0 +1,962 @@
# 📝 **Documents Management System Version 1.4.5: แนวทางการพัฒนา FullStackJS**
**สถานะ:** FINAL GUIDELINE Rev.05
**วันที่:** 2025-11-29
**อ้างอิง:** Requirements Specification v1.4.4
**Classification:** Internal Technical Documentation
## 🧠 **1. ปรัชญาทั่วไป (General Philosophy)**
แนวทางปฏิบัติที่ดีที่สุดแบบครบวงจรสำหรับการพัฒนา NestJS Backend, NextJS Frontend และ Tailwind-based UI/UX ในสภาพแวดล้อม TypeScript มุ่งเน้นที่ **"Data Integrity First"** (ความถูกต้องของข้อมูลต้องมาก่อน) ตามด้วย Security และ UX
- **ความชัดเจน (clarity), ความง่ายในการบำรุงรักษา (maintainability), ความสอดคล้องกัน (consistency) และ การเข้าถึงได้ (accessibility)** ตลอดทั้งสแต็ก
- **Strict Typing:** ใช้ TypeScript อย่างเคร่งครัด ห้าม `any`
- **Consistency:** ใช้ภาษาอังกฤษใน Code / ภาษาไทยใน Comment
- **Resilience:** ระบบต้องทนทานต่อ Network Failure และ Race Condition
## ⚙️ **2. แนวทางทั่วไปสำหรับ TypeScript**
### **2.1 หลักการพื้นฐาน**
- ใช้ **ภาษาอังกฤษ** สำหรับโค้ด
- ใช้ **ภาษาไทย** สำหรับ comment และเอกสารทั้งหมด
- กำหนดไทป์ (type) อย่างชัดเจนสำหรับตัวแปร, พารามิเตอร์ และค่าที่ส่งกลับ (return values) ทั้งหมด
- หลีกเลี่ยงการใช้ any; ให้สร้างไทป์ (types) หรืออินเทอร์เฟซ (interfaces) ที่กำหนดเอง
- ใช้ **JSDoc** สำหรับคลาส (classes) และเมธอด (methods) ที่เป็น public
- ส่งออก (Export) **สัญลักษณ์หลัก (main symbol) เพียงหนึ่งเดียว** ต่อไฟล์
- หลีกเลี่ยงบรรทัดว่างภายในฟังก์ชัน
- ระบุ // File: path/filename ในบรรทัดแรกของทุกไฟล์
- ระบุ // บันทึกการแก้ไข, หากมีการแก้ไขเพิ่มในอนาคต ให้เพิ่มบันทึก
### **2.2 Configuration & Secrets Management**
- **Production/Staging:** ห้ามใส่ Secrets (Password, Keys) ใน `docker-compose.yml` หลัก
- **Development:** ให้สร้างไฟล์ `docker-compose.override.yml` (เพิ่มใน `.gitignore`) เพื่อ Inject ตัวแปร Environment ที่เป็นความลับ
- **Validation:** ใช้ `joi` หรือ `zod` ในการ Validate Environment Variables ตอน Start App หากขาดตัวแปรสำคัญให้ Throw Error ทันที
### **2.3 Idempotency (ความสามารถในการทำซ้ำได้)**
- สำหรับการทำงานที่สำคัญ (Create Document, Approve, Transactional) **ต้อง** ออกแบบให้เป็น Idempotent
- Client **ต้อง** ส่ง Header `Idempotency-Key` (UUID) มากับ Request
- Server **ต้อง** ตรวจสอบว่า Key นี้เคยถูกประมวลผลสำเร็จไปแล้วหรือไม่ ถ้าใช่ ให้คืนค่าเดิมโดยไม่ทำซ้ำ
### **2.4 ข้อตกลงในการตั้งชื่อ (Naming Conventions)**
| Entity (สิ่งที่ตั้งชื่อ) | Convention (รูปแบบ) | Example (ตัวอย่าง) |
| :----------------------- | :------------------ | :--------------------------------- |
| Classes | PascalCase | UserService |
| Property | snake_case | user_id |
| Variables & Functions | camelCase | getUserInfo |
| Files & Folders | kebab-case | user-service.ts |
| Environment Variables | UPPERCASE | DATABASE_URL |
| Booleans | Verb + Noun | isActive, canDelete, hasPermission |
ใช้คำเต็ม — ไม่ใช้อักษรย่อ — ยกเว้นคำมาตรฐาน (เช่น API, URL, req, res, err, ctx)
### 🧩**2.5 ฟังก์ชัน (Functions)**
- เขียนฟังก์ชันให้สั้น และทำ **หน้าที่เพียงอย่างเดียว** (single-purpose) (\< 20 บรรทัด)
- ใช้ **early returns** เพื่อลดการซ้อน (nesting) ของโค้ด
- ใช้ **map**, **filter**, **reduce** แทนการใช้ loops เมื่อเหมาะสม
- ควรใช้ **arrow functions** สำหรับตรรกะสั้นๆ, และใช้ **named functions** ในกรณีอื่น
- ใช้ **default parameters** แทนการตรวจสอบค่า null
- จัดกลุ่มพารามิเตอร์หลายตัวให้เป็นอ็อบเจกต์เดียว (RO-RO pattern)
- ส่งค่ากลับ (Return) เป็นอ็อบเจกต์ที่มีไทป์กำหนด (typed objects) ไม่ใช่ค่าพื้นฐาน (primitives)
- รักษาระดับของสิ่งที่เป็นนามธรรม (abstraction level) ให้เป็นระดับเดียวในแต่ละฟังก์ชัน
### 🧱**2.6 การจัดการข้อมูล (Data Handling)**
- ห่อหุ้มข้อมูล (Encapsulate) ในไทป์แบบผสม (composite types)
- ใช้ **immutability** (การไม่เปลี่ยนแปลงค่า) ด้วย readonly และ as const
- ทำการตรวจสอบความถูกต้องของข้อมูล (Validations) ในคลาสหรือ DTOs ไม่ใช่ภายในฟังก์ชันทางธุรกิจ
- ตรวจสอบความถูกต้องของข้อมูลโดยใช้ DTOs ที่มีไทป์กำหนดเสมอ
### 🧰**2.7 คลาส (Classes)**
- ปฏิบัติตามหลักการ **SOLID**
- ควรใช้ **composition มากกว่า inheritance** (Prefer composition over inheritance)
- กำหนด **interfaces** สำหรับสัญญา (contracts)
- ให้คลาสมุ่งเน้นการทำงานเฉพาะอย่างและมีขนาดเล็ก (\< 200 บรรทัด, \< 10 เมธอด, \< 10 properties)
### 🚨**2.8 การจัดการข้อผิดพลาด (Error Handling)**
- ใช้ Exceptions สำหรับข้อผิดพลาดที่ไม่คาดคิด
- ดักจับ (Catch) ข้อผิดพลาดเพื่อแก้ไขหรือเพิ่มบริบท (context) เท่านั้น; หากไม่เช่นนั้น ให้ใช้ global error handlers
- ระบุข้อความข้อผิดพลาด (error messages) ที่มีความหมายเสมอ
### 🧪**2.9 การทดสอบ (ทั่วไป) (Testing (General))**
- ใช้รูปแบบ **ArrangeActAssert**
- ใช้ชื่อตัวแปรในการทดสอบที่สื่อความหมาย (inputData, expectedOutput)
- เขียน **unit tests** สำหรับ public methods ทั้งหมด
- จำลอง (Mock) การพึ่งพาภายนอก (external dependencies)
- เพิ่ม **acceptance tests** ต่อโมดูลโดยใช้รูปแบบ GivenWhen-Then
### **Testing Strategy โดยละเอียด**
- **Test Pyramid Structure**
/\
/ \ E2E Tests (10%)
/\_**\_\ Integration Tests (20%)
/ \ Unit Tests (70%)
/**\_\_\*\*\*\*\
- **Testing Tools Stack**
```typescript
// Backend Testing Stack
const backendTesting = {
unit: ['Jest', 'ts-jest', '@nestjs/testing'],
integration: ['Supertest', 'Testcontainers', 'Jest'],
e2e: ['Supertest', 'Jest', 'Database Seeds'],
security: ['Jest', 'Custom Security Test Helpers'],
performance: ['Jest', 'autocannon', 'artillery'],
};
// Frontend Testing Stack
const frontendTesting = {
unit: ['Vitest', 'React Testing Library'],
integration: ['React Testing Library', 'MSW'],
e2e: ['Playwright', 'Jest'],
visual: ['Playwright', 'Loki'],
};
```
- **Test Data Management**
```typescript
// Test Data Factories
interface TestDataFactory {
createUser(overrides?: Partial<User>): User;
createCorrespondence(overrides?: Partial<Correspondence>): Correspondence;
createRoutingTemplate(overrides?: Partial<RoutingTemplate>): RoutingTemplate;
}
// Test Scenarios
const testScenarios = {
happyPath: 'Normal workflow execution',
edgeCases: 'Boundary conditions and limits',
errorConditions: 'Error handling and recovery',
security: 'Authentication and authorization',
performance: 'Load and stress conditions',
};
```
## 🏗️ **3. แบ็กเอนด์ (NestJS) - Implementation Details**
### **3.1 หลักการ**
- **สถาปัตยกรรมแบบโมดูลาร์ (Modular architecture)**:
- หนึ่งโมดูลต่อหนึ่งโดเมน
- โครงสร้างแบบ Controller → Service → Repository (Model)
- API-First: มุ่งเน้นการสร้าง API ที่มีคุณภาพสูง มีเอกสารประกอบ (Swagger) ที่ชัดเจนสำหรับ Frontend Team
- DTOs ที่ตรวจสอบความถูกต้องด้วย **class-validator**
- ใช้ **MikroORM** (หรือ TypeORM/Prisma) สำหรับการคงอยู่ของข้อมูล (persistence) ซึ่งสอดคล้องกับสคีมา MariaDB
- ห่อหุ้มโค้ดที่ใช้ซ้ำได้ไว้ใน **common module** (@app/common):
- Configs, decorators, DTOs, guards, interceptors, notifications, shared services, types, validators
### **3.2 Database & Data Modeling (MariaDB + TypeORM)**
#### **3.2.1 Optimistic Locking & Versioning**
เพื่อป้องกัน Race Condition ในการแก้ไขข้อมูลพร้อมกัน (โดยเฉพาะการรันเลขที่เอกสาร) ให้เพิ่ม Column `@VersionColumn()` ใน Entity ที่สำคัญ
```typescript
@Entity()
export class DocumentCounter {
// ... fields
@Column()
last_number: number;
@VersionColumn() // เพิ่ม Versioning
version: number;
}
```
#### **3.2.2 Virtual Columns for JSON Performance**
เนื่องจากเราใช้ MariaDB 10.11 และมีการเก็บข้อมูล JSON (Details) ให้ใช้ **Generated Columns (Virtual)** สำหรับ Field ที่ต้อง Search/Sort บ่อยๆ และทำ Index บน Virtual Column นั้น
```sql
-- ตัวอย่าง SQL Migration
ALTER TABLE correspondence_revisions
ADD COLUMN ref_project_id INT GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(details, '$.projectId'))) VIRTUAL;
CREATE INDEX idx_ref_project_id ON correspondence_revisions(ref_project_id);
```
#### **3.2.3 Partitioning Strategy**
สำหรับตาราง `audit_logs` และ `notifications` ให้เตรียมออกแบบ Entity ให้รองรับ Partitioning (เช่น แยกตามปี) โดยใช้ Raw SQL Migration ในการสร้างตาราง
### **3.3 File Storage Service (Two-Phase Storage)**
ปรับปรุง Service จัดการไฟล์ให้รองรับ Transactional Integrity
1. **Upload (Phase 1):**
- รับไฟล์ → Scan Virus (ClamAV) → Save ลงโฟลเดอร์ `temp/`
- Return `temp_id` และ Metadata กลับไปให้ Client
2. **Commit (Phase 2):**
- เมื่อ Business Logic (เช่น Create Correspondence) ทำงานสำเร็จ
- Service จะย้ายไฟล์จาก `temp/` ไปยัง `permanent/{YYYY}/{MM}/`
- Update path ใน Database
- ทั้งหมดนี้ต้องอยู่ภายใต้ Database Transaction เดียวกัน (ถ้า DB Fail, ไฟล์จะค้างที่ Temp และถูกลบโดย Cron Job)
### **3.4 Document Numbering (Double-Lock Mechanism)**
การออกเลขที่เอกสารต้องใช้กลไกความปลอดภัย 2 ชั้น:
1. **Layer 1 (Redis Lock):** ใช้ `redlock` เพื่อ Block ไม่ให้ Process อื่นเข้ามายุ่งกับ Counter ของ Project/Type นั้นๆ ชั่วคราว
2. **Layer 2 (Optimistic Lock):** ตอน Update Database ให้เช็ค `version` ถ้า version เปลี่ยน (แสดงว่า Redis Lock หลุดหรือมีคนแทรก) ให้ Throw Error และ Retry ใหม่
### **3.5 Unified Workflow Engine**
Unified Workflow Engine (Core Architecture)
- ระบบใช้ Workflow Engine เป็นหัวใจหลักในการขับเคลื่อน State ของเอกสาร:
- DSL Based: Logic ทั้งหมดอยู่ที่ workflow_definitions.dsl
- Instance Based: สถานะปัจจุบันอยู่ที่ workflow_instances
- Module Integration:
- CorrespondenceModule -> เรียก WorkflowEngine
- RfaModule -> เรียก WorkflowEngine
- CirculationModule -> เรียก WorkflowEngine
- ห้าม สร้างตาราง Routing แยก (เช่น rfa_workflows หรือ correspondence_routings) อีกต่อไป
### **3.6 ฟังก์ชันหลัก (Core Functionalities)**
- Global **filters** สำหรับการจัดการ exception
- **Middlewares** สำหรับการจัดการ request
- **Guards** สำหรับการอนุญาต (permissions) และ RBAC
- **Interceptors** สำหรับการแปลงข้อมูล response และการบันทึก log
### **3.7 ข้อจำกัดในการ Deploy (QNAP Container Station)**
- **ห้ามใช้ไฟล์ .env** ในการตั้งค่า Environment Variables [cite: 2.1]
- การตั้งค่าทั้งหมด (เช่น Database connection string, JWT secret) **จะต้องถูกกำหนดผ่าน Environment Variable ใน docker-compose.yml โดยตรง** [cite: 6.5] ซึ่งจะจัดการผ่าน UI ของ QNAP Container Station [cite: 2.1]
### **3.8 ข้อจำกัดด้านความปลอดภัย (Security Constraints):**
- **File Upload Security:** ต้องมี virus scanning (ClamAV), file type validation (white-list), และ file size limits (50MB)
- **Input Validation:** ต้องป้องกัน OWASP Top 10 vulnerabilities (SQL Injection, XSS, CSRF)
- **Rate Limiting:** ต้อง implement rate limiting ตาม strategy ที่กำหนด
- **Secrets Management:** ต้องมี mechanism สำหรับจัดการ sensitive secrets อย่างปลอดภัย แม้จะใช้ docker-compose.yml
### **3.9 โครงสร้างโมดูลตามโดเมน (Domain-Driven Module Structure)**
เพื่อให้สอดคล้องกับสคีมา SQL (LCBP3-DMS) เราจะใช้โครงสร้างโมดูลแบบ **Domain-Driven (แบ่งตามขอบเขตธุรกิจ)** แทนการแบ่งตามฟังก์ชัน:
#### 3.9.1 **CommonModule:**
- เก็บ Services ที่ใช้ร่วมกัน เช่น DatabaseModule, FileStorageService (จัดการไฟล์ใน QNAP), AuditLogService, NotificationService
- จัดการ audit_logs
- NotificationService ต้องรองรับ Triggers ที่ระบุใน Requirement 6.7 [cite: 6.7]
#### 3.9.2 **AuthModule:**
- จัดการะการยืนยันตัวตน (JWT, Guards)
- **(สำคัญ)** ต้องรับผิดชอบการตรวจสอบสิทธิ์ **4 ระดับ** [cite: 4.2]: สิทธิ์ระดับระบบ (Global Role), สิทธิ์ระดับองกรณ์ (Organization Role), สิทธิ์ระดับโปรเจกต์ (Project Role), และ สิทธิ์ระดับสัญญา (Contract Role)
- **(สำคัญ)** ต้องมี API สำหรับ **Admin Panel** เพื่อ:
- สร้างและจัดการ Role และการจับคู่ Permission แบบไดนามิก [cite: 4.3]
- ให้ Superadmin สร้าง Organizations และกำหนด Org Admin ได้ [cite: 4.6]
- ให้ Superadmin/Admin จัดการ document_number_formats (รูปแบบเลขที่เอกสาร), document_number_counters (Running Number) [cite: 3.10]
#### 3.9.3 **UserModule:**
- จัดการ users, roles, permissions, global_default_roles, role_permissions, user_roles, user_project_roles
- **(สำคัญ)** ต้องมี API สำหรับ **Admin Panel** เพื่อ:
- สร้างและจัดการ Role และการจับคู่ Permission แบบไดนามิก [cite: 4.3]
#### 3.9.4 **ProjectModule:**
- จัดการ projects, organizations, contracts, project_parties, contract_parties
#### 3.9.5 **MasterModule:**
- จัดการ master data (correspondence_types, rfa_types, rfa_status_codes, rfa_approve_codes, circulation_status_codes, correspondence_types, correspondence_status, tags) [cite: 4.5]
#### 3.9.6 **CorrespondenceModule (โมดูลศูนย์กลาง):**
- จัดการ correspondences, correspondence_revisions, correspondence_tags
- **(สำคัญ)** Service นี้ต้อง Inject DocumentNumberingService เพื่อขอเลขที่เอกสารใหม่ก่อนการสร้าง
- **(สำคัญ)** ตรรกะการสร้าง/อัปเดต Revision จะอยู่ใน Service นี้
- จัดการ correspondence_attachments (ตารางเชื่อมไฟล์แนบ)
- รับผิดชอบ Routing **Correspondence WorkflowService** เป็น Adapter เชื่อมต่อกับ Engine สำหรับการส่งต่อเอกสารทั่วไประหว่างองค์กร
#### 3.9.7 **RfaModule:**
- จัดการ rfas, rfa_revisions, rfa_items
- รับผิดชอบเวิร์กโฟลว์ **"RFA WorkflowService"** เป็น Adapter เชื่อมต่อกับ Engine สำหรับการอนุมัติเอกสารทางเทคนิค
#### 3.9.8 **DrawingModule:**
- จัดการ shop_drawings, shop_drawing_revisions, contract_drawings, contract_drawing_volumes, contract_drawing_cats, contract_drawing_sub_cats, shop_drawing_main_categories, shop_drawing_sub_categories, contract_drawing_subcat_cat_maps, shop_drawing_revision_contract_refs
- จัดการ shop_drawing_revision_attachments และ contract_drawing_attachments(ตารางเชื่อมไฟล์แนบ)
#### 3.9.9 **CirculationModule:**
- จัดการ circulations, circulation_templates, circulation_assignees
- จัดการ circulation_attachments (ตารางเชื่อมไฟล์แนบ)
- รับผิดชอบเวิร์กโฟลว์ **"Circulations WorkflowService"** เป็น Adapter เชื่อมต่อกับ Engine สำหรับการเวียนเอกสาร **ภายในองค์กร**
#### 3.9.10 **TransmittalModule:**
- จัดการ transmittals และ transmittal_items
#### 3.9.11 **SearchModule:**
- ให้บริการค้นหาขั้นสูง (Advanced Search) [cite: 6.2] โดยใช้ **Elasticsearch** เพื่อรองรับการค้นหาแบบ Full-text จากชื่อเรื่อง, รายละเอียด, เลขที่เอกสาร, ประเภท, วันที่, และ Tags
- ระบบจะใช้ Elasticsearch Engine ในการจัดทำดัชนีเพื่อการค้นหาข้อมูลเชิงลึกจากเนื้อหาของเอกสาร โดยข้อมูลจะถูกส่งไปทำดัชนีจาก Backend (NestJS) ทุกครั้งที่มีการสร้างหรือแก้ไขเอกสาร
#### 3.9.12 **DocumentNumberingModule:**
- **สถานะ:** เป็น Module ภายใน (Internal Module) ไม่เปิด API สู่ภายนอก
- **หน้าที่:** ให้บริการ `DocumentNumberingService` แบบ **Token-Based Generator**
- **Logic ใหม่ (v1.4.4):**
- รับ Context: `{ projectId, orgId, typeId, disciplineId?, subTypeId?, year }`
- ดึง Template จาก DB
- Parse Template เพื่อหาว่าต้องใช้ Key ใดบ้างในการทำ Grouping Counter (เช่น ถ้า Template มี `{DISCIPLINE}` ให้ใช้ `discipline_id` ในการ query counter)
- ใช้ **Double-Lock Mechanism** (Redis + Optimistic DB Lock) ในการดึงและอัพเดทค่า `last_number`
- **Features:**
- Application-level locking เพื่อป้องกัน race condition
- Retry mechanism ด้วย exponential backoff
- Fallback mechanism เมื่อการขอเลขล้มเหลว
- Audit log ทุกครั้งที่มีการ generate เลขที่เอกสารใหม่
#### 3.9.13 **CorrespondenceRoutingModule:**
- **สถานะ:** โมดูลหลักสำหรับจัดการการส่งต่อเอกสาร
- **หน้าที่:** จัดการแม่แบบการส่งต่อและการส่งต่อจริง
- **Entities:**
- CorrespondenceRoutingTemplate
- CorrespondenceRoutingTemplateStep
- CorrespondenceRouting
- **Features:**
- สร้างและจัดการแม่แบบการส่งต่อ
- ดำเนินการส่งต่อเอกสารตามแม่แบบ
- ติดตามสถานะการส่งต่อ
- คำนวณวันครบกำหนดอัตโนมัติ
- ส่งการแจ้งเตือนเมื่อมีการส่งต่อใหม่
#### 3.9.14 WorkflowEngineModule (New Core):
- Entities: WorkflowDefinition, WorkflowInstance, WorkflowHistory
- Services: WorkflowEngineService, WorkflowDslService, WorkflowEventService
- Responsibility: จัดการ State Machine, Validate DSL, Execute Transitions
#### 3.9.15 **JsonSchemaModule:**
- **สถานะ:** Internal Module สำหรับจัดการ JSON schemas
- **หน้าที่:** Validate, transform, และ manage JSON data structures
- **Features:**
- JSON schema validation ด้วย AJV
- Schema versioning และ migration
- Dynamic schema generation
- Data transformation และ sanitization
#### 3.9.16 **DetailsService:**
- **สถานะ:** Shared Service สำหรับจัดการ details fields
- **หน้าที่:** Centralized service สำหรับ JSON details operations
- **Methods:**
- validateDetails(type: string, data: any): ValidationResult
- transformDetails(input: any, targetVersion: string): any
- sanitizeDetails(data: any): any
- getDefaultDetails(type: string): any
### **3.10 สถาปัตยกรรมระบบ (System Architecture)**
โครงสร้างโมดูล (Module Structure) อ้างถึง Backend Development Plan v1.4.5
### **3.11 กลยุทธ์ความทนทานและการจัดการข้อผิดพลาด (Resilience & Error Handling Strategy)**
- **Circuit Breaker Pattern:** ใช้สำหรับ external service calls (Email, LINE, Elasticsearch)
- **Retry Mechanism:** ด้วย exponential backoff สำหรับ transient failures
- **Fallback Strategies:** Graceful degradation เมื่อบริการภายนอกล้มเหลว
- **Error Handling:** Error messages ต้องไม่เปิดเผยข้อมูล sensitive
- **Monitoring:** Centralized error monitoring และ alerting system
### **3.12 FileStorageService (ปรับปรุงใหม่):**
- **Virus Scanning:** Integrate ClamAV สำหรับ scan ไฟล์ที่อัปโหลดทั้งหมด
- **File Type Validation:** ใช้ white-list approach (PDF, DWG, DOCX, XLSX, ZIP)
- **File Size Limits:** 50MB ต่อไฟล์
- **Security Measures:**
- เก็บไฟล์นอก web root
- Download ผ่าน authenticated endpoint เท่านั้น
- Download links มี expiration time (24 ชั่วโมง)
- File integrity checks (checksum)
- Access control checks ก่อนดาวน์โหลด
### **3.13 เเทคโนโลยีที่ใช้ (Technology Stack)**
| ส่วน | Library/Tool | หมายเหตุ |
| ----------------------- | ---------------------------------------------------- | -------------------------------------------- |
| **Framework** | `@nestjs/core`, `@nestjs/common` | Core Framework |
| **Language** | `TypeScript` | ใช้ TypeScript ทั้งระบบ |
| **Database** | `MariaDB 10.11` | ฐานข้อมูลหลัก |
| **ORM** | `@nestjs/typeorm`, `typeorm` | 🗃️จัดการการเชื่อมต่อและ Query ฐานข้อมูล |
| **Validation** | `class-validator`, `class-transformer` | 📦ตรวจสอบและแปลงข้อมูลใน DTO |
| **Auth** | `@nestjs/jwt`, `@nestjs/passport`, `passport-jwt` | 🔐การยืนยันตัวตนด้วย JWT |
| **Authorization** | `casl` | 🔐จัดการสิทธิ์แบบ RBAC |
| **File Upload** | `multer` | 📁จัดการการอัปโหลดไฟล์ |
| **Search** | `@nestjs/elasticsearch` | 🔍สำหรับการค้นหาขั้นสูง |
| **Notification** | `nodemailer` | 📬ส่งอีเมลแจ้งเตือน |
| **Scheduling** | `@nestjs/schedule` | 📬สำหรับ Cron Jobs (เช่น แจ้งเตือน Deadline) |
| **Logging** | `winston` | 📊บันทึก Log ที่มีประสิทธิภาพ |
| **Testing** | `@nestjs/testing`, `jest`, `supertest` | 🧪ทดสอบ Unit, Integration และ E2E |
| **Documentation** | `@nestjs/swagger` | 🌐สร้าง API Documentation อัตโนมัติ |
| **Security** | `helmet`, `rate-limiter-flexible` | 🛡️เพิ่มความปลอดภัยให้ API |
| **Resilience** | `@nestjs/circuit-breaker` | 🔄 Circuit breaker pattern |
| **Caching** | `@nestjs/cache-manager`, `cache-manager-redis-store` | 💾 Distributed caching |
| **Security** | `helmet`, `csurf`, `rate-limiter-flexible` | 🛡️ Security enhancements |
| **Validation** | `class-validator`, `class-transformer` | ✅ Input validation |
| **Monitoring** | `@nestjs/monitoring`, `winston` | 📊 Application monitoring |
| **File Processing** | `clamscan` | 🦠 Virus scanning |
| **Cryptography** | `bcrypt`, `crypto` | 🔐 Password hashing และ checksums |
| **JSON Validation** | `ajv`, `ajv-formats` | 🎯 JSON schema validation |
| **JSON Processing** | `jsonpath`, `json-schema-ref-parser` | 🔧 JSON manipulation |
| **Data Transformation** | `class-transformer` | 🔄 Object transformation |
| **Compression** | `compression` | 📦 JSON compression |
### **3.14 Security Testing:**
- **Penetration Testing:** ทดสอบ OWASP Top 10 vulnerabilities
- **Security Audit:** Review code สำหรับ security flaws
- **Virus Scanning Test:** ทดสอบ file upload security
- **Rate Limiting Test:** ทดสอบ rate limiting functionality
### **3.15 Performance Testing:**
- **Load Testing:** ทดสอบด้วย realistic workloads
- **Stress Testing:** หา breaking points ของระบบ
- **Endurance Testing:** ทดสอบการทำงานต่อเนื่องเป็นเวลานาน
### 🗄️**3.16 Backend State Management**
Backend (NestJS) ควรเป็น **Stateless** (ไม่เก็บสถานะ) "State" ทั้งหมดจะถูกจัดเก็บใน MariaDB
- **Request-Scoped State (สถานะภายใน Request เดียว):**
- **ปัญหา:** จะส่งต่อข้อมูล (เช่น User ที่ล็อกอิน) ระหว่าง Guard และ Service ใน Request เดียวกันได้อย่างไร?
- **วิธีแก้:** ใช้ **Request-Scoped Providers** ของ NestJS (เช่น AuthContextService) เพื่อเก็บข้อมูล User ปัจจุบันที่ได้จาก AuthGuard และให้ Service อื่น Inject ไปใช้
- **Application-Scoped State (การ Caching):**
- **ปัญหา:** ข้อมูล Master (เช่น roles, permissions, organizations) ถูกเรียกใช้บ่อย
- **วิธีแก้:** ใช้ **Caching** (เช่น @nestjs/cache-manager) เพื่อ Caching ข้อมูลเหล่านี้ และลดภาระ Database
### **3.17 Caching Strategy (ตามข้อ 6.4.2):**
- **Master Data Cache:** Roles, Permissions, Organizations (TTL: 1 hour)
- **User Session Cache:** User permissions และ profile (TTL: 30 minutes)
- **Search Result Cache:** Frequently searched queries (TTL: 15 minutes)
- **File Metadata Cache:** Attachment metadata (TTL: 1 hour)
- **Cache Invalidation:** Clear cache on update/delete operations
### **3.18 การไหลของข้อมูล (Data Flow)**
#### **3.18.1 Main Flow:**
1. Request: ผ่าน Nginx Proxy Manager -> NestJS Controller
2. **Rate Limiting:** RateLimitGuard ตรวจสอบ request limits
3. **Input Validation:** Validation Pipe ตรวจสอบและ sanitize inputs
4. Authentication: JWT Guard ตรวจสอบ Token และดึงข้อมูล User
5. Authorization: RBAC Guard ตรวจสอบสิทธิ์
6. **Security Checks:** Virus scanning (สำหรับ file upload), XSS protection
7. Business Logic: Service Layer ประมวลผลตรรกะทางธุรกิจ
8. **Resilience:** Circuit breaker และ retry logic สำหรับ external calls
9. Data Access: Repository Layer ติดต่อกับฐานข้อมูล
10. **Caching:** Cache frequently accessed data
11. **Audit Log:** บันทึกการกระทำสำคัญ
12. Response: ส่งกลับไปยัง Frontend
#### **3.18.2 Workflow Data Flow:**
1. User สร้างเอกสาร → เลือก routing template
2. System สร้าง routing instances ตาม template
3. สำหรับแต่ละ routing step:
- กำหนด due date (จาก expected_days)
- ส่ง notification ไปยังองค์กรผู้รับ
- อัพเดทสถานะเป็น SENT
4. เมื่อองค์กรผู้รับดำเนินการ:
- อัพเดทสถานะเป็น ACTIONED/FORWARDED/REPLIED
- บันทึก processed_by และ processed_at
- ส่ง notification ไปยังขั้นตอนต่อไป (ถ้ามี)
5. เมื่อครบทุกขั้นตอน → อัพเดทสถานะเอกสารเป็น COMPLETED
#### **3.18.3 JSON Details Processing Flow:**
1. **Receive Request** → Get JSON data from client
2. **Schema Validation** → Validate against predefined schema
3. **Data Sanitization** → Sanitize and transform data
4. **Version Check** → Handle schema version compatibility
5. **Storage** → Store validated JSON in database
6. **Retrieval** → Retrieve and transform on demand
### 📊**3.19 Monitoring & Observability (ตามข้อ 6.8)**
#### **Application Monitoring:**
- **Health Checks:** `/health` endpoint สำหรับ load balancer
- **Metrics Collection:** Response times, error rates, throughput
- **Distributed Tracing:** สำหรับ request tracing across services
- **Log Aggregation:** Structured logging ด้วย JSON format
- **Alerting:** สำหรับ critical errors และ performance degradation
#### **Business Metrics:**
- จำนวน documents created ต่อวัน
- Workflow completion rates
- User activity metrics
- System utilization rates
- Search query performance
#### **Performance Targets:**
- API Response Time: < 200ms (90th percentile)
- Search Query Performance: < 500ms
- File Upload Performance: < 30 seconds สำหรับไฟล์ 50MB
- Cache Hit Ratio: > 80%
## 🖥️ **4. ฟรอนต์เอนด์ (Next.js) - Implementation Details**
**โปรไฟล์นักพัฒนา (Developer Profile:** วิศวกร TypeScript + React/NextJS ระดับ Senior
เชี่ยวชาญ TailwindCSS, Shadcn/UI, และ Radix สำหรับการพัฒนา UI
### **4.1 State Management & Offline Support**
#### **4.1.1 Auto-Save Drafts**
ใช้ `zustand` ร่วมกับ middleware `persist` (ลง LocalStorage) สำหรับฟอร์มที่มีขนาดใหญ่ (RFA, Correspondence) เพื่อป้องกันข้อมูลหายเมื่อเน็ตหลุด
```typescript
// lib/stores/draft-store.ts
export const useDraftStore = create(
persist(
(set) => ({
drafts: {},
saveDraft: (key, data) =>
set((state) => ({ drafts: { ...state.drafts, [key]: data } })),
clearDraft: (key) =>
set((state) => {
const newDrafts = { ...state.drafts };
delete newDrafts[key];
return { drafts: newDrafts };
}),
}),
{ name: 'form-drafts' }
)
);
```
### **4.2 Dynamic Form Generator**
เพื่อรองรับ JSON Schema หลากหลายรูปแบบ ให้สร้าง Component กลางที่รับ Schema แล้ว Gen Form ออกมา (ลดการแก้ Code บ่อยๆ)
- **Libraries:** แนะนำ `react-jsonschema-form` หรือสร้าง Wrapper บน `react-hook-form` ที่ Recursively render field ตาม Type
- **Validation:** ใช้ `ajv` ที่ฝั่ง Client เพื่อ Validate JSON ก่อน Submit
### **4.3 Mobile Responsiveness (Card View)**
ตารางข้อมูล (`DataTable`) ต้องมีความฉลาดในการแสดงผล:
- **Desktop:** แสดงเป็น Table ปกติ
- **Mobile:** แปลงเป็น **Card View** โดยอัตโนมัติ (ซ่อน Header, แสดง Label คู่ Value ในแต่ละ Card)
```tsx
// components/ui/responsive-table.tsx
<div className="hidden md:block">
<Table>{/* Desktop View */}</Table>
</div>
<div className="md:hidden space-y-4">
{data.map((item) => (
<Card key={item.id}>
{/* Mobile View: Render cells as list items */}
</Card>
))}
</div>
```
### **4.4 Optimistic Updates**
ใช้ความสามารถของ **TanStack Query** (`onMutate`) เพื่ออัปเดต UI ทันที (เช่น เปลี่ยนสถานะจาก "รออ่าน" เป็น "อ่านแล้ว") แล้วค่อยส่ง Request ไป Server ถ้า Failed ค่อย Rollback
### **4.5 แนวทางการพัฒนาโค้ด (Code Implementation Guidelines)**
- ใช้ **early returns** เพื่อความชัดเจน
- ใช้คลาสของ **TailwindCSS** ในการกำหนดสไตล์เสมอ
- ควรใช้ class: syntax แบบมีเงื่อนไข (หรือ utility clsx) มากกว่าการใช้ ternary operators ใน class strings
- ใช้ **const arrow functions** สำหรับ components และ handlers
- Event handlers ให้ขึ้นต้นด้วย handle... (เช่น handleClick, handleSubmit)
- รวมแอตทริบิวต์สำหรับการเข้าถึง (accessibility) ด้วย:
tabIndex="0", aria-label, onKeyDown, ฯลฯ
- ตรวจสอบให้แน่ใจว่าโค้ดทั้งหมด **สมบูรณ์**, **ผ่านการทดสอบ**, และ **ไม่ซ้ำซ้อน (DRY)**
- ต้อง import โมดูลที่จำเป็นต้องใช้อย่างชัดเจนเสมอ
### **4.6 UI/UX ด้วย React**
- ใช้ **semantic HTML**
- ใช้คลาสของ **Tailwind** ที่รองรับ responsive (sm:, md:, lg:)
- รักษาลำดับชั้นของการมองเห็น (visual hierarchy) ด้วยการใช้ typography และ spacing
- ใช้ **Shadcn** components (Button, Input, Card, ฯลฯ) เพื่อ UI ที่สอดคล้องกัน
- ทำให้ components มีขนาดเล็กและมุ่งเน้นการทำงานเฉพาะอย่าง
- ใช้ utility classes สำหรับการจัดสไตล์อย่างรวดเร็ว (spacing, colors, text, ฯลฯ)
- ตรวจสอบให้แน่ใจว่าสอดคล้องกับ **ARIA** และใช้ semantic markup
### **4.7 การตรวจสอบฟอร์มและข้อผิดพลาด (Form Validation & Errors)**
- ใช้ไลบรารีฝั่ง client เช่น zod และ react-hook-form
- แสดงข้อผิดพลาดด้วย **alert components** หรือข้อความ inline
- ต้องมี labels, placeholders, และข้อความ feedback
### **🧪4.8 Frontend Testing**
เราจะใช้ **React Testing Library (RTL)** สำหรับการทดสอบ Component และ **Playwright** สำหรับ E2E:
- **Unit Tests (การทดสอบหน่วยย่อย):**
- **เครื่องมือ:** Vitest + RTL
- **เป้าหมาย:** ทดสอบ Component ขนาดเล็ก (เช่น Buttons, Inputs) หรือ Utility functions
- **Integration Tests (การทดสอบการบูรณาการ):**
- **เครื่องมือ:** RTL + **Mock Service Worker (MSW)**
- **เป้าหมาย:** ทดสอบว่า Component หรือ Page ทำงานกับ API (ที่จำลองขึ้น) ได้ถูกต้อง
- **เทคนิค:** ใช้ MSW เพื่อจำลอง NestJS API และทดสอบว่า Component แสดงผลข้อมูลจำลองได้ถูกต้องหรือไม่ (เช่น ทดสอบหน้า Dashboard [cite: 5.3] ที่ดึงข้อมูลจาก v_user_tasks)
- **E2E (End-to-End) Tests:**
- **เครื่องมือ:** **Playwright**
- **เป้าหมาย:** ทดสอบ User Flow ทั้งระบบโดยอัตโนมัติ (เช่น ล็อกอิน -> สร้าง RFA -> ตรวจสอบ Workflow Visualization [cite: 5.6])
### **🗄4.9 Frontend State Management**
สำหรับ Next.js App Router เราจะแบ่ง State เป็น 4 ระดับ:
1. **Local UI State (สถานะ UI ชั่วคราว):**
- **เครื่องมือ:** useState, useReducer
- **ใช้เมื่อ:** จัดการสถานะเล็กๆ ที่จบใน Component เดียว (เช่น Modal เปิด/ปิด, ค่าใน Input)
2. **Server State (สถานะข้อมูลจากเซิร์ฟเวอร์):**
- **เครื่องมือ:** **React Query (TanStack Query)** หรือ SWR
- **ใช้เมื่อ:** จัดการข้อมูลที่ดึงมาจาก NestJS API (เช่น รายการ correspondences, rfas, drawings)
- **ทำไม:** React Query เป็น "Cache" ที่จัดการ Caching, Re-fetching, และ Invalidation ให้โดยอัตโนมัติ
3. **Global Client State (สถานะส่วนกลางฝั่ง Client):**
- **เครื่องมือ:** **Zustand** (แนะนำ) หรือ Context API
- **ใช้เมื่อ:** จัดการข้อมูลที่ต้องใช้ร่วมกันทั่วทั้งแอป และ _ไม่ใช่_ ข้อมูลจากเซิร์ฟเวอร์ (เช่น ข้อมูล User ที่ล็อกอิน, สิทธิ์ Permissions)
4. **Form State (สถานะของฟอร์ม):**
- **เครื่องมือ:** **React Hook Form** + **Zod**
- **ใช้เมื่อ:** จัดการฟอร์มที่ซับซ้อน (เช่น ฟอร์มสร้าง RFA, ฟอร์ม Circulation [cite: 3.7])
## 🔐 **5. Security & Access Control (Full Stack Integration)**
### **5.1 CASL Integration (Shared Ability)**
- **Backend:** ใช้ CASL กำหนด Permission Rule
- **Frontend:** ให้ดึง Rule (JSON) จาก Backend มา Load ใส่ `@casl/react` เพื่อให้ Logic การ Show/Hide ปุ่ม ตรงกัน 100%
### **5.2 Maintenance Mode**
เพิ่ม Middleware (ทั้ง NestJS และ Next.js) เพื่อตรวจสอบ Flag ใน Redis:
- ถ้า `MAINTENANCE_MODE = true`
- **API:** Return `503 Service Unavailable` (ยกเว้น Admin IP)
- **Frontend:** Redirect ไปหน้า `/maintenance`
### **5.3 Idempotency Client**
สร้าง Axios Interceptor เพื่อ Generate `Idempotency-Key` สำหรับ POST/PUT/DELETE requests ทุกครั้ง
```typescript
// lib/api/client.ts
import { v4 as uuidv4 } from 'uuid';
apiClient.interceptors.request.use((config) => {
if (['post', 'put', 'delete'].includes(config.method)) {
config.headers['Idempotency-Key'] = uuidv4();
}
return config;
});
```
### **5.4 RBAC และการควบคุมสิทธิ์ (RBAC & Permission Control)**
ใช้ Decorators เพื่อบังคับใช้สิทธิ์การเข้าถึง โดยอ้างอิงสิทธิ์จากตาราง permissions
```typescript
@RequirePermission('rfas.respond') // ต้องตรงกับ 'permission_code'
@Put(':id')
updateRFA(@Param('id') id: string) {
return this.rfaService.update(id);
}
```
#### **5.4.1 Roles (บทบาท)**
- **Superadmin**: ไม่มีข้อจำกัดใดๆ [cite: 4.3]
- **Admin**: มีสิทธิ์เต็มที่ในองค์กร [cite: 4.3]
- **Document Control**: เพิ่ม/แก้ไข/ลบ เอกสารในองค์กร [cite: 4.3]
- **Editor**: สามารถ เพิ่ม/แก้ไข เอกสารที่กำหนด [cite: 4.3]
- **Viewer**: สามารถดู เอกสาร [cite: 4.3]
#### **5.4.2 ตัวอย่าง Permissions (จากตาราง permissions)**
- rfas.view, rfas.create, rfas.respond, rfas.delete
- drawings.view, drawings.upload, drawings.delete
- corr.view, corr.manage
- transmittals.manage
- cirs.manage
- project_parties.manage
การจับคู่ระหว่าง roles และ permissions **เริ่มต้น** จะถูก seed ผ่านสคริปต์ (ดังที่เห็นในไฟล์ SQL)**อย่างไรก็ตาม AuthModule/UserModule ต้องมี API สำหรับ Admin เพื่อสร้าง Role ใหม่และกำหนดสิทธิ์ (Permissions) เพิ่มเติมได้ในภายหลัง** [cite: 4.3]
## 📊 **6. Notification & Background Jobs**
### **6.1 Digest Notification**
ห้ามส่ง Email ทันทีที่เกิด Event ให้:
1. Push Event ลง Queue (Redis/BullMQ)
2. มี Processor รอเวลา (เช่น 5 นาที) เพื่อ Group Events ที่คล้ายกัน (เช่น "คุณมีเอกสารรออนุมัติ 5 ฉบับ")
3. ส่ง Email เดียว (Digest) เพื่อลด Spam
## 🔗 **7. แนวทางการบูรณาการ Full Stack (Full Stack Integration Guidelines)**
| Aspect (แง่มุม) | Backend (NestJS) | Frontend (NextJS) | UI Layer (Tailwind/Shadcn) |
| :------------------------- | :------------------------- | :----------------------------- | :------------------------------------- |
| API | REST / GraphQL Controllers | API hooks ผ่าน fetch/axios/SWR | Components ที่รับข้อมูล |
| Validation (การตรวจสอบ) | class-validator DTOs | zod / react-hook-form | สถานะของฟอร์ม/input ใน Shadcn |
| Auth (การยืนยันตัวตน) | Guards, JWT | NextAuth / cookies | สถานะ UI ของ Auth (loading, signed in) |
| Errors (ข้อผิดพลาด) | Global filters | Toasts / modals | Alerts / ข้อความ feedback |
| Testing (การทดสอบ) | Jest (unit/e2e) | Vitest / Playwright | Visual regression |
| Styles (สไตล์) | Scoped modules (ถ้าจำเป็น) | Tailwind / Shadcn | Tailwind utilities |
| Accessibility (การเข้าถึง) | Guards + filters | ARIA attributes | Semantic HTML |
## 🗂️ **8. ข้อตกลงเฉพาะสำหรับ DMS (LCBP3-DMS)**
ส่วนนี้ขยายแนวทาง FullStackJS ทั่วไปสำหรับโปรเจกต์ **LCBP3-DMS** โดยมุ่งเน้นไปที่เวิร์กโฟลว์การอนุมัติเอกสาร (Correspondence, RFA, Drawing, Contract, Transmittal, Circulation)
### 🧾**8.1 มาตรฐาน AuditLog (AuditLog Standard)**
บันทึกการดำเนินการ CRUD และการจับคู่ทั้งหมดลงในตาราง audit_logs
| Field (ฟิลด์) | Type (จาก SQL) | Description (คำอธิบาย) |
| :------------ | :------------- | :----------------------------------------------- |
| audit_id | BIGINT | Primary Key |
| user_id | INT | ผู้ใช้ที่ดำเนินการ (FK -> users) |
| action | VARCHAR(100) | rfa.create, correspondence.update, login.success |
| entity_type | VARCHAR(50) | ชื่อตาราง/โมดูล เช่น 'rfa', 'correspondence' |
| entity_id | VARCHAR(50) | Primary ID ของระเบียนที่ได้รับผลกระทบ |
| details_json | JSON | ข้อมูลบริบท (เช่น ฟิลด์ที่มีการเปลี่ยนแปลง) |
| ip_address | VARCHAR(45) | IP address ของผู้ดำเนินการ |
| user_agent | VARCHAR(255) | User Agent ของผู้ดำเนินการ |
| created_at | TIMESTAMP | Timestamp (UTC) |
### 📂**8.2 การจัดการไฟล์ (File Handling)**
#### **8.2.1 มาตรฐานการอัปโหลดไฟล์ (File Upload Standard)**
- **Security-First Approach:** การอัปโหลดไฟล์ทั้งหมดจะถูกจัดการโดย FileStorageService ที่มี security measures ครบถ้วน
- ไฟล์จะถูกเชื่อมโยงไปยัง Entity ที่ถูกต้องผ่าน **ตารางเชื่อม (Junction Tables)** เท่านั้น:
- correspondence_attachments (เชื่อม Correspondence กับ Attachments)
- circulation_attachments (เชื่อม Circulation กับ Attachments)
- shop_drawing_revision_attachments (เชื่อม Shop Drawing Revision กับ Attachments)
- contract_drawing_attachments (เชื่อม Contract Drawing กับ Attachments)
- เส้นทางจัดเก็บไฟล์ (Upload path): อ้างอิงจาก Requirement 2.1 คือ /share/dms-data [cite: 2.1] โดย FileStorageService จะสร้างโฟลเดอร์ย่อยแบบรวมศูนย์ (เช่น /share/dms-data/uploads/{YYYY}/{MM}/[stored_filename])
- ประเภทไฟล์ที่อนุญาต: pdf, dwg, docx, xlsx, zip (ผ่าน white-list validation)
- ขนาดสูงสุด: **50 MB**
- จัดเก็บนอก webroot
- ให้บริการไฟล์ผ่าน endpoint ที่ปลอดภัย /files/:attachment_id/download
#### **8.2.2 Security Controls สำหรับ File Access:**
การเข้าถึงไฟล์ไม่ใช่การเข้าถึงโดยตรง endpoint /files/:attachment_id/download จะต้อง:
1. ค้นหาระเบียน attachment
2. ตรวจสอบว่า attachment_id นี้ เชื่อมโยงกับ Entity ใด (เช่น correspondence, circulation, shop_drawing_revision, contract_drawing) ผ่านตารางเชื่อม
3. ตรวจสอบว่าผู้ใช้มีสิทธิ์ (permission) ในการดู Entity ต้นทางนั้นๆ หรือไม่
4. ตรวจสอบ download token expiration (24 ชั่วโมง)
5. บันทึก audit log การดาวน์โหลด
### 🔟**8.3 การจัดการเลขที่เอกสาร (Document Numbering) [cite: 3.10]**
- **เป้าหมาย:** สร้างเลขที่เอกสาร (เช่น correspondence_number) โดยอัตโนมัติ ตามรูปแบบที่กำหนด
- **ตรรกะการนับ:** การนับ Running number (SEQ) จะนับแยกตาม Key: **Project + Originator Organization + Document Type + Year**
- **ตาราง SQL (Updated):**
- `document_number_formats`: เก็บ Template String (เช่น `{CONTRACT}-{TYPE}-{DISCIPLINE}-{SEQ:4}`)
- `document_number_counters`: **Primary Key เปลี่ยนเป็น Composite Key ใหม่:** `(project_id, originator_id, type_id, discipline_id, current_year)` เพื่อรองรับการรันเลขแยกตามสาขา
- **การทำงาน:**
- Service ต้องรองรับการ Resolve Token พิเศษ เช่น `{SUBTYPE_NUM}` ที่ต้องไป Join กับตาราง `correspondence_sub_types`
- DocumentNumberingModule จะให้บริการ DocumentNumberingService
- เมื่อ CorrespondenceModule ต้องการสร้างเอกสารใหม่, มันจะเรียก documentNumberingService.generateNextNumber(...)
- Service นี้จะใช้ **Redis distributed locking** แทน stored procedure ซึ่งจะจัดการ Database Transaction และ Row Locking ภายใน Application Layer เพื่อรับประกันการป้องกัน Race Condition
- มี retry mechanism และ fallback strategies
### 📊**8.4 การรายงานและการส่งออก (Reporting & Exports)**
#### **8.4.1 วิวสำหรับการรายงาน (Reporting Views) (จาก SQL)**
การรายงานควรสร้างขึ้นจาก Views ที่กำหนดไว้ล่วงหน้าในฐานข้อมูลเป็นหลัก:
- v_current_correspondences: สำหรับ revision ปัจจุบันทั้งหมดของเอกสารที่ไม่ใช่ RFA
- v_current_rfas: สำหรับ revision ปัจจุบันทั้งหมดของ RFA และข้อมูล master
- v_contract_parties_all: สำหรับการตรวจสอบความสัมพันธ์ของ project/contract/organization
- v_user_tasks: สำหรับ Dashboard "งานของฉัน"
- v_audit_log_details: สำหรับ Activity Feed
Views เหล่านี้ทำหน้าที่เป็นแหล่งข้อมูลหลักสำหรับการรายงานฝั่งเซิร์ฟเวอร์และการส่งออกข้อมูล
#### **8.4.2 กฎการส่งออก (Export Rules)**
- Export formats: CSV, Excel, PDF.
- จัดเตรียมมุมมองสำหรับพิมพ์ (Print view).
- รวมลิงก์ไปยังต้นทาง (เช่น /rfas/:id).
## 🧮 **9. ฟรอนต์เอนด์: รูปแบบ DataTable และฟอร์ม (Frontend: DataTable & Form Patterns)**
### **9.1 DataTable (ServerSide)**
- Endpoint: /api/{module}?page=1&pageSize=20&sort=...&filter=...
- ต้องรองรับ: การแบ่งหน้า (pagination), การเรียงลำดับ (sorting), การค้นหา (search), การกรอง (filters)
- แสดง revision ล่าสุดแบบ inline เสมอ (สำหรับ RFA/Drawing)
### **9.2 มาตรฐานฟอร์ม (Form Standards)**
- ต้องมีการใช้งาน Dropdowns แบบขึ้นต่อกัน (Dependent dropdowns) (ตามที่สคีมารองรับ):
- Project → Contract Drawing Volumes
- Contract Drawing Category → Sub-Category
- RFA (ประเภท Shop Drawing) → Shop Drawing Revisions ที่เชื่อมโยงได้
- **File Upload Security:** ต้องรองรับ **Multi-file upload (Drag-and-Drop)** [cite: 5.7] พร้อม virus scanning feedback
- **File Type Indicators:** UI ต้องอนุญาตให้ผู้ใช้กำหนดว่าไฟล์ใดเป็น **"เอกสารหลัก"** หรือ "เอกสารแนบประกอบ" [cite: 5.7] พร้อมแสดง file type icons
- **Security Feedback:** แสดง security warnings สำหรับ file types ที่เสี่ยงหรือ files ที่ fail virus scan
- ส่ง (Submit) ผ่าน API พร้อม feedback แบบ toast
### **9.3 ข้อกำหนด Component เฉพาะ (Specific UI Requirements)**
- **Dashboard - My Tasks:** ต้องพัฒนา Component ตาราง "งานของฉัน" (My Tasks)ซึ่งดึงข้อมูลงานที่ผู้ใช้ล็อกอินอยู่ต้องรับผิดชอบ (Main/Action) จาก v_user_tasks [cite: 5.3]
- **Workflow Visualization:** ต้องพัฒนา Component สำหรับแสดงผล Workflow (โดยเฉพาะ RFA)ที่แสดงขั้นตอนทั้งหมดเป็นลำดับ โดยขั้นตอนปัจจุบัน (active) เท่านั้นที่ดำเนินการได้ และขั้นตอนอื่นเป็น disabled [cite: 5.6] ต้องมีตรรกะสำหรับ Admin ในการ override หรือย้อนกลับขั้นตอนได้ [cite: 5.6]
- **Admin Panel:** ต้องมีหน้า UI สำหรับ Superadmin/Admin เพื่อจัดการข้อมูลหลัก (Master Data [cite: 4.5]), การเริ่มต้นใช้งาน (Onboarding [cite: 4.6]), และ **รูปแบบเลขที่เอกสาร (Numbering Formats [cite: 3.10])**
- **Security Dashboard:** แสดง security metrics และ audit logs สำหรับ administrators
## 🧭 **10. แดชบอร์ดและฟีดกิจกรรม (Dashboard & Activity Feed)**
### **10.1 การ์ดบนแดชบอร์ด (Dashboard Cards)**
- แสดง Correspondences, RFAs, Circulations, Shop Drawing Revision ล่าสุด
- รวมสรุป KPI (เช่น "RFAs ที่รอการอนุมัติ", "Shop Drawing ที่รอการอนุมัติ") [cite: 5.3]
- รวมลิงก์ด่วนไปยังโมดูลต่างๆ
- **Security Metrics:** แสดงจำนวน files scanned, security incidents, failed login attempts
### **10.2 ฟีดกิจกรรม (Activity Feed)**
- แสดงรายการ v_audit_log_details ล่าสุด (10 รายการ) ที่เกี่ยวข้องกับผู้ใช้
- รวม security-related activities (failed logins, permission changes)
```typescript
// ตัวอย่าง API response
[
{
user: 'editor01',
action: 'Updated RFA (LCBP3-RFA-001)',
time: '2025-11-04T09:30Z',
},
{
user: 'system',
action: 'Virus scan completed - 0 threats found',
time: '2025-11-04T09:25Z',
},
];
```
## 🛡️ **11. ข้อกำหนดที่ไม่ใช่ฟังก์ชันการทำงาน (Non-Functional Requirements)**
ส่วนนี้สรุปข้อกำหนด Non-Functional จาก requirements.md เพื่อให้ทีมพัฒนาทาน
- **Audit Log [cite: 6.1]:** ทุกการกระทำที่สำคัญ (C/U/D) ต้องถูกบันทึกใน audit_logs
- **Performance [cite: 6.4]:** ต้องใช้ Caching สำหรับข้อมูลที่เรียกบ่อย และใช้ Pagination
- **Security [cite: 6.5]:** ต้องมี Rate Limiting และจัดการ Secret ผ่าน docker-compose.yml (ไม่ใช่ .env)
- **File Security [cite: 3.9.6]:** ต้องมี virus scanning, file type validation, access controls
- **Resilience [cite: 6.5.3]:** ต้องมี circuit breaker, retry mechanisms, graceful degradation
- **Backup & Recovery [cite: 6.6]:** ต้องมีแผนสำรองข้อมูลทั้ง Database (MariaDB) และ File Storage (/share/dms-data) อย่างน้อยวันละ 1 ครั้ง
- **Notification Strategy [cite: 6.7]:** ระบบแจ้งเตือน (Email/Line) ต้องถูก Trigger เมื่อมีเอกสารใหม่ส่งถึง, มีการมอบหมายงานใหม่ (Circulation), หรือ (ทางเลือก) เมื่องานเสร็จ/ใกล้ถึงกำหนด
- **Monitoring [cite: 6.8]:** ต้องมี health checks, metrics collection, alerting
## ✅ **12. มาตรฐานที่นำไปใช้แล้ว (จาก SQL v1.4.0) (Implemented Standards (from SQL v1.4.0))**
ส่วนนี้ยืนยันว่าแนวทางปฏิบัติที่ดีที่สุดต่อไปนี้เป็นส่วนหนึ่งของการออกแบบฐานข้อมูลอยู่แล้ว และควรถูกนำไปใช้ประโยชน์ ไม่ใช่สร้างขึ้นใหม่
-**Soft Delete:** นำไปใช้แล้วผ่านคอลัมน์ deleted_at ในตารางสำคัญ (เช่น correspondences, rfas, project_parties) ตรรกะการดึงข้อมูลต้องกรอง deleted_at IS NULL
-**Database Indexes:** สคีมาได้มีการทำ index ไว้อย่างหนักหน่วงบน foreign keys และคอลัมน์ที่ใช้ค้นหาบ่อย (เช่น idx_rr_rfa, idx_cor_project, idx_cr_is_current) เพื่อประสิทธิภาพ
-**โครงสร้าง RBAC:** มีระบบ users, roles, permissions, user_roles, และ user_project_roles ที่ครอบคลุมอยู่แล้ว
-**Data Seeding:** ข้อมูล Master (roles, permissions, organization_roles, initial users, project parties) ถูกรวมอยู่ในสคริปต์สคีมาแล้ว
-**Application-level Locking:** ใช้ Redis distributed lock แทน stored procedure
-**File Security:** Virus scanning, file type validation, access control
-**Resilience Patterns:** Circuit breaker, retry, fallback mechanisms
-**Security Measures:** Input validation, rate limiting, security headers
-**Monitoring:** Health checks, metrics collection, distributed tracing
## 🧩 **13. การปรับปรุงที่แนะนำ (สำหรับอนาคต) (Recommended Enhancements (Future))**
- ✅ สร้าง Background job (โดยใช้ **n8n** เพื่อเชื่อมต่อกับ **Line** [cite: 2.7] และ/หรือใช้สำหรับการแจ้งเตือน RFA ที่ใกล้ถึงกำหนด due_date [cite: 6.7])
- ✅ เพิ่ม job ล้างข้อมูลเป็นระยะสำหรับ attachments ที่ไม่ถูกเชื่อมโยงกับ Entity ใดๆ เลย (ไฟล์กำพร้า)
- 🔄 **AI-Powered Document Classification:** ใช้ machine learning สำหรับ automatic document categorization
- 🔄 **Advanced Analytics:** Predictive analytics สำหรับ workflow optimization
- 🔄 **Mobile App:** Native mobile application สำหรับ field workers
- 🔄 **Blockchain Integration:** สำหรับ document integrity verification ที่ต้องการความปลอดภัยสูงสุด
## ✅ **14. Summary Checklist for Developers**
ก่อนส่ง PR (Pull Request) นักพัฒนาต้องตรวจสอบหัวข้อต่อไปนี้:
- [ ] **Security:** ไม่มี Secrets ใน Code, ใช้ `docker-compose.override.yml` แล้ว
- [ ] **Concurrency:** ใช้ Optimistic Lock ใน Entity ที่เสี่ยง Race Condition แล้ว
- [ ] **Idempotency:** API รองรับ Idempotency Key แล้ว
- [ ] **File Upload:** ใช้ Flow Two-Phase (Temp -> Perm) แล้ว
- [ ] **Mobile:** หน้าจอแสดงผลแบบ Card View บนมือถือได้ถูกต้อง
- [ ] **Performance:** สร้าง Index สำหรับ JSON Virtual Columns แล้ว (ถ้ามี)
---
## 📋 **15. Summary of Key Changes from Previous Version**
### **Security Enhancements:**
1. **File Upload Security** - Virus scanning, file type validation, access controls
2. **Input Validation** - OWASP Top 10 protection, XSS/CSRF prevention
3. **Rate Limiting** - Comprehensive rate limiting strategy
4. **Secrets Management** - Secure handling of sensitive configuration
### **Architecture Improvements:**
1. **Document Numbering** - Changed from Stored Procedure to Application-level Locking
2. **Resilience Patterns** - Circuit breaker, retry mechanisms, fallback strategies
3. **Monitoring & Observability** - Health checks, metrics, distributed tracing
4. **Caching Strategy** - Comprehensive caching with proper invalidation
### **Performance Targets :**
1. **API Response Time** - < 200ms (90th percentile)
2. **Search Performance** - < 500ms
3. **File Upload** - < 30 seconds for 50MB files
4. **Cache Hit Ratio** - > 80%
### **Operational Excellence:**
1. **Disaster Recovery** - RTO < 4 hours, RPO < 1 hour
2. **Backup Procedures** - Comprehensive backup and restoration
3. **Security Testing** - Penetration testing and security audits
4. **Performance Testing** - Load testing with realistic workloads
เอกสารนี้สะท้อนถึงความมุ่งมั่นในการสร้างระบบที่มีความปลอดภัย, มีความทนทาน, และมีประสิทธิภาพสูง พร้อมรองรับการเติบโตในอนาคตและความต้องการทางธุรกิจที่เปลี่ยนแปลงไป
**หมายเหตุ:** แนวทางนี้จะถูกทบทวนและปรับปรุงเป็นระยะตาม feedback จากทีมพัฒนาและความต้องการทางธุรกิจที่เปลี่ยนแปลงไป
## **Document Control:**
- **Document:** FullStackJS v1.4.5
- **Version:** 1.4
- **Date:** 2025-11-29
- **Author:** NAP LCBP3-DMS & Gemini
- **Status:** FINAL-Rev.05
- **Classification:** Internal Technical Documentation
- **Approved By:** Nattanin
---
`End of FullStackJS Guidelines v1.4.5`

View File

@@ -1,16 +0,0 @@
-- File: database/migrations/phase_6a_workflow_dsl.sql
-- ตารางสำหรับเก็บนิยามของ Workflow (Workflow Definition)
CREATE TABLE workflow_definitions (
id CHAR(36) NOT NULL PRIMARY KEY COMMENT 'UUID ของ Workflow Definition',
workflow_code VARCHAR(50) NOT NULL COMMENT 'รหัส Workflow เช่น RFA, CORR, CIRCULATION',
version INT NOT NULL DEFAULT 1 COMMENT 'หมายเลข Version',
dsl JSON NOT NULL COMMENT 'นิยาม Workflow ต้นฉบับ (YAML/JSON Format)',
compiled JSON NOT NULL COMMENT 'โครงสร้าง Execution Tree ที่ Compile แล้ว',
is_active BOOLEAN DEFAULT TRUE COMMENT 'สถานะการใช้งาน',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'วันที่แก้ไขล่าสุด',
-- ป้องกันการมี Workflow Code และ Version ซ้ำกัน
UNIQUE KEY uq_workflow_version (workflow_code, version)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'ตารางเก็บนิยามกฎการเดินเอกสาร (Workflow DSL)';
-- สร้าง Index สำหรับการค้นหา Workflow ที่ Active ล่าสุดได้เร็วขึ้น
CREATE INDEX idx_workflow_active ON workflow_definitions(workflow_code, is_active, version);

2137
2_Backend_Plan_V1_4_5.md Normal file

File diff suppressed because it is too large Load Diff

978
3_Frontend_Plan_V1_4_5.md Normal file
View File

@@ -0,0 +1,978 @@
# 📋 **แผนการพัฒนา Frontend (Next.js) - LCBP3-DMS v1.4.5**
**สถานะ:** FINAL GUIDELINE Rev.05
**วันที่:** 2025-11-29
**อ้างอิง:** Requirements v1.4.5 & FullStackJS Guidelines v1.4.5
**Classification:** Internal Technical Documentation
## 🎯 **ภาพรวมโครงการ**
พัฒนา Frontend สำหรับระบบบริหารจัดการเอกสารโครงการ (Document Management System) ที่มีความทันสมัย รองรับการทำงานบนอุปกรณ์ต่างๆ ได้อย่างสมบูรณ์ มีประสบการณ์ผู้ใช้ที่ราบรื่น และรองรับการทำงานแบบ Offline เบื้องต้น
---
## 📐 **สถาปัตยกรรมระบบ**
### **Technology Stack**
- **Framework:** Next.js 14+ (App Router, React 18, TypeScript, ESM)
- **Styling:** Tailwind CSS + PostCSS
- **UI Components:** shadcn/ui + Radix UI Primitives
- **State Management:**
- **Server State:** TanStack Query (React Query)
- **Client State:** Zustand
- **Form State:** React Hook Form + Zod
- **API Client:** Axios (พร้อม Idempotency Interceptor)
- **Authentication:** NextAuth.js (รองรับ JWT)
- **File Upload:** Custom Hook + Drag & Drop
- **Testing:**
- **Unit/Integration:** Vitest + React Testing Library
- **E2E:** Playwright
- **Mocking:** MSW (Mock Service Worker)
- **Development:**
- **Package Manager:** pnpm
- **Linting:** ESLint + Prettier
- **Type Checking:** TypeScript Strict Mode
### **โครงสร้างโปรเจกต์**
```
📁frontend
├── .env.local
├── .eslintrc.json
├── .gitignore
├── components.json
├── middleware.ts
├── next-env.d.ts
├── next.config.mjs
├── package.json
├── pnpm-lock.yaml
├── postcss.config.mjs
├── README.md
├── tailwind.config.ts
├── tsconfig.json
├── 📁app
│ ├── 📁(auth)
│ │ └── 📁login
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── 📁(dashboard)
│ │ └── 📁admin
│ │ ├──📁users
│ │ │ └── page.tsx
│ │ ├──📁correspondences
│ │ │ └── 📁new
│ │ │ │ └── page.tsx
│ │ │ └── page.tsx
│ │ ├──📁dashboard
│ │ │ └── page.tsx
│ │ ├──📁profile
│ │ │ └── page.tsx
│ │ ├──📁projects
│ │ │ ├──📁new
│ │ │ │ └── page.tsx
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── 📁api
│ │ └── 📁auth
│ │ └── 📁[...nextauth]
│ │ └── route.ts
│ ├── 📁demo
│ │ └── page.tsx
│ ├── 📁fonts
│ │ ├── GeistMonoVF.woff
│ │ └── GeistVF.woff
│ ├── favicon.ico
│ ├── globals copy.css
│ ├── globals.css
│ ├── layout copy.tsx
│ ├── layout.tsx
│ └── page.tsx
├── 📁components
│ ├── 📁custom
│ │ ├── file-upload-zone.tsx
│ │ ├── responsive-data-table.tsx
│ │ └── workflow-visualizer.tsx
│ ├── 📁dashboard
│ │ └── recent-activity.tsx
│ ├── 📁forms
│ │ └── file-upload.tsx
│ ├── 📁layout
│ │ ├── dashboard-shell.tsx
│ │ ├── navbar.tsx
│ │ ├── sidebar.tsx
│ │ └── user-nav.tsx
│ ├── 📁tables
│ └── 📁ui
│ ├── avatar.tsx
│ ├── badge.tsx
│ ├── button.tsx
│ ├── calendar.tsx
│ ├── card.tsx
│ ├── checkbox.tsx
│ ├── dropdown-menu.tsx
│ ├── input.tsx
│ ├── label.tsx
│ ├── popover.tsx
│ ├── progress.tsx
│ ├── scroll-area.tsx
│ ├── select.tsx
│ ├── switch.tsx
│ ├── table.tsx
│ ├── tabs.tsx
│ └── textarea.tsx
├── 📁config
│ └── menu.ts
├── 📁lib
│ ├── 📁api
│ │ └── client.ts
│ ├── 📁auth
│ ├── 📁hooks
│ ├── 📁services
│ │ ├── circulation.service.ts
│ │ ├── contract-drawing.service.ts
│ │ ├── correspondence.service.ts
│ │ ├── index.ts
│ │ ├── json-schema.service.ts
│ │ ├── master-data.service.ts
│ │ ├── monitoring.service.ts
│ │ ├── notification.service.ts
│ │ ├── project.service.ts
│ │ ├── rfa.service.ts
│ │ ├── search.service.ts
│ │ ├── shop-drawing.service.ts
│ │ ├── transmittal.service.ts
│ │ ├── user.service.ts
│ │ └── workflow-engine.service.ts
│ ├── 📁stores
│ │ ├── draft-store.ts
│ │ └── ui-store.ts
│ ├── auth.ts
│ └── utils.ts
├── 📁providers
│ ├── query-provider.tsx
│ └── session-provider.tsx
├── 📁public
├── 📁styles
└── 📁types
└── 📁dto
└── next-auth.d.ts
├── 📁circulation
│ ├── create-circulation.dto.ts
│ ├── search-circulation.dto.ts
│ └── update-circulation-routing.dto.ts
├── 📁correspondence
│ ├── add-reference.dto.ts
│ ├── create-correspondence.dto.ts
│ ├── search-correspondence.dto.ts
│ ├── submit-correspondence.dto.ts
│ └── workflow-action.dto.ts
├── 📁drawing
│ ├── contract-drawing.dto.ts
│ └── shop-drawing.dto.ts
├── 📁json-schema
│ └── json-schema.dto.ts
├── 📁master
│ ├── discipline.dto.ts
│ ├── number-format.dto.ts
│ ├── sub-type.dto.ts
│ └── tag.dto.ts
├── 📁monitoring
│ └── set-maintenance.dto.ts
├── 📁notification
│ └── notification.dto.ts
├── 📁project
│ └── project.dto.ts
├── 📁rfa
│ └── rfa.dto.ts
├── 📁search
│ └── search-query.dto.ts
├── 📁transmittal
│ └── transmittal.dto.ts
├── 📁user
│ └── user.dto.ts
└── 📁workflow-engine
└── workflow-engine.dto.ts
```
---
## 🗓️ **แผนการพัฒนาแบบ Phase-Based**
### **Phase 0: Foundation & Configuration (สัปดาห์ที่ 1)**
**Milestone:** โครงสร้างพื้นฐานพร้อม รองรับ Development Workflow ที่มีประสิทธิภาพ
### **Phase 0: Tasks**
- **[ ] F0.1 Project Setup & Tooling**
- [ ] Initialize Next.js 14+ project with TypeScript
- [ ] Configure pnpm workspace
- [ ] Setup ESLint, Prettier, and pre-commit hooks
- [ ] Configure Tailwind CSS with PostCSS
- [ ] Setup shadcn/ui components
- [ ] Configure absolute imports and path aliases
- [ ] **Deliverable:** Development environment ready
- [ ] **Dependencies:** None
- **[ ] F0.2 Design System & UI Components**
- [ ] Setup color palette and design tokens
- [ ] Create responsive design breakpoints
- [ ] Implement core shadcn/ui components:
- [ ] Button, Input, Label, Form
- [ ] Card, Table, Badge
- [ ] Dialog, Dropdown, Select
- [ ] Tabs, Accordion
- [ ] Create custom design system components:
- [ ] DataTable (responsive)
- [ ] FileUpload **zone**
- [ ] Workflow visualization
- [ ] **Deliverable:** Consistent UI component library
- [ ] **Dependencies:** F0.1
- **[ ] F0.3 API Client & Authentication**
- [ ] Setup Axios client with interceptors:
- [ ] Idempotency-Key header injection
- [ ] Authentication token management
- [ ] Error handling and retry logic
- [ ] Configure NextAuth.js for JWT authentication
- [ ] Create auth hooks (useAuth, usePermissions)
- [ ] Setup API route handlers for auth callbacks
- [ ] **Security:** Implement secure token storage
- [ ] **Deliverable:** Secure API communication layer
- [ ] **Dependencies:** F0.1
- **[ ] F0.4 State Management Setup**
- [ ] Configure TanStack Query for server state:
- [ ] Query client setup
- [ ] Default configurations
- [ ] Error boundaries
- [ ] Create Zustand stores:
- [ ] Auth store (user, permissions)
- [ ] UI store (theme, sidebar state)
- [ ] Draft store (form auto-save)
- [ ] Setup React Hook Form with Zod integration
- [ ] Create form validation schemas
- [ ] **Deliverable:** Robust state management system
- [ ] **Dependencies:** F0.1, F0.3
### **Phase 0: Testing - Foundation**
#### **F0.T1 Component Test Suite**
- [ ] **Unit Tests:** Core UI components (Button, Input, Card)
- [ ] **Integration Tests:** Form validation, API client interceptors
- [ ] **Visual Tests:** Component styling and responsive behavior
#### **F0.T2 Authentication Test Suite**
- [ ] **Unit Tests:** Auth hooks, token management
- [ ] **Integration Tests:** Login/logout flow, permission checks
- [ ] **Security Tests:** Token security, API authentication
---
### **Phase 1: Core Application Structure (สัปดาห์ที่ 2)**
**Milestone:** Layout หลักพร้อมใช้งาน การนำทางและ Authentication ทำงานได้
### **Phase 1: Tasks**
- **[ ] F1.1 Main Layout & Navigation**
- [ ] Create App Shell layout:
- [ ] Navbar with user menu and notifications
- [ ] Collapsible sidebar with navigation
- [ ] Main content area with responsive design
- [ ] Implement navigation menu structure:
- [ ] Dashboard, Correspondences, RFAs, Drawings, etc.
- [ ] Dynamic menu based on user permissions
- [ ] Create breadcrumb navigation component
- [ ] Implement mobile-responsive sidebar (drawer)
- [ ] Maintenance Mode Integration:
- [ ] Implement a Global Middleware/Wrapper ที่ตรวจสอบสถานะ Maintenance Mode ผ่าน API/Service ก่อนการ Render หน้า หากสถานะเป็น true ให้ Redirect ผู้ใช้ (ยกเว้น Admin) ไปยังหน้า /maintenance ทันที เพื่อให้สอดคล้องกับ Logic ของ Backend.
- [ ] **Accessibility:** Ensure keyboard navigation and screen reader support
- [ ] **Deliverable:** Fully functional application layout
- [ ] **Dependencies:** F0.2, F0.3
- **[ ] F1.2 Authentication Pages**
- [ ] Create login page with form validation
- [ ] Implement forgot password flow
- [ ] Create registration page (admin-only)
- [ ] Setup protected route middleware
- [ ] Implement route-based permission checks
- [ ] **Security:** Rate limiting on auth attempts, secure password requirements
- [ ] **Deliverable:** Complete authentication flow
- [ ] **Dependencies:** F0.3, F1.1
- **[ ] F1.3 Dashboard & Landing**
- [ ] Create public landing page for non-authenticated users
- [ ] Implement main dashboard with:
- [ ] KPI cards (document counts, pending tasks)
- [ ] "My Tasks" table from v_user_tasks
- [ ] Recent activity feed
- [ ] Security metrics display
- [ ] Create dashboard widgets system
- [ ] Implement data fetching with TanStack Query
- [ ] **Performance:** Optimize dashboard data loading
- [ ] **Deliverable:** Functional dashboard with real data
- [ ] **Dependencies:** F0.4, F1.1
- **[ ] F1.4 Responsive Design System**
- [ ] Implement mobile-first responsive design
- [ ] Create card view components for mobile tables
- [ ] Setup touch-friendly interactions
- [ ] Optimize images and assets for mobile
- [ ] Test across multiple device sizes
- [ ] **UX:** Ensure seamless mobile experience
- [ ] **Deliverable:** Fully responsive application
- [ ] **Dependencies:** F0.2, F1.1
### **Phase 1: Testing - Core Structure**
- **[ ] F1.T1 Layout Test Suite**
- [ ] **Unit Tests:** Navigation components, layout responsiveness
- [ ] **Integration Tests:** Route protection, permission-based navigation
- [ ] **E2E Tests:** Complete user navigation flow
- **[ ] F1.T2 Dashboard Test Suite**
- [ ] **Unit Tests:** Dashboard components, data formatting
- [ ] **Integration Tests:** Data fetching and display, real-time updates
- [ ] **Performance Tests:** Dashboard loading performance
---
### **Phase 2: User Management & Security (สัปดาห์ที่ 3)**
**Milestone:** การจัดการผู้ใช้และสิทธิ์แบบสมบูรณ์
### **Phase 2: Tasks**
- **[ ] F2.1 User Profile & Settings**
- [ ] Create user profile page:
- [ ] Personal information display/edit
- [ ] Password change functionality
- [ ] Notification preferences
- [ ] Implement profile picture upload
- [ ] Create user settings page
- [ ] **Security:** Secure password change with current password verification
- [ ] **Deliverable:** Complete user self-service management
- [ ] **Dependencies:** F1.1, F0.4
- **[ ] F2.2 Admin Panel - User Management**
- [ ] Create user list with search and filters
- [ ] Implement user creation form
- [ ] Create user edit interface
- [ ] Implement bulk user operations
- [ ] Add user activity tracking display
- [ ] **Security:** Admin-only access enforcement
- [ ] **Deliverable:** Comprehensive user management interface
- [ ] **Dependencies:** F1.1, F2.1
- **[ ] F2.3 Admin Panel - Role Management**
- [ ] Create role list and management interface
- [ ] Implement role creation and editing
- [ ] Create permission assignment interface
- [ ] Implement role-based access control visualization
- [ ] Add role usage statistics
- [ ] **Security:** Permission hierarchy enforcement
- [ ] **Deliverable:** Complete RBAC management system
- [ ] **Dependencies:** F2.2
- **[ ] F2.4 Permission Integration**
- [ ] Implement CASL ability integration
- [ ] Create permission-based UI components:
- [ ] ProtectedButton, ProtectedLink
- [ ] Conditional rendering based on permissions
- [ ] Setup real-time permission updates
- [ ] Implement permission debugging tools
- [ ] **Security:** Frontend-backend permission consistency
- [ ] **Deliverable:** Seamless permission enforcement throughout app
- [ ] **Dependencies:** F0.3, F2.3
### **Phase 2: User Management & Admin Panel (สัปดาห์ที่ 3)**
- **[ ] F2.5 Admin Panel - Master Data Management (Req 6B)** (New)
- [ ] Create "Disciplines Management" page (CRUD)
- [ ] Create "Sub-Types Management" page (CRUD + Mapping Number)
- [ ] Create "Numbering Format" configuration page (Template Editor)
- [ ] **Deliverable:** UI for Admin to configure system master data
- [ ] **Dependencies:** F2.1
### **Phase 2: Testing - User Management**
- **[ ] F2.T1 User Management Test Suite**
- [ ] **Unit Tests:** User CRUD operations, form validation
- [ ] **Integration Tests:** User-role assignment, permission propagation
- [ ] **Security Tests:** Permission escalation attempts, admin access control
- **[ ] F2.T2 RBAC Test Suite**
- [ ] **Unit Tests:** Permission checks, role validation
- [ ] **Integration Tests:** Multi-level permission enforcement, UI element protection
- [ ] **E2E Tests:** Complete role-based workflow testing
---
### **Phase 3: Project Structure (สัปดาห์ที่ 4)**
**Milestone:** การจัดการโครงสร้างโปรเจกต์และองค์กร
### **Phase 3: Tasks**
- **[ ] F3.1 Project Management UI**
- [ ] Create project list with search and filters
- [ ] Implement project creation and editing
- [ ] Create project detail view
- [ ] Implement project dashboard with statistics
- [ ] Add project member management
- [ ] **UX:** Intuitive project navigation and management
- [ ] **Deliverable:** Complete project management interface
- [ ] **Dependencies:** F1.1, F2.4
- **[ ] F3.2 Organization Management**
- [ ] Create organization list and management
- [ ] Implement organization creation and editing
- [ ] Create organization detail view
- [ ] Add organization user management
- [ ] Implement organization hierarchy visualization
- [ ] **Business Logic:** Proper organization-project relationships
- [ ] **Deliverable:** Comprehensive organization management
- [ ] **Dependencies:** F3.1
- **[ ] F3.3 Contract Management**
- [ ] Create contract list within projects
- [ ] Implement contract creation and editing
- [ ] Create contract detail view
- [ ] Add contract party management
- [ ] Implement contract document associations
- [ ] **Business Logic:** Contract-project-organization relationships
- [ ] **Deliverable:** Complete contract management system
- [ ] **Dependencies:** F3.1, F3.2
### **Phase 3: Testing - Project Structure**
- **[ ] F3.T1 Project Management Test Suite**
- [ ] **Unit Tests:** Project CRUD operations, validation
- [ ] **Integration Tests:** Project-organization relationships, member management
- [ ] **Business Logic Tests:** Project hierarchy, access control
---
### **Phase 4: Correspondence System (สัปดาห์ที่ 5-6)**
**Milestone:** ระบบจัดการเอกสารโต้ตอบแบบสมบูรณ์
### **Phase 4: Tasks**
- **[ ] F4.1 Correspondence List & Search**
- [ ] Create correspondence list with advanced filtering:
- [ ] Filter by type, status, project, organization
- [ ] Search by title, document number, content
- [ ] Date range filtering
- [ ] Implement responsive data table:
- [ ] Desktop: Full table view
- [ ] Mobile: Card view conversion
- [ ] Add bulk operations (export, status change)
- [ ] Implement real-time updates
- [ ] **Performance:** Virtual scrolling for large datasets
- [ ] **Deliverable:** High-performance correspondence listing
- [ ] **Dependencies:** F1.1, F3.1
- **[ ] F4.2 Correspondence Creation Form**
- [ ] Create dynamic form generator based on JSON schema
- [ ] Implement form with multiple sections:
- [ ] Basic information (type, title, recipients)
- [ ] Content and details (JSON schema-based)
- [ ] File attachments
- [ ] Routing template selection
- [ ] [New] Implement "Originator Selector" component: Dropdown สำหรับเลือกองค์กรผู้ส่ง (แสดงเฉพาะเมื่อผู้ใช้มีสิทธิ์ system.manage_all หรือสิทธิ์พิเศษ) หากไม่เลือกให้ใช้ Organization ของผู้ใช้ตามปกติ
- [ ] **[New] Discipline Selector:** Add Dropdown for Disciplines (Dependent on Contract/Project)
- [ ] **[New] Sub-Type Selector:** Add Dropdown for Sub-types (Dependent on Type)
- [ ] Logic: Show/Hide selectors based on Document Type configuration
- [ ] Add draft auto-save functionality
- [ ] Implement form validation with Zod
- [ ] **UX:** Intuitive form with progress indication
- [ ] **Deliverable:** Flexible correspondence creation interface
- [ ] **Dependencies:** F0.4, F4.1
- **[ ] F4.3 Correspondence Detail View**
- [ ] Create comprehensive detail page:
- [ ] Document header with metadata
- [ ] Content display based on type
- [ ] Revision history
- [ ] Related documents
- [ ] Workflow status visualization
- [ ] Implement document actions:
- [ ] Edit, withdraw, cancel (with permissions)
- [ ] Download, print
- [ ] Create related documents
- [ ] Add comments and activity timeline
- [ ] **UX:** Clean, readable document presentation
- [ ] **Deliverable:** Complete document detail experience
- [ ] **Dependencies:** F4.1, F4.2
- **[ ] F4.4 File Upload Integration**
- [ ] Create drag-and-drop file upload component
- [ ] Implement file type validation and preview
- [ ] Add virus scan status indication
- [ ] Create file management interface:
- [ ] Mark files as main/supporting documents
- [ ] Reorder and manage attachments
- [ ] File security status display
- [ ] Implement two-phase upload progress
- [ ] **Security:** File type restrictions, size limits, virus scan integration
- [ ] **Deliverable:** Secure and user-friendly file management
- [ ] **Dependencies:** F0.2, F4.2
### **Phase 4: Testing - Correspondence System**
- **[ ] F4.T1 Correspondence Test Suite**
- [ ] **Unit Tests:** Form validation, file upload components
- [ ] **Integration Tests:** Complete document lifecycle, file attachment flow
- [ ] **E2E Tests:** End-to-end correspondence creation and management
- **[ ] F4.T2 File Upload Test Suite**
- [ ] **Unit Tests:** File validation, type checking
- [ ] **Integration Tests:** Two-phase upload process, virus scan integration
- [ ] **Security Tests:** Malicious file upload attempts, security feedback
---
### **Phase 5: Workflow Management (สัปดาห์ที่ 7)**
**Milestone:** ระบบ Visualization และจัดการ Workflow
### **Phase 5: Tasks**
- **[ ] F5.1 Workflow Visualization Component**
- [ ] Create horizontal workflow progress visualization
- [ ] Implement step status indicators (pending, active, completed, skipped)
- [ ] Add due date and assignee information
- [ ] Create interactive workflow diagram
- [ ] Implement workflow history timeline
- [ ] **UX:** Clear visual representation of complex workflows
- [ ] **Deliverable:** Intuitive workflow visualization
- [ ] **Dependencies:** F4.3
- **[ ] F5.2 Routing Template Management**
- [ ] Create routing template list and editor
- [ ] Implement drag-and-drop step configuration
- [ ] Add step configuration (purpose, duration, assignee rules)
- [ ] Create template preview functionality
- [ ] Implement template versioning
- [ ] **Business Logic:** Proper step sequencing and validation
- [ ] **Deliverable:** Comprehensive routing template management
- [ ] **Dependencies:** F3.1, F4.2
- **[ ] F5.3 Workflow Step Actions**
- [ ] Create step action interface:
- [ ] Approve, reject, request changes
- [ ] Add comments and attachments
- [ ] Forward to other users
- [ ] Implement bulk step actions
- [ ] Add action confirmation with reason required
- [ ] Create step delegation functionality
- [ ] **UX:** Streamlined step completion process
- [ ] **Deliverable:** Efficient workflow step management
- [ ] **Dependencies:** F5.1
- **[ ] F5.4 Real-time Status Updates**
- [ ] Implement WebSocket connections for real-time updates
- [ ] Create status change notifications
- [ ] Add auto-refresh for workflow states
- [ ] Implement optimistic updates for better UX
- [ ] Create update history and audit trail
- [ ] **Performance:** Efficient real-time data synchronization
- [ ] **Deliverable:** Real-time workflow monitoring
- [ ] **Dependencies:** F5.1, F9.2
### **Phase 5: Testing - Workflow Management**
- **[ ] F5.T1 Workflow Test Suite**
- [ ] **Unit Tests:** Workflow visualization, step status logic
- [ ] **Integration Tests:** Complete workflow execution, real-time updates
- [ ] **E2E Tests:** Multi-step workflow with different user roles
---
### **Phase 6: Drawing System (สัปดาห์ที่ 8)**
**Milestone:** ระบบจัดการแบบแปลนแบบสมบูรณ์
### **Phase 6: Tasks**
- **[ ] F6.1 Contract Drawings Management**
- [ ] Create contract drawing list with categorization
- [ ] Implement drawing upload and metadata management
- [ ] Create drawing preview and viewer
- [ ] Add drawing version control
- [ ] Implement drawing search and filtering
- [ ] **UX:** Efficient drawing navigation and access
- [ ] **Deliverable:** Comprehensive contract drawing management
- [ ] **Dependencies:** F3.1, F4.4
- **[ ] F6.2 Shop Drawings Management**
- [ ] Create shop drawing list with revision tracking
- [ ] Implement shop drawing creation and revision system
- [ ] Create drawing comparison interface
- [ ] Add drawing approval status tracking
- [ ] Implement bulk drawing operations
- [ ] **Business Logic:** Proper revision control and approval workflows
- [ ] **Deliverable:** Complete shop drawing management system
- [ ] **Dependencies:** F6.1
- **[ ] F6.3 Drawing Revision System**
- [ ] Create revision history interface
- [ ] Implement revision comparison functionality
- [ ] Add revision notes and change tracking
- [ ] Create revision approval workflow
- [ ] Implement revision rollback capability
- [ ] **UX:** Clear visualization of changes between revisions
- [ ] **Deliverable:** Robust drawing revision control
- [ ] **Dependencies:** F6.2
- **[ ] F6.4 Drawing References**
- [ ] Create drawing reference management
- [ ] Implement cross-drawing references
- [ ] Add reference validation and integrity checks
- [ ] Create reference visualization
- [ ] Implement reference impact analysis
- [ ] **Business Logic:** Maintain reference integrity during changes
- [ ] **Deliverable:** Comprehensive drawing reference system
- [ ] **Dependencies:** F6.2, F6.3
### **Phase 6: Testing - Drawing System**
- **[ ] F6.T1 Drawing Management Test Suite**
- [ ] **Unit Tests:** Drawing CRUD operations, revision logic
- [ ] **Integration Tests:** Drawing approval workflows, reference management
- [ ] **E2E Tests:** Complete drawing lifecycle with revisions
---
### **Phase 7: RFA System (สัปดาห์ที่ 9-10)**
**Milestone:** ระบบขออนุมัติแบบสมบูรณ์พร้อม Dynamic Forms
### **Phase 7: Tasks**
- **[ ] F7.1 RFA List & Dashboard**
- [ ] Create RFA dashboard with status overview
- [ ] Implement advanced RFA filtering and search
- [ ] Create RFA calendar view for deadlines
- [ ] Add RFA statistics and reporting
- [ ] Implement RFA bulk operations
- [ ] **UX:** Comprehensive RFA overview and management
- [ ] **Deliverable:** Complete RFA dashboard and listing
- [ ] **Dependencies:** F4.1, F5.1
- **[ ] F7.2 RFA Creation with Dynamic Forms**
- [ ] Create RFA type-specific form generator
- [ ] Implement dynamic form fields based on RFA type:
- [ ] RFA_DWG: Shop drawing selection
- [ ] RFA_DOC: Document specifications
- [ ] RFA_MES: Method statement details
- [ ] RFA_MAT: Material specifications
- [ ] Add form validation with JSON schema
- [ ] Implement form data persistence and recovery
- [ ] **UX:** Intuitive form experience for complex RFA types
- [ ] Dynamic Form & Schema Validation: สร้าง Component Dynamic Form Generator ที่:
- [ ] Fetch Schema: ดึงโครงสร้าง JSON Schema ที่ถูกต้องตาม rfa_type จาก Backend (ตาราง json_schemas ที่สร้างใหม่) ก่อนการ Render Form.
- [ ] Client-side Validation: Implement AJV (Another JSON Schema Validator) หรือไลบรารีที่เทียบเท่า เพื่อทำ Client-side Validation บนข้อมูล JSON ก่อนส่ง Submit.
- [ ] Implement dynamic form fields based on RFA type: RFA_DWG, RFA_DOC, RFA_MES, RFA_MAT.
- [ ] Add form data persistence and recovery.
- [ ] **[New] Discipline & Sub-Type Integration:** Ensure RFA forms capture these fields correctly for numbering generation.
- [ ] **Deliverable:** Flexible RFA creation system
- [ ] **Dependencies:** F4.2, F6.2
- **[ ] F7.3 RFA Workflow Integration**
- [ ] Integrate RFA with unified workflow engine
- [ ] Create RFA-specific workflow steps and actions
- [ ] Implement RFA approval interface
- [ ] Add RFA workflow history and tracking
- [ ] Create RFA workflow templates
- [ ] **Business Logic:** Proper RFA approval sequencing and validation
- [ ] **Deliverable:** Seamless RFA workflow integration
- [ ] **Dependencies:** F5.1, F7.2
- **[ ] F7.4 RFA Approval Interface**
- [ ] Create RFA review and approval interface
- [ ] Implement side-by-side document comparison
- [ ] Add approval comments and attachments
- [ ] Create conditional approval workflows
- [ ] Implement approval delegation and escalation
- [ ] **UX:** Efficient approval process for technical reviews
- [ ] **Deliverable:** Comprehensive RFA approval system
- [ ] **Dependencies:** F7.1, F7.3
### **Phase 7: Testing - RFA System**
- **[ ] F7.T1 RFA Test Suite**
- [ ] **Unit Tests:** RFA form generation, validation logic
- [ ] **Integration Tests:** Complete RFA lifecycle, workflow integration
- [ ] **E2E Tests:** Multi-type RFA creation and approval workflows
---
### **Phase 8: Internal Workflows (สัปดาห์ที่ 11)**
**Milestone:** ระบบใบเวียนและการจัดการงานภายใน
### **Phase 8: Tasks**
- **[ ] F8.1 Circulation Management**
- [ ] Create circulation list and management interface
- [ ] Implement circulation creation from correspondence
- [ ] Create circulation template management
- [ ] Add circulation status tracking
- [ ] Implement circulation search and filtering
- [ ] **Business Logic:** Proper circulation-correspondence relationships
- [ ] **Deliverable:** Comprehensive circulation management
- [ ] **Dependencies:** F4.1, F5.2
- **[ ] F8.2 Task Assignment Interface**
- [ ] Create task assignment interface with user selection
- [ ] Implement task priority and deadline setting
- [ ] Add task dependency management
- [ ] Create task progress tracking
- [ ] Implement task reassignment and delegation
- [ ] **UX:** Intuitive task management and assignment
- [ ] **Deliverable:** Efficient task assignment system
- [ ] **Dependencies:** F8.1
- **[ ] F8.3 Internal Approval Flows**
- [ ] Create internal approval workflow interface
- [ ] Implement multi-level approval processes
- [ ] Add approval chain visualization
- [ ] Create approval delegation system
- [ ] Implement approval deadline management
- [ ] **Business Logic:** Proper approval hierarchy and escalation
- [ ] **Deliverable:** Robust internal approval system
- [ ] **Dependencies:** F8.1, F8.2
### **Phase 8: Testing - Internal Workflows**
- **[ ] F8.T1 Circulation Test Suite**
- [ ] **Unit Tests:** Circulation creation, task assignment logic
- [ ] **Integration Tests:** Complete circulation workflow, internal approvals
- [ ] **E2E Tests:** End-to-end circulation with task completion
---
### **Phase 9: Advanced Features (สัปดาห์ที่ 12)**
**Milestone:** ฟีเจอร์ขั้นสูงและการปรับปรุงประสบการณ์ผู้ใช้
### **Phase 9: Tasks**
- **[ ] F9.1 Advanced Search Interface**
- [ ] Create unified search interface across all document types
- [ ] Implement faceted search with multiple filters
- [ ] Add search result highlighting and relevance scoring
- [ ] Create saved search and search templates
- [ ] Implement search result export functionality
- [ ] **Performance:** Efficient search with large datasets
- [ ] **Deliverable:** Powerful cross-document search system
- [ ] **Dependencies:** F4.1, F7.1
- **[ ] F9.2 Notification System**
- [ ] Create notification center with real-time updates
- [ ] Implement notification preferences management
- [ ] Add notification grouping and digest views
- [ ] Create actionable notifications with quick actions
- [ ] Implement notification read/unread status
- [ ] **UX:** Non-intrusive but effective notification delivery
- [ ] **Deliverable:** Comprehensive notification management
- [ ] **Dependencies:** F1.3, F5.4
- **[ ] F9.3 Reporting & Analytics**
- [ ] Create reporting dashboard with customizable widgets
- [ ] Implement data visualization components (charts, graphs)
- [ ] Add report scheduling and export
- [ ] Create ad-hoc reporting interface
- [ ] Implement performance metrics tracking
- [ ] **Business Logic:** Accurate data aggregation and reporting
- [ ] **Deliverable:** Powerful reporting and analytics system
- [ ] **Dependencies:** F1.3, F7.1
- **[ ] F9.4 Mobile Optimization**
- [ ] Implement touch-optimized interactions
- [ ] Create mobile-specific navigation patterns
- [ ] Add offline capability for critical functions
- [ ] Optimize images and assets for mobile networks
- [ ] Implement mobile-specific performance optimizations
- [ ] **UX:** Seamless mobile experience comparable to desktop
- [ ] **Deliverable:** Fully optimized mobile application
- [ ] **Dependencies:** F1.4
### **Phase 9: Testing - Advanced Features**
- **[ ] F9.T1 Advanced Features Test Suite**
- [ ] **Unit Tests:** Search algorithms, notification logic
- [ ] **Integration Tests:** Cross-module search, real-time notifications
- [ ] **Performance Tests:** Search performance, mobile responsiveness
---
### **Phase 10: Testing & Polish (สัปดาห์ที่ 13-14)**
**Milestone:** แอปพลิเคชันที่ผ่านการทดสอบและปรับปรุงอย่างสมบูรณ์
### **Phase 10: Tasks**
- **[ ] F10.1 Comprehensive Testing**
- [ ] Idempotency Testing: เพิ่มการทดสอบเฉพาะสำหรับ Axios Interceptor เพื่อจำลองการส่ง Request POST/PUT/DELETE ที่มี Idempotency-Key ซ้ำไปยัง Mock API (MSW) เพื่อยืนยันว่า Client-side ไม่ส่ง Key ซ้ำในการทำงานปกติ และไม่เกิด Side Effect จากการ Replay Attack.
- [ ] Write unit tests for all components and utilities
- [ ] Create integration tests for critical user flows
- [ ] Implement E2E tests for complete workflows
- [ ] Perform cross-browser compatibility testing
- [ ] Conduct accessibility testing (WCAG 2.1 AA)
- [ ] **Quality:** 80%+ test coverage, all critical paths tested
- [ ] **Deliverable:** Fully tested application
- [ ] **Dependencies:** All previous phases
- **[ ] F10.2 Performance Optimization**
- [ ] Implement code splitting and lazy loading
- [ ] Optimize bundle size and asset delivery
- [ ] Add performance monitoring and metrics
- [ ] Implement caching strategies for static assets
- [ ] Optimize API call patterns and reduce over-fetching
- [ ] **Performance:** Core Web Vitals targets met
- [ ] **Deliverable:** High-performance application
- [ ] **Dependencies:** F10.1
- **[ ] F10.3 Security Hardening**
- [ ] Conduct security audit and penetration testing
- [ ] Implement Content Security Policy (CSP)
- [ ] Add security headers and protections
- [ ] Conduct dependency vulnerability scanning
- [ ] Implement secure coding practices review
- [ ] **Security:** No critical security vulnerabilities
- [ ] **Deliverable:** Security-hardened application
- [ ] **Dependencies:** F10.1
- **[ ] F10.4 Documentation**
- [ ] Create user documentation and guides
- [ ] Write technical documentation for developers
- [ ] Create API integration documentation
- [ ] Add inline code documentation
- [ ] Create deployment and maintenance guides
- [ ] **Quality:** Comprehensive and up-to-date documentation
- [ ] **Deliverable:** Complete documentation suite
- [ ] **Dependencies:** F10.1
### **Phase 10: Testing - Final Validation**
- **[ ] F10.T1 Final Test Suite**
- [ ] **Performance Tests:** Load testing, stress testing
- [ ] **Security Tests:** Final security audit, vulnerability assessment
- [ ] **User Acceptance Tests:** Real user testing, feedback incorporation
- [ ] **Compatibility Tests:** Cross-browser, cross-device testing
---
## 📊 **สรุป Timeline**
| Phase | ระยะเวลา | จำนวนงาน | Output หลัก |
| -------- | -------------- | ------------ | ------------------------------------ |
| Phase 0 | 1 สัปดาห์ | 4 | Foundation & Tooling Ready |
| Phase 1 | 1 สัปดาห์ | 4 | Core Application Structure |
| Phase 2 | 1 สัปดาห์ | 4 | User Management & Security |
| Phase 3 | 1 สัปดาห์ | 3 | Project Structure Management |
| Phase 4 | 2 สัปดาห์ | 4 | Correspondence System |
| Phase 5 | 1 สัปดาห์ | 4 | Workflow Management |
| Phase 6 | 1 สัปดาห์ | 4 | Drawing System |
| Phase 7 | 2 สัปดาห์ | 4 | RFA System (Dynamic Forms) |
| Phase 8 | 1 สัปดาห์ | 3 | Internal Workflows |
| Phase 9 | 1 สัปดาห์ | 4 | Advanced Features |
| Phase 10 | 2 สัปดาห์ | 4 | Testing & Polish (Idempotency Test) |
| **รวม** | **14 สัปดาห์** | **39 Tasks** | **Production-Ready Frontend v1.4.2** |
---
## 🎯 **Critical Success Factors**
1. **User Experience First:** ทุกฟีเจอร์ต้องออกแบบเพื่อประสบการณ์ผู้ใช้ที่ดี
2. **Responsive Design:** รองรับการใช้งานบนอุปกรณ์ทุกรูปแบบ
3. **Performance:** Core Web Vitals ต้องอยู่ในเกณฑ์ที่ดี
4. **Accessibility:** ต้องเป็นไปตามมาตรฐาน WCAG 2.1 AA
5. **Security:** ป้องกัน XSS, CSRF และความเสี่ยงด้านความปลอดภัยอื่นๆ
6. **Offline Support:** รองรับการทำงานแบบ Offline เบื้องต้น
7. **Real-time Updates:** การอัปเดตสถานะแบบ Real-time
8. **Testing Coverage:** ครอบคลุมการทดสอบทุก Critical Path
9. **Documentation:** เอกสารครบถ้วนสำหรับผู้ใช้และนักพัฒนา
---
## 📋 **Quality Assurance Checklist**
### **ก่อน Production Deployment**
- [ ] **Performance:** Core Web Vitals ผ่านเกณฑ์
- [ ] **Accessibility:** WCAG 2.1 AA compliant
- [ ] **Security:** Security audit ผ่าน
- [ ] **Testing:** Test coverage ≥ 80%
- [ ] **Browser Compatibility:** ทำงานได้บนเบราว์เซอร์หลัก
- [ ] **Mobile Responsive:** ใช้งานได้ดีบนมือถือ
- [ ] **Documentation:** เอกสารครบถ้วน
- [ ] **User Acceptance:** ได้รับการยอมรับจากผู้ใช้
---
## 🚀 **ขั้นตอนถัดไป**
1. **Approve แผนนี้** → ปรับแต่งตาม Feedback
2. **Coordinate กับ Backend Team** → Sync API Specifications
3. **เริ่มพัฒนา Phase 0** → Setup Foundation
4. **Regular Sync** → ประสานงานกับ Backend ทุกสัปดาห์
5. **User Testing** → ทดสอบกับผู้ใช้จริงระหว่างพัฒนา
6. **Deploy to Production** → Week 15 (พร้อม Backend)
## **Document Control:**
- **Document:** Frontend Development Plan v1.4.5
- **Version:** 1.4
- **Date:** 2025-11-29
- **Author:** NAP LCBP3-DMS & Gemini
- **Status:** FINAL-Rev.05
- **Classification:** Internal Technical Documentation
- **Approved By:** Nattanin
---
`End of Frontend Development Plan v1.4.5`

2673
4_Data_Dictionary_V1_4_5.md Normal file

File diff suppressed because it is too large Load Diff

1477
8_lcbp3_v1_4_5.sql Normal file

File diff suppressed because it is too large Load Diff

2127
8_lcbp3_v1_4_5_seed.sql Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
# NAP-DMS Project Context & Rules
## 🧠 Role & Persona
Act as a **Senior Full Stack Developer** expert in **NestJS**, **Next.js**, and **TypeScript**.
You value **Data Integrity**, **Security**, and **Clean Architecture**.
## 🏗️ Project Overview
This is **LCBP3-DMS (Laem Chabang Port Phase 3 - Document Management System)**.
- **Goal:** Manage construction documents (Correspondence, RFA, Drawings) with complex approval workflows.
- **Infrastructure:** Deployed on QNAP Server via Docker Container Station.
## 💻 Tech Stack & Constraints
- **Backend:** NestJS (Modular Architecture), TypeORM, MariaDB 10.11, Redis (BullMQ).
- **Frontend:** Next.js 14+ (App Router), Tailwind CSS, Shadcn/UI.
- **Language:** TypeScript (Strict Mode). **NO `any` types allowed.**
## 🛡️ Security & Integrity Rules
1. **Idempotency:** All critical POST/PUT requests MUST check for `Idempotency-Key` header.
2. **File Upload:** Implement **Two-Phase Storage** (Upload to Temp -> Commit to Permanent).
3. **Race Conditions:** Use **Redis Lock** + **Optimistic Locking** for Document Numbering generation.
4. **Validation:** Use Zod or Class-validator for all inputs.
## workflow Guidelines
- When implementing **Workflow Engine**, strictly follow the **DSL** design in `2_Backend_Plan_V1_4_4.Phase6A.md`.
- Always verify database schema against `4_Data_Dictionary_V1_4_4.md` before writing queries.
## 🚫 Forbidden Actions
- DO NOT use SQL Triggers (Business logic must be in NestJS services).
- DO NOT use `.env` files for production configuration (Use Docker environment variables).
- DO NOT generate code that violates OWASP Top 10 security practices.

View File

@@ -1,6 +1,6 @@
{
"name": "backend",
"version": "0.0.1",
"version": "1.4.4",
"description": "",
"author": "",
"private": true,
@@ -17,7 +17,8 @@
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
"test:e2e": "jest --config ./test/jest-e2e.json",
"seed": "ts-node -r tsconfig-paths/register src/database/seeds/run-seed.ts"
},
"dependencies": {
"@casl/ability": "^6.7.3",

View File

@@ -0,0 +1,125 @@
# **Workflow DSL Specification v1.0**
เอกสารนี้ระบุโครงสร้างภาษา (Domain-Specific Language) สำหรับกำหนด Business Logic ของการเดินเอกสารในระบบ LCBP3-DMS
## **1\. โครงสร้างหลัก (Root Structure)**
ไฟล์ Definition ต้องอยู่ในรูปแบบ YAML หรือ JSON โดยมีโครงสร้างดังนี้:
```json
workflow: "RFA_FLOW" # Workflow (Unique)
version: 1 # Logic
description: "RFA Approval Process" #
#
states:
- name: "DRAFT" # (Case-sensitive)
initial: true # ( 1 )
on: # Action
SUBMIT: # Action ( User )
to: "IN_REVIEW" #
require: # (Optional)
role: "EDITOR"
events: # (Optional)
- type: "notify"
target: "reviewer"
- name: "IN_REVIEW"
on:
APPROVE:
to: "APPROVED"
condition: "context.amount < 1000000" # (Optional) JS Expression
REJECT:
to: "DRAFT"
events:
- type: "notify"
target: "creator"
- name: "APPROVED"
terminal: true # ()
```
## **2. รายละเอียด Field (Field Definitions)**
### **2.1 State Object**
| Field | Type | Required | Description |
| :------- | :------ | :------- | :--------------------------------------------- |
| name | string | Yes | ชื่อสถานะ (Unique Key) |
| initial | boolean | No | ระบุว่าเป็นจุดเริ่มต้น (ต้องมี 1 state ในระบบ) |
| terminal | boolean | No | ระบุว่าเป็นจุดสิ้นสุด |
| on | object | No | Map ของ Action -> Transition Rule |
### **2.2 Transition Rule Object**
| Field | Type | Required | Description |
| :-------- | :----- | :------- | :-------------------------------------- |
| to | string | Yes | ชื่อสถานะปลายทาง |
| require | object | No | เงื่อนไข Role/User |
| condition | string | No | JavaScript Expression (return boolean) |
| events | array | No | Side-effects ที่จะทำงานหลังเปลี่ยนสถานะ |
### **2.3 Requirements Object**
| Field | Type | Description |
| :---- | :----- | :------------------------------------------ |
| role | string | User ต้องมี Role นี้ (เช่น PROJECT_MANAGER) |
| user | string | User ต้องมี ID นี้ (Hard-code) |
### **2.4 Event Object**
| Field | Type | Description |
| :------- | :----- | :----------------------------------------- |
| type | string | notify, webhook, update_status |
| target | string | ผู้รับ (เช่น creator, assignee, หรือ Role) |
| template | string | รหัส Template ข้อความ |
## **3\. ตัวอย่างการใช้งานจริง (Real-world Examples)**
### **ตัวอย่าง: RFA Approval Flow**
```json
{
"workflow": "RFA_STD",
"version": 1,
"states": [
{
"name": "DRAFT",
"initial": true,
"on": {
"SUBMIT": {
"to": "CONSULTANT_REVIEW",
"require": { "role": "CONTRACTOR" }
}
}
},
{
"name": "CONSULTANT_REVIEW",
"on": {
"APPROVE_1": {
"to": "OWNER_REVIEW",
"condition": "context.priority === 'HIGH'"
},
"APPROVE_2": {
"to": "APPROVED",
"condition": "context.priority === 'NORMAL'"
},
"REJECT": {
"to": "DRAFT"
}
}
},
{
"name": "OWNER_REVIEW",
"on": {
"APPROVE": { "to": "APPROVED" },
"REJECT": { "to": "CONSULTANT_REVIEW" }
}
},
{
"name": "APPROVED",
"terminal": true
}
]
}
```

View File

@@ -0,0 +1,67 @@
import { config } from 'dotenv';
import { DataSource } from 'typeorm';
import { seedWorkflowDefinitions } from '../seeds/workflow-definitions.seed'; // Import ฟังก์ชัน Seed ที่คุณมี
// Import Entities ที่เกี่ยวข้อง
import { WorkflowDefinition } from '../../modules/workflow-engine/entities/workflow-definition.entity';
import { WorkflowHistory } from '../../modules/workflow-engine/entities/workflow-history.entity';
import { WorkflowInstance } from '../../modules/workflow-engine/entities/workflow-instance.entity';
// โหลด Environment Variables (.env)
config();
const runSeed = async () => {
// ตั้งค่าการเชื่อมต่อฐานข้อมูล (ควรตรงกับ docker-compose หรือ .env ของคุณ)
const dataSource = new DataSource({
type: 'mariadb',
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '3306'),
username: process.env.DB_USERNAME || 'root',
password: process.env.DB_PASSWORD || 'Center#2025',
database: process.env.DB_DATABASE || 'lcbp3_dev',
// สำคัญ: ต้องใส่ Entities ที่เกี่ยวข้องทั้งหมดเพื่อให้ TypeORM รู้จัก
entities: [
WorkflowDefinition,
WorkflowInstance,
WorkflowHistory,
// ใส่ Entity อื่นๆ ถ้าจำเป็น หรือใช้ path pattern: __dirname + '/../../modules/**/*.entity{.ts,.js}'
],
synchronize: false, // ห้ามใช้ true บน Production
});
try {
console.log('🔌 Connecting to database...');
await dataSource.initialize();
console.log('✅ Database connected.');
console.log('🌱 Running Seeds...');
await seedWorkflowDefinitions(dataSource);
console.log('✅ Seeding completed successfully.');
} catch (error) {
console.error('❌ Error during seeding:', error);
} finally {
if (dataSource.isInitialized) {
await dataSource.destroy();
console.log('🔌 Database connection closed.');
}
}
};
runSeed();
/*
npx ts-node -r tsconfig-paths/register src/database/run-seed.ts
**หรือเพิ่มใน `package.json` (แนะนำ):**
คุณสามารถเพิ่ม script ใน `package.json` เพื่อให้เรียกใช้ได้ง่ายขึ้นในอนาคต:
"scripts": {
"seed": "ts-node -r tsconfig-paths/register src/database/seeds/run-seed.ts"
}
http://googleusercontent.com/immersive_entry_chip/1
### 💡 ข้อควรระวัง
1. **Environment Variables:** ตรวจสอบให้แน่ใจว่าค่า Config (Host, User, Password) ในไฟล์ `run-seed.ts` หรือ `.env` นั้นถูกต้องและตรงกับ Docker Container ที่กำลังรันอยู่
2. **Entities:** หากฟังก์ชัน Seed มีการเรียกใช้ Entity อื่นนอกเหนือจาก `WorkflowDefinition` ต้องนำมาใส่ใน `entities: [...]` ของ `DataSource` ให้ครบ ไม่อย่างนั้นจะเจอ Error `RepositoryNotFoundError`
*/

View File

@@ -10,26 +10,41 @@ export const seedWorkflowDefinitions = async (dataSource: DataSource) => {
// 1. RFA Workflow (Standard)
const rfaDsl = {
workflow: 'RFA',
workflow: 'RFA_FLOW_V1', // [FIX] เปลี่ยนชื่อให้ตรงกับค่าใน RfaWorkflowService
version: 1,
description: 'Standard RFA Approval Workflow',
states: [
{
name: 'DRAFT',
initial: true,
on: { SUBMIT: { to: 'IN_REVIEW', requirements: [{ role: 'Editor' }] } },
on: {
SUBMIT: {
to: 'IN_REVIEW',
require: { role: 'Editor' }, // [FIX] แก้ไข Syntax เป็น Object
},
},
},
{
name: 'IN_REVIEW',
on: {
APPROVE: {
to: 'APPROVED',
requirements: [{ role: 'Contract Admin' }],
APPROVE_1: {
to: 'APPROVED', // [FIX] ชี้ไปที่ State ที่มีอยู่จริง
require: { role: 'Contract Admin' },
condition: "context.priority === 'HIGH'",
},
APPROVE_2: {
to: 'APPROVED', // [FIX] ชี้ไปที่ State ที่มีอยู่จริง
require: { role: 'Contract Admin' },
condition: "context.priority === 'NORMAL'",
},
REJECT: {
to: 'REJECTED',
requirements: [{ role: 'Contract Admin' }],
require: { role: 'Contract Admin' },
},
COMMENT: { to: 'DRAFT', requirements: [{ role: 'Contract Admin' }] }, // ส่งกลับแก้ไข
COMMENT: {
to: 'DRAFT',
require: { role: 'Contract Admin' },
}, // ส่งกลับแก้ไข
},
},
{ name: 'APPROVED', terminal: true },
@@ -39,18 +54,27 @@ export const seedWorkflowDefinitions = async (dataSource: DataSource) => {
// 2. Circulation Workflow
const circulationDsl = {
workflow: 'CIRCULATION',
workflow: 'CIRCULATION_INTERNAL_V1', // [FIX] เปลี่ยนชื่อให้ตรงกับค่าใน CirculationWorkflowService
version: 1,
description: 'Internal Document Circulation',
states: [
{
name: 'OPEN',
initial: true,
on: { SEND: { to: 'IN_REVIEW' } },
on: {
START: {
// [FIX] เปลี่ยนชื่อ Action ให้ตรงกับที่ Service เรียกใช้ ('START')
to: 'IN_REVIEW',
},
},
},
{
name: 'IN_REVIEW',
on: {
COMPLETE: { to: 'COMPLETED' }, // เมื่อทุกคนตอบครบ
COMPLETE_TASK: {
// [FIX] เปลี่ยนให้สอดคล้องกับ Action ที่ใช้จริง
to: 'COMPLETED',
},
CANCEL: { to: 'CANCELLED' },
},
},
@@ -59,24 +83,60 @@ export const seedWorkflowDefinitions = async (dataSource: DataSource) => {
],
};
const workflows = [rfaDsl, circulationDsl];
// 3. Correspondence Workflow (Optional - ถ้ามี)
const correspondenceDsl = {
workflow: 'CORRESPONDENCE_FLOW_V1',
version: 1,
description: 'Standard Correspondence Routing',
states: [
{
name: 'DRAFT',
initial: true,
on: { SUBMIT: { to: 'IN_REVIEW' } },
},
{
name: 'IN_REVIEW',
on: {
APPROVE: { to: 'APPROVED' },
REJECT: { to: 'REJECTED' },
},
},
{ name: 'APPROVED', terminal: true },
{ name: 'REJECTED', terminal: true },
],
};
const workflows = [rfaDsl, circulationDsl, correspondenceDsl];
for (const dsl of workflows) {
const exists = await repo.findOne({
where: { workflow_code: dsl.workflow, version: dsl.version },
});
if (!exists) {
const compiled = dslService.compile(dsl);
await repo.save(
repo.create({
workflow_code: dsl.workflow,
version: dsl.version,
dsl: dsl,
compiled: compiled,
is_active: true,
}),
try {
// Compile เพื่อ Validate และ Normalize ก่อนบันทึก
// cast as any เพื่อ bypass type checking ตอน seed raw data
const compiled = dslService.compile(dsl as any);
await repo.save(
repo.create({
workflow_code: dsl.workflow,
version: dsl.version,
description: dsl.description,
dsl: dsl,
compiled: compiled,
is_active: true,
}),
);
console.log(`✅ Seeded Workflow: ${dsl.workflow} v${dsl.version}`);
} catch (error) {
console.error(`❌ Failed to seed workflow ${dsl.workflow}:`, error);
}
} else {
console.log(
`⏭️ Workflow already exists: ${dsl.workflow} v${dsl.version}`,
);
console.log(`✅ Seeded Workflow: ${dsl.workflow} v${dsl.version}`);
}
}
};

View File

@@ -0,0 +1,164 @@
// File: src/modules/circulation/circulation-workflow.service.ts
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, Repository } from 'typeorm';
// Modules
import { WorkflowEngineService } from '../workflow-engine/workflow-engine.service';
// Entities
import { CirculationStatusCode } from './entities/circulation-status-code.entity';
import { Circulation } from './entities/circulation.entity';
// DTOs
import { WorkflowTransitionDto } from '../workflow-engine/dto/workflow-transition.dto';
@Injectable()
export class CirculationWorkflowService {
private readonly logger = new Logger(CirculationWorkflowService.name);
private readonly WORKFLOW_CODE = 'CIRCULATION_INTERNAL_V1';
constructor(
private readonly workflowEngine: WorkflowEngineService,
@InjectRepository(Circulation)
private readonly circulationRepo: Repository<Circulation>,
@InjectRepository(CirculationStatusCode)
private readonly statusRepo: Repository<CirculationStatusCode>,
private readonly dataSource: DataSource,
) {}
/**
* เริ่มต้นใบเวียน (Start Circulation)
* ปกติจะเริ่มเมื่อสร้าง Circulation หรือเมื่อกดส่ง
*/
async startCirculation(circulationId: number, userId: number) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const circulation = await this.circulationRepo.findOne({
where: { id: circulationId },
});
if (!circulation) {
throw new NotFoundException(
`Circulation ID ${circulationId} not found`,
);
}
// Context อาจประกอบด้วย Department หรือ Priority
const context = {
organizationId: circulation.organization,
creatorId: userId,
};
// Create Instance (Entity Type = 'circulation')
const instance = await this.workflowEngine.createInstance(
this.WORKFLOW_CODE,
'circulation',
circulation.id.toString(),
context,
);
// Auto start (OPEN -> IN_REVIEW)
const transitionResult = await this.workflowEngine.processTransition(
instance.id,
'START',
userId,
'Start Circulation Process',
{},
);
// Sync Status
await this.syncStatus(
circulation,
transitionResult.nextState,
queryRunner,
);
await queryRunner.commitTransaction();
return {
instanceId: instance.id,
currentState: transitionResult.nextState,
};
} catch (error) {
await queryRunner.rollbackTransaction();
this.logger.error(`Failed to start circulation: ${error}`);
throw error;
} finally {
await queryRunner.release();
}
}
/**
* รับทราบ/ดำเนินการในใบเวียน (Acknowledge / Action)
*/
async processAction(
instanceId: string,
userId: number,
dto: WorkflowTransitionDto,
) {
// ส่งให้ Engine
const result = await this.workflowEngine.processTransition(
instanceId,
dto.action,
userId,
dto.comment,
dto.payload,
);
// Sync Status กลับ
const instance = await this.workflowEngine.getInstanceById(instanceId);
if (instance && instance.entityType === 'circulation') {
const circulation = await this.circulationRepo.findOne({
where: { id: parseInt(instance.entityId) },
});
if (circulation) {
await this.syncStatus(circulation, result.nextState);
}
}
return result;
}
/**
* Helper: Map Workflow State -> Circulation Status (OPEN, IN_REVIEW, COMPLETED)
*/
private async syncStatus(
circulation: Circulation,
workflowState: string,
queryRunner?: any,
) {
const statusMap: Record<string, string> = {
DRAFT: 'OPEN',
ROUTING: 'IN_REVIEW',
COMPLETED: 'COMPLETED',
CANCELLED: 'CANCELLED',
};
const targetCode = statusMap[workflowState] || 'IN_REVIEW';
// เนื่องจาก circulation เก็บ status_code เป็น String ในตารางเลย (ตาม Schema v1.4.4)
// หรืออาจเป็น Relation ID ขึ้นอยู่กับ Implementation จริง
// สมมติว่าเป็น String Code ตาม Schema:
circulation.statusCode = targetCode;
// ถ้าจบแล้ว ให้ลงเวลาปิด
if (targetCode === 'COMPLETED') {
circulation.closedAt = new Date();
}
const manager = queryRunner
? queryRunner.manager
: this.circulationRepo.manager;
await manager.save(circulation);
this.logger.log(
`Synced Circulation #${circulation.id}: State=${workflowState} -> Status=${targetCode}`,
);
}
}

View File

@@ -1,13 +1,15 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Circulation } from './entities/circulation.entity';
import { CirculationRouting } from './entities/circulation-routing.entity';
import { CirculationStatusCode } from './entities/circulation-status-code.entity';
import { Circulation } from './entities/circulation.entity';
import { CirculationService } from './circulation.service';
import { CirculationController } from './circulation.controller';
import { UserModule } from '../user/user.module';
import { WorkflowEngineModule } from '../workflow-engine/workflow-engine.module';
import { CirculationWorkflowService } from './circulation-workflow.service';
import { CirculationController } from './circulation.controller';
import { CirculationService } from './circulation.service';
@Module({
imports: [
@@ -17,9 +19,10 @@ import { UserModule } from '../user/user.module';
CirculationStatusCode,
]),
UserModule,
WorkflowEngineModule,
],
controllers: [CirculationController],
providers: [CirculationService],
providers: [CirculationService, CirculationWorkflowService],
exports: [CirculationService],
})
export class CirculationModule {}

View File

@@ -0,0 +1,153 @@
// File: src/modules/correspondence/correspondence-workflow.service.ts
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, Repository } from 'typeorm';
import { WorkflowTransitionDto } from '../workflow-engine/dto/workflow-transition.dto';
import { WorkflowEngineService } from '../workflow-engine/workflow-engine.service';
import { CorrespondenceRevision } from './entities/correspondence-revision.entity';
import { CorrespondenceStatus } from './entities/correspondence-status.entity';
import { Correspondence } from './entities/correspondence.entity';
@Injectable()
export class CorrespondenceWorkflowService {
private readonly logger = new Logger(CorrespondenceWorkflowService.name);
private readonly WORKFLOW_CODE = 'CORRESPONDENCE_FLOW_V1';
constructor(
private readonly workflowEngine: WorkflowEngineService,
@InjectRepository(Correspondence)
private readonly correspondenceRepo: Repository<Correspondence>,
@InjectRepository(CorrespondenceRevision)
private readonly revisionRepo: Repository<CorrespondenceRevision>,
@InjectRepository(CorrespondenceStatus)
private readonly statusRepo: Repository<CorrespondenceStatus>,
private readonly dataSource: DataSource,
) {}
async submitWorkflow(
correspondenceId: number,
userId: number,
note?: string,
) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const revision = await this.revisionRepo.findOne({
// ✅ FIX: CamelCase (correspondenceId, isCurrent)
where: { correspondenceId: correspondenceId, isCurrent: true },
relations: ['correspondence'],
});
if (!revision) {
throw new NotFoundException(
`Correspondence Revision for ID ${correspondenceId} not found`,
);
}
// ✅ FIX: Check undefined before access
if (!revision.correspondence) {
throw new NotFoundException(`Correspondence relation not found`);
}
const context = {
// ✅ FIX: CamelCase (projectId, correspondenceTypeId)
projectId: revision.correspondence.projectId,
typeId: revision.correspondence.correspondenceTypeId,
ownerId: userId,
amount: 0,
priority: 'NORMAL',
};
const instance = await this.workflowEngine.createInstance(
this.WORKFLOW_CODE,
'correspondence_revision',
revision.id.toString(),
context,
);
const transitionResult = await this.workflowEngine.processTransition(
instance.id,
'SUBMIT',
userId,
note || 'Initial Submission',
{},
);
await this.syncStatus(revision, transitionResult.nextState, queryRunner);
await queryRunner.commitTransaction();
return {
instanceId: instance.id,
currentState: transitionResult.nextState,
};
} catch (error) {
await queryRunner.rollbackTransaction();
this.logger.error(`Failed to submit workflow: ${error}`);
throw error;
} finally {
await queryRunner.release();
}
}
async processAction(
instanceId: string,
userId: number,
dto: WorkflowTransitionDto,
) {
const result = await this.workflowEngine.processTransition(
instanceId,
dto.action,
userId,
dto.comment,
dto.payload,
);
// ✅ FIX: Method exists now
const instance = await this.workflowEngine.getInstanceById(instanceId);
if (instance && instance.entityType === 'correspondence_revision') {
const revision = await this.revisionRepo.findOne({
where: { id: parseInt(instance.entityId) },
});
if (revision) {
await this.syncStatus(revision, result.nextState);
}
}
return result;
}
private async syncStatus(
revision: CorrespondenceRevision,
workflowState: string,
queryRunner?: any,
) {
const statusMap: Record<string, string> = {
DRAFT: 'DRAFT',
IN_REVIEW: 'SUBOWN',
APPROVED: 'CLBOWN',
REJECTED: 'CCBOWN',
};
const targetCode = statusMap[workflowState] || 'DRAFT';
const status = await this.statusRepo.findOne({
where: { statusCode: targetCode }, // ✅ FIX: CamelCase
});
if (status) {
// ✅ FIX: CamelCase (correspondenceStatusId)
revision.statusId = status.id;
const manager = queryRunner
? queryRunner.manager
: this.revisionRepo.manager;
await manager.save(revision);
}
}
}

View File

@@ -1,22 +1,24 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CorrespondenceService } from './correspondence.service.js';
import { CorrespondenceController } from './correspondence.controller.js';
import { Correspondence } from './entities/correspondence.entity.js';
import { CorrespondenceService } from './correspondence.service.js';
import { CorrespondenceRevision } from './entities/correspondence-revision.entity.js';
import { CorrespondenceType } from './entities/correspondence-type.entity.js';
import { Correspondence } from './entities/correspondence.entity.js';
// Import Entities ใหม่
import { RoutingTemplate } from './entities/routing-template.entity.js';
import { RoutingTemplateStep } from './entities/routing-template-step.entity.js';
import { CorrespondenceRouting } from './entities/correspondence-routing.entity.js';
import { RoutingTemplateStep } from './entities/routing-template-step.entity.js';
import { RoutingTemplate } from './entities/routing-template.entity.js';
import { CorrespondenceStatus } from './entities/correspondence-status.entity.js';
import { DocumentNumberingModule } from '../document-numbering/document-numbering.module.js'; // ต้องใช้ตอน Create
import { JsonSchemaModule } from '../json-schema/json-schema.module.js'; // ต้องใช้ Validate Details
import { SearchModule } from '../search/search.module'; // ✅ 1. เพิ่ม Import SearchModule
import { UserModule } from '../user/user.module.js'; // <--- 1. Import UserModule
import { WorkflowEngineModule } from '../workflow-engine/workflow-engine.module.js'; // <--- ✅ เพิ่มบรรทัดนี้ครับ
import { CorrespondenceReference } from './entities/correspondence-reference.entity.js';
import { SearchModule } from '../search/search.module'; // ✅ 1. เพิ่ม Import SearchModule
import { CorrespondenceStatus } from './entities/correspondence-status.entity.js';
// Controllers & Services
import { CorrespondenceWorkflowService } from './correspondence-workflow.service'; // Register Service นี้
@Module({
imports: [
@@ -37,7 +39,7 @@ import { SearchModule } from '../search/search.module'; // ✅ 1. เพิ่
SearchModule, // ✅ 2. ใส่ SearchModule ที่นี่
],
controllers: [CorrespondenceController],
providers: [CorrespondenceService],
providers: [CorrespondenceService, CorrespondenceWorkflowService],
exports: [CorrespondenceService],
})
export class CorrespondenceModule {}

View File

@@ -15,14 +15,10 @@ import { VirtualColumnService } from './services/virtual-column.service';
import { CryptoService } from '../../common/services/crypto.service';
// Import Module อื่นๆ ที่จำเป็นสำหรับ Guard (ถ้า Guards อยู่ใน Common อาจจะไม่ต้อง import ที่นี่โดยตรง)
// import { UserModule } from '../user/user.module';
import { UserModule } from '../user/user.module';
@Module({
imports: [
TypeOrmModule.forFeature([JsonSchema]),
ConfigModule,
// UserModule,
],
imports: [TypeOrmModule.forFeature([JsonSchema]), ConfigModule, UserModule],
controllers: [JsonSchemaController],
providers: [
JsonSchemaService,

View File

@@ -1,172 +1,406 @@
// File: src/modules/json-schema/json-schema.controller.ts
import {
Body,
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Param,
ParseIntPipe,
Patch,
Post,
Query,
UseGuards,
} from '@nestjs/common';
import {
ApiBearerAuth,
ApiOperation,
ApiParam,
ApiResponse,
ApiTags,
} from '@nestjs/swagger';
// File: src/modules/json-schema/json-schema.service.ts
// บันทึกการแก้ไข: Fix TS2345 (undefined check)
import { JsonSchemaService } from './json-schema.service';
import { SchemaMigrationService } from './services/schema-migration.service';
import {
BadRequestException,
Injectable,
Logger,
NotFoundException,
OnModuleInit,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import Ajv, { ValidateFunction } from 'ajv';
import addFormats from 'ajv-formats';
import { Repository } from 'typeorm';
import { CreateJsonSchemaDto } from './dto/create-json-schema.dto';
import { MigrateDataDto } from './dto/migrate-data.dto';
import { SearchJsonSchemaDto } from './dto/search-json-schema.dto';
import { UpdateJsonSchemaDto } from './dto/update-json-schema.dto';
import { JsonSchema } from './entities/json-schema.entity';
import { CurrentUser } from '../../common/decorators/current-user.decorator';
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { RbacGuard } from '../../common/guards/rbac.guard';
import { User } from '../user/entities/user.entity';
// Services ย่อยที่แยกตามหน้าที่ (Single Responsibility)
import {
JsonSecurityService,
SecurityContext,
} from './services/json-security.service';
import { UiSchemaService } from './services/ui-schema.service';
import { VirtualColumnService } from './services/virtual-column.service';
import {
ValidationErrorDetail,
ValidationOptions,
ValidationResult,
} from './interfaces/validation-result.interface';
@Injectable()
export class JsonSchemaService implements OnModuleInit {
private ajv: Ajv;
private validators = new Map<string, ValidateFunction>(); // Cache สำหรับเก็บ Validator ที่ Compile แล้ว
private readonly logger = new Logger(JsonSchemaService.name);
// ค่า Default สำหรับการตรวจสอบข้อมูล
private readonly defaultOptions: ValidationOptions = {
removeAdditional: true, // ลบฟิลด์เกิน
coerceTypes: true, // แปลงชนิดข้อมูลอัตโนมัติ (เช่น "123" -> 123)
useDefaults: true, // ใส่ค่า Default ถ้าไม่มีข้อมูล
};
@ApiTags('JSON Schemas Management')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard, RbacGuard)
@Controller('json-schemas')
export class JsonSchemaController {
constructor(
private readonly jsonSchemaService: JsonSchemaService,
private readonly migrationService: SchemaMigrationService,
) {}
// ----------------------------------------------------------------------
// Schema Management (CRUD)
// ----------------------------------------------------------------------
@Post()
@ApiOperation({
summary: 'Create a new schema or new version of existing schema',
})
@ApiResponse({
status: 201,
description: 'The schema has been successfully created.',
})
@RequirePermission('system.manage_all') // Admin Only
create(@Body() createDto: CreateJsonSchemaDto) {
return this.jsonSchemaService.create(createDto);
}
@Get()
@ApiOperation({ summary: 'List all schemas with pagination and filtering' })
@RequirePermission('document.view') // Viewer+ can see schemas
findAll(@Query() searchDto: SearchJsonSchemaDto) {
return this.jsonSchemaService.findAll(searchDto);
}
@Get(':id')
@ApiOperation({ summary: 'Get a specific schema version by ID' })
@RequirePermission('document.view')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.jsonSchemaService.findOne(id);
}
@Get('latest/:code')
@ApiOperation({
summary: 'Get the latest active version of a schema by code',
})
@ApiParam({ name: 'code', description: 'Schema Code (e.g., RFA_DWG)' })
@RequirePermission('document.view')
findLatest(@Param('code') code: string) {
return this.jsonSchemaService.findLatestByCode(code);
}
@Patch(':id')
@ApiOperation({
summary: 'Update a specific schema (Not recommended for active schemas)',
})
@RequirePermission('system.manage_all')
update(
@Param('id', ParseIntPipe) id: number,
@Body() updateDto: UpdateJsonSchemaDto,
@InjectRepository(JsonSchema)
private readonly jsonSchemaRepository: Repository<JsonSchema>,
private readonly virtualColumnService: VirtualColumnService,
private readonly uiSchemaService: UiSchemaService,
private readonly jsonSecurityService: JsonSecurityService,
) {
return this.jsonSchemaService.update(id, updateDto);
// กำหนดค่าเริ่มต้นให้กับ AJV Validation Engine
this.ajv = new Ajv({
allErrors: true, // แสดง Error ทั้งหมด ไม่หยุดแค่จุดแรก
strict: false, // ไม่เคร่งครัดเกินไป (ยอมรับ Keyword แปลกๆ เช่น ui:widget)
coerceTypes: true,
useDefaults: true,
removeAdditional: true,
});
addFormats(this.ajv); // เพิ่ม Format มาตรฐาน (email, date, uri ฯลฯ)
this.registerCustomValidators(); // ลงทะเบียน Validator เฉพาะของโปรเจกต์
}
@Delete(':id')
@ApiOperation({ summary: 'Delete a schema version (Hard Delete)' })
@RequirePermission('system.manage_all')
remove(@Param('id', ParseIntPipe) id: number) {
return this.jsonSchemaService.remove(id);
async onModuleInit() {
// สามารถโหลด Schema ที่ Active ทั้งหมดมา Cache ไว้ล่วงหน้าได้ที่นี่ เพื่อความเร็วในการตอบสนองครั้งแรก
}
// ----------------------------------------------------------------------
// Validation & Security
// ----------------------------------------------------------------------
/**
* ลงทะเบียน Custom Validators เฉพาะสำหรับ LCBP3
*/
private registerCustomValidators() {
// 1. ตรวจสอบรูปแบบเลขที่เอกสาร (เช่น TEAM-RFA-STR-0001)
this.ajv.addFormat('document-number', {
type: 'string',
validate: (value: string) => {
// Regex อย่างง่าย: กลุ่มตัวอักษรขีดคั่นด้วย -
return /^[A-Z0-9]{2,10}-[A-Z]{2,5}(-[A-Z0-9]{2,5})?-\d{4}-\d{3,5}$/.test(
value,
);
},
});
@Post('validate/:code')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Validate data against the latest schema version' })
@ApiResponse({
status: 200,
description: 'Validation result including errors and sanitized data',
})
@RequirePermission('document.view')
async validate(@Param('code') code: string, @Body() data: any) {
// Note: Validation API นี้ใช้สำหรับ Test หรือ Pre-check เท่านั้น
// การ Save จริงจะเรียกผ่าน Service ภายใน
return this.jsonSchemaService.validateData(code, data);
// 2. Keyword สำหรับระบุ Role ที่จำเป็น (ใช้ร่วมกับ Security Service)
this.ajv.addKeyword({
keyword: 'requiredRole',
type: 'string',
metaSchema: { type: 'string' },
validate: (schema: string, data: any) => true, // ผ่านเสมอในขั้น AJV (Security Service จะจัดการเอง)
});
}
@Post('read/:code')
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'Process read data (Decrypt & Filter) based on user roles',
})
@RequirePermission('document.view')
/**
* สร้าง Schema ใหม่ พร้อมจัดการ Version, UI Schema และ Virtual Columns
*/
async create(createDto: CreateJsonSchemaDto): Promise<JsonSchema> {
// 1. ตรวจสอบความถูกต้องของ JSON Schema Definition (AJV Syntax)
try {
this.ajv.compile(createDto.schemaDefinition);
} catch (error: any) {
throw new BadRequestException(
`Invalid JSON Schema format: ${error.message}`,
);
}
// 2. จัดการ UI Schema
if (createDto.uiSchema) {
// ถ้าส่งมา ให้ตรวจสอบความถูกต้องเทียบกับ Data Schema
this.uiSchemaService.validateUiSchema(
createDto.uiSchema as any,
createDto.schemaDefinition,
);
} else {
// ถ้าไม่ส่งมา ให้สร้าง UI Schema พื้นฐานให้อัตโนมัติ
createDto.uiSchema = this.uiSchemaService.generateDefaultUiSchema(
createDto.schemaDefinition,
);
}
// 3. จัดการ Versioning อัตโนมัติ (Auto-increment)
const latestSchema = await this.jsonSchemaRepository.findOne({
where: { schemaCode: createDto.schemaCode },
order: { version: 'DESC' },
});
let newVersion = 1;
if (latestSchema) {
// ถ้าผู้ใช้ไม่ระบุ Version หรือระบุมาน้อยกว่าล่าสุด ให้ +1
if (!createDto.version || createDto.version <= latestSchema.version) {
newVersion = latestSchema.version + 1;
} else {
newVersion = createDto.version;
}
} else if (createDto.version) {
newVersion = createDto.version;
}
// 4. บันทึกลงฐานข้อมูล
const newSchema = this.jsonSchemaRepository.create({
...createDto,
version: newVersion,
});
const savedSchema = await this.jsonSchemaRepository.save(newSchema);
// ล้าง Cache เพื่อให้โหลดตัวใหม่ในครั้งถัดไป
this.validators.delete(savedSchema.schemaCode);
this.logger.log(
`Schema '${savedSchema.schemaCode}' created (v${savedSchema.version})`,
);
// 5. สร้าง/อัปเดต Virtual Columns บน Database จริง (Performance Optimization)
// Fix TS2345: Add empty array fallback
if (savedSchema.virtualColumns && savedSchema.virtualColumns.length > 0) {
await this.virtualColumnService.setupVirtualColumns(
savedSchema.tableName,
savedSchema.virtualColumns || [],
);
}
return savedSchema;
}
/**
* ค้นหา Schema ทั้งหมด (Pagination & Filter)
*/
async findAll(searchDto: SearchJsonSchemaDto) {
const { search, isActive, page = 1, limit = 20 } = searchDto;
const skip = (page - 1) * limit;
const query = this.jsonSchemaRepository.createQueryBuilder('schema');
if (search) {
query.andWhere('schema.schemaCode LIKE :search', {
search: `%${search}%`,
});
}
if (isActive !== undefined) {
query.andWhere('schema.isActive = :isActive', { isActive });
}
// เรียงตาม Code ก่อน แล้วตามด้วย Version ล่าสุด
query.orderBy('schema.schemaCode', 'ASC');
query.addOrderBy('schema.version', 'DESC');
const [items, total] = await query.skip(skip).take(limit).getManyAndCount();
return {
data: items,
meta: {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
},
};
}
/**
* ดึงข้อมูล Schema ตาม ID
*/
async findOne(id: number): Promise<JsonSchema> {
const schema = await this.jsonSchemaRepository.findOne({ where: { id } });
if (!schema) {
throw new NotFoundException(`JsonSchema with ID ${id} not found`);
}
return schema;
}
/**
* ดึงข้อมูล Schema ตาม Code และ Version (สำหรับ Migration)
*/
async findOneByCodeAndVersion(
code: string,
version: number,
): Promise<JsonSchema> {
const schema = await this.jsonSchemaRepository.findOne({
where: { schemaCode: code, version },
});
if (!schema) {
throw new NotFoundException(
`JsonSchema '${code}' version ${version} not found`,
);
}
return schema;
}
/**
* ดึง Schema เวอร์ชันล่าสุดที่ Active (สำหรับใช้งานทั่วไป)
*/
async findLatestByCode(code: string): Promise<JsonSchema> {
const schema = await this.jsonSchemaRepository.findOne({
where: { schemaCode: code, isActive: true },
order: { version: 'DESC' },
});
if (!schema) {
throw new NotFoundException(
`Active JsonSchema with code '${code}' not found`,
);
}
return schema;
}
/**
* [CORE FUNCTION] ตรวจสอบข้อมูล (Validate), ทำความสะอาด (Sanitize) และเข้ารหัส (Encrypt)
* ใช้สำหรับ "ขาเข้า" (Write) ก่อนบันทึกลง Database
*/
async validateData(
schemaCode: string,
data: any,
options: ValidationOptions = {},
): Promise<ValidationResult> {
// 1. ดึงและ Compile Validator
const validate = await this.getValidator(schemaCode);
const schema = await this.findLatestByCode(schemaCode); // ดึง Full Schema เพื่อใช้ Config อื่นๆ
// 2. สำเนาข้อมูลเพื่อป้องกัน Side Effect และเตรียมสำหรับ AJV Mutation (Sanitization)
const dataToValidate = JSON.parse(JSON.stringify(data));
// 3. เริ่มการตรวจสอบ (AJV จะทำการ Coerce Type และ Remove Additional Properties ให้ด้วย)
const valid = validate(dataToValidate);
// 4. จัดการกรณีข้อมูลไม่ถูกต้อง
if (!valid) {
const errors: ValidationErrorDetail[] = (validate.errors || []).map(
(err) => ({
field: err.instancePath || 'root',
message: err.message || 'Validation error',
value: err.params,
}),
);
return {
isValid: false,
errors,
sanitizedData: null,
};
}
// 5. เข้ารหัสข้อมูล (Encryption) สำหรับ Field ที่มีความลับ (x-encrypt: true)
const secureData = this.jsonSecurityService.encryptFields(
dataToValidate,
schema.schemaDefinition,
);
return {
isValid: true,
errors: [],
sanitizedData: secureData, // ข้อมูลนี้สะอาดและปลอดภัย พร้อมบันทึก
};
}
/**
* [CORE FUNCTION] อ่านข้อมูล, ถอดรหัส (Decrypt) และกรองตามสิทธิ์ (Filter)
* ใช้สำหรับ "ขาออก" (Read) ก่อนส่งให้ Frontend
*/
async processReadData(
@Param('code') code: string,
@Body() data: any,
@CurrentUser() user: User,
) {
// แปลง User Entity เป็น Security Context
// แก้ไข TS2339 & TS7006: Type Casting เพื่อให้เข้าถึง roles ได้โดยไม่ error
// เนื่องจาก User Entity ปกติไม่มี property roles (แต่อาจถูก Inject มาตอน Runtime หรือผ่าน Assignments)
const userWithRoles = user as any;
const userRoles = userWithRoles.roles
? userWithRoles.roles.map((r: any) => r.roleName)
: [];
schemaCode: string,
data: any,
userContext: SecurityContext,
): Promise<any> {
if (!data) return data;
return this.jsonSchemaService.processReadData(code, data, { userRoles });
}
// ดึง Schema เพื่อดู Config การถอดรหัสและการมองเห็น
const schema = await this.findLatestByCode(schemaCode);
// ----------------------------------------------------------------------
// Data Migration
// ----------------------------------------------------------------------
@Post('migrate/:table/:id')
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'Migrate specific entity data to target schema version',
})
@ApiParam({ name: 'table', description: 'Table Name (e.g. rfa_revisions)' })
@ApiParam({ name: 'id', description: 'Entity ID' })
@RequirePermission('system.manage_all') // Dangerous Op -> Admin Only
async migrateData(
@Param('table') tableName: string,
@Param('id', ParseIntPipe) id: number,
@Body() dto: MigrateDataDto,
) {
return this.migrationService.migrateData(
tableName,
id,
dto.targetSchemaCode,
dto.targetVersion,
return this.jsonSecurityService.decryptAndFilterFields(
data,
schema.schemaDefinition,
userContext,
);
}
/**
* Helper: ดึงและ Cache AJV Validator Function เพื่อประสิทธิภาพ
*/
private async getValidator(schemaCode: string): Promise<ValidateFunction> {
let validate = this.validators.get(schemaCode);
if (!validate) {
const schema = await this.findLatestByCode(schemaCode);
try {
validate = this.ajv.compile(schema.schemaDefinition);
this.validators.set(schemaCode, validate);
} catch (error: any) {
throw new BadRequestException(
`Invalid Schema Definition for '${schemaCode}': ${error.message}`,
);
}
}
return validate;
}
/**
* Wrapper เก่าสำหรับ Backward Compatibility (ถ้ามีโค้ดเก่าเรียกใช้)
*/
async validate(schemaCode: string, data: any): Promise<boolean> {
const result = await this.validateData(schemaCode, data);
if (!result.isValid) {
const errorMsg = result.errors
.map((e) => `${e.field}: ${e.message}`)
.join(', ');
throw new BadRequestException(`JSON Validation Failed: ${errorMsg}`);
}
return true;
}
/**
* อัปเดตข้อมูล Schema และจัดการผลกระทบ (Virtual Columns / UI Schema)
*/
async update(
id: number,
updateDto: UpdateJsonSchemaDto,
): Promise<JsonSchema> {
const schema = await this.findOne(id);
// ตรวจสอบ JSON Schema
if (updateDto.schemaDefinition) {
try {
this.ajv.compile(updateDto.schemaDefinition);
} catch (error: any) {
throw new BadRequestException(
`Invalid JSON Schema format: ${error.message}`,
);
}
this.validators.delete(schema.schemaCode); // เคลียร์ Cache เก่า
}
// ตรวจสอบ UI Schema
if (updateDto.uiSchema) {
this.uiSchemaService.validateUiSchema(
updateDto.uiSchema as any,
updateDto.schemaDefinition || schema.schemaDefinition,
);
}
const updatedSchema = this.jsonSchemaRepository.merge(schema, updateDto);
const savedSchema = await this.jsonSchemaRepository.save(updatedSchema);
// อัปเดต Virtual Columns ใน Database ถ้ามีการเปลี่ยนแปลง Config
// Fix TS2345: Add empty array fallback
if (updateDto.virtualColumns && updatedSchema.virtualColumns) {
await this.virtualColumnService.setupVirtualColumns(
savedSchema.tableName,
savedSchema.virtualColumns || [],
);
}
return savedSchema;
}
/**
* ลบ Schema (Hard Delete)
*/
async remove(id: number): Promise<void> {
const schema = await this.findOne(id);
this.validators.delete(schema.schemaCode);
await this.jsonSchemaRepository.remove(schema);
}
}

View File

@@ -1,52 +1,66 @@
// File: src/modules/rfa/dto/create-rfa-revision.dto.ts
// File: src/modules/rfa/dto/create-rfa.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import {
IsString,
IsNotEmpty,
IsInt,
IsOptional,
IsDateString,
IsObject,
IsArray,
IsDateString,
IsInt,
IsNotEmpty,
IsObject,
IsOptional,
IsString,
} from 'class-validator';
export class CreateRfaRevisionDto {
export class CreateRfaDto {
@ApiProperty({ description: 'ID ของโครงการ', example: 1 })
@IsInt()
@IsNotEmpty()
projectId!: number;
@ApiProperty({ description: 'ID ของประเภท RFA', example: 1 })
@IsInt()
@IsNotEmpty()
rfaTypeId!: number;
@ApiProperty({
description: 'ID ของสาขางาน (Discipline) ตาม Req 6B',
example: 1,
})
@IsInt()
@IsOptional() // Optional ไว้ก่อนเผื่อบางโครงการไม่บังคับ
disciplineId?: number;
@ApiProperty({
description: 'หัวข้อเอกสาร',
example: 'Submission of Shop Drawing for Building A',
})
@IsString()
@IsNotEmpty()
title!: string;
@IsInt()
@IsNotEmpty()
rfaStatusCodeId!: number;
@IsInt()
@IsOptional()
rfaApproveCodeId?: number;
@IsDateString()
@IsOptional()
documentDate?: string;
@IsDateString()
@IsOptional()
issuedDate?: string;
@IsDateString()
@IsOptional()
receivedDate?: string;
@IsDateString()
@IsOptional()
approvedDate?: string;
@ApiProperty({ description: 'รายละเอียดเพิ่มเติม', required: false })
@IsString()
@IsOptional()
description?: string;
@ApiProperty({ description: 'วันที่ในเอกสาร', required: false })
@IsDateString()
@IsOptional()
documentDate?: string;
@ApiProperty({
description: 'ข้อมูล Dynamic Details (JSON)',
required: false,
})
@IsObject()
@IsOptional()
details?: Record<string, any>;
@ApiProperty({
description: 'รายการ Shop Drawing Revisions ที่แนบมาด้วย',
required: false,
type: [Number],
})
@IsArray()
@IsOptional()
shopDrawingRevisionIds?: number[]; // IDs of linked Shop Drawings
shopDrawingRevisionIds?: number[];
}

View File

@@ -0,0 +1,13 @@
// File: src/modules/rfa/dto/submit-rfa.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { IsInt, IsNotEmpty } from 'class-validator';
export class SubmitRfaDto {
@ApiProperty({
description: 'ID ของ Routing Template ที่จะใช้เดินเรื่อง',
example: 1,
})
@IsInt()
@IsNotEmpty()
templateId!: number;
}

View File

@@ -1,23 +1,21 @@
// File: src/modules/rfa/entities/rfa-revision.entity.ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
ManyToOne,
Entity,
JoinColumn,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
Unique,
Index,
} from 'typeorm';
import { Rfa } from './rfa.entity';
import { Correspondence } from '../../correspondence/entities/correspondence.entity';
import { RfaStatusCode } from './rfa-status-code.entity';
import { RfaApproveCode } from './rfa-approve-code.entity';
import { User } from '../../user/entities/user.entity';
import { RfaApproveCode } from './rfa-approve-code.entity';
import { RfaItem } from './rfa-item.entity';
import { RfaStatusCode } from './rfa-status-code.entity';
import { RfaWorkflow } from './rfa-workflow.entity';
import { Rfa } from './rfa.entity';
@Entity('rfa_revisions')
@Unique(['rfaId', 'revisionNumber'])
@@ -65,11 +63,16 @@ export class RfaRevision {
@Column({ type: 'text', nullable: true })
description?: string;
// ✅ [New] เพิ่ม field details สำหรับเก็บข้อมูล Dynamic ของ RFA (เช่น Method Statement Details)
// --- JSON & Schema Section ---
@Column({ type: 'json', nullable: true })
details?: any;
// ✅ [New] Virtual Column: ดึงจำนวนแบบที่แนบ (drawingCount) จาก JSON
// ✅ [New] จำเป็นสำหรับ Data Migration (T2.5.5)
@Column({ name: 'schema_version', default: 1 })
schemaVersion!: number;
// ✅ Virtual Column
@Column({
name: 'v_ref_drawing_count',
type: 'int',
@@ -79,6 +82,8 @@ export class RfaRevision {
})
vRefDrawingCount?: number;
// --- Timestamp ---
@CreateDateColumn({ name: 'created_at' })
createdAt!: Date;
@@ -110,11 +115,9 @@ export class RfaRevision {
@JoinColumn({ name: 'created_by' })
creator?: User;
// Items (Shop Drawings inside this RFA)
@OneToMany(() => RfaItem, (item) => item.rfaRevision, { cascade: true })
items!: RfaItem[];
// Workflows
@OneToMany(() => RfaWorkflow, (workflow) => workflow.rfaRevision, {
cascade: true,
})

View File

@@ -1,15 +1,17 @@
// File: src/modules/rfa/entities/rfa-workflow.entity.ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
ManyToOne,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { RfaRevision } from './rfa-revision.entity';
import { Organization } from '../../project/entities/organization.entity';
import { User } from '../../user/entities/user.entity';
import { RfaRevision } from './rfa-revision.entity';
import { RfaActionType } from './rfa-workflow-template-step.entity'; // ✅ Import Enum
@Entity('rfa_workflows')
export class RfaWorkflow {
@@ -31,10 +33,10 @@ export class RfaWorkflow {
@Column({
name: 'action_type',
type: 'enum',
enum: ['REVIEW', 'APPROVE', 'ACKNOWLEDGE'],
enum: RfaActionType, // ✅ Use Shared Enum
nullable: true,
})
actionType?: string;
actionType?: RfaActionType;
@Column({
type: 'enum',
@@ -50,7 +52,7 @@ export class RfaWorkflow {
completedAt?: Date;
@Column({ type: 'json', nullable: true })
stateContext?: Record<string, any>; // เก็บ Snapshot ข้อมูล ณ ขณะนั้น
stateContext?: Record<string, any>;
@CreateDateColumn({ name: 'created_at' })
createdAt!: Date;
@@ -59,7 +61,7 @@ export class RfaWorkflow {
updatedAt!: Date;
// Relations
@ManyToOne(() => RfaRevision, (rev) => rev.workflows, { onDelete: 'CASCADE' }) // ต้องไปเพิ่ม Property workflows ใน RfaRevision ด้วย
@ManyToOne(() => RfaRevision, (rev) => rev.workflows, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'rfa_revision_id' })
rfaRevision!: RfaRevision;

View File

@@ -1,16 +1,17 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
DeleteDateColumn,
ManyToOne,
Entity,
JoinColumn,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { RfaType } from './rfa-type.entity';
import { Discipline } from '../../master/entities/discipline.entity'; // Import ใหม่
import { User } from '../../user/entities/user.entity';
import { RfaRevision } from './rfa-revision.entity';
import { RfaType } from './rfa-type.entity';
@Entity('rfas')
export class Rfa {
@@ -34,6 +35,11 @@ export class Rfa {
@JoinColumn({ name: 'rfa_type_id' })
rfaType!: RfaType;
// ✅ [NEW] Relation
@ManyToOne(() => Discipline)
@JoinColumn({ name: 'discipline_id' })
discipline?: Discipline;
@ManyToOne(() => User)
@JoinColumn({ name: 'created_by' })
creator?: User;

View File

@@ -0,0 +1,193 @@
// File: src/modules/rfa/rfa-workflow.service.ts
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, Repository } from 'typeorm';
// Modules
import { WorkflowEngineService } from '../workflow-engine/workflow-engine.service';
// Entities
import { RfaApproveCode } from './entities/rfa-approve-code.entity';
import { RfaRevision } from './entities/rfa-revision.entity';
import { RfaStatusCode } from './entities/rfa-status-code.entity';
import { Rfa } from './entities/rfa.entity';
// DTOs
import { WorkflowTransitionDto } from '../workflow-engine/dto/workflow-transition.dto';
@Injectable()
export class RfaWorkflowService {
private readonly logger = new Logger(RfaWorkflowService.name);
private readonly WORKFLOW_CODE = 'RFA_FLOW_V1'; // ควรกำหนดใน Config หรือ Enum
constructor(
private readonly workflowEngine: WorkflowEngineService,
@InjectRepository(Rfa)
private readonly rfaRepo: Repository<Rfa>,
@InjectRepository(RfaRevision)
private readonly revisionRepo: Repository<RfaRevision>,
@InjectRepository(RfaStatusCode)
private readonly statusRepo: Repository<RfaStatusCode>,
@InjectRepository(RfaApproveCode)
private readonly approveCodeRepo: Repository<RfaApproveCode>,
private readonly dataSource: DataSource,
) {}
/**
* เริ่มต้น Workflow สำหรับเอกสาร RFA (เมื่อกด Submit)
*/
async submitWorkflow(rfaId: number, userId: number, note?: string) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
// 1. ดึงข้อมูล Revision ปัจจุบัน
const revision = await this.revisionRepo.findOne({
where: { id: rfaId, isCurrent: true },
relations: ['rfa'],
});
if (!revision) {
throw new NotFoundException(
`Current Revision for RFA ID ${rfaId} not found`,
);
}
// 2. สร้าง Context (ข้อมูลประกอบการตัดสินใจ)
const context = {
rfaType: revision.rfa.rfaTypeId,
discipline: revision.rfa.discipline,
ownerId: userId,
// อาจเพิ่มเงื่อนไขอื่นๆ เช่น จำนวนวัน, ความเร่งด่วน
};
// 3. สร้าง Workflow Instance
// Entity Type = 'rfa_revision'
const instance = await this.workflowEngine.createInstance(
this.WORKFLOW_CODE,
'rfa_revision',
revision.id.toString(),
context,
);
// 4. Auto Transition: SUBMIT
const transitionResult = await this.workflowEngine.processTransition(
instance.id,
'SUBMIT',
userId,
note || 'RFA Submitted',
{},
);
// 5. Sync สถานะกลับตาราง RFA Revision
await this.syncStatus(
revision,
transitionResult.nextState,
undefined,
queryRunner,
);
await queryRunner.commitTransaction();
this.logger.log(
`Started workflow for RFA #${rfaId} (Instance: ${instance.id})`,
);
return {
instanceId: instance.id,
currentState: transitionResult.nextState,
};
} catch (error) {
await queryRunner.rollbackTransaction();
this.logger.error(`Failed to submit RFA workflow: ${error}`);
throw error;
} finally {
await queryRunner.release();
}
}
/**
* ดำเนินการอนุมัติ/ตรวจสอบ RFA
*/
async processAction(
instanceId: string,
userId: number,
dto: WorkflowTransitionDto,
) {
// 1. ส่งคำสั่งให้ Engine ประมวลผล
const result = await this.workflowEngine.processTransition(
instanceId,
dto.action,
userId,
dto.comment,
dto.payload,
);
// 2. Sync สถานะกลับตารางเดิม
const instance = await this.workflowEngine.getInstanceById(instanceId);
if (instance && instance.entityType === 'rfa_revision') {
const revision = await this.revisionRepo.findOne({
where: { id: parseInt(instance.entityId) },
});
if (revision) {
// เช็คว่า Action นี้มีการระบุ Approve Code มาใน Payload หรือไม่ (เช่น '1A', '3R')
const approveCodeStr = dto.payload?.approveCode;
await this.syncStatus(revision, result.nextState, approveCodeStr);
}
}
return result;
}
/**
* Helper: Map Workflow State -> RFA Status & Approve Code
*/
private async syncStatus(
revision: RfaRevision,
workflowState: string,
approveCodeStr?: string, // เช่น '1A', '1C'
queryRunner?: any,
) {
// 1. Map Workflow State -> RFA Status Code (DFT, FAP, FCO...)
const statusMap: Record<string, string> = {
DRAFT: 'DFT',
IN_REVIEW_CSC: 'FRE', // For Review (CSC)
IN_REVIEW_OWNER: 'FAP', // For Approve (Owner)
APPROVED: 'FCO', // For Construction (ตัวอย่าง)
REJECTED: 'CC', // Canceled/Rejected
REVISE: 'DFT', // กลับไปแก้ (Draft)
};
const targetStatusCode = statusMap[workflowState] || 'DFT';
const status = await this.statusRepo.findOne({
where: { statusCode: targetStatusCode },
});
if (status) {
revision.rfaStatusCodeId = status.id;
}
// 2. Map Approve Code (ถ้ามี)
if (approveCodeStr) {
const approveCode = await this.approveCodeRepo.findOne({
where: { approveCode: approveCodeStr },
});
if (approveCode) {
revision.rfaApproveCodeId = approveCode.id;
revision.approvedDate = new Date(); // บันทึกวันที่อนุมัติ
}
}
// 3. Save
const manager = queryRunner
? queryRunner.manager
: this.revisionRepo.manager;
await manager.save(revision);
this.logger.log(
`Synced RFA Status Revision ${revision.id}: State=${workflowState} -> Status=${targetStatusCode}, AppCode=${approveCodeStr}`,
);
}
}

View File

@@ -1,24 +1,26 @@
// File: src/modules/rfa/rfa.controller.ts
import {
Body,
Controller,
Get,
Post,
Body,
Param,
ParseIntPipe,
Post,
UseGuards,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { RfaService } from './rfa.service';
import { CreateRfaDto } from './dto/create-rfa.dto';
import { WorkflowActionDto } from '../correspondence/dto/workflow-action.dto'; // Reuse DTO
import { WorkflowActionDto } from '../correspondence/dto/workflow-action.dto';
import { User } from '../user/entities/user.entity';
import { CreateRfaDto } from './dto/create-rfa.dto';
import { SubmitRfaDto } from './dto/submit-rfa.dto'; // ✅ Import DTO ใหม่
import { RfaService } from './rfa.service';
import { Audit } from '../../common/decorators/audit.decorator';
import { CurrentUser } from '../../common/decorators/current-user.decorator';
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { RbacGuard } from '../../common/guards/rbac.guard';
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
import { CurrentUser } from '../../common/decorators/current-user.decorator';
import { Audit } from '../../common/decorators/audit.decorator'; // Import
@ApiTags('RFA (Request for Approval)')
@ApiBearerAuth()
@@ -29,26 +31,28 @@ export class RfaController {
@Post()
@ApiOperation({ summary: 'Create new RFA (Draft)' })
@RequirePermission('rfa.create') // สิทธิ์ ID 37
@Audit('rfa.create', 'rfa') // ✅ แปะตรงนี้
@RequirePermission('rfa.create')
@Audit('rfa.create', 'rfa')
create(@Body() createDto: CreateRfaDto, @CurrentUser() user: User) {
return this.rfaService.create(createDto, user);
}
@Post(':id/submit')
@ApiOperation({ summary: 'Submit RFA to Workflow' })
@RequirePermission('rfa.create') // ผู้สร้างมีสิทธิ์ส่ง
@RequirePermission('rfa.create')
@Audit('rfa.submit', 'rfa')
submit(
@Param('id', ParseIntPipe) id: number,
@Body('templateId', ParseIntPipe) templateId: number, // รับ Template ID
@Body() submitDto: SubmitRfaDto, // ✅ ใช้ DTO
@CurrentUser() user: User,
) {
return this.rfaService.submit(id, templateId, user);
return this.rfaService.submit(id, submitDto.templateId, user);
}
@Post(':id/action')
@ApiOperation({ summary: 'Process Workflow Action (Approve/Reject)' })
@RequirePermission('workflow.action_review') // สิทธิ์ในการ Approve/Review
@RequirePermission('workflow.action_review')
@Audit('rfa.action', 'rfa')
processAction(
@Param('id', ParseIntPipe) id: number,
@Body() actionDto: WorkflowActionDto,

View File

@@ -3,35 +3,34 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
// Entities
import { Rfa } from './entities/rfa.entity';
import { RfaRevision } from './entities/rfa-revision.entity';
import { RfaItem } from './entities/rfa-item.entity';
import { RfaType } from './entities/rfa-type.entity';
import { RfaStatusCode } from './entities/rfa-status-code.entity';
import { RfaApproveCode } from './entities/rfa-approve-code.entity';
import { Correspondence } from '../correspondence/entities/correspondence.entity';
import { ShopDrawingRevision } from '../drawing/entities/shop-drawing-revision.entity';
import { RfaWorkflow } from './entities/rfa-workflow.entity';
import { RfaWorkflowTemplate } from './entities/rfa-workflow-template.entity';
import { RfaWorkflowTemplateStep } from './entities/rfa-workflow-template-step.entity';
import { CorrespondenceRouting } from '../correspondence/entities/correspondence-routing.entity';
import { Correspondence } from '../correspondence/entities/correspondence.entity';
import { RoutingTemplate } from '../correspondence/entities/routing-template.entity';
// หมายเหตุ: ตรวจสอบชื่อไฟล์ Entity ให้ตรงกับที่มีจริง (บางทีอาจชื่อ RoutingTemplate)
import { ShopDrawingRevision } from '../drawing/entities/shop-drawing-revision.entity';
import { RfaApproveCode } from './entities/rfa-approve-code.entity';
import { RfaItem } from './entities/rfa-item.entity';
import { RfaRevision } from './entities/rfa-revision.entity';
import { RfaStatusCode } from './entities/rfa-status-code.entity';
import { RfaType } from './entities/rfa-type.entity';
import { RfaWorkflowTemplateStep } from './entities/rfa-workflow-template-step.entity';
import { RfaWorkflowTemplate } from './entities/rfa-workflow-template.entity';
import { RfaWorkflow } from './entities/rfa-workflow.entity';
import { Rfa } from './entities/rfa.entity';
// Services & Controllers
import { RfaService } from './rfa.service';
import { RfaWorkflowService } from './rfa-workflow.service'; // Register Service
import { RfaController } from './rfa.controller';
import { RfaService } from './rfa.service';
// External Modules
import { DocumentNumberingModule } from '../document-numbering/document-numbering.module';
import { UserModule } from '../user/user.module';
import { NotificationModule } from '../notification/notification.module';
import { SearchModule } from '../search/search.module';
import { WorkflowEngineModule } from '../workflow-engine/workflow-engine.module'; // ✅ Import
import { NotificationModule } from '../notification/notification.module'; // ✅ เพิ่ม NotificationModule
import { UserModule } from '../user/user.module';
import { WorkflowEngineModule } from '../workflow-engine/workflow-engine.module';
@Module({
imports: [
// 1. Register Entities (เฉพาะ Entity เท่านั้น ห้ามใส่ Module)
TypeOrmModule.forFeature([
Rfa,
RfaRevision,
@@ -47,15 +46,13 @@ import { NotificationModule } from '../notification/notification.module'; // ✅
CorrespondenceRouting,
RoutingTemplate,
]),
// 2. Import External Modules (Services ที่ Inject เข้ามา)
DocumentNumberingModule,
UserModule,
SearchModule,
WorkflowEngineModule, // ✅ ย้ายมาใส่ตรงนี้ (imports หลัก)
NotificationModule, // ✅ เพิ่มตรงนี้ เพื่อแก้ dependency index [13]
WorkflowEngineModule,
NotificationModule,
],
providers: [RfaService],
providers: [RfaService, RfaWorkflowService],
controllers: [RfaController],
exports: [RfaService],
})

View File

@@ -1,42 +1,41 @@
// File: src/modules/rfa/rfa.service.ts
import {
Injectable,
NotFoundException,
InternalServerErrorException,
Logger,
BadRequestException,
ForbiddenException,
Injectable,
InternalServerErrorException,
Logger,
NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource, In } from 'typeorm';
import { DataSource, In, Repository } from 'typeorm';
// Entities
import { Rfa } from './entities/rfa.entity.js';
import { RfaRevision } from './entities/rfa-revision.entity.js';
import { RfaItem } from './entities/rfa-item.entity.js';
import { RfaType } from './entities/rfa-type.entity.js';
import { RfaStatusCode } from './entities/rfa-status-code.entity.js';
import { RfaApproveCode } from './entities/rfa-approve-code.entity.js';
import { Correspondence } from '../correspondence/entities/correspondence.entity.js';
import { CorrespondenceRouting } from '../correspondence/entities/correspondence-routing.entity.js';
import { RoutingTemplate } from '../correspondence/entities/routing-template.entity.js';
import { ShopDrawingRevision } from '../drawing/entities/shop-drawing-revision.entity.js';
import { User } from '../user/entities/user.entity.js';
import { CorrespondenceRouting } from '../correspondence/entities/correspondence-routing.entity';
import { Correspondence } from '../correspondence/entities/correspondence.entity';
import { RoutingTemplate } from '../correspondence/entities/routing-template.entity';
import { ShopDrawingRevision } from '../drawing/entities/shop-drawing-revision.entity';
import { User } from '../user/entities/user.entity';
import { RfaApproveCode } from './entities/rfa-approve-code.entity';
import { RfaItem } from './entities/rfa-item.entity';
import { RfaRevision } from './entities/rfa-revision.entity';
import { RfaStatusCode } from './entities/rfa-status-code.entity';
import { RfaType } from './entities/rfa-type.entity';
import { Rfa } from './entities/rfa.entity';
// DTOs
import { CreateRfaDto } from './dto/create-rfa.dto.js';
import { WorkflowActionDto } from '../correspondence/dto/workflow-action.dto.js';
import { WorkflowActionDto } from '../correspondence/dto/workflow-action.dto';
import { CreateRfaDto } from './dto/create-rfa.dto';
// Interfaces & Enums
import { WorkflowAction } from '../workflow-engine/interfaces/workflow.interface.js';
// Services
import { DocumentNumberingService } from '../document-numbering/document-numbering.service.js';
import { UserService } from '../user/user.service.js';
import { WorkflowEngineService } from '../workflow-engine/workflow-engine.service.js';
import { NotificationService } from '../notification/notification.service.js';
import { SearchService } from '../search/search.service.js';
import { DocumentNumberingService } from '../document-numbering/document-numbering.service';
import { NotificationService } from '../notification/notification.service';
import { SearchService } from '../search/search.service';
import { UserService } from '../user/user.service';
import { WorkflowEngineService } from '../workflow-engine/workflow-engine.service';
@Injectable()
export class RfaService {
@@ -87,6 +86,7 @@ export class RfaService {
);
}
// Determine User Organization
let userOrgId = user.primaryOrganizationId;
if (!userOrgId) {
const fullUser = await this.userService.findOne(user.user_id);
@@ -101,14 +101,14 @@ export class RfaService {
await queryRunner.startTransaction();
try {
const orgCode = 'ORG'; // TODO: Fetch real ORG Code
const orgCode = 'ORG'; // TODO: Fetch real ORG Code from Org Service if needed
// [FIXED] เรียกใช้แบบ Object Context พร้อม disciplineId
// [UPDATED] Generate Document Number with Discipline
const docNumber = await this.numberingService.generateNextNumber({
projectId: createDto.projectId,
originatorId: userOrgId,
typeId: createDto.rfaTypeId, // RFA Type ใช้เป็น ID ในการนับเลข
disciplineId: createDto.disciplineId, // สำคัญมากสำหรับ RFA (Req 6B)
typeId: createDto.rfaTypeId,
disciplineId: createDto.disciplineId ?? 0, // ✅ ส่ง disciplineId ไปด้วย (0 ถ้าไม่มี)
year: new Date().getFullYear(),
customTokens: {
TYPE_CODE: rfaType.typeCode,
@@ -116,24 +116,31 @@ export class RfaService {
},
});
// 1. Create Correspondence Record
const correspondence = queryRunner.manager.create(Correspondence, {
correspondenceNumber: docNumber,
correspondenceTypeId: createDto.rfaTypeId, // Map RFA Type to Corr Type ID
disciplineId: createDto.disciplineId, // บันทึก Discipline
correspondenceTypeId: createDto.rfaTypeId, // Assuming RFA Type maps directly or via logic
// Note: ถ้า CorrespondenceType แยก ID กับ RFA Type ต้อง Map ให้ถูก
// ในที่นี้สมมติว่าใช้ ID เดียวกัน หรือ RFA Type เป็น SubType ของ Correspondence
projectId: createDto.projectId,
originatorId: userOrgId,
isInternal: false,
isInternalCommunication: false,
createdBy: user.user_id,
// ✅ Add disciplineId column if correspondence table supports it (as per Data Dictionary Update)
// disciplineId: createDto.disciplineId
});
const savedCorr = await queryRunner.manager.save(correspondence);
// 2. Create RFA Master Record
const rfa = queryRunner.manager.create(Rfa, {
rfaTypeId: createDto.rfaTypeId,
disciplineId: createDto.disciplineId, // บันทึก Discipline
createdBy: user.user_id,
// ✅ ถ้า Entity Rfa มี disciplineId ให้ใส่ตรงนี้ด้วย
// disciplineId: createDto.disciplineId
});
const savedRfa = await queryRunner.manager.save(rfa);
// 3. Create First Revision (Draft)
const rfaRevision = queryRunner.manager.create(RfaRevision, {
correspondenceId: savedCorr.id,
rfaId: savedRfa.id,
@@ -147,9 +154,12 @@ export class RfaService {
? new Date(createDto.documentDate)
: new Date(),
createdBy: user.user_id,
details: createDto.details, // ✅ Save JSON Details
schemaVersion: 1, // ✅ Default Schema Version
});
const savedRevision = await queryRunner.manager.save(rfaRevision);
// 4. Link Shop Drawings
if (
createDto.shopDrawingRevisionIds &&
createDto.shopDrawingRevisionIds.length > 0
@@ -164,7 +174,8 @@ export class RfaService {
const rfaItems = shopDrawings.map((sd) =>
queryRunner.manager.create(RfaItem, {
rfaRevisionId: savedCorr.id,
rfaRevisionId: savedCorr.id, // ใช้ ID ของ Correspondence (ตาม Schema ที่ออกแบบไว้) หรือ RFA Revision ID แล้วแต่การ Map Entity
// ตาม Entity RfaItem ที่ให้มา: rfaRevisionId map ไปที่ correspondence_id
shopDrawingRevisionId: sd.id,
}),
);
@@ -173,9 +184,10 @@ export class RfaService {
await queryRunner.commitTransaction();
// Indexing for Search
this.searchService.indexDocument({
id: savedCorr.id,
type: 'correspondence',
type: 'rfa',
docNumber: docNumber,
title: createDto.title,
description: createDto.description,
@@ -186,10 +198,8 @@ export class RfaService {
return {
...savedRfa,
currentRevision: {
...savedRevision,
correspondenceNumber: docNumber,
},
correspondenceNumber: docNumber,
currentRevision: savedRevision,
};
} catch (err) {
await queryRunner.rollbackTransaction();
@@ -200,7 +210,8 @@ export class RfaService {
}
}
// ... (method อื่นๆ findOne, submit, processAction คงเดิม)
// ... (ส่วน findOne, submit, processAction คงเดิมจากไฟล์ที่แนบมา แค่ปรับปรุงเล็กน้อยตาม Context) ...
async findOne(id: number) {
const rfa = await this.rfaRepo.findOne({
where: { id },
@@ -230,14 +241,8 @@ export class RfaService {
const rfa = await this.findOne(rfaId);
const currentRevision = rfa.revisions.find((r) => r.isCurrent);
if (!currentRevision) {
if (!currentRevision)
throw new NotFoundException('Current revision not found');
}
if (!currentRevision.correspondence) {
throw new InternalServerErrorException('Correspondence relation missing');
}
if (currentRevision.statusCode.statusCode !== 'DFT') {
throw new BadRequestException('Only DRAFT documents can be submitted');
}
@@ -255,21 +260,21 @@ export class RfaService {
const statusForApprove = await this.rfaStatusRepo.findOne({
where: { statusCode: 'FAP' },
});
if (!statusForApprove) {
if (!statusForApprove)
throw new InternalServerErrorException('Status FAP not found');
}
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
// Update Revision Status
currentRevision.rfaStatusCodeId = statusForApprove.id;
currentRevision.issuedDate = new Date();
await queryRunner.manager.save(currentRevision);
// Create First Routing Step
const firstStep = template.steps[0];
const routing = queryRunner.manager.create(CorrespondenceRouting, {
correspondenceId: currentRevision.correspondenceId,
templateId: template.id,
@@ -286,20 +291,18 @@ export class RfaService {
});
await queryRunner.manager.save(routing);
// Notify
const recipientUserId = await this.userService.findDocControlIdByOrg(
firstStep.toOrganizationId,
);
if (recipientUserId) {
const docNo = currentRevision.correspondence.correspondenceNumber;
await this.notificationService.send({
userId: recipientUserId,
title: `RFA Submitted: ${currentRevision.title}`,
message: `มีเอกสาร RFA ใหม่รอการตรวจสอบจากคุณ (เลขที่: ${docNo})`,
message: `RFA ${currentRevision.correspondence.correspondenceNumber} submitted for approval.`,
type: 'SYSTEM',
entityType: 'rfa',
entityId: rfa.id,
link: `/rfas/${rfa.id}`,
});
}
@@ -307,7 +310,6 @@ export class RfaService {
return { message: 'RFA Submitted successfully', routing };
} catch (err) {
await queryRunner.rollbackTransaction();
this.logger.error(`Failed to submit RFA: ${(err as Error).message}`);
throw err;
} finally {
await queryRunner.release();
@@ -315,11 +317,13 @@ export class RfaService {
}
async processAction(rfaId: number, dto: WorkflowActionDto, user: User) {
// Logic คงเดิม: หา Current Routing -> Check Permission -> Call Workflow Engine -> Update DB
// ใช้ this.workflowEngine.processAction (Legacy Support)
// ... (สามารถใช้ Code เดิมจากที่คุณแนบมาได้เลย เพราะ Logic ถูกต้องแล้วสำหรับการใช้ CorrespondenceRouting) ...
const rfa = await this.findOne(rfaId);
const currentRevision = rfa.revisions.find((r) => r.isCurrent);
if (!currentRevision) {
if (!currentRevision)
throw new NotFoundException('Current revision not found');
}
const currentRouting = await this.routingRepo.findOne({
where: {
@@ -330,10 +334,8 @@ export class RfaService {
relations: ['toOrganization'],
});
if (!currentRouting) {
if (!currentRouting)
throw new BadRequestException('No active workflow step found');
}
if (currentRouting.toOrganizationId !== user.primaryOrganizationId) {
throw new ForbiddenException(
'You are not authorized to process this step',
@@ -345,10 +347,10 @@ export class RfaService {
relations: ['steps'],
});
if (!template || !template.steps) {
throw new InternalServerErrorException('Template or steps not found');
}
if (!template || !template.steps)
throw new InternalServerErrorException('Template not found');
// Call Engine to calculate next step
const result = this.workflowEngine.processAction(
currentRouting.sequence,
template.steps.length,
@@ -361,19 +363,19 @@ export class RfaService {
await queryRunner.startTransaction();
try {
currentRouting.status =
dto.action === WorkflowAction.REJECT ? 'REJECTED' : 'ACTIONED';
// Update current routing
currentRouting.status = dto.action === 'REJECT' ? 'REJECTED' : 'ACTIONED';
currentRouting.processedByUserId = user.user_id;
currentRouting.processedAt = new Date();
currentRouting.comments = dto.comments;
await queryRunner.manager.save(currentRouting);
if (result.nextStepSequence && dto.action !== WorkflowAction.REJECT) {
const nextStepConfig = template.steps.find(
// Create next routing if available
if (result.nextStepSequence && dto.action !== 'REJECT') {
const nextStep = template.steps.find(
(s) => s.sequence === result.nextStepSequence,
);
if (nextStepConfig) {
if (nextStep) {
const nextRouting = queryRunner.manager.create(
CorrespondenceRouting,
{
@@ -381,52 +383,43 @@ export class RfaService {
templateId: template.id,
sequence: result.nextStepSequence,
fromOrganizationId: user.primaryOrganizationId,
toOrganizationId: nextStepConfig.toOrganizationId,
stepPurpose: nextStepConfig.stepPurpose,
toOrganizationId: nextStep.toOrganizationId,
stepPurpose: nextStep.stepPurpose,
status: 'SENT',
dueDate: new Date(
Date.now() +
(nextStepConfig.expectedDays || 7) * 24 * 60 * 60 * 1000,
Date.now() + (nextStep.expectedDays || 7) * 24 * 60 * 60 * 1000,
),
},
);
await queryRunner.manager.save(nextRouting);
}
} else if (
result.nextStepSequence === null &&
dto.action !== WorkflowAction.REJECT
) {
const approveCodeStr =
dto.action === WorkflowAction.APPROVE ? '1A' : '4X';
const approveCode = await this.rfaApproveRepo.findOne({
where: { approveCode: approveCodeStr },
});
if (approveCode) {
currentRevision.rfaApproveCodeId = approveCode.id;
currentRevision.approvedDate = new Date();
}
await queryRunner.manager.save(currentRevision);
} else if (dto.action === WorkflowAction.REJECT) {
const rejectCode = await this.rfaApproveRepo.findOne({
where: { approveCode: '4X' },
});
if (rejectCode) {
currentRevision.rfaApproveCodeId = rejectCode.id;
} else if (result.nextStepSequence === null) {
// Workflow Ended (Completed or Rejected)
// Update RFA Status (Approved/Rejected Code)
if (dto.action !== 'REJECT') {
const approveCode = await this.rfaApproveRepo.findOne({
where: { approveCode: dto.action === 'APPROVE' ? '1A' : '4X' },
}); // Logic Map Code อย่างง่าย
if (approveCode) {
currentRevision.rfaApproveCodeId = approveCode.id;
currentRevision.approvedDate = new Date();
}
} else {
const rejectCode = await this.rfaApproveRepo.findOne({
where: { approveCode: '4X' },
});
if (rejectCode) currentRevision.rfaApproveCodeId = rejectCode.id;
}
await queryRunner.manager.save(currentRevision);
}
await queryRunner.commitTransaction();
return { message: 'Action processed successfully', result };
return { message: 'Action processed', result };
} catch (err) {
await queryRunner.rollbackTransaction();
this.logger.error(
`Failed to process RFA action: ${(err as Error).message}`,
);
throw err;
} finally {
await queryRunner.release();
}
}
}
}

View File

@@ -0,0 +1,30 @@
// File: src/modules/workflow-engine/dto/workflow-transition.dto.ts
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsNotEmpty, IsObject, IsOptional, IsString } from 'class-validator';
export class WorkflowTransitionDto {
@ApiProperty({
description: 'ชื่อ Action ที่ต้องการทำ (ต้องตรงกับที่กำหนดใน DSL)',
example: 'APPROVE',
})
@IsString()
@IsNotEmpty()
action!: string;
@ApiPropertyOptional({
description: 'ความเห็นประกอบการดำเนินการ',
example: 'อนุมัติครับ ดำเนินการต่อได้เลย',
})
@IsString()
@IsOptional()
comment?: string;
@ApiPropertyOptional({
description: 'ข้อมูลเพิ่มเติมที่ต้องการแนบไปกับ Event หรือบันทึกใน Context',
example: { urgent: true, assign_to: 'user_123' },
})
@IsObject()
@IsOptional()
payload?: Record<string, any>;
}

View File

@@ -1,37 +1,58 @@
// File: src/modules/workflow-engine/entities/workflow-definition.entity.ts
import {
Entity,
Column,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
Entity,
Index,
PrimaryGeneratedColumn,
Unique,
UpdateDateColumn,
} from 'typeorm';
/**
* เก็บแม่แบบ (Blueprint) ของ Workflow
* 1 Workflow Code (เช่น RFA) สามารถมีได้หลาย Version
*/
@Entity('workflow_definitions')
@Index(['workflow_code', 'is_active', 'version'])
@Unique(['workflow_code', 'version']) // ป้องกัน Version ซ้ำใน Workflow เดียวกัน
@Index(['workflow_code', 'is_active', 'version']) // เพื่อการ Query หา Active Version ล่าสุดได้เร็ว
export class WorkflowDefinition {
@PrimaryGeneratedColumn('uuid')
id!: string; // เพิ่ม !
id!: string;
@Column({ length: 50, comment: 'รหัส Workflow เช่น RFA, CORR' })
workflow_code!: string; // เพิ่ม !
@Column({ length: 50, comment: 'รหัส Workflow เช่น RFA, CORR, LEAVE_REQ' })
workflow_code!: string;
@Column({ type: 'int', default: 1, comment: 'หมายเลข Version' })
version!: number; // เพิ่ม !
@Column({
type: 'int',
default: 1,
comment: 'หมายเลข Version (Running sequence)',
})
version!: number;
@Column({ type: 'json', comment: 'นิยาม Workflow ต้นฉบับ' })
dsl!: any; // เพิ่ม !
@Column({ type: 'text', nullable: true, comment: 'คำอธิบายเพิ่มเติม' })
description?: string;
@Column({ type: 'json', comment: 'โครงสร้างที่ Compile แล้ว' })
compiled!: any; // เพิ่ม !
@Column({
type: 'json',
comment: 'Raw DSL ที่ User/Admin เขียน (เก็บไว้เพื่อดูหรือแก้ไข)',
})
dsl!: any; // ควรตรงกับ RawWorkflowDSL interface
@Column({ default: true, comment: 'สถานะการใช้งาน' })
is_active!: boolean; // เพิ่ม !
@Column({
type: 'json',
comment:
'Compiled JSON Structure ที่ผ่านการ Validate และ Optimize สำหรับ Runtime Engine แล้ว',
})
compiled!: any; // ควรตรงกับ CompiledWorkflow interface
@CreateDateColumn()
created_at!: Date; // เพิ่ม !
@Column({ default: true, comment: 'สถานะการใช้งาน (Soft Disable)' })
is_active!: boolean;
@UpdateDateColumn()
updated_at!: Date; // เพิ่ม !
@CreateDateColumn({ name: 'created_at' })
created_at!: Date;
@UpdateDateColumn({ name: 'updated_at' })
updated_at!: Date;
}

View File

@@ -1,15 +1,23 @@
// File: src/modules/workflow-engine/entities/workflow-history.entity.ts
import {
Column,
CreateDateColumn,
Entity,
Index,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { WorkflowInstance } from './workflow-instance.entity';
/**
* เก็บประวัติการเปลี่ยนสถานะ (Audit Trail)
* สำคัญมากสำหรับการตรวจสอบย้อนหลัง (Who did What, When)
*/
@Entity('workflow_histories')
@Index(['instanceId']) // ค้นหาประวัติของ Instance นี้
@Index(['actionByUserId']) // ค้นหาว่า User คนนี้ทำอะไรไปบ้าง
export class WorkflowHistory {
@PrimaryGeneratedColumn('uuid')
id!: string;
@@ -21,23 +29,32 @@ export class WorkflowHistory {
@Column({ name: 'instance_id' })
instanceId!: string;
@Column({ name: 'from_state', length: 50 })
@Column({ name: 'from_state', length: 50, comment: 'สถานะต้นทาง' })
fromState!: string;
@Column({ name: 'to_state', length: 50 })
@Column({ name: 'to_state', length: 50, comment: 'สถานะปลายทาง' })
toState!: string;
@Column({ length: 50 })
@Column({ length: 50, comment: 'Action ที่ User กด (เช่น APPROVE, REJECT)' })
action!: string;
@Column({ name: 'action_by_user_id', nullable: true })
actionByUserId?: number; // User ID ของผู้ดำเนินการ
@Column({
name: 'action_by_user_id',
nullable: true,
comment: 'User ID ผู้ดำเนินการ (Nullable กรณี System Auto)',
})
actionByUserId?: number;
@Column({ type: 'text', nullable: true })
@Column({ type: 'text', nullable: true, comment: 'ความเห็นประกอบการอนุมัติ' })
comment?: string;
@Column({ type: 'json', nullable: true })
metadata?: Record<string, any>; // เก็บข้อมูลเพิ่มเติม เช่น Snapshot ของ Context ณ ตอนนั้น
// Snapshot ข้อมูล ณ เวลาที่เปลี่ยนสถานะ เพื่อเป็นหลักฐานหาก Context เปลี่ยนในอนาคต
@Column({
type: 'json',
nullable: true,
comment: 'Snapshot of Context or Metadata',
})
metadata?: Record<string, any>;
@CreateDateColumn({ name: 'created_at' })
createdAt!: Date;

View File

@@ -1,4 +1,5 @@
// File: src/modules/workflow-engine/entities/workflow-instance.entity.ts
import {
Column,
CreateDateColumn,
@@ -12,20 +13,23 @@ import {
import { WorkflowDefinition } from './workflow-definition.entity';
export enum WorkflowStatus {
ACTIVE = 'ACTIVE',
COMPLETED = 'COMPLETED',
CANCELLED = 'CANCELLED',
TERMINATED = 'TERMINATED',
ACTIVE = 'ACTIVE', // กำลังดำเนินการ
COMPLETED = 'COMPLETED', // จบกระบวนการ (ถึง Terminal State)
CANCELLED = 'CANCELLED', // ถูกยกเลิกกลางคัน
TERMINATED = 'TERMINATED', // ถูกบังคับจบโดยระบบ หรือ Error
}
/**
* เก็บสถานะการเดินเรื่องของเอกสารแต่ละใบ (Runtime State)
*/
@Entity('workflow_instances')
@Index(['entityType', 'entityId']) // Index สำหรับค้นหาตามเอกสาร
@Index(['currentState']) // Index สำหรับ Filter ตามสถานะ
@Index(['entityType', 'entityId']) // เพื่อค้นหาว่าเอกสารนี้ (เช่น RFA-001) อยู่ขั้นตอนไหน
@Index(['currentState']) // เพื่อ Dashboard: "มีงานค้างที่ขั้นตอนไหนบ้าง"
export class WorkflowInstance {
@PrimaryGeneratedColumn('uuid')
id!: string;
// เชื่อมโยงกับ Definition ที่ใช้ตอนสร้าง Instance นี้
// ผูกกับ Definition เพื่อรู้ว่าใช้กฎชุดไหน (Version ไหน)
@ManyToOne(() => WorkflowDefinition)
@JoinColumn({ name: 'definition_id' })
definition!: WorkflowDefinition;
@@ -33,25 +37,39 @@ export class WorkflowInstance {
@Column({ name: 'definition_id' })
definitionId!: string;
// Polymorphic Relation: เชื่อมกับเอกสารได้หลายประเภท (RFA, CORR, etc.)
@Column({ name: 'entity_type', length: 50 })
// Polymorphic Relation: เชื่อมกับเอกสารได้หลายประเภท (RFA, CORR, etc.) โดยไม่ต้อง Foreign Key จริง
@Column({
name: 'entity_type',
length: 50,
comment: 'ประเภทเอกสาร เช่น rfa, correspondence',
})
entityType!: string;
@Column({ name: 'entity_id', length: 50 })
entityId!: string; // รองรับทั้ง ID แบบ Int และ UUID (เก็บเป็น String)
@Column({
name: 'entity_id',
length: 50,
comment: 'ID ของเอกสาร (String/UUID)',
})
entityId!: string;
@Column({ name: 'current_state', length: 50 })
@Column({
name: 'current_state',
length: 50,
comment: 'ชื่อ State ปัจจุบัน เช่น DRAFT, IN_REVIEW',
})
currentState!: string;
@Column({
type: 'enum',
enum: WorkflowStatus,
default: WorkflowStatus.ACTIVE,
comment: 'สถานะภาพรวมของ Instance',
})
status!: WorkflowStatus;
// Context เฉพาะของ Instance นี้ (เช่น ตัวแปรที่ส่งต่อระหว่าง State)
@Column({ type: 'json', nullable: true })
// Context:ก็บตัวแปรที่จำเป็นสำหรับการตัดสินใจใน Workflow
// เช่น { "amount": 500000, "requester_role": "ENGINEER", "approver_ids": [1, 2] }
@Column({ type: 'json', nullable: true, comment: 'Runtime Context Data' })
context?: Record<string, any>;
@CreateDateColumn({ name: 'created_at' })

View File

@@ -1,181 +1,255 @@
// File: src/modules/workflow-engine/workflow-dsl.service.ts
import { BadRequestException, Injectable } from '@nestjs/common';
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
export interface WorkflowState {
// ==========================================
// 1. Interfaces for RAW DSL (Input from User)
// ==========================================
export interface RawWorkflowDSL {
workflow: string;
version?: number;
description?: string;
states: RawState[];
}
export interface RawState {
name: string;
initial?: boolean;
terminal?: boolean;
transitions?: Record<string, TransitionRule>;
on?: Record<string, RawTransition>;
}
export interface TransitionRule {
export interface RawTransition {
to: string;
requirements?: RequirementRule[];
events?: EventRule[];
require?: {
role?: string | string[];
user?: string;
};
condition?: string; // JavaScript Expression string
events?: RawEvent[];
}
export interface RequirementRule {
role?: string;
user?: string;
condition?: string;
}
export interface EventRule {
type: 'notify' | 'webhook' | 'update_status';
export interface RawEvent {
type: 'notify' | 'webhook' | 'assign' | 'auto_action';
target?: string;
template?: string;
payload?: any;
}
// ==========================================
// 2. Interfaces for COMPILED Schema (Optimized for Runtime)
// ==========================================
export interface CompiledWorkflow {
workflow: string;
version: string | number;
states: Record<string, WorkflowState>;
version: number;
initialState: string; // Optimize: เก็บชื่อ Initial State ไว้เลย ไม่ต้อง loop หา
states: Record<string, CompiledState>;
}
export interface CompiledState {
terminal: boolean;
transitions: Record<string, CompiledTransition>;
}
export interface CompiledTransition {
to: string;
requirements: {
roles: string[];
userId?: string;
};
condition?: string;
events: RawEvent[];
}
@Injectable()
export class WorkflowDslService {
/**
* คอมไพล์ DSL Input ให้เป็น Standard Execution Tree
*/
compile(dsl: any): CompiledWorkflow {
if (!dsl || typeof dsl !== 'object') {
throw new BadRequestException('DSL must be a valid JSON object.');
}
private readonly logger = new Logger(WorkflowDslService.name);
if (!dsl.states || !Array.isArray(dsl.states)) {
throw new BadRequestException(
'DSL syntax error: "states" array is required.',
);
}
/**
* [Compile Time]
* แปลง Raw DSL เป็น Compiled Structure พร้อม Validation
*/
compile(dsl: RawWorkflowDSL): CompiledWorkflow {
this.validateSchemaStructure(dsl);
const compiled: CompiledWorkflow = {
workflow: dsl.workflow || 'UNKNOWN',
workflow: dsl.workflow,
version: dsl.version || 1,
initialState: '',
states: {},
};
const stateMap = new Set<string>();
const definedStates = new Set<string>(dsl.states.map((s) => s.name));
let initialFound = false;
// 1. Process States
for (const rawState of dsl.states) {
if (!rawState.name) {
throw new BadRequestException(
'DSL syntax error: All states must have a "name".',
);
if (rawState.initial) {
if (initialFound) {
throw new BadRequestException(
`DSL Error: Multiple initial states found (at "${rawState.name}").`,
);
}
compiled.initialState = rawState.name;
initialFound = true;
}
stateMap.add(rawState.name);
const normalizedState: WorkflowState = {
initial: !!rawState.initial,
const compiledState: CompiledState = {
terminal: !!rawState.terminal,
transitions: {},
};
// 2. Process Transitions
if (rawState.on) {
for (const [action, rule] of Object.entries(rawState.on)) {
const rawRule = rule as any;
normalizedState.transitions![action] = {
to: rawRule.to,
requirements: rawRule.require || [],
events: rawRule.events || [],
// Validation: Target state must exist
if (!definedStates.has(rule.to)) {
throw new BadRequestException(
`DSL Error: State "${rawState.name}" transitions via "${action}" to unknown state "${rule.to}".`,
);
}
compiledState.transitions[action] = {
to: rule.to,
requirements: {
roles: rule.require?.role
? Array.isArray(rule.require.role)
? rule.require.role
: [rule.require.role]
: [],
userId: rule.require?.user,
},
condition: rule.condition,
events: rule.events || [],
};
}
} else if (!rawState.terminal) {
this.logger.warn(
`State "${rawState.name}" is not terminal but has no transitions.`,
);
}
compiled.states[rawState.name] = normalizedState;
compiled.states[rawState.name] = compiledState;
}
this.validateIntegrity(compiled, stateMap);
if (!initialFound) {
throw new BadRequestException('DSL Error: No initial state defined.');
}
return compiled;
}
private validateIntegrity(compiled: CompiledWorkflow, stateMap: Set<string>) {
let hasInitial = false;
for (const [stateName, state] of Object.entries(compiled.states)) {
if (state.initial) {
if (hasInitial)
throw new BadRequestException(
`DSL Error: Multiple initial states found.`,
);
hasInitial = true;
}
if (state.transitions) {
for (const [action, rule] of Object.entries(state.transitions)) {
if (!stateMap.has(rule.to)) {
throw new BadRequestException(
`DSL Error: State "${stateName}" transitions via "${action}" to unknown state "${rule.to}".`,
);
}
}
}
}
if (!hasInitial) {
throw new BadRequestException('DSL Error: No initial state defined.');
}
}
/**
* [Runtime]
* ประมวลผล Action และคืนค่า State ถัดไป
*/
evaluate(
compiled: CompiledWorkflow,
currentState: string,
action: string,
context: any = {}, // Default empty object
): { nextState: string; events: EventRule[] } {
context: any = {},
): { nextState: string; events: RawEvent[] } {
const stateConfig = compiled.states[currentState];
// 1. Validate State Existence
if (!stateConfig) {
throw new BadRequestException(
`Runtime Error: Current state "${currentState}" not found in definition.`,
`Runtime Error: Current state "${currentState}" is invalid.`,
);
}
// 2. Check if terminal
if (stateConfig.terminal) {
throw new BadRequestException(
`Runtime Error: Cannot transition from terminal state "${currentState}".`,
);
}
const transition = stateConfig.transitions?.[action];
// 3. Find Transition
const transition = stateConfig.transitions[action];
if (!transition) {
const allowed = Object.keys(stateConfig.transitions).join(', ');
throw new BadRequestException(
`Runtime Error: Action "${action}" is not allowed from state "${currentState}". Available actions: ${Object.keys(stateConfig.transitions || {}).join(', ')}`,
`Invalid Action: "${action}" is not allowed from "${currentState}". Allowed: [${allowed}]`,
);
}
if (transition.requirements && transition.requirements.length > 0) {
this.checkRequirements(transition.requirements, context);
// 4. Validate Requirements (RBAC)
this.checkRequirements(transition.requirements, context);
// 5. Evaluate Condition (Dynamic Logic)
if (transition.condition) {
const isMet = this.evaluateCondition(transition.condition, context);
if (!isMet) {
throw new BadRequestException(
'Condition Failed: The criteria for this transition are not met.',
);
}
}
return {
nextState: transition.to,
events: transition.events || [],
events: transition.events,
};
}
private checkRequirements(requirements: RequirementRule[], context: any) {
const safeContext = context || {};
const userRoles = safeContext.roles || [];
const userId = safeContext.userId;
// --------------------------------------------------------
// Private Helpers
// --------------------------------------------------------
const isAllowed = requirements.some((req) => {
if (req.role) {
return userRoles.includes(req.role);
}
if (req.user) {
return userId === req.user;
}
// Future: Add Condition Logic Evaluation here
return false;
});
if (!isAllowed) {
private validateSchemaStructure(dsl: any) {
if (!dsl || typeof dsl !== 'object') {
throw new BadRequestException('DSL must be a JSON object.');
}
if (!dsl.workflow || !dsl.states || !Array.isArray(dsl.states)) {
throw new BadRequestException(
'Access Denied: You do not meet the requirements for this action.',
'DSL Error: Missing required fields (workflow, states).',
);
}
}
private checkRequirements(
req: CompiledTransition['requirements'],
context: any,
) {
const userRoles: string[] = context.roles || [];
const userId: string | number = context.userId;
// Check Roles (OR logic inside array)
if (req.roles.length > 0) {
const hasRole = req.roles.some((r) => userRoles.includes(r));
if (!hasRole) {
throw new BadRequestException(
`Access Denied: Required roles [${req.roles.join(', ')}]`,
);
}
}
// Check Specific User
if (req.userId && String(req.userId) !== String(userId)) {
throw new BadRequestException('Access Denied: User mismatch.');
}
}
/**
* Evaluate simple JS expression securely
* NOTE: In production, use a safe parser like 'json-logic-js' or vm2
* For this phase, we use a simple Function constructor with restricted scope.
*/
private evaluateCondition(expression: string, context: any): boolean {
try {
// Simple guard against malicious code (basic)
if (expression.includes('process') || expression.includes('require')) {
throw new Error('Unsafe expression detected');
}
// Create a function that returns the expression result
// "context" is available inside the expression
const func = new Function('context', `return ${expression};`);
return !!func(context);
} catch (error: any) {
this.logger.error(`Condition Error: "${expression}" -> ${error.message}`);
return false; // Fail safe
}
}
}

View File

@@ -5,58 +5,103 @@ import {
Controller,
Get,
Param,
ParseUUIDPipe,
Patch,
Post,
Query,
Request,
UseGuards,
} from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { CreateWorkflowDefinitionDto } from './dto/create-workflow-definition.dto';
import { EvaluateWorkflowDto } from './dto/evaluate-workflow.dto';
import { GetAvailableActionsDto } from './dto/get-available-actions.dto';
import { UpdateWorkflowDefinitionDto } from './dto/update-workflow-definition.dto';
import {
ApiBearerAuth,
ApiOperation,
ApiParam,
ApiResponse,
ApiTags,
} from '@nestjs/swagger';
// Services
import { WorkflowEngineService } from './workflow-engine.service';
@ApiTags('Workflow Engine (DSL)')
// DTOs
import { CreateWorkflowDefinitionDto } from './dto/create-workflow-definition.dto';
import { EvaluateWorkflowDto } from './dto/evaluate-workflow.dto';
import { UpdateWorkflowDefinitionDto } from './dto/update-workflow-definition.dto';
import { WorkflowTransitionDto } from './dto/workflow-transition.dto';
// Guards & Decorators (อ้างอิงตามโครงสร้าง src/common ในแผนงาน)
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { RbacGuard } from '../../common/guards/rbac.guard';
@ApiTags('Workflow Engine')
@ApiBearerAuth() // ระบุว่าต้องใช้ Token ใน Swagger
@Controller('workflow-engine')
@UseGuards(JwtAuthGuard)
@UseGuards(JwtAuthGuard, RbacGuard) // บังคับ Login และตรวจสอบสิทธิ์ทุก Request
export class WorkflowEngineController {
constructor(private readonly workflowService: WorkflowEngineService) {}
// =================================================================
// Definition Management (Admin / Developer)
// =================================================================
@Post('definitions')
@ApiOperation({ summary: 'Create or Update Workflow Definition (DSL)' })
@ApiResponse({ status: 201, description: 'Workflow compiled and saved.' })
@ApiOperation({ summary: 'สร้าง Workflow Definition ใหม่ (Auto Versioning)' })
@ApiResponse({ status: 201, description: 'Created successfully' })
// ใช้ Permission 'system.manage_all' (Admin) หรือสร้าง permission ใหม่ 'workflow.manage' ในอนาคต
@RequirePermission('system.manage_all')
async createDefinition(@Body() dto: CreateWorkflowDefinitionDto) {
return this.workflowService.createDefinition(dto);
}
@Post('evaluate')
@ApiOperation({
summary: 'Evaluate transition (Run logic without saving state)',
})
async evaluate(@Body() dto: EvaluateWorkflowDto) {
return this.workflowService.evaluate(dto);
}
@Get('actions')
@ApiOperation({ summary: 'Get available actions for current state' })
async getAvailableActions(@Query() query: GetAvailableActionsDto) {
return this.workflowService.getAvailableActions(
query.workflow_code,
query.current_state,
);
}
@Patch('definitions/:id')
@ApiOperation({
summary: 'Update workflow status or details (DSL Re-compile)',
})
@ApiOperation({ summary: 'แก้ไข Workflow Definition (Re-compile DSL)' })
@RequirePermission('system.manage_all')
async updateDefinition(
@Param('id', ParseUUIDPipe) id: string, // เพิ่ม ParseUUIDPipe เพื่อ Validate ID
@Param('id') id: string,
@Body() dto: UpdateWorkflowDefinitionDto,
) {
return this.workflowService.update(id, dto);
}
@Post('evaluate')
@ApiOperation({ summary: 'ทดสอบ Logic Workflow (Dry Run) ไม่บันทึกข้อมูล' })
@RequirePermission('system.manage_all')
async evaluate(@Body() dto: EvaluateWorkflowDto) {
return this.workflowService.evaluate(dto);
}
// =================================================================
// Runtime Engine (User Actions)
// =================================================================
@Post('instances/:id/transition')
@ApiOperation({ summary: 'สั่งเปลี่ยนสถานะเอกสาร (User Action)' })
@ApiParam({ name: 'id', description: 'Workflow Instance ID (UUID)' })
// Permission จะถูกตรวจสอบ Dynamic ภายใน Service ตาม State ของ Workflow แต่ขั้นต้นต้องมีสิทธิ์ทำงาน Workflow
@RequirePermission('workflow.action_review')
async processTransition(
@Param('id') instanceId: string,
@Body() dto: WorkflowTransitionDto,
@Request() req: any,
) {
// ดึง User ID จาก Token (req.user มาจาก JwtStrategy)
const userId = req.user?.userId;
return this.workflowService.processTransition(
instanceId,
dto.action,
userId,
dto.comment,
dto.payload,
);
}
@Get('instances/:id/actions')
@ApiOperation({
summary: 'ดึงรายการปุ่ม Action ที่สามารถกดได้ ณ สถานะปัจจุบัน',
})
@RequirePermission('document.view') // ผู้ที่มีสิทธิ์ดูเอกสาร ควรดู Action ได้
async getAvailableActions(@Param('id') instanceId: string) {
// Note: Logic การดึง Action ตาม Instance ID จะถูก Implement ใน Task ถัดไป
return { message: 'Pending implementation in Service layer' };
}
}

View File

@@ -2,23 +2,31 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkflowDefinition } from './entities/workflow-definition.entity';
import { WorkflowHistory } from './entities/workflow-history.entity'; // [New]
import { WorkflowInstance } from './entities/workflow-instance.entity'; // [New]
import { WorkflowDslService } from './workflow-dsl.service';
import { WorkflowEngineController } from './workflow-engine.controller';
import { WorkflowEngineService } from './workflow-engine.service';
// Entities
import { WorkflowDefinition } from './entities/workflow-definition.entity';
import { WorkflowHistory } from './entities/workflow-history.entity';
import { WorkflowInstance } from './entities/workflow-instance.entity';
// Services
import { WorkflowDslService } from './workflow-dsl.service';
import { WorkflowEngineService } from './workflow-engine.service';
import { WorkflowEventService } from './workflow-event.service'; // [NEW]
// Controllers
import { UserModule } from '../user/user.module';
import { WorkflowEngineController } from './workflow-engine.controller';
@Module({
imports: [
TypeOrmModule.forFeature([
WorkflowDefinition,
WorkflowInstance, // [New]
WorkflowHistory, // [New]
WorkflowInstance,
WorkflowHistory,
]),
UserModule,
],
controllers: [WorkflowEngineController],
providers: [WorkflowEngineService, WorkflowDslService],
exports: [WorkflowEngineService],
providers: [WorkflowEngineService, WorkflowDslService, WorkflowEventService],
exports: [WorkflowEngineService], // Export Service ให้ Module อื่น (Correspondence, RFA) เรียกใช้
})
export class WorkflowEngineModule {}

View File

@@ -22,6 +22,7 @@ import { CreateWorkflowDefinitionDto } from './dto/create-workflow-definition.dt
import { EvaluateWorkflowDto } from './dto/evaluate-workflow.dto';
import { UpdateWorkflowDefinitionDto } from './dto/update-workflow-definition.dto';
import { CompiledWorkflow, WorkflowDslService } from './workflow-dsl.service';
import { WorkflowEventService } from './workflow-event.service'; // [NEW] Import Event Service
// Legacy Interface (Backward Compatibility)
export enum WorkflowAction {
@@ -49,6 +50,7 @@ export class WorkflowEngineService {
@InjectRepository(WorkflowHistory)
private readonly historyRepo: Repository<WorkflowHistory>,
private readonly dslService: WorkflowDslService,
private readonly eventService: WorkflowEventService, // [NEW] Inject Service
private readonly dataSource: DataSource, // ใช้สำหรับ Transaction
) {}
@@ -166,9 +168,9 @@ export class WorkflowEngineService {
// 2. หา Initial State จาก Compiled Structure
const compiled: CompiledWorkflow = definition.compiled;
const initialState = Object.keys(compiled.states).find(
(key) => compiled.states[key].initial,
);
// [FIX] ใช้ initialState จาก Root Property โดยตรง (ตามที่ Optimize ใน DSL Service)
// เพราะ CompiledState ใน states map ไม่มี property 'initial' แล้ว
const initialState = compiled.initialState;
if (!initialState) {
throw new BadRequestException(
@@ -193,6 +195,25 @@ export class WorkflowEngineService {
return savedInstance;
}
/**
* ดึงข้อมูล Workflow Instance ตาม ID
* ใช้สำหรับการตรวจสอบสถานะหรือซิงค์ข้อมูลกลับไปยัง Module หลัก
*/
async getInstanceById(instanceId: string): Promise<WorkflowInstance> {
const instance = await this.instanceRepo.findOne({
where: { id: instanceId },
relations: ['definition'],
});
if (!instance) {
throw new NotFoundException(
`Workflow Instance "${instanceId}" not found`,
);
}
return instance;
}
/**
* ดำเนินการเปลี่ยนสถานะ (Transition) ของ Instance จริงแบบ Transactional
*/
@@ -207,6 +228,9 @@ export class WorkflowEngineService {
await queryRunner.connect();
await queryRunner.startTransaction();
let eventsToDispatch: any[] = [];
let updatedContext: any = {};
try {
// 1. Lock Instance เพื่อป้องกัน Race Condition (Pessimistic Write Lock)
const instance = await queryRunner.manager.findOne(WorkflowInstance, {
@@ -268,25 +292,29 @@ export class WorkflowEngineService {
});
await queryRunner.manager.save(history);
// 5. Trigger Events (Integration Point)
// ในอนาคตสามารถ Inject NotificationService มาเรียกตรงนี้ได้
if (evaluation.events && evaluation.events.length > 0) {
this.logger.log(
`Triggering ${evaluation.events.length} events for instance ${instanceId}`,
);
// await this.eventHandler.handle(evaluation.events);
}
await queryRunner.commitTransaction();
// [NEW] เก็บค่าไว้ Dispatch หลัง Commit
eventsToDispatch = evaluation.events;
updatedContext = context;
this.logger.log(
`Transition: ${instanceId} [${fromState}] --${action}--> [${toState}] by User:${userId}`,
);
// [NEW] Dispatch Events (Async) ผ่าน WorkflowEventService
if (eventsToDispatch && eventsToDispatch.length > 0) {
this.eventService.dispatchEvents(
instance.id,
eventsToDispatch,
updatedContext,
);
}
return {
success: true,
nextState: toState,
events: evaluation.events,
events: eventsToDispatch,
isCompleted: instance.status === WorkflowStatus.COMPLETED,
};
} catch (err) {

View File

@@ -0,0 +1,96 @@
// File: src/modules/workflow-engine/workflow-event.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { RawEvent } from './workflow-dsl.service';
// Interface สำหรับ External Services ที่จะมารับ Event ต่อ
// (ในอนาคตควรใช้ NestJS Event Emitter เพื่อ Decouple อย่างสมบูรณ์)
export interface WorkflowEventHandler {
handleNotification(
target: string,
template: string,
payload: any,
): Promise<void>;
handleWebhook(url: string, payload: any): Promise<void>;
handleAutoAction(instanceId: string, action: string): Promise<void>;
}
@Injectable()
export class WorkflowEventService {
private readonly logger = new Logger(WorkflowEventService.name);
// สามารถ Inject NotificationService หรือ HttpService เข้ามาได้ตรงนี้
// constructor(private readonly notificationService: NotificationService) {}
/**
* ประมวลผลรายการ Events ที่เกิดจากการเปลี่ยนสถานะ
*/
async dispatchEvents(
instanceId: string,
events: RawEvent[],
context: Record<string, any>,
) {
if (!events || events.length === 0) return;
this.logger.log(
`Dispatching ${events.length} events for Instance ${instanceId}`,
);
// ทำแบบ Async ไม่รอผล (Fire-and-forget) เพื่อไม่ให้กระทบ Response Time ของ User
Promise.allSettled(
events.map((event) =>
this.processSingleEvent(instanceId, event, context),
),
).then((results) => {
// Log errors if any
results.forEach((res, idx) => {
if (res.status === 'rejected') {
this.logger.error(`Failed to process event [${idx}]: ${res.reason}`);
}
});
});
}
private async processSingleEvent(
instanceId: string,
event: RawEvent,
context: any,
) {
try {
switch (event.type) {
case 'notify':
await this.handleNotify(event, context);
break;
case 'webhook':
await this.handleWebhook(event, context);
break;
case 'auto_action':
// Logic สำหรับ Auto Transition (เช่น ถ้าผ่านเงื่อนไข ให้ไปต่อเลย)
this.logger.log(`Auto Action triggered for ${instanceId}`);
break;
default:
this.logger.warn(`Unknown event type: ${event.type}`);
}
} catch (error) {
this.logger.error(`Error processing event ${event.type}: ${error}`);
throw error;
}
}
// --- Handlers ---
private async handleNotify(event: RawEvent, context: any) {
// Mockup: ในของจริงจะเรียก NotificationService.send()
// const recipients = this.resolveRecipients(event.target, context);
this.logger.log(
`[EVENT] Notify target: "${event.target}" | Template: "${event.template}"`,
);
}
private async handleWebhook(event: RawEvent, context: any) {
// Mockup: เรียก HttpService.post()
this.logger.log(
`[EVENT] Webhook to: "${event.target}" | Payload: ${JSON.stringify(event.payload)}`,
);
}
}

View File

@@ -1,191 +0,0 @@
# Special requirements for document-numbering
การใช้งานจริงต้องการความยืดหยุ่นสูง สำหรับ ระบบ document-numbering ดังนี้
## 1. ต้องให้ admin สามารถกำหนดรูปแบบในถายหลังได้
## 2. มีรูปแบบเริ่มต้นดังนี้
* 2.1 สำหรับ correspondence ทั่วไป
* ใช้รูปแบบ [organizations.organization_code]-[organizations.organization_code]-[sequence]-[year]
* **_ตัวอย่าง: คคง.-ผรม.2-0123-2568_**
* 2.2 สำหรับ correspondence type = transmittal
* to OWNER ใช้รูปแบบ [organizations.organization_code]-[organizations.organization_code]-[codecorrespondence_sub_types.sub_type_number]-[seq]-[year]
* **_ตัวอย่าง: คคง.-สคฉ.3-22-0123-2568_**
* to CONTRACTOR ใช้รูปแบบ [organizations.organization_code]-[organizations.organization_code]-[codecorrespondence_sub_types.sub_type_number]-[seq]-[year]
* **_ตัวอย่าง: คคง.-สคฉ.3-22-0123-2568_**
* 2.3 สำหรับ correspondence type = rfi
* ใช้รูปแบบ [contrcts.contract_code]-[correspondences_types.type_code]-[disciplines_code]-[seq]-[revision]
* **_ตัวอย่าง: LCBP3-C2-RFI-TER-2345-A_**
* 2.4 สำหรับ rfa ใช้แบบเลขแยกกัน
* ใช้รูปแบบ [contrcts.contract_code]-[correspondences_types.type_code]-[disciplines_code]-[rfa_types.type_code]-[seq]-[revision]
* **_ตัวอย่าง: LCBP3-C1-RFA-TER-MAT-1234-A_**
และตรวจสอบ 01_lcbp3_v1_4_3.sql, 4_Data_Dictionary_V1_4_3.md
## ตาราง correspondence_sub_types
| contract_code | correspondence_types.type_code | sub_type_code | sub_type_name | sub_type_number |
| ------------- | ------------------------------ | ------------- | ------------------------------ | --------------- |
| LCBP3-C1 | RFA | MAT | Material Approval | 11 |
| LCBP3-C1 | RFA | SHP | Shop Drawing Submittal | 12 |
| LCBP3-C1 | RFA | DWG | Document Approval | 13 |
| LCBP3-C1 | RFA | MET | Engineering Document Submittal | 14 |
| LCBP3-C2 | RFA | MAT | Material Approval | 21 |
| LCBP3-C2 | RFA | SHP | Shop Drawing Submittal | 22 |
| LCBP3-C2 | RFA | DWG | Document Approval | 23 |
| LCBP3-C2 | RFA | MET | Engineering Document Submittal | 24 |
| LCBP3-C3 | RFA | MAT | Material Approval | 31 |
| LCBP3-C3 | RFA | SHP | Shop Drawing Submittal | 32 |
| LCBP3-C3 | RFA | DWG | Document Approval | 33 |
| LCBP3-C4 | RFA | MET | Engineering Document Submittal | 34 |
| LCBP3-C4 | RFA | MAT | Material Approval | 41 |
| LCBP3-C4 | RFA | SHP | Shop Drawing Submittal | 42 |
| LCBP3-C4 | RFA | DWG | Document Approval | 43 |
| LCBP3-C4 | RFA | MET | Engineering Document Submittal | 44 |
## ตาราง disciplines
| contract_code | disciplines_code | code_name_th | code_name_en |
| ------------- | ---------------- | ----------------------------- | ------------------------------------------ |
| LCBP3-C1 | GEN | งานบริหารโครงการ | General Management |
| LCBP3-C1 | COD | สัญญาและข้อโต้แย้ง | Contracting |
| LCBP3-C1 | QSB | สำรวจปริมาณและควบคุมงบประมาณ | Quantity Survey and Budget Control |
| LCBP3-C1 | PPG | บริหารแผนและความก้าวหน้า | Plan and Progress Management |
| LCBP3-C1 | PRC | งานจัดซื้อ | Procurement |
| LCBP3-C1 | SUB | ผู้รับเหมาช่วง | Subcontractor |
| LCBP3-C1 | ODC | สำนักงาน-ควบคุมเอกสาร | Operation Docment Control |
| LCBP3-C1 | LAW | กฎหมาย | Law |
| LCBP3-C1 | TRF | จราจร | Traffic |
| LCBP3-C1 | BIM | BIM | Building information modeling |
| LCBP3-C1 | SRV | งานสำรวจ | Survey |
| LCBP3-C1 | SFT | ความปลอดภัย | Safety |
| LCBP3-C1 | BST | งานโครงสร้างอาคาร | Building Structure Work |
| LCBP3-C1 | TEM | งานชั่วคราว | Temporary Work |
| LCBP3-C1 | UTL | งานระบบสาธารณูปโภค | Utility |
| LCBP3-C1 | EPW | งานระบบไฟฟ้า | Electrical Power Work |
| LCBP3-C1 | ECM | งานระบบไฟฟ้าสื่อสาร | Electrical Communication Work |
| LCBP3-C1 | ENV | สิ่งแวดล้อม | Environment |
| LCBP3-C1 | AQV | คุณภาพอากาศและความสั่นสะเทือน | Air quality and vibration |
| LCBP3-C1 | WAB | คุณภาพน้ำและชีววิทยาทางน้ำ | Water quality and Aquatic biology |
| LCBP3-C1 | ONS | วิศวกรรมชายฝั่ง | Onshore Engineer Work |
| LCBP3-C1 | PPR | มวลชนสัมพันธ์และการประชาสัมพันธ์ | Public Relations |
| LCBP3-C1 | OSW | งานก่อสร้างงานทางทะเล | Offshore Work |
| LCBP3-C1 | DRE | งานขุดและถมทะเล | Dredging and Reclamation |
| LCBP3-C1 | REV | งานคันหินล้อมพื้นที่ถมทะเล | Revetment |
| LCBP3-C1 | BRW | งานเขื่อนกันคลื่น | Breakwater |
| LCBP3-C1 | SOI | ปรับปรุงคุณภาพดิน | Soil Improvement |
| LCBP3-C1 | BLC | งานปรับปรุงคลองบางละมุง | Bang Lamung Canal Bank Protection |
| LCBP3-C1 | FUP | งานประตูระบายน้ำและท่อลอด | Floodgate & Under Ground Piping Works |
| LCBP3-C1 | SWP | งานอาคารควบคุมสถานีสูบน้ำทะเล | Sea Water Pumping Station Control BuilDing |
| LCBP3-C1 | NAV | งานติดตั้งเครื่องหมายช่วงการเดินเรือ | Navigations Aids |
| LCBP3-C1 | GEO | งานด้านธรณีเทคนิค | Geotechnical |
| LCBP3-C1 | CRW | งานด้านโยธา - Rock Works | Civil-Rock work |
| LCBP3-C1 | DVR | ทีมนักประดาน้ำ | Dive Work |
| LCBP3-C1 | MTS | งานทดสอบวัสดุและธรณีเทคนิค | Materials and Geotechnical Testing |
| LCBP3-C1 | OTH | อื่นๆ | Other |
| LCBP3-C2 | GEN | งานบริหารโครงการ | Project Management |
| LCBP3-C2 | COD | สัญญาและข้อโต้แย้ง | Contracts and arguments |
| LCBP3-C2 | QSB | สำรวจปริมาณและควบคุมงบประมาณ | Survey the quantity and control the budget |
| LCBP3-C2 | PPM | บริหารแผนและความก้าวหน้า | Plan Management & Progress |
| LCBP3-C2 | ODC | สำนักงาน-ควบคุมเอกสาร | Document Control Office |
| LCBP3-C2 | LAW | กฎหมาย | Law |
| LCBP3-C2 | TRF | จราจร | Traffic |
| LCBP3-C2 | BIM | Building Information Modeling | Building Information Modeling |
| LCBP3-C2 | SRV | งานสำรวจ | Survey |
| LCBP3-C2 | SFT | ความปลอดภัย | Safety |
| LCBP3-C2 | BST | งานโครงสร้างอาคาร | Building Structure |
| LCBP3-C2 | UTL | งานะบบสาธารณูปโภค | Public Utilities |
| LCBP3-C2 | EPW | งานระบบไฟฟ้า | Electrical Systems |
| LCBP3-C2 | ECM | งานระบบไฟฟ้าสื่อสาร | Electrical Communication System |
| LCBP3-C2 | ENV | สิ่งแวดล้อม | Environment |
| LCBP3-C2 | AQV | คุณภาพอากาศและความสั่นสะเทือน | Air Quality and Vibration |
| LCBP3-C2 | WAB | คุณภาพน้ำและชีววิทยาทางน้ำ | Water Quality and Aquatic Biology |
| LCBP3-C2 | ONS | วิศวกรรมชายฝั่ง | Coastal Engineering |
| LCBP3-C2 | PPR | มวลชนสัมพันธ์และประชาสัมพันธ์ | Mass Relations and Public Relations |
| LCBP3-C2 | OFW | งานก่อสร้างทางทะเล | Marine Construction |
| LCBP3-C2 | EXR | งานขุดและถมทะเล | Excavation and reclamation |
| LCBP3-C2 | GEO | งานด้านธรณีเทคนิค | Geotechnical work |
| LCBP3-C2 | CRW | งานด้านโยธา - Rock Works | Civil Works - Rock Works |
| LCBP3-C2 | DVW | ทีมนักประดาน้ำ | Team of Divers |
| LCBP3-C2 | MTT | งานทดสอบวัสดุ | Materials Testing |
| LCBP3-C2 | ARC | งานสถาปัตยกรรม | Architecture |
| LCBP3-C2 | STR | งานโครงสร้าง | Structural work |
| LCBP3-C2 | SAN | งานระบบสุขาภิบาล | Sanitation System |
| LCBP3-C2 | DRA | งานระบบระบายน้ำ | Drainage system work |
| LCBP3-C2 | TER | งานท่าเทียบเรือ | Terminal Work work |
| LCBP3-C2 | BUD | งานอาคาร | Building |
| LCBP3-C2 | ROW | งานถนนและสะพาน | Road and Bridge Work |
| LCBP3-C2 | MEC | งานเคริองกล | Mechanical work |
| LCBP3-C2 | OTH | อื่น ๆ | Others |
## 📌ตาราง rfa_types
| contract_code | type_code | type_name_en | type_name_th |
| ------------- | --------- | ---------------------------------------------- | ------------------------------------------------ |
| LCBP3-C1 | ADW | As Built Drawing | แบบร่างหลังการก่อสร้าง |
| LCBP3-C1 | BC | Box Culvert | ท่อระบายน้ำรูปกล่อง |
| LCBP3-C1 | BM | Benchmark | หมุดหลักฐาน |
| LCBP3-C1 | CER | Certificates | ใบรับรอง |
| LCBP3-C1 | CN | Canal Drainage | ระบบระบายน้ำในคลอง |
| LCBP3-C1 | CON | Contract | สัญญา |
| LCBP3-C1 | DDS | Design Data Submission | นำส่งข้อมูลการออกแบบ |
| LCBP3-C1 | DDW | Draft Drawing | แบบร่าง |
| LCBP3-C1 | DRW | Drawings (All Types) | แบบก่อสร้าง |
| LCBP3-C1 | DSN | Design/Calculation/Manual (All Stages) | ออกแบบ / คำนวณ / คู่มือ |
| LCBP3-C1 | GEN | General | ทั่วไป |
| LCBP3-C1 | ICR | Incident Report | รายงานการเกิดอุบัติเหตุและการบาดเจ็บ |
| LCBP3-C1 | INS | Insurances/Bond/Guarantee | การประกัน / พันธบัตร / การค้ำประกัน |
| LCBP3-C1 | INR | Inspection/Audit/Surveillance Report | รายงานการตรวจสอบ / การตรวจสอบ / รายงานการเฝ้าระวัง |
| LCBP3-C1 | ITP | Inspection and Test Plan | แผนการตรวจสอบและทดสอบ |
| LCBP3-C1 | JSA | Jobs Analysis | รายงานการวิเคราะห์ความปลอดภัย |
| LCBP3-C1 | MAN | Manual | คู่มือ |
| LCBP3-C1 | MAT | Materials/Equipment/Plant | วัสดุ / อุปกรณ์ / โรงงาน |
| LCBP3-C1 | MOM | Minutes of Meeting | รายงานการประชุม |
| LCBP3-C1 | MPR | Monthly Progress Report | รายงานความคืบหน้าประจำเดือน |
| LCBP3-C1 | MST | Method Statement for Construction/Installation | ขั้นตอนการก่อสร้าง / ติดตั้ง |
| LCBP3-C1 | NDS | Non-Design Data Submission | นำส่งข้อมูลที่ไม่เกี่ยวข้องกับการออกแบบ |
| LCBP3-C1 | PMA | Payment/Invoice/Retention/Estimate | การชำระเงิน / ใบแจ้งหนี้ / ประกันผลงาน / ประมาณการ |
| LCBP3-C1 | PRD | Procedure | ระเบียบปฏิบัติ |
| LCBP3-C1 | PRG | Progress of Construction | ความคืบหน้าของการก่อสร้าง / ภาพถ่าย / วิดีโอ |
| LCBP3-C1 | QMS | Quality Document (Plan/Work Instruction) | เอกสารด้านคุณภาพ (แผนงาน / ข้อแนะนำในการทำงาน) |
| LCBP3-C1 | RPT | Report | รายงาน |
| LCBP3-C1 | SAR | Semi Annual Report | รายงานประจำหกเดือน |
| LCBP3-C1 | SCH | Schedule and Program | แผนงาน |
| LCBP3-C1 | SDW | Shop Drawing | แบบขยายรายละเอียด |
| LCBP3-C1 | SI | Soil Investigation | การตรวจสอบดิน |
| LCBP3-C1 | SPE | Specification | ข้อกำหนด |
| LCBP3-C1 | TNR | Training Report | รายงานการฝึกปฏิบัติ |
| LCBP3-C1 | UC | Underground Construction | โครงสร้างใต้ดิน |
| LCBP3-C1 | VEN | Vendor | ผู้ขาย |
| LCBP3-C1 | VRO | Variation Request/Instruction/Order | คำขอเปลี่ยนแปลง / ข้อเสนอแนะ / ข้อเรียกร้อง |
| LCBP3-C1 | WTY | Warranty | การประกัน |
| LCBP3-C2 | GEN | General | ทั่วไป |
| LCBP3-C2 | CON | Contract | สัญญา |
| LCBP3-C2 | INS | Insurances/Bond/Guarantee | การประกัน / พันธบัตร / การค้ำประกัน |
| LCBP3-C2 | SCH | Schedule and Program | แผนงาน |
| LCBP3-C2 | PMA | Payment/Invoice/Retention/Estimate | การชำระเงิน / ใบแจ้งหนี้ / ประกันผลงาน / ประมาณการ |
| LCBP3-C2 | VRO | Variation Request/Instruction/Order | คำขอเปลี่ยนแปลง / ข้อเสนอแนะ / ข้อเรียกร้อง |
| LCBP3-C2 | VEN | Vendor | ผู้ขาย |
| LCBP3-C2 | WTY | Warranty | การประกัน |
| LCBP3-C2 | DRW | Drawings (All Types) | แบบก่อสร้าง |
| LCBP3-C2 | DDW | Draft Drawing | แบบร่าง |
| LCBP3-C2 | SDW | Shop Drawing | แบบขยายรายละเอียด |
| LCBP3-C2 | ADW | As Built Drawing | แบบร่างหลังการก่อสร้าง |
| LCBP3-C2 | DDS | Design Data Submission | นำส่งข้อมูลการออกแบบ |
| LCBP3-C2 | DSN | Design/Calculation/Manual (All Stages) | ออกแบบ / คำนวณ / คู่มือ |
| LCBP3-C2 | NDS | Non-Design Data Submission | นำส่งข้อมูลที่ไม่เกี่ยวข้องกับการออกแบบ |
| LCBP3-C2 | PRD | Procedure | ระเบียบปฏิบัติ |
| LCBP3-C2 | MST | Method Statement for Construction/Installation | ขั้นตอนการก่อสร้าง / ติดตั้ง |
| LCBP3-C2 | QMS | Quality Document (Plan/Work Instruction) | เอกสารด้านคุณภาพ (แผนงาน / ข้อแนะนำในการทำงาน) |
| LCBP3-C2 | INR | Inspection/Audit/Surveillance Report | รายงานการตรวจสอบ / การตรวจสอบ / รายงานการเฝ้าระวัง |
| LCBP3-C2 | ITP | Inspection and Test Plan | แผนการตรวจสอบและทดสอบ |
| LCBP3-C2 | MAT | Materials/Equipment/Plant | วัสดุ / อุปกรณ์ / โรงงาน |
| LCBP3-C2 | SPE | Specification | ข้อกำหนด |
| LCBP3-C2 | MAN | Manual | คู่มือ |
| LCBP3-C2 | CER | Certificates | ใบรับรอง |
| LCBP3-C2 | SAR | Semi Annual Report | รายงานประจำหกเดือน |
| LCBP3-C2 | JSA | Jobs Analysis | รายงานการวิเคราะห์ความปลอดภัย |
| LCBP3-C2 | MOM | Minutes of Meeting | รายงานการประชุม |
| LCBP3-C2 | MPR | Monthly Progress Report | รายงานความคืบหน้าประจำเดือน |
| LCBP3-C2 | ICR | Incident Report | รายงานการเกิดอุบัติเหตุและการบาดเจ็บ |
| LCBP3-C2 | PRG | Progress of Construction | ความคืบหน้าของการก่อสร้าง / ภาพถ่าย / วิดีโอ |
| LCBP3-C2 | RPT | Report | รายงาน |
| LCBP3-C2 | TNR | Training Report | รายงานการฝึกปฏิบัติ |
---

View File

@@ -1162,7 +1162,63 @@ ALTER TABLE rfa_revisions
ADD COLUMN v_ref_drawing_count INT GENERATED ALWAYS AS (
JSON_UNQUOTE(JSON_EXTRACT(details, '$.drawingCount'))
) VIRTUAL;
ALTER TABLE rfa_revisions
ADD COLUMN schema_version INT DEFAULT 1 COMMENT 'Version ของ JSON Schema'
AFTER details;
CREATE INDEX idx_rfa_rev_v_drawing_count ON rfa_revisions(v_ref_drawing_count);
-- ... (ต่อท้ายไฟล์เดิม)
-- ============================================================
-- ส่วนที่ 11: Unified Workflow Engine (Phase 6A/Phase 3)
-- ============================================================
DROP TABLE IF EXISTS workflow_histories;
DROP TABLE IF EXISTS workflow_instances;
DROP TABLE IF EXISTS workflow_definitions;
-- 1. ตารางเก็บนิยาม Workflow (Definition / DSL)
CREATE TABLE workflow_definitions (
id CHAR(36) NOT NULL PRIMARY KEY COMMENT 'UUID ของ Workflow Definition',
workflow_code VARCHAR(50) NOT NULL COMMENT 'รหัส Workflow เช่น RFA_FLOW_V1, CORRESPONDENCE_FLOW_V1',
version INT NOT NULL DEFAULT 1 COMMENT 'หมายเลข Version',
description TEXT NULL COMMENT 'คำอธิบาย Workflow',
dsl JSON NOT NULL COMMENT 'นิยาม Workflow ต้นฉบับ (YAML/JSON Format)',
compiled JSON NOT NULL COMMENT 'โครงสร้าง Execution Tree ที่ Compile แล้ว',
is_active BOOLEAN DEFAULT TRUE COMMENT 'สถานะการใช้งาน',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'วันที่แก้ไขล่าสุด',
-- ป้องกันการมี Workflow Code และ Version ซ้ำกัน
UNIQUE KEY uq_workflow_version (workflow_code, version)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'ตารางเก็บนิยามกฎการเดินเอกสาร (Workflow DSL)';
-- สร้าง Index สำหรับการค้นหา Workflow ที่ Active ล่าสุดได้เร็วขึ้น
CREATE INDEX idx_workflow_active ON workflow_definitions(workflow_code, is_active, version);
-- 2. ตารางเก็บ Workflow Instance (สถานะเอกสารจริง)
CREATE TABLE workflow_instances (
id CHAR(36) NOT NULL PRIMARY KEY COMMENT 'UUID ของ Instance',
definition_id CHAR(36) NOT NULL COMMENT 'อ้างอิง Definition ที่ใช้',
entity_type VARCHAR(50) NOT NULL COMMENT 'ประเภทเอกสาร (rfa_revision, correspondence_revision, circulation)',
entity_id VARCHAR(50) NOT NULL COMMENT 'ID ของเอกสาร (String/Int)',
current_state VARCHAR(50) NOT NULL COMMENT 'สถานะปัจจุบัน',
status ENUM('ACTIVE', 'COMPLETED', 'CANCELLED', 'TERMINATED') DEFAULT 'ACTIVE' COMMENT 'สถานะภาพรวม',
context JSON NULL COMMENT 'ตัวแปร Context สำหรับตัดสินใจ',
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
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'ตารางเก็บสถานะการเดินเรื่องของเอกสาร';
CREATE INDEX idx_wf_inst_entity ON workflow_instances(entity_type, entity_id);
CREATE INDEX idx_wf_inst_state ON workflow_instances(current_state);
-- 3. ตารางเก็บประวัติ (Audit Log / History)
CREATE TABLE workflow_histories (
id CHAR(36) NOT NULL PRIMARY KEY COMMENT 'UUID',
instance_id CHAR(36) NOT NULL COMMENT 'อ้างอิง Instance',
from_state VARCHAR(50) NOT NULL COMMENT 'สถานะต้นทาง',
to_state VARCHAR(50) NOT NULL COMMENT 'สถานะปลายทาง',
action VARCHAR(50) NOT NULL COMMENT 'Action ที่กระทำ',
action_by_user_id INT NULL COMMENT 'User ID ผู้กระทำ',
comment TEXT NULL COMMENT 'ความเห็น',
metadata JSON NULL COMMENT 'Snapshot ข้อมูล ณ ขณะนั้น',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_wf_hist_inst FOREIGN KEY (instance_id) REFERENCES workflow_instances(id) ON DELETE CASCADE
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'ตารางประวัติการเปลี่ยนสถานะ Workflow';
CREATE INDEX idx_wf_hist_instance ON workflow_histories(instance_id);
CREATE INDEX idx_wf_hist_user ON workflow_histories(action_by_user_id);
-- ============================================================
-- 5. PARTITIONING PREPARATION (Advance - Optional)
-- ============================================================

View File

@@ -1,724 +0,0 @@
<!DOCTYPE html>
<html lang="th" class="scroll-smooth">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DMS v1.3.0 - รายงานสรุปสถาปัตยกรรมระบบ</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Sarabun:wght@300;400;500;700&display=swap" rel="stylesheet">
<!-- Chosen Palette: Calm Harmony (Stone & Teal) -->
<!-- Application Structure Plan: A single-page application with a fixed top navigation (scroll-to-section). This structure is user-friendly, allowing exploration of the complex DMS specs thematically (Overview, Architecture, Modules, Features, Data Explorer) rather than forcing a linear read. The interactive "Data Explorer" is the key interaction, allowing users to dynamically query the database schema. This structure was chosen to synthesize all 4 source reports (Requirements, FullStack, Data Dictionary, SQL) into one cohesive and easily digestible dashboard for all stakeholders (devs, managers). -->
<!-- Visualization & Content Choices: 1. Architecture Diagram: (Report Info: Docker Stack from Req 2.1) -> (Goal: Organize) -> (Viz: HTML/Tailwind blocks) -> (Interaction: None) -> (Justification: No SVG/Mermaid rule. This is the clearest way to show the stack). 2. RBAC Chart: (Report Info: Roles/Permissions from Req 4.x, DD 2.x) -> (Goal: Compare) -> (Viz: Chart.js Horizontal Bar) -> (Interaction: Tooltip) -> (Justification: Clearly shows permission hierarchy). 3. Data Explorer: (Report Info: Data Dictionary/SQL) -> (Goal: Organize/Explore) -> (Viz: Dynamic HTML Table) -> (Interaction: Dropdown Select) -> (Justification: Makes the dense 44+ table schema interactive and searchable). -->
<!-- CONFIRMATION: NO SVG graphics used. NO Mermaid JS used. -->
<style>
body {
font-family: 'Sarabun', sans-serif;
background-color: #f5f5f4; /* stone-100 */
}
.chart-container {
position: relative;
width: 100%;
max-width: 600px; /* max-w-2xl */
margin-left: auto;
margin-right: auto;
height: 350px; /* h-~80 */
max-height: 40vh;
}
.chart-container-sm {
position: relative;
width: 100%;
max-width: 320px; /* max-w-xs */
margin-left: auto;
margin-right: auto;
height: 280px; /* h-72 */
max-height: 35vh;
}
@media (max-width: 640px) {
.chart-container {
height: 300px;
max-height: 50vh;
}
}
nav a {
transition: color 0.2s;
}
nav a:hover {
color: #0d9488; /* teal-600 */
}
.section-title {
font-size: 1.875rem; /* text-3xl */
font-weight: 700;
color: #1c1917; /* stone-900 */
margin-bottom: 1rem;
border-bottom: 2px solid #0d9488; /* teal-600 */
padding-bottom: 0.5rem;
}
.card {
background-color: #ffffff;
border-radius: 0.75rem; /* rounded-xl */
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); /* shadow-lg */
padding: 1.5rem; /* p-6 */
transition: all 0.3s;
display: flex;
flex-direction: column;
}
.card-content {
flex-grow: 1;
}
.card:hover {
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); /* shadow-xl */
transform: translateY(-2px);
}
.stat-card {
background-color: #f5f5f4; /* stone-100 */
border: 1px solid #d6d3d1; /* stone-300 */
border-radius: 0.5rem; /* rounded-lg */
padding: 1rem;
text-align: center;
}
.stat-value {
font-size: 2.25rem; /* text-4xl */
font-weight: 700;
color: #0d9488; /* teal-600 */
}
.stat-label {
font-size: 0.875rem; /* text-sm */
color: #57534e; /* stone-600 */
margin-top: 0.25rem;
}
.schema-relation {
font-size: 0.75rem; /* text-xs */
font-family: monospace;
color: #0d9488; /* teal-600 */
}
.schema-type {
font-size: 0.875rem; /* text-sm */
font-family: monospace;
color: #a16207; /* yellow-700 */
}
</style>
</head>
<body class="text-stone-700">
<!-- Header & Navigation -->
<header class="bg-white/80 backdrop-blur-md shadow-sm sticky top-0 z-50">
<nav class="container mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-16">
<div class="flex-shrink-0">
<h1 class="text-xl font-bold text-stone-800">DMS v1.3.0 <span class="text-teal-600">Report</span></h1>
</div>
<div class="hidden md:flex md:space-x-6 lg:space-x-8">
<a href="#overview" class="nav-link font-medium text-stone-600">ภาพรวม</a>
<a href="#architecture" class="nav-link font-medium text-stone-600">สถาปัตยกรรม</a>
<a href="#modules" class="nav-link font-medium text-stone-600">โมดูลเอกสาร</a>
<a href="#features" class="nav-link font-medium text-stone-600">ฟีเจอร์หลัก</a>
<a href="#data-explorer" class="nav-link font-medium text-stone-600">โครงสร้างข้อมูล</a>
</div>
<div class="md:hidden">
<select id="mobile-nav" class="block w-full rounded-md border-stone-300 shadow-sm focus:border-teal-500 focus:ring-teal-500">
<option value="#overview">ภาพรวม</option>
<option value="#architecture">สถาปัตยกรรม</option>
<option value="#modules">โมดูลเอกสาร</option>
<option value="#features">ฟีเจอร์หลัก</option>
<option value="#data-explorer">โครงสร้างข้อมูล</option>
</select>
</div>
</div>
</nav>
</header>
<!-- Main Content -->
<main class="container mx-auto p-4 sm:p-6 lg:p-8 pt-24">
<!-- 1. Overview Section -->
<section id="overview" class="mb-16 -mt-16 pt-20">
<h2 class="section-title">ภาพรวมระบบ (Overview)</h2>
<p class="mb-8 text-lg text-stone-600">
นี่คือรายงานสรุปเชิงโต้ตอบสำหรับ **ระบบจัดการเอกสาร (DMS) v1.3.0** แอปพลิเคชันนี้ถูกออกแบบมาเพื่อวิเคราะห์ข้อกำหนดของระบบ, สถาปัตยกรรม, และโครงสร้างข้อมูล เพื่อให้ทีมพัฒนาและผู้มีส่วนได้ส่วนเสียสามารถทำความเข้าใจภาพรวมของโปรเจกต์ที่ซับซ้อนนี้ได้อย่างรวดเร็ว
</p>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<div class="stat-card">
<div class="stat-value">6+</div>
<div class="stat-label">โมดูลเอกสารหลัก</div>
</div>
<div class="stat-card">
<div class="stat-value">44+</div>
<div class="stat-label">ตารางข้อมูล (Tables)</div>
</div>
<div class="stat-card">
<div class="stat-value">5+</div>
<div class="stat-label">วิวข้อมูล (Views)</div>
</div>
<div class="stat-card">
<div class="stat-value">7</div>
<div class="stat-label">Services (Docker)</div>
</div>
</div>
</section>
<!-- 2. Architecture Section -->
<section id="architecture" class="mb-16 -mt-16 pt-20">
<h2 class="section-title">สถาปัตยกรรมและเทคโนโลยี (System Architecture)</h2>
<p class="mb-8 text-lg text-stone-600">
ระบบ DMS v1.3.0 ถูกออกแบบบนสถาปัตยกรรม Headless/API-First ที่ทันสมัย โดยทำงานทั้งหมดบน QNAP Server ผ่าน Container Station (Docker) เพื่อให้ง่ายต่อการจัดการและบำรุงรักษา
</p>
<div class="card">
<h3 class="text-xl font-bold text-stone-800 mb-6 text-center">แผนผังการเชื่อมต่อ (Docker Container Stack)</h3>
<div class="w-full bg-stone-50 p-6 rounded-lg border border-stone-200">
<!-- Nginx Reverse Proxy -->
<div class="relative p-4 border-2 border-teal-600 rounded-lg bg-teal-50/50">
<div class="absolute -top-3 left-4 bg-teal-600 text-white px-3 py-0.5 rounded-full text-sm font-medium">Nginx Proxy Manager</div>
<p class="text-center text-sm font-medium text-teal-800 pt-2">np-dms.work (HTTPS)</p>
<!-- Internal Network 'lcbp3' -->
<div class="mt-6 p-4 border-2 border-dashed border-stone-400 rounded-lg">
<div class="absolute -top-3 right-4 bg-stone-500 text-white px-3 py-0.5 rounded-full text-sm font-medium">Network: 'lcbp3'</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mt-4">
<!-- Frontend -->
<div class="flex flex-col items-center">
<div class="w-full p-4 bg-white border border-blue-500 rounded-lg shadow-md text-center">
<h4 class="font-bold text-blue-700">Next.js (Frontend)</h4>
<p class="text-xs text-stone-600">ให้บริการ UI (React)</p>
</div>
</div>
<!-- Backend -->
<div class="flex flex-col items-center">
<div class="w-full p-4 bg-white border border-red-500 rounded-lg shadow-md text-center">
<h4 class="font-bold text-red-700">NestJS (Backend)</h4>
<p class="text-xs text-stone-600">จัดการ API & Business Logic</p>
</div>
</div>
<!-- Other Services -->
<div class="flex flex-col items-center">
<div class="w-full p-4 bg-white border border-purple-500 rounded-lg shadow-md text-center">
<h4 class="font-bold text-purple-700">N8N (Automation)</h4>
<p class="text-xs text-stone-600">จัดการ Workflow & Line Notify</p>
</div>
</div>
</div>
<!-- Arrow (Backend -> Other Services) -->
<div class="relative h-10 my-2">
<div class="absolute top-1/2 left-1/3 md:left-1/2 w-2/3 md:w-1/2 -translate-x-1/2 -translate-y-1/2 border-t-2 border-stone-500"></div>
<div class="absolute top-1/2 left-1/3 md:left-1/2 -translate-x-1/2 -translate-y-1/2 text-stone-500"></div>
</div>
<!-- Data Layer -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="w-full p-4 bg-white border border-orange-500 rounded-lg shadow-md text-center md:col-start-2">
<h4 class="font-bold text-orange-700">MariaDB (Database)</h4>
<p class="text-xs text-stone-600">เก็บข้อมูลทั้งหมด (SQL)</p>
</div>
<div class="w-full p-4 bg-white border border-green-500 rounded-lg shadow-md text-center">
<h4 class="font-bold text-green-700">Elasticsearch (Search)</h4>
<p class="text-xs text-stone-600">ให้บริการค้นหาขั้นสูง</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 3. Document Modules Section -->
<section id="modules" class="mb-16 -mt-16 pt-20">
<h2 class="section-title">โมดูลเอกสารหลัก (Document Modules)</h2>
<p class="mb-8 text-lg text-stone-600">
ระบบ DMS แบ่งการจัดการเอกสารออกเป็นโมดูลย่อยที่ชัดเจน แต่ละโมดูลมี Workflow และตารางข้อมูลเฉพาะของตนเอง แต่ทั้งหมดเชื่อมโยงกับโครงสร้างเอกสารกลาง (Correspondence)
</p>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div class="card">
<div class="card-content">
<h3 class="text-xl font-bold text-teal-700 mb-2">1. Correspondence (เอกสารโต้ตอบ)</h3>
<p class="text-sm text-stone-600 mb-4">โมดูลหลักสำหรับเอกสารเข้า-ออกทั่วไป (จดหมาย, เมโม) เป็นตาราง "แม่" สำหรับเอกสารประเภทอื่นเกือบทั้งหมด</p>
<span class="text-xs font-semibold text-stone-500">ตารางข้อมูลหลัก:</span>
<ul class="text-sm list-disc list-inside ml-2 text-stone-700">
<li>`correspondences` (Master)</li>
<li>`correspondence_revisions` (Child)</li>
<li>`correspondence_recipients`</li>
</ul>
</div>
</div>
<div class="card">
<div class="card-content">
<h3 class="text-xl font-bold text-teal-700 mb-2">2. RFA (ขออนุมัติ)</h3>
<p class="text-sm text-stone-600 mb-4">โมดูลสำหรับเอกสารขออนุมัติ (Request for Approval) เช่น ขออนุมัติแบบ, วัสดุ, หรือเอกสาร ที่มี Workflow ชัดเจน (Draft, Submit, Review, Approved)</p>
<span class="text-xs font-semibold text-stone-500">ตารางข้อมูลหลัก:</span>
<ul class="text-sm list-disc list-inside ml-2 text-stone-700">
<li>`rfas` (Master)</li>
<li>`rfa_revisions` (Child)</li>
<li>`rfa_status_codes`</li>
<li>`rfa_items` (เชื่อม Shop Drawing)</li>
</ul>
</div>
</div>
<div class="card">
<div class="card-content">
<h3 class="text-xl font-bold text-teal-700 mb-2">3. Drawing (แบบแปลน)</h3>
<p class="text-sm text-stone-600 mb-4">จัดการแบบแปลน 2 ประเภทหลัก: แบบตามสัญญา (Contract Drawing) และแบบสำหรับก่อสร้าง (Shop Drawing) ซึ่ง Shop Drawing จะถูกอ้างอิงใน RFA</p>
<span class="text-xs font-semibold text-stone-500">ตารางข้อมูลหลัก:</span>
<ul class="text-sm list-disc list-inside ml-2 text-stone-700">
<li>`contract_drawings`</li>
<li>`shop_drawings` (Master)</li>
<li>`shop_drawing_revisions` (Child)</li>
<li>`shop_drawing_revision_contract_refs`</li>
</ul>
</div>
</div>
<div class="card">
<div class="card-content">
<h3 class="text-xl font-bold text-teal-700 mb-2">4. Transmittal (เอกสารนำส่ง)</h3>
<p class="text-sm text-stone-600 mb-4">เอกสารนำส่ง (คล้ายใบปะหน้า) ที่ใช้สำหรับรวบรวมเอกสาร RFA หลายๆ ฉบับ เพื่อส่งให้ผู้รับในคราวเดียว</p>
<span class="text-xs font-semibold text-stone-500">ตารางข้อมูลหลัก:</span>
<ul class="text-sm list-disc list-inside ml-2 text-stone-700">
<li>`transmittals` (1:1 with Correspondence)</li>
<li>`transmittal_items` (เชื่อม RFA)</li>
</ul>
</div>
</div>
<div class="card">
<div class="card-content">
<h3 class="text-xl font-bold text-teal-700 mb-2">5. Circulation (ใบเวียนภายใน)</h3>
<p class="text-sm text-stone-600 mb-4">ระบบใบเวียนภายในองค์กร (Internal) ใช้สำหรับส่งเอกสาร (ที่อ้างอิงจาก Correspondence) เพื่อให้ทีมตรวจสอบ, รับทราบ, หรือดำเนินการ</p>
<span class="text-xs font-semibold text-stone-500">ตารางข้อมูลหลัก:</span>
<ul class="text-sm list-disc list-inside ml-2 text-stone-700">
<li>`circulations` (Master)</li>
<li>`circulation_assignees` (Tasks)</li>
<li>`circulation_actions` (Logs)</li>
</ul>
</div>
</div>
<div class="card">
<div class="card-content">
<h3 class="text-xl font-bold text-teal-700 mb-2">6. สัดส่วนตารางข้อมูล</h3>
<p class="text-sm text-stone-600 mb-4">แผนภูมิแสดงสัดส่วนตารางข้อมูลที่เกี่ยวข้องในแต่ละโมดูลหลัก (ไม่รวมตาราง Core และ RBAC)</p>
<div class="chart-container-sm">
<canvas id="moduleChart"></canvas>
</div>
</div>
</div>
</div>
</section>
<!-- 4. Core Features Section -->
<section id="features" class="mb-16 -mt-16 pt-20">
<h2 class="section-title">ฟีเจอร์หลัก (Core Features)</h2>
<p class="mb-8 text-lg text-stone-600">
นอกเหนือจากโมดูลเอกสาร ระบบยังมีฟีเจอร์สนับสนุนที่สำคัญ ซึ่งทำงานข้ามโมดูลต่างๆ เพื่อให้ระบบทำงานได้อย่างสมบูรณ์
</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="card">
<h3 class="text-xl font-bold text-stone-800 mb-4">การจัดการสิทธิ์ (RBAC)</h3>
<p class="text-sm text-stone-600 mb-4">
ระบบใช้ Role-Based Access Control (RBAC) ที่ละเอียดมาก ผู้ใช้ (Users) จะได้รับบทบาท (Roles) ซึ่งผูกกับสิทธิ์ (Permissions)
แผนภูมินี้แสดงตัวอย่างจำนวนสิทธิ์ (Permissions) ที่แต่ละบทบาทพื้นฐานอาจมี เพื่อให้เห็นภาพความซับซ้อนในการเข้าถึง
</p>
<div class="chart-container">
<canvas id="rbacChart"></canvas>
</div>
</div>
<div class="card">
<h3 class="text-xl font-bold text-stone-800 mb-4">การสร้างเลขที่เอกสาร (Numbering)</h3>
<p class="text-sm text-stone-600 mb-4">
หนึ่งในส่วนที่สำคัญที่สุด คือการสร้างเลขที่เอกสาร (Running Number) เพื่อป้องกันเลขที่ซ้ำ (Race Condition) ระบบใช้ Stored Procedure (`sp_get_next_document_number`) ใน MariaDB ซึ่งใช้คำสั่ง `SELECT ... FOR UPDATE` เพื่อ "ล็อก" แถวของตัวนับก่อนที่จะอัปเดตค่า
</p>
<pre class="bg-stone-900 text-stone-100 text-sm rounded-lg p-4 overflow-x-auto">
<span class="text-blue-400">SELECT</span> last_number
<span class="text-blue-400">INTO</span> v_last_number
<span class="text-blue-400">FROM</span> document_number_counters
<span class="text-blue-400">WHERE</span> project_id = p_project_id
<span class="text-blue-400">AND</span> ...
<span class="text-red-400">FOR UPDATE</span>;
<span class="text-blue-400">IF</span> v_last_number IS NULL <span class="text-blue-400">THEN</span>
<span class="text-blue-400">INSERT INTO</span> ... (last_number) <span class="text-blue-400">VALUES</span> (1);
<span class="text-blue-400">SET</span> p_next_number = 1;
<span class="text-blue-400">ELSE</span>
<span class="text-blue-400">SET</span> p_next_number = v_last_number + 1;
<span class="text-blue-400">UPDATE</span> document_number_counters
<span class="text-blue-400">SET</span> last_number = p_next_number
<span class="text-blue-400">WHERE</span> ...;
<span class="text-blue-400">END IF</span>;</pre>
</div>
<div class="card">
<h3 class="text-xl font-bold text-stone-800 mb-4">การค้นหา (Advanced Search)</h3>
<p class="text-sm text-stone-600 mb-4">
(Req 6.2) ระบบใช้ **Elasticsearch** เป็น Service แยกต่างหากสำหรับให้บริการค้นหาขั้นสูง
NestJS (Backend) จะทำการ Index ข้อมูลจาก Views (`v_current_correspondences`, `v_current_rfas` ฯลฯ) ไปยัง Elasticsearch
ทำให้ผู้ใช้สามารถค้นหาแบบ Full-text ข้ามโมดูลทั้งหมด (Correspondence, RFA, Drawing) ได้อย่างรวดเร็ว
</p>
</div>
<div class="card">
<h3 class="text-xl font-bold text-stone-800 mb-4">การแจ้งเตือน (Notifications)</h3>
<p class="text-sm text-stone-600 mb-4">
(Req 6.7) ระบบใช้ **N8N (Automation)** เป็นศูนย์กลางการแจ้งเตือน
เมื่อมีเหตุการณ์สำคัญ (เช่น มีการสร้างใบเวียนใหม่) NestJS (Backend) จะยิง Webhook ไปที่ N8N
จากนั้น N8N จะทำหน้าที่ส่งการแจ้งเตือนไปยัง **Line Notify** หรือ Email ตาม Workflow ที่ตั้งค่าไว้
</p>
</div>
</div>
</section>
<!-- 5. Data Explorer Section -->
<section id="data-explorer" class="mb-16 -mt-16 pt-20">
<h2 class="section-title">สำรวจโครงสร้างข้อมูล (Data Explorer)</h2>
<p class="mb-8 text-lg text-stone-600">
ระบบ DMS มีตารางข้อมูลมากกว่า 44 ตารางเพื่อรองรับฟีเจอร์ที่ซับซ้อน เลือกตารางจากรายการด้านล่างเพื่อดูโครงสร้าง, ประเภทข้อมูล, และความสัมพันธ์ (Foreign Keys)
</p>
<div class="card">
<div class="mb-4">
<label for="table-select" class="block text-sm font-medium text-stone-700 mb-1">เลือกตารางเพื่อดูรายละเอียด:</label>
<select id="table-select" class="block w-full md:w-1/2 lg:w-1/3 rounded-md border-stone-300 shadow-sm focus:border-teal-500 focus:ring-teal-500">
<option value="users">users (ผู้ใช้)</option>
<option value="roles">roles (บทบาท)</option>
<option value="permissions">permissions (สิทธิ์)</option>
<option value="user_project_roles">user_project_roles (สิทธิ์ในโปรเจกต์)</option>
<option value="correspondences">correspondences (เอกสาร - Master)</option>
<option value="correspondence_revisions">correspondence_revisions (เอกสาร - Child)</option>
<option value="rfas">rfas (RFA - Master)</option>
<option value="rfa_revisions">rfa_revisions (RFA - Child)</option>
<option value="shop_drawings">shop_drawings (Shop Drawing - Master)</option>
<option value="shop_drawing_revisions">shop_drawing_revisions (Shop Drawing - Child)</option>
<option value="circulations">circulations (ใบเวียน - Master)</option>
<option value="circulation_assignees">circulation_assignees (ใบเวียน - Tasks)</option>
<option value="attachments">attachments (ไฟล์แนบ - Master)</option>
</select>
</div>
<div>
<h4 id="schema-title" class="text-2xl font-bold text-teal-700 mb-1"></h4>
<p id="schema-description" class="text-sm text-stone-600 mb-6"></p>
<div class="overflow-x-auto border border-stone-200 rounded-lg">
<table class="min-w-full divide-y divide-stone-200">
<thead class="bg-stone-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-stone-600 uppercase tracking-wider">Column (Field)</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-stone-600 uppercase tracking-wider">Type</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-stone-600 uppercase tracking-wider">Description / Relation</th>
</tr>
</thead>
<tbody id="schema-body" class="bg-white divide-y divide-stone-200">
<!-- JS will populate this -->
</tbody>
</table>
</div>
</div>
</div>
</section>
</main>
<footer class="text-center py-8 mt-12 border-t border-stone-200">
<p class="text-sm text-stone-500">Interactive Report generated for DMS v1.3.0</p>
</footer>
<script>
document.addEventListener('DOMContentLoaded', () => {
// --- Chart.js Rendering ---
// 1. Module Chart (Donut)
const moduleCtx = document.getElementById('moduleChart');
if (moduleCtx) {
new Chart(moduleCtx, {
type: 'donut',
data: {
labels: ['Correspondence', 'RFA', 'Drawing', 'Circulation', 'Transmittal'],
datasets: [{
label: 'จำนวนตารางที่เกี่ยวข้อง',
data: [5, 5, 8, 5, 2],
backgroundColor: [
'rgb(20, 184, 166)', // teal-500
'rgb(15, 118, 110)', // teal-700
'rgb(107, 114, 128)', // stone-500
'rgb(55, 65, 81)', // stone-700
'rgb(245, 158, 11)' // amber-500
],
hoverOffset: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: {
font: { family: 'Sarabun' }
}
}
}
}
});
}
// 2. RBAC Chart (Horizontal Bar)
const rbacCtx = document.getElementById('rbacChart');
if (rbacCtx) {
new Chart(rbacCtx, {
type: 'bar',
data: {
labels: ['Viewer (ผู้ดู)', 'Editor (ผู้แก้ไข)', 'Org Admin (แอดมินองค์กร)', 'Superadmin (ผู้ดูแลระบบ)'],
datasets: [{
label: 'จำนวนสิทธิ์ (ตัวอย่าง)',
data: [5, 12, 20, 30],
backgroundColor: 'rgba(13, 148, 136, 0.6)', // teal-600
borderColor: 'rgb(15, 118, 110)', // teal-700
borderWidth: 1
}]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
beginAtZero: true,
grid: { display: false }
},
y: {
grid: { display: false }
}
},
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: (context) => ` ${context.dataset.label}: ${context.raw} สิทธิ์`
}
}
}
}
});
}
// --- Data Explorer Logic ---
const databaseSchema = {
'users': {
description: 'ตารางเก็บข้อมูลผู้ใช้ในระบบ (Req 4.x)',
columns: [
{ name: 'user_id', type: 'INT (PK)', desc: 'ID หลักของผู้ใช้' },
{ name: 'username', type: 'VARCHAR(100)', desc: 'ชื่อเข้าระบบ (ต้อง Unique)' },
{ name: 'password_hash', type: 'VARCHAR(255)', desc: 'รหัสผ่านที่ถูก Hashed (Bcrypt)' },
{ name: 'email', type: 'VARCHAR(100)', desc: 'อีเมล' },
{ name: 'organization_id', type: 'INT', desc: 'องค์กรที่ผู้ใช้สังกัด', relation: 'organizations(id)' },
{ name: 'is_active', type: 'BOOLEAN', desc: 'สถานะการใช้งาน' },
{ name: 'is_superadmin', type: 'BOOLEAN', desc: 'สถานะผู้ดูแลสูงสุด' }
]
},
'roles': {
description: 'ตาราง Master เก็บ "บทบาท" ทั้งหมด (Req 4.3)',
columns: [
{ name: 'role_id', type: 'INT (PK)', desc: 'ID ของบทบาท' },
{ name: 'role_code', type: 'VARCHAR(50)', desc: 'รหัสบทบาท (เช่น EDITOR, VIEWER)' },
{ name: 'role_name', type: 'VARCHAR(100)', desc: 'ชื่อบทบาท (เช่น "ผู้แก้ไข")' },
{ name: 'is_system', type: 'BOOLEAN', desc: 'เป็นบทบาทของระบบ (ห้ามแอดมินลบ)' }
]
},
'permissions': {
description: 'ตาราง Master เก็บ "สิทธิ์" การกระทำทั้งหมดในระบบ (Req 4.3)',
columns: [
{ name: 'permission_id', type: 'INT (PK)', desc: 'ID ของสิทธิ์' },
{ name: 'permission_code', type: 'VARCHAR(100)', desc: 'รหัสสิทธิ์ (เช่น corr.create, rfa.view)' },
{ name: 'permission_group', type: 'VARCHAR(50)', desc: 'กลุ่มของสิทธิ์ (เช่น CORR, RFA, ADMIN)' }
]
},
'user_project_roles': {
description: 'ตารางเชื่อม M:N (Junction) ระหว่าง User, Role, และ Project',
columns: [
{ name: 'id', type: 'INT (PK)', desc: 'ID ของการผูกสิทธิ์' },
{ name: 'user_id', type: 'INT', desc: 'ผู้ใช้', relation: 'users(user_id)' },
{ name: 'role_id', type: 'INT', desc: 'บทบาท', relation: 'roles(role_id)' },
{ name: 'project_id', type: 'INT', desc: 'โปรเจกต์', relation: 'projects(id)' }
]
},
'correspondences': {
description: 'ตาราง "แม่" (Master) สำหรับเอกสารทั้งหมด (Req 3.2)',
columns: [
{ name: 'id', type: 'INT (PK)', desc: 'ID หลักของเอกสาร' },
{ name: 'correspondence_number', type: 'VARCHAR(100)', desc: 'เลขที่เอกสาร (จาก Stored Procedure)' },
{ name: 'project_id', type: 'INT', desc: 'โปรเจกต์', relation: 'projects(id)' },
{ name: 'correspondence_type_id', type: 'INT', desc: 'ประเภทเอกสาร', relation: 'correspondence_types(id)' },
{ name: 'originator_id', type: 'INT', desc: 'องค์กรผู้ส่ง', relation: 'organizations(id)' },
{ name: 'recipient_id', type: 'INT', desc: 'องค์กรผู้รับ (หลัก)', relation: 'organizations(id)' },
{ name: 'deleted_at', type: 'DATETIME', desc: 'สำหรับ Soft Delete' }
]
},
'correspondence_revisions': {
description: 'ตาราง "ลูก" (Child) เก็บประวัติการแก้ไขและเนื้อหาของเอกสาร (Req 3.2)',
columns: [
{ name: 'id', type: 'INT (PK)', desc: 'ID ของ Revision' },
{ name: 'correspondence_id', type: 'INT', desc: 'ID เอกสาร (Master)', relation: 'correspondences(id)' },
{ name: 'revision_number', type: 'INT', desc: 'เลข Revision (เช่น 0, 1, 2)' },
{ name: 'is_current', type: 'BOOLEAN', desc: 'เป็น Revision ปัจจุบันหรือไม่' },
{ name: 'title', type: 'VARCHAR(255)', desc: 'ชื่อเรื่อง/หัวข้อ' },
{ name: 'document_date', type: 'DATE', desc: 'วันที่ในเอกสาร' },
{ name: 'issued_date', type: 'DATETIME', desc: 'วันที่ส่ง (Submit)' },
{ name: 'details', type: 'JSON', desc: 'ข้อมูลอื่นๆ (ถ้ามี)' }
]
},
'rfas': {
description: 'ตาราง "แม่" (Master) สำหรับเอกสาร RFA (Req 3.5)',
columns: [
{ name: 'id', type: 'INT (PK)', desc: 'ID หลักของ RFA' },
{ name: 'rfa_type_id', type: 'INT', desc: 'ประเภท RFA (DWG, MAT, DOC)', relation: 'rfa_types(id)' },
{ name: 'revision_number', type: 'INT', desc: 'Revision ล่าสุด (เช่น 0, 1)' }
]
},
'rfa_revisions': {
description: 'ตาราง "ลูก" (Child) เก็บประวัติและสถานะของ RFA (Req 3.5)',
columns: [
{ name: 'id', type: 'INT (PK)', desc: 'ID ของ RFA Revision' },
{ name: 'correspondence_id', type: 'INT', desc: 'ID เอกสาร (Master)', relation: 'correspondences(id)' },
{ name: 'rfa_id', type: 'INT', desc: 'ID RFA (Master)', relation: 'rfas(id)' },
{ name: 'rfa_status_code_id', type: 'INT', desc: 'สถานะ (เช่น DFT, FAP)', relation: 'rfa_status_codes(id)' },
{ name: 'rfa_approve_code_id', type: 'INT', desc: 'ผลการอนุมัติ (เช่น A, B, C)', relation: 'rfa_approve_codes(id)' },
{ name: 'title', type: 'VARCHAR(255)', desc: 'ชื่อเรื่อง' }
]
},
'shop_drawings': {
description: 'ตาราง "แม่" (Master) สำหรับ Shop Drawing (Req 3.4)',
columns: [
{ name: 'id', type: 'INT (PK)', desc: 'ID หลักของ Shop Drawing' },
{ name: 'project_id', type: 'INT', desc: 'โปรเจกต์', relation: 'projects(id)' },
{ name: 'drawing_number', type: 'VARCHAR(100)', desc: 'เลขที่แบบ (Unique)' },
{ name: 'title', type: 'VARCHAR(500)', desc: 'ชื่อแบบ' }
]
},
'shop_drawing_revisions': {
description: 'ตาราง "ลูก" (Child) เก็บ Revision ของ Shop Drawing (Req 3.4)',
columns: [
{ name: 'id', type: 'INT (PK)', desc: 'ID ของ Revision' },
{ name: 'shop_drawing_id', type: 'INT', desc: 'ID Master', relation: 'shop_drawings(id)' },
{ name: 'revision_number', type: 'VARCHAR(10)', desc: 'เลข Revision (เช่น A, B, 0, 1)' },
{ name: 'revision_date', type: 'DATE', desc: 'วันที่ของ Revision' }
]
},
'circulations': {
description: 'ตาราง "แม่" (Master) สำหรับใบเวียนภายใน (Req 3.7)',
columns: [
{ name: 'id', type: 'INT (PK)', desc: 'ID ของใบเวียน' },
{ name: 'correspondence_id', type: 'INT', desc: 'เอกสารที่อ้างอิง', relation: 'correspondences(id)' },
{ name: 'organization_id', type: 'INT', desc: 'องค์กรเจ้าของใบเวียน', relation: 'organizations(id)' },
{ name: 'circulation_no', type: 'VARCHAR(100)', desc: 'เลขที่ใบเวียน' },
{ name: 'circulation_subject', type: 'VARCHAR(500)', desc: 'เรื่อง' },
{ name: 'circulation_status_code', type: 'VARCHAR(20)', desc: 'สถานะ (OPEN, COMPLETED)', relation: 'circulation_status_codes(code)' }
]
},
'circulation_assignees': {
description: 'ตาราง "Task" สำหรับผู้ที่ต้องดำเนินการในใบเวียน (Req 3.7.4)',
columns: [
{ name: 'id', type: 'INT (PK)', desc: 'ID ของ Task' },
{ name: 'circulation_id', type: 'INT', desc: 'ใบเวียน', relation: 'circulations(id)' },
{ name: 'user_id', type: 'INT', desc: 'ผู้ได้รับมอบหมาย', relation: 'users(user_id)' },
{ name: 'assignee_type', type: 'ENUM', desc: 'ประเภท (MAIN, ACTION, INFO)' },
{ name: 'deadline', type: 'DATE', desc: 'วันที่กำหนดเสร็จ' },
{ name: 'is_completed', type: 'BOOLEAN', desc: 'สถานะว่าเสร็จสิ้นหรือยัง' }
]
},
'attachments': {
description: 'ตาราง "แม่" (Master) สำหรับไฟล์แนบทั้งหมด (Req 3.9)',
columns: [
{ name: 'id', type: 'INT (PK)', desc: 'ID ของไฟล์' },
{ name: 'original_filename', type: 'VARCHAR(255)', desc: 'ชื่อไฟล์เดิม' },
{ name: 'stored_filename', type: 'VARCHAR(255)', desc: 'ชื่อไฟล์ที่เก็บ (UUID)' },
{ name: 'file_path', type: 'VARCHAR(500)', desc: 'Path ที่เก็บไฟล์บน Server' },
{ name: 'mime_type', type: 'VARCHAR(100)', desc: 'ประเภทไฟล์' },
{ name: 'file_size', type: 'INT', desc: 'ขนาดไฟล์ (bytes)' },
{ name: 'uploaded_by_user_id', type: 'INT', desc: 'ผู้อัปโหลด', relation: 'users(user_id)' }
]
}
};
const tableSelect = document.getElementById('table-select');
const schemaTitle = document.getElementById('schema-title');
const schemaDescription = document.getElementById('schema-description');
const schemaBody = document.getElementById('schema-body');
function updateSchemaView(tableName) {
const tableData = databaseSchema[tableName];
if (!tableData) return;
schemaTitle.textContent = tableName;
schemaDescription.textContent = tableData.description;
let html = '';
tableData.columns.forEach(col => {
const relationHtml = col.relation
? `<span class="schema-relation block">FK &rarr; ${col.relation}</span>`
: '';
html += `
<tr class="hover:bg-stone-50">
<td class="px-6 py-4 whitespace-nowrap">
<strong class="text-sm font-medium text-stone-900">${col.name}</strong>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="schema-type">${col.type}</span>
</td>
<td class="px-6 py-4">
<span class="text-sm text-stone-700">${col.desc}</span>
${relationHtml}
</td>
</tr>
`;
});
schemaBody.innerHTML = html;
}
tableSelect.addEventListener('change', (e) => updateSchemaView(e.target.value));
// Initial load
updateSchemaView(tableSelect.value);
// --- Navigation Logic ---
// 1. Mobile Navigation
const mobileNav = document.getElementById('mobile-nav');
mobileNav.addEventListener('change', (e) => {
const targetId = e.target.value;
const targetElement = document.querySelector(targetId);
if (targetElement) {
targetElement.scrollIntoView({ behavior: 'smooth' });
}
});
// 2. Desktop Smooth Scroll
const desktopLinks = document.querySelectorAll('a.nav-link');
desktopLinks.forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const targetId = this.getAttribute('href');
const targetElement = document.querySelector(targetId);
if (targetElement) {
targetElement.scrollIntoView({ behavior: 'smooth' });
}
});
});
});
</script>
</body>
</html>

View File

@@ -1,13 +1,13 @@
# 📝 **Documents Management System Version 1.4.4: Application Requirements Specification**
**สถานะ:** FINAL-Rev.04
**สถานะ:** FINAL-Rev.05
**วันที่:** 2025-11-26
**อ้างอิงพื้นฐาน:** v1.4.3
**อ้างอิงพื้นฐาน:** v1.4.4
**Classification:** Internal Technical Documentation
## 📌 **1. วัตถุประสงค์**
สร้างเว็บแอปพลิเคชันสำหรับระบบบริหารจัดการเอกสารโครงการ (Document Management System - DMS) แบบครบวงจร ที่เน้นความปลอดภัยสูงสุด ความถูกต้องของข้อมูล (Data Integrity) และรองรับการขยายตัวในอนาคต (Scalability) โดยแก้ไขปัญหา Race Condition และเพิ่มความเสถียรในการจัดการไฟล์และ Workflow
สร้างเว็บแอปพลิเคชันสำหรับระบบบริหารจัดการเอกสารโครงการ (Document Management System - DMS) แบบครบวงจร ที่เน้นความปลอดภัยสูงสุด ความถูกต้องของข้อมูล (Data Integrity) และรองรับการขยายตัวในอนาคต (Scalability) โดยแก้ไขปัญหา Race Condition และเพิ่มความเสถียรในการจัดการไฟล์ และใช้ Unified Workflow Engine ในการจัดการกระบวนการอนุมัติทั้งหมดเพื่อความยืดหยุ่น
- มีฟังก์ชันหลักในการอัปโหลด จัดเก็บ ค้นหา แชร์ และควบคุมสิทธิ์การเข้าถึงเอกสาร
- ช่วยลดการใช้เอกสารกระดาษ เพิ่มความปลอดภัยในการจัดเก็บข้อมูล
@@ -50,12 +50,14 @@
### **2.3 Core Services:**
- **Code Hosting:** Gitea (Self-hosted on QNAP)
- Application name: git
- Service name: gitea
- Domain: `git.np-dms.work`
- หน้าที่: เป็นศูนย์กลางในการเก็บและจัดการเวอร์ชันของโค้ด (Source Code) สำหรับทุกส่วน
- **Backend / Data Platform:** NestJS
- Application name: lcbp3-backend
- Service name: backend
- Domain: `backend.np-dms.work`
@@ -63,6 +65,7 @@
- หน้าที่: จัดการโครงสร้างข้อมูล (Data Models), สร้าง API, จัดการสิทธิ์ผู้ใช้ (Roles & Permissions), และสร้าง Workflow ทั้งหมดของระบบ
- **Database:** MariaDB 10.11
- Application name: lcbp3-db
- Service name: mariadb
- Domain: `db.np-dms.work`
@@ -70,6 +73,7 @@
- Tooling: DBeaver (Community Edition), phpmyadmin สำหรับการออกแบบและจัดการฐานข้อมูล
- **Database Management:** phpMyAdmin
- Application name: lcbp3-db
- Service: phpmyadmin:5-apache
- Service name: pma
@@ -77,6 +81,7 @@
- หน้าที่: จัดการฐานข้อมูล mariadb ผ่าน Web UI
- **Frontend:** Next.js
- Application name: lcbp3-frontend
- Service name: frontend
- Domain: `lcbp3.np-dms.work`
@@ -86,6 +91,7 @@
- หน้าที่: สร้างหน้าตาเว็บแอปพลิเคชันสำหรับให้ผู้ใช้งานเข้ามาดู Dashboard, จัดการเอกสาร, และติดตามงาน โดยจะสื่อสารกับ Backend ผ่าน API
- **Workflow Automation:** n8n
- Application name: lcbp3-n8n
- Service: n8nio/n8n:latest
- Service name: n8n
@@ -93,6 +99,7 @@
- หน้าที่: จัดการ workflow ระหว่าง Backend และ Line
- **Reverse Proxy:** Nginx Proxy Manager
- Application name: lcbp3-npm
- Service: Nginx Proxy Manager (nginx-proxy-manage: latest)
- Service name: npm
@@ -104,11 +111,11 @@
### **2.4 Business Logic & Consistency (ปรับปรุง):**
- **2.4.1 ตรรกะทางธุรกิจที่ซับซ้อนทั้งหมด** (เช่น การเปลี่ยนสถาน Workflow [cite: 3.5.3, 3.6.5], การบังคับใช้สิทธิ์ [cite: 4.4], การตรวจสอบ Deadline [cite: 3.2.5]) **จะถูกจัดการในฝั่ง Backend (NestJS)** [cite: 2.3] เพื่อให้สามารถบำรุงรักษาและทดสอบได้ง่าย (Testability)
- **2.4.1 Unified Workflow Engine (หลัก):** ระบบการเดินเอกสารทั้งหมด (Correspondence, RFA, Circulation) ต้อง ใช้ Engine กลางเดียวกัน โดยกำหนด Logic ผ่าน Workflow DSL (JSON Configuration) แทนการเขียน Hard-coded ลงในตาราง
- **2.4.2 Unified Workflow Engine (ใหม่):** รวม Logic การเดินเอกสารของ `CorrespondenceRouting` และ `RfaWorkflow` ให้ใช้ Core Engine เดียวกันเพื่อลดความซ้ำซ้อนและง่ายต่อการบำรุงรักษา
- **2.4.2 Separation of Concerns:** Module ต่างๆ (RFA, Correspondence) จะเก็บเฉพาะข้อมูลของเอกสาร (Data) ส่วนสถานะและการเปลี่ยนสถานะ (State Transition) จะถูกจัดการโดย Workflow Engine
- **2.4.3 Idempotency Keys (ใหม่):** API ที่สำคัญ (เช่น Submit Document, Approve) ต้องบังคับส่ง Header `Idempotency-Key` เพื่อป้องกันการทำรายการซ้ำจากการกดปุ่มรัวๆ หรือ Network Retry
- **2.4.3 Idempotency & Locking:** ใช้กลไกเดิมในการป้องกันการทำรายการซ้ำ
- **2.4.4 Optimistic Locking (ใหม่):** ใช้ Version Column ใน Database ควบคู่กับ Redis Lock สำหรับการสร้างเลขที่เอกสาร เพื่อเป็น Safety Net ชั้นสุดท้าย
@@ -190,22 +197,28 @@
- 3.4.3. การสร้างเอกสาร: ผู้ใช้ที่มีสิทธิ์ สามารถสร้างและแก้ไขได้
- 3.4.4. การอ้างอิงและจัดกลุ่ม: ช้สำหรับอ้างอิง ใน Shop Drawings, มีการจัดหมวดหมู่ของ Shop Drawings
### **3.5. การจัดการเอกสารขออนุมัติ (Request for Approval & Workflow)**
### **3.5. การจัดการ Workflow (Unified Workflow)**
- 3.5.1. วัตถุประสงค์: เอกสารขออนุมัติ (Request for Approval) ใช้ในการส่งเอกสารเพิอขออนุมัติ
- 3.5.2. ประเภทเอกสาร: Request for Approval (RFA) เป็นชนิดหนึ่งของ Correspondence ที่มีลักษณะเฉพาะที่ต้องได้รับการอนุมัติ มีประเภทดังนี้:
- Request for Drawing Approval (RFA_DWG)
- Request for Document Approval (RFA_DOC)
- Request for Method statement Approval (RFA_MES)
- Request for Material Approval (RFA_MAT)
- 3.5.2. การสร้างเอกสาร: ผู้ใช้ที่มีสิทธิ์ สามารถสร้างและแก้ไขได้
- 3.5.3. การ้างอิงและจัดกลุ่ม: การจัดการ Drawing (RFA_DWG):
- เอกสาร RFA_DWG จะประกอบไปด้วย Shop Drawing (shop_drawings) หลายแผ่น ซึ่งแต่ละแผ่นมี Revision ของตัวเอง
- Shop Drawing แต่ละ Revision สามารถอ้างอิงถึง Contract Drawing (Ccontract_drawings) หลายแผ่น หรือไม่อ้างถึงก็ได้
- ระบบต้องมีส่วนสำหรับจัดการข้อมูล Master Data ของทั้ง Shop Drawing และ Contract Drawing แยกจากกัน
- 3.5.4. Workflow การอนุมัติ: ต้องรองรับกระบวนการอนุมัติที่ซับซ้อนและเป็นลำดับ เช่น
- ส่งจาก Originator -> Organization 1 -> Organization 2 -> Organization 3 แล้วส่งผลกลับตามลำดับเดิม (โดยถ้า องกรณ์ใดใน Workflow ให้ส่งกลับ ก็สามารถส่งผลกลับตามลำดับเดิมโดยไม่ต้องรอให้ถึง องกรณืในลำดับถัดไป)
- 3.5.5. การจัดการ: มีการจัดการอย่างน้อยดังนี้
- 3.5.1 Workflow Definition:
- Admin ต้องสามารถสร้าง/แก้ไข Workflow Rule ได้ผ่านหน้าจอ UI (DSL Editor) ร
- องรับการกำหนด State, Transition, Required Role, Condition (JS Expression)
- 3.5.2 Workflow Execution:
- ระบบต้องรองรับการสร้าง Instance ของ Workflow ผูกกับเอกสาร (Polymorphic)
- รองรับการเปลี่ยนสถานะ (Action) เช่น Approve, Reject, Comment, Return
- Auto-Action: รองรับการเปลี่ยนสถานะอัตโนมัติเมื่อครบเงื่อนไข (เช่น Review ครบทุกคน)
- 3.5.3 Flexibility:
- รองรับ Parallel Review (ส่งให้หลายคนตรวจพร้อมกัน)
- รองรับ Conditional Flow (เช่น ถ้ายอดเงิน > X ให้เพิ่มผู้อนุมัติ)
- 3.5.4 Workflow การอนุมัติ:
- ต้องรองรับกระบวนการอนุมัติที่ซับซ้อนและเป็นลำดับ เช่น ส่งจาก Originator -> Organization 1 -> Organization 2 -> Organization 3 แล้วส่งผลกลับตามลำดับเดิม (โดยถ้า องกรณ์ใดใน Workflow ให้ส่งกลับ ก็สามารถส่งผลกลับตามลำดับเดิมโดยไม่ต้องรอให้ถึง องกรณืในลำดับถัดไป)
- 3.5.5 การจัดการ:
- สามารถกำหนดวันแล้วเสร็จ (Deadline) สำหรับผู้รับผิดชอบของ องกรณ์ ที่อยู่ใน Workflow ได้
- มีระบบแจ้งเตือน ให้ผู้รับผิดชอบของ องกรณ์ ที่อยู่ใน Workflow ทราบ เมื่อมี RFA ใหม่ หรือมีการเปลี่ยนสถานะ
@@ -235,11 +248,13 @@
### **3.9. การจัดเก็บไฟล์ (File Handling - ปรับปรุงใหญ่)**
- **3.9.1 Two-Phase Storage Strategy:**
1. **Phase 1 (Upload):** ไฟล์ถูกอัปโหลดเข้าโฟลเดอร์ `temp/` และได้รับ `temp_id`
2. **Phase 2 (Commit):** เมื่อ User กด Submit ฟอร์มสำเร็จ ระบบจะย้ายไฟล์จาก `temp/` ไปยัง `permanent/{YYYY}/{MM}/` และบันทึกลง Database ภายใน Transaction เดียวกัน
3. **Cleanup:** มี Cron Job ลบไฟล์ใน `temp/` ที่ค้างเกิน 24 ชม. (Orphan Files)
- **3.9.2 Security:**
- Virus Scan (ClamAV) ก่อนย้ายเข้า Permanent
- Whitelist File Types: PDF, DWG, DOCX, XLSX, ZIP
- Max Size: 50MB
@@ -269,12 +284,14 @@
### **3.11 การจัดการ JSON Details (JSON & Performance - ปรับปรุง)**
- **3.11.1 วัตถุประสงค์**
- จัดเก็บข้อมูลแบบไดนามิกที่เฉพาะเจาะจงกับแต่ละประเภทของเอกสาร
- รองรับการขยายตัวของระบบโดยไม่ต้องเปลี่ยนแปลง database schema
- จัดการ metadata และข้อมูลประกอบสำหรับ correspondence, routing, และ workflows
- **3.11.2 โครงสร้าง JSON Schema**
ระบบต้องมี predefined JSON schemas สำหรับประเภทเอกสารต่างๆ:
- **3.11.2.1 Correspondence Types**
- **GENERIC**: ข้อมูลพื้นฐานสำหรับเอกสารทั่วไป
- **RFI**: รายละเอียดคำถามและข้อมูลทางเทคนิค
@@ -282,10 +299,10 @@
- **TRANSMITTAL**: รายการเอกสารที่ส่งต่อ
- **LETTER**: ข้อมูลจดหมายทางการ
- **EMAIL**: ข้อมูลอีเมล
- **3.11.2.2 Routing Types**
- **ROUTING_TEMPLATE**: กฎและเงื่อนไขการส่งต่อ
- **ROUTING_INSTANCE**: สถานะและประวัติการส่งต่อ
- **ROUTING_ACTION**: การดำเนินการในแต่ละขั้นตอน
- **3.11.2.2 Rworkflow Types**
- **workflow_definitions**: กฎและเงื่อนไขการส่งต่อ
- **workflow_histories**: สถานะและประวัติการส่งต่อ
- **workflow_instances**: การดำเนินการในแต่ละขั้นตอน
- **3.11.2.3 Audit Types**
- **AUDIT_LOG**: ข้อมูลการตรวจสอบ
- **SECURITY_SCAN**: ผลการตรวจสอบความปลอดภัย
@@ -293,12 +310,14 @@
- **3.11.3 Virtual Columns (ใหม่):** สำหรับ Field ใน JSON ที่ต้องใช้ในการค้นหา (Search) หรือจัดเรียง (Sort) บ่อยๆ **ต้องสร้าง Generated Column (Virtual Column)** ใน Database และทำ Index ไว้ เพื่อประสิทธิภาพสูงสุด
- **3.11.4 Validation Rules**
- ต้องมี JSON schema validation สำหรับแต่ละประเภท
- ต้องรองรับ versioning ของ schema
- ต้องมี default values สำหรับ field ที่ไม่บังคับ
- ต้องตรวจสอบ data types และ format ให้ถูกต้อง
- **3.11.5 Performance Requirements**
- JSON field ต้องมีขนาดไม่เกิน 50KB
- ต้องรองรับ indexing สำหรับ field ที่ใช้ค้นหาบ่อย
- ต้องมี compression สำหรับ JSON ขนาดใหญ่
@@ -309,6 +328,7 @@
- ต้อง encrypt sensitive data ใน JSON fields
### **3.12 ข้อกำหนดพิเศษ**
- **ผู้ใช้งานที่มีสิทธิ์ระดับสูง (Global) หรือผู้ได้รับอนุญาตเป็นกรณีพิเศษ**
- สามารถเลือก **สร้างในนามองค์กร (Create on behalf of)** ได้ เพื่อให้สามารถออกเลขที่เอกสาร (Running Number) ขององค์กรอื่นได้โดยไม่ต้องล็อกอินใหม่
- สามารถทำงานแทนผู้ใช้งานอื่นได้ Routing & Workflow ของ Correspondence, RFA, Circulation Sheet
@@ -336,15 +356,15 @@
### **4.3. การกำหนดบทบาท (Roles) และขอบเขต (Scope)**
| บทบาท (Role) | ขอบเขต (Scope) | คำอธิบาย | สิทธิ์หลัก (Key Permissions) |
| :------------------- | :------------- | :------------------ | :----------------------------------------------------------------------------- |
| **Superadmin** | Global | ผู้ดูแลระบบสูงสุด | ทำทุกอย่างในระบบ, จัดการองค์กร, จัดการข้อมูลหลักระดับ Global |
| **Org Admin** | Organization | ผู้ดูแลองค์กร | จัดการผู้ใช้ในองค์กร, จัดการบทบาท/สิทธิ์ภายในองค์กร, ดูรายงานขององค์กร |
| **Document Control** | Organization | ควบคุมเอกสารขององค์กร | เพิ่ม/แก้ไข/ลบเอกสาร, กำหนดสิทธิ์เอกสารภายในองค์กร |
| **Editor** | Organization | ผู้แก้ไขเอกสารขององค์กร | เพิ่ม/แก้ไขเอกสารที่ได้รับมอบหมาย |
| **Viewer** | Organization | ผู้ดูเอกสารขององค์กร | ดูเอกสารที่มีสิทธิ์เข้าถึง |
| **Project Manager** | Project | ผู้จัดการโครงการ | จัดการสมาชิกในโครงการ (เพิ่ม/ลบ/มอบบทบาท), สร้าง/จัดการสัญญาในโครงการ, ดูรายงานโครงการ |
| **Contract Admin** | Contract | ผู้ดูแลสัญญา | จัดการสมาชิกในสัญญา, สร้าง/จัดการข้อมูลหลักเฉพาะสัญญา (ถ้ามี), อนุมัติเอกสารในสัญญา |
| บทบาท (Role) | ขอบเขต (Scope) | คำอธิบาย | สิทธิ์หลัก (Key Permissions) |
| :------------------- | :------------- | :---------------------- | :------------------------------------------------------------------------------------- |
| **Superadmin** | Global | ผู้ดูแลระบบสูงสุด | ทำทุกอย่างในระบบ, จัดการองค์กร, จัดการข้อมูลหลักระดับ Global |
| **Org Admin** | Organization | ผู้ดูแลองค์กร | จัดการผู้ใช้ในองค์กร, จัดการบทบาท/สิทธิ์ภายในองค์กร, ดูรายงานขององค์กร |
| **Document Control** | Organization | ควบคุมเอกสารขององค์กร | เพิ่ม/แก้ไข/ลบเอกสาร, กำหนดสิทธิ์เอกสารภายในองค์กร |
| **Editor** | Organization | ผู้แก้ไขเอกสารขององค์กร | เพิ่ม/แก้ไขเอกสารที่ได้รับมอบหมาย |
| **Viewer** | Organization | ผู้ดูเอกสารขององค์กร | ดูเอกสารที่มีสิทธิ์เข้าถึง |
| **Project Manager** | Project | ผู้จัดการโครงการ | จัดการสมาชิกในโครงการ (เพิ่ม/ลบ/มอบบทบาท), สร้าง/จัดการสัญญาในโครงการ, ดูรายงานโครงการ |
| **Contract Admin** | Contract | ผู้ดูแลสัญญา | จัดการสมาชิกในสัญญา, สร้าง/จัดการข้อมูลหลักเฉพาะสัญญา (ถ้ามี), อนุมัติเอกสารในสัญญา |
### **4.4. Token Management (ปรับปรุง)**
@@ -372,14 +392,14 @@
### **4.6. การจัดการข้อมูลหลัก (Master Data Management) ที่แบ่งตามระดับ**
| ข้อมูลหลัก | ผู้มีสิทธิ์จัดการ | ระดับ |
| :---------------------------------- | :------------------------------ | :------------------------------ |
| ประเภทเอกสาร (Correspondence, RFA) | **Superadmin** | Global |
| สถานะเอกสาร (Draft, Approved, etc.) | **Superadmin** | Global |
| หมวดหมู่แบบ (Shop Drawing) | **Project Manager** | Project (สร้างใหม่ได้ภายในโครงการ) |
| Tags | **Org Admin / Project Manager** | Organization / Project |
| บทบาทและสิทธิ์ (Custom Roles) | **Superadmin / Org Admin** | Global / Organization |
| Document Numbering Formats | **Superadmin / Admin** | Global / Organization |
| ข้อมูลหลัก | ผู้มีสิทธิ์จัดการ | ระดับ |
| :---------------------------------- | :------------------------------ | :--------------------------------- |
| ประเภทเอกสาร (Correspondence, RFA) | **Superadmin** | Global |
| สถานะเอกสาร (Draft, Approved, etc.) | **Superadmin** | Global |
| หมวดหมู่แบบ (Shop Drawing) | **Project Manager** | Project (สร้างใหม่ได้ภายในโครงการ) |
| Tags | **Org Admin / Project Manager** | Organization / Project |
| บทบาทและสิทธิ์ (Custom Roles) | **Superadmin / Org Admin** | Global / Organization |
| Document Numbering Formats | **Superadmin / Admin** | Global / Organization |
## **👥 5. ข้อกำหนดด้านผู้ใช้งาน (User Interface & Experience)**
@@ -434,6 +454,7 @@
### **6.1. การบันทึกการกระทำ (Audit Log):** ทุกการกระทำที่สำคัญของผู้ใช้ (สร้าง, แก้ไข, ลบ, ส่ง) จะถูกบันทึกไว้ใน audit_logs เพื่อการตรวจสอบย้อนหลัง
- **6.1.1 ขอบเขตการบันทึก Audit Log:**
- ทุกการสร้าง/แก้ไข/ลบ ข้อมูลสำคัญ (correspondences, RFAs, drawings, users, permissions)
- ทุกการเข้าถึงข้อมูล sensitive (user data, financial information)
- ทุกการเปลี่ยนสถานะ workflow (status transitions)
@@ -465,6 +486,7 @@
### **6.5. ประสิทธิภาพ (Performance):** มีการใช้ Caching กับข้อมูลที่เรียกใช้บ่อย และใช้ Pagination ในตารางข้อมูลเพื่อจัดการข้อมูลจำนวนมาก
- **6.5.1 ตัวชี้วัดประสิทธิภาพ:**
- **API Response Time:** < 200ms (90th percentile) สำหรับ operation ทั่วไป
- **Search Query Performance:** < 500ms สำหรับการค้นหาขั้นสูง
- **File Upload Performance:** < 30 seconds สำหรับไฟล์ขนาด 50MB
@@ -492,6 +514,7 @@
- การจัดการ Secret (เช่น รหัสผ่าน DB, JWT Secret) จะต้องทำผ่าน Environment Variable ของ Docker เพื่อความปลอดภัยสูงสุด
- **6.6.1 Rate Limiting Strategy:**
- **Anonymous Endpoints:** 100 requests/hour ต่อ IP address
- **Authenticated Endpoints:**
- Viewer: 500 requests/hour
@@ -505,12 +528,14 @@
- ต้องบันทึก log เมื่อมีการ trigger rate limiting
- **6.6.2 Error Handling และ Resilience:**
- ต้องมี circuit breaker pattern สำหรับ external service calls
- ต้องมี retry mechanism ด้วย exponential backoff
- ต้องมี graceful degradation เมื่อบริการภายนอกล้มเหลว
- Error messages ต้องไม่เปิดเผยข้อมูล sensitive
- **6.6.3 Input Validation:**
- ต้องมี input validation ทั้งฝั่ง client และ server (defense in depth)
- ต้องป้องกัน OWASP Top 10 vulnerabilities:
- SQL Injection (ใช้ parameterized queries ผ่าน ORM)
@@ -564,6 +589,7 @@
### **6.8. กลยุทธ์การแจ้งเตือน (Notification Strategy - ปรับปรุง):**
- **6.8.1 ระบบจะส่งการแจ้งเตือน (ผ่าน Email หรือ Line [cite: 2.7]) เมื่อมีการกระทำที่สำคัญ** ดังนี้:
1. เมื่อมีเอกสารใหม่ (Correspondence, RFA) ถูกส่งมาถึงองค์กรณ์ของเรา
2. เมื่อมีใบเวียน (Circulation) ใหม่ มอบหมายงานมาที่เรา
3. (ทางเลือก) เมื่อเอกสารที่เราส่งไป ถูกดำเนินการ (เช่น อนุมัติ/ปฏิเสธ)

View File

@@ -0,0 +1,397 @@
# “Phase 6A + Technical Design Document : Workflow DSL (Mini-Language)”**
ออกแบบสำหรับระบบ Workflow Engine กลางของโครงการ
**ไม่มีโค้ดผูกกับ Framework** เพื่อให้สามารถนำไป Implement ใน NestJS หรือ Microservice ใด ๆ ได้
---
## 📌 **Phase 6A Workflow DSL Implementation Plan**
### 🎯 เป้าหมายของ Phase 6A
ใน Phase นี้ จะเริ่มสร้าง “Workflow DSL (Domain-Specific Language)” สำหรับนิยามกฎการเดินงาน (Workflow Transition Rules) ให้สามารถ:
* แยก **Business Workflow Logic** ออกจาก Source Code
* แก้ไขกฎ Workflow ได้โดย **ไม่ต้องแก้โค้ดและไม่ต้อง Deploy ใหม่**
* รองรับ Document หลายประเภท เช่น
* Correspondence
* RFA
* Internal Circulation
* Document Transmittal
* รองรับ Multi-step routing, skip, reject, rollback, parallel assignments
* สามารถนำไปใช้งานทั้งใน
* Backend (NestJS)
* Frontend (UI Driven)
* External Microservices
---
### 📅 ระยะเวลา
**1 สัปดาห์ (หลัง Phase 6.5)**
---
### 🧩 Output ของ Phase 6A
* DSL Specification (Grammar)
* JSON Schema for Workflow Definition
* Workflow Rule Interpreter (Parser + Executor)
* Validation Engine (Compile-time and Runtime)
* Storage (DB Table / Registry)
* Execution API:
| Action | Description |
| -------------------------------- | ------------------------------- |
| compile() | ตรวจ DSL → สร้าง Execution Tree |
| evaluate(state, action, context) | ประมวลผลและส่งสถานะใหม่ |
| preview(state) | คำนวณ Next Possible Transitions |
| validate() | ตรวจว่า DSL ถูกต้อง |
---
## 📘 **Technical Specification Workflow DSL**
---
### 1⃣ Requirements
#### Functional Requirements
* นิยาม Workflow เป็นภาษาคล้าย State Machine
* แต่ละเอกสารมี **State, Actions, Entry/Exit Events**
* สามารถมี:
* Required approvals
* Conditional transition
* Auto-transition
* Parallel approval
* Return/rollback
####
* Running time: < 20ms ต่อคำสั่ง
* Hot reload ไม่ต้อง Compile ใหม่ทั้ง Backend
* DSL ต้อง Debug ได้ง่าย
* ต้อง Versioned
* ต้องรองรับ Audit 100%
---
### 2⃣ DSL Format (Human Friendly)
```yaml
workflow: RFA
version: 1.0
states:
- name: DRAFT
initial: true
on:
SUBMIT:
to: IN_REVIEW
require:
- role: ENGINEER
events:
- notify: reviewer
- name: IN_REVIEW
on:
APPROVE:
to: APPROVED
REJECT:
to: DRAFT
events:
- notify: creator
- name: APPROVED
terminal: true
```
---
### 3⃣ Compiled Execution Model (Normalized JSON)
```json
{
"workflow": "RFA",
"version": "1.0",
"states": {
"DRAFT": {
"initial": true,
"transitions": {
"SUBMIT": {
"to": "IN_REVIEW",
"requirements": [
{ "role": "ENGINEER" }
],
"events": [
{ "type": "notify", "target": "reviewer" }
]
}
}
},
"IN_REVIEW": {
"transitions": {
"APPROVE": { "to": "APPROVED" },
"REJECT": {
"to": "DRAFT",
"events": [
{ "type": "notify", "target": "creator" }
]
}
}
},
"APPROVED": {
"terminal": true
}
}
}
```
Frontend และ Backend สามารถแชร์ JSON นี้ร่วมกันได้
---
### 4⃣ DSL Grammar Definition (EBNF)
```ebnf
workflow = "workflow" ":" identifier ;
version = "version" ":" number ;
states = "states:" state_list ;
state_list = { state } ;
state = "- name:" identifier
[ "initial:" boolean ]
[ "terminal:" boolean ]
[ "on:" transition_list ] ;
transition_list = { transition } ;
transition = action ":"
indent "to:" identifier
[ indent "require:" requirements ]
[ indent "events:" event_list ] ;
requirements = "- role:" identifier | "- user:" identifier ;
event_list = { event } ;
event = "- notify:" identifier ;
```
---
### 5⃣ Validation Rules (Compile-Time)
#### 5.1 State Rules
* ต้องมีอย่างน้อย 1 state ที่ `initial: true`
* หาก `terminal: true` → ต้องไม่มี transition ต่อไป
#### 5.2 Transition Rules
ตรวจสอบว่า:
* `to` ชี้ไปยัง state ที่มีอยู่
* `require.role` ต้องเป็น role ที่ระบบรู้จัก
* Action name ต้องเป็น **UPPER_CASE**
#### 5.3 Version Safety
* ทุกชุด Workflow DSL ต้องขึ้นกับ version
* แก้ไขต้องสร้าง version ใหม่
* ไม่ overwrite version เก่า
* “Document ที่กำลังอยู่ใน step เดิมยังต้องใช้กฎเดิมได้”
---
### 6⃣ Runtime Validation Rules
เมื่อ execute(action):
```
input: current_state, action, context
1) ตรวจว่า state มี transition "action"
2) ตรวจว่าผู้ใช้มีสิทธิ์ตาม require[]
3) Compute next state
4) Execute events[]
5) Return next_state
```
---
### 7⃣ Context Model
```ts
interface WorkflowContext {
userId: string;
roles: string[];
documentId: string;
now: Date;
payload?: any;
}
```
---
### 8⃣ Execution API (Abstract)
```ts
class WorkflowEngine {
load(dsl: string | object): CompiledWorkflow
compile(dsl: string | object): CompiledWorkflow
evaluate(state: string, action: string, context: WorkflowContext): EvalResult
getAvailableActions(state: string, context: WorkflowContext): string[]
}
```
---
### 9⃣ Interpreter Execution Flow
```mermaid
flowchart TD
A[Receive Action] --> B[Load Compiled Workflow]
B --> C[Check allowed actions]
C -->|Invalid| E[Return Error]
C --> D[Evaluate Requirements]
D --> F[Transition to Next State]
F --> G[Run Events]
G --> H[Return Success]
```
---
### 🔟 Events System
รองรับ event หลายประเภท:
| event.type | ตัวอย่าง |
| ----------- | ------------------------- |
| notify | ส่ง Email/Line |
| assign | เปลี่ยนผู้รับผิดชอบ |
| webhook | ยิง Webhook ไปยังระบบอื่น |
| auto_action | ทำ action ซ้ำโดยอัตโนมัติ |
---
### 11⃣ Database Schema
#### Table: `workflow_definition`
| Field | Type | Description |
| ------------- | ----------- | --------------------- |
| id | UUID | PK |
| workflow_code | varchar(50) | เช่น `RFA`, `CORR` |
| version | int | Version number |
| dsl | JSON | YAML/JSON DSL เก็บดิบ |
| compiled | JSON | JSON ที่ Compile แล้ว |
| created_at | timestamp | |
| is_active | boolean | ใช้อยู่หรือไม่ |
#### Table: `workflow_history`
เก็บ audit แบบ immutable append-only
| Field | Description |
| ----------- | --------------- |
| workflow | Document Type |
| document_id | เอกสารไหน |
| from_state | เดิม |
| to_state | ใหม่ |
| action | คำสั่ง |
| user | ใครเป็นคนทำ |
| timestamp | เวลา |
| metadata | เหตุการณ์อื่น ๆ |
---
### 12⃣ Error Codes
| Code | Meaning |
| ----------------------- | ---------------------- |
| WF_NO_TRANSITION | Action นี้ไม่ถูกต้อง |
| WF_RESTRICTED | User ไม่มีสิทธิ์ |
| WF_MISSING_REQUIREMENTS | ไม่ผ่านเงื่อนไข |
| WF_STATE_NOT_FOUND | ไม่มี state ที่อ้างอิง |
| WF_SYNTAX_ERROR | DSL ผิดรูปแบบ |
---
### 13⃣ Testing Strategy
#### Unit Tests
* Parse DSL → JSON
* Invalid syntax → throw error
* Invalid transitions → throw error
#### Integration Tests
* Evaluate() ผ่าน 20+ cases
* RFA ย้อนกลับ
* Approve chain
* Parallel review
#### Load Tests
* 1,000 documents running workflow
* Evaluate < 20ms ต่อ action
---
### 14⃣ Deployment Strategy
#### Hot Reload Options
* DSL stored in DB
* Cache in Redis
* Touched timestamp triggers:
```
invalidate cache → recompile
```
#### No downtime required
---
### 15⃣ Microservice-Ready
DSL Engine แยกเป็น:
* `workflow-engine-core` → Pure JS library
* `workflow-service` → NestJS module
* API public:
```
POST /workflow/evaluate
GET /workflow/preview
POST /workflow/compile
```
ภายหลังสามารถนำไปวางบน:
* Kubernetes
* Worker Node
* API Gateway
---
## 🎉 Summary
### สิ่งที่ Phase 6A เพิ่มเข้าในระบบ
✔ Workflow DSL ที่แก้ไขได้โดยไม่ต้อง Deploy
✔ Parser + Validator + Runtime Interpreter
✔ Database storage + Versioning
✔ Execution API สำหรับ Backend และ Frontend
✔ รองรับ Business Workflow ซับซ้อนทั้งหมด
✔ Ready สำหรับ Microservice model ในอนาคต

View File

@@ -0,0 +1,709 @@
# 🎯 **Admin Panel Comprehensive Analysis สำหรับ LCBP3-DMS v1.4.4**
จากการวิเคราะห์ Requirements และ Technical Design ทั้งหมด ขอนำเสนอ Admin Panel ที่สมบูรณ์สำหรับระบบ LCBP3-DMS
## 📊 **ภาพรวม Admin Panel Structure**
### **Role-Based Access Levels**
- **Superadmin:** Full system access
- **Org Admin:** Organization-level management
- **Project Manager:** Project-level administration
- **Contract Admin:** Contract-specific management
---
## 🛠️ **1. System Administration**
### **1.1 Global System Configuration**
```typescript
interface SystemConfig {
maintenance_mode: boolean;
session_timeout: number;
password_policy: {
min_length: number;
require_uppercase: boolean;
require_numbers: boolean;
require_special_chars: boolean;
expiry_days: number;
};
file_upload: {
max_size_mb: number;
allowed_types: string[];
virus_scan_enabled: boolean;
};
notification: {
email_enabled: boolean;
line_enabled: boolean;
digest_frequency: number; // minutes
};
}
```
**Features:**
- [ ] Toggle Maintenance Mode
- [ ] Configure Security Policies
- [ ] Manage File Upload Settings
- [ ] Configure Notification Channels
- [ ] System Health Monitoring Dashboard
### **1.2 Audit & Monitoring Center**
```typescript
interface AuditDashboard {
security_metrics: {
failed_logins: number;
file_scans: number;
virus_detections: number;
permission_changes: number;
};
user_activity: AuditLog[];
system_performance: PerformanceMetrics;
recent_errors: SystemError[];
}
```
**Features:**
- [ ] Real-time Activity Monitoring
- [ ] Security Incident Reporting
- [ ] Performance Metrics Dashboard
- [ ] Error Log Viewer
- [ ] Export Audit Reports
---
## 👥 **2. User & Organization Management**
### **2.1 User Management**
```typescript
interface UserManagement {
user_list: User[];
bulk_operations: {
import_users: FileUpload;
export_users: CSVExport;
bulk_assign_roles: RoleAssignment[];
};
user_lifecycle: {
onboarding_workflow: WorkflowConfig;
offboarding_checklist: ChecklistItem[];
};
}
```
**Features:**
- [ ] User CRUD Operations
- [ ] Bulk User Import/Export
- [ ] User Activity Tracking
- [ ] Password Reset Management
- [ ] User Session Management
### **2.2 Organization Hierarchy**
```typescript
interface OrganizationManagement {
organization_tree: Organization[];
department_structure: Department[];
contact_persons: Contact[];
organization_settings: {
document_retention_policy: RetentionPolicy;
notification_preferences: NotificationConfig;
};
}
```
**Features:**
- [ ] Organization CRUD
- [ ] Department Structure Management
- [ ] Contact Person Assignment
- [ ] Organization-specific Policies
### **2.3 Advanced RBAC Management**
```typescript
interface RBACManagement {
role_definitions: Role[];
permission_matrix: Permission[];
assignment_rules: {
automatic_assignments: AutoAssignmentRule[];
conditional_access: ConditionalRule[];
};
permission_audit: {
permission_usage: UsageStats[];
conflict_detection: Conflict[];
};
}
```
**Features:**
- [ ] Custom Role Creation
- [ ] Granular Permission Management
- [ ] Automatic Role Assignment Rules
- [ ] Permission Conflict Detection
- [ ] Role Usage Analytics
---
## 📁 **3. Project & Contract Administration**
### **3.1 Project Portfolio Management**
```typescript
interface ProjectManagement {
project_dashboard: ProjectOverview[];
project_phases: Phase[];
milestone_tracking: Milestone[];
resource_allocation: Resource[];
project_analytics: ProjectMetrics;
}
```
**Features:**
- [ ] Project Lifecycle Management
- [ ] Phase and Milestone Tracking
- [ ] Resource Allocation
- [ ] Project Performance Analytics
- [ ] Project Documentation Repository
### **3.2 Contract Administration**
```typescript
interface ContractManagement {
contract_register: Contract[];
party_management: ContractParty[];
amendment_tracking: Amendment[];
compliance_monitoring: ComplianceCheck[];
financial_tracking: FinancialMetrics;
}
```
**Features:**
- [ ] Contract CRUD Operations
- [ ] Contract Party Management
- [ ] Amendment History
- [ ] Compliance Monitoring
- [ ] Financial Tracking
### **3.3 Project-Organization Mapping**
```typescript
interface ProjectOrgMapping {
project_assignments: ProjectAssignment[];
access_control: AccessRule[];
collaboration_settings: CollaborationConfig;
}
```
**Features:**
- [ ] Organization Assignment to Projects
- [ ] Cross-Organization Collaboration Settings
- [ ] Access Control Configuration
---
## 📋 **4. Master Data Management**
### **4.1 Document Type Ecosystem**
```typescript
interface DocumentTypeManagement {
correspondence_types: DocumentType[];
rfa_types: RFAType[];
circulation_types: CirculationType[];
drawing_categories: DrawingCategory[];
type_hierarchy: TypeHierarchy;
}
```
**Features:**
- [ ] Document Type CRUD
- [ ] Type-Specific Workflow Configuration
- [ ] Category Hierarchy Management
- [ ] Template Association
### **4.2 Discipline & Classification System**
```typescript
interface DisciplineManagement {
disciplines: Discipline[];
sub_types: SubType[];
classification_rules: ClassificationRule[];
mapping_configurations: MappingConfig[];
}
```
**Features:**
- [ ] Discipline CRUD (ตาม Requirement 6B)
- [ ] Sub-Type Management with Number Mapping
- [ ] Automatic Classification Rules
- [ ] Cross-Reference Mapping
### **4.3 Status & Code Management**
```typescript
interface StatusManagement {
status_codes: StatusCode[];
transition_rules: TransitionRule[];
status_workflows: StatusWorkflow[];
automated_status_changes: AutoStatusChange[];
}
```
**Features:**
- [ ] Status Code Configuration
- [ ] State Transition Rules
- [ ] Automated Status Updates
- [ ] Status Change Analytics
---
## 🔢 **5. Document Numbering System Administration**
### **5.1 Numbering Format Configuration**
```typescript
interface NumberingFormatManagement {
format_templates: NumberingTemplate[];
token_library: TokenDefinition[];
format_preview: FormatPreview;
validation_rules: ValidationRule[];
}
```
**Features:**
- [ ] Template Editor with Live Preview
- [ ] Custom Token Definition
- [ ] Format Validation
- [ ] Bulk Template Application
### **5.2 Counter Management**
```typescript
interface CounterManagement {
counter_groups: CounterGroup[];
reset_schedules: ResetSchedule[];
counter_audit: CounterHistory[];
conflict_resolution: ConflictResolutionRule[];
}
```
**Features:**
- [ ] Counter Group Configuration
- [ ] Scheduled Reset Management
- [ ] Counter Audit Trail
- [ ] Conflict Resolution Rules
### **5.3 Numbering Rule Engine**
```typescript
interface NumberingRuleEngine {
conditional_rules: ConditionalRule[];
context_resolvers: ContextResolver[];
fallback_strategies: FallbackStrategy[];
performance_monitoring: PerformanceMetrics;
}
```
**Features:**
- [ ] Conditional Numbering Rules
- [ ] Context Variable Management
- [ ] Fallback Strategy Configuration
- [ ] Performance Optimization
---
## ⚙️ **6. Workflow & Routing Administration**
### **6.1 Workflow DSL Management**
```typescript
interface WorkflowDSLManagement {
workflow_library: WorkflowDefinition[];
dsl_editor: DSLEditor;
version_control: VersionHistory[];
deployment_pipeline: DeploymentConfig[];
}
```
**Features:**
- [ ] Visual Workflow Designer
- [ ] DSL Code Editor with Syntax Highlighting
- [ ] Version Control and Rollback
- [ ] Testing and Deployment Pipeline
### **6.2 Routing Template Administration**
```typescript
interface RoutingTemplateManagement {
template_library: RoutingTemplate[];
step_configurations: StepConfig[];
approval_chains: ApprovalChain[];
escalation_rules: EscalationRule[];
}
```
**Features:**
- [ ] Template CRUD Operations
- [ ] Drag-and-Drop Step Configuration
- [ ] Approval Chain Management
- [ ] Escalation Rule Setup
### **6.3 Workflow Analytics**
```typescript
interface WorkflowAnalytics {
performance_metrics: WorkflowMetrics[];
bottleneck_analysis: Bottleneck[];
compliance_reporting: ComplianceReport[];
optimization_recommendations: Recommendation[];
}
```
**Features:**
- [ ] Workflow Performance Dashboard
- [ ] Bottleneck Identification
- [ ] Compliance Reporting
- [ ] Optimization Suggestions
---
## 📊 **7. Reporting & Analytics Center**
### **7.1 Custom Report Builder**
```typescript
interface ReportBuilder {
data_sources: DataSource[];
visualization_types: VisualizationType[];
report_templates: ReportTemplate[];
scheduling: ScheduleConfig[];
}
```
**Features:**
- [ ] Drag-and-Drop Report Designer
- [ ] Multiple Visualization Options
- [ ] Template Library
- [ ] Automated Report Scheduling
### **7.2 Business Intelligence**
```typescript
interface BusinessIntelligence {
kpi_dashboard: KPIMetric[];
trend_analysis: TrendData[];
predictive_analytics: PredictiveModel[];
data_export: ExportConfig[];
}
```
**Features:**
- [ ] Real-time KPI Dashboard
- [ ] Trend Analysis Tools
- [ ] Predictive Analytics
- [ ] Data Export and Integration
### **7.3 Compliance Reporting**
```typescript
interface ComplianceReporting {
regulatory_reports: RegulatoryReport[];
audit_trails: AuditTrail[];
compliance_dashboard: ComplianceMetric[];
certification_tracking: Certification[];
}
```
**Features:**
- [ ] Pre-built Regulatory Reports
- [ ] Comprehensive Audit Trails
- [ ] Compliance Dashboard
- [ ] Certification Management
---
## 🔐 **8. Security & Compliance Center**
### **8.1 Security Policy Management**
```typescript
interface SecurityPolicyManagement {
access_policies: AccessPolicy[];
data_classification: DataClassification[];
encryption_settings: EncryptionConfig[];
security_incidents: SecurityIncident[];
}
```
**Features:**
- [ ] Access Policy Configuration
- [ ] Data Classification Scheme
- [ ] Encryption Management
- [ ] Security Incident Tracking
### **8.2 Compliance Framework**
```typescript
interface ComplianceFramework {
compliance_rules: ComplianceRule[];
control_testing: ControlTest[];
evidence_management: Evidence[];
compliance_calendar: ComplianceEvent[];
}
```
**Features:**
- [ ] Compliance Rule Engine
- [ ] Control Testing Framework
- [ ] Evidence Collection
- [ ] Compliance Calendar
### **8.3 Risk Management**
```typescript
interface RiskManagement {
risk_register: Risk[];
risk_assessments: RiskAssessment[];
mitigation_plans: MitigationPlan[];
risk_dashboard: RiskMetrics;
}
```
**Features:**
- [ ] Risk Identification and Registration
- [ ] Risk Assessment Tools
- [ ] Mitigation Planning
- [ ] Risk Monitoring Dashboard
---
## 📧 **9. Notification & Communication Management**
### **9.1 Notification Template System**
```typescript
interface NotificationTemplateManagement {
email_templates: EmailTemplate[];
line_templates: LineTemplate[];
system_notifications: SystemTemplate[];
template_variables: TemplateVariable[];
}
```
**Features:**
- [ ] Multi-channel Template Management
- [ ] Variable Substitution System
- [ ] Template Testing and Preview
- [ ] Bulk Template Operations
### **9.2 Subscription Management**
```typescript
interface SubscriptionManagement {
user_preferences: UserPreference[];
group_subscriptions: GroupSubscription[];
escalation_policies: EscalationPolicy[];
delivery_reports: DeliveryReport[];
}
```
**Features:**
- [ ] User Preference Management
- [ ] Group Subscription Configuration
- [ ] Escalation Policy Setup
- [ ] Delivery Monitoring
### **9.3 Digest Configuration**
```typescript
interface DigestConfiguration {
digest_rules: DigestRule[];
grouping_criteria: GroupingCriteria[];
timing_configurations: TimingConfig[];
content_prioritization: PriorityRule[];
}
```
**Features:**
- [ ] Digest Rule Engine
- [ ] Content Grouping Configuration
- [ ] Timing and Frequency Settings
- [ ] Content Prioritization Rules
---
## 🗃️ **10. Data Management & Maintenance**
### **10.1 Data Lifecycle Management**
```typescript
interface DataLifecycleManagement {
retention_policies: RetentionPolicy[];
archival_rules: ArchivalRule[];
purge_schedules: PurgeSchedule[];
data_governance: GovernancePolicy[];
}
```
**Features:**
- [ ] Retention Policy Configuration
- [ ] Automated Archival Rules
- [ ] Scheduled Data Purge
- [ ] Data Governance Framework
### **10.2 Backup & Recovery**
```typescript
interface BackupRecoveryManagement {
backup_configurations: BackupConfig[];
recovery_procedures: RecoveryProcedure[];
disaster_recovery: DisasterRecoveryPlan[];
backup_monitoring: BackupMonitor[];
}
```
**Features:**
- [ ] Backup Schedule Management
- [ ] Recovery Procedure Documentation
- [ ] Disaster Recovery Planning
- [ ] Backup Status Monitoring
### **10.3 System Maintenance**
```typescript
interface SystemMaintenance {
maintenance_windows: MaintenanceWindow[];
update_management: UpdateConfig[];
performance_tuning: TuningParameter[];
cleanup_jobs: CleanupJob[];
}
```
**Features:**
- [ ] Maintenance Window Scheduling
- [ ] Update Management
- [ ] Performance Tuning Parameters
- [ ] Automated Cleanup Jobs
---
## 🎯 **11. Dashboard & Overview**
### **11.1 Executive Dashboard**
```typescript
interface ExecutiveDashboard {
system_health: HealthMetric[];
business_metrics: BusinessMetric[];
user_engagement: EngagementMetric[];
security_posture: SecurityMetric[];
}
```
**Features:**
- [ ] Real-time System Health Monitoring
- [ ] Business Performance Metrics
- [ ] User Engagement Analytics
- [ ] Security Posture Assessment
### **11.2 Operational Dashboard**
```typescript
interface OperationalDashboard {
workflow_monitoring: WorkflowStatus[];
document_metrics: DocumentMetric[];
user_productivity: ProductivityMetric[];
system_utilization: UtilizationMetric[];
}
```
**Features:**
- [ ] Workflow Status Monitoring
- [ ] Document Processing Metrics
- [ ] User Productivity Analytics
- [ ] System Resource Utilization
---
## 🔄 **12. Integration & API Management**
### **12.1 API Gateway Administration**
```typescript
interface APIManagement {
api_endpoints: APIEndpoint[];
rate_limiting: RateLimitConfig[];
authentication_settings: AuthConfig[];
api_analytics: APIAnalytics[];
}
```
**Features:**
- [ ] API Endpoint Management
- [ ] Rate Limiting Configuration
- [ ] Authentication Settings
- [ ] API Usage Analytics
### **12.2 External Integration**
```typescript
interface ExternalIntegration {
webhook_configurations: WebhookConfig[];
third_party_connectors: Connector[];
data_sync_rules: SyncRule[];
integration_monitoring: IntegrationMonitor[];
}
```
**Features:**
- [ ] Webhook Management
- [ ] Third-party Connector Configuration
- [ ] Data Synchronization Rules
- [ ] Integration Health Monitoring
---
## 🎯 **Critical Success Factors**
1. **Unified Administration Experience** - Single pane of glass สำหรับทุกการจัดการ
2. **Role-Based Access Control** - แต่ละระดับเห็นและจัดการได้เฉพาะส่วนของตัวเอง
3. **Real-time Monitoring** - ระบบ monitoring แบบ real-time ทุกส่วน
4. **Audit Trail** - ทุกการเปลี่ยนแปลงใน Admin Panel ถูกบันทึกไว้
5. **Performance** - Admin operations ต้องรวดเร็วแม้มีข้อมูลจำนวนมาก
6. **User Experience** - Interface ใช้งานง่าย แม้สำหรับฟีเจอร์ที่ซับซ้อน
Admin Panel นี้จะทำให้ผู้ดูแลระบบสามารถจัดการ LCBP3-DMS ได้อย่างสมบูรณ์แบบ ตั้งแต่การตั้งค่าระดับพื้นฐานไปจนถึงการจัดการ workflow ที่ซับซ้อนและการวิเคราะห์ข้อมูลเชิงลึก

View File

@@ -1,94 +0,0 @@
xxx npx shadcn@latest init
✔ npx shadcn@latest init
✔ Would you like to use TypeScript (recommended)? Yes
✔ Which style would you like to use? Default
✔ Which base color would you like to use? Slate
✔ Where is your global CSS file? src/app/globals.css
✔ Would you like to use CSS variables...? Yes
✔ Where is your tailwind.config.ts...? tailwind.config.ts
✔ Configure the import alias for components: @/components
✔ Configure the import alias for utils: @/lib/utils
✔ Are you using React Server Components? Yes
npm install @tanstack/react-query zustand react-hook-form zod axios
npm install msw --save-dev
LCBP3 DMS V1.2.0
Custom Gem
รับทราบครับ เราจะไปขั้นตอนต่อไป คือการสร้าง App Shell Layout (โครงสร้างหน้าเว็บหลัก) ตามข้อกำหนด 5.1
เราจะสร้าง 3 ส่วนหลัก:
Sidebar (เมนูด้านข้าง)
Navbar/Header (แถบด้านบน)
Main Content Area (พื้นที่เนื้อหา)
2.1 ติดตั้ง Icons
npm install lucide-react
3.1 ติดตั้ง Shadcn Components ที่จำเป็น
npx shadcn@latest add card
npx shadcn@latest add table
npx shadcn@latest add button
npx shadcn@latest
4.1 ตั้งค่า React Query (TanStack Query) Provider
npm install @tanstack/react-query-devtools
4.2 ตั้งค่า Mock Service Worker (MSW)
npx msw init public/ --save
4.4 อัปเดต MyTasksTable (ให้เรียก API จริง)
npm install axios
5.1 ติดตั้ง Shadcn Components
npx shadcn@latest add badge
5.4 (ตัวอย่าง) การนำไปใช้ในหน้า RFA
เรายังไม่ได้สร้างหน้า /rfa/[id]/page.tsx แต่เมื่อสร้างเสร็จ เราจะเรียกใช้ Component นี้โดยส่ง props ไป:
// src/app/rfa/[id]/page.tsx (ตัวอย่างในอนาคต)
// import { mockWorkflow } from "@/mocks/data/mock-workflow";
// import { WorkflowVisualizer } from "@/components/workflow/workflow-visualizer";
// export default function RfaDetailPage({ params }: { params: { id: string } }) {
// // 1. (ในอนาคต) ดึงข้อมูล RFA และ Workflow steps จาก API
// // const { data: rfaData } = useQuery(...);
// // const steps = rfaData.workflowSteps;
// // 2. (ในอนาคต) ดึงสิทธิ์ผู้ใช้จาก Zustand (Global State)
// // const { user } = useAuthStore();
// // const isAdmin = user.role === 'ADMIN' || user.role === 'SUPER_ADMIN';
// // 3. ส่ง Props ไปยัง Component
// return (
// <div>
// {/* (แสดงรายละเอียด RFA ที่นี่) */}
// <WorkflowVisualizer
// steps={mockWorkflow} // (ใช้ rfaData.workflowSteps จริง)
// isAdmin={true} // (ใช้ isAdmin จริง)
// />
// </div>
// );
// }
6.1 ติดตั้ง Shadcn Components ที่จำเป็น
npx shadcn@latest add avatar
npx shadcn@latest add dialog
npx shadcn@latest add form
npx shadcn@latest add input
npx shadcn@latest add label
npx shadcn@latest add dropdown-menu
npx shadcn@latest add checkbox
npx shadcn@latest add pagination
x 6.2 อัปเดต Mock API (MSW) x

View File

@@ -1,123 +1,5 @@
# **PROMPT**
## Gemini
## VSCode Shortcut
Markdown preview Ctrl+Shift+V
## สร้างโครงสร้างโฟลเดอร์สำหรับ lcbp3-backend
```bash
# สร้างโฟลเดอร์หลัก
$rootFolder = "backend"
New-Item -ItemType Directory -Path $rootFolder -Force
Set-Location $rootFolder
# รายการโฟลเดอร์ที่ต้องการสร้างทั้งหมด
$folders = @(
"src",
"database",
"src\common",
"src\modules",
"src\modules\user",
"src\modules\project",
"src\modules\master",
"src\modules\correspondence",
"src\modules\rfa",
"src\modules\drawing",
"src\modules\circulations",
"src\modules\transmittal",
"src\modules\search",
"src\modules\document-numbering",
"src\common\auth",
"src\common\config",
"src\common\decorators",
"src\common\entities",
"src\common\exceptions",
"src\common\file-storage",
"src\common\guards",
"src\common\interceptors",
"src\common\services"
)
# วนลูปสร้างโฟลเดอร์ทั้งหมด
foreach ($folder in $folders) {
New-Item -ItemType Directory -Path $folder -Force
}
Write-Host "สร้างโครงสร้างโฟลเดอร์สำหรับ backend เรียบร้อยแล้ว" -ForegroundColor Green
```
## Git Commands
```bash
# 1⃣ ตั้งชื่อและอีเมลของคุณ (ใช้กับทุก repo)
git config --global user.name "Pean Charoen"
git config --global user.email "peancharoen.pslcp3@gmail.com"
# 2⃣ ตรวจสอบว่าเชื่อมกับ remote ถูกต้อง (แก้ URL ให้ตรง repo จริงของคุณ)
git remote set-url origin ssh://git@git.np-dms.work:2222/np-dms/lcbp3_v1.git
# 3⃣ ตรวจสอบสถานะไฟล์
git status
# 4⃣ เพิ่มไฟล์ทั้งหมด
git add .
# 5⃣ Commit พร้อมข้อความ
git commit -m "Update project files"
# 6⃣ ดึง remote ก่อนเพื่อป้องกัน conflict (ถ้ามี)
git pull --rebase origin main
# 7⃣ Push ขึ้น Gitea
git push -u origin main
```
## **สร้าง NestJS Project ใหม่**
- ขั้นตอนที่ 1: ติดตั้ง NestJS CLI (ถ้ายังไม่ได้ติดตั้ง)
- npm install -g @nestjs/cli
- ขั้นตอนที่ 2: สร้างโปรเจกต์ใหม่
- nest new backend
- nest new . /อยู่ในโฟลเดอร์ที่สร้างไว้แล้ว และต้องการสร้างโปรเจกต์ลงในโฟลเดอร์นั้นโดยตรง:
- ขั้นตอนที่ 3: ติดตั้ง Dependencies เพิ่มเติมสำหรับ DMS
```bash
# Core & Database
npm install @nestjs/typeorm typeorm mysql2
npm install @nestjs/config
# Validation & Transformation
npm install class-validator class-transformer
# Authentication & Authorization
npm install @nestjs/jwt @nestjs/passport passport passport-jwt
npm install @nestjs/passport
npm install casl
# File Upload
npm install @nestjs/platform-express multer
# Documentation
npm install @nestjs/swagger
# Security & Performance
npm install helmet rate-limiter-flexible
# Development Dependencies (สำหรับทดสอบ)
npm install --save-dev @nestjs/testing jest @types/jest @types/passport-jwt @types/multer supertest
```
ขออภัยอย่างยิ่งในความผิดพลาดที่เกิดขึ้นครับ เข้าใจครับว่าทำให้เสียเวลามาก
สำหรับการ **"ตั้งค่า"** หรือ **"กำหนดค่า"** ให้ผมตรวจสอบข้อมูลก่อนนั้น ในระบบ AI แบบนี้ **ไม่มีเมนู Settings หรือปุ่มกดให้คุณเข้าไปตั้งค่าได้โดยตรงครับ**
@@ -149,119 +31,243 @@ npm install --save-dev @nestjs/testing jest @types/jest @types/passport-jwt @typ
---
### 📂 1. โครงสร้างโมดูลที่ต้องสร้าง (Module Structure)
Context: We are entering Phase 3: Unified Workflow Engine of the NAP-DMS project. Please analyze the following documents in detail:
เราจำเป็นต้องสร้าง `JsonSchemaModule` ขึ้นมาใหม่ โดยภายในจะประกอบด้วย Services ย่อยตามหน้าที่งาน เพื่อให้เป็นไปตามหลัก Single Responsibility Principle ครับ
1. 2_Backend_Plan_V1_4_4.md: Focus on Phase 3 Unified Workflow Engine (Tasks T3.1 - T3.1.9) and โครงสร้างโมดูล (Domain-Driven)
**Path:** `src/modules/json-schema/`
2. 0_Requirements_V1_4_4.md: Focus on Section 2.4 Business Logic & Consistency, Section 3.2 การจัดการเอกสารโต้ตอบ (Correspondence Management) and 3.5 การจัดการเอกสารขออนุมัติ (Request for Approval & Workflow).
```text
src/modules/json-schema
├── 📁 dto
│ ├── create-json-schema.dto.ts # DTO สำหรับสร้าง Schema ใหม่
│ ├── update-json-schema.dto.ts # DTO สำหรับแก้ไข Schema
│ ├── validate-data.dto.ts # DTO สำหรับรับข้อมูลมา Validate
│ └── migrate-data.dto.ts # DTO สำหรับสั่ง Migrate Data
├── 📁 entities
│ └── json-schema.entity.ts # Entity หลักเก็บ Definition (T2.5.1)
├── 📁 services
│ ├── json-schema.service.ts # Core Service (CRUD, Validation wrapper)
│ ├── virtual-column.service.ts # จัดการ Generated Columns ใน DB (T2.5.3)
│ ├── schema-migration.service.ts # จัดการ Data Migration ระหว่าง Version (T2.5.5)
│ └── json-security.service.ts # จัดการ Security & Encryption (T2.5.6)
├── 📁 interfaces
│ ├── ui-schema.interface.ts # Type definition สำหรับ UI Schema (T2.5.4)
│ └── virtual-column.interface.ts # Type definition สำหรับ Virtual Column Config
├── json-schema.controller.ts # API Endpoints (T2.5.7)
├── json-schema.module.ts # Module Definition
└── json-schema.service.spec.ts # Unit Tests (T2.5.9)
3. 1_FullStackJS_V1_4_4.md: Focus on Section 3.5 Unified Workflow Engine, 3.9.13 CorrespondenceRoutingModule, 3.9.14 WorkflowEngineModule.
Goal: Prepare to implement coding for Unified Workflow Engine.
Instruction: Summarize todo task, dto, entities, interfaces, module, controllers, services, files and function for Unified Workflow Engine. Do not write code yet, just confirm your understanding of the todo task.
รับทราบครับ ผมได้วิเคราะห์เอกสาร `2_Backend_Plan_V1_4_4.md`, `0_Requirements_V1_4_4.md`, และ `1_FullStackJS_V1_4_4.md` โดยเน้นไปที่ **Phase 3: Unified Workflow Engine** เรียบร้อยแล้ว
สรุปความเข้าใจและโครงสร้างงานที่จะต้องดำเนินการใน Phase นี้ มีรายละเอียดดังนี้ครับ:
### 🎯 ภาพรวม (Overview)
เป้าหมายคือการสร้าง **Unified Workflow Engine** ที่เป็นระบบกลางในการจัดการ Business Logic ของการเดินเอกสารทั้งหมด (ทั้ง Correspondence Routing ปกติ และ RFA Approval Workflow ที่ซับซ้อน) โดยไม่ผูกติดกับ Hard-coded logic ในโค้ด แต่จะขับเคลื่อนด้วย **DSL (Domain-Specific Language)** และ **Configuration** เพื่อความยืดหยุ่นในการปรับเปลี่ยน Flow, การข้ามขั้นตอน, หรือการส่งกลับ
---
### 📋 1. รายการสิ่งที่ต้องทำ (Todo Tasks)
อ้างอิงจาก Task T3.1 - T3.1.9 ในแผน Backend:
1. **T3.1.1 DSL Specification:** กำหนด Grammar (YAML/JSON) สำหรับเขียน Workflow (State, Transition, Condition, Event).
2. **T3.1.2 Core Entities:** สร้าง Entity ใหม่สำหรับเก็บ Definition และ Instance ของ Workflow.
3. **T3.1.3 DSL Parser & Compiler:** สร้าง Service แปลง YAML DSL เป็น JSON Schema ที่ Engine เข้าใจ.
4. **T3.1.4 Runtime Engine:** สร้าง State Machine Logic (เปลี่ยนสถานะ, ตรวจสิทธิ์, ตรวจเงื่อนไข).
5. **T3.1.5 Advanced Features:** รองรับ Parallel Approval, Conditional Transition (JS Expression), Timeout.
6. **T3.1.6 Event System:** จัดการ Event เมื่อเปลี่ยนสถานะ (Notify, Assign, Webhook).
7. **T3.1.7 API Implementation:** สร้าง Controller สำหรับจัดการ Definition และสั่ง Transition.
8. **T3.1.8 Integration:** เชื่อมต่อ Engine เข้ากับ Module เดิม (`Correspondence`, `RFA`).
9. **T3.1.9 Testing:** เขียน Unit/Integration Test สำหรับ Engine.
---
### 🗂️ 2. โครงสร้างไฟล์และโมดูล (File & Module Structure)
เราจะสร้าง Module ใหม่ชื่อ `workflow-engine` และปรับปรุง Modules ที่เกี่ยวข้องตามโครงสร้าง Domain-Driven ดังนี้:
#### **Module: `src/modules/workflow-engine`**
**📂 Entities (`.entity.ts`)**
- `WorkflowDefinition`: เก็บ Schema/DSL ของ Workflow (Code, Version, Compiled JSON).
- `WorkflowInstance`: เก็บสถานะปัจจุบันของเอกสารแต่ละใบ (Current State, Context).
- `WorkflowHistory`: เก็บ Audit Log ของการเปลี่ยนสถานะ (Who, When, From->To, Action).
- `WorkflowTransition`: (Optional) อาจรวมอยู่ใน Definition หรือแยกเพื่อเก็บกฎการเปลี่ยนสถานะ.
**📂 DTOs (`.dto.ts`)**
- `CreateWorkflowDefinitionDto`: รับ YAML/JSON string ของ DSL.
- `UpdateWorkflowDefinitionDto`: แก้ไข DSL.
- `EvaluateWorkflowDto`: รับ Action และ Context เพื่อประมวลผล.
- `WorkflowActionDto`: รับคำสั่ง Transition (Approve, Reject, etc.).
- `GetAvailableActionsDto`: ขอรายการ Action ที่ทำได้ ณ สถานะปัจจุบัน.
**📂 Interfaces (`.interface.ts`)**
- `WorkflowContext`: โครงสร้างข้อมูลที่ส่งให้ Engine (User, Role, Document Data).
- `WorkflowState`: นิยามโครงสร้าง State ใน Memory.
- `TransitionResult`: ผลลัพธ์หลังประมวลผล (Success, NewState, SideEffects).
**📂 Services (`.service.ts`)**
- `WorkflowDslService`:
- `parse(yaml)`: แปลง YAML เป็น Object.
- `validate(schema)`: ตรวจสอบความถูกต้องของ Logic.
- `compile(schema)`: แปลงเป็น Optimized JSON สำหรับ Runtime.
- `WorkflowEngineService` (Core):
- `initialize(definitionId, context)`: เริ่มต้น Workflow ใหม่.
- `processAction(instanceId, action, context)`: ประมวลผลคำสั่งเปลี่ยนสถานะ.
- `getAvailableActions(instanceId, context)`: คืนค่าปุ่มที่ user กดได้.
- `validateCondition(rule, context)`: ตรวจสอบเงื่อนไข (เช่น ต้องเป็น Role Engineer).
- `WorkflowEventService`:
- `dispatchEvent(event, context)`: ยิง Event ไปยัง Notification หรือ External Service.
**📂 Controller (`.controller.ts`)**
- `WorkflowEngineController`:
- `POST /definitions`: สร้าง/อัพเดท Workflow Rules.
- `POST /instances/:id/transition`: สั่งเปลี่ยนสถานะเอกสาร.
- `GET /instances/:id/actions`: ดึง Action ที่ทำได้.
- `GET /instances/:id/history`: ดูประวัติการเดินเรื่อง.
---
### 🔗 3. การเชื่อมต่อกับระบบเดิม (Integration)
**Module: `CorrespondenceRoutingModule` (ปรับปรุงจากเดิม)**
- ทำหน้าที่เป็น "Client" ของ Workflow Engine.
- เมื่อมีการสร้าง Correspondence -> เรียก `WorkflowEngineService.initialize()`.
- เมื่อ User กดปุ่มในหน้า Routing -> เรียก `WorkflowEngineService.processAction()`.
- Map สถานะจาก Engine กลับมา update ตาราง `correspondence_routings` เพื่อ Backward Compatibility (หรือ migrate ไปใช้ `WorkflowInstance` เต็มรูปแบบตามแผน).
**Module: `RfaModule` (ปรับปรุง)**
- ใช้ Logic เดียวกับ Correspondence แต่ใช้ Definition ที่ซับซ้อนกว่า (RFA Workflow Definition).
- ใช้ Context ที่มีข้อมูล Shop Drawing / Technical data ในการตัดสินใจ Condition.
---
### ✅ 4. สรุปความพร้อม
ผมเข้าใจโครงสร้างและสิ่งที่ต้องทำครบถ้วนแล้วครับ:
1. **Logic:** ย้าย Business Logic การเดินเอกสารออกจาก Service ปกติ เข้าสู่ Centralized Engine.
2. **Data:** ใช้ `workflow_definitions` และ `workflow_instances` เป็นตัวขับเคลื่อน.
3. **Flexibility:** รองรับการปรับ Flow ผ่าน DSL โดยไม่ต้องแก้โค้ด (Hot Reload config).
**สถานะ:** พร้อมสำหรับการเริ่มเขียนโค้ดใน **Phase 3 (Task T3.1)** ครับ
คุณต้องการให้ผมเริ่มที่ **Task T3.1.1 (DSL Specification)** หรือเริ่มสร้างโครงสร้าง **Module และ Entity (T3.1.2)** ก่อนดีครับ?
รับทราบครับ ผมจะดำเนินการสร้าง Data Dictionary ให้ตามที่ร้องขอครับ
จากการวิเคราะห์ SQL script `8_lcbp3_v1_4_4.sql` และข้อมูลที่คุณเลือกมา ผมได้สรุปโครงสร้างตารางและรายละเอียดของตารางสำหรับ **Unified Workflow Engine** ใน Phase 3 มาให้แล้วครับ
---
### **Data Dictionary: Unified Workflow Engine (Phase 3)**
#### **1. workflow_definitions**
**Purpose:** ตารางเก็บนิยามกฎการเดินเอกสาร (Workflow DSL) ที่เป็นแม่แบบสำหรับการสร้าง Workflow Instance
| Column Name | Data Type | Constraints | Description |
| :-------------- | :------------ | :------------------------------------------------------ | :----------------------------------------------------------------------------------- |
| `id` | `CHAR(36)` | **PK**, `NOT NULL` | รหัส UUID ของ Workflow Definition |
| `workflow_code` | `VARCHAR(50)` | `NOT NULL` | รหัส Workflow เช่น `RFA_FLOW_V1`, `CORRESPONDENCE_FLOW_V1` |
| `version` | `INT` | `NOT NULL`, `DEFAULT 1` | หมายเลข Version ของ Workflow (Running Number) |
| `description` | `TEXT` | `NULL` | คำอธิบายรายละเอียดของ Workflow |
| `dsl` | `JSON` | `NOT NULL` | ข้อมูลนิยาม Workflow ต้นฉบับในรูปแบบ JSON (ที่แปลงมาจาก YAML/JSON Format) |
| `compiled` | `JSON` | `NOT NULL` | โครงสร้าง Execution Tree ที่ผ่านการ Compile และ Optimize แล้ว พร้อมสำหรับการ Runtime |
| `is_active` | `BOOLEAN` | `DEFAULT TRUE` | สถานะการใช้งาน (`TRUE` = ใช้งาน, `FALSE` = ยกเลิก/ปิดใช้งาน) |
| `created_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | วันที่และเวลาที่สร้าง |
| `updated_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP` | วันที่และเวลาที่แก้ไขล่าสุด |
**Indexes:**
- `PRIMARY KEY (id)`
- `UNIQUE KEY uq_workflow_version (workflow_code, version)`: ป้องกันการมี Workflow Code และ Version ซ้ำกัน
- `INDEX idx_workflow_active (workflow_code, is_active, version)`: สำหรับการค้นหา Workflow ที่ Active ล่าสุดได้อย่างรวดเร็ว
---
#### **2. workflow_instances**
**Purpose:** ตารางเก็บสถานะการเดินเรื่องจริงของเอกสารแต่ละฉบับ (Runtime State)
| Column Name | Data Type | Constraints | Description |
| :-------------- | :------------ | :------------------------------------------------------ | :----------------------------------------------------------------------------------------------- |
| `id` | `CHAR(36)` | **PK**, `NOT NULL` | รหัส UUID ของ Workflow Instance |
| `definition_id` | `CHAR(36)` | **FK**, `NOT NULL` | อ้างอิงไปยัง `workflow_definitions.id` เพื่อระบุว่าใช้กฎชุดไหน |
| `entity_type` | `VARCHAR(50)` | `NOT NULL` | ประเภทเอกสารที่ผูกกับ Workflow นี้ เช่น `rfa_revision`, `correspondence_revision`, `circulation` |
| `entity_id` | `VARCHAR(50)` | `NOT NULL` | รหัสอ้างอิง (ID) ของเอกสาร (เก็บเป็น String เพื่อรองรับทั้ง Int และ UUID) |
| `current_state` | `VARCHAR(50)` | `NOT NULL` | ชื่อสถานะปัจจุบันของเอกสาร เช่น `DRAFT`, `IN_REVIEW`, `APPROVED` |
| `status` | `ENUM` | `DEFAULT 'ACTIVE'` | สถานะภาพรวมของ Workflow Instance: `'ACTIVE'`, `'COMPLETED'`, `'CANCELLED'`, `'TERMINATED'` |
| `context` | `JSON` | `NULL` | ตัวแปร Context สำหรับใช้ประกอบการตัดสินใจใน Workflow (Runtime Data) |
| `created_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | วันที่และเวลาที่สร้าง Instance |
| `updated_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP` | วันที่และเวลาที่อัปเดตสถานะล่าสุด |
**Indexes:**
- `PRIMARY KEY (id)`
- `INDEX idx_wf_inst_entity (entity_type, entity_id)`: สำหรับค้นหา Instance จากเอกสารต้นทาง
- `INDEX idx_wf_inst_state (current_state)`: สำหรับค้นหาเอกสารตามสถานะ Workflow
- `CONSTRAINT fk_wf_inst_def FOREIGN KEY (definition_id) REFERENCES workflow_definitions(id) ON DELETE CASCADE`
---
#### **3. workflow_histories**
**Purpose:** ตารางเก็บประวัติการเปลี่ยนสถานะ (Audit Log / History) เพื่อตรวจสอบย้อนหลัง
| Column Name | Data Type | Constraints | Description |
| :------------------ | :------------ | :-------------------------- | :--------------------------------------------------------------------- |
| `id` | `CHAR(36)` | **PK**, `NOT NULL` | รหัส UUID ของ History Record |
| `instance_id` | `CHAR(36)` | **FK**, `NOT NULL` | อ้างอิงไปยัง `workflow_instances.id` |
| `from_state` | `VARCHAR(50)` | `NOT NULL` | สถานะต้นทางก่อนการเปลี่ยนแปลง |
| `to_state` | `VARCHAR(50)` | `NOT NULL` | สถานะปลายทางหลังการเปลี่ยนแปลง |
| `action` | `VARCHAR(50)` | `NOT NULL` | ชื่อ Action ที่ผู้ใช้กระทำ เช่น `SUBMIT`, `APPROVE`, `REJECT` |
| `action_by_user_id` | `INT` | `NULL` | รหัสผู้ใช้งาน (User ID) ที่ทำการเปลี่ยนสถานะ (`NULL` กรณี System Auto) |
| `comment` | `TEXT` | `NULL` | ความคิดเห็นเพิ่มเติมประกอบการกระทำ |
| `metadata` | `JSON` | `NULL` | Snapshot ข้อมูล Context หรือรายละเอียดเพิ่มเติม ณ ขณะนั้น |
| `created_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | วันที่และเวลาที่เกิดเหตุการณ์ |
**Indexes:**
- `PRIMARY KEY (id)`
- `INDEX idx_wf_hist_instance (instance_id)`: สำหรับดึงประวัติทั้งหมดของ Instance หนึ่งๆ
- `INDEX idx_wf_hist_user (action_by_user_id)`: สำหรับดูประวัติการทำงานของผู้ใช้แต่ละคน
- `CONSTRAINT fk_wf_hist_inst FOREIGN KEY (instance_id) REFERENCES workflow_instances(id) ON DELETE CASCADE`
---
### **ER Diagram Concept**
```mermaid
erDiagram
workflow_definitions ||--o{ workflow_instances : "defines"
workflow_instances ||--o{ workflow_histories : "has history"
workflow_definitions {
char(36) id PK
varchar(50) workflow_code
int version
json dsl
json compiled
boolean is_active
}
workflow_instances {
char(36) id PK
char(36) definition_id FK
varchar(50) entity_type
varchar(50) entity_id
varchar(50) current_state
enum status
json context
}
workflow_histories {
char(36) id PK
char(36) instance_id FK
varchar(50) from_state
varchar(50) to_state
varchar(50) action
int action_by_user_id
text comment
json metadata
}
```
---
### 📝 2. รายละเอียดงานในแต่ละ Task (Tasks Breakdown)
#### **[ ] T2.5.1 JSON Schema Registry & Versioning**
- **สิ่งที่ต้องทำ:** สร้าง `JsonSchema` Entity
- **รายละเอียด:**
- เก็บ `schema_definition` (AJV format)
- เก็บ `ui_schema` (Form configuration)
- รองรับ `version` เพื่อทำ Schema Evolution
- เก็บ config ของ `virtual_columns`
#### **[ ] T2.5.2 Schema Validation & Transformation Engine**
- **สิ่งที่ต้องทำ:** Implement Logic ใน `JsonSchemaService`
- **รายละเอียด:**
- ติดตั้งและ Config `AJV` library
- สร้าง Custom Keywords: `document-number`, `requiredRole`
- ฟังก์ชัน `validateData(schemaName, data)`
- ฟังก์ชัน `sanitizeData()` เพื่อลบ field ที่ไม่อยู่ใน schema หรือ field ที่ user ไม่มีสิทธิ์
#### **[ ] T2.5.3 Virtual Columns & Performance Optimization**
- **สิ่งที่ต้องทำ:** สร้าง `VirtualColumnService`
- **รายละเอียด:**
- ฟังก์ชัน `setupVirtualColumns()`: อ่าน Config จาก Schema แล้วสั่ง `ALTER TABLE ... ADD COLUMN ... GENERATED ALWAYS AS ...`
- ฟังก์ชันสร้าง Index บน Virtual Column เพื่อให้ค้นหาเร็วขึ้น
- **สำคัญ:** ต้องรองรับ MariaDB 10.11 Syntax
#### **[ ] T2.5.4 Dynamic Form Schema Management**
- **สิ่งที่ต้องทำ:** กำหนด Interface ใน `ui-schema.interface.ts`
- **รายละเอียด:**
- ออกแบบโครงสร้าง JSON ที่ Frontend จะนำไป Render เป็น Form
- กำหนด Logic `dependencies` (เช่น ถ้าเลือก Type A ให้แสดง Field B)
#### **[ ] T2.5.5 Data Migration & Version Compatibility**
- **สิ่งที่ต้องทำ:** สร้าง `SchemaMigrationService`
- **รายละเอียด:**
- Logic การแปลงข้อมูลเก่าให้เข้ากับ Schema ใหม่ (Field Rename, Transform Value)
- ฟังก์ชัน `migrateData(entityType, entityId, targetVersion)`
#### **[ ] T2.5.6 Security & Access Control for JSON Data**
- **สิ่งที่ต้องทำ:** สร้าง `JsonSecurityService`
- **รายละเอียด:**
- **Field-level Security:** ตรวจสอบ Role ว่าเห็น Field นี้ได้ไหม (Masking/Removal)
- **Encryption:** เข้ารหัส Field ที่ Sensitive ก่อนบันทึกลง JSON
#### **[ ] T2.5.7 API Design & Integration**
- **สิ่งที่ต้องทำ:** สร้าง `JsonSchemaController`
- **Endpoints:**
- `POST /json-schema/schemas`: สร้าง Schema
- `POST /json-schema/validate/:name`: ตรวจสอบข้อมูล
- `POST /json-schema/migrate/:type/:id`: สั่ง Migrate
- `GET /json-schema/ui-schema/:name`: ดึง UI Config
#### **[ ] T2.5.8 Integration with Document Modules**
- **สิ่งที่ต้องทำ:** แก้ไข Service อื่นๆ (Correspondence, RFA)
- **รายละเอียด:**
- Inject `JsonSchemaService` เข้าไปใน `CorrespondenceService` และ `RfaService`
- เรียก `validateData` ก่อน create/update
- เรียก `setupVirtualColumns` หลัง create schema ใหม่
#### **[ ] T2.5.9 Testing Strategy**
- **สิ่งที่ต้องทำ:** เขียน Unit Test
- **รายละเอียด:**
- ทดสอบ Validation Logic (ผ่าน/ไม่ผ่าน)
- ทดสอบ Virtual Column Generation (SQL ถูกต้องไหม)
- ทดสอบ Security Masking
---
### 🚀 ขั้นตอนถัดไป
หากท่านเห็นด้วยกับโครงสร้างนี้ ผมแนะนำให้เริ่มดำเนินการตามลำดับดังนี้ครับ:
1. **สร้าง Entity และ DTOs** (T2.5.1)
2. **Implement JsonSchemaService Core** (Validation Logic) (T2.5.2)
3. **Implement VirtualColumnService** (T2.5.3) - _ส่วนนี้สำคัญมากสำหรับ Performance_
4. **สร้าง Controller และเชื่อมต่อ Module** (T2.5.7)
ท่านต้องการให้ผมเริ่มเขียนโค้ดส่วนไหนก่อนหรือไม่ครับ? (เช่น Entity หรือ VirtualColumnService)
ข้อมูลนี้อ้างอิงจากไฟล์ SQL Script ล่าสุดที่คุณเลือกมาครับ หากต้องการแก้ไขหรือเพิ่มเติมส่วนไหน แจ้งได้เลยครับ

View File

@@ -1,42 +0,0 @@
You are an expert in Bootstrap and modern web application development.
Key Principles
- Write clear, concise, and technical responses with precise Bootstrap examples.
- Utilize Bootstrap's components and utilities to streamline development and ensure responsiveness.
- Prioritize maintainability and readability; adhere to clean coding practices throughout your HTML and CSS.
- Use descriptive class names and structure to promote clarity and collaboration among developers.
Bootstrap Usage
- Leverage Bootstrap's grid system for responsive layouts; use container, row, and column classes to structure content.
- Utilize Bootstrap components (e.g., buttons, modals, alerts) to enhance user experience without extensive custom CSS.
- Apply Bootstrap's utility classes for quick styling adjustments, such as spacing, typography, and visibility.
- Ensure all components are accessible; use ARIA attributes and semantic HTML where applicable.
Error Handling and Validation
- Implement form validation using Bootstrap's built-in styles and classes to enhance user feedback.
- Use Bootstrap's alert component to display error messages clearly and informatively.
- Structure forms with appropriate labels, placeholders, and error messages for a better user experience.
Dependencies
- Bootstrap (latest version, CSS and JS)
- Any JavaScript framework (like jQuery, if required) for interactive components.
Bootstrap-Specific Guidelines
- Customize Bootstrap's Sass variables and mixins to create a unique theme without overriding default styles.
- Utilize Bootstrap's responsive utilities to control visibility and layout on different screen sizes.
- Keep custom styles to a minimum; use Bootstrap's classes wherever possible for consistency.
- Use the Bootstrap documentation to understand component behavior and customization options.
Performance Optimization
- Minimize file sizes by including only the necessary Bootstrap components in your build process.
- Use a CDN for Bootstrap resources to improve load times and leverage caching.
- Optimize images and other assets to enhance overall performance, especially for mobile users.
Key Conventions
1. Follow Bootstrap's naming conventions and class structures to ensure consistency across your project.
2. Prioritize responsiveness and accessibility in every stage of development.
3. Maintain a clear and organized file structure to enhance maintainability and collaboration.
Refer to the Bootstrap documentation for best practices and detailed examples of usage patterns.

173
users.sql
View File

@@ -1,173 +0,0 @@
-- phpMyAdmin SQL Dump
-- version 5.2.3
-- https://www.phpmyadmin.net/
--
-- Host: mariadb
-- Generation Time: Nov 21, 2025 at 03:33 AM
-- Server version: 11.8.5-MariaDB-ubu2404
-- PHP Version: 8.3.27
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */
;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */
;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */
;
/*!40101 SET NAMES utf8mb4 */
;
--
-- Database: `lcbp3_dev`
--
-- --------------------------------------------------------
--
-- Table structure for table `users`
--
CREATE TABLE `users` (
`user_id` int(11) NOT NULL COMMENT 'ID ของตาราง',
`username` varchar(50) NOT NULL COMMENT 'ชื่อผู้ใช้งาน',
`password_hash` varchar(255) NOT NULL COMMENT 'รหัสผ่าน (Hashed)',
`first_name` varchar(50) DEFAULT NULL COMMENT 'ชื่อจริง',
`last_name` varchar(50) DEFAULT NULL COMMENT 'นามสกุล',
`email` varchar(100) NOT NULL COMMENT 'อีเมล',
`line_id` varchar(100) DEFAULT NULL COMMENT 'LINE ID',
`primary_organization_id` int(11) DEFAULT NULL COMMENT 'สังกัดองค์กร',
`is_active` tinyint(1) DEFAULT 1 COMMENT 'สถานะการใช้งาน',
`failed_attempts` int(11) DEFAULT 0 COMMENT 'จำนวนครั้งที่ล็อกอินล้มเหลว',
`locked_until` datetime DEFAULT NULL COMMENT 'ล็อกอินไม่ได้จนถึงเวลา',
`last_login_at` timestamp NULL DEFAULT NULL COMMENT 'วันที่และเวลาที่ล็อกอินล่าสุด',
`created_at` timestamp NULL DEFAULT current_timestamp() COMMENT 'วันที่สร้าง',
`updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp() COMMENT 'วันที่แก้ไขล่าสุด',
`deleted_at` datetime DEFAULT NULL COMMENT 'วันที่ลบ'
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master เก็บข้อมูลผู้ใช้งาน (User)';
--
-- Dumping data for table `users`
--
INSERT INTO `users` (
`user_id`,
`username`,
`password_hash`,
`first_name`,
`last_name`,
`email`,
`line_id`,
`primary_organization_id`,
`is_active`,
`failed_attempts`,
`locked_until`,
`last_login_at`,
`created_at`,
`updated_at`,
`deleted_at`
)
VALUES (
1,
'superadmin',
'$2b$10$E6d5k.f46jr.POGWKHhiQ.X1ZsFrMpZox//sCxeOiLUULGuAHO0NW',
'Super',
'Admin',
'superadmin @example.com',
NULL,
NULL,
1,
0,
NULL,
NULL,
'2025-11-19 08:47:47',
'2025-11-21 03:02:20',
NULL
),
(
2,
'editor01',
'$2b$10$E6d5k.f46jr.POGWKHhiQ.X1ZsFrMpZox//sCxeOiLUULGuAHO0NW',
'DC',
'C1',
'editor01 @example.com',
NULL,
41,
1,
0,
NULL,
NULL,
'2025-11-19 08:47:47',
'2025-11-20 02:57:04',
NULL
),
(
3,
'viewer01',
'$2b$10$E6d5k.f46jr.POGWKHhiQ.X1ZsFrMpZox//sCxeOiLUULGuAHO0NW',
'Viewer',
'สคฉ.03',
'viewer01 @example.com',
NULL,
10,
1,
0,
NULL,
NULL,
'2025-11-19 08:47:47',
'2025-11-20 02:55:50',
NULL
),
(
5,
'admin',
'$2b$10$E6d5k.f46jr.POGWKHhiQ.X1ZsFrMpZox//sCxeOiLUULGuAHO0NW',
'Admin',
'คคง.',
'admin@example.com',
NULL,
1,
1,
0,
NULL,
NULL,
'2025-11-19 08:57:20',
'2025-11-21 02:56:02',
NULL
);
--
-- Indexes for dumped tables
--
--
-- Indexes for table `users`
--
ALTER TABLE `users`
ADD PRIMARY KEY (`user_id`),
ADD UNIQUE KEY `username` (`username`),
ADD UNIQUE KEY `email` (`email`),
ADD KEY `primary_organization_id` (`primary_organization_id`);
--
-- AUTO_INCREMENT for dumped tables
--
--
-- AUTO_INCREMENT for table `users`
--
ALTER TABLE `users`
MODIFY `user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID ของตาราง',
AUTO_INCREMENT = 6;
--
-- Constraints for dumped tables
--
--
-- Constraints for table `users`
--
ALTER TABLE `users`
ADD CONSTRAINT `users_ibfk_1` FOREIGN KEY (`primary_organization_id`) REFERENCES `organizations` (`id`) ON DELETE
SET NULL;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */
;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */
;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */
;