260322:1648 Correct Coresspondence / Doing RFA / Correct CI
CI Pipeline / build (push) Failing after 12m41s
Build and Deploy / deploy (push) Failing after 2m44s

This commit is contained in:
admin
2026-03-22 16:48:12 +07:00
parent e5deedb42e
commit 11984bfa29
683 changed files with 105251 additions and 29068 deletions
@@ -1,25 +1,29 @@
# 4. Access Control & RBAC Matrix (V1.8.0)
---
title: 'Access Control & RBAC Matrix'
version: 1.8.0
status: APPROVED
owner: Nattanin Peancharoen / Development Team
last_updated: 2026-02-23
related:
- specs/02-architecture/02-01-system-architecture.md
- specs/03-implementation/03-02-backend-guidelines.md
- specs/07-database/07-01-data-dictionary-v1.8.0.md
- specs/05-decisions/ADR-005-redis-usage-strategy.md
- specs/05-decisions/ADR-001-unified-workflow-engine.md
references:
- [RBAC Implementation](../../99-archives/ADR-004-rbac-implementation.md)
- [Access Control](../../99-archives/01-04-access-control.md)
- specs/02-architecture/02-01-system-architecture.md
- specs/03-implementation/03-02-backend-guidelines.md
- specs/07-database/07-01-data-dictionary-v1.8.0.md
- specs/05-decisions/ADR-005-redis-usage-strategy.md
- specs/05-decisions/ADR-001-unified-workflow-engine.md
references:
- [RBAC Implementation](../../99-archives/ADR-004-rbac-implementation.md)
- [Access Control](../../99-archives/01-04-access-control.md)
---
## 4.1. Overview and Problem Statement
LCBP3-DMS จัดการสิทธิ์การเข้าถึงข้อมูลที่ซับซ้อน ได้แก่:
- **Multi-Organization**: หลายองค์กรใช้ระบบร่วมกัน แต่ต้องแยกข้อมูลตามบริบท
- **Project-Based**: โครงการสามารถมีหลายสัญญาย่อย (Contracts)
- **Hierarchical Permissions**: สิทธิ์ระดับบนสามารถครอบคลุมระดับล่าง
@@ -28,6 +32,7 @@ LCBP3-DMS จัดการสิทธิ์การเข้าถึงข
Users และ Organizations สามารถเข้าดูหรือแก้ไขเอกสารได้จากสิทธิ์ที่ได้รับ ระบบออกแบบด้วย **4-Level Hierarchical Role-Based Access Control (RBAC)** ដើម្បីรองรับความซับซ้อนนี้.
### Key Requirements
1. User หนึ่งคนสามารถมีหลาย Roles ในหลาย Scopes
2. Permission Inheritance (Global → Organization → Project → Contract)
3. Fine-grained Access Control (เช่น "อนุญาตให้ดู Correspondence เฉพาะใน Project A รวมถึง Contract ภายใต้ Project A เท่านั้น")
@@ -56,8 +61,9 @@ Global (ทั้งระบบ)
```
**Permission Enforcement:**
- เมื่อตรวจสอบสิทธิ์ ระบบจะพิจารณาสิทธิ์จากทุก Level ที่ผู้ใช้มี และใช้สิทธิ์ที่ **"ครอบคลุมที่สุด (Most Permissive)"** ในบริบท (Context) นั้นมาเป็นเกณฑ์.
- *Example*: User A เป็น `Viewer` ในองค์กร (ระดับ Organization Level) แต่มอบหมายหน้าที่ให้เป็น `Editor` ในตำแหน่งของ Project X. เมื่อ User A ดำเนินการในบริบท Context ของ Project X (หรือ Contract ที่อยู่ใต้ Project X), User A จะสามารถทำงานด้วยสิทธิ์ `Editor` ทันที.
- _Example_: User A เป็น `Viewer` ในองค์กร (ระดับ Organization Level) แต่มอบหมายหน้าที่ให้เป็น `Editor` ในตำแหน่งของ Project X. เมื่อ User A ดำเนินการในบริบท Context ของ Project X (หรือ Contract ที่อยู่ใต้ Project X), User A จะสามารถทำงานด้วยสิทธิ์ `Editor` ทันที.
---
@@ -75,14 +81,14 @@ Global (ทั้งระบบ)
### Master Data Management Authority
| Master Data | Manager | Scope |
| :-------------------------------------- | :------------------------------ | :------------------------------ |
| Document Type (Correspondence, RFA) | **Superadmin** | Global |
| Document Status (Draft, Approved, etc.) | **Superadmin** | Global |
| Master Data | Manager | Scope |
| :-------------------------------------- | :------------------------------ | :--------------------------------- |
| Document Type (Correspondence, RFA) | **Superadmin** | Global |
| Document Status (Draft, Approved, etc.) | **Superadmin** | Global |
| Shop Drawing Category | **Project Manager** | Project (สร้างใหม่ได้ภายในโครงการ) |
| Tags | **Org Admin / Project Manager** | Organization / Project |
| Custom Roles | **Superadmin / Org Admin** | Global / Organization |
| Document Numbering Formats | **Superadmin / Admin** | Global / Organization |
| Tags | **Org Admin / Project Manager** | Organization / Project |
| Custom Roles | **Superadmin / Org Admin** | Global / Organization |
| Document Numbering Formats | **Superadmin / Admin** | Global / Organization |
---
@@ -272,13 +278,11 @@ Controller Example Usage:
@Controller('correspondences')
@UseGuards(JwtAuthGuard, PermissionGuard)
export class CorrespondenceController {
@Post()
@RequirePermission('correspondence.create')
async create(@Body() dto: CreateCorrespondenceDto) {
// Accessible only when CASL Evaluate "correspondence.create" successfully fits the parameters limits (Project/Contract scope check)
}
}
```
@@ -299,15 +303,21 @@ export class CorrespondenceController {
ส่วนอ้างอิงและประวัติศาสตร์การตัดสินใจพิจารณาในอดีต (Reference) ก่อนการนำมาปรับใช้ร่าง Architecture ฉบับ V1.8.0.
### Considered Options Before Version 1.5.0
1. **Option 1**: Simple Role-Based (No Scope)
- *Pros*: Very simple implementation, Easy to understand
- *Cons*: ไม่รองรับ Multi-organization, Superadmin เห็นข้อมูลองค์กรอื่นมั่วข้ามกลุ่ม
- _Pros_: Very simple implementation, Easy to understand
- _Cons_: ไม่รองรับ Multi-organization, Superadmin เห็นข้อมูลองค์กรอื่นมั่วข้ามกลุ่ม
2. **Option 2**: Organization-Only Scope
- *Pros*: แยกข้อมูลระหว่าง Organizations ได้ชัดเจน
- *Cons*: ไม่รองรับระดับ Sub-level Permissions (Project/Contract) ไม่เพียงพอกับ Business Rule.
- _Pros_: แยกข้อมูลระหว่าง Organizations ได้ชัดเจน
- _Cons_: ไม่รองรับระดับ Sub-level Permissions (Project/Contract) ไม่เพียงพอกับ Business Rule.
3. **Option 3**: **4-Level Hierarchical RBAC (Selected)**
- *Pros*: ครอบคลุม Use Case สูงสุด (Maximum Flexibility), รองรับ Hierarchy สืบทอดสิทธิ์ (Inheritance), ขอบเขตจำกัดข้อมูลอย่างรัดกุมระดับ Contract (Data Isolation).
- *Cons*: Complexity ทางด้านการ Implement, Invalidate Caching หางานให้ฝั่ง Operations, Developer Learning Curve.
- _Pros_: ครอบคลุม Use Case สูงสุด (Maximum Flexibility), รองรับ Hierarchy สืบทอดสิทธิ์ (Inheritance), ขอบเขตจำกัดข้อมูลอย่างรัดกุมระดับ Contract (Data Isolation).
- _Cons_: Complexity ทางด้านการ Implement, Invalidate Caching หางานให้ฝั่ง Operations, Developer Learning Curve.
**Decision Rationale:**
เลือกแนวทางที่ 3 รองรับความต้องการของ Construction Projects ที่มีการแบ่งกลุ่มรับเหมาช่วงย่อยระดับ Contracts ต่อเนื่อง (Future proof). แก้ไขปัญหา Performance Cons ด้วยแนวคิดการประจุ Redis Caching ข้ามขั้ว และล้าง Invalidation เฉพาะเมื่อ Triggering Database Updates (อ้างอิงหัวข้อ 4.6).
@@ -1,26 +1,29 @@
# 3.11 Document Numbering Management & Implementation (V1.8.0)
---
title: 'Specifications & Implementation Guide: Document Numbering System'
version: 1.8.0
status: draft
owner: Nattanin Peancharoen / Development Team
last_updated: 2026-02-23
related:
- specs/01-requirements/01-01-objectives.md
- specs/02-architecture/README.md
- specs/03-implementation/03-02-backend-guidelines.md
- specs/04-operations/04-08-document-numbering-operations.md
- specs/07-database/07-01-data-dictionary-v1.8.0.md
- specs/05-decisions/ADR-002-document-numbering-strategy.md
Clean Version v1.8.0 Scope of Changes:
- รวม Functional Requirements เข้ากับ Implementation Guide
- เลือกใช้ Single Numbering System (Option A) `document_number_counters` เป็น Authoritative Counter
-พิ่ม Idempotency Key, Reservation (Two-Phase Commit)
- Number State Machine, Pattern Validate UTF-8, Cancellation Rule (Void/Replace)
references:
- [Document Numbering](../../99-archives/01-03.11-document-numbering.md)
- [Document Numbering](../../99-archives/03-04-document-numbering.md)
- specs/01-requirements/01-01-objectives.md
- specs/02-architecture/README.md
- specs/03-implementation/03-02-backend-guidelines.md
- specs/04-operations/04-08-document-numbering-operations.md
- specs/07-database/07-01-data-dictionary-v1.8.0.md
- specs/05-decisions/ADR-002-document-numbering-strategy.md
Clean Version v1.8.0 Scope of Changes:
- รวม Functional Requirements เข้ากับ Implementation Guide
-ลือกใช้ Single Numbering System (Option A) `document_number_counters` เป็น Authoritative Counter
- เพิ่ม Idempotency Key, Reservation (Two-Phase Commit)
- Number State Machine, Pattern Validate UTF-8, Cancellation Rule (Void/Replace)
references:
- [Document Numbering](../../99-archives/01-03.11-document-numbering.md)
- [Document Numbering](../../99-archives/03-04-document-numbering.md)
---
> **📖 เอกสารฉบับนี้เป็นการรวมรายละเอียดจาก `01-03.11-document-numbering.md` และ `03-04-document-numbering.md` ให้อยู่ในฉบับเดียวสำหรับใช้อ้างอิงการออกแบบเชิง Functional และการพัฒนา Technology Component**
@@ -32,6 +35,7 @@ references:
ระบบ Document Numbering สำหรับสร้างเลขที่เอกสารอัตโนมัติที่มีความเป็นเอกลักษณ์ (unique) และสามารถติดตามได้ (traceable) สำหรับเอกสารทุกประเภทในระบบ LCBP3-DMS
### 1.1 Requirements Summary & Scope
- **Auto-generation**: สร้างเลขที่อัตโนมัติ ไม่ซ้ำ (Unique) ยืดหยุ่น
- **Configurable Templates**: รองรับแบบฟอร์มกำหนดค่า สำหรับโปรเจกต์ ประเภทเอกสาร ฯลฯ
- **Uniqueness Guarantee**: การันตี Uniqueness ใน Concurrent Environment (Race Conditions)
@@ -40,6 +44,7 @@ references:
- **Audit Logging**: บันทึกเหตุการณ์ Operation ทั้งหมดอย่างละเอียดครบถ้วน 7 ปี
### 1.2 Technology Stack
| Component | Technology |
| ----------------- | -------------------- |
| Backend Framework | NestJS 10.x |
@@ -50,7 +55,9 @@ references:
| Monitoring | Prometheus + Grafana |
### 1.3 Architectural Decision (AD-DN-001)
ระบบเลือกใช้ **Option A**:
- `document_number_counters` เป็น Core / Authoritative Counter System.
- `document_numbering_configs` (หรือ `document_number_formats`) ใช้เฉพาะกำหนดระเบียบเลข (Template format) และ Permission.
- เหตุผล: ลดความซ้ำซ้อน, ป้องกัน Counter Mismatch, Debug ง่าย, Ops เคลียร์.
@@ -62,30 +69,33 @@ references:
### 2.1 Counter Logic & Reset Policy
การนับเลขจะแยกตาม **Counter Key** ที่ประกอบด้วยหลายส่วน ซึ่งขึ้นกับประเภทเอกสาร
* `(project_id, originator_organization_id, recipient_organization_id, correspondence_type_id, sub_type_id, rfa_type_id, discipline_id, reset_scope)`
- `(project_id, originator_organization_id, recipient_organization_id, correspondence_type_id, sub_type_id, rfa_type_id, discipline_id, reset_scope)`
| Document Type | Reset Policy | Counter Key Format / Details |
| ---------------------------------- | ------------------ | ------------------------------------------------------------------------------ |
| Correspondence (LETTER, MEMO, RFI) | Yearly reset | `(project_id, originator, recipient, type_id, 0, 0, 0, 'YEAR_2025')` |
| Transmittal | Yearly reset | `(project_id, originator, recipient, type_id, sub_type_id, 0, 0, 'YEAR_2025')` |
| RFA | No reset | `(project_id, originator, 0, type_id, 0, rfa_type_id, discipline_id, 'NONE')` |
| Drawing | Separate Namespace | `DRAWING::<project>::<contract>` (ไม่ได้ใช้ counter rules เดียวกัน) |
| Drawing | Separate Namespace | `DRAWING::<project>::<contract>` (ไม่ได้ใช้ counter rules เดียวกัน) |
### 2.2 Format Templates & Supported Tokens
**Supported Token Types**:
* `{PROJECT}`: รหัสโครงการ (เช่น `LCBP3`)
* `{ORIGINATOR}`: รหัสองค์กรส่ง (เช่น `คคง.`)
* `{RECIPIENT}`: รหัสองค์กรรับหลัก (เช่น `สคฉ.3`) *ไม่ใช้กับ RFA
* `{CORR_TYPE}`: รหัสประเภทเอกสาร (เช่น `RFA`, `LETTER`)
* `{SUB_TYPE}`: ประเภทย่อย (สำหรับ Transmittal)
* `{RFA_TYPE}`: รหัสประเภท RFA (เช่น `SDW`, `RPT`)
* `{DISCIPLINE}`: รหัสสาขาวิชา (เช่น `STR`, `CV`)
* `{SEQ:n}`: Running Number ตามจำนวนหลัก `n` ลบด้วยศูนย์นำหน้า
* `{YEAR:B.E.}`, `{YEAR:A.D.}`, `{YYYY}`, `{YY}`, `{MM}`: สัญลักษณ์บอกเวลาและปฏิทิน.
* `{REV}`: Revision Code (เช่น `A`, `B`)
- `{PROJECT}`: รหัสโครงการ (เช่น `LCBP3`)
- `{ORIGINATOR}`: รหัสองค์กรส่ง (เช่น `คคง.`)
- `{RECIPIENT}`: รหัสองค์กรรับหลัก (เช่น `สคฉ.3`) \*ไม่ใช้กับ RFA
- `{CORR_TYPE}`: รหัสประเภทเอกสาร (เช่น `RFA`, `LETTER`)
- `{SUB_TYPE}`: ประเภทย่อย (สำหรับ Transmittal)
- `{RFA_TYPE}`: รหัสประเภท RFA (เช่น `SDW`, `RPT`)
- `{DISCIPLINE}`: รหัสสาขาวิชา (เช่น `STR`, `CV`)
- `{SEQ:n}`: Running Number ตามจำนวนหลัก `n` ลบด้วยศูนย์นำหน้า
- `{YEAR:B.E.}`, `{YEAR:A.D.}`, `{YYYY}`, `{YY}`, `{MM}`: สัญลักษณ์บอกเวลาและปฏิทิน.
- `{REV}`: Revision Code (เช่น `A`, `B`)
**Token Validation Grammar**
```ebnf
TEMPLATE := TOKEN ("-" TOKEN)*
TOKEN := SIMPLE | PARAM
@@ -95,11 +105,13 @@ DIGIT := "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
```
### 2.3 Character & Format Rules (BR-DN-002, BR-DN-003)
- Document number **must be printable UTF8** (Thai, English, Numbers, `-`, `_`, `.`). ไม่อนุญาต Control characters, newlines.
- ต้องยาวระหว่าง 10 ถึง 50 ตัวอักษร
- ต้องกำหนด Token `{SEQ:n}` ลำดับที่ exactly once. ห้ามเป็น Unknown token ใดๆ.
### 2.4 Number State Machine & Idempotency
1. **States Lifecycle**: `RESERVED` (TTL 5 mins) → `CONFIRMED``VOID` / `CANCELLED`. Document ที่ Confirmed แล้วสามารถมีพฤติกรรม VOID ในอนาคตเพื่อแทนที่ด้วยเอกสารใหม่ได้ การ Request จะได้ Document ชุดใหม่ทดแทนต่อเนื่องทันที. ห้าม Reuse เลขเดิมโดยสิ้นเชิง.
2. **Idempotency Key Support**: ทุก API ในการ Generator จำเป็นต้องระบุ HTTP Header `Idempotency-Key` ป้องกันระบบสร้างเลขเบิ้ล (Double Submission). ถ้าระบบได้รับคู่ Request + Key ชุดเดิม จะ Response เลขที่เอกสารเดิม.
@@ -107,22 +119,23 @@ DIGIT := "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
## 3. Functional Requirements
* **FR-DN-001 (Sequential Auto-generation)**: ระบบตอบกลับความรวดเร็วที่ระดับ < 100ms โดยที่ทน Concurrent ได้ ทนต่อปัญหา Duplicate
* **FR-DN-002 (Configurable)**: สามารถเปลี่ยนรูปแบบเทมเพลตผ่านระบบ Admin ได้ด้วยการ Validation ก่อน حفظ
* **FR-DN-003 (Scope-based sequences)**: รองรับ Scope แยกระดับเอกสาร
* **FR-DN-004 (Manual Override)**: ระบบรองรับการตั้งเลขด้วยตนเองสำหรับ Admin Level พร้อมระบุเหตุผลผ่าน Audit Trails เป็นหลักฐานสำคัญ (Import Legacy, Correction)
* **FR-DN-005 (Bulk Import)**: รับเข้าระบบจากไฟล์ Excel/CSV และแก้ไข Counters Sequence ต่อเนื่องพร้อมเช็ค Duplicates.
* **FR-DN-006 (Skip Cancelled)**: ไม่ให้สิทธิ์ดึงเอกสารยกเลิกใช้งานซ้ำ. คงรักษาสภาพ Audit ต่อไป.
* **FR-DN-007 (Void & Replace)**: เปลี่ยน Status เลขเป็น VOID ไม่มีการ Delete. Reference Link เอกสารใหม่ที่เกิดขึ้นทดแทนอิงตาม `voided_from_id`.
* **FR-DN-008 (Race Condition Prevention)**: จัดการ Race Condition (RedLock + Database Pessimistic Locking) เพื่อ Guarantee zero duplicate numbers ที่ Load 1000 req/s.
* **FR-DN-009 (Two-phase Commit)**: แบ่งการออกเลขเป็นช่วง Reserve 5 นาที แล้วค่อยเรียก Confirm หลังจากได้เอกสารที่ Submit เรียบร้อย (ลดอาการเลขหาย/เลขฟันหลอที่ยังไม่ถูกใช้).
* **FR-DN-010/011 (Audit / Metrics Alerting)**: Audit ทุกๆ Step / Transaction ไว้ใน DB ให้เสิร์ชได้เกิน 7 ปี. ส่งแจ้งเตือนถ้า Sequence เริ่มเต็ม (เกิน 90%) หรือ Rate error เริ่มสูง.
- **FR-DN-001 (Sequential Auto-generation)**: ระบบตอบกลับความรวดเร็วที่ระดับ < 100ms โดยที่ทน Concurrent ได้ ทนต่อปัญหา Duplicate
- **FR-DN-002 (Configurable)**: สามารถเปลี่ยนรูปแบบเทมเพลตผ่านระบบ Admin ได้ด้วยการ Validation ก่อน حفظ
- **FR-DN-003 (Scope-based sequences)**: รองรับ Scope แยกระดับเอกสาร
- **FR-DN-004 (Manual Override)**: ระบบรองรับการตั้งเลขด้วยตนเองสำหรับ Admin Level พร้อมระบุเหตุผลผ่าน Audit Trails เป็นหลักฐานสำคัญ (Import Legacy, Correction)
- **FR-DN-005 (Bulk Import)**: รับเข้าระบบจากไฟล์ Excel/CSV และแก้ไข Counters Sequence ต่อเนื่องพร้อมเช็ค Duplicates.
- **FR-DN-006 (Skip Cancelled)**: ไม่ให้สิทธิ์ดึงเอกสารยกเลิกใช้งานซ้ำ. คงรักษาสภาพ Audit ต่อไป.
- **FR-DN-007 (Void & Replace)**: เปลี่ยน Status เลขเป็น VOID ไม่มีการ Delete. Reference Link เอกสารใหม่ที่เกิดขึ้นทดแทนอิงตาม `voided_from_id`.
- **FR-DN-008 (Race Condition Prevention)**: จัดการ Race Condition (RedLock + Database Pessimistic Locking) เพื่อ Guarantee zero duplicate numbers ที่ Load 1000 req/s.
- **FR-DN-009 (Two-phase Commit)**: แบ่งการออกเลขเป็นช่วง Reserve 5 นาที แล้วค่อยเรียก Confirm หลังจากได้เอกสารที่ Submit เรียบร้อย (ลดอาการเลขหาย/เลขฟันหลอที่ยังไม่ถูกใช้).
- **FR-DN-010/011 (Audit / Metrics Alerting)**: Audit ทุกๆ Step / Transaction ไว้ใน DB ให้เสิร์ชได้เกิน 7 ปี. ส่งแจ้งเตือนถ้า Sequence เริ่มเต็ม (เกิน 90%) หรือ Rate error เริ่มสูง.
---
## 4. Module System & Code Architecture
### 4.1 Folder Structure
```
backend/src/modules/document-numbering/
├── document-numbering.module.ts
@@ -138,6 +151,7 @@ backend/src/modules/document-numbering/
### 4.2 Sequence Process Architecture
**1. Number Generation Process Diagram**
```mermaid
sequenceDiagram
participant C as Client
@@ -166,6 +180,7 @@ sequenceDiagram
```
**2. Two-Phase Commit Pattern (Reserve / Confirm)**
```mermaid
sequenceDiagram
participant C as Client
@@ -189,6 +204,7 @@ sequenceDiagram
```
### 4.3 Lock Service (Redis Redlock Example)
```typescript
@Injectable()
export class DocumentNumberingLockService {
@@ -204,6 +220,7 @@ export class DocumentNumberingLockService {
```
### 4.4 Counter Service & Transaction Strategy (Optimistic Example)
```typescript
async incrementCounter(counterKey: CounterKey): Promise<number> {
const MAX_RETRIES = 2;
@@ -231,6 +248,7 @@ async incrementCounter(counterKey: CounterKey): Promise<number> {
## 5. Database Schema Details
### 5.1 Format Storage & Counters
```sql
-- Format Template Configuration
CREATE TABLE document_number_formats (
@@ -261,6 +279,7 @@ CREATE TABLE document_number_counters (
```
### 5.2 Two-Phase Commit Reservations
```sql
CREATE TABLE document_number_reservations (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
@@ -274,6 +293,7 @@ CREATE TABLE document_number_reservations (
```
### 5.3 Audit Trails & Error Logs
```sql
CREATE TABLE document_number_audit (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
@@ -313,23 +333,26 @@ CREATE TABLE document_number_errors (
## 7. Security, Error Handling & Concurrency Checklists
**Fallback Strategy for Database Lock Failures**:
1. System attempt to acquire `Redlock`.
2. Redis Down? → **Fallback DB-only Lock** Transaction Layer.
3. Redis Online but Timeout `>3 Times`? → Return HTTP 503 (Exponential Backoff).
4. Save Failed via TypeORM Version Confilct? → Retry Loop `2 Times`, otherwise Return 409 Conflict.
**Rate Limiting Profiles**:
* Single User Threshold: `10 requests/minute`.
* Specific Request IP: `50 requests/minute`.
- Single User Threshold: `10 requests/minute`.
- Specific Request IP: `50 requests/minute`.
**Authorization Policies**:
* `Super Admin` เท่านั้นที่บังคับสั่ง `Reset Counter` ให้เริ่มนับศูนย์ได้เมื่อฉุกเฉิน.
* กฎ Audit Log System ระบุชัดเจนว่าต้อง Retain Information ไม่ต่ำกว่า 7 ปี.
- `Super Admin` เท่านั้นที่บังคับสั่ง `Reset Counter` ให้เริ่มนับศูนย์ได้เมื่อฉุกเฉิน.
- กฎ Audit Log System ระบุชัดเจนว่าต้อง Retain Information ไม่ต่ำกว่า 7 ปี.
## 8. Monitoring / Observability (Prometheus + Grafana)
| Condition Event | Prometheus Counter Target | Severity Reaction |
| ---------------------- | -------------------------------- | ----------------------------------------------------------------- |
| Condition Event | Prometheus Counter Target | Severity Reaction |
| ---------------------- | -------------------------------- | ------------------------------------------------------------------ |
| Utilization `>95%` | `numbering_sequence_utilization` | 🔴 **CRITICAL** (PagerDuty/Slack). Limit Maximum sequence reached. |
| Redis Downtime `>1M` | Health System | 🔴 **CRITICAL** (PagerDuty/Slack) |
| Lock Latency p95 `>1s` | `numbering_lock_wait_seconds` | 🟡 **WARNING** (Slack). Redis connection struggling. |
@@ -340,11 +363,13 @@ CREATE TABLE document_number_errors (
## 9. Testing & Rollout Migration Strategies
### 9.1 Test Approach Requirements
* **Unit Tests**: Template Tokens Validations, Error handling retry, Optimistic locking checks.
* **Concurrency Integration Test**: Assert >1000 requests without double generating numbers simultaneously per `project_id`.
* **E2E Load Sequence Flow**: Mocking bulk API loads over 500 requests per seconds via CI/CD load pipeline.
- **Unit Tests**: Template Tokens Validations, Error handling retry, Optimistic locking checks.
- **Concurrency Integration Test**: Assert >1000 requests without double generating numbers simultaneously per `project_id`.
- **E2E Load Sequence Flow**: Mocking bulk API loads over 500 requests per seconds via CI/CD load pipeline.
### 9.2 Data Rollout Plan (Legacy Legacy Import)
1. Dump out existing Sequence numbers (Extracted Document Strings).
2. Write validation script for Sequence Max Counts.
3. Import to Table `document_number_counters` as Manual Override API Method context (`FR-DN-004`).
@@ -353,6 +378,7 @@ CREATE TABLE document_number_errors (
---
**Best Practices Checklist**
-**DO**: Two-Phase Commit (`Reserve` + `Confirm`) ให้เป็น Routine System Concept.
-**DO**: DB Fallback เมื่อ Redis ดาวน์. ให้ Availability สูงสุด ไม่หยุดทำงาน.
-**DO**: ข้ามเลขที่ยกเลิกทั้งหมดห้ามมีการ Re-Use เด็ดขาด ไม่ว่าเจตนาใดๆ.
@@ -361,11 +387,11 @@ CREATE TABLE document_number_errors (
-**DON'T**: ลืมเขียน Idempotency-Key สำหรับ Request.
---
**Document Version**: 1.8.0
**Created By**: Development Team
**End of Document**
---
## 10. Operations & Infrastructure Guidelines
@@ -374,18 +400,18 @@ CREATE TABLE document_number_errors (
### 1.1. Response Time Targets
| Metric | Target | Measurement |
| ---------------- | -------- | ------------------------ |
| Metric | Target | Measurement |
| ---------------- | ---------- | ---------------------------- |
| 95th percentile | ≤ 2 วินาที | ตั้งแต่ request ถึง response |
| 99th percentile | ≤ 5 วินาที | ตั้งแต่ request ถึง response |
| Normal operation | ≤ 500ms | ไม่มี retry |
| Normal operation | ≤ 500ms | ไม่มี retry |
### 1.2. Throughput Targets
| Load Level | Target | Notes |
| -------------- | ----------- | ------------------------ |
| Normal load | ≥ 50 req/s | ใช้งานปกติ |
| Peak load | ≥ 100 req/s | ช่วงเร่งงาน |
| Normal load | ≥ 50 req/s | ใช้งานปกติ |
| Peak load | ≥ 100 req/s | ช่วงเร่งงาน |
| Burst capacity | ≥ 200 req/s | Short duration (< 1 min) |
### 1.3. Availability SLA
@@ -395,7 +421,6 @@ CREATE TABLE document_number_errors (
- **Recovery Time Objective (RTO)**: ≤ 30 นาที
- **Recovery Point Objective (RPO)**: ≤ 5 นาที
### 2. Infrastructure Setup
### 2.1. Database Configuration
@@ -567,12 +592,12 @@ services:
- backend
```
### 4. Troubleshooting Runbooks
### 4.1. Scenario: Redis Unavailable
**Symptoms:**
- Alert: `RedisUnavailable`
- System falls back to DB-only locking
- Performance degraded 30-50%
@@ -580,22 +605,26 @@ services:
**Action Steps:**
1. **Check Redis status:**
```bash
docker exec lcbp3-redis redis-cli ping
# Expected: PONG
```
2. **Check Redis logs:**
```bash
docker logs lcbp3-redis --tail=100
```
3. **Restart Redis (if needed):**
```bash
docker restart lcbp3-redis
```
4. **Verify failover (if using Sentinel):**
```bash
docker exec lcbp3-redis-sentinel redis-cli -p 26379 SENTINEL masters
```
@@ -607,29 +636,34 @@ services:
### 4.2. Scenario: High Lock Failure Rate
**Symptoms:**
- Alert: `HighLockFailureRate` (> 10%)
- Users report "ระบบกำลังยุ่ง" errors
**Action Steps:**
1. **Check concurrent load:**
```bash
# Check current request rate
curl http://prometheus:9090/api/v1/query?query=rate(docnum_generation_duration_ms_count[1m])
```
2. **Check database connections:**
```sql
SHOW PROCESSLIST;
-- Look for waiting/locked queries
```
3. **Check Redis memory:**
```bash
docker exec lcbp3-redis redis-cli INFO memory
```
4. **Scale up if needed:**
```bash
# Increase backend replicas
docker-compose up -d --scale backend=5
@@ -644,12 +678,14 @@ services:
### 4.3. Scenario: Slow Performance
**Symptoms:**
- Alert: `SlowDocumentNumberGeneration`
- P95 > 2 seconds
**Action Steps:**
1. **Check database query performance:**
```sql
SELECT * FROM document_number_counters USE INDEX (idx_counter_lookup)
WHERE project_id = 2 AND correspondence_type_id = 6 AND current_year = 2025;
@@ -659,16 +695,19 @@ services:
```
2. **Check for missing indexes:**
```sql
SHOW INDEX FROM document_number_counters;
```
3. **Check Redis latency:**
```bash
docker exec lcbp3-redis redis-cli --latency
```
4. **Check network latency:**
```bash
ping mariadb-master
ping redis-master
@@ -682,12 +721,14 @@ services:
### 4.4. Scenario: Version Conflicts
**Symptoms:**
- High retry count
- Users report "เลขที่เอกสารถูกเปลี่ยน" errors
**Action Steps:**
1. **Check concurrent requests to same counter:**
```sql
SELECT
project_id,
@@ -701,6 +742,7 @@ services:
```
2. **Investigate specific counter:**
```sql
SELECT * FROM document_number_counters
WHERE project_id = X AND correspondence_type_id = Y;
@@ -720,7 +762,6 @@ services:
- Increase retry count in application config
- Consider manual counter adjustment (last resort)
### 5. Maintenance Procedures
### 5.1. Counter Reset (Manual)
@@ -730,6 +771,7 @@ services:
**Steps:**
1. **Request approval via API:**
```bash
POST /api/v1/document-numbering/configs/{configId}/reset-counter
{
@@ -757,6 +799,7 @@ services:
4. Template changes do NOT affect existing documents
**API Call:**
```bash
PUT /api/v1/document-numbering/configs/{configId}
{
@@ -768,6 +811,7 @@ PUT /api/v1/document-numbering/configs/{configId}
### 5.3. Database Maintenance
**Weekly Tasks:**
- Check slow query log
- Optimize tables if needed:
```sql
@@ -776,11 +820,10 @@ PUT /api/v1/document-numbering/configs/{configId}
```
**Monthly Tasks:**
- Review and archive old audit logs (> 2 years)
- Check index usage:
```sql
SELECT * FROM sys.schema_unused_indexes
WHERE object_schema = 'lcbp3_db';
```
@@ -54,7 +54,6 @@ related:
- มีการใช้ Caching กับข้อมูลที่เรียกใช้บ่อย และใช้ Pagination ในตารางข้อมูลเพื่อจัดการข้อมูลจำนวนมาก
- ตัวชี้วัดประสิทธิภาพ:
- **API Response Time:** < 200ms (90th percentile) สำหรับ operation ทั่วไป
- **Search Query Performance:** < 500ms สำหรับการค้นหาขั้นสูง
- **File Upload Performance:** < 30 seconds สำหรับไฟล์ขนาด 50MB
@@ -64,7 +63,6 @@ related:
- **Application Startup Time:** < 30 seconds
- 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)
@@ -13,31 +13,41 @@ last_updated: 2026-02-23
หน้านี้รวบรวมข้อกำหนดการใช้งาน (Functional Requirements) ที่แบ่งตามระบบย่อย (Modules) ทั้งหมด
## 1. การจัดการโครงการและองค์กร (Project Management)
[01-02-01-project-management.md](01-02-01-project-management.md)
## 2. การจัดการจดหมายรับ-ส่ง (Correspondence)
[01-02-02-correspondence.md](01-02-02-correspondence.md)
## 3. การจัดการขออนุมัติวัสดุอุปกรณ์ (RFA)
[01-02-03-rfa.md](01-02-03-rfa.md)
## 4. การจัดการแบบคู่สัญญา (Contract Drawing)
[01-02-04-contract-drawing.md](01-02-04-contract-drawing.md)
## 5. การจัดการแบบก่อสร้าง (Shop Drawing)
[01-02-05-shop-drawing.md](01-02-05-shop-drawing.md)
## 6. กระบวนการอนุมัติ (Unified Workflow)
[01-02-06-unified-workflow.md](01-02-06-unified-workflow.md)
## 7. เอกสารนำส่ง (Transmittals)
[01-02-07-transmittals.md](01-02-07-transmittals.md)
## 8. ใบเวียนเอกสาร (Circulation Sheet)
[01-02-08-circulation-sheet.md](01-02-08-circulation-sheet.md)
## 9. ประวัติการแก้ไข (Logs / Revisions)
[01-02-09-logs.md](01-02-09-logs.md)
## 10. รายละเอียด JSON (JSON Details)
[01-02-10-json-details.md](01-02-10-json-details.md)
@@ -24,7 +24,6 @@ related:
## 3.12.2 โครงสร้าง JSON Schema
- ระบบต้องมี predefined JSON schemas สำหรับประเภทเอกสารต่างๆ:
- 3.12.2.1 Correspondence Types
- GENERIC: ข้อมูลพื้นฐานสำหรับเอกสารทั่วไป
- RFI: รายละเอียดคำถามและข้อมูลทางเทคนิค
@@ -67,7 +66,6 @@ related:
## 3.12.7 JSON Schema Migration Strategy (เพิ่มเติม)
- สำหรับ Schema Breaking Changes:
- Phase 1 - Add New Column
ALTER TABLE correspondence_revisions
ADD COLUMN ref_project_id_v2 INT GENERATED ALWAYS AS
@@ -79,7 +77,6 @@ related:
- Phase 3 - Switch Application Code
- Deploy code ที่ใช้ path ใหม่
- Phase 4 - Remove Old Column
- หลังจาก verify แล้วว่าไม่มี error
- Drop old virtual column
+98 -14
View File
@@ -1,15 +1,18 @@
# 📖 User Stories — LCBP3-DMS v1.8.0
---
title: 'User Stories — All Modules'
version: 1.0.0
status: DRAFT
owner: Nattanin Peancharoen (Product Owner)
last_updated: 2026-03-11
related:
- specs/01-Requirements/01-01-objectives.md
- specs/01-Requirements/01-05-acceptance-criteria.md
- specs/01-Requirements/01-03-modules/
- specs/01-Requirements/01-01-objectives.md
- specs/01-Requirements/01-05-acceptance-criteria.md
- specs/01-Requirements/01-03-modules/
---
## 📖 วิธีอ่าน
@@ -24,6 +27,7 @@ related:
## 👥 Epic 1: Authentication & User Management
### US-001 — Login เข้าระบบ
**Priority:** 🔴 M | **SP:** 2 | **AC:** AC-AUTH-001, AC-AUTH-002
```
@@ -31,7 +35,9 @@ As a ผู้ใช้งานทุกประเภท
I want to login ด้วย Username + Password
So that ฉันเข้าถึงระบบตามบทบาทของตนได้
```
**Done When:**
- Login สำเร็จ → Dashboard | Password ผิด → Error กลางๆ
- ผิดเกิน 5 ครั้งใน 1 นาที → 429 Rate Limit
- Audit Log: `LOGIN_SUCCESS` / `LOGIN_FAILED` + IP
@@ -39,6 +45,7 @@ So that ฉันเข้าถึงระบบตามบทบาทขอ
---
### US-002 — เปลี่ยนรหัสผ่านครั้งแรก
**Priority:** 🔴 M | **SP:** 2 | **AC:** AC-AUTH-003
```
@@ -46,13 +53,16 @@ As a ผู้ใช้งานใหม่ที่เพิ่งถูกส
I want to ถูกบังคับเปลี่ยนรหัสผ่านใน Login ครั้งแรก
So that ฉันมีรหัสผ่านที่ฉันรู้คนเดียว
```
**Done When:**
- Login ครั้งแรก → Redirect หน้าเปลี่ยน Password ทันที (ผ่านไม่ได้)
- Password ใหม่ต้องผ่าน Policy (8+ ตัว, อักษรใหญ่-เล็ก-ตัวเลข)
---
### US-003 — จัดการ Profile ส่วนตัว
**Priority:** 🟠 S | **SP:** 2
```
@@ -60,13 +70,16 @@ As a ผู้ใช้งานทุกประเภท
I want to แก้ไขข้อมูลส่วนตัวและเปลี่ยนรหัสผ่าน
So that ฉันดูแล Account ของตัวเองได้
```
**Done When:**
- แก้ไข ชื่อ, Email, ช่องทาง Notification ได้
- เปลี่ยน Password ต้องยืนยัน Password เก่า
---
### US-004 — Superadmin สร้างองค์กร + Project + Contract
**Priority:** 🔴 M | **SP:** 5 | **AC:** AC-ADMIN-001, AC-ADMIN-002
```
@@ -74,7 +87,9 @@ As a Superadmin
I want to สร้าง Organization, Project, Contract และผูกความสัมพันธ์กัน
So that ระบบพร้อมให้แต่ละองค์กรเริ่มทำงานได้
```
**Done When:**
- สร้าง Org → แต่งตั้ง Org Admin ได้ทันที
- สร้าง Project → ผูก Organizations (หลายองค์กร)
- สร้าง Contract → 1 Contractor ต่อ 1 Contract
@@ -83,6 +98,7 @@ So that ระบบพร้อมให้แต่ละองค์กรเ
---
### US-005 — Org Admin จัดการ User ในองค์กร
**Priority:** 🔴 M | **SP:** 3 | **AC:** AC-ADMIN-003
```
@@ -90,7 +106,9 @@ As a Org Admin
I want to เพิ่ม/แก้ไข/Deactivate User และกำหนด Role
So that ทีมงานเข้าถึงระบบได้ตามหน้าที่
```
**Done When:**
- เพิ่ม User → Email แจ้ง Credentials อัตโนมัติ
- Deactivate ได้ (ไม่ Delete — Audit ยังอยู่)
- เห็นเฉพาะ User ในองค์กรตัวเอง
@@ -98,6 +116,7 @@ So that ทีมงานเข้าถึงระบบได้ตามห
---
### US-006 — Permission Isolation ระหว่าง Contractor
**Priority:** 🔴 M | **SP:** 2 | **AC:** AC-ADMIN-004
```
@@ -105,7 +124,9 @@ As a ระบบ
I want to ป้องกัน Contractor A เห็นข้อมูล Contractor B
So that ข้อมูลทางธุรกิจของแต่ละ Contractor เป็นความลับ
```
**Done When:**
- Contractor A→ ไม่เห็น List เอกสาร B | พยายาม Access URL ตรงๆ → 403
- Audit Log บันทึก Unauthorized Attempt
@@ -114,6 +135,7 @@ So that ข้อมูลทางธุรกิจของแต่ละ Co
## 📨 Epic 2: Correspondence Management
### US-007 — สร้าง Correspondence Draft
**Priority:** 🔴 M | **SP:** 5 | **AC:** AC-CORR-001
```
@@ -121,7 +143,9 @@ As a Document Control
I want to สร้าง Correspondence (Letter/RFI) ในสถานะ Draft พร้อมแนบไฟล์
So that เตรียมเอกสารได้ก่อนส่งโดยองค์กรอื่นยังไม่เห็น
```
**Done When:**
- Form: Subject, To (หลายองค์กร), CC, Doc Type, Attachments (PDF/ZIP)
- Drag-and-Drop Multi-file | ClamAV Scan ทุกไฟล์
- Auto-Save Draft → LocalStorage (กันข้อมูลสูญ)
@@ -130,6 +154,7 @@ So that เตรียมเอกสารได้ก่อนส่งโด
---
### US-008 — Submit Correspondence + Auto Number
**Priority:** 🔴 M | **SP:** 5 | **AC:** AC-CORR-002
```
@@ -137,7 +162,9 @@ As a Document Control
I want to Submit Correspondence และให้ระบบออกเลขอัตโนมัติ
So that เอกสารได้รับเลขที่ไม่ซ้ำและส่งถึงผู้รับทันที
```
**Done When:**
- ออกเลขตาม Template (เช่น `LCBP3-สค.-กทท.-LETTER-0001-68`)
- เลขไม่ซ้ำแม้ Submit พร้อมกัน (Redis Redlock)
- สถานะ → SUBMITTED | Workflow Instance ถูกสร้าง
@@ -146,6 +173,7 @@ So that เอกสารได้รับเลขที่ไม่ซ้ำ
---
### US-009 — ดู Correspondence ใน Inbox
**Priority:** 🔴 M | **SP:** 3 | **AC:** AC-CORR-003
```
@@ -153,13 +181,16 @@ As a Document Control ขององค์กรผู้รับ
I want to เห็น Correspondence ที่ส่งมาถึงองค์กรในรายการ
So that ดำเนินการได้ทันที (สร้าง Circulation, ตอบกลับ)
```
**Done When:**
- List แสดง Received/Sent แยก | PDF Viewer ในแอป (Streaming)
- ดาวน์โหลดไฟล์แนบได้ (ถ้ามีสิทธิ์)
---
### US-010 — อ้างอิง + Tag เอกสาร
**Priority:** 🟠 S | **SP:** 3 | **AC:** AC-CORR-004
```
@@ -167,13 +198,16 @@ As a Document Control
I want to อ้างอิงเอกสารเก่าและกำหนด Tag หลาย Tag
So that จัดกลุ่มเอกสารและ Navigate ข้าม Thread ได้
```
**Done When:**
- ค้นหาและเลือก Reference Documents | Link ระหว่างเอกสาร (คลิก Navigate)
- กำหนด Tag หลาย Tag | ค้นหาได้จาก Tag
---
### US-011 — ยกเลิก Correspondence (Admin)
**Priority:** 🟡 C | **SP:** 2 | **AC:** AC-CORR-005
```
@@ -181,7 +215,9 @@ As a Org Admin / Superadmin
I want to ยกเลิก Correspondence ที่ Submit แล้ว พร้อมระบุเหตุผล
So that เอกสารที่ส่งผิดถูกระงับโดยมีหลักฐาน Audit
```
**Done When:**
- ต้องกรอก cancel_reason | สถานะ → CANCELLED
- Editor/Viewer ทำ Cancel ไม่ได้ → 403
@@ -190,6 +226,7 @@ So that เอกสารที่ส่งผิดถูกระงับโ
## 📋 Epic 3: RFA Management
### US-012 — สร้าง RFA + Shop Drawing
**Priority:** 🔴 M | **SP:** 8 | **AC:** AC-RFA-001
```
@@ -197,7 +234,9 @@ As a Document Control ของ Contractor
I want to สร้าง RFA พร้อม Upload Shop Drawing
So that ยื่นขออนุมัติแบบก่อสร้างจากที่ปรึกษาได้อย่างเป็นระบบ
```
**Done When:**
- Form: RFA Type, Discipline, Shop Drawing (PDF/DWG/ZIP)
- 1 Shop Drawing Revision = 1 RFA เท่านั้น
- ClamAV Scan | Draft (ไม่เห็นข้ามองค์กร)
@@ -205,6 +244,7 @@ So that ยื่นขออนุมัติแบบก่อสร้าง
---
### US-013 — Submit RFA ผ่าน Transmittal
**Priority:** 🔴 M | **SP:** 8 | **AC:** AC-RFA-002, AC-TRM-001
```
@@ -212,7 +252,9 @@ As a Document Control ของ Contractor
I want to สร้าง Transmittal รวม RFA หลายฉบับ ส่งไปยังที่ปรึกษา
So that ส่งเอกสารเป็นชุด ที่ปรึกษาได้รับทุกฉบับพร้อมกัน
```
**Done When:**
- Transmittal → เลือก RFA หลายฉบับ | ออกเลขเองเป็น Correspondence
- RFA แต่ละฉบับ → ออกเลขตาม Format `LCBP3-RFA-{DISCIPLINE}-{SEQ}`
- ที่ปรึกษา → Notification
@@ -220,6 +262,7 @@ So that ส่งเอกสารเป็นชุด ที่ปรึก
---
### US-014 — Review RFA (Approved / w.Comments / Rejected)
**Priority:** 🔴 M | **SP:** 5 | **AC:** AC-RFA-003~005
```
@@ -227,7 +270,9 @@ As a Engineer ของ Supervisor Organization
I want to เปิดดู Shop Drawing และให้คำตอบ RFA
So that Contractor ได้รับผลพิจารณาและดำเนินการต่อได้
```
**Done When:**
- PDF Viewer Streaming | ปุ่ม: Approved / Approved w/Comments / Rejected
- Comment บังคับสำหรับ Approved w/Comments และ Rejected
- Originator → Notification | Workflow History บันทึกครบ
@@ -235,6 +280,7 @@ So that Contractor ได้รับผลพิจารณาและดำ
---
### US-015 — ยื่น RFA Revision ใหม่หลัง Reject
**Priority:** 🟠 S | **SP:** 5 | **AC:** AC-RFA-006
```
@@ -242,7 +288,9 @@ As a Document Control ของ Contractor
I want to สร้าง RFA Revision ใหม่ (Rev.B) หลัง Rev.A ถูก Rejected
So that แก้ไขและยื่น Shop Drawing เวอร์ชันใหม่ได้
```
**Done When:**
- Rev.B ผูกกับ Shop Drawing Rev.B | Rev.A ยังดูได้
- Revision Code เปลี่ยนตาม Sequence (A→B→C)
@@ -251,6 +299,7 @@ So that แก้ไขและยื่น Shop Drawing เวอร์ชั
## 📐 Epic 4: Drawing Management
### US-016 — Upload Contract Drawing
**Priority:** 🟠 S | **SP:** 5 | **AC:** AC-DRW-001
```
@@ -258,13 +307,16 @@ As a Document Control ของ Design Consultant
I want to Upload แบบคู่สัญญา (Contract Drawing) พร้อมกำหนดหมวดหมู่
So that Contractor ใช้อ้างอิงใน Shop Drawing ได้
```
**Done When:**
- Upload PDF → Drawing Number, Category, Discipline
- Shop Drawing Link Reference กับ Contract Drawing ได้
---
### US-017 — Upload Shop Drawing + Revision Control
**Priority:** 🟠 S | **SP:** 5 | **AC:** AC-DRW-002, AC-DRW-003
```
@@ -272,7 +324,9 @@ As a Document Control ของ Contractor
I want to Upload Shop Drawing พร้อม Reference Contract Drawing และจัดการ Revision
So that ผู้ Review เห็น Revision ล่าสุด และ History ทุก Revision
```
**Done When:**
- ค้นหาและเลือก Contract Drawing ที่ Reference
- Rev.ใหม่ → Rev.เก่า Mark "Superseded" แต่ยังดูได้
- 1 Revision = 1 RFA เท่านั้น (Constraint enforced)
@@ -282,6 +336,7 @@ So that ผู้ Review เห็น Revision ล่าสุด และ Hist
## ⚙️ Epic 5: Workflow Engine
### US-018 — ดู Workflow Diagram ของเอกสาร
**Priority:** 🟠 S | **SP:** 3 | **AC:** AC-WF-004
```
@@ -289,7 +344,9 @@ As a ผู้ใช้งานที่มีสิทธิ์ดูเอก
I want to เห็น Workflow Diagram แบบ Visual
So that รู้ทันทีว่าเอกสารอยู่ขั้นตอนไหน ใครถืออยู่
```
**Done When:**
- Workflow Diagram แสดง State ปัจจุบัน (Highlight)
- คลิก Step ที่ผ่านมา → Audit Log ย่อย (ใคร/เมื่อไหร่/Comment)
- Step ที่ยังไม่ถึง → Disabled Style
@@ -297,6 +354,7 @@ So that รู้ทันทีว่าเอกสารอยู่ขั้
---
### US-019 — Approve / Reject ผ่าน Workflow
**Priority:** 🔴 M | **SP:** 5 | **AC:** AC-WF-002, AC-WF-003
```
@@ -304,7 +362,9 @@ As a Reviewer ที่ถูก Assign ใน Workflow
I want to Approve / Reject เอกสารในขั้นตอนที่ฉันรับผิดชอบ
So that Workflow เดินหน้าหรือส่งกลับตามการตัดสินใจ
```
**Done When:**
- เห็นปุ่ม Action เฉพาะ Step ที่เป็นของฉัน
- Wrong Role → ปุ่มซ่อน / 403 ถ้าเรียก API ตรงๆ
- ทุก Action → Workflow History + Timestamp
@@ -312,6 +372,7 @@ So that Workflow เดินหน้าหรือส่งกลับตา
---
### US-020 — Force Proceed + Revert (Document Control)
**Priority:** 🟡 C | **SP:** 3 | **AC:** AC-WF-005
```
@@ -319,13 +380,16 @@ As a Document Control
I want to บังคับข้ามหรือย้อน Workflow Step ในกรณีพิเศษ
So that Workflow ที่ติดขัดถูก Unblock ได้อย่างมีหลักฐาน
```
**Done When:**
- ปุ่ม "Force Proceed" / "Revert" เห็นได้เฉพาะ Document Control ขึ้นไป
- ต้องกรอก reason | Audit Log: FORCE_PROCEED / REVERT
---
### US-021 — กำหนด Deadline ใน Workflow
**Priority:** 🟡 C | **SP:** 3 | **AC:** AC-WF-006
```
@@ -333,7 +397,9 @@ As a Document Control
I want to กำหนด Deadline ให้แต่ละ Organization ใน Workflow
So that ดำเนินการทันเวลา ไม่เกิดความล่าช้า
```
**Done When:**
- กำหนด Due Date ต่อ Org ใน Workflow
- เกิน Deadline → Dashboard Overdue Badge + Notification
- 2 วันก่อน → Reminder Notification
@@ -343,6 +409,7 @@ So that ดำเนินการทันเวลา ไม่เกิด
## 📄 Epic 6: Circulation Sheet
### US-022 — สร้าง Circulation Sheet
**Priority:** 🟠 S | **SP:** 5 | **AC:** AC-CIRC-001
```
@@ -350,7 +417,9 @@ As a Document Control ภายในองค์กร
I want to สร้างใบเวียนสำหรับ Correspondence พร้อมกำหนดผู้รับผิดชอบ
So that งานถูก Assign ชัดเจนและมีหลักฐาน
```
**Done When:**
- กำหนด Main / Action / Information Assignees (หลายคน)
- Assignees → In-App + Email Notification
- Internal Only (ไม่เห็นข้ามองค์กร)
@@ -358,6 +427,7 @@ So that งานถูก Assign ชัดเจนและมีหลัก
---
### US-023 — ติดตาม Circulation Deadline + ปิด
**Priority:** 🟠 S | **SP:** 3 | **AC:** AC-CIRC-002, AC-CIRC-003
```
@@ -365,7 +435,9 @@ As a Assignee / Document Control
I want to เห็น Deadline งานของฉันและปิด Circulation เมื่อเสร็จ
So that ไม่พลาดงาน และ Track สถานะได้ชัดเจน
```
**Done When:**
- Dashboard My Tasks แสดง Deadline + Overdue Badge
- ปิด Circulation → สถานะ CLOSED + Timestamp
- My Tasks ลบรายการที่ปิดแล้ว
@@ -375,6 +447,7 @@ So that ไม่พลาดงาน และ Track สถานะได้
## 🔍 Epic 7: Search & Notifications
### US-024 — ค้นหาเอกสาร Full-text
**Priority:** 🟠 S | **SP:** 5 | **AC:** AC-SRCH-001
```
@@ -382,13 +455,16 @@ As a ผู้ใช้งานทุกประเภท
I want to ค้นหาเอกสารด้วย Keyword ได้รวดเร็ว
So that พบเอกสารที่ต้องการโดยไม่ต้องจำเลขที่แม่นยำ
```
**Done When:**
- ผล Search < 500ms (Elasticsearch) | ค้นจาก เลขเอกสาร, Subject, Tag
- แสดงเฉพาะเอกสารที่มีสิทธิ์เห็น
---
### US-025 — รับ Notification (Email + In-App)
**Priority:** 🟠 S | **SP:** 5 | **AC:** AC-NOTIF-001, AC-NOTIF-002
```
@@ -396,7 +472,9 @@ As a ผู้ใช้งาน
I want to รับ Email และ In-App Notification เมื่อมี Event เกี่ยวข้องกับฉัน
So that ไม่พลาดงานโดยไม่ต้องเช็คระบบตลอดเวลา
```
**Done When:**
- Email ถูกส่งภายใน 5 นาที หลัง Event (BullMQ Queue)
- Bell Icon Unread Count | คลิก → Navigate ไปเอกสาร
- Retry 3 ครั้ง ถ้าส่งไม่ได้ → Dead Letter Queue
@@ -406,6 +484,7 @@ So that ไม่พลาดงานโดยไม่ต้องเช็ค
## 💾 Epic 8: File & Dashboard
### US-026 — Upload ไฟล์ + ดู PDF ในแอป
**Priority:** 🟠 S | **SP:** 5 | **AC:** AC-STOR-001, AC-STOR-003
```
@@ -413,7 +492,9 @@ As a Document Control / Reviewer
I want to Upload ไฟล์หลายไฟล์และดู PDF ในแอปได้เลย
So that ทำงานเร็วขึ้นและลดความเสี่ยงข้อมูลรั่วจาก Download
```
**Done When:**
- Drag-and-Drop Multi-file | ClamAV Scan < 30s
- In-App PDF Viewer (Range Requests Streaming)
- ปุ่ม Download → Disabled สำหรับ Viewer-only
@@ -421,6 +502,7 @@ So that ทำงานเร็วขึ้นและลดความเส
---
### US-027 — Dashboard KPI ส่วนตัว
**Priority:** 🟠 S | **SP:** 5 | **AC:** AC-DASH-001
```
@@ -428,7 +510,9 @@ As a ผู้ใช้งานทุกประเภท
I want to เห็น Dashboard สรุปงานของฉันทันทีที่ Login
So that รู้สถานะงานโดยไม่ต้องไล่ดูแต่ละ Module
```
**Done When:**
- KPI Cards: Pending RFA, Pending Correspondence, Overdue Circulation
- My Tasks Table: Circulation ที่ค้าง + Deadline
- Filter ตาม Permission ของ User
@@ -437,17 +521,17 @@ So that รู้สถานะงานโดยไม่ต้องไล่
## 📊 Story Map Summary
| Epic | Stories | 🔴 Must | 🟠 Should | 🟡 Could |
|------|---------|---------|----------|--------|
| Auth & Users | US-001~006 | 4 | 1 | 1 |
| Correspondence | US-007~011 | 2 | 2 | 1 |
| RFA | US-012~015 | 2 | 2 | 0 |
| Drawing | US-016~017 | 0 | 2 | 0 |
| Workflow | US-018~021 | 1 | 1 | 2 |
| Circulation | US-022~023 | 0 | 2 | 0 |
| Search & Notify | US-024~025 | 0 | 2 | 0 |
| File & Dashboard | US-026~027 | 0 | 2 | 0 |
| **รวม** | **27** | **9** | **14** | **4** |
| Epic | Stories | 🔴 Must | 🟠 Should | 🟡 Could |
| ---------------- | ---------- | ------- | --------- | -------- |
| Auth & Users | US-001~006 | 4 | 1 | 1 |
| Correspondence | US-007~011 | 2 | 2 | 1 |
| RFA | US-012~015 | 2 | 2 | 0 |
| Drawing | US-016~017 | 0 | 2 | 0 |
| Workflow | US-018~021 | 1 | 1 | 2 |
| Circulation | US-022~023 | 0 | 2 | 0 |
| Search & Notify | US-024~025 | 0 | 2 | 0 |
| File & Dashboard | US-026~027 | 0 | 2 | 0 |
| **รวม** | **27** | **9** | **14** | **4** |
> **MVP Sprint Focus:** US-001~006, US-007~008, US-012~014, US-019 — ครอบคลุม Core Happy Path ทั้งหมด
File diff suppressed because it is too large Load Diff
@@ -1,17 +1,20 @@
# 🛡️ Module Edge Cases & Business Rules — LCBP3-DMS v1.8.0
---
title: 'Edge Cases, Business Rules, and Anti-Bug Specifications'
version: 1.0.0
status: DRAFT
owner: Nattanin Peancharoen (Product Owner / System Architect)
last_updated: 2026-03-11
related:
- specs/01-Requirements/01-05-acceptance-criteria.md
- specs/01-Requirements/01-02-business-rules/01-02-02-doc-numbering-rules.md
- specs/06-Decision-Records/ADR-001-unified-workflow-engine.md
- specs/06-Decision-Records/ADR-016-security-authentication.md
- specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql
- specs/01-Requirements/01-05-acceptance-criteria.md
- specs/01-Requirements/01-02-business-rules/01-02-02-doc-numbering-rules.md
- specs/06-Decision-Records/ADR-001-unified-workflow-engine.md
- specs/06-Decision-Records/ADR-016-security-authentication.md
- specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql
---
> [!IMPORTANT]
@@ -31,16 +34,19 @@ related:
## Module 1: Document Numbering Edge Cases
### EC-DN-001 — Concurrent Submission (Race Condition)
**Severity:** 🔴 Critical | **Type:** Concurrency, Data Integrity
**Scenario:** User A และ User B กด Submit Correspondence พร้อมกันทุก millisecond สำหรับ Project/Type/Sender/Receiver เดียวกัน
**Expected Behavior:**
- ทั้งสองได้รับเลขเอกสาร **ต่างกัน** (เช่น 0001 และ 0002)
- ไม่มีเลข Duplicate ในระบบ
- API ทั้งสองตอบ 201 Created สำเร็จ
**Implementation Rule:**
```
1. Redis Redlock acquire บน counterKey ก่อน
2. ถ้า Lock ไม่ได้ใน 5 วินาที → 503 Service Unavailable (Retry-After: 3s)
@@ -54,20 +60,24 @@ related:
---
### EC-DN-002 — Yearly Reset Boundary Condition
**Severity:** 🟠 High | **Type:** Business Rule, Data Integrity
**Scenario A:** Document ถูก Submit เวลา 23:59:59 วันที่ 31 ธันวาคม
**Scenario B:** Cron Job Reset Counter ทำงานตอนเที่ยงคืน แต่มี Document ในสถานะ RESERVED อยู่
**Expected Behavior (A):**
- ได้รับเลขของปีเก่า (counter ปีเก่า) — เวลา Submit คือที่กำหนด
- ถ้า Confirm หลังเที่ยงคืน → เลขยังเป็นของปีเก่า (ใช้เวลา Reserve ไม่ใช่ Confirm)
**Expected Behavior (B):**
- Cron Job ต้อง **Skip** เลขที่อยู่ใน RESERVED state — ไม่ Reset Counter จนกว่า Reservation จะ Expire หรือ Confirmed
- ถ้า Reset รันก่อน Expiry: Counter ใหม่เริ่ม 0001 แต่ Reserved เลขยังคงอยู่ (ไม่ถูก Overwrite)
**Implementation Rule:**
```
- Cron Job ติด Lock เดียวกับ Reserve Process ก่อน Reset
- Reset scope = 'YEAR_2025' → Counter Key ใหม่ = 'YEAR_2026'
@@ -77,11 +87,13 @@ related:
---
### EC-DN-003 — Cancelled/Voided Number Must Not Reuse
**Severity:** 🔴 Critical | **Type:** Business Rule, Data Integrity
**Scenario:** Document ถูก Submit → ได้เลข 0005 → Admin Cancel Document → User Submit ใหม่
**Expected Behavior:**
- เลขถัดไปต้อง **0006** ไม่ใช่ 0005
- เลข 0005 อยู่ใน `document_number_reservations` สถานะ CANCELLED ตลอดไป
- ไม่มีการ Reuse เลขที่ถูก Cancel เด็ดขาด
@@ -91,16 +103,19 @@ related:
---
### EC-DN-004 — Reservation TTL Expired Cleanup
**Severity:** 🟠 High | **Type:** Data Integrity, UX
**Scenario:** User Reserve เลข (TTL 5 นาที) แต่ Browser ปิดก่อน Confirm
**Expected Behavior:**
- หลัง 5 นาที → `document_number_reservations.status` เปลี่ยนเป็น EXPIRED (by Cron/TTL)
- Counter ไม่ถูก Decrement (เลขนั้นหายไปถาวร — ฟัน-หลอ-เลข เป็นที่ยอมรับ)
- ถ้า User กลับมา Confirm Token ที่ Expired → 410 Gone (Token expired)
**Implementation Rule:**
```sql
-- Cron ทุก 1 นาที
UPDATE document_number_reservations
@@ -111,11 +126,13 @@ WHERE status = 'RESERVED' AND expires_at < NOW();
---
### EC-DN-005 — Idempotency Key Duplicate Submission
**Severity:** 🟠 High | **Type:** Concurrency, UX
**Scenario:** Network ไม่เสถียร → User คลิก Submit 2 ครั้ง → Frontend ส่ง POST 2 ครั้งด้วย Idempotency-Key เดียวกัน
**Expected Behavior:**
- Request แรก → ออกเลขใหม่ → 201 Created
- Request ที่สอง (same Idempotency-Key) → **Return เลขเดิม** → 200 OK (ไม่ออกเลขใหม่)
- ไม่ว่า Request ที่สองจะมาเร็วแค่ไหน
@@ -127,17 +144,20 @@ WHERE status = 'RESERVED' AND expires_at < NOW();
## Module 2: Workflow Engine Edge Cases
### EC-WF-001 — Concurrent Approval (Parallel Steps)
**Severity:** 🔴 Critical | **Type:** Concurrency, Business Rule
**Scenario:** Workflow มี Parallel Approval (Engineer A **และ** Engineer B ต้อง Approve พร้อมกัน)
Engineer A Approve พร้อมกับ Engineer B Approve ใน millisecond เดียวกัน
**Expected Behavior:**
- Workflow System บันทึกทั้งสอง Action อย่างถูกต้อง
- State เปลี่ยนเป็น "Approved" ก็ต่อเมื่อ **ทุก Parallel Branch** Complete แล้ว
- ไม่เกิด State Corruption (เช่น State ถูก Override โดย Action หนึ่ง)
**Implementation Rule:**
```
- DB Transaction Isolation: SERIALIZABLE สำหรับ State Transition
- Check: all parallel branches completed → ถ้าใช่ → advance to next state
@@ -147,16 +167,19 @@ Engineer A Approve พร้อมกับ Engineer B Approve ใน millisecon
---
### EC-WF-002 — Action on Wrong Workflow State
**Severity:** 🔴 Critical | **Type:** Security, Business Rule
**Scenario A:** Reviewer พยายาม Approve เอกสารที่ถูก Cancel แล้ว
**Scenario B:** Reviewer Approve เอกสารที่ Approve ไปแล้ว (Double-click)
**Expected Behavior (A):**
- `GET /correspondences/:id` → status: CANCELLED → ปุ่ม Approve ไม่แสดง (UI)
- ถ้าโจมตีตรงๆ ผ่าน API → 422 Unprocessable Entity (Invalid state transition)
**Expected Behavior (B):**
- `workflow_state_transitions` ตรวจสอบ current_state + action ก่อน
- ถ้า Action ไม่ Valid สำหรับ State ปัจจุบัน → 409 Conflict (Already processed)
- Idempotency: ถ้า User กด Approve ซ้ำด้วย Action เดียวกัน → Return เดิม ไม่ Error
@@ -164,27 +187,32 @@ Engineer A Approve พร้อมกับ Engineer B Approve ใน millisecon
---
### EC-WF-003 — Force Proceed on Final State
**Severity:** 🟠 High | **Type:** Business Rule, UX
**Scenario:** Document Control กด "Force Proceed" บนเอกสารที่อยู่ใน APPROVED (Final State) แล้ว
**Expected Behavior:**
- ถ้าไม่มี Next State ใน DSL → ปุ่ม Force Proceed ไม่แสดง (UI)
- ถ้าเรียก API ตรงๆ → 422 (No next state available from current state)
---
### EC-WF-004 — Workflow Definition Changed During Execution
**Severity:** 🟡 Medium | **Type:** Business Rule, Data Integrity
**Scenario:** Admin แก้ไข Workflow DSL ขณะที่มี Workflow Instance กำลังดำเนินการอยู่
**Expected Behavior:**
- Workflow Instance ที่กำลังเดินอยู่ **ใช้ DSL เวอร์ชันที่สร้าง Instance** (Snapshot at creation)
- Instance ใหม่ที่สร้างหลังจากนั้นใช้ DSL เวอร์ชันใหม่
- ไม่มีการ Interrupt Instance ที่กำลังเดินอยู่
**Implementation Rule:**
```
workflow_instances.workflow_definition_snapshot (JSON) — บันทึก DSL ณ เวลาสร้าง
ไม่ Reference workflow_definitions.id โดยตรงสำหรับ Active Instances
@@ -193,11 +221,13 @@ workflow_instances.workflow_definition_snapshot (JSON) — บันทึก DS
---
### EC-WF-005 — Deadline Passed — No Action Taken
**Severity:** 🟡 Medium | **Type:** Business Rule, UX
**Scenario:** Deadline ของ Organization ผ่านไปแล้ว แต่ User ยังไม่ Approve
**Expected Behavior:**
- Workflow **ไม่ Auto-advance** (ต้องการ Human Decision เสมอ)
- Dashboard แสดง "Overdue" Badge (สีแดง)
- Notification Reminder ส่งซ้ำตาม Schedule (ไม่ใช่ตลอดเวลา — Anti-Spam)
@@ -208,11 +238,13 @@ workflow_instances.workflow_definition_snapshot (JSON) — บันทึก DS
## Module 3: File Storage Edge Cases
### EC-STOR-001 — File Upload During Network Interruption
**Severity:** 🟠 High | **Type:** UX, Data Integrity
**Scenario:** User Upload ไฟล์ 50MB ผ่าน Wi-Fi แล้วเน็ตหลุดระหว่าง Upload
**Expected Behavior:**
- Partial upload ไม่ถูก Save ใน Temp Storage
- User เห็น Error: "การอัปโหลดล้มเหลว กรุณาลองใหม่" + ปุ่ม Retry
- Draft ข้อมูล Form (ที่ไม่ใช่ไฟล์) ยังอยู่ใน LocalStorage (Auto-saved)
@@ -221,11 +253,13 @@ workflow_instances.workflow_definition_snapshot (JSON) — บันทึก DS
---
### EC-STOR-002 — Virus Detected in Uploaded File
**Severity:** 🔴 Critical | **Type:** Security
**Scenario:** User พยายาม Upload ไฟล์ที่ ClamAV ตรวจพบ Malware
**Expected Behavior:**
- ClamAV Scan ใน Temp Storage → พบ → ลบไฟล์ออกจาก Temp ทันที
- API ตอบ 422 Unprocessable Entity: `{ "error": "FILE_VIRUS_DETECTED", "filename": "..." }`
- Audit Log บันทึก: `VIRUS_DETECTED` + filename + user_id + ip_address
@@ -235,17 +269,20 @@ workflow_instances.workflow_definition_snapshot (JSON) — บันทึก DS
---
### EC-STOR-003 — File Type Mismatch (MIME Sniffing Attack)
**Severity:** 🔴 Critical | **Type:** Security
**Scenario:** Attacker เปลี่ยน Extension ไฟล์ `malware.exe``document.pdf` แล้ว Upload
**Expected Behavior:**
- Backend ตรวจ MIME Type จาก **File Content** (ไม่ใช่ Extension)
- ถ้า MIME Type ไม่ตรงกับ Whitelist (PDF, DWG, ZIP, DOCX) → 400 Bad Request
- ถ้า Extension กับ MIME Type ไม่ตรงกัน → 400 Bad Request: "File type mismatch"
- Audit Log บันทึก Security Event
**Whitelist:**
```
PDF: application/pdf
DWG: application/acad, image/vnd.dwg
@@ -257,16 +294,19 @@ XLSX: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
---
### EC-STOR-004 — Orphan File Cleanup (Document Cancelled Before Confirm)
**Severity:** 🟠 High | **Type:** Data Integrity, Storage
**Scenario:** User Reserve Document Number → อัปโหลดไฟล์ไป Temp → Cancel Document → ออกจากหน้า
**Expected Behavior:**
- Temp files ต้องถูกลบออกจาก Storage ภายใน 1 ชั่วโมง (Cleanup Cron)
- ไม่มี Orphan Files ใน Temp Storage เกิน TTL
- Permanent Storage ไม่มีไฟล์ที่ไม่มี Document Reference
**Implementation Rule:**
```typescript
// Cron ทุกชั่วโมง
// ลบ Temp files ที่ older than 1 hour และ ไม่ได้ถูก Confirm
@@ -275,11 +315,13 @@ XLSX: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
---
### EC-STOR-005 — Duplicate File Upload Detection
**Severity:** 🟡 Medium | **Type:** UX, Storage
**Scenario:** User อัปโหลดไฟล์เดิมซ้ำสองครั้ง (ลืมว่าอัปโหลดแล้ว)
**Expected Behavior:**
- **ไม่ Block** การ Upload ซ้ำ — เก็บเป็น 2 Attachment แยกกัน
- แสดง Warning (ไม่ใช่ Error): "ไฟล์นี้อาจถูกอัปโหลดแล้ว — ชื่อเดียวกัน"
- User สามารถลบ Duplicate ออกก่อน Submit
@@ -289,11 +331,13 @@ XLSX: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
## Module 4: RFA & Drawing Edge Cases
### EC-RFA-001 — 1 Shop Drawing Revision = Max 1 RFA Constraint
**Severity:** 🔴 Critical | **Type:** Business Rule, Data Integrity
**Scenario:** Document Control พยายามสร้าง RFA ที่สอง สำหรับ Shop Drawing Revision เดิม
**Expected Behavior:**
- ตรวจสอบ: `rfas WHERE shop_drawing_revision_id = X AND status NOT IN ('REJECTED', 'CANCELLED')`
- ถ้ามี Active RFA อยู่แล้ว → 409 Conflict: "Shop Drawing Revision นี้มี RFA อยู่แล้ว"
- UI: Disable ปุ่ม "สร้าง RFA" ถ้า Revision มี Active RFA แล้ว
@@ -303,11 +347,13 @@ XLSX: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
---
### EC-RFA-002 — RFA Revision While Previous Still Pending
**Severity:** 🟠 High | **Type:** Business Rule
**Scenario:** RFA Rev.A ยัง Pending Review อยู่ แต่ Contractor พยายามสร้าง Rev.B
**Expected Behavior:**
- ถ้า Rev.A ยังไม่มีคำตอบสุดท้าย (REJECTED/APPROVED/APPROVED_WITH_COMMENTS) → Block
- 409 Conflict: "ต้องรอคำตอบของ Revision ก่อนหน้าก่อน"
- ไม่อนุญาตให้มี 2 Active Revision พร้อมกัน
@@ -315,11 +361,13 @@ XLSX: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
---
### EC-RFA-003 — Shop Drawing Uploaded to Wrong Category
**Severity:** 🟡 Medium | **Type:** Business Rule, UX
**Scenario:** User เลือก Discipline = "Structural" แต่ Upload Shop Drawing ที่เป็น Electrical
**Expected Behavior (MVP):**
- ไม่มี Auto-detection (AI Classification เป็น Phase 3)
- Validation: Discipline ต้องเลือก (ไม่มี Default)
- เตือนผู้ใช้ให้ตรวจสอบก่อน Submit (Review Mode)
@@ -328,11 +376,13 @@ XLSX: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
---
### EC-RFA-004 — Transmittal Contains Mixed-Status RFAs
**Severity:** 🟠 High | **Type:** Business Rule
**Scenario:** Transmittal ถูกสร้างโดยรวม RFA บางฉบับที่ยัง DRAFT และบางฉบับที่ READY
**Expected Behavior:**
- Transmittal Submit ได้เฉพาะเมื่อ **ทุก RFA ใน Transmittal** อยู่ในสถานะ READY (ไม่ใช่ DRAFT)
- ถ้ามี DRAFT อยู่ → 422: "RFA [เลข] ยังอยู่ใน Draft กรุณา Submit ก่อน"
- UI: แสดง Status ของแต่ละ RFA ใน Transmittal ก่อน Submit
@@ -342,12 +392,14 @@ XLSX: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
## Module 5: Authentication & Session Edge Cases
### EC-AUTH-001 — Token Refresh Race Condition
**Severity:** 🔴 Critical | **Type:** Concurrency, Security
**Scenario:** Browser Tab A และ Tab B ทำ API Call พร้อมกันด้วย Access Token ที่ Expired
ทั้งสองตรวจพบ 401 และพยายาม Refresh Token พร้อมกัน
**Expected Behavior:**
- ใช้ **Single Refresh Promise Pattern**: Tab แรกที่ Refresh สำเร็จ → Tab ที่สองใช้ Token ใหม่ (ไม่ Refresh ซ้อน)
- ถ้า Tab ที่สอง Refresh ก็ได้ Token ใหม่เหมือนกัน → ถือว่า OK (Refresh Token ยังใช้ได้)
- Refresh Token ถูก Rotate ทุกครั้งที่ใช้ (Refresh Token Rotation)
@@ -357,11 +409,13 @@ XLSX: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
---
### EC-AUTH-002 — Permission Changed While User is Logged In
**Severity:** 🔴 Critical | **Type:** Security, Business Rule
**Scenario:** Admin เปลี่ยน Role ของ User จาก Document Control → Viewer ขณะที่ User กำลัง Login อยู่
**Expected Behavior:**
- Redis Permission Cache ของ User ถูกล้าง **ทันที** (ไม่รอ TTL)
- Access Token เดิมยังใช้ได้จนหมดอายุ (15 นาที) — เป็นที่ยอมรับ
- **Request ถัดไปหลัง Token Refresh** → Permission ใหม่มีผล
@@ -370,31 +424,37 @@ XLSX: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
---
### EC-AUTH-003 — Concurrent Login (Same Account, Multiple Devices)
**Severity:** 🟡 Medium | **Type:** Security, Business Rule
**Scenario:** User Login จาก 2 Device พร้อมกัน (PC และ Tablet)
**Expected Behavior (MVP):**
- อนุญาต (Session ทั้งสองทำงาน Independent)
- แต่ละ Device มี Refresh Token แยกกัน
- Logout จาก Device หนึ่ง → Revoke เฉพาะ Refresh Token ของ Device นั้น
**Future Enhancement (Phase 2):**
- Option: "Logout จาก Device อื่นทั้งหมด"
---
### EC-AUTH-004 — Account Deactivated While Logged In
**Severity:** 🔴 Critical | **Type:** Security
**Scenario:** Admin Deactivate User Account ขณะที่ User กำลัง Login อยู่
**Expected Behavior:**
- Redis: Blacklist User ID (ทุก Token ของ User นั้นถือว่า Invalid ทันที)
- Request ถัดไปของ User → 401 Unauthorized: "Account has been deactivated"
- User ถูก Redirect ไปหน้า Login พร้อม Message ชัดเจน
**Implementation:**
```typescript
// ใน JWT Guard: ตรวจ Redis Blacklist ก่อน Validate Token
const isBlacklisted = await redis.get(`user:blacklist:${userId}`);
@@ -406,11 +466,13 @@ if (isBlacklisted) throw new UnauthorizedException('Account deactivated');
## Module 6: Permission & RBAC Edge Cases
### EC-PERM-001 — Direct Object Reference (IDOR Attack)
**Severity:** 🔴 Critical | **Type:** Security
**Scenario:** User A รู้ ID ของเอกสาร User B (เช่น `/correspondences/12345`) แล้วเรียกตรงๆ
**Expected Behavior:**
- CASL AbilityGuard ตรวจสอบทั้ง Action และ Resource Owner
- ถ้าไม่มีสิทธิ์ → **403 Forbidden** (ไม่ใช่ 404 — เพราะ 404 บอกว่ามีอยู่แต่หาไม่เจอ)
- **Exception:** ถ้าต้องการซ่อน Existence ของ Document → Return 404
@@ -419,11 +481,13 @@ if (isBlacklisted) throw new UnauthorizedException('Account deactivated');
---
### EC-PERM-002 — Super Admin Impersonation Prevention
**Severity:** 🔴 Critical | **Type:** Security
**Scenario:** User พยายาม Forge JWT payload เพิ่ม role: 'SUPERADMIN'
**Expected Behavior:**
- JWT ถูก Sign ด้วย Secret ที่ไม่เปิดเผย → Signature ไม่ตรง → 401 Invalid token
- Role ไม่ถูก Read จาก Token โดยตรงสำหรับ Permission Check — ต้อง Verify จาก DB/Redis
- JWT payload ใช้แค่ `user_id` → ดึง Permission จาก Redis Cache/DB
@@ -431,11 +495,13 @@ if (isBlacklisted) throw new UnauthorizedException('Account deactivated');
---
### EC-PERM-003 — Organization Switch Mid-session
**Severity:** 🟡 Medium | **Type:** Business Rule, UX
**Scenario (ถ้ามี):** User เป็นสมาชิกในหลาย Organization (กรณี Consultant ที่ทำงานหลายโครงการ)
**Expected Behavior:**
- User เห็นเฉพาะ Data ขององค์กรที่ Login อยู่ (Active Context)
- ถ้าต้องการดูอีก Org → ต้อง "Switch Organization" (Session Context เปลี่ยน)
- ไม่มี Cross-org Data Leak แม้ User เป็นสมาชิกทั้งสอง Org
@@ -445,11 +511,13 @@ if (isBlacklisted) throw new UnauthorizedException('Account deactivated');
## Module 7: Correspondence Edge Cases
### EC-CORR-001 — Cancel Correspondence with Downstream Circulation
**Severity:** 🔴 Critical | **Type:** Business Rule, Data Integrity
**Scenario:** Correspondence ถูก Submit → ผู้รับสร้าง Circulation แล้ว → Originator ขอ Cancel
**Expected Behavior:**
- ต้องแจ้งเตือน Admin ว่า "มี Circulation ที่เปิดอยู่ [X รายการ] สำหรับเอกสารนี้"
- ต้องยืนยันก่อน Cancel: "การ Cancel จะส่งผลให้ Circulation ที่เกี่ยวข้องถูกปิดทั้งหมด"
- เมื่อ Confirm → Correspondence = CANCELLED + Circulation ที่เกี่ยวข้อง = FORCE_CLOSED
@@ -458,11 +526,13 @@ if (isBlacklisted) throw new UnauthorizedException('Account deactivated');
---
### EC-CORR-002 — Reply to Cancel Correspondence
**Severity:** 🟡 Medium | **Type:** Business Rule
**Scenario:** Document Control พยายามสร้าง Correspondence เพื่อ Reply ต่อ Correspondence ที่ถูก Cancel
**Expected Behavior:**
- Reply ทำได้ — Reference ถึง CANCELLED เอกสารได้ (เพื่อ acknowledge การยกเลิก)
- UI แสดง Warning: "กำลัง Reply ต่อเอกสารที่ถูกยกเลิกแล้ว"
- ไม่ Block การ Reply (เป็น Business Decision ไม่ใช่ Technical Constraint)
@@ -470,11 +540,13 @@ if (isBlacklisted) throw new UnauthorizedException('Account deactivated');
---
### EC-CORR-003 — Correspondence to Self (Same Organization)
**Severity:** 🟡 Medium | **Type:** Business Rule
**Scenario:** User พยายามสร้าง Correspondence ที่ Sender และ Receiver เป็นองค์กรเดียวกัน
**Expected Behavior:**
- External Correspondence (Letter/RFI) → Block: "ไม่สามารถส่งหาตัวเองได้"
- Internal Communication → ใช้ Circulation Sheet แทน (ไม่ใช่ Correspondence)
- UI Validation + Backend Validation (Double Check)
@@ -484,11 +556,13 @@ if (isBlacklisted) throw new UnauthorizedException('Account deactivated');
## Module 8: Circulation Edge Cases
### EC-CIRC-001 — Assignee Deactivated Before Completing Task
**Severity:** 🟠 High | **Type:** Business Rule, UX
**Scenario:** User ถูก Deactivate หลังจากถูก Assign ใน Circulation แต่ก่อน Respond
**Expected Behavior:**
- Circulation ยัง Active อยู่ — ไม่หยุดอัตโนมัติ
- Document Control เห็น Warning: "Assignee [ชื่อ] ไม่ Active แล้ว"
- Document Control สามารถ Re-assign ไปยัง User อื่นได้
@@ -497,11 +571,13 @@ if (isBlacklisted) throw new UnauthorizedException('Account deactivated');
---
### EC-CIRC-002 — Multi-Assignee: Partial Response
**Severity:** 🟡 Medium | **Type:** Business Rule, UX
**Scenario:** Circulation มี Action Assignees 3 คน — 2 คน Respond แล้ว แต่ 1 คนยังไม่ Respond
**Expected Behavior (MVP):**
- Document Control เห็นสถานะ "2/3 ตอบกลับแล้ว"
- Document Control สามารถ Force Close ได้ (พร้อมระบุเหตุผล)
- ถ้า Force Close → ทุก Partial Response ถูกบันทึก + หมายเหตุว่า Force Closed
@@ -509,11 +585,13 @@ if (isBlacklisted) throw new UnauthorizedException('Account deactivated');
---
### EC-CIRC-003 — Circulation Deadline = Today (Edge of Day)
**Severity:** 🟡 Medium | **Type:** Business Rule, UX
**Scenario:** Deadline ถูกกำหนด = "วันนี้" แต่ User ดูตอนบ่ายสอง
**Expected Behavior:**
- ถ้า Deadline = วันที่ X → หมดเขตเมื่อ X เวลา 23:59:59 (ไม่ใช่ 00:00:00)
- Reminder: ส่ง Notification เวลา 08:00 ของวัน Deadline
- Overdue Badge ขึ้นเมื่อ `NOW() > deadline_date + 1 day` (วันถัดไป 00:00)
@@ -523,11 +601,13 @@ if (isBlacklisted) throw new UnauthorizedException('Account deactivated');
## Module 9: Search & Elasticsearch Edge Cases
### EC-SRCH-001 — Search Index Lag (Eventual Consistency)
**Severity:** 🟡 Medium | **Type:** Data Consistency, UX
**Scenario:** Document ถูก Submit แล้ว → User ค้นหาทันที แต่ไม่เจอ
**Expected Behavior:**
- Index อาจ Lag 530 วินาที (BullMQ Async Job)
- UI แสดง "เอกสารอาจใช้เวลาสักครู่ก่อนปรากฏในผลค้นหา"
- **ไม่ถือว่า Bug** — เป็น By Design (Eventual Consistency)
@@ -536,11 +616,13 @@ if (isBlacklisted) throw new UnauthorizedException('Account deactivated');
---
### EC-SRCH-002 — Permission-filtered Search Results
**Severity:** 🔴 Critical | **Type:** Security
**Scenario:** Contractor A ค้นหา Keyword ที่มีใน Document ของ Contractor B
**Expected Behavior:**
- Elasticsearch Index ต้องมี `organization_id` / `contract_id` Field
- ทุก Search Query ต้อง Filter ด้วย `must: [{ term: { visible_to_org: userOrgId } }]`
- Contractor A **ไม่เห็น** Document ของ Contractor B ในผลค้นหา
@@ -549,11 +631,13 @@ if (isBlacklisted) throw new UnauthorizedException('Account deactivated');
---
### EC-SRCH-003 — Special Characters in Search Query
**Severity:** 🟡 Medium | **Type:** Security, UX
**Scenario:** User ค้นหาด้วย `คคง. สค. - 2025` (มี `-`, `.`, ช่องว่าง)
**Expected Behavior:**
- ไม่ Crash — Elasticsearch รองรับ Special Characters
- Sanitize Query ก่อนส่ง (กัน Elasticsearch Injection)
- ผล Search ยังคง Relevance สูง
@@ -563,11 +647,13 @@ if (isBlacklisted) throw new UnauthorizedException('Account deactivated');
## Module 10: Notifications Edge Cases
### EC-NOTIF-001 — Notification Flood Prevention
**Severity:** 🟠 High | **Type:** UX, Anti-Spam
**Scenario:** Workflow มีหลาย Step ที่เปลี่ยนเร็ว → ส่ง Notification ทุก State Change → User ได้รับ Email 10 ฉบับในนาทีเดียว
**Expected Behavior:**
- **Notification Debounce/Batch:** รวม Notifications ภายใน 5 นาทีเป็น Summary Email เดียว
- ถ้าเปลี่ยน State 5 ครั้งใน 5 นาที → Email เดียว: "เอกสาร X มี 5 การเปลี่ยนแปลง"
- In-App Notifications ยังแสดงทุกรายการ (ไม่ Batch)
@@ -575,11 +661,13 @@ if (isBlacklisted) throw new UnauthorizedException('Account deactivated');
---
### EC-NOTIF-002 — User Unsubscribed from EMAIL but still needs In-App
**Severity:** 🟡 Medium | **Type:** UX, Business Rule
**Scenario:** User ปิด Email Notification แต่ยังต้องการ In-App Notification
**Expected Behavior:**
- Notification Settings: แยก Toggle สำหรับ Email / LINE / In-App
- Core Workflow Assignments (ที่ User ต้อง Action) → **ไม่สามารถ Disable** ทุก Channel ได้
- ต้องมี In-App อย่างน้อย 1 Channel สำหรับ Action Required Notifications
@@ -588,34 +676,33 @@ if (isBlacklisted) throw new UnauthorizedException('Account deactivated');
## 📊 Edge Case Summary by Module
| Module | Critical | High | Medium | Total |
|--------|----------|------|--------|-------|
| Document Numbering | 2 | 2 | 1 | 5 |
| Workflow Engine | 2 | 1 | 2 | 5 |
| File Storage | 2 | 2 | 1 | 5 |
| RFA & Drawing | 1 | 2 | 1 | 4 |
| Auth & Session | 3 | 0 | 1 | 4 |
| Permission & RBAC | 2 | 0 | 1 | 3 |
| Correspondence | 1 | 0 | 2 | 3 |
| Circulation | 0 | 1 | 2 | 3 |
| Search | 1 | 0 | 2 | 3 |
| Notifications | 0 | 1 | 1 | 2 |
| **รวม** | **14** | **9** | **14** | **37** |
| Module | Critical | High | Medium | Total |
| ------------------ | -------- | ----- | ------ | ------ |
| Document Numbering | 2 | 2 | 1 | 5 |
| Workflow Engine | 2 | 1 | 2 | 5 |
| File Storage | 2 | 2 | 1 | 5 |
| RFA & Drawing | 1 | 2 | 1 | 4 |
| Auth & Session | 3 | 0 | 1 | 4 |
| Permission & RBAC | 2 | 0 | 1 | 3 |
| Correspondence | 1 | 0 | 2 | 3 |
| Circulation | 0 | 1 | 2 | 3 |
| Search | 1 | 0 | 2 | 3 |
| Notifications | 0 | 1 | 1 | 2 |
| **รวม** | **14** | **9** | **14** | **37** |
---
## 🧪 Testing Strategy for Edge Cases
### สำหรับ Unit Tests (Backend)
```typescript
// ตัวอย่าง: EC-DN-001 — Concurrent Number Generation
describe('DocumentNumberingService - Concurrency', () => {
it('should generate unique numbers for concurrent requests', async () => {
const promises = Array.from({ length: 50 }, () =>
service.reserve({ projectId: 1, typeId: 2, orgId: 3 })
);
const promises = Array.from({ length: 50 }, () => service.reserve({ projectId: 1, typeId: 2, orgId: 3 }));
const results = await Promise.all(promises);
const numbers = results.map(r => r.documentNumber);
const numbers = results.map((r) => r.documentNumber);
const unique = new Set(numbers);
expect(unique.size).toBe(50); // ไม่มีซ้ำ
});
@@ -623,11 +710,13 @@ describe('DocumentNumberingService - Concurrency', () => {
```
### สำหรับ Integration Tests
- EC-DN-001: k6 Load Test Script (50 VUs, `/document-numbering/reserve`)
- EC-AUTH-001: Cypress Multi-tab Token Refresh Test
- EC-PERM-001: API Test Suite — Direct Object Reference สำหรับทุก Resource
### สำหรับ Manual UAT
- EC-WF-001: Test Parallel Approval ด้วย 2 Browser Session พร้อมกัน
- EC-STOR-002: Upload EICAR Test File (ClamAV Test Virus)
- EC-RFA-001: สร้าง RFA สำหรับ Revision เดิมที่มี Active RFA → Assert Block
+69 -59
View File
@@ -1,15 +1,18 @@
# 🖼️ UI/UX Wireframes & Screen Inventory — LCBP3-DMS v1.8.0
---
title: 'UI/UX Screen Inventory, Navigation Map, and Wireframes'
version: 1.0.0
status: DRAFT
owner: Nattanin Peancharoen (Product Owner)
last_updated: 2026-03-11
related:
- specs/01-Requirements/01-02-business-rules/01-02-03-ui-ux-rules.md
- specs/01-Requirements/01-04-user-stories.md
- specs/01-Requirements/01-05-acceptance-criteria.md
- specs/01-Requirements/01-02-business-rules/01-02-03-ui-ux-rules.md
- specs/01-Requirements/01-04-user-stories.md
- specs/01-Requirements/01-05-acceptance-criteria.md
---
> [!NOTE]
@@ -112,34 +115,34 @@ Mobile: Sidebar → Collapsible Hamburger Drawer (ตาม UI-Rule 5.11)
## 3. 📋 Screen Inventory
| Screen ID | Route | ชื่อหน้า | Primary Role | Priority |
|-----------|-------|---------|-------------|---------|
| SCR-001 | `/login` | Login | ทุก Role | 🔴 Must |
| SCR-002 | `/login/change-password` | Force Password Change | ทุก Role | 🔴 Must |
| SCR-003 | `/dashboard` | Dashboard | ทุก Role | 🔴 Must |
| SCR-004 | `/correspondences` | Correspondence List | Doc Control | 🔴 Must |
| SCR-005 | `/correspondences/new` | Create Correspondence | Doc Control | 🔴 Must |
| SCR-006 | `/correspondences/:id` | Correspondence Detail + Workflow | ทุก Role | 🔴 Must |
| SCR-007 | `/rfas` | RFA List | Doc Control | 🔴 Must |
| SCR-008 | `/rfas/new` | Create RFA | Doc Control | 🔴 Must |
| SCR-009 | `/rfas/:id` | RFA Detail + Workflow | ทุก Role | 🔴 Must |
| SCR-010 | `/transmittals` | Transmittal List | Doc Control | 🟠 Should |
| SCR-011 | `/transmittals/new` | Create Transmittal | Doc Control | 🟠 Should |
| SCR-012 | `/transmittals/:id` | Transmittal Detail | ทุก Role | 🟠 Should |
| SCR-013 | `/drawings/contract` | Contract Drawing List | Doc Control | 🟠 Should |
| SCR-014 | `/drawings/shop` | Shop Drawing List | Doc Control | 🟠 Should |
| SCR-015 | `/drawings/shop/:id` | Shop Drawing Detail | ทุก Role | 🟠 Should |
| SCR-016 | `/circulations` | Circulation List | Doc Control | 🟠 Should |
| SCR-017 | `/circulations/new` | Create Circulation | Doc Control | 🟠 Should |
| SCR-018 | `/circulations/:id` | Circulation Detail | ทุก Role | 🟠 Should |
| SCR-019 | `/search` | Search Results | ทุก Role | 🟠 Should |
| SCR-020 | `/notifications` | Notification Center | ทุก Role | 🟡 Could |
| SCR-021 | `/profile` | Profile & Settings | ทุก Role | 🟠 Should |
| SCR-022 | `/admin/users` | User Management | Org Admin+ | 🔴 Must |
| SCR-023 | `/admin/organizations` | Organization Management | Superadmin | 🔴 Must |
| SCR-024 | `/admin/projects` | Project & Contract Mgmt | Superadmin | 🔴 Must |
| SCR-025 | `/admin/doc-numbering` | Document Number Config | Superadmin | 🟠 Should |
| SCR-026 | `/admin/audit-logs` | Audit Log Viewer | Org Admin+ | 🟠 Should |
| Screen ID | Route | ชื่อหน้า | Primary Role | Priority |
| --------- | ------------------------ | -------------------------------- | ------------ | --------- |
| SCR-001 | `/login` | Login | ทุก Role | 🔴 Must |
| SCR-002 | `/login/change-password` | Force Password Change | ทุก Role | 🔴 Must |
| SCR-003 | `/dashboard` | Dashboard | ทุก Role | 🔴 Must |
| SCR-004 | `/correspondences` | Correspondence List | Doc Control | 🔴 Must |
| SCR-005 | `/correspondences/new` | Create Correspondence | Doc Control | 🔴 Must |
| SCR-006 | `/correspondences/:id` | Correspondence Detail + Workflow | ทุก Role | 🔴 Must |
| SCR-007 | `/rfas` | RFA List | Doc Control | 🔴 Must |
| SCR-008 | `/rfas/new` | Create RFA | Doc Control | 🔴 Must |
| SCR-009 | `/rfas/:id` | RFA Detail + Workflow | ทุก Role | 🔴 Must |
| SCR-010 | `/transmittals` | Transmittal List | Doc Control | 🟠 Should |
| SCR-011 | `/transmittals/new` | Create Transmittal | Doc Control | 🟠 Should |
| SCR-012 | `/transmittals/:id` | Transmittal Detail | ทุก Role | 🟠 Should |
| SCR-013 | `/drawings/contract` | Contract Drawing List | Doc Control | 🟠 Should |
| SCR-014 | `/drawings/shop` | Shop Drawing List | Doc Control | 🟠 Should |
| SCR-015 | `/drawings/shop/:id` | Shop Drawing Detail | ทุก Role | 🟠 Should |
| SCR-016 | `/circulations` | Circulation List | Doc Control | 🟠 Should |
| SCR-017 | `/circulations/new` | Create Circulation | Doc Control | 🟠 Should |
| SCR-018 | `/circulations/:id` | Circulation Detail | ทุก Role | 🟠 Should |
| SCR-019 | `/search` | Search Results | ทุก Role | 🟠 Should |
| SCR-020 | `/notifications` | Notification Center | ทุก Role | 🟡 Could |
| SCR-021 | `/profile` | Profile & Settings | ทุก Role | 🟠 Should |
| SCR-022 | `/admin/users` | User Management | Org Admin+ | 🔴 Must |
| SCR-023 | `/admin/organizations` | Organization Management | Superadmin | 🔴 Must |
| SCR-024 | `/admin/projects` | Project & Contract Mgmt | Superadmin | 🔴 Must |
| SCR-025 | `/admin/doc-numbering` | Document Number Config | Superadmin | 🟠 Should |
| SCR-026 | `/admin/audit-logs` | Audit Log Viewer | Org Admin+ | 🟠 Should |
**รวม:** 26 หน้า (9 Must / 13 Should / 1 Could)
@@ -536,58 +539,62 @@ User Edit Drawer (Slide in from right):
## 5. 🎨 Design System Reference
### Color Tokens
```css
/* Primary — ใช้กับ Action Buttons, Links */
--primary: hsl(221, 83%, 53%); /* Blue-600 */
--primary: hsl(221, 83%, 53%); /* Blue-600 */
--primary-hover: hsl(221, 83%, 45%);
/* Status Colors */
--status-draft: hsl(48, 96%, 53%); /* Yellow */
--status-submitted: hsl(217, 91%, 60%); /* Blue */
--status-review: hsl(24, 95%, 53%); /* Orange */
--status-approved: hsl(142, 71%, 45%); /* Green */
--status-rejected: hsl(0, 84%, 60%); /* Red */
--status-cancelled: hsl(215, 14%, 55%); /* Gray */
--status-overdue: hsl(0, 84%, 60%); /* Red (same as rejected) */
--status-draft: hsl(48, 96%, 53%); /* Yellow */
--status-submitted: hsl(217, 91%, 60%); /* Blue */
--status-review: hsl(24, 95%, 53%); /* Orange */
--status-approved: hsl(142, 71%, 45%); /* Green */
--status-rejected: hsl(0, 84%, 60%); /* Red */
--status-cancelled: hsl(215, 14%, 55%); /* Gray */
--status-overdue: hsl(0, 84%, 60%); /* Red (same as rejected) */
/* Background */
--bg-base: hsl(222, 47%, 11%); /* Dark Navy (dark mode base) */
--bg-surface: hsl(222, 47%, 16%); /* Card surface */
--bg-muted: hsl(215, 28%, 17%); /* Muted sections */
--bg-base: hsl(222, 47%, 11%); /* Dark Navy (dark mode base) */
--bg-surface: hsl(222, 47%, 16%); /* Card surface */
--bg-muted: hsl(215, 28%, 17%); /* Muted sections */
```
### Typography
```css
font-family: 'Inter', 'Noto Sans Thai', sans-serif;
/* Scale */
--text-xs: 0.75rem; /* 12px — Badge, Caption */
--text-sm: 0.875rem; /* 14px — Table cell, Label */
--text-base:1rem; /* 16px — Body */
--text-lg: 1.125rem; /* 18px — Subheading */
--text-xl: 1.25rem; /* 20px — Page title */
--text-2xl: 1.5rem; /* 24px — Dashboard KPI */
--text-xs: 0.75rem; /* 12px — Badge, Caption */
--text-sm: 0.875rem; /* 14px — Table cell, Label */
--text-base: 1rem; /* 16px — Body */
--text-lg: 1.125rem; /* 18px — Subheading */
--text-xl: 1.25rem; /* 20px — Page title */
--text-2xl: 1.5rem; /* 24px — Dashboard KPI */
```
### Component States
| Component | Default | Hover | Active | Disabled | Error |
|-----------|---------|-------|--------|----------|-------|
| Button Primary | bg-primary | bg-primary-hover | scale-95 | opacity-50 | — |
| Input | border-gray-300 | border-primary | border-primary ring | border-gray-200 | border-red-500 |
| Table Row | bg-surface | bg-muted | — | opacity-60 | bg-red-50 |
| Badge | per status color | — | — | — | — |
| Component | Default | Hover | Active | Disabled | Error |
| -------------- | ---------------- | ---------------- | ------------------- | --------------- | -------------- |
| Button Primary | bg-primary | bg-primary-hover | scale-95 | opacity-50 | — |
| Input | border-gray-300 | border-primary | border-primary ring | border-gray-200 | border-red-500 |
| Table Row | bg-surface | bg-muted | — | opacity-60 | bg-red-50 |
| Badge | per status color | — | — | — | — |
---
## 6. 📱 Responsive Breakpoints
| Breakpoint | Width | Behavior |
|-----------|-------|---------|
| `sm` | < 640px | Mobile: Sidebar → Drawer, Table → Cards |
| `md` | 640-1024px | Tablet: Collapsed Sidebar |
| `lg` | > 1024px | Desktop: Full Sidebar |
| Breakpoint | Width | Behavior |
| ---------- | ---------- | --------------------------------------- |
| `sm` | < 640px | Mobile: Sidebar → Drawer, Table → Cards |
| `md` | 640-1024px | Tablet: Collapsed Sidebar |
| `lg` | > 1024px | Desktop: Full Sidebar |
**Mobile-specific Rules (UI-Rule 5.11):**
- ตาราง → Card View อัตโนมัติ
- Sidebar → Collapsible Hamburger Drawer
- Action Panel → Bottom Sheet แทน Inline Panel
@@ -597,6 +604,7 @@ font-family: 'Inter', 'Noto Sans Thai', sans-serif;
## 7. ⚡ Interaction Patterns
### Optimistic Updates (UI-Rule 5.10)
```
User กด "Approve" → UI เปลี่ยนสถานะทันที (ไม่รอ API)
@@ -606,12 +614,14 @@ Rollback UI → แสดง Toast Error: "เกิดข้อผิดพล
```
### Auto-save Draft (UI-Rule 5.12)
```
User พิมพ์ใน Form → debounce 2 วินาที → บันทึกลง localStorage
ปิด Browser → เปิดใหม่ → แสดง Banner: "พบ Draft ที่บันทึกไว้ [กู้คืน] [ทิ้ง]"
```
### File Upload Progress
```
เลือกไฟล์ → แสดง Progress Bar → ClamAV Scan → ✅/❌
```
+12 -14
View File
@@ -54,8 +54,6 @@ This directory contains the functional and non-functional requirements for the L
- 📘 [Implementation Guide](../03-implementation/03-04-document-numbering.md) - NestJS, TypeORM, Redis code examples
- 📗 [Operations Guide](../04-operations/04-08-document-numbering-operations.md) - Monitoring, troubleshooting, runbooks
### Cross-Cutting Concerns
4. [Access Control & RBAC](./01-01-business-rules/01-02-01-rbac-matrix.md) - 4-level hierarchical RBAC
@@ -104,19 +102,19 @@ See [CHANGELOG.md](../../CHANGELOG.md) for detailed version history.
### By Feature Status
| Feature Area | Requirements Doc | Status | Implementation | Operations |
| ------------------------- | ---------------------------------------------------- | ---------- | ----------------------------------------------------------- | ------------------------------------------------------------------ |
| Correspondence Management | [03.2](./01-03.2-correspondence.md) | ✅ Complete | ✅ Complete | Available |
| RFA Management | [03.3](./01-03.3-rfa.md) | ✅ Complete | ✅ Complete | Available |
| Contract Drawing | [03.4](./01-03.4-contract-drawing.md) | ✅ Complete | ✅ Complete | Available |
| Shop Drawing | [03.5](./01-03.5-shop-drawing.md) | ✅ Complete | ✅ Complete | Available |
| Workflow Engine | [03.6](./01-03.6-unified-workflow.md) | ✅ Complete | ✅ Complete | Available |
| Transmittals | [03.7](./01-03.7-transmittals.md) | ✅ Complete | ✅ Complete | Available |
| Circulation Sheets | [03.8](./01-03.8-circulation-sheet.md) | ✅ Complete | ✅ Complete | Available |
| Feature Area | Requirements Doc | Status | Implementation | Operations |
| ------------------------- | ---------------------------------------------------- | ----------- | ------------------------------------------------------------ | ------------------------------------------------------------------- |
| Correspondence Management | [03.2](./01-03.2-correspondence.md) | ✅ Complete | ✅ Complete | Available |
| RFA Management | [03.3](./01-03.3-rfa.md) | ✅ Complete | ✅ Complete | Available |
| Contract Drawing | [03.4](./01-03.4-contract-drawing.md) | ✅ Complete | ✅ Complete | Available |
| Shop Drawing | [03.5](./01-03.5-shop-drawing.md) | ✅ Complete | ✅ Complete | Available |
| Workflow Engine | [03.6](./01-03.6-unified-workflow.md) | ✅ Complete | ✅ Complete | Available |
| Transmittals | [03.7](./01-03.7-transmittals.md) | ✅ Complete | ✅ Complete | Available |
| Circulation Sheets | [03.8](./01-03.8-circulation-sheet.md) | ✅ Complete | ✅ Complete | Available |
| **Document Numbering** | [03.11](./01-03.11-document-numbering.md) | ✅ Complete | ✅ [Guide](../03-implementation/03-04-document-numbering.md) | ✅ [Guide](../04-operations/04-08-document-numbering-operations.md) |
| Access Control (RBAC) | [04](./01-02-business-rules/01-02-01-rbac-matrix.md) | ✅ Complete | ✅ Complete | Available |
| Search (Elasticsearch) | N/A | ✅ Complete | 🔄 95% | Available |
| Dashboard & Analytics | N/A | ✅ Complete | ✅ Complete | Available |
| Access Control (RBAC) | [04](./01-02-business-rules/01-02-01-rbac-matrix.md) | ✅ Complete | ✅ Complete | Available |
| Search (Elasticsearch) | N/A | ✅ Complete | 🔄 95% | Available |
| Dashboard & Analytics | N/A | ✅ Complete | ✅ Complete | Available |
### By Priority