251129:1700 update to 1.4.5
This commit is contained in:
20
.agent/rules/code-execution.md
Normal file
20
.agent/rules/code-execution.md
Normal 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.
|
||||||
11
.agent/rules/code-references.md
Normal file
11
.agent/rules/code-references.md
Normal 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
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
---
|
---
|
||||||
trigger: always_on
|
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
803
0_Requirements_V1_4_5.md
Normal 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
962
1_FullStackJS_V1_4_5.md
Normal 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))**
|
||||||
|
|
||||||
|
- ใช้รูปแบบ **Arrange–Act–Assert**
|
||||||
|
- ใช้ชื่อตัวแปรในการทดสอบที่สื่อความหมาย (inputData, expectedOutput)
|
||||||
|
- เขียน **unit tests** สำหรับ public methods ทั้งหมด
|
||||||
|
- จำลอง (Mock) การพึ่งพาภายนอก (external dependencies)
|
||||||
|
- เพิ่ม **acceptance tests** ต่อโมดูลโดยใช้รูปแบบ Given–When-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 (Server‑Side)**
|
||||||
|
|
||||||
|
- 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`
|
||||||
@@ -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
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
978
3_Frontend_Plan_V1_4_5.md
Normal 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
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
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
2127
8_lcbp3_v1_4_5_seed.sql
Normal file
File diff suppressed because it is too large
Load Diff
37
GEMINI.md
37
GEMINI.md
@@ -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.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "backend",
|
"name": "backend",
|
||||||
"version": "0.0.1",
|
"version": "1.4.4",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -17,7 +17,8 @@
|
|||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:cov": "jest --coverage",
|
"test:cov": "jest --coverage",
|
||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
"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": {
|
"dependencies": {
|
||||||
"@casl/ability": "^6.7.3",
|
"@casl/ability": "^6.7.3",
|
||||||
|
|||||||
125
backend/src/Workflow DSL Specification.md
Normal file
125
backend/src/Workflow DSL Specification.md
Normal 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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
67
backend/src/database/seeds/run-seed.ts
Normal file
67
backend/src/database/seeds/run-seed.ts
Normal 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`
|
||||||
|
|
||||||
|
*/
|
||||||
@@ -10,26 +10,41 @@ export const seedWorkflowDefinitions = async (dataSource: DataSource) => {
|
|||||||
|
|
||||||
// 1. RFA Workflow (Standard)
|
// 1. RFA Workflow (Standard)
|
||||||
const rfaDsl = {
|
const rfaDsl = {
|
||||||
workflow: 'RFA',
|
workflow: 'RFA_FLOW_V1', // [FIX] เปลี่ยนชื่อให้ตรงกับค่าใน RfaWorkflowService
|
||||||
version: 1,
|
version: 1,
|
||||||
|
description: 'Standard RFA Approval Workflow',
|
||||||
states: [
|
states: [
|
||||||
{
|
{
|
||||||
name: 'DRAFT',
|
name: 'DRAFT',
|
||||||
initial: true,
|
initial: true,
|
||||||
on: { SUBMIT: { to: 'IN_REVIEW', requirements: [{ role: 'Editor' }] } },
|
on: {
|
||||||
|
SUBMIT: {
|
||||||
|
to: 'IN_REVIEW',
|
||||||
|
require: { role: 'Editor' }, // [FIX] แก้ไข Syntax เป็น Object
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'IN_REVIEW',
|
name: 'IN_REVIEW',
|
||||||
on: {
|
on: {
|
||||||
APPROVE: {
|
APPROVE_1: {
|
||||||
to: 'APPROVED',
|
to: 'APPROVED', // [FIX] ชี้ไปที่ State ที่มีอยู่จริง
|
||||||
requirements: [{ role: 'Contract Admin' }],
|
require: { role: 'Contract Admin' },
|
||||||
|
condition: "context.priority === 'HIGH'",
|
||||||
|
},
|
||||||
|
APPROVE_2: {
|
||||||
|
to: 'APPROVED', // [FIX] ชี้ไปที่ State ที่มีอยู่จริง
|
||||||
|
require: { role: 'Contract Admin' },
|
||||||
|
condition: "context.priority === 'NORMAL'",
|
||||||
},
|
},
|
||||||
REJECT: {
|
REJECT: {
|
||||||
to: 'REJECTED',
|
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 },
|
{ name: 'APPROVED', terminal: true },
|
||||||
@@ -39,18 +54,27 @@ export const seedWorkflowDefinitions = async (dataSource: DataSource) => {
|
|||||||
|
|
||||||
// 2. Circulation Workflow
|
// 2. Circulation Workflow
|
||||||
const circulationDsl = {
|
const circulationDsl = {
|
||||||
workflow: 'CIRCULATION',
|
workflow: 'CIRCULATION_INTERNAL_V1', // [FIX] เปลี่ยนชื่อให้ตรงกับค่าใน CirculationWorkflowService
|
||||||
version: 1,
|
version: 1,
|
||||||
|
description: 'Internal Document Circulation',
|
||||||
states: [
|
states: [
|
||||||
{
|
{
|
||||||
name: 'OPEN',
|
name: 'OPEN',
|
||||||
initial: true,
|
initial: true,
|
||||||
on: { SEND: { to: 'IN_REVIEW' } },
|
on: {
|
||||||
|
START: {
|
||||||
|
// [FIX] เปลี่ยนชื่อ Action ให้ตรงกับที่ Service เรียกใช้ ('START')
|
||||||
|
to: 'IN_REVIEW',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'IN_REVIEW',
|
name: 'IN_REVIEW',
|
||||||
on: {
|
on: {
|
||||||
COMPLETE: { to: 'COMPLETED' }, // เมื่อทุกคนตอบครบ
|
COMPLETE_TASK: {
|
||||||
|
// [FIX] เปลี่ยนให้สอดคล้องกับ Action ที่ใช้จริง
|
||||||
|
to: 'COMPLETED',
|
||||||
|
},
|
||||||
CANCEL: { to: 'CANCELLED' },
|
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) {
|
for (const dsl of workflows) {
|
||||||
const exists = await repo.findOne({
|
const exists = await repo.findOne({
|
||||||
where: { workflow_code: dsl.workflow, version: dsl.version },
|
where: { workflow_code: dsl.workflow, version: dsl.version },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
const compiled = dslService.compile(dsl);
|
try {
|
||||||
|
// Compile เพื่อ Validate และ Normalize ก่อนบันทึก
|
||||||
|
// cast as any เพื่อ bypass type checking ตอน seed raw data
|
||||||
|
const compiled = dslService.compile(dsl as any);
|
||||||
|
|
||||||
await repo.save(
|
await repo.save(
|
||||||
repo.create({
|
repo.create({
|
||||||
workflow_code: dsl.workflow,
|
workflow_code: dsl.workflow,
|
||||||
version: dsl.version,
|
version: dsl.version,
|
||||||
|
description: dsl.description,
|
||||||
dsl: dsl,
|
dsl: dsl,
|
||||||
compiled: compiled,
|
compiled: compiled,
|
||||||
is_active: true,
|
is_active: true,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
console.log(`✅ Seeded Workflow: ${dsl.workflow} v${dsl.version}`);
|
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}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
164
backend/src/modules/circulation/circulation-workflow.service.ts
Normal file
164
backend/src/modules/circulation/circulation-workflow.service.ts
Normal 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}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { Circulation } from './entities/circulation.entity';
|
|
||||||
import { CirculationRouting } from './entities/circulation-routing.entity';
|
import { CirculationRouting } from './entities/circulation-routing.entity';
|
||||||
import { CirculationStatusCode } from './entities/circulation-status-code.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 { 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({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -17,9 +19,10 @@ import { UserModule } from '../user/user.module';
|
|||||||
CirculationStatusCode,
|
CirculationStatusCode,
|
||||||
]),
|
]),
|
||||||
UserModule,
|
UserModule,
|
||||||
|
WorkflowEngineModule,
|
||||||
],
|
],
|
||||||
controllers: [CirculationController],
|
controllers: [CirculationController],
|
||||||
providers: [CirculationService],
|
providers: [CirculationService, CirculationWorkflowService],
|
||||||
exports: [CirculationService],
|
exports: [CirculationService],
|
||||||
})
|
})
|
||||||
export class CirculationModule {}
|
export class CirculationModule {}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,24 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { CorrespondenceService } from './correspondence.service.js';
|
|
||||||
import { CorrespondenceController } from './correspondence.controller.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 { CorrespondenceRevision } from './entities/correspondence-revision.entity.js';
|
||||||
import { CorrespondenceType } from './entities/correspondence-type.entity.js';
|
import { CorrespondenceType } from './entities/correspondence-type.entity.js';
|
||||||
|
import { Correspondence } from './entities/correspondence.entity.js';
|
||||||
// Import Entities ใหม่
|
// 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 { 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 { DocumentNumberingModule } from '../document-numbering/document-numbering.module.js'; // ต้องใช้ตอน Create
|
||||||
import { JsonSchemaModule } from '../json-schema/json-schema.module.js'; // ต้องใช้ Validate Details
|
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 { UserModule } from '../user/user.module.js'; // <--- 1. Import UserModule
|
||||||
import { WorkflowEngineModule } from '../workflow-engine/workflow-engine.module.js'; // <--- ✅ เพิ่มบรรทัดนี้ครับ
|
import { WorkflowEngineModule } from '../workflow-engine/workflow-engine.module.js'; // <--- ✅ เพิ่มบรรทัดนี้ครับ
|
||||||
import { CorrespondenceReference } from './entities/correspondence-reference.entity.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({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -37,7 +39,7 @@ import { SearchModule } from '../search/search.module'; // ✅ 1. เพิ่
|
|||||||
SearchModule, // ✅ 2. ใส่ SearchModule ที่นี่
|
SearchModule, // ✅ 2. ใส่ SearchModule ที่นี่
|
||||||
],
|
],
|
||||||
controllers: [CorrespondenceController],
|
controllers: [CorrespondenceController],
|
||||||
providers: [CorrespondenceService],
|
providers: [CorrespondenceService, CorrespondenceWorkflowService],
|
||||||
exports: [CorrespondenceService],
|
exports: [CorrespondenceService],
|
||||||
})
|
})
|
||||||
export class CorrespondenceModule {}
|
export class CorrespondenceModule {}
|
||||||
|
|||||||
@@ -15,14 +15,10 @@ import { VirtualColumnService } from './services/virtual-column.service';
|
|||||||
import { CryptoService } from '../../common/services/crypto.service';
|
import { CryptoService } from '../../common/services/crypto.service';
|
||||||
|
|
||||||
// Import Module อื่นๆ ที่จำเป็นสำหรับ Guard (ถ้า Guards อยู่ใน Common อาจจะไม่ต้อง import ที่นี่โดยตรง)
|
// Import Module อื่นๆ ที่จำเป็นสำหรับ Guard (ถ้า Guards อยู่ใน Common อาจจะไม่ต้อง import ที่นี่โดยตรง)
|
||||||
// import { UserModule } from '../user/user.module';
|
import { UserModule } from '../user/user.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [TypeOrmModule.forFeature([JsonSchema]), ConfigModule, UserModule],
|
||||||
TypeOrmModule.forFeature([JsonSchema]),
|
|
||||||
ConfigModule,
|
|
||||||
// UserModule,
|
|
||||||
],
|
|
||||||
controllers: [JsonSchemaController],
|
controllers: [JsonSchemaController],
|
||||||
providers: [
|
providers: [
|
||||||
JsonSchemaService,
|
JsonSchemaService,
|
||||||
|
|||||||
@@ -1,172 +1,406 @@
|
|||||||
// File: src/modules/json-schema/json-schema.controller.ts
|
// File: src/modules/json-schema/json-schema.service.ts
|
||||||
import {
|
// บันทึกการแก้ไข: Fix TS2345 (undefined check)
|
||||||
Body,
|
|
||||||
Controller,
|
|
||||||
Delete,
|
|
||||||
Get,
|
|
||||||
HttpCode,
|
|
||||||
HttpStatus,
|
|
||||||
Param,
|
|
||||||
ParseIntPipe,
|
|
||||||
Patch,
|
|
||||||
Post,
|
|
||||||
Query,
|
|
||||||
UseGuards,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import {
|
|
||||||
ApiBearerAuth,
|
|
||||||
ApiOperation,
|
|
||||||
ApiParam,
|
|
||||||
ApiResponse,
|
|
||||||
ApiTags,
|
|
||||||
} from '@nestjs/swagger';
|
|
||||||
|
|
||||||
import { JsonSchemaService } from './json-schema.service';
|
import {
|
||||||
import { SchemaMigrationService } from './services/schema-migration.service';
|
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 { CreateJsonSchemaDto } from './dto/create-json-schema.dto';
|
||||||
import { MigrateDataDto } from './dto/migrate-data.dto';
|
|
||||||
import { SearchJsonSchemaDto } from './dto/search-json-schema.dto';
|
import { SearchJsonSchemaDto } from './dto/search-json-schema.dto';
|
||||||
import { UpdateJsonSchemaDto } from './dto/update-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';
|
// Services ย่อยที่แยกตามหน้าที่ (Single Responsibility)
|
||||||
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
|
import {
|
||||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
JsonSecurityService,
|
||||||
import { RbacGuard } from '../../common/guards/rbac.guard';
|
SecurityContext,
|
||||||
import { User } from '../user/entities/user.entity';
|
} 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(
|
constructor(
|
||||||
private readonly jsonSchemaService: JsonSchemaService,
|
@InjectRepository(JsonSchema)
|
||||||
private readonly migrationService: SchemaMigrationService,
|
private readonly jsonSchemaRepository: Repository<JsonSchema>,
|
||||||
) {}
|
private readonly virtualColumnService: VirtualColumnService,
|
||||||
|
private readonly uiSchemaService: UiSchemaService,
|
||||||
// ----------------------------------------------------------------------
|
private readonly jsonSecurityService: JsonSecurityService,
|
||||||
// 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,
|
|
||||||
) {
|
) {
|
||||||
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')
|
async onModuleInit() {
|
||||||
@ApiOperation({ summary: 'Delete a schema version (Hard Delete)' })
|
// สามารถโหลด Schema ที่ Active ทั้งหมดมา Cache ไว้ล่วงหน้าได้ที่นี่ เพื่อความเร็วในการตอบสนองครั้งแรก
|
||||||
@RequirePermission('system.manage_all')
|
|
||||||
remove(@Param('id', ParseIntPipe) id: number) {
|
|
||||||
return this.jsonSchemaService.remove(id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
/**
|
||||||
// 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')
|
// 2. Keyword สำหรับระบุ Role ที่จำเป็น (ใช้ร่วมกับ Security Service)
|
||||||
@HttpCode(HttpStatus.OK)
|
this.ajv.addKeyword({
|
||||||
@ApiOperation({ summary: 'Validate data against the latest schema version' })
|
keyword: 'requiredRole',
|
||||||
@ApiResponse({
|
type: 'string',
|
||||||
status: 200,
|
metaSchema: { type: 'string' },
|
||||||
description: 'Validation result including errors and sanitized data',
|
validate: (schema: string, data: any) => true, // ผ่านเสมอในขั้น AJV (Security Service จะจัดการเอง)
|
||||||
})
|
});
|
||||||
@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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('read/:code')
|
/**
|
||||||
@HttpCode(HttpStatus.OK)
|
* สร้าง Schema ใหม่ พร้อมจัดการ Version, UI Schema และ Virtual Columns
|
||||||
@ApiOperation({
|
*/
|
||||||
summary: 'Process read data (Decrypt & Filter) based on user roles',
|
async create(createDto: CreateJsonSchemaDto): Promise<JsonSchema> {
|
||||||
})
|
// 1. ตรวจสอบความถูกต้องของ JSON Schema Definition (AJV Syntax)
|
||||||
@RequirePermission('document.view')
|
try {
|
||||||
async processReadData(
|
this.ajv.compile(createDto.schemaDefinition);
|
||||||
@Param('code') code: string,
|
} catch (error: any) {
|
||||||
@Body() data: any,
|
throw new BadRequestException(
|
||||||
@CurrentUser() user: User,
|
`Invalid JSON Schema format: ${error.message}`,
|
||||||
) {
|
|
||||||
// แปลง 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)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return this.jsonSchemaService.processReadData(code, data, { userRoles });
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// 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,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
schemaCode: string,
|
||||||
|
data: any,
|
||||||
|
userContext: SecurityContext,
|
||||||
|
): Promise<any> {
|
||||||
|
if (!data) return data;
|
||||||
|
|
||||||
|
// ดึง Schema เพื่อดู Config การถอดรหัสและการมองเห็น
|
||||||
|
const schema = await this.findLatestByCode(schemaCode);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
import {
|
||||||
IsString,
|
|
||||||
IsNotEmpty,
|
|
||||||
IsInt,
|
|
||||||
IsOptional,
|
|
||||||
IsDateString,
|
|
||||||
IsObject,
|
|
||||||
IsArray,
|
IsArray,
|
||||||
|
IsDateString,
|
||||||
|
IsInt,
|
||||||
|
IsNotEmpty,
|
||||||
|
IsObject,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
} from 'class-validator';
|
} 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()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
title!: string;
|
title!: string;
|
||||||
|
|
||||||
@IsInt()
|
@ApiProperty({ description: 'รายละเอียดเพิ่มเติม', required: false })
|
||||||
@IsNotEmpty()
|
|
||||||
rfaStatusCodeId!: number;
|
|
||||||
|
|
||||||
@IsInt()
|
|
||||||
@IsOptional()
|
|
||||||
rfaApproveCodeId?: number;
|
|
||||||
|
|
||||||
@IsDateString()
|
|
||||||
@IsOptional()
|
|
||||||
documentDate?: string;
|
|
||||||
|
|
||||||
@IsDateString()
|
|
||||||
@IsOptional()
|
|
||||||
issuedDate?: string;
|
|
||||||
|
|
||||||
@IsDateString()
|
|
||||||
@IsOptional()
|
|
||||||
receivedDate?: string;
|
|
||||||
|
|
||||||
@IsDateString()
|
|
||||||
@IsOptional()
|
|
||||||
approvedDate?: string;
|
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'วันที่ในเอกสาร', required: false })
|
||||||
|
@IsDateString()
|
||||||
|
@IsOptional()
|
||||||
|
documentDate?: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'ข้อมูล Dynamic Details (JSON)',
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
@IsObject()
|
@IsObject()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
details?: Record<string, any>;
|
details?: Record<string, any>;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'รายการ Shop Drawing Revisions ที่แนบมาด้วย',
|
||||||
|
required: false,
|
||||||
|
type: [Number],
|
||||||
|
})
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
shopDrawingRevisionIds?: number[]; // IDs of linked Shop Drawings
|
shopDrawingRevisionIds?: number[];
|
||||||
}
|
}
|
||||||
|
|||||||
13
backend/src/modules/rfa/dto/submit-rfa.dto.ts
Normal file
13
backend/src/modules/rfa/dto/submit-rfa.dto.ts
Normal 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;
|
||||||
|
}
|
||||||
@@ -1,23 +1,21 @@
|
|||||||
// File: src/modules/rfa/entities/rfa-revision.entity.ts
|
// File: src/modules/rfa/entities/rfa-revision.entity.ts
|
||||||
import {
|
import {
|
||||||
Entity,
|
|
||||||
PrimaryGeneratedColumn,
|
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
UpdateDateColumn,
|
Entity,
|
||||||
ManyToOne,
|
|
||||||
JoinColumn,
|
JoinColumn,
|
||||||
|
ManyToOne,
|
||||||
OneToMany,
|
OneToMany,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
Unique,
|
Unique,
|
||||||
Index,
|
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { Rfa } from './rfa.entity';
|
|
||||||
import { Correspondence } from '../../correspondence/entities/correspondence.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 { User } from '../../user/entities/user.entity';
|
||||||
|
import { RfaApproveCode } from './rfa-approve-code.entity';
|
||||||
import { RfaItem } from './rfa-item.entity';
|
import { RfaItem } from './rfa-item.entity';
|
||||||
|
import { RfaStatusCode } from './rfa-status-code.entity';
|
||||||
import { RfaWorkflow } from './rfa-workflow.entity';
|
import { RfaWorkflow } from './rfa-workflow.entity';
|
||||||
|
import { Rfa } from './rfa.entity';
|
||||||
|
|
||||||
@Entity('rfa_revisions')
|
@Entity('rfa_revisions')
|
||||||
@Unique(['rfaId', 'revisionNumber'])
|
@Unique(['rfaId', 'revisionNumber'])
|
||||||
@@ -65,11 +63,16 @@ export class RfaRevision {
|
|||||||
@Column({ type: 'text', nullable: true })
|
@Column({ type: 'text', nullable: true })
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|
||||||
// ✅ [New] เพิ่ม field details สำหรับเก็บข้อมูล Dynamic ของ RFA (เช่น Method Statement Details)
|
// --- JSON & Schema Section ---
|
||||||
|
|
||||||
@Column({ type: 'json', nullable: true })
|
@Column({ type: 'json', nullable: true })
|
||||||
details?: any;
|
details?: any;
|
||||||
|
|
||||||
// ✅ [New] Virtual Column: ดึงจำนวนแบบที่แนบ (drawingCount) จาก JSON
|
// ✅ [New] จำเป็นสำหรับ Data Migration (T2.5.5)
|
||||||
|
@Column({ name: 'schema_version', default: 1 })
|
||||||
|
schemaVersion!: number;
|
||||||
|
|
||||||
|
// ✅ Virtual Column
|
||||||
@Column({
|
@Column({
|
||||||
name: 'v_ref_drawing_count',
|
name: 'v_ref_drawing_count',
|
||||||
type: 'int',
|
type: 'int',
|
||||||
@@ -79,6 +82,8 @@ export class RfaRevision {
|
|||||||
})
|
})
|
||||||
vRefDrawingCount?: number;
|
vRefDrawingCount?: number;
|
||||||
|
|
||||||
|
// --- Timestamp ---
|
||||||
|
|
||||||
@CreateDateColumn({ name: 'created_at' })
|
@CreateDateColumn({ name: 'created_at' })
|
||||||
createdAt!: Date;
|
createdAt!: Date;
|
||||||
|
|
||||||
@@ -110,11 +115,9 @@ export class RfaRevision {
|
|||||||
@JoinColumn({ name: 'created_by' })
|
@JoinColumn({ name: 'created_by' })
|
||||||
creator?: User;
|
creator?: User;
|
||||||
|
|
||||||
// Items (Shop Drawings inside this RFA)
|
|
||||||
@OneToMany(() => RfaItem, (item) => item.rfaRevision, { cascade: true })
|
@OneToMany(() => RfaItem, (item) => item.rfaRevision, { cascade: true })
|
||||||
items!: RfaItem[];
|
items!: RfaItem[];
|
||||||
|
|
||||||
// Workflows
|
|
||||||
@OneToMany(() => RfaWorkflow, (workflow) => workflow.rfaRevision, {
|
@OneToMany(() => RfaWorkflow, (workflow) => workflow.rfaRevision, {
|
||||||
cascade: true,
|
cascade: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
|
// File: src/modules/rfa/entities/rfa-workflow.entity.ts
|
||||||
import {
|
import {
|
||||||
Entity,
|
|
||||||
PrimaryGeneratedColumn,
|
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
UpdateDateColumn,
|
Entity,
|
||||||
ManyToOne,
|
|
||||||
JoinColumn,
|
JoinColumn,
|
||||||
|
ManyToOne,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { RfaRevision } from './rfa-revision.entity';
|
|
||||||
import { Organization } from '../../project/entities/organization.entity';
|
import { Organization } from '../../project/entities/organization.entity';
|
||||||
import { User } from '../../user/entities/user.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')
|
@Entity('rfa_workflows')
|
||||||
export class RfaWorkflow {
|
export class RfaWorkflow {
|
||||||
@@ -31,10 +33,10 @@ export class RfaWorkflow {
|
|||||||
@Column({
|
@Column({
|
||||||
name: 'action_type',
|
name: 'action_type',
|
||||||
type: 'enum',
|
type: 'enum',
|
||||||
enum: ['REVIEW', 'APPROVE', 'ACKNOWLEDGE'],
|
enum: RfaActionType, // ✅ Use Shared Enum
|
||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
actionType?: string;
|
actionType?: RfaActionType;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
type: 'enum',
|
type: 'enum',
|
||||||
@@ -50,7 +52,7 @@ export class RfaWorkflow {
|
|||||||
completedAt?: Date;
|
completedAt?: Date;
|
||||||
|
|
||||||
@Column({ type: 'json', nullable: true })
|
@Column({ type: 'json', nullable: true })
|
||||||
stateContext?: Record<string, any>; // เก็บ Snapshot ข้อมูล ณ ขณะนั้น
|
stateContext?: Record<string, any>;
|
||||||
|
|
||||||
@CreateDateColumn({ name: 'created_at' })
|
@CreateDateColumn({ name: 'created_at' })
|
||||||
createdAt!: Date;
|
createdAt!: Date;
|
||||||
@@ -59,7 +61,7 @@ export class RfaWorkflow {
|
|||||||
updatedAt!: Date;
|
updatedAt!: Date;
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
@ManyToOne(() => RfaRevision, (rev) => rev.workflows, { onDelete: 'CASCADE' }) // ต้องไปเพิ่ม Property workflows ใน RfaRevision ด้วย
|
@ManyToOne(() => RfaRevision, (rev) => rev.workflows, { onDelete: 'CASCADE' })
|
||||||
@JoinColumn({ name: 'rfa_revision_id' })
|
@JoinColumn({ name: 'rfa_revision_id' })
|
||||||
rfaRevision!: RfaRevision;
|
rfaRevision!: RfaRevision;
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import {
|
import {
|
||||||
Entity,
|
|
||||||
PrimaryGeneratedColumn,
|
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
DeleteDateColumn,
|
DeleteDateColumn,
|
||||||
ManyToOne,
|
Entity,
|
||||||
JoinColumn,
|
JoinColumn,
|
||||||
|
ManyToOne,
|
||||||
OneToMany,
|
OneToMany,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { RfaType } from './rfa-type.entity';
|
import { Discipline } from '../../master/entities/discipline.entity'; // Import ใหม่
|
||||||
import { User } from '../../user/entities/user.entity';
|
import { User } from '../../user/entities/user.entity';
|
||||||
import { RfaRevision } from './rfa-revision.entity';
|
import { RfaRevision } from './rfa-revision.entity';
|
||||||
|
import { RfaType } from './rfa-type.entity';
|
||||||
|
|
||||||
@Entity('rfas')
|
@Entity('rfas')
|
||||||
export class Rfa {
|
export class Rfa {
|
||||||
@@ -34,6 +35,11 @@ export class Rfa {
|
|||||||
@JoinColumn({ name: 'rfa_type_id' })
|
@JoinColumn({ name: 'rfa_type_id' })
|
||||||
rfaType!: RfaType;
|
rfaType!: RfaType;
|
||||||
|
|
||||||
|
// ✅ [NEW] Relation
|
||||||
|
@ManyToOne(() => Discipline)
|
||||||
|
@JoinColumn({ name: 'discipline_id' })
|
||||||
|
discipline?: Discipline;
|
||||||
|
|
||||||
@ManyToOne(() => User)
|
@ManyToOne(() => User)
|
||||||
@JoinColumn({ name: 'created_by' })
|
@JoinColumn({ name: 'created_by' })
|
||||||
creator?: User;
|
creator?: User;
|
||||||
|
|||||||
193
backend/src/modules/rfa/rfa-workflow.service.ts
Normal file
193
backend/src/modules/rfa/rfa-workflow.service.ts
Normal 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}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,26 @@
|
|||||||
|
// File: src/modules/rfa/rfa.controller.ts
|
||||||
import {
|
import {
|
||||||
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
Post,
|
|
||||||
Body,
|
|
||||||
Param,
|
Param,
|
||||||
ParseIntPipe,
|
ParseIntPipe,
|
||||||
|
Post,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
import { RfaService } from './rfa.service';
|
import { WorkflowActionDto } from '../correspondence/dto/workflow-action.dto';
|
||||||
import { CreateRfaDto } from './dto/create-rfa.dto';
|
|
||||||
import { WorkflowActionDto } from '../correspondence/dto/workflow-action.dto'; // Reuse DTO
|
|
||||||
import { User } from '../user/entities/user.entity';
|
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 { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||||
import { RbacGuard } from '../../common/guards/rbac.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)')
|
@ApiTags('RFA (Request for Approval)')
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@@ -29,26 +31,28 @@ export class RfaController {
|
|||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@ApiOperation({ summary: 'Create new RFA (Draft)' })
|
@ApiOperation({ summary: 'Create new RFA (Draft)' })
|
||||||
@RequirePermission('rfa.create') // สิทธิ์ ID 37
|
@RequirePermission('rfa.create')
|
||||||
@Audit('rfa.create', 'rfa') // ✅ แปะตรงนี้
|
@Audit('rfa.create', 'rfa')
|
||||||
create(@Body() createDto: CreateRfaDto, @CurrentUser() user: User) {
|
create(@Body() createDto: CreateRfaDto, @CurrentUser() user: User) {
|
||||||
return this.rfaService.create(createDto, user);
|
return this.rfaService.create(createDto, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post(':id/submit')
|
@Post(':id/submit')
|
||||||
@ApiOperation({ summary: 'Submit RFA to Workflow' })
|
@ApiOperation({ summary: 'Submit RFA to Workflow' })
|
||||||
@RequirePermission('rfa.create') // ผู้สร้างมีสิทธิ์ส่ง
|
@RequirePermission('rfa.create')
|
||||||
|
@Audit('rfa.submit', 'rfa')
|
||||||
submit(
|
submit(
|
||||||
@Param('id', ParseIntPipe) id: number,
|
@Param('id', ParseIntPipe) id: number,
|
||||||
@Body('templateId', ParseIntPipe) templateId: number, // รับ Template ID
|
@Body() submitDto: SubmitRfaDto, // ✅ ใช้ DTO
|
||||||
@CurrentUser() user: User,
|
@CurrentUser() user: User,
|
||||||
) {
|
) {
|
||||||
return this.rfaService.submit(id, templateId, user);
|
return this.rfaService.submit(id, submitDto.templateId, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post(':id/action')
|
@Post(':id/action')
|
||||||
@ApiOperation({ summary: 'Process Workflow Action (Approve/Reject)' })
|
@ApiOperation({ summary: 'Process Workflow Action (Approve/Reject)' })
|
||||||
@RequirePermission('workflow.action_review') // สิทธิ์ในการ Approve/Review
|
@RequirePermission('workflow.action_review')
|
||||||
|
@Audit('rfa.action', 'rfa')
|
||||||
processAction(
|
processAction(
|
||||||
@Param('id', ParseIntPipe) id: number,
|
@Param('id', ParseIntPipe) id: number,
|
||||||
@Body() actionDto: WorkflowActionDto,
|
@Body() actionDto: WorkflowActionDto,
|
||||||
|
|||||||
@@ -3,35 +3,34 @@ import { Module } from '@nestjs/common';
|
|||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
// Entities
|
// 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 { CorrespondenceRouting } from '../correspondence/entities/correspondence-routing.entity';
|
||||||
|
import { Correspondence } from '../correspondence/entities/correspondence.entity';
|
||||||
import { RoutingTemplate } from '../correspondence/entities/routing-template.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
|
// Services & Controllers
|
||||||
import { RfaService } from './rfa.service';
|
import { RfaWorkflowService } from './rfa-workflow.service'; // Register Service
|
||||||
import { RfaController } from './rfa.controller';
|
import { RfaController } from './rfa.controller';
|
||||||
|
import { RfaService } from './rfa.service';
|
||||||
|
|
||||||
// External Modules
|
// External Modules
|
||||||
import { DocumentNumberingModule } from '../document-numbering/document-numbering.module';
|
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 { SearchModule } from '../search/search.module';
|
||||||
import { WorkflowEngineModule } from '../workflow-engine/workflow-engine.module'; // ✅ Import
|
import { UserModule } from '../user/user.module';
|
||||||
import { NotificationModule } from '../notification/notification.module'; // ✅ เพิ่ม NotificationModule
|
import { WorkflowEngineModule } from '../workflow-engine/workflow-engine.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
// 1. Register Entities (เฉพาะ Entity เท่านั้น ห้ามใส่ Module)
|
|
||||||
TypeOrmModule.forFeature([
|
TypeOrmModule.forFeature([
|
||||||
Rfa,
|
Rfa,
|
||||||
RfaRevision,
|
RfaRevision,
|
||||||
@@ -47,15 +46,13 @@ import { NotificationModule } from '../notification/notification.module'; // ✅
|
|||||||
CorrespondenceRouting,
|
CorrespondenceRouting,
|
||||||
RoutingTemplate,
|
RoutingTemplate,
|
||||||
]),
|
]),
|
||||||
|
|
||||||
// 2. Import External Modules (Services ที่ Inject เข้ามา)
|
|
||||||
DocumentNumberingModule,
|
DocumentNumberingModule,
|
||||||
UserModule,
|
UserModule,
|
||||||
SearchModule,
|
SearchModule,
|
||||||
WorkflowEngineModule, // ✅ ย้ายมาใส่ตรงนี้ (imports หลัก)
|
WorkflowEngineModule,
|
||||||
NotificationModule, // ✅ เพิ่มตรงนี้ เพื่อแก้ dependency index [13]
|
NotificationModule,
|
||||||
],
|
],
|
||||||
providers: [RfaService],
|
providers: [RfaService, RfaWorkflowService],
|
||||||
controllers: [RfaController],
|
controllers: [RfaController],
|
||||||
exports: [RfaService],
|
exports: [RfaService],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,42 +1,41 @@
|
|||||||
// File: src/modules/rfa/rfa.service.ts
|
// File: src/modules/rfa/rfa.service.ts
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Injectable,
|
|
||||||
NotFoundException,
|
|
||||||
InternalServerErrorException,
|
|
||||||
Logger,
|
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
ForbiddenException,
|
ForbiddenException,
|
||||||
|
Injectable,
|
||||||
|
InternalServerErrorException,
|
||||||
|
Logger,
|
||||||
|
NotFoundException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository, DataSource, In } from 'typeorm';
|
import { DataSource, In, Repository } from 'typeorm';
|
||||||
|
|
||||||
// Entities
|
// Entities
|
||||||
import { Rfa } from './entities/rfa.entity.js';
|
import { CorrespondenceRouting } from '../correspondence/entities/correspondence-routing.entity';
|
||||||
import { RfaRevision } from './entities/rfa-revision.entity.js';
|
import { Correspondence } from '../correspondence/entities/correspondence.entity';
|
||||||
import { RfaItem } from './entities/rfa-item.entity.js';
|
import { RoutingTemplate } from '../correspondence/entities/routing-template.entity';
|
||||||
import { RfaType } from './entities/rfa-type.entity.js';
|
import { ShopDrawingRevision } from '../drawing/entities/shop-drawing-revision.entity';
|
||||||
import { RfaStatusCode } from './entities/rfa-status-code.entity.js';
|
import { User } from '../user/entities/user.entity';
|
||||||
import { RfaApproveCode } from './entities/rfa-approve-code.entity.js';
|
import { RfaApproveCode } from './entities/rfa-approve-code.entity';
|
||||||
import { Correspondence } from '../correspondence/entities/correspondence.entity.js';
|
import { RfaItem } from './entities/rfa-item.entity';
|
||||||
import { CorrespondenceRouting } from '../correspondence/entities/correspondence-routing.entity.js';
|
import { RfaRevision } from './entities/rfa-revision.entity';
|
||||||
import { RoutingTemplate } from '../correspondence/entities/routing-template.entity.js';
|
import { RfaStatusCode } from './entities/rfa-status-code.entity';
|
||||||
import { ShopDrawingRevision } from '../drawing/entities/shop-drawing-revision.entity.js';
|
import { RfaType } from './entities/rfa-type.entity';
|
||||||
import { User } from '../user/entities/user.entity.js';
|
import { Rfa } from './entities/rfa.entity';
|
||||||
|
|
||||||
// DTOs
|
// DTOs
|
||||||
import { CreateRfaDto } from './dto/create-rfa.dto.js';
|
import { WorkflowActionDto } from '../correspondence/dto/workflow-action.dto';
|
||||||
import { WorkflowActionDto } from '../correspondence/dto/workflow-action.dto.js';
|
import { CreateRfaDto } from './dto/create-rfa.dto';
|
||||||
|
|
||||||
// Interfaces & Enums
|
// Interfaces & Enums
|
||||||
import { WorkflowAction } from '../workflow-engine/interfaces/workflow.interface.js';
|
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
import { DocumentNumberingService } from '../document-numbering/document-numbering.service.js';
|
import { DocumentNumberingService } from '../document-numbering/document-numbering.service';
|
||||||
import { UserService } from '../user/user.service.js';
|
import { NotificationService } from '../notification/notification.service';
|
||||||
import { WorkflowEngineService } from '../workflow-engine/workflow-engine.service.js';
|
import { SearchService } from '../search/search.service';
|
||||||
import { NotificationService } from '../notification/notification.service.js';
|
import { UserService } from '../user/user.service';
|
||||||
import { SearchService } from '../search/search.service.js';
|
import { WorkflowEngineService } from '../workflow-engine/workflow-engine.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RfaService {
|
export class RfaService {
|
||||||
@@ -87,6 +86,7 @@ export class RfaService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine User Organization
|
||||||
let userOrgId = user.primaryOrganizationId;
|
let userOrgId = user.primaryOrganizationId;
|
||||||
if (!userOrgId) {
|
if (!userOrgId) {
|
||||||
const fullUser = await this.userService.findOne(user.user_id);
|
const fullUser = await this.userService.findOne(user.user_id);
|
||||||
@@ -101,14 +101,14 @@ export class RfaService {
|
|||||||
await queryRunner.startTransaction();
|
await queryRunner.startTransaction();
|
||||||
|
|
||||||
try {
|
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({
|
const docNumber = await this.numberingService.generateNextNumber({
|
||||||
projectId: createDto.projectId,
|
projectId: createDto.projectId,
|
||||||
originatorId: userOrgId,
|
originatorId: userOrgId,
|
||||||
typeId: createDto.rfaTypeId, // RFA Type ใช้เป็น ID ในการนับเลข
|
typeId: createDto.rfaTypeId,
|
||||||
disciplineId: createDto.disciplineId, // สำคัญมากสำหรับ RFA (Req 6B)
|
disciplineId: createDto.disciplineId ?? 0, // ✅ ส่ง disciplineId ไปด้วย (0 ถ้าไม่มี)
|
||||||
year: new Date().getFullYear(),
|
year: new Date().getFullYear(),
|
||||||
customTokens: {
|
customTokens: {
|
||||||
TYPE_CODE: rfaType.typeCode,
|
TYPE_CODE: rfaType.typeCode,
|
||||||
@@ -116,24 +116,31 @@ export class RfaService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 1. Create Correspondence Record
|
||||||
const correspondence = queryRunner.manager.create(Correspondence, {
|
const correspondence = queryRunner.manager.create(Correspondence, {
|
||||||
correspondenceNumber: docNumber,
|
correspondenceNumber: docNumber,
|
||||||
correspondenceTypeId: createDto.rfaTypeId, // Map RFA Type to Corr Type ID
|
correspondenceTypeId: createDto.rfaTypeId, // Assuming RFA Type maps directly or via logic
|
||||||
disciplineId: createDto.disciplineId, // บันทึก Discipline
|
// Note: ถ้า CorrespondenceType แยก ID กับ RFA Type ต้อง Map ให้ถูก
|
||||||
|
// ในที่นี้สมมติว่าใช้ ID เดียวกัน หรือ RFA Type เป็น SubType ของ Correspondence
|
||||||
projectId: createDto.projectId,
|
projectId: createDto.projectId,
|
||||||
originatorId: userOrgId,
|
originatorId: userOrgId,
|
||||||
isInternal: false,
|
isInternalCommunication: false,
|
||||||
createdBy: user.user_id,
|
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);
|
const savedCorr = await queryRunner.manager.save(correspondence);
|
||||||
|
|
||||||
|
// 2. Create RFA Master Record
|
||||||
const rfa = queryRunner.manager.create(Rfa, {
|
const rfa = queryRunner.manager.create(Rfa, {
|
||||||
rfaTypeId: createDto.rfaTypeId,
|
rfaTypeId: createDto.rfaTypeId,
|
||||||
disciplineId: createDto.disciplineId, // บันทึก Discipline
|
|
||||||
createdBy: user.user_id,
|
createdBy: user.user_id,
|
||||||
|
// ✅ ถ้า Entity Rfa มี disciplineId ให้ใส่ตรงนี้ด้วย
|
||||||
|
// disciplineId: createDto.disciplineId
|
||||||
});
|
});
|
||||||
const savedRfa = await queryRunner.manager.save(rfa);
|
const savedRfa = await queryRunner.manager.save(rfa);
|
||||||
|
|
||||||
|
// 3. Create First Revision (Draft)
|
||||||
const rfaRevision = queryRunner.manager.create(RfaRevision, {
|
const rfaRevision = queryRunner.manager.create(RfaRevision, {
|
||||||
correspondenceId: savedCorr.id,
|
correspondenceId: savedCorr.id,
|
||||||
rfaId: savedRfa.id,
|
rfaId: savedRfa.id,
|
||||||
@@ -147,9 +154,12 @@ export class RfaService {
|
|||||||
? new Date(createDto.documentDate)
|
? new Date(createDto.documentDate)
|
||||||
: new Date(),
|
: new Date(),
|
||||||
createdBy: user.user_id,
|
createdBy: user.user_id,
|
||||||
|
details: createDto.details, // ✅ Save JSON Details
|
||||||
|
schemaVersion: 1, // ✅ Default Schema Version
|
||||||
});
|
});
|
||||||
const savedRevision = await queryRunner.manager.save(rfaRevision);
|
const savedRevision = await queryRunner.manager.save(rfaRevision);
|
||||||
|
|
||||||
|
// 4. Link Shop Drawings
|
||||||
if (
|
if (
|
||||||
createDto.shopDrawingRevisionIds &&
|
createDto.shopDrawingRevisionIds &&
|
||||||
createDto.shopDrawingRevisionIds.length > 0
|
createDto.shopDrawingRevisionIds.length > 0
|
||||||
@@ -164,7 +174,8 @@ export class RfaService {
|
|||||||
|
|
||||||
const rfaItems = shopDrawings.map((sd) =>
|
const rfaItems = shopDrawings.map((sd) =>
|
||||||
queryRunner.manager.create(RfaItem, {
|
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,
|
shopDrawingRevisionId: sd.id,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -173,9 +184,10 @@ export class RfaService {
|
|||||||
|
|
||||||
await queryRunner.commitTransaction();
|
await queryRunner.commitTransaction();
|
||||||
|
|
||||||
|
// Indexing for Search
|
||||||
this.searchService.indexDocument({
|
this.searchService.indexDocument({
|
||||||
id: savedCorr.id,
|
id: savedCorr.id,
|
||||||
type: 'correspondence',
|
type: 'rfa',
|
||||||
docNumber: docNumber,
|
docNumber: docNumber,
|
||||||
title: createDto.title,
|
title: createDto.title,
|
||||||
description: createDto.description,
|
description: createDto.description,
|
||||||
@@ -186,10 +198,8 @@ export class RfaService {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...savedRfa,
|
...savedRfa,
|
||||||
currentRevision: {
|
|
||||||
...savedRevision,
|
|
||||||
correspondenceNumber: docNumber,
|
correspondenceNumber: docNumber,
|
||||||
},
|
currentRevision: savedRevision,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await queryRunner.rollbackTransaction();
|
await queryRunner.rollbackTransaction();
|
||||||
@@ -200,7 +210,8 @@ export class RfaService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... (method อื่นๆ findOne, submit, processAction คงเดิม)
|
// ... (ส่วน findOne, submit, processAction คงเดิมจากไฟล์ที่แนบมา แค่ปรับปรุงเล็กน้อยตาม Context) ...
|
||||||
|
|
||||||
async findOne(id: number) {
|
async findOne(id: number) {
|
||||||
const rfa = await this.rfaRepo.findOne({
|
const rfa = await this.rfaRepo.findOne({
|
||||||
where: { id },
|
where: { id },
|
||||||
@@ -230,14 +241,8 @@ export class RfaService {
|
|||||||
const rfa = await this.findOne(rfaId);
|
const rfa = await this.findOne(rfaId);
|
||||||
const currentRevision = rfa.revisions.find((r) => r.isCurrent);
|
const currentRevision = rfa.revisions.find((r) => r.isCurrent);
|
||||||
|
|
||||||
if (!currentRevision) {
|
if (!currentRevision)
|
||||||
throw new NotFoundException('Current revision not found');
|
throw new NotFoundException('Current revision not found');
|
||||||
}
|
|
||||||
|
|
||||||
if (!currentRevision.correspondence) {
|
|
||||||
throw new InternalServerErrorException('Correspondence relation missing');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentRevision.statusCode.statusCode !== 'DFT') {
|
if (currentRevision.statusCode.statusCode !== 'DFT') {
|
||||||
throw new BadRequestException('Only DRAFT documents can be submitted');
|
throw new BadRequestException('Only DRAFT documents can be submitted');
|
||||||
}
|
}
|
||||||
@@ -255,21 +260,21 @@ export class RfaService {
|
|||||||
const statusForApprove = await this.rfaStatusRepo.findOne({
|
const statusForApprove = await this.rfaStatusRepo.findOne({
|
||||||
where: { statusCode: 'FAP' },
|
where: { statusCode: 'FAP' },
|
||||||
});
|
});
|
||||||
if (!statusForApprove) {
|
if (!statusForApprove)
|
||||||
throw new InternalServerErrorException('Status FAP not found');
|
throw new InternalServerErrorException('Status FAP not found');
|
||||||
}
|
|
||||||
|
|
||||||
const queryRunner = this.dataSource.createQueryRunner();
|
const queryRunner = this.dataSource.createQueryRunner();
|
||||||
await queryRunner.connect();
|
await queryRunner.connect();
|
||||||
await queryRunner.startTransaction();
|
await queryRunner.startTransaction();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Update Revision Status
|
||||||
currentRevision.rfaStatusCodeId = statusForApprove.id;
|
currentRevision.rfaStatusCodeId = statusForApprove.id;
|
||||||
currentRevision.issuedDate = new Date();
|
currentRevision.issuedDate = new Date();
|
||||||
await queryRunner.manager.save(currentRevision);
|
await queryRunner.manager.save(currentRevision);
|
||||||
|
|
||||||
|
// Create First Routing Step
|
||||||
const firstStep = template.steps[0];
|
const firstStep = template.steps[0];
|
||||||
|
|
||||||
const routing = queryRunner.manager.create(CorrespondenceRouting, {
|
const routing = queryRunner.manager.create(CorrespondenceRouting, {
|
||||||
correspondenceId: currentRevision.correspondenceId,
|
correspondenceId: currentRevision.correspondenceId,
|
||||||
templateId: template.id,
|
templateId: template.id,
|
||||||
@@ -286,20 +291,18 @@ export class RfaService {
|
|||||||
});
|
});
|
||||||
await queryRunner.manager.save(routing);
|
await queryRunner.manager.save(routing);
|
||||||
|
|
||||||
|
// Notify
|
||||||
const recipientUserId = await this.userService.findDocControlIdByOrg(
|
const recipientUserId = await this.userService.findDocControlIdByOrg(
|
||||||
firstStep.toOrganizationId,
|
firstStep.toOrganizationId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (recipientUserId) {
|
if (recipientUserId) {
|
||||||
const docNo = currentRevision.correspondence.correspondenceNumber;
|
|
||||||
await this.notificationService.send({
|
await this.notificationService.send({
|
||||||
userId: recipientUserId,
|
userId: recipientUserId,
|
||||||
title: `RFA Submitted: ${currentRevision.title}`,
|
title: `RFA Submitted: ${currentRevision.title}`,
|
||||||
message: `มีเอกสาร RFA ใหม่รอการตรวจสอบจากคุณ (เลขที่: ${docNo})`,
|
message: `RFA ${currentRevision.correspondence.correspondenceNumber} submitted for approval.`,
|
||||||
type: 'SYSTEM',
|
type: 'SYSTEM',
|
||||||
entityType: 'rfa',
|
entityType: 'rfa',
|
||||||
entityId: rfa.id,
|
entityId: rfa.id,
|
||||||
link: `/rfas/${rfa.id}`,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,7 +310,6 @@ export class RfaService {
|
|||||||
return { message: 'RFA Submitted successfully', routing };
|
return { message: 'RFA Submitted successfully', routing };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await queryRunner.rollbackTransaction();
|
await queryRunner.rollbackTransaction();
|
||||||
this.logger.error(`Failed to submit RFA: ${(err as Error).message}`);
|
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
await queryRunner.release();
|
await queryRunner.release();
|
||||||
@@ -315,11 +317,13 @@ export class RfaService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async processAction(rfaId: number, dto: WorkflowActionDto, user: User) {
|
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 rfa = await this.findOne(rfaId);
|
||||||
const currentRevision = rfa.revisions.find((r) => r.isCurrent);
|
const currentRevision = rfa.revisions.find((r) => r.isCurrent);
|
||||||
if (!currentRevision) {
|
if (!currentRevision)
|
||||||
throw new NotFoundException('Current revision not found');
|
throw new NotFoundException('Current revision not found');
|
||||||
}
|
|
||||||
|
|
||||||
const currentRouting = await this.routingRepo.findOne({
|
const currentRouting = await this.routingRepo.findOne({
|
||||||
where: {
|
where: {
|
||||||
@@ -330,10 +334,8 @@ export class RfaService {
|
|||||||
relations: ['toOrganization'],
|
relations: ['toOrganization'],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!currentRouting) {
|
if (!currentRouting)
|
||||||
throw new BadRequestException('No active workflow step found');
|
throw new BadRequestException('No active workflow step found');
|
||||||
}
|
|
||||||
|
|
||||||
if (currentRouting.toOrganizationId !== user.primaryOrganizationId) {
|
if (currentRouting.toOrganizationId !== user.primaryOrganizationId) {
|
||||||
throw new ForbiddenException(
|
throw new ForbiddenException(
|
||||||
'You are not authorized to process this step',
|
'You are not authorized to process this step',
|
||||||
@@ -345,10 +347,10 @@ export class RfaService {
|
|||||||
relations: ['steps'],
|
relations: ['steps'],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!template || !template.steps) {
|
if (!template || !template.steps)
|
||||||
throw new InternalServerErrorException('Template or steps not found');
|
throw new InternalServerErrorException('Template not found');
|
||||||
}
|
|
||||||
|
|
||||||
|
// Call Engine to calculate next step
|
||||||
const result = this.workflowEngine.processAction(
|
const result = this.workflowEngine.processAction(
|
||||||
currentRouting.sequence,
|
currentRouting.sequence,
|
||||||
template.steps.length,
|
template.steps.length,
|
||||||
@@ -361,19 +363,19 @@ export class RfaService {
|
|||||||
await queryRunner.startTransaction();
|
await queryRunner.startTransaction();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
currentRouting.status =
|
// Update current routing
|
||||||
dto.action === WorkflowAction.REJECT ? 'REJECTED' : 'ACTIONED';
|
currentRouting.status = dto.action === 'REJECT' ? 'REJECTED' : 'ACTIONED';
|
||||||
currentRouting.processedByUserId = user.user_id;
|
currentRouting.processedByUserId = user.user_id;
|
||||||
currentRouting.processedAt = new Date();
|
currentRouting.processedAt = new Date();
|
||||||
currentRouting.comments = dto.comments;
|
currentRouting.comments = dto.comments;
|
||||||
await queryRunner.manager.save(currentRouting);
|
await queryRunner.manager.save(currentRouting);
|
||||||
|
|
||||||
if (result.nextStepSequence && dto.action !== WorkflowAction.REJECT) {
|
// Create next routing if available
|
||||||
const nextStepConfig = template.steps.find(
|
if (result.nextStepSequence && dto.action !== 'REJECT') {
|
||||||
|
const nextStep = template.steps.find(
|
||||||
(s) => s.sequence === result.nextStepSequence,
|
(s) => s.sequence === result.nextStepSequence,
|
||||||
);
|
);
|
||||||
|
if (nextStep) {
|
||||||
if (nextStepConfig) {
|
|
||||||
const nextRouting = queryRunner.manager.create(
|
const nextRouting = queryRunner.manager.create(
|
||||||
CorrespondenceRouting,
|
CorrespondenceRouting,
|
||||||
{
|
{
|
||||||
@@ -381,49 +383,40 @@ export class RfaService {
|
|||||||
templateId: template.id,
|
templateId: template.id,
|
||||||
sequence: result.nextStepSequence,
|
sequence: result.nextStepSequence,
|
||||||
fromOrganizationId: user.primaryOrganizationId,
|
fromOrganizationId: user.primaryOrganizationId,
|
||||||
toOrganizationId: nextStepConfig.toOrganizationId,
|
toOrganizationId: nextStep.toOrganizationId,
|
||||||
stepPurpose: nextStepConfig.stepPurpose,
|
stepPurpose: nextStep.stepPurpose,
|
||||||
status: 'SENT',
|
status: 'SENT',
|
||||||
dueDate: new Date(
|
dueDate: new Date(
|
||||||
Date.now() +
|
Date.now() + (nextStep.expectedDays || 7) * 24 * 60 * 60 * 1000,
|
||||||
(nextStepConfig.expectedDays || 7) * 24 * 60 * 60 * 1000,
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
await queryRunner.manager.save(nextRouting);
|
await queryRunner.manager.save(nextRouting);
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (result.nextStepSequence === null) {
|
||||||
result.nextStepSequence === null &&
|
// Workflow Ended (Completed or Rejected)
|
||||||
dto.action !== WorkflowAction.REJECT
|
// Update RFA Status (Approved/Rejected Code)
|
||||||
) {
|
if (dto.action !== 'REJECT') {
|
||||||
const approveCodeStr =
|
|
||||||
dto.action === WorkflowAction.APPROVE ? '1A' : '4X';
|
|
||||||
const approveCode = await this.rfaApproveRepo.findOne({
|
const approveCode = await this.rfaApproveRepo.findOne({
|
||||||
where: { approveCode: approveCodeStr },
|
where: { approveCode: dto.action === 'APPROVE' ? '1A' : '4X' },
|
||||||
});
|
}); // Logic Map Code อย่างง่าย
|
||||||
|
|
||||||
if (approveCode) {
|
if (approveCode) {
|
||||||
currentRevision.rfaApproveCodeId = approveCode.id;
|
currentRevision.rfaApproveCodeId = approveCode.id;
|
||||||
currentRevision.approvedDate = new Date();
|
currentRevision.approvedDate = new Date();
|
||||||
}
|
}
|
||||||
await queryRunner.manager.save(currentRevision);
|
} else {
|
||||||
} else if (dto.action === WorkflowAction.REJECT) {
|
|
||||||
const rejectCode = await this.rfaApproveRepo.findOne({
|
const rejectCode = await this.rfaApproveRepo.findOne({
|
||||||
where: { approveCode: '4X' },
|
where: { approveCode: '4X' },
|
||||||
});
|
});
|
||||||
if (rejectCode) {
|
if (rejectCode) currentRevision.rfaApproveCodeId = rejectCode.id;
|
||||||
currentRevision.rfaApproveCodeId = rejectCode.id;
|
|
||||||
}
|
}
|
||||||
await queryRunner.manager.save(currentRevision);
|
await queryRunner.manager.save(currentRevision);
|
||||||
}
|
}
|
||||||
|
|
||||||
await queryRunner.commitTransaction();
|
await queryRunner.commitTransaction();
|
||||||
return { message: 'Action processed successfully', result };
|
return { message: 'Action processed', result };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await queryRunner.rollbackTransaction();
|
await queryRunner.rollbackTransaction();
|
||||||
this.logger.error(
|
|
||||||
`Failed to process RFA action: ${(err as Error).message}`,
|
|
||||||
);
|
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
await queryRunner.release();
|
await queryRunner.release();
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
}
|
||||||
@@ -1,37 +1,58 @@
|
|||||||
// File: src/modules/workflow-engine/entities/workflow-definition.entity.ts
|
// File: src/modules/workflow-engine/entities/workflow-definition.entity.ts
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Entity,
|
|
||||||
Column,
|
Column,
|
||||||
PrimaryGeneratedColumn,
|
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
UpdateDateColumn,
|
Entity,
|
||||||
Index,
|
Index,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Unique,
|
||||||
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* เก็บแม่แบบ (Blueprint) ของ Workflow
|
||||||
|
* 1 Workflow Code (เช่น RFA) สามารถมีได้หลาย Version
|
||||||
|
*/
|
||||||
@Entity('workflow_definitions')
|
@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 {
|
export class WorkflowDefinition {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id!: string; // เพิ่ม !
|
id!: string;
|
||||||
|
|
||||||
@Column({ length: 50, comment: 'รหัส Workflow เช่น RFA, CORR' })
|
@Column({ length: 50, comment: 'รหัส Workflow เช่น RFA, CORR, LEAVE_REQ' })
|
||||||
workflow_code!: string; // เพิ่ม !
|
workflow_code!: string;
|
||||||
|
|
||||||
@Column({ type: 'int', default: 1, comment: 'หมายเลข Version' })
|
@Column({
|
||||||
version!: number; // เพิ่ม !
|
type: 'int',
|
||||||
|
default: 1,
|
||||||
|
comment: 'หมายเลข Version (Running sequence)',
|
||||||
|
})
|
||||||
|
version!: number;
|
||||||
|
|
||||||
@Column({ type: 'json', comment: 'นิยาม Workflow ต้นฉบับ' })
|
@Column({ type: 'text', nullable: true, comment: 'คำอธิบายเพิ่มเติม' })
|
||||||
dsl!: any; // เพิ่ม !
|
description?: string;
|
||||||
|
|
||||||
@Column({ type: 'json', comment: 'โครงสร้างที่ Compile แล้ว' })
|
@Column({
|
||||||
compiled!: any; // เพิ่ม !
|
type: 'json',
|
||||||
|
comment: 'Raw DSL ที่ User/Admin เขียน (เก็บไว้เพื่อดูหรือแก้ไข)',
|
||||||
|
})
|
||||||
|
dsl!: any; // ควรตรงกับ RawWorkflowDSL interface
|
||||||
|
|
||||||
@Column({ default: true, comment: 'สถานะการใช้งาน' })
|
@Column({
|
||||||
is_active!: boolean; // เพิ่ม !
|
type: 'json',
|
||||||
|
comment:
|
||||||
|
'Compiled JSON Structure ที่ผ่านการ Validate และ Optimize สำหรับ Runtime Engine แล้ว',
|
||||||
|
})
|
||||||
|
compiled!: any; // ควรตรงกับ CompiledWorkflow interface
|
||||||
|
|
||||||
@CreateDateColumn()
|
@Column({ default: true, comment: 'สถานะการใช้งาน (Soft Disable)' })
|
||||||
created_at!: Date; // เพิ่ม !
|
is_active!: boolean;
|
||||||
|
|
||||||
@UpdateDateColumn()
|
@CreateDateColumn({ name: 'created_at' })
|
||||||
updated_at!: Date; // เพิ่ม !
|
created_at!: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at' })
|
||||||
|
updated_at!: Date;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,23 @@
|
|||||||
// File: src/modules/workflow-engine/entities/workflow-history.entity.ts
|
// File: src/modules/workflow-engine/entities/workflow-history.entity.ts
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
Entity,
|
Entity,
|
||||||
|
Index,
|
||||||
JoinColumn,
|
JoinColumn,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { WorkflowInstance } from './workflow-instance.entity';
|
import { WorkflowInstance } from './workflow-instance.entity';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* เก็บประวัติการเปลี่ยนสถานะ (Audit Trail)
|
||||||
|
* สำคัญมากสำหรับการตรวจสอบย้อนหลัง (Who did What, When)
|
||||||
|
*/
|
||||||
@Entity('workflow_histories')
|
@Entity('workflow_histories')
|
||||||
|
@Index(['instanceId']) // ค้นหาประวัติของ Instance นี้
|
||||||
|
@Index(['actionByUserId']) // ค้นหาว่า User คนนี้ทำอะไรไปบ้าง
|
||||||
export class WorkflowHistory {
|
export class WorkflowHistory {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id!: string;
|
id!: string;
|
||||||
@@ -21,23 +29,32 @@ export class WorkflowHistory {
|
|||||||
@Column({ name: 'instance_id' })
|
@Column({ name: 'instance_id' })
|
||||||
instanceId!: string;
|
instanceId!: string;
|
||||||
|
|
||||||
@Column({ name: 'from_state', length: 50 })
|
@Column({ name: 'from_state', length: 50, comment: 'สถานะต้นทาง' })
|
||||||
fromState!: string;
|
fromState!: string;
|
||||||
|
|
||||||
@Column({ name: 'to_state', length: 50 })
|
@Column({ name: 'to_state', length: 50, comment: 'สถานะปลายทาง' })
|
||||||
toState!: string;
|
toState!: string;
|
||||||
|
|
||||||
@Column({ length: 50 })
|
@Column({ length: 50, comment: 'Action ที่ User กด (เช่น APPROVE, REJECT)' })
|
||||||
action!: string;
|
action!: string;
|
||||||
|
|
||||||
@Column({ name: 'action_by_user_id', nullable: true })
|
@Column({
|
||||||
actionByUserId?: number; // User ID ของผู้ดำเนินการ
|
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;
|
comment?: string;
|
||||||
|
|
||||||
@Column({ type: 'json', nullable: true })
|
// Snapshot ข้อมูล ณ เวลาที่เปลี่ยนสถานะ เพื่อเป็นหลักฐานหาก Context เปลี่ยนในอนาคต
|
||||||
metadata?: Record<string, any>; // เก็บข้อมูลเพิ่มเติม เช่น Snapshot ของ Context ณ ตอนนั้น
|
@Column({
|
||||||
|
type: 'json',
|
||||||
|
nullable: true,
|
||||||
|
comment: 'Snapshot of Context or Metadata',
|
||||||
|
})
|
||||||
|
metadata?: Record<string, any>;
|
||||||
|
|
||||||
@CreateDateColumn({ name: 'created_at' })
|
@CreateDateColumn({ name: 'created_at' })
|
||||||
createdAt!: Date;
|
createdAt!: Date;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// File: src/modules/workflow-engine/entities/workflow-instance.entity.ts
|
// File: src/modules/workflow-engine/entities/workflow-instance.entity.ts
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
@@ -12,20 +13,23 @@ import {
|
|||||||
import { WorkflowDefinition } from './workflow-definition.entity';
|
import { WorkflowDefinition } from './workflow-definition.entity';
|
||||||
|
|
||||||
export enum WorkflowStatus {
|
export enum WorkflowStatus {
|
||||||
ACTIVE = 'ACTIVE',
|
ACTIVE = 'ACTIVE', // กำลังดำเนินการ
|
||||||
COMPLETED = 'COMPLETED',
|
COMPLETED = 'COMPLETED', // จบกระบวนการ (ถึง Terminal State)
|
||||||
CANCELLED = 'CANCELLED',
|
CANCELLED = 'CANCELLED', // ถูกยกเลิกกลางคัน
|
||||||
TERMINATED = 'TERMINATED',
|
TERMINATED = 'TERMINATED', // ถูกบังคับจบโดยระบบ หรือ Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* เก็บสถานะการเดินเรื่องของเอกสารแต่ละใบ (Runtime State)
|
||||||
|
*/
|
||||||
@Entity('workflow_instances')
|
@Entity('workflow_instances')
|
||||||
@Index(['entityType', 'entityId']) // Index สำหรับค้นหาตามเอกสาร
|
@Index(['entityType', 'entityId']) // เพื่อค้นหาว่าเอกสารนี้ (เช่น RFA-001) อยู่ขั้นตอนไหน
|
||||||
@Index(['currentState']) // Index สำหรับ Filter ตามสถานะ
|
@Index(['currentState']) // เพื่อ Dashboard: "มีงานค้างที่ขั้นตอนไหนบ้าง"
|
||||||
export class WorkflowInstance {
|
export class WorkflowInstance {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id!: string;
|
id!: string;
|
||||||
|
|
||||||
// เชื่อมโยงกับ Definition ที่ใช้ตอนสร้าง Instance นี้
|
// ผูกกับ Definition เพื่อรู้ว่าใช้กฎชุดไหน (Version ไหน)
|
||||||
@ManyToOne(() => WorkflowDefinition)
|
@ManyToOne(() => WorkflowDefinition)
|
||||||
@JoinColumn({ name: 'definition_id' })
|
@JoinColumn({ name: 'definition_id' })
|
||||||
definition!: WorkflowDefinition;
|
definition!: WorkflowDefinition;
|
||||||
@@ -33,25 +37,39 @@ export class WorkflowInstance {
|
|||||||
@Column({ name: 'definition_id' })
|
@Column({ name: 'definition_id' })
|
||||||
definitionId!: string;
|
definitionId!: string;
|
||||||
|
|
||||||
// Polymorphic Relation: เชื่อมกับเอกสารได้หลายประเภท (RFA, CORR, etc.)
|
// Polymorphic Relation: เชื่อมกับเอกสารได้หลายประเภท (RFA, CORR, etc.) โดยไม่ต้อง Foreign Key จริง
|
||||||
@Column({ name: 'entity_type', length: 50 })
|
@Column({
|
||||||
|
name: 'entity_type',
|
||||||
|
length: 50,
|
||||||
|
comment: 'ประเภทเอกสาร เช่น rfa, correspondence',
|
||||||
|
})
|
||||||
entityType!: string;
|
entityType!: string;
|
||||||
|
|
||||||
@Column({ name: 'entity_id', length: 50 })
|
@Column({
|
||||||
entityId!: string; // รองรับทั้ง ID แบบ Int และ UUID (เก็บเป็น String)
|
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;
|
currentState!: string;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
type: 'enum',
|
type: 'enum',
|
||||||
enum: WorkflowStatus,
|
enum: WorkflowStatus,
|
||||||
default: WorkflowStatus.ACTIVE,
|
default: WorkflowStatus.ACTIVE,
|
||||||
|
comment: 'สถานะภาพรวมของ Instance',
|
||||||
})
|
})
|
||||||
status!: WorkflowStatus;
|
status!: WorkflowStatus;
|
||||||
|
|
||||||
// Context เฉพาะของ Instance นี้ (เช่น ตัวแปรที่ส่งต่อระหว่าง State)
|
// Context: เก็บตัวแปรที่จำเป็นสำหรับการตัดสินใจใน Workflow
|
||||||
@Column({ type: 'json', nullable: true })
|
// เช่น { "amount": 500000, "requester_role": "ENGINEER", "approver_ids": [1, 2] }
|
||||||
|
@Column({ type: 'json', nullable: true, comment: 'Runtime Context Data' })
|
||||||
context?: Record<string, any>;
|
context?: Record<string, any>;
|
||||||
|
|
||||||
@CreateDateColumn({ name: 'created_at' })
|
@CreateDateColumn({ name: 'created_at' })
|
||||||
|
|||||||
@@ -1,181 +1,255 @@
|
|||||||
// File: src/modules/workflow-engine/workflow-dsl.service.ts
|
// 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;
|
initial?: boolean;
|
||||||
terminal?: boolean;
|
terminal?: boolean;
|
||||||
transitions?: Record<string, TransitionRule>;
|
on?: Record<string, RawTransition>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TransitionRule {
|
export interface RawTransition {
|
||||||
to: string;
|
to: string;
|
||||||
requirements?: RequirementRule[];
|
require?: {
|
||||||
events?: EventRule[];
|
role?: string | string[];
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequirementRule {
|
|
||||||
role?: string;
|
|
||||||
user?: string;
|
user?: string;
|
||||||
condition?: string;
|
};
|
||||||
|
condition?: string; // JavaScript Expression string
|
||||||
|
events?: RawEvent[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EventRule {
|
export interface RawEvent {
|
||||||
type: 'notify' | 'webhook' | 'update_status';
|
type: 'notify' | 'webhook' | 'assign' | 'auto_action';
|
||||||
target?: string;
|
target?: string;
|
||||||
|
template?: string;
|
||||||
payload?: any;
|
payload?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// 2. Interfaces for COMPILED Schema (Optimized for Runtime)
|
||||||
|
// ==========================================
|
||||||
export interface CompiledWorkflow {
|
export interface CompiledWorkflow {
|
||||||
workflow: string;
|
workflow: string;
|
||||||
version: string | number;
|
version: number;
|
||||||
states: Record<string, WorkflowState>;
|
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()
|
@Injectable()
|
||||||
export class WorkflowDslService {
|
export class WorkflowDslService {
|
||||||
/**
|
private readonly logger = new Logger(WorkflowDslService.name);
|
||||||
* คอมไพล์ DSL Input ให้เป็น Standard Execution Tree
|
|
||||||
*/
|
|
||||||
compile(dsl: any): CompiledWorkflow {
|
|
||||||
if (!dsl || typeof dsl !== 'object') {
|
|
||||||
throw new BadRequestException('DSL must be a valid JSON object.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dsl.states || !Array.isArray(dsl.states)) {
|
/**
|
||||||
throw new BadRequestException(
|
* [Compile Time]
|
||||||
'DSL syntax error: "states" array is required.',
|
* แปลง Raw DSL เป็น Compiled Structure พร้อม Validation
|
||||||
);
|
*/
|
||||||
}
|
compile(dsl: RawWorkflowDSL): CompiledWorkflow {
|
||||||
|
this.validateSchemaStructure(dsl);
|
||||||
|
|
||||||
const compiled: CompiledWorkflow = {
|
const compiled: CompiledWorkflow = {
|
||||||
workflow: dsl.workflow || 'UNKNOWN',
|
workflow: dsl.workflow,
|
||||||
version: dsl.version || 1,
|
version: dsl.version || 1,
|
||||||
|
initialState: '',
|
||||||
states: {},
|
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) {
|
for (const rawState of dsl.states) {
|
||||||
if (!rawState.name) {
|
if (rawState.initial) {
|
||||||
|
if (initialFound) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
'DSL syntax error: All states must have a "name".',
|
`DSL Error: Multiple initial states found (at "${rawState.name}").`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
compiled.initialState = rawState.name;
|
||||||
|
initialFound = true;
|
||||||
|
}
|
||||||
|
|
||||||
stateMap.add(rawState.name);
|
const compiledState: CompiledState = {
|
||||||
|
|
||||||
const normalizedState: WorkflowState = {
|
|
||||||
initial: !!rawState.initial,
|
|
||||||
terminal: !!rawState.terminal,
|
terminal: !!rawState.terminal,
|
||||||
transitions: {},
|
transitions: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 2. Process Transitions
|
||||||
if (rawState.on) {
|
if (rawState.on) {
|
||||||
for (const [action, rule] of Object.entries(rawState.on)) {
|
for (const [action, rule] of Object.entries(rawState.on)) {
|
||||||
const rawRule = rule as any;
|
// Validation: Target state must exist
|
||||||
normalizedState.transitions![action] = {
|
if (!definedStates.has(rule.to)) {
|
||||||
to: rawRule.to,
|
throw new BadRequestException(
|
||||||
requirements: rawRule.require || [],
|
`DSL Error: State "${rawState.name}" transitions via "${action}" to unknown state "${rule.to}".`,
|
||||||
events: rawRule.events || [],
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
return compiled;
|
||||||
}
|
}
|
||||||
|
|
||||||
private validateIntegrity(compiled: CompiledWorkflow, stateMap: Set<string>) {
|
/**
|
||||||
let hasInitial = false;
|
* [Runtime]
|
||||||
|
* ประมวลผล Action และคืนค่า State ถัดไป
|
||||||
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.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
evaluate(
|
evaluate(
|
||||||
compiled: CompiledWorkflow,
|
compiled: CompiledWorkflow,
|
||||||
currentState: string,
|
currentState: string,
|
||||||
action: string,
|
action: string,
|
||||||
context: any = {}, // Default empty object
|
context: any = {},
|
||||||
): { nextState: string; events: EventRule[] } {
|
): { nextState: string; events: RawEvent[] } {
|
||||||
const stateConfig = compiled.states[currentState];
|
const stateConfig = compiled.states[currentState];
|
||||||
|
|
||||||
|
// 1. Validate State Existence
|
||||||
if (!stateConfig) {
|
if (!stateConfig) {
|
||||||
throw new BadRequestException(
|
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) {
|
if (stateConfig.terminal) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
`Runtime Error: Cannot transition from terminal state "${currentState}".`,
|
`Runtime Error: Cannot transition from terminal state "${currentState}".`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const transition = stateConfig.transitions?.[action];
|
// 3. Find Transition
|
||||||
|
const transition = stateConfig.transitions[action];
|
||||||
if (!transition) {
|
if (!transition) {
|
||||||
|
const allowed = Object.keys(stateConfig.transitions).join(', ');
|
||||||
throw new BadRequestException(
|
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) {
|
// 4. Validate Requirements (RBAC)
|
||||||
this.checkRequirements(transition.requirements, context);
|
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 {
|
return {
|
||||||
nextState: transition.to,
|
nextState: transition.to,
|
||||||
events: transition.events || [],
|
events: transition.events,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private checkRequirements(requirements: RequirementRule[], context: any) {
|
// --------------------------------------------------------
|
||||||
const safeContext = context || {};
|
// Private Helpers
|
||||||
const userRoles = safeContext.roles || [];
|
// --------------------------------------------------------
|
||||||
const userId = safeContext.userId;
|
|
||||||
|
|
||||||
const isAllowed = requirements.some((req) => {
|
private validateSchemaStructure(dsl: any) {
|
||||||
if (req.role) {
|
if (!dsl || typeof dsl !== 'object') {
|
||||||
return userRoles.includes(req.role);
|
throw new BadRequestException('DSL must be a JSON object.');
|
||||||
}
|
}
|
||||||
if (req.user) {
|
if (!dsl.workflow || !dsl.states || !Array.isArray(dsl.states)) {
|
||||||
return userId === req.user;
|
|
||||||
}
|
|
||||||
// Future: Add Condition Logic Evaluation here
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isAllowed) {
|
|
||||||
throw new BadRequestException(
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,58 +5,103 @@ import {
|
|||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
Param,
|
Param,
|
||||||
ParseUUIDPipe,
|
|
||||||
Patch,
|
Patch,
|
||||||
Post,
|
Post,
|
||||||
Query,
|
Request,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
import {
|
||||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
ApiBearerAuth,
|
||||||
import { CreateWorkflowDefinitionDto } from './dto/create-workflow-definition.dto';
|
ApiOperation,
|
||||||
import { EvaluateWorkflowDto } from './dto/evaluate-workflow.dto';
|
ApiParam,
|
||||||
import { GetAvailableActionsDto } from './dto/get-available-actions.dto';
|
ApiResponse,
|
||||||
import { UpdateWorkflowDefinitionDto } from './dto/update-workflow-definition.dto';
|
ApiTags,
|
||||||
|
} from '@nestjs/swagger';
|
||||||
|
|
||||||
|
// Services
|
||||||
import { WorkflowEngineService } from './workflow-engine.service';
|
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')
|
@Controller('workflow-engine')
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard, RbacGuard) // บังคับ Login และตรวจสอบสิทธิ์ทุก Request
|
||||||
export class WorkflowEngineController {
|
export class WorkflowEngineController {
|
||||||
constructor(private readonly workflowService: WorkflowEngineService) {}
|
constructor(private readonly workflowService: WorkflowEngineService) {}
|
||||||
|
|
||||||
|
// =================================================================
|
||||||
|
// Definition Management (Admin / Developer)
|
||||||
|
// =================================================================
|
||||||
|
|
||||||
@Post('definitions')
|
@Post('definitions')
|
||||||
@ApiOperation({ summary: 'Create or Update Workflow Definition (DSL)' })
|
@ApiOperation({ summary: 'สร้าง Workflow Definition ใหม่ (Auto Versioning)' })
|
||||||
@ApiResponse({ status: 201, description: 'Workflow compiled and saved.' })
|
@ApiResponse({ status: 201, description: 'Created successfully' })
|
||||||
|
// ใช้ Permission 'system.manage_all' (Admin) หรือสร้าง permission ใหม่ 'workflow.manage' ในอนาคต
|
||||||
|
@RequirePermission('system.manage_all')
|
||||||
async createDefinition(@Body() dto: CreateWorkflowDefinitionDto) {
|
async createDefinition(@Body() dto: CreateWorkflowDefinitionDto) {
|
||||||
return this.workflowService.createDefinition(dto);
|
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')
|
@Patch('definitions/:id')
|
||||||
@ApiOperation({
|
@ApiOperation({ summary: 'แก้ไข Workflow Definition (Re-compile DSL)' })
|
||||||
summary: 'Update workflow status or details (DSL Re-compile)',
|
@RequirePermission('system.manage_all')
|
||||||
})
|
|
||||||
async updateDefinition(
|
async updateDefinition(
|
||||||
@Param('id', ParseUUIDPipe) id: string, // เพิ่ม ParseUUIDPipe เพื่อ Validate ID
|
@Param('id') id: string,
|
||||||
@Body() dto: UpdateWorkflowDefinitionDto,
|
@Body() dto: UpdateWorkflowDefinitionDto,
|
||||||
) {
|
) {
|
||||||
return this.workflowService.update(id, dto);
|
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' };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,23 +2,31 @@
|
|||||||
|
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
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({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([
|
TypeOrmModule.forFeature([
|
||||||
WorkflowDefinition,
|
WorkflowDefinition,
|
||||||
WorkflowInstance, // [New]
|
WorkflowInstance,
|
||||||
WorkflowHistory, // [New]
|
WorkflowHistory,
|
||||||
]),
|
]),
|
||||||
|
UserModule,
|
||||||
],
|
],
|
||||||
controllers: [WorkflowEngineController],
|
controllers: [WorkflowEngineController],
|
||||||
providers: [WorkflowEngineService, WorkflowDslService],
|
providers: [WorkflowEngineService, WorkflowDslService, WorkflowEventService],
|
||||||
exports: [WorkflowEngineService],
|
exports: [WorkflowEngineService], // Export Service ให้ Module อื่น (Correspondence, RFA) เรียกใช้
|
||||||
})
|
})
|
||||||
export class WorkflowEngineModule {}
|
export class WorkflowEngineModule {}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { CreateWorkflowDefinitionDto } from './dto/create-workflow-definition.dt
|
|||||||
import { EvaluateWorkflowDto } from './dto/evaluate-workflow.dto';
|
import { EvaluateWorkflowDto } from './dto/evaluate-workflow.dto';
|
||||||
import { UpdateWorkflowDefinitionDto } from './dto/update-workflow-definition.dto';
|
import { UpdateWorkflowDefinitionDto } from './dto/update-workflow-definition.dto';
|
||||||
import { CompiledWorkflow, WorkflowDslService } from './workflow-dsl.service';
|
import { CompiledWorkflow, WorkflowDslService } from './workflow-dsl.service';
|
||||||
|
import { WorkflowEventService } from './workflow-event.service'; // [NEW] Import Event Service
|
||||||
|
|
||||||
// Legacy Interface (Backward Compatibility)
|
// Legacy Interface (Backward Compatibility)
|
||||||
export enum WorkflowAction {
|
export enum WorkflowAction {
|
||||||
@@ -49,6 +50,7 @@ export class WorkflowEngineService {
|
|||||||
@InjectRepository(WorkflowHistory)
|
@InjectRepository(WorkflowHistory)
|
||||||
private readonly historyRepo: Repository<WorkflowHistory>,
|
private readonly historyRepo: Repository<WorkflowHistory>,
|
||||||
private readonly dslService: WorkflowDslService,
|
private readonly dslService: WorkflowDslService,
|
||||||
|
private readonly eventService: WorkflowEventService, // [NEW] Inject Service
|
||||||
private readonly dataSource: DataSource, // ใช้สำหรับ Transaction
|
private readonly dataSource: DataSource, // ใช้สำหรับ Transaction
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -166,9 +168,9 @@ export class WorkflowEngineService {
|
|||||||
|
|
||||||
// 2. หา Initial State จาก Compiled Structure
|
// 2. หา Initial State จาก Compiled Structure
|
||||||
const compiled: CompiledWorkflow = definition.compiled;
|
const compiled: CompiledWorkflow = definition.compiled;
|
||||||
const initialState = Object.keys(compiled.states).find(
|
// [FIX] ใช้ initialState จาก Root Property โดยตรง (ตามที่ Optimize ใน DSL Service)
|
||||||
(key) => compiled.states[key].initial,
|
// เพราะ CompiledState ใน states map ไม่มี property 'initial' แล้ว
|
||||||
);
|
const initialState = compiled.initialState;
|
||||||
|
|
||||||
if (!initialState) {
|
if (!initialState) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
@@ -193,6 +195,25 @@ export class WorkflowEngineService {
|
|||||||
return savedInstance;
|
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
|
* ดำเนินการเปลี่ยนสถานะ (Transition) ของ Instance จริงแบบ Transactional
|
||||||
*/
|
*/
|
||||||
@@ -207,6 +228,9 @@ export class WorkflowEngineService {
|
|||||||
await queryRunner.connect();
|
await queryRunner.connect();
|
||||||
await queryRunner.startTransaction();
|
await queryRunner.startTransaction();
|
||||||
|
|
||||||
|
let eventsToDispatch: any[] = [];
|
||||||
|
let updatedContext: any = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Lock Instance เพื่อป้องกัน Race Condition (Pessimistic Write Lock)
|
// 1. Lock Instance เพื่อป้องกัน Race Condition (Pessimistic Write Lock)
|
||||||
const instance = await queryRunner.manager.findOne(WorkflowInstance, {
|
const instance = await queryRunner.manager.findOne(WorkflowInstance, {
|
||||||
@@ -268,25 +292,29 @@ export class WorkflowEngineService {
|
|||||||
});
|
});
|
||||||
await queryRunner.manager.save(history);
|
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();
|
await queryRunner.commitTransaction();
|
||||||
|
|
||||||
|
// [NEW] เก็บค่าไว้ Dispatch หลัง Commit
|
||||||
|
eventsToDispatch = evaluation.events;
|
||||||
|
updatedContext = context;
|
||||||
|
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`Transition: ${instanceId} [${fromState}] --${action}--> [${toState}] by User:${userId}`,
|
`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 {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
nextState: toState,
|
nextState: toState,
|
||||||
events: evaluation.events,
|
events: eventsToDispatch,
|
||||||
isCompleted: instance.status === WorkflowStatus.COMPLETED,
|
isCompleted: instance.status === WorkflowStatus.COMPLETED,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -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)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
191
docs/6B.md
191
docs/6B.md
@@ -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 | รายงานการฝึกปฏิบัติ |
|
|
||||||
|
|
||||||
---
|
|
||||||
@@ -1162,7 +1162,63 @@ ALTER TABLE rfa_revisions
|
|||||||
ADD COLUMN v_ref_drawing_count INT GENERATED ALWAYS AS (
|
ADD COLUMN v_ref_drawing_count INT GENERATED ALWAYS AS (
|
||||||
JSON_UNQUOTE(JSON_EXTRACT(details, '$.drawingCount'))
|
JSON_UNQUOTE(JSON_EXTRACT(details, '$.drawingCount'))
|
||||||
) VIRTUAL;
|
) 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);
|
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)
|
-- 5. PARTITIONING PREPARATION (Advance - Optional)
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
@@ -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 → ${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>
|
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
# 📝 **Documents Management System Version 1.4.4: Application Requirements Specification**
|
# 📝 **Documents Management System Version 1.4.4: Application Requirements Specification**
|
||||||
|
|
||||||
**สถานะ:** FINAL-Rev.04
|
**สถานะ:** FINAL-Rev.05
|
||||||
**วันที่:** 2025-11-26
|
**วันที่:** 2025-11-26
|
||||||
**อ้างอิงพื้นฐาน:** v1.4.3
|
**อ้างอิงพื้นฐาน:** v1.4.4
|
||||||
**Classification:** Internal Technical Documentation
|
**Classification:** Internal Technical Documentation
|
||||||
|
|
||||||
## 📌 **1. วัตถุประสงค์**
|
## 📌 **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:**
|
### **2.3 Core Services:**
|
||||||
|
|
||||||
- **Code Hosting:** Gitea (Self-hosted on QNAP)
|
- **Code Hosting:** Gitea (Self-hosted on QNAP)
|
||||||
|
|
||||||
- Application name: git
|
- Application name: git
|
||||||
- Service name: gitea
|
- Service name: gitea
|
||||||
- Domain: `git.np-dms.work`
|
- Domain: `git.np-dms.work`
|
||||||
- หน้าที่: เป็นศูนย์กลางในการเก็บและจัดการเวอร์ชันของโค้ด (Source Code) สำหรับทุกส่วน
|
- หน้าที่: เป็นศูนย์กลางในการเก็บและจัดการเวอร์ชันของโค้ด (Source Code) สำหรับทุกส่วน
|
||||||
|
|
||||||
- **Backend / Data Platform:** NestJS
|
- **Backend / Data Platform:** NestJS
|
||||||
|
|
||||||
- Application name: lcbp3-backend
|
- Application name: lcbp3-backend
|
||||||
- Service name: backend
|
- Service name: backend
|
||||||
- Domain: `backend.np-dms.work`
|
- Domain: `backend.np-dms.work`
|
||||||
@@ -63,6 +65,7 @@
|
|||||||
- หน้าที่: จัดการโครงสร้างข้อมูล (Data Models), สร้าง API, จัดการสิทธิ์ผู้ใช้ (Roles & Permissions), และสร้าง Workflow ทั้งหมดของระบบ
|
- หน้าที่: จัดการโครงสร้างข้อมูล (Data Models), สร้าง API, จัดการสิทธิ์ผู้ใช้ (Roles & Permissions), และสร้าง Workflow ทั้งหมดของระบบ
|
||||||
|
|
||||||
- **Database:** MariaDB 10.11
|
- **Database:** MariaDB 10.11
|
||||||
|
|
||||||
- Application name: lcbp3-db
|
- Application name: lcbp3-db
|
||||||
- Service name: mariadb
|
- Service name: mariadb
|
||||||
- Domain: `db.np-dms.work`
|
- Domain: `db.np-dms.work`
|
||||||
@@ -70,6 +73,7 @@
|
|||||||
- Tooling: DBeaver (Community Edition), phpmyadmin สำหรับการออกแบบและจัดการฐานข้อมูล
|
- Tooling: DBeaver (Community Edition), phpmyadmin สำหรับการออกแบบและจัดการฐานข้อมูล
|
||||||
|
|
||||||
- **Database Management:** phpMyAdmin
|
- **Database Management:** phpMyAdmin
|
||||||
|
|
||||||
- Application name: lcbp3-db
|
- Application name: lcbp3-db
|
||||||
- Service: phpmyadmin:5-apache
|
- Service: phpmyadmin:5-apache
|
||||||
- Service name: pma
|
- Service name: pma
|
||||||
@@ -77,6 +81,7 @@
|
|||||||
- หน้าที่: จัดการฐานข้อมูล mariadb ผ่าน Web UI
|
- หน้าที่: จัดการฐานข้อมูล mariadb ผ่าน Web UI
|
||||||
|
|
||||||
- **Frontend:** Next.js
|
- **Frontend:** Next.js
|
||||||
|
|
||||||
- Application name: lcbp3-frontend
|
- Application name: lcbp3-frontend
|
||||||
- Service name: frontend
|
- Service name: frontend
|
||||||
- Domain: `lcbp3.np-dms.work`
|
- Domain: `lcbp3.np-dms.work`
|
||||||
@@ -86,6 +91,7 @@
|
|||||||
- หน้าที่: สร้างหน้าตาเว็บแอปพลิเคชันสำหรับให้ผู้ใช้งานเข้ามาดู Dashboard, จัดการเอกสาร, และติดตามงาน โดยจะสื่อสารกับ Backend ผ่าน API
|
- หน้าที่: สร้างหน้าตาเว็บแอปพลิเคชันสำหรับให้ผู้ใช้งานเข้ามาดู Dashboard, จัดการเอกสาร, และติดตามงาน โดยจะสื่อสารกับ Backend ผ่าน API
|
||||||
|
|
||||||
- **Workflow Automation:** n8n
|
- **Workflow Automation:** n8n
|
||||||
|
|
||||||
- Application name: lcbp3-n8n
|
- Application name: lcbp3-n8n
|
||||||
- Service: n8nio/n8n:latest
|
- Service: n8nio/n8n:latest
|
||||||
- Service name: n8n
|
- Service name: n8n
|
||||||
@@ -93,6 +99,7 @@
|
|||||||
- หน้าที่: จัดการ workflow ระหว่าง Backend และ Line
|
- หน้าที่: จัดการ workflow ระหว่าง Backend และ Line
|
||||||
|
|
||||||
- **Reverse Proxy:** Nginx Proxy Manager
|
- **Reverse Proxy:** Nginx Proxy Manager
|
||||||
|
|
||||||
- Application name: lcbp3-npm
|
- Application name: lcbp3-npm
|
||||||
- Service: Nginx Proxy Manager (nginx-proxy-manage: latest)
|
- Service: Nginx Proxy Manager (nginx-proxy-manage: latest)
|
||||||
- Service name: npm
|
- Service name: npm
|
||||||
@@ -104,11 +111,11 @@
|
|||||||
|
|
||||||
### **2.4 Business Logic & Consistency (ปรับปรุง):**
|
### **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 ชั้นสุดท้าย
|
- **2.4.4 Optimistic Locking (ใหม่):** ใช้ Version Column ใน Database ควบคู่กับ Redis Lock สำหรับการสร้างเลขที่เอกสาร เพื่อเป็น Safety Net ชั้นสุดท้าย
|
||||||
|
|
||||||
@@ -190,22 +197,28 @@
|
|||||||
- 3.4.3. การสร้างเอกสาร: ผู้ใช้ที่มีสิทธิ์ สามารถสร้างและแก้ไขได้
|
- 3.4.3. การสร้างเอกสาร: ผู้ใช้ที่มีสิทธิ์ สามารถสร้างและแก้ไขได้
|
||||||
- 3.4.4. การอ้างอิงและจัดกลุ่ม: ช้สำหรับอ้างอิง ใน Shop Drawings, มีการจัดหมวดหมู่ของ Shop Drawings
|
- 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.1 Workflow Definition:
|
||||||
- 3.5.2. ประเภทเอกสาร: Request for Approval (RFA) เป็นชนิดหนึ่งของ Correspondence ที่มีลักษณะเฉพาะที่ต้องได้รับการอนุมัติ มีประเภทดังนี้:
|
|
||||||
- Request for Drawing Approval (RFA_DWG)
|
- Admin ต้องสามารถสร้าง/แก้ไข Workflow Rule ได้ผ่านหน้าจอ UI (DSL Editor) ร
|
||||||
- Request for Document Approval (RFA_DOC)
|
- องรับการกำหนด State, Transition, Required Role, Condition (JS Expression)
|
||||||
- Request for Method statement Approval (RFA_MES)
|
|
||||||
- Request for Material Approval (RFA_MAT)
|
- 3.5.2 Workflow Execution:
|
||||||
- 3.5.2. การสร้างเอกสาร: ผู้ใช้ที่มีสิทธิ์ สามารถสร้างและแก้ไขได้
|
|
||||||
- 3.5.3. การอ้างอิงและจัดกลุ่ม: การจัดการ Drawing (RFA_DWG):
|
- ระบบต้องรองรับการสร้าง Instance ของ Workflow ผูกกับเอกสาร (Polymorphic)
|
||||||
- เอกสาร RFA_DWG จะประกอบไปด้วย Shop Drawing (shop_drawings) หลายแผ่น ซึ่งแต่ละแผ่นมี Revision ของตัวเอง
|
- รองรับการเปลี่ยนสถานะ (Action) เช่น Approve, Reject, Comment, Return
|
||||||
- Shop Drawing แต่ละ Revision สามารถอ้างอิงถึง Contract Drawing (Ccontract_drawings) หลายแผ่น หรือไม่อ้างถึงก็ได้
|
- Auto-Action: รองรับการเปลี่ยนสถานะอัตโนมัติเมื่อครบเงื่อนไข (เช่น Review ครบทุกคน)
|
||||||
- ระบบต้องมีส่วนสำหรับจัดการข้อมูล Master Data ของทั้ง Shop Drawing และ Contract Drawing แยกจากกัน
|
|
||||||
- 3.5.4. Workflow การอนุมัติ: ต้องรองรับกระบวนการอนุมัติที่ซับซ้อนและเป็นลำดับ เช่น
|
- 3.5.3 Flexibility:
|
||||||
- ส่งจาก Originator -> Organization 1 -> Organization 2 -> Organization 3 แล้วส่งผลกลับตามลำดับเดิม (โดยถ้า องกรณ์ใดใน Workflow ให้ส่งกลับ ก็สามารถส่งผลกลับตามลำดับเดิมโดยไม่ต้องรอให้ถึง องกรณืในลำดับถัดไป)
|
|
||||||
- 3.5.5. การจัดการ: มีการจัดการอย่างน้อยดังนี้
|
- รองรับ Parallel Review (ส่งให้หลายคนตรวจพร้อมกัน)
|
||||||
|
- รองรับ Conditional Flow (เช่น ถ้ายอดเงิน > X ให้เพิ่มผู้อนุมัติ)
|
||||||
|
|
||||||
|
- 3.5.4 Workflow การอนุมัติ:
|
||||||
|
- ต้องรองรับกระบวนการอนุมัติที่ซับซ้อนและเป็นลำดับ เช่น ส่งจาก Originator -> Organization 1 -> Organization 2 -> Organization 3 แล้วส่งผลกลับตามลำดับเดิม (โดยถ้า องกรณ์ใดใน Workflow ให้ส่งกลับ ก็สามารถส่งผลกลับตามลำดับเดิมโดยไม่ต้องรอให้ถึง องกรณืในลำดับถัดไป)
|
||||||
|
|
||||||
|
- 3.5.5 การจัดการ:
|
||||||
- สามารถกำหนดวันแล้วเสร็จ (Deadline) สำหรับผู้รับผิดชอบของ องกรณ์ ที่อยู่ใน Workflow ได้
|
- สามารถกำหนดวันแล้วเสร็จ (Deadline) สำหรับผู้รับผิดชอบของ องกรณ์ ที่อยู่ใน Workflow ได้
|
||||||
- มีระบบแจ้งเตือน ให้ผู้รับผิดชอบของ องกรณ์ ที่อยู่ใน Workflow ทราบ เมื่อมี RFA ใหม่ หรือมีการเปลี่ยนสถานะ
|
- มีระบบแจ้งเตือน ให้ผู้รับผิดชอบของ องกรณ์ ที่อยู่ใน Workflow ทราบ เมื่อมี RFA ใหม่ หรือมีการเปลี่ยนสถานะ
|
||||||
|
|
||||||
@@ -235,11 +248,13 @@
|
|||||||
### **3.9. การจัดเก็บไฟล์ (File Handling - ปรับปรุงใหญ่)**
|
### **3.9. การจัดเก็บไฟล์ (File Handling - ปรับปรุงใหญ่)**
|
||||||
|
|
||||||
- **3.9.1 Two-Phase Storage Strategy:**
|
- **3.9.1 Two-Phase Storage Strategy:**
|
||||||
|
|
||||||
1. **Phase 1 (Upload):** ไฟล์ถูกอัปโหลดเข้าโฟลเดอร์ `temp/` และได้รับ `temp_id`
|
1. **Phase 1 (Upload):** ไฟล์ถูกอัปโหลดเข้าโฟลเดอร์ `temp/` และได้รับ `temp_id`
|
||||||
2. **Phase 2 (Commit):** เมื่อ User กด Submit ฟอร์มสำเร็จ ระบบจะย้ายไฟล์จาก `temp/` ไปยัง `permanent/{YYYY}/{MM}/` และบันทึกลง Database ภายใน Transaction เดียวกัน
|
2. **Phase 2 (Commit):** เมื่อ User กด Submit ฟอร์มสำเร็จ ระบบจะย้ายไฟล์จาก `temp/` ไปยัง `permanent/{YYYY}/{MM}/` และบันทึกลง Database ภายใน Transaction เดียวกัน
|
||||||
3. **Cleanup:** มี Cron Job ลบไฟล์ใน `temp/` ที่ค้างเกิน 24 ชม. (Orphan Files)
|
3. **Cleanup:** มี Cron Job ลบไฟล์ใน `temp/` ที่ค้างเกิน 24 ชม. (Orphan Files)
|
||||||
|
|
||||||
- **3.9.2 Security:**
|
- **3.9.2 Security:**
|
||||||
|
|
||||||
- Virus Scan (ClamAV) ก่อนย้ายเข้า Permanent
|
- Virus Scan (ClamAV) ก่อนย้ายเข้า Permanent
|
||||||
- Whitelist File Types: PDF, DWG, DOCX, XLSX, ZIP
|
- Whitelist File Types: PDF, DWG, DOCX, XLSX, ZIP
|
||||||
- Max Size: 50MB
|
- Max Size: 50MB
|
||||||
@@ -269,12 +284,14 @@
|
|||||||
### **3.11 การจัดการ JSON Details (JSON & Performance - ปรับปรุง)**
|
### **3.11 การจัดการ JSON Details (JSON & Performance - ปรับปรุง)**
|
||||||
|
|
||||||
- **3.11.1 วัตถุประสงค์**
|
- **3.11.1 วัตถุประสงค์**
|
||||||
|
|
||||||
- จัดเก็บข้อมูลแบบไดนามิกที่เฉพาะเจาะจงกับแต่ละประเภทของเอกสาร
|
- จัดเก็บข้อมูลแบบไดนามิกที่เฉพาะเจาะจงกับแต่ละประเภทของเอกสาร
|
||||||
- รองรับการขยายตัวของระบบโดยไม่ต้องเปลี่ยนแปลง database schema
|
- รองรับการขยายตัวของระบบโดยไม่ต้องเปลี่ยนแปลง database schema
|
||||||
- จัดการ metadata และข้อมูลประกอบสำหรับ correspondence, routing, และ workflows
|
- จัดการ metadata และข้อมูลประกอบสำหรับ correspondence, routing, และ workflows
|
||||||
|
|
||||||
- **3.11.2 โครงสร้าง JSON Schema**
|
- **3.11.2 โครงสร้าง JSON Schema**
|
||||||
ระบบต้องมี predefined JSON schemas สำหรับประเภทเอกสารต่างๆ:
|
ระบบต้องมี predefined JSON schemas สำหรับประเภทเอกสารต่างๆ:
|
||||||
|
|
||||||
- **3.11.2.1 Correspondence Types**
|
- **3.11.2.1 Correspondence Types**
|
||||||
- **GENERIC**: ข้อมูลพื้นฐานสำหรับเอกสารทั่วไป
|
- **GENERIC**: ข้อมูลพื้นฐานสำหรับเอกสารทั่วไป
|
||||||
- **RFI**: รายละเอียดคำถามและข้อมูลทางเทคนิค
|
- **RFI**: รายละเอียดคำถามและข้อมูลทางเทคนิค
|
||||||
@@ -282,10 +299,10 @@
|
|||||||
- **TRANSMITTAL**: รายการเอกสารที่ส่งต่อ
|
- **TRANSMITTAL**: รายการเอกสารที่ส่งต่อ
|
||||||
- **LETTER**: ข้อมูลจดหมายทางการ
|
- **LETTER**: ข้อมูลจดหมายทางการ
|
||||||
- **EMAIL**: ข้อมูลอีเมล
|
- **EMAIL**: ข้อมูลอีเมล
|
||||||
- **3.11.2.2 Routing Types**
|
- **3.11.2.2 Rworkflow Types**
|
||||||
- **ROUTING_TEMPLATE**: กฎและเงื่อนไขการส่งต่อ
|
- **workflow_definitions**: กฎและเงื่อนไขการส่งต่อ
|
||||||
- **ROUTING_INSTANCE**: สถานะและประวัติการส่งต่อ
|
- **workflow_histories**: สถานะและประวัติการส่งต่อ
|
||||||
- **ROUTING_ACTION**: การดำเนินการในแต่ละขั้นตอน
|
- **workflow_instances**: การดำเนินการในแต่ละขั้นตอน
|
||||||
- **3.11.2.3 Audit Types**
|
- **3.11.2.3 Audit Types**
|
||||||
- **AUDIT_LOG**: ข้อมูลการตรวจสอบ
|
- **AUDIT_LOG**: ข้อมูลการตรวจสอบ
|
||||||
- **SECURITY_SCAN**: ผลการตรวจสอบความปลอดภัย
|
- **SECURITY_SCAN**: ผลการตรวจสอบความปลอดภัย
|
||||||
@@ -293,12 +310,14 @@
|
|||||||
- **3.11.3 Virtual Columns (ใหม่):** สำหรับ Field ใน JSON ที่ต้องใช้ในการค้นหา (Search) หรือจัดเรียง (Sort) บ่อยๆ **ต้องสร้าง Generated Column (Virtual Column)** ใน Database และทำ Index ไว้ เพื่อประสิทธิภาพสูงสุด
|
- **3.11.3 Virtual Columns (ใหม่):** สำหรับ Field ใน JSON ที่ต้องใช้ในการค้นหา (Search) หรือจัดเรียง (Sort) บ่อยๆ **ต้องสร้าง Generated Column (Virtual Column)** ใน Database และทำ Index ไว้ เพื่อประสิทธิภาพสูงสุด
|
||||||
|
|
||||||
- **3.11.4 Validation Rules**
|
- **3.11.4 Validation Rules**
|
||||||
|
|
||||||
- ต้องมี JSON schema validation สำหรับแต่ละประเภท
|
- ต้องมี JSON schema validation สำหรับแต่ละประเภท
|
||||||
- ต้องรองรับ versioning ของ schema
|
- ต้องรองรับ versioning ของ schema
|
||||||
- ต้องมี default values สำหรับ field ที่ไม่บังคับ
|
- ต้องมี default values สำหรับ field ที่ไม่บังคับ
|
||||||
- ต้องตรวจสอบ data types และ format ให้ถูกต้อง
|
- ต้องตรวจสอบ data types และ format ให้ถูกต้อง
|
||||||
|
|
||||||
- **3.11.5 Performance Requirements**
|
- **3.11.5 Performance Requirements**
|
||||||
|
|
||||||
- JSON field ต้องมีขนาดไม่เกิน 50KB
|
- JSON field ต้องมีขนาดไม่เกิน 50KB
|
||||||
- ต้องรองรับ indexing สำหรับ field ที่ใช้ค้นหาบ่อย
|
- ต้องรองรับ indexing สำหรับ field ที่ใช้ค้นหาบ่อย
|
||||||
- ต้องมี compression สำหรับ JSON ขนาดใหญ่
|
- ต้องมี compression สำหรับ JSON ขนาดใหญ่
|
||||||
@@ -309,6 +328,7 @@
|
|||||||
- ต้อง encrypt sensitive data ใน JSON fields
|
- ต้อง encrypt sensitive data ใน JSON fields
|
||||||
|
|
||||||
### **3.12 ข้อกำหนดพิเศษ**
|
### **3.12 ข้อกำหนดพิเศษ**
|
||||||
|
|
||||||
- **ผู้ใช้งานที่มีสิทธิ์ระดับสูง (Global) หรือผู้ได้รับอนุญาตเป็นกรณีพิเศษ**
|
- **ผู้ใช้งานที่มีสิทธิ์ระดับสูง (Global) หรือผู้ได้รับอนุญาตเป็นกรณีพิเศษ**
|
||||||
- สามารถเลือก **สร้างในนามองค์กร (Create on behalf of)** ได้ เพื่อให้สามารถออกเลขที่เอกสาร (Running Number) ขององค์กรอื่นได้โดยไม่ต้องล็อกอินใหม่
|
- สามารถเลือก **สร้างในนามองค์กร (Create on behalf of)** ได้ เพื่อให้สามารถออกเลขที่เอกสาร (Running Number) ขององค์กรอื่นได้โดยไม่ต้องล็อกอินใหม่
|
||||||
- สามารถทำงานแทนผู้ใช้งานอื่นได้ Routing & Workflow ของ Correspondence, RFA, Circulation Sheet
|
- สามารถทำงานแทนผู้ใช้งานอื่นได้ Routing & Workflow ของ Correspondence, RFA, Circulation Sheet
|
||||||
@@ -337,7 +357,7 @@
|
|||||||
### **4.3. การกำหนดบทบาท (Roles) และขอบเขต (Scope)**
|
### **4.3. การกำหนดบทบาท (Roles) และขอบเขต (Scope)**
|
||||||
|
|
||||||
| บทบาท (Role) | ขอบเขต (Scope) | คำอธิบาย | สิทธิ์หลัก (Key Permissions) |
|
| บทบาท (Role) | ขอบเขต (Scope) | คำอธิบาย | สิทธิ์หลัก (Key Permissions) |
|
||||||
| :------------------- | :------------- | :------------------ | :----------------------------------------------------------------------------- |
|
| :------------------- | :------------- | :---------------------- | :------------------------------------------------------------------------------------- |
|
||||||
| **Superadmin** | Global | ผู้ดูแลระบบสูงสุด | ทำทุกอย่างในระบบ, จัดการองค์กร, จัดการข้อมูลหลักระดับ Global |
|
| **Superadmin** | Global | ผู้ดูแลระบบสูงสุด | ทำทุกอย่างในระบบ, จัดการองค์กร, จัดการข้อมูลหลักระดับ Global |
|
||||||
| **Org Admin** | Organization | ผู้ดูแลองค์กร | จัดการผู้ใช้ในองค์กร, จัดการบทบาท/สิทธิ์ภายในองค์กร, ดูรายงานขององค์กร |
|
| **Org Admin** | Organization | ผู้ดูแลองค์กร | จัดการผู้ใช้ในองค์กร, จัดการบทบาท/สิทธิ์ภายในองค์กร, ดูรายงานขององค์กร |
|
||||||
| **Document Control** | Organization | ควบคุมเอกสารขององค์กร | เพิ่ม/แก้ไข/ลบเอกสาร, กำหนดสิทธิ์เอกสารภายในองค์กร |
|
| **Document Control** | Organization | ควบคุมเอกสารขององค์กร | เพิ่ม/แก้ไข/ลบเอกสาร, กำหนดสิทธิ์เอกสารภายในองค์กร |
|
||||||
@@ -373,7 +393,7 @@
|
|||||||
### **4.6. การจัดการข้อมูลหลัก (Master Data Management) ที่แบ่งตามระดับ**
|
### **4.6. การจัดการข้อมูลหลัก (Master Data Management) ที่แบ่งตามระดับ**
|
||||||
|
|
||||||
| ข้อมูลหลัก | ผู้มีสิทธิ์จัดการ | ระดับ |
|
| ข้อมูลหลัก | ผู้มีสิทธิ์จัดการ | ระดับ |
|
||||||
| :---------------------------------- | :------------------------------ | :------------------------------ |
|
| :---------------------------------- | :------------------------------ | :--------------------------------- |
|
||||||
| ประเภทเอกสาร (Correspondence, RFA) | **Superadmin** | Global |
|
| ประเภทเอกสาร (Correspondence, RFA) | **Superadmin** | Global |
|
||||||
| สถานะเอกสาร (Draft, Approved, etc.) | **Superadmin** | Global |
|
| สถานะเอกสาร (Draft, Approved, etc.) | **Superadmin** | Global |
|
||||||
| หมวดหมู่แบบ (Shop Drawing) | **Project Manager** | Project (สร้างใหม่ได้ภายในโครงการ) |
|
| หมวดหมู่แบบ (Shop Drawing) | **Project Manager** | Project (สร้างใหม่ได้ภายในโครงการ) |
|
||||||
@@ -434,6 +454,7 @@
|
|||||||
### **6.1. การบันทึกการกระทำ (Audit Log):** ทุกการกระทำที่สำคัญของผู้ใช้ (สร้าง, แก้ไข, ลบ, ส่ง) จะถูกบันทึกไว้ใน audit_logs เพื่อการตรวจสอบย้อนหลัง
|
### **6.1. การบันทึกการกระทำ (Audit Log):** ทุกการกระทำที่สำคัญของผู้ใช้ (สร้าง, แก้ไข, ลบ, ส่ง) จะถูกบันทึกไว้ใน audit_logs เพื่อการตรวจสอบย้อนหลัง
|
||||||
|
|
||||||
- **6.1.1 ขอบเขตการบันทึก Audit Log:**
|
- **6.1.1 ขอบเขตการบันทึก Audit Log:**
|
||||||
|
|
||||||
- ทุกการสร้าง/แก้ไข/ลบ ข้อมูลสำคัญ (correspondences, RFAs, drawings, users, permissions)
|
- ทุกการสร้าง/แก้ไข/ลบ ข้อมูลสำคัญ (correspondences, RFAs, drawings, users, permissions)
|
||||||
- ทุกการเข้าถึงข้อมูล sensitive (user data, financial information)
|
- ทุกการเข้าถึงข้อมูล sensitive (user data, financial information)
|
||||||
- ทุกการเปลี่ยนสถานะ workflow (status transitions)
|
- ทุกการเปลี่ยนสถานะ workflow (status transitions)
|
||||||
@@ -465,6 +486,7 @@
|
|||||||
### **6.5. ประสิทธิภาพ (Performance):** มีการใช้ Caching กับข้อมูลที่เรียกใช้บ่อย และใช้ Pagination ในตารางข้อมูลเพื่อจัดการข้อมูลจำนวนมาก
|
### **6.5. ประสิทธิภาพ (Performance):** มีการใช้ Caching กับข้อมูลที่เรียกใช้บ่อย และใช้ Pagination ในตารางข้อมูลเพื่อจัดการข้อมูลจำนวนมาก
|
||||||
|
|
||||||
- **6.5.1 ตัวชี้วัดประสิทธิภาพ:**
|
- **6.5.1 ตัวชี้วัดประสิทธิภาพ:**
|
||||||
|
|
||||||
- **API Response Time:** < 200ms (90th percentile) สำหรับ operation ทั่วไป
|
- **API Response Time:** < 200ms (90th percentile) สำหรับ operation ทั่วไป
|
||||||
- **Search Query Performance:** < 500ms สำหรับการค้นหาขั้นสูง
|
- **Search Query Performance:** < 500ms สำหรับการค้นหาขั้นสูง
|
||||||
- **File Upload Performance:** < 30 seconds สำหรับไฟล์ขนาด 50MB
|
- **File Upload Performance:** < 30 seconds สำหรับไฟล์ขนาด 50MB
|
||||||
@@ -492,6 +514,7 @@
|
|||||||
- การจัดการ Secret (เช่น รหัสผ่าน DB, JWT Secret) จะต้องทำผ่าน Environment Variable ของ Docker เพื่อความปลอดภัยสูงสุด
|
- การจัดการ Secret (เช่น รหัสผ่าน DB, JWT Secret) จะต้องทำผ่าน Environment Variable ของ Docker เพื่อความปลอดภัยสูงสุด
|
||||||
|
|
||||||
- **6.6.1 Rate Limiting Strategy:**
|
- **6.6.1 Rate Limiting Strategy:**
|
||||||
|
|
||||||
- **Anonymous Endpoints:** 100 requests/hour ต่อ IP address
|
- **Anonymous Endpoints:** 100 requests/hour ต่อ IP address
|
||||||
- **Authenticated Endpoints:**
|
- **Authenticated Endpoints:**
|
||||||
- Viewer: 500 requests/hour
|
- Viewer: 500 requests/hour
|
||||||
@@ -505,12 +528,14 @@
|
|||||||
- ต้องบันทึก log เมื่อมีการ trigger rate limiting
|
- ต้องบันทึก log เมื่อมีการ trigger rate limiting
|
||||||
|
|
||||||
- **6.6.2 Error Handling และ Resilience:**
|
- **6.6.2 Error Handling และ Resilience:**
|
||||||
|
|
||||||
- ต้องมี circuit breaker pattern สำหรับ external service calls
|
- ต้องมี circuit breaker pattern สำหรับ external service calls
|
||||||
- ต้องมี retry mechanism ด้วย exponential backoff
|
- ต้องมี retry mechanism ด้วย exponential backoff
|
||||||
- ต้องมี graceful degradation เมื่อบริการภายนอกล้มเหลว
|
- ต้องมี graceful degradation เมื่อบริการภายนอกล้มเหลว
|
||||||
- Error messages ต้องไม่เปิดเผยข้อมูล sensitive
|
- Error messages ต้องไม่เปิดเผยข้อมูล sensitive
|
||||||
|
|
||||||
- **6.6.3 Input Validation:**
|
- **6.6.3 Input Validation:**
|
||||||
|
|
||||||
- ต้องมี input validation ทั้งฝั่ง client และ server (defense in depth)
|
- ต้องมี input validation ทั้งฝั่ง client และ server (defense in depth)
|
||||||
- ต้องป้องกัน OWASP Top 10 vulnerabilities:
|
- ต้องป้องกัน OWASP Top 10 vulnerabilities:
|
||||||
- SQL Injection (ใช้ parameterized queries ผ่าน ORM)
|
- SQL Injection (ใช้ parameterized queries ผ่าน ORM)
|
||||||
@@ -564,6 +589,7 @@
|
|||||||
### **6.8. กลยุทธ์การแจ้งเตือน (Notification Strategy - ปรับปรุง):**
|
### **6.8. กลยุทธ์การแจ้งเตือน (Notification Strategy - ปรับปรุง):**
|
||||||
|
|
||||||
- **6.8.1 ระบบจะส่งการแจ้งเตือน (ผ่าน Email หรือ Line [cite: 2.7]) เมื่อมีการกระทำที่สำคัญ** ดังนี้:
|
- **6.8.1 ระบบจะส่งการแจ้งเตือน (ผ่าน Email หรือ Line [cite: 2.7]) เมื่อมีการกระทำที่สำคัญ** ดังนี้:
|
||||||
|
|
||||||
1. เมื่อมีเอกสารใหม่ (Correspondence, RFA) ถูกส่งมาถึงองค์กรณ์ของเรา
|
1. เมื่อมีเอกสารใหม่ (Correspondence, RFA) ถูกส่งมาถึงองค์กรณ์ของเรา
|
||||||
2. เมื่อมีใบเวียน (Circulation) ใหม่ มอบหมายงานมาที่เรา
|
2. เมื่อมีใบเวียน (Circulation) ใหม่ มอบหมายงานมาที่เรา
|
||||||
3. (ทางเลือก) เมื่อเอกสารที่เราส่งไป ถูกดำเนินการ (เช่น อนุมัติ/ปฏิเสธ)
|
3. (ทางเลือก) เมื่อเอกสารที่เราส่งไป ถูกดำเนินการ (เช่น อนุมัติ/ปฏิเสธ)
|
||||||
397
docs/Markdown/2_Backend_Plan_V1_4_4.Phase6A.md
Normal file
397
docs/Markdown/2_Backend_Plan_V1_4_4.Phase6A.md
Normal 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 ในอนาคต
|
||||||
|
|
||||||
709
docs/Markdown/2_Backend_Plan_V1_4_4.Phase_Addition.md
Normal file
709
docs/Markdown/2_Backend_Plan_V1_4_4.Phase_Addition.md
Normal 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 ที่ซับซ้อนและการวิเคราะห์ข้อมูลเชิงลึก
|
||||||
@@ -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
|
|
||||||
460
docs/prompt.md
460
docs/prompt.md
@@ -1,123 +1,5 @@
|
|||||||
# **PROMPT**
|
# **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 หรือปุ่มกดให้คุณเข้าไปตั้งค่าได้โดยตรงครับ**
|
สำหรับการ **"ตั้งค่า"** หรือ **"กำหนดค่า"** ให้ผมตรวจสอบข้อมูลก่อนนั้น ในระบบ 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
|
3. 1_FullStackJS_V1_4_4.md: Focus on Section 3.5 Unified Workflow Engine, 3.9.13 CorrespondenceRoutingModule, 3.9.14 WorkflowEngineModule.
|
||||||
src/modules/json-schema
|
|
||||||
├── 📁 dto
|
Goal: Prepare to implement coding for Unified Workflow Engine.
|
||||||
│ ├── create-json-schema.dto.ts # DTO สำหรับสร้าง Schema ใหม่
|
|
||||||
│ ├── update-json-schema.dto.ts # DTO สำหรับแก้ไข Schema
|
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.
|
||||||
│ ├── validate-data.dto.ts # DTO สำหรับรับข้อมูลมา Validate
|
|
||||||
│ └── migrate-data.dto.ts # DTO สำหรับสั่ง Migrate Data
|
รับทราบครับ ผมได้วิเคราะห์เอกสาร `2_Backend_Plan_V1_4_4.md`, `0_Requirements_V1_4_4.md`, และ `1_FullStackJS_V1_4_4.md` โดยเน้นไปที่ **Phase 3: Unified Workflow Engine** เรียบร้อยแล้ว
|
||||||
├── 📁 entities
|
|
||||||
│ └── json-schema.entity.ts # Entity หลักเก็บ Definition (T2.5.1)
|
สรุปความเข้าใจและโครงสร้างงานที่จะต้องดำเนินการใน Phase นี้ มีรายละเอียดดังนี้ครับ:
|
||||||
├── 📁 services
|
|
||||||
│ ├── json-schema.service.ts # Core Service (CRUD, Validation wrapper)
|
### 🎯 ภาพรวม (Overview)
|
||||||
│ ├── virtual-column.service.ts # จัดการ Generated Columns ใน DB (T2.5.3)
|
|
||||||
│ ├── schema-migration.service.ts # จัดการ Data Migration ระหว่าง Version (T2.5.5)
|
เป้าหมายคือการสร้าง **Unified Workflow Engine** ที่เป็นระบบกลางในการจัดการ Business Logic ของการเดินเอกสารทั้งหมด (ทั้ง Correspondence Routing ปกติ และ RFA Approval Workflow ที่ซับซ้อน) โดยไม่ผูกติดกับ Hard-coded logic ในโค้ด แต่จะขับเคลื่อนด้วย **DSL (Domain-Specific Language)** และ **Configuration** เพื่อความยืดหยุ่นในการปรับเปลี่ยน Flow, การข้ามขั้นตอน, หรือการส่งกลับ
|
||||||
│ └── 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
|
### 📋 1. รายการสิ่งที่ต้องทำ (Todo Tasks)
|
||||||
├── json-schema.controller.ts # API Endpoints (T2.5.7)
|
|
||||||
├── json-schema.module.ts # Module Definition
|
อ้างอิงจาก Task T3.1 - T3.1.9 ในแผน Backend:
|
||||||
└── json-schema.service.spec.ts # Unit Tests (T2.5.9)
|
|
||||||
|
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)
|
ข้อมูลนี้อ้างอิงจากไฟล์ SQL Script ล่าสุดที่คุณเลือกมาครับ หากต้องการแก้ไขหรือเพิ่มเติมส่วนไหน แจ้งได้เลยครับ
|
||||||
|
|
||||||
#### **[ ] 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)
|
|
||||||
|
|||||||
@@ -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
173
users.sql
@@ -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 */
|
|
||||||
;
|
|
||||||
Reference in New Issue
Block a user