260223:1415 20260223 nextJS & nestJS Best pratices
All checks were successful
Build and Deploy / deploy (push) Successful in 4m44s

This commit is contained in:
admin
2026-02-23 14:15:06 +07:00
parent c90a664f53
commit ef16817f38
164 changed files with 24815 additions and 311 deletions

View File

@@ -3,10 +3,10 @@
---
title: 'Objectives'
version: 1.5.0
version: 1.8.0
status: first-draft
owner: Nattanin Peancharoen
last_updated: 2025-11-30
last_updated: 2026-02-23
related: -
---

View File

@@ -0,0 +1,313 @@
# 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)
---
## 4.1. Overview and Problem Statement
LCBP3-DMS จัดการสิทธิ์การเข้าถึงข้อมูลที่ซับซ้อน ได้แก่:
- **Multi-Organization**: หลายองค์กรใช้ระบบร่วมกัน แต่ต้องแยกข้อมูลตามบริบท
- **Project-Based**: โครงการสามารถมีหลายสัญญาย่อย (Contracts)
- **Hierarchical Permissions**: สิทธิ์ระดับบนสามารถครอบคลุมระดับล่าง
- **Dynamic Roles**: สิทธิ์สามารถปรับเปลี่ยน Role หรือเพิ่ม Role พิเศษได้โดยไม่ต้อง Deploy ระบบใหม่.
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 เท่านั้น")
4. Performance (Check permission ผ่าน Redis ต้องเร็ว < 10ms)
---
## 4.2. Permission Hierarchy & Enforcement
### 4-Level Scope Hierarchy
การออกแบบสิทธิ์ครอบคลุม Scope ลำดับขั้นดังนี้:
```text
Global (ทั้งระบบ)
├─ Organization (ระดับองค์กร)
│ ├─ Project (ระดับโครงการ)
│ │ └─ Contract (ระดับสัญญา)
│ │
│ └─ Project B
│ └─ Contract B
└─ Organization 2
└─ Project C
```
**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` ทันที.
---
## 4.3. Role and Scope Summary
| Role | Scope | Description | Key Permissions |
| :------------------- | :----------- | :------------------------- | :-------------------------------------------------------------------------------------------------------------------- |
| **Superadmin** | Global | System administrator | Do everything in the system, manage organizations, manage global data |
| **Org Admin** | Organization | Organization administrator | Manage users in the organization, manage roles/permissions within the organization, view organization reports |
| **Document Control** | Organization | Document controller | Add/edit/delete documents, set document permissions within the organization |
| **Editor** | Organization | Document editor | Edit documents that have been assigned to them |
| **Viewer** | Organization | Document viewer | View documents that have access permissions |
| **Project Manager** | Project | Project manager | Manage members in the project (add/delete/assign roles), create/manage contracts in the project, view project reports |
| **Contract Admin** | Contract | Contract administrator | Manage users in the contract, manage roles/permissions within the contract, view contract reports |
### Master Data Management Authority
| 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 |
---
## 4.4. Onboarding Workflow
- **4.4.1. Create Organization**
- **Superadmin** สร้าง Organization ใหม่ (e.g. Company A)
- **Superadmin** แต่งตั้ง User อย่างน้อย 1 คน ให้เป็น **Org Admin** หรือ **Document Control**
- **4.4.2. Add Users to Organization**
- **Org Admin** เพิ่ม users (`Editor`, `Viewer`) เข้าสู่งองค์กร
- **4.4.3. Assign Users to Project**
- **Project Manager** เชิญ/กำหนดผู้ใช้เข้าสู่ Project. ในขั้นตอนนี้จะกำหนด **Project Role**
- **4.4.4. Assign Users to Contract**
- **Contract Admin** เลือก users จาก Project และมอบหมายหน้าที่เจาะจงใน Contract ขั้นตอนนี้จะกำหนด **Contract Role**
- **4.4.5. Security Onboarding**
- บังคับ Users to change password เป็นครั้งแรก
- ฝึกอบรม (Security awareness training) สำหรับ users ที่มีสิทธิ์สูงระดับแอดมิน.
- บันทึก Audit Log ทุกเหตุการณ์เกี่ยวกับการมอบหมาย/ตั้งค่า Permissions.
---
## 4.5. Implementation Details
### 4.5.1 Database Schema (RBAC Tables)
```sql
-- Role Definitions with Scope
CREATE TABLE roles (
role_id INT PRIMARY KEY AUTO_INCREMENT,
role_name VARCHAR(100) NOT NULL,
scope ENUM('Global', 'Organization', 'Project', 'Contract') NOT NULL,
description TEXT,
is_system BOOLEAN DEFAULT FALSE
);
-- Granular Permissions
CREATE TABLE permissions (
permission_id INT PRIMARY KEY AUTO_INCREMENT,
permission_name VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
module VARCHAR(50),
-- Scope Reference ENUM includes CONTRACT
scope_level ENUM('GLOBAL', 'ORG', 'PROJECT', 'CONTRACT')
);
-- Role -> Permissions Mapping
CREATE TABLE role_permissions (
role_id INT,
permission_id INT,
PRIMARY KEY (role_id, permission_id),
FOREIGN KEY (role_id) REFERENCES roles(role_id) ON DELETE CASCADE,
FOREIGN KEY (permission_id) REFERENCES permissions(permission_id) ON DELETE CASCADE
);
-- User Role Assignments with Context Map
CREATE TABLE user_assignments (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
role_id INT NOT NULL,
organization_id INT NULL,
project_id INT NULL,
contract_id INT NULL,
assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
FOREIGN KEY (role_id) REFERENCES roles(role_id) ON DELETE CASCADE,
FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE,
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY (contract_id) REFERENCES contracts(id) ON DELETE CASCADE,
-- ควบคุมโครงสร้าง: ไม่ให้ระบุ Scope เลื่อนลอยข้ามขั้นต้องมีชั้นเดียวชัดเจน
CONSTRAINT chk_scope CHECK (
(organization_id IS NOT NULL AND project_id IS NULL AND contract_id IS NULL) OR
(organization_id IS NULL AND project_id IS NOT NULL AND contract_id IS NULL) OR
(organization_id IS NULL AND project_id IS NULL AND contract_id IS NOT NULL) OR
(organization_id IS NULL AND project_id IS NULL AND contract_id IS NULL)
)
);
```
### 4.5.2 Setup CASL Ability Rules
เพื่อให้ **"การสืบทอดสิทธิ์ (Inheritance Logic)"** ทำงานได้ถูกต้อง เช่น บทบาทระดับ `Project Manager` ให้แผ่คลุมไปถึงทรัพยากรระดับ `Contract` ด้านในด้วย (Logic `project_id -> all embedded contracts`).
```typescript
// ability.factory.ts
import { AbilityBuilder, PureAbility } from '@casl/ability';
export type AppAbility = PureAbility<[string, any]>;
@Injectable()
export class AbilityFactory {
async createForUser(user: User): Promise<AppAbility> {
const { can, cannot, build } = new AbilityBuilder<AppAbility>(PureAbility);
const assignments = await this.getUserAssignments(user.user_id);
for (const assignment of assignments) {
const role = await this.getRole(assignment.role_id);
const permissions = await this.getRolePermissions(role.role_id);
for (const permission of permissions) {
// e.g. 'correspondence.create', 'project.view'
const [subject, action] = permission.permission_name.split('.');
// Apply Scope conditions based on the Role's specified scope level
switch (role.scope) {
case 'Global':
// ได้รับสิทธิ์กับองค์ประกอบทั้งหมด
can(action, subject);
break;
case 'Organization':
// อนุญาตการ Action ทั้งในองค์กร และโปรเจกต์ภายใต้องค์กร
can(action, subject, { organization_id: assignment.organization_id });
// Advanced Case: In some queries mapped to Projects/Contracts, support fallback checks
// can(action, subject, { '__orgIdFallback': assignment.organization_id });
break;
case 'Project':
// สืบทอดสิทธิ์ไปยัง Project ID นั้นๆ เสมอ และสิทธิ์ครอบคลุมถึงทุก Contracts ที่ผูกกับ Project นี้
can(action, subject, { project_id: assignment.project_id });
break;
case 'Contract':
// จำกัดเฉพาะใน Contract นั้น ตรงเป้าหมาย
can(action, subject, { contract_id: assignment.contract_id });
break;
}
}
}
return build();
}
}
```
### 4.5.3 Token Management & Redis Permission Cache
- **Payload Optimization**: JWT Access Token ให้เก็บเฉพาะ `userId` และ ข้อมูล Sessions ขั้นต้น. โครงสร้าง Permissions List ปริมาณมากจะ**ไม่อยู่ใน Token**.
- **Permission Caching**: นำโครงสร้างสิทธิ์ทั้งหมด (Ability Rules ที่ประกอบแล้วจาก 4.5.2) ไป Cache ไว้เป็น Key ภายใต้ฐานข้อมูล **Redis** โดยคงระยะการตั้ง TTL ประมาณ 30 นาที `1800` วินาที. ลดขนาด Payload ลง และเพื่อเป้าหมาย Performance ที่ `< 10ms`.
### 4.5.4 Permission Guard Enforcement (NestJS)
```typescript
// permission.guard.ts
@Injectable()
export class PermissionGuard implements CanActivate {
constructor(
private reflector: Reflector,
private abilityFactory: AbilityFactory,
private redis: Redis
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const permission = this.reflector.get<string>('permission', context.getHandler());
if (!permission) return true; // Route without specific permission requirements
const request = context.switchToHttp().getRequest();
const user = request.user;
// Check Redis cache first
const cacheKey = `user:${user.user_id}:permissions`;
let abilityRules = await this.redis.get(cacheKey);
if (!abilityRules) {
const newAbility = await this.abilityFactory.createForUser(user);
abilityRules = newAbility.rules;
// Store in Cache (1800ms TTL)
await this.redis.set(cacheKey, JSON.stringify(abilityRules), 'EX', 1800);
} else {
abilityRules = JSON.parse(abilityRules);
}
const ability = new PureAbility(abilityRules);
const [action, subject] = permission.split('.');
// Evaluate mapped payload / resource parameters directly into CASL engine
const resource = { ...request.params, ...request.body };
return ability.can(action, subject, resource);
}
}
```
Controller Example Usage:
```typescript
@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)
}
}
```
---
## 4.6. Cache Invalidation Strategy
เพื่อให้ระบบ Security Update ได้รวดเร็วและปลอดภัย ต้องมีกลไกสั่งรีเซ็ตสิทธิ์ (Invalidation):
1. **When Role Assignemnt Modified**: เมื่อช้อมูลในตาราง `user_assignments` (เช่นลบ/เพิ่ม user ระดับโปรเจกต์) หรือตาราง `role_permissions` (สลับสิทธิ์ของกลุ่ม) เกิดการแก้ไขในฐานข้อมูลแล้ว.
2. **Execute Invalidation**: API Services ต้องทำการลบ Cache เก่าที่ฝั่งขัดข้องทันที ผ่านคำสั่ง `DEL user:{user_id}:permissions` ณ ฝั่ง Redis.
3. ระบบจะทำงานโปรโตซ้ำ (Cold Boot) เข้ามาหยิบ DB -> สร้าง CASL Factory ใหม่ ใน First API Request อีกครั้งในทันที.
---
## 4.7. Appendix: ADR-004 Decision History & Justification
ส่วนอ้างอิงและประวัติศาสตร์การตัดสินใจพิจารณาในอดีต (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 เห็นข้อมูลองค์กรอื่นมั่วข้ามกลุ่ม
2. **Option 2**: Organization-Only Scope
- *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.
**Decision Rationale:**
เลือกแนวทางที่ 3 รองรับความต้องการของ Construction Projects ที่มีการแบ่งกลุ่มรับเหมาช่วงย่อยระดับ Contracts ต่อเนื่อง (Future proof). แก้ไขปัญหา Performance Cons ด้วยแนวคิดการประจุ Redis Caching ข้ามขั้ว และล้าง Invalidation เฉพาะเมื่อ Triggering Database Updates (อ้างอิงหัวข้อ 4.6).

View File

@@ -0,0 +1,786 @@
# 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)
---
> **📖 เอกสารฉบับนี้เป็นการรวมรายละเอียดจาก `01-03.11-document-numbering.md` และ `03-04-document-numbering.md` ให้อยู่ในฉบับเดียวสำหรับใช้อ้างอิงการออกแบบเชิง Functional และการพัฒนา Technology Component**
---
## 1. Overview & วัตถุประสงค์ (Purpose)
ระบบ Document Numbering สำหรับสร้างเลขที่เอกสารอัตโนมัติที่มีความเป็นเอกลักษณ์ (unique) และสามารถติดตามได้ (traceable) สำหรับเอกสารทุกประเภทในระบบ LCBP3-DMS
### 1.1 Requirements Summary & Scope
- **Auto-generation**: สร้างเลขที่อัตโนมัติ ไม่ซ้ำ (Unique) ยืดหยุ่น
- **Configurable Templates**: รองรับแบบฟอร์มกำหนดค่า สำหรับโปรเจกต์ ประเภทเอกสาร ฯลฯ
- **Uniqueness Guarantee**: การันตี Uniqueness ใน Concurrent Environment (Race Conditions)
- **Manual override**: รองรับการ Import หรือ Override สำหรับเอกสารเก่า
- **Cancelled/Void Handling**: ไม่นำหมายเลขที่ใช้ หรือ Cancel/Void กลับมาใช้ใหม่ (No reuse)
- **Audit Logging**: บันทึกเหตุการณ์ Operation ทั้งหมดอย่างละเอียดครบถ้วน 7 ปี
### 1.2 Technology Stack
| Component | Technology |
| ----------------- | -------------------- |
| Backend Framework | NestJS 10.x |
| ORM | TypeORM 0.3.x |
| Database | MariaDB 11.8 |
| Cache/Lock | Redis 7.x + Redlock |
| Message Queue | BullMQ |
| 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 เคลียร์.
---
## 2. Business Rules & Logic
### 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)`
| 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 เดียวกัน) |
### 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`)
**Token Validation Grammar**
```ebnf
TEMPLATE := TOKEN ("-" TOKEN)*
TOKEN := SIMPLE | PARAM
SIMPLE := "{PROJECT}" | "{ORIGINATOR}" | "{RECIPIENT}" | "{CORR_TYPE}" | "{DISCIPLINE}" | "{RFA_TYPE}" | "{REV}" | "{YYYY}" | "{YY}" | "{MM}"
PARAM := "{SEQ:" DIGIT+ "}"
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 เลขที่เอกสารเดิม.
---
## 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 เริ่มสูง.
---
## 4. Module System & Code Architecture
### 4.1 Folder Structure
```
backend/src/modules/document-numbering/
├── document-numbering.module.ts
├── controllers/ # document-numbering.controller.ts, admin.controller.ts, metrics.controller.ts
├── services/ # Main orchestration (document-numbering.service.ts), lock, counter, reserve, format, audit
├── entities/ # DB Entities mappings
├── dto/ # DTOs
├── validators/ # template.validator.ts
├── guards/ # manual-override.guard.ts
└── jobs/ # counter-reset.job.ts (Cron)
```
### 4.2 Sequence Process Architecture
**1. Number Generation Process Diagram**
```mermaid
sequenceDiagram
participant C as Client
participant S as NumberingService
participant L as LockService
participant CS as CounterService
participant DB as Database
participant R as Redis
C->>S: generateDocumentNumber(dto)
S->>L: acquireLock(counterKey)
L->>R: REDLOCK acquire
R-->>L: lock acquired
L-->>S: lock handle
S->>CS: incrementCounter(counterKey)
CS->>DB: BEGIN TRANSACTION
CS->>DB: SELECT FOR UPDATE
CS->>DB: UPDATE last_number
CS->>DB: COMMIT
DB-->>CS: newNumber
CS-->>S: sequence
S->>S: formatNumber(template, seq)
S->>L: releaseLock()
L->>R: REDLOCK release
S-->>C: documentNumber
```
**2. Two-Phase Commit Pattern (Reserve / Confirm)**
```mermaid
sequenceDiagram
participant C as Client
participant RS as ReservationService
participant SS as SequenceService
participant R as Redis
Note over C,R: Phase 1: Reserve
C->>RS: reserve(documentType)
RS->>SS: getNextSequence()
SS-->>RS: documentNumber
RS->>R: SETEX reservation:{token} (TTL: 5min)
RS-->>C: {token, documentNumber, expiresAt}
Note over C,R: Phase 2: Confirm
C->>RS: confirm(token)
RS->>R: GET reservation:{token}
R-->>RS: reservationData
RS->>R: DEL reservation:{token}
RS-->>C: documentNumber (confirmed)
```
### 4.3 Lock Service (Redis Redlock Example)
```typescript
@Injectable()
export class DocumentNumberingLockService {
constructor(@InjectRedis() private readonly redis: Redis) {
this.redlock = new Redlock([redis], { driftFactor: 0.01, retryCount: 5, retryDelay: 100, retryJitter: 50 });
}
async acquireLock(counterKey: CounterKey): Promise<Redlock.Lock> {
const lockKey = this.buildLockKey(counterKey);
return await this.redlock.acquire([lockKey], /* ttl */ 5000); // 5 sec retention
}
}
```
### 4.4 Counter Service & Transaction Strategy (Optimistic Example)
```typescript
async incrementCounter(counterKey: CounterKey): Promise<number> {
const MAX_RETRIES = 2;
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
try {
return await this.dataSource.transaction(async (manager) => {
const counter = await manager.findOne(DocumentNumberCounter, { /* rules */ });
if (!counter) {
// Insert base 1
return 1;
}
counter.lastNumber += 1;
await manager.save(counter); // Trigger Optimistic Version Check
return counter.lastNumber;
});
} catch (error) {
// Loop if version mismatch
}
}
}
```
---
## 5. Database Schema Details
### 5.1 Format Storage & Counters
```sql
-- Format Template Configuration
CREATE TABLE document_number_formats (
id INT AUTO_INCREMENT PRIMARY KEY,
project_id INT NOT NULL,
correspondence_type_id INT NULL,
format_template VARCHAR(100) NOT NULL,
reset_sequence_yearly TINYINT(1) DEFAULT 1,
UNIQUE KEY idx_unique_project_type (project_id, correspondence_type_id),
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
);
-- Active Sequences
CREATE TABLE document_number_counters (
project_id INT NOT NULL,
correspondence_type_id INT NULL,
originator_organization_id INT NOT NULL,
recipient_organization_id INT NOT NULL DEFAULT 0,
sub_type_id INT DEFAULT 0,
rfa_type_id INT DEFAULT 0,
discipline_id INT DEFAULT 0,
reset_scope VARCHAR(20) NOT NULL,
last_number INT DEFAULT 0 NOT NULL,
version INT DEFAULT 0 NOT NULL,
PRIMARY KEY (... 8 fields combination ...),
INDEX idx_counter_lookup (project_id, correspondence_type_id, reset_scope)
);
```
### 5.2 Two-Phase Commit Reservations
```sql
CREATE TABLE document_number_reservations (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
token VARCHAR(36) NOT NULL UNIQUE COMMENT 'UUID v4',
document_number VARCHAR(100) NOT NULL UNIQUE,
status ENUM('RESERVED', 'CONFIRMED', 'CANCELLED', 'VOID') NOT NULL DEFAULT 'RESERVED',
document_id INT NULL COMMENT 'Link after confirmed',
expires_at DATETIME(6) NOT NULL,
... Context fields ...
);
```
### 5.3 Audit Trails & Error Logs
```sql
CREATE TABLE document_number_audit (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
document_number VARCHAR(100) NOT NULL,
operation ENUM('RESERVE', 'CONFIRM', 'CANCEL', 'MANUAL_OVERRIDE', 'VOID', 'GENERATE') NOT NULL,
counter_key JSON NOT NULL,
is_success BOOLEAN DEFAULT TRUE,
lock_wait_ms INT,
... Extraneous Auditing fields ...
);
CREATE TABLE document_number_errors (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
error_type ENUM('LOCK_TIMEOUT','VERSION_CONFLICT','DB_ERROR','REDIS_ERROR','VALIDATION_ERROR','SEQUENCE_EXHAUSTED') NOT NULL,
stack_trace TEXT,
context_data JSON
);
```
---
## 6. Endpoints & API Definitions
| Endpoint | Method | Permission | Meaning |
| -------------------------------------------- | -------- | ------------------------ | ------------------------------------------ |
| `/document-numbering/preview` | POST | `correspondence.read` | Preview Formats |
| `/document-numbering/reserve` | POST | `correspondence.create` | Reserve Token & Logic Number (2PC) |
| `/document-numbering/confirm` | POST | `correspondence.create` | Confirm Reservation (2PC) |
| `/document-numbering/cancel` | POST | `correspondence.create` | Manual or System Cancel Reservation |
| `/admin/document-numbering/manual-override` | POST | `system.manage_settings` | Inject / Legacy specific number generation |
| `/admin/document-numbering/void-and-replace` | POST | `system.manage_settings` | Replace document marking old logic as VOID |
| `/admin/document-numbering/bulk-import` | POST | `system.manage_settings` | Batch Migration Numbers from legacy |
| `/admin/document-numbering/templates` | GET/POST | `system.manage_settings` | Setting Pattern Configurations |
---
## 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`.
**Authorization Policies**:
* `Super Admin` เท่านั้นที่บังคับสั่ง `Reset Counter` ให้เริ่มนับศูนย์ได้เมื่อฉุกเฉิน.
* กฎ Audit Log System ระบุชัดเจนว่าต้อง Retain Information ไม่ต่ำกว่า 7 ปี.
## 8. Monitoring / Observability (Prometheus + Grafana)
| 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. |
| Error Rate Burst | `numbering_lock_failures_total` | 🟡 **WARNING** (Slack). Need investigation logs check |
---
## 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.
### 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`).
4. Re-Verify next sequence logic output `+1` count seamlessly integrates to `nextNumber()`.
---
**Best Practices Checklist**
-**DO**: Two-Phase Commit (`Reserve` + `Confirm`) ให้เป็น Routine System Concept.
-**DO**: DB Fallback เมื่อ Redis ดาวน์. ให้ Availability สูงสุด ไม่หยุดทำงาน.
-**DO**: ข้ามเลขที่ยกเลิกทั้งหมดห้ามมีการ Re-Use เด็ดขาด ไม่ว่าเจตนาใดๆ.
-**DON'T**: ไม่แก้ Sequence จาก DB Console ตรงๆ โดยเด็ดขาด.
-**DON'T**: ลืม Validate format หรือ Tokens แปลกๆ ในระบบ Template (ต้อง Check Grammar ตลอดไป).
-**DON'T**: ลืมเขียน Idempotency-Key สำหรับ Request.
---
**Document Version**: 1.8.0
**Created By**: Development Team
**End of Document**
---
## 10. Operations & Infrastructure Guidelines
### 1. Performance Requirements
### 1.1. Response Time Targets
| Metric | Target | Measurement |
| ---------------- | -------- | ------------------------ |
| 95th percentile | ≤ 2 วินาที | ตั้งแต่ request ถึง response |
| 99th percentile | ≤ 5 วินาที | ตั้งแต่ request ถึง response |
| Normal operation | ≤ 500ms | ไม่มี retry |
### 1.2. Throughput Targets
| Load Level | Target | Notes |
| -------------- | ----------- | ------------------------ |
| Normal load | ≥ 50 req/s | ใช้งานปกติ |
| Peak load | ≥ 100 req/s | ช่วงเร่งงาน |
| Burst capacity | ≥ 200 req/s | Short duration (< 1 min) |
### 1.3. Availability SLA
- **Uptime**: ≥ 99.5% (excluding planned maintenance)
- **Maximum downtime**: ≤ 3.6 ชั่วโมง/เดือน (~ 8.6 นาที/วัน)
- **Recovery Time Objective (RTO)**: ≤ 30 นาที
- **Recovery Point Objective (RPO)**: ≤ 5 นาที
### 2. Infrastructure Setup
### 2.1. Database Configuration
#### MariaDB Connection Pool
```typescript
// ormconfig.ts
{
type: 'mysql',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
extra: {
connectionLimit: 20, // Pool size
queueLimit: 0, // Unlimited queue
acquireTimeout: 10000, // 10s timeout
retryAttempts: 3,
retryDelay: 1000
}
}
```
#### High Availability Setup
```yaml
# docker-compose.yml
services:
mariadb-master:
image: mariadb:11.8
environment:
MYSQL_REPLICATION_MODE: master
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
volumes:
- mariadb-master-data:/var/lib/mysql
networks:
- backend
mariadb-replica:
image: mariadb:11.8
environment:
MYSQL_REPLICATION_MODE: slave
MYSQL_MASTER_HOST: mariadb-master
MYSQL_MASTER_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
volumes:
- mariadb-replica-data:/var/lib/mysql
networks:
- backend
```
### 2.2. Redis Configuration
#### Redis Sentinel for High Availability
```yaml
# docker-compose.yml
services:
redis-master:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis-master-data:/data
networks:
- backend
redis-replica:
image: redis:7-alpine
command: redis-server --replicaof redis-master 6379 --appendonly yes
volumes:
- redis-replica-data:/data
networks:
- backend
redis-sentinel:
image: redis:7-alpine
command: >
redis-sentinel /etc/redis/sentinel.conf
--sentinel monitor mymaster redis-master 6379 2
--sentinel down-after-milliseconds mymaster 5000
--sentinel failover-timeout mymaster 10000
networks:
- backend
```
#### Redis Connection Pool
```typescript
// redis.config.ts
import IORedis from 'ioredis';
export const redisConfig = {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT) || 6379,
password: process.env.REDIS_PASSWORD,
maxRetriesPerRequest: 3,
enableReadyCheck: true,
lazyConnect: false,
poolSize: 10,
retryStrategy: (times: number) => {
if (times > 3) {
return null; // Stop retry
}
return Math.min(times * 100, 3000);
},
};
```
### 2.3. Load Balancing
#### Nginx Configuration
```nginx
# nginx.conf
upstream backend {
least_conn; # Least connections algorithm
server backend-1:3000 max_fails=3 fail_timeout=30s weight=1;
server backend-2:3000 max_fails=3 fail_timeout=30s weight=1;
server backend-3:3000 max_fails=3 fail_timeout=30s weight=1;
keepalive 32;
}
server {
listen 80;
server_name api.lcbp3.local;
location /api/v1/document-numbering/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_next_upstream error timeout;
proxy_connect_timeout 10s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
}
```
#### Docker Compose Scaling
```yaml
# docker-compose.yml
services:
backend:
image: lcbp3-backend:latest
deploy:
replicas: 3
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
environment:
NODE_ENV: production
DB_POOL_SIZE: 20
networks:
- backend
```
### 4. Troubleshooting Runbooks
### 4.1. Scenario: Redis Unavailable
**Symptoms:**
- Alert: `RedisUnavailable`
- System falls back to DB-only locking
- Performance degraded 30-50%
**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
```
5. **Monitor recovery:**
- Check metric: `docnum_redis_connection_status` returns to 1
- Check performance: P95 latency returns to normal (< 500ms)
### 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
```
5. **Check for deadlocks:**
```sql
SHOW ENGINE INNODB STATUS;
-- Look for LATEST DETECTED DEADLOCK section
```
### 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;
-- Check execution plan
EXPLAIN SELECT ...;
```
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
```
5. **Review slow query log:**
```bash
docker exec lcbp3-mariadb-master cat /var/log/mysql/slow.log
```
### 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,
correspondence_type_id,
COUNT(*) as concurrent_requests
FROM document_number_audit
WHERE created_at > NOW() - INTERVAL 5 MINUTE
GROUP BY project_id, correspondence_type_id
HAVING COUNT(*) > 10
ORDER BY concurrent_requests DESC;
```
2. **Investigate specific counter:**
```sql
SELECT * FROM document_number_counters
WHERE project_id = X AND correspondence_type_id = Y;
-- Check audit trail
SELECT * FROM document_number_audit
WHERE counter_key LIKE '%project_id:X%'
ORDER BY created_at DESC
LIMIT 20;
```
3. **Check for application bugs:**
- Review error logs for stack traces
- Check if retry logic is working correctly
4. **Temporary mitigation:**
- Increase retry count in application config
- Consider manual counter adjustment (last resort)
### 5. Maintenance Procedures
### 5.1. Counter Reset (Manual)
**Requires:** SUPER_ADMIN role + 2-person approval
**Steps:**
1. **Request approval via API:**
```bash
POST /api/v1/document-numbering/configs/{configId}/reset-counter
{
"reason": "เหตุผลที่ชัดเจน อย่างน้อย 20 ตัวอักษร",
"approver_1": "user_id",
"approver_2": "user_id"
}
```
2. **Verify in audit log:**
```sql
SELECT * FROM document_number_config_history
WHERE config_id = X
ORDER BY changed_at DESC
LIMIT 1;
```
### 5.2. Template Update
**Best Practices:**
1. Always test template in staging first
2. Preview generated numbers before applying
3. Document reason for change
4. Template changes do NOT affect existing documents
**API Call:**
```bash
PUT /api/v1/document-numbering/configs/{configId}
{
"template": "{ORIGINATOR}-{RECIPIENT}-{SEQ:4}-{YEAR:B.E.}",
"change_reason": "เหตุผลในการเปลี่ยนแปลง"
}
```
### 5.3. Database Maintenance
**Weekly Tasks:**
- Check slow query log
- Optimize tables if needed:
```sql
OPTIMIZE TABLE document_number_counters;
OPTIMIZE TABLE document_number_audit;
```
**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';
```

View File

@@ -3,10 +3,10 @@
---
title: 'UI/UX Requirements'
version: 1.5.0
version: 1.8.0
status: first-draft
owner: Nattanin Peancharoen
last_updated: 2025-11-30
last_updated: 2026-02-23
related:
- specs/02-architecture/data-model.md#correspondence

View File

@@ -3,10 +3,10 @@
---
title: 'Non-Functional Requirements'
version: 1.5.0
version: 1.8.0
status: first-draft
owner: Nattanin Peancharoen
last_updated: 2025-11-30
last_updated: 2026-02-23
related:
- specs/02-architecture/data-model.md#correspondence

View File

@@ -3,10 +3,10 @@
---
title: 'Testing Requirements'
version: 1.5.0
version: 1.8.0
status: first-draft
owner: Nattanin Peancharoen
last_updated: 2025-11-30
last_updated: 2026-02-23
related:
- specs/02-architecture/data-model.md#correspondence

View File

@@ -1,75 +0,0 @@
# 📦Section 3: ข้อกำหนดด้านฟังก์ชันการทำงาน (Functional Requirements)
---
title: "Functional Requirements: Correspondence Management"
version: 1.5.0
status: first-draft
owner: Nattanin Peancharoen
last_updated: 2025-11-30
related:
- specs/01-requirements/01-objectives.md
- specs/01-requirements/02-architecture.md
- specs/01-requirements/03.1-project-management.md
- specs/01-requirements/03.2-correspondence.md
- specs/01-requirements/03.3-rfa.md
- specs/01-requirements/03.4-contract-drawing.md
- specs/01-requirements/03.5-shop-drawing.md
- specs/01-requirements/03.6-unified-workflow.md
- specs/01-requirements/03.7-transmittals.md
- specs/01-requirements/03.8-circulation-sheet.md
- specs/01-requirements/03.9-logs.md
- specs/01-requirements/03.10-file-handling.md
- specs/01-requirements/03.11-document-numbering.md
- specs/01-requirements/03.12-json-details.md
---
## 3.1 การจัดการโครงสร้างโครงการและองค์กร (Project Management)
[specs/01-requirements/03.1-project-management.md](01-03.1-project-management.md)
## 3.2 การจัดการเอกสารโครงการ (Correspondence)
[specs/01-requirements/03.2-correspondence.md](01-03.2-correspondence.md)
## 3.3 การจัดการเอกสารโครงการ (RFA)
[specs/01-requirements/03.3-rfa.md](01-03.3-rfa.md)
## 3.4 การจัดการแบบคู่สัญญา (Contract Drawing)
[specs/01-requirements/03.4-contract-drawing.md](01-03.4-contract-drawing.md)
## 3.5 การจัดกาแบบก่อสร้าง (Shop Drawing)
[specs/01-requirements/03.5-shop-drawing.md](01-03.5-shop-drawing.md)
## 3.6 การจัดการ Workflow (Unified Workflow)
[specs/01-requirements/03.6-unified-workflow.md](01-03.6-unified-workflow.md)
## 3.7 การจัดการเอกสารนำส่ง (Transmittals)
[specs/01-requirements/03.7-transmittals.md](01-03.7-transmittals.md)
## 3.8 การจัดการใบเวียนเอกสาร (Circulation Sheet)
[specs/01-requirements/03.8-circulation-sheet.md](01-03.8-circulation-sheet.md)
## 3.9 ประวัติการแก้ไข (logs)
[specs/01-requirements/03.9-logs.md](01-03.9-logs.md)
## 3.10 การจัดเก็บไฟล์ (File Handling)
[specs/01-requirements/03.10-file-handling.md](01-03.10-file-handling.md)
## 3.11 การจัดการเลขที่เอกสาร (Document Numbering)
[specs/01-requirements/03.11-document-numbering.md](01-03.11-document-numbering.md)
## 3.12 การจัดการ JSON Details (JSON & Performance - ปรับปรุง)
[specs/01-requirements/03.12-json-details.md](01-03.12-json-details.md)

View File

@@ -0,0 +1,43 @@
# 📦 01.2 ข้อกำหนดด้านฟังก์ชันการทำงานระดับโมดูล (Functional Requirements: Modules)
---
title: "Functional Requirements: Modules Index"
version: 1.8.0
status: active
owner: Nattanin Peancharoen
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)

View File

@@ -3,15 +3,15 @@
---
title: "Functional Requirements: Project Management"
version: 1.5.0
version: 1.8.0
status: first-draft
owner: Nattanin Peancharoen
last_updated: 2025-11-30
last_updated: 2026-02-23
related:
- specs/01-requirements/01-objectives.md
- specs/01-requirements/02-architecture.md
- specs/01-requirements/03-functional-requirements.md
- specs/01-requirements/01-01-objectives.md
- specs/02-architecture/README.md
- specs/01-requirements/01-03-modules/01-03-00-index.md
---

View File

@@ -3,15 +3,15 @@
---
title: 'Functional Requirements: Correspondence Management'
version: 1.5.0
version: 1.8.0
status: first-draft
owner: Nattanin Peancharoen
last_updated: 2025-11-30
last_updated: 2026-02-23
related:
- specs/01-requirements/01-objectives.md
- specs/01-requirements/02-architecture.md
- specs/01-requirements/03-functional-requirements.md
- specs/01-requirements/01-01-objectives.md
- specs/02-architecture/README.md
- specs/01-requirements/01-03-modules/01-03-00-index.md
---
@@ -27,7 +27,7 @@ related:
## 3.2.3. การสร้างเอกสาร (Correspondence):
- ผู้ใช้ที่มีสิทธิ์ (เช่น Document Control) สามารถสร้างเอกสารรอไว้ในสถานะ ฉบับร่าง" (Draft) ได้ ซึ่งผู้ใช้งานต่างองค์กรจะมองไม่เห็น
- เมื่อกด "Submitted" แล้ว การแก้ไข, ถอนเอกสารกลับไปสถานะ Draft, หรือยกเลิก (Cancel) จะต้องทำโดยผู้ใช้ระดับ Admin ขึ้นไป พร้อมระบุเหตุผล
- เมื่อเอกสารเปลี่ยนสถานะเป็น "Submitted" แล้ว การแก้ไข, ถอนเอกสารกลับไปสถานะ Draft, หรือยกเลิก (Cancel) จะต้องทำโดยผู้ใช้ระดับ Admin ขึ้นไป พร้อมระบุเหตุผล
## 3.2.4. การอ้างอิงและจัดกลุ่ม:

View File

@@ -3,15 +3,15 @@
---
title: 'Functional Requirements: RFA Management'
version: 1.5.0
version: 1.8.0
status: first-draft
owner: Nattanin Peancharoen
last_updated: 2025-11-30
last_updated: 2026-02-23
related:
- specs/01-requirements/01-objectives.md
- specs/01-requirements/02-architecture.md
- specs/01-requirements/03-functional-requirements.md
- specs/01-requirements/01-01-objectives.md
- specs/02-architecture/README.md
- specs/01-requirements/01-03-modules/01-03-00-index.md
---
@@ -28,7 +28,7 @@ related:
## 3.3.3. การสร้างเอกสาร:
- ผู้ใช้ที่มีสิทธิ์ (เช่น Document Control) สามารถสร้างเอกสารขออนุมัติ (RFA) รอไว้ในสถานะ ฉบับร่าง" (Draft) ได้ ซึ่งผู้ใช้งานต่างองค์กรจะมองไม่เห็น
- เมื่อกด "Submitted" แล้ว การแก้ไข, ถอนเอกสารกลับไปสถานะ Draft, หรือยกเลิก (Cancel) จะต้องทำโดยผู้ใช้ระดับ Admin ขึ้นไป พร้อมระบุเหตุผล
- เมื่อเอกสารเปลี่ยนสถานะเป็น "Submitted" แล้ว การแก้ไข, ถอนเอกสารกลับไปสถานะ Draft, หรือยกเลิก (Cancel) จะต้องทำโดยผู้ใช้ระดับ Admin ขึ้นไป พร้อมระบุเหตุผล
## 3.3.4. การอ้างอิงและจัดกลุ่ม:

View File

@@ -3,15 +3,15 @@
---
title: 'Functional Requirements: Contract Drawing Management'
version: 1.5.0
version: 1.8.0
status: first-draft
owner: Nattanin Peancharoen
last_updated: 2025-11-30
last_updated: 2026-02-23
related:
- specs/01-requirements/01-objectives.md
- specs/01-requirements/02-architecture.md
- specs/01-requirements/03-functional-requirements.md
- specs/01-requirements/01-01-objectives.md
- specs/02-architecture/README.md
- specs/01-requirements/01-03-modules/01-03-00-index.md
---

View File

@@ -3,15 +3,15 @@
---
title: 'Functional Requirements: Shop Drawing Management'
version: 1.5.0
version: 1.8.0
status: first-draft
owner: Nattanin Peancharoen
last_updated: 2025-11-30
last_updated: 2026-02-23
related:
- specs/01-requirements/01-objectives.md
- specs/01-requirements/02-architecture.md
- specs/01-requirements/03-functional-requirements.md
- specs/01-requirements/01-01-objectives.md
- specs/02-architecture/README.md
- specs/01-requirements/01-03-modules/01-03-00-index.md
---

View File

@@ -3,21 +3,21 @@
---
title: 'Functional Requirements: Unified Workflow Management'
version: 1.5.0
version: 1.8.0
status: first-draft
owner: Nattanin Peancharoen
last_updated: 2025-11-30
last_updated: 2026-02-23
related:
- specs/01-requirements/01-objectives.md
- specs/01-requirements/02-architecture.md
- specs/01-requirements/03-functional-requirements.md
- specs/01-requirements/01-01-objectives.md
- specs/02-architecture/README.md
- specs/01-requirements/01-03-modules/01-03-00-index.md
---
## 3.6.1 Workflow Definition:
- Admin ต้องสามารถสร้าง/แก้ไข Workflow Rule ได้ผ่านหน้าจอ UI (DSL Editor)
- Admin ต้องสามารถกำหนดและสร้าง/แก้ไข Workflow Rule ได้ (DSL)
- รองรับการกำหนด State, Transition, Required Role, Condition (JS Expression)
## 3.6.2 Workflow Execution:

View File

@@ -3,15 +3,15 @@
---
title: 'Functional Requirements: Transmittals Management'
version: 1.5.0
version: 1.8.0
status: first-draft
owner: Nattanin Peancharoen
last_updated: 2025-11-30
last_updated: 2026-02-23
related:
- specs/01-requirements/01-objectives.md
- specs/01-requirements/02-architecture.md
- specs/01-requirements/03-functional-requirements.md
- specs/01-requirements/01-01-objectives.md
- specs/02-architecture/README.md
- specs/01-requirements/01-03-modules/01-03-00-index.md
---

View File

@@ -3,15 +3,15 @@
---
title: 'Functional Requirements: Circulation Sheet Management'
version: 1.5.0
version: 1.8.0
status: first-draft
owner: Nattanin Peancharoen
last_updated: 2025-11-30
last_updated: 2026-02-23
related:
- specs/01-requirements/01-objectives.md
- specs/01-requirements/02-architecture.md
- specs/01-requirements/03-functional-requirements.md
- specs/01-requirements/01-01-objectives.md
- specs/02-architecture/README.md
- specs/01-requirements/01-03-modules/01-03-00-index.md
---

View File

@@ -3,15 +3,15 @@
---
title: 'Functional Requirements: Logs Management'
version: 1.5.0
version: 1.8.0
status: first-draft
owner: Nattanin Peancharoen
last_updated: 2025-11-30
last_updated: 2026-02-23
related:
- specs/01-requirements/01-objectives.md
- specs/01-requirements/02-architecture.md
- specs/01-requirements/03-functional-requirements.md
- specs/01-requirements/01-01-objectives.md
- specs/02-architecture/README.md
- specs/01-requirements/01-03-modules/01-03-00-index.md
---

View File

@@ -3,15 +3,15 @@
---
title: 'Functional Requirements: JSON Details Management'
version: 1.5.0
version: 1.8.0
status: first-draft
owner: Nattanin Peancharoen
last_updated: 2025-11-30
last_updated: 2026-02-23
related:
- specs/01-requirements/01-objectives.md
- specs/01-requirements/02-architecture.md
- specs/01-requirements/03-functional-requirements.md
- specs/01-requirements/01-01-objectives.md
- specs/02-architecture/README.md
- specs/01-requirements/01-03-modules/01-03-00-index.md
---
@@ -93,8 +93,8 @@ related:
- สามารถเลือก สร้างในนามองค์กร (Create on behalf of) ได้ เพื่อให้สามารถออกเลขที่เอกสาร (Running Number) ขององค์กรอื่นได้โดยไม่ต้องล็อกอินใหม่
- สามารถทำงานแทนผู้ใช้งานอื่นได้ Routing & Workflow ของ Correspondence, RFA, Circulation Sheet
## 3.14. การจัดการข้อมูลหลักขั้นสูง (Admin Panel for Master Data)
## 3.14. การจัดการข้อมูลหลักขั้นสูง (Master Data Management)
- 3.14.1. Disciplines Management: Admin ต้องสามารถ เพิ่ม/ลบ/แก้ไข สาขางาน (Disciplines) แยกตามสัญญา (Contract) ได้
- 3.14.2. Sub-Type Mapping: Admin ต้องสามารถกำหนด Correspondence Sub-types และ Mapping รหัสตัวเลข (เช่น MAT = 11) ได้
- 3.14.3. Numbering Format Configuration: Admin ต้องมี UI สำหรับตั้งค่า Format Template ของแต่ละ Project/Type ได้โดยไม่ต้องแก้โค้ด
- 3.14.3. Numbering Format Configuration: ระบบต้องรองรับการตั้งค่า Format Template ของแต่ละ Project/Type ได้โดยไม่ต้องแก้โค้ด

View File

@@ -1,8 +1,8 @@
# 📋 Requirements Specification
**Version:** 1.7.0
**Version:** 1.8.0
**Status:** Active
**Last Updated:** 2025-12-18
**Last Updated:** 2026-02-23
---
@@ -17,30 +17,30 @@ This directory contains the functional and non-functional requirements for the L
### Core Requirements
1. [Objectives & Goals](./01-01-objectives.md) - Project objectives and success criteria
2. [System Architecture & Technology](./01-02-architecture.md) - High-level architecture requirements
3. [Functional Requirements](./01-03-functional-requirements.md) - Detailed feature specifications
2. [System Architecture & Technology](../02-Architecture/README.md) - High-level architecture requirements
3. [Functional Requirements](./01-02-modules/01-02-00-index.md) - Detailed feature specifications
### Functional Areas
#### Document Management
- [3.1 Project & Organization Management](./01-03.1-project-management.md) - Projects, contracts, organizations
- [3.2 Correspondence Management](./01-03.2-correspondence.md) - Letters and communications
- [3.3 RFA Management](./01-03.3-rfa.md) - Request for Approval
- [3.4 Contract Drawing Management](./01-03.4-contract-drawing.md) - Contract drawings (แบบคู่สัญญา)
- [3.5 Shop Drawing Management](./01-03.5-shop-drawing.md) - Shop drawings (แบบก่อสร้าง)
- [3.1 Project & Organization Management](./01-02-modules/01-02-01-project-management.md) - Projects, contracts, organizations
- [3.2 Correspondence Management](./01-02-modules/01-02-02-correspondence.md) - Letters and communications
- [3.3 RFA Management](./01-02-modules/01-02-03-rfa.md) - Request for Approval
- [3.4 Contract Drawing Management](./01-02-modules/01-02-04-contract-drawing.md) - Contract drawings (แบบคู่สัญญา)
- [3.5 Shop Drawing Management](./01-02-modules/01-02-05-shop-drawing.md) - Shop drawings (แบบก่อสร้าง)
#### Supporting Features
- [3.6 Unified Workflow](./01-03.6-unified-workflow.md) - Workflow engine and routing
- [3.7 Transmittals Management](./01-03.7-transmittals.md) - Document transmittals
- [3.8 Circulation Sheet Management](./01-03.8-circulation-sheet.md) - Document circulation
- [3.9 Revisions Management](./01-03.9-logs.md) - Version control
- [3.10 File Handling](./01-03.10-file-handling.md) - File storage and processing
- [3.6 Unified Workflow](./01-02-modules/01-02-06-unified-workflow.md) - Workflow engine and routing
- [3.7 Transmittals Management](./01-02-modules/01-02-07-transmittals.md) - Document transmittals
- [3.8 Circulation Sheet Management](./01-02-modules/01-02-08-circulation-sheet.md) - Document circulation
- [3.9 Revisions Management](./01-02-modules/01-02-09-logs.md) - Version control
- [3.10 JSON Details](./01-02-modules/01-02-10-json-details.md) - JSON field specifications
#### **⭐ Document Numbering System**
- [3.11 Document Numbering](./01-03.11-document-numbering.md) - **Requirements**
- [3.11 Document Numbering](./01-01-business-rules/01-01-02-doc-numbering-rules.md) - **Requirements**
- Automatic number generation
- Template-based formatting
- Concurrent request handling
@@ -51,16 +51,14 @@ 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
#### Technical Details
- [3.12 JSON Details](./01-03.12-json-details.md) - JSON field specifications
### Cross-Cutting Concerns
4. [Access Control & RBAC](./01-04-access-control.md) - 4-level hierarchical RBAC
5. [UI/UX Requirements](./01-05-ui-ux.md) - User interface specifications
6. [Non-Functional Requirements](./01-06-non-functional.md) - Performance, security, scalability
7. [Testing Requirements](./01-07-testing.md) - Test strategy and coverage
4. [Access Control & RBAC](./01-01-business-rules/01-01-01-rbac-matrix.md) - 4-level hierarchical RBAC
5. [UI/UX Requirements](./01-01-business-rules/01-01-03-ui-ux-rules.md) - User interface specifications
6. [Non-Functional Requirements](./01-01-business-rules/01-01-04-non-functional-rules.md) - Performance, security, scalability
7. [Testing Requirements](./01-01-business-rules/01-01-05-testing-rules.md) - Test strategy and coverage
---
@@ -101,19 +99,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 |
| **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-04-access-control.md) | ✅ Complete | ✅ Complete | Available |
| Search (Elasticsearch) | N/A | ✅ Complete | 🔄 95% | Available |
| Dashboard & Analytics | N/A | ✅ 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 |
### By Priority
@@ -178,8 +176,8 @@ All requirements documents must meet these criteria:
## 📝 Document Control
- **Version:** 1.7.0
- **Version:** 1.8.0
- **Owner:** System Architect (Nattanin Peancharoen)
- **Last Review:** 2025-12-18
- **Next Review:** 2026-01-01
- **Last Review:** 2026-02-23
- **Next Review:** 2026-03-01
- **Classification:** Internal Use Only

View File

@@ -1,78 +0,0 @@
# 🔐 Section 4: Access Control (ข้อกำหนดด้านสิทธิ์และการเข้าถึง)
---
title: 'Access Control'
version: 1.5.0
status: first-draft
owner: Nattanin Peancharoen
last_updated: 2025-11-30
related:
- specs/02-architecture/data-model.md#correspondence
- specs/03-implementation/backend-guidelines.md#correspondencemodule
---
## 4.1. Overview:
- Users and organizations can view and edit documents based on the permissions they have. The system's permissions will be based on Role-Based Access Control (RBAC).
## 4.2. Permission Hierarchy:
- Global: The highest level of permissions in the system
- Organization: Permissions within an organization, which is the basic permission for users
- Project: Permissions specific to a project, which will be considered when the user is in that project
- Contract: Permissions specific to a contract, which will be considered when the user is in that contract
## 4.3. Permission Enforcement:
- When checking permissions, the system will consider permissions from all levels that the user has and use the most permissive permission as the decision
- Example: User A is a Viewer in the organization, but is assigned as an Editor in Project X when in Project X, User A will have the right to edit
## 4.4. Role and Scope:
| Role | Scope | Description | Key Permissions |
| :------------------- | :----------- | :------------------------- | :-------------------------------------------------------------------------------------------------------------------- |
| **Superadmin** | Global | System administrator | Do everything in the system, manage organizations, manage global data |
| **Org Admin** | Organization | Organization administrator | Manage users in the organization, manage roles/permissions within the organization, view organization reports |
| **Document Control** | Organization | Document controller | Add/edit/delete documents, set document permissions within the organization |
| **Editor** | Organization | Document editor | Edit documents that have been assigned to them |
| **Viewer** | Organization | Document viewer | View documents that have access permissions |
| **Project Manager** | Project | Project manager | Manage members in the project (add/delete/assign roles), create/manage contracts in the project, view project reports |
| **Contract Admin** | Contract | Contract administrator | Manage users in the contract, manage roles/permissions within the contract, view contract reports |
## 4.5. Token Management (ปรับปรุง)
- **Payload Optimization:** ใน JWT Access Token ให้เก็บเฉพาะ `userId` และ `scope` ปัจจุบันเท่านั้น
- **Permission Caching:** สิทธิ์ละเอียด (Permissions List) ให้เก็บใน **Redis** และดึงมาตรวจสอบเมื่อ Request เข้ามา เพื่อลดขนาด Token และเพิ่มความเร็ว
## 4.6. Onboarding Workflow
- 4.6.1. Create Organization
- **Superadmin** creates a new organization (e.g. Company A)
- **Superadmin** appoints at least 1 user as **Org Admin** or **Document Control** of Company A
- 4.6.2. Add Users to Organization
- **Org Admin** of Company A adds other users (Editor, Viewer) to the organization
- 4.6.3. Assign Users to Project
- **Project Manager** of Project X (which may come from Company A or another company) invites or assigns users from different organizations to join Project X
- In this step, **Project Manager** will assign **Project Role** (e.g. Project Member, or may use organization-level permissions)
- 4.6.4. Assign Users to Contract
- **Contract Admin** of Contract Y (which is part of Project X) selects users from Project X and assigns them to Contract Y
- In this step, **Contract Admin** will assign **Contract Role** (e.g. Contract Member) and specific permissions
- 4.6.5 Security Onboarding:
- Force users to change password for the first time
- Security awareness training for users with high permissions
- Safe password reset process
- Audit log recording every permission change
### **4.7. Master Data Management**
| 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 |

View File

@@ -1,821 +0,0 @@
# Document Numbering Implementation Guide (Combined)
---
title: 'Implementation Guide: Document Numbering System'
version: 1.6.2
status: APPROVED
owner: Development Team
last_updated: 2025-12-17
related:
- specs/01-requirements/03.11-document-numbering.md
- specs/04-operations/document-numbering-operations.md
- specs/05-decisions/ADR-002-document-numbering-strategy.md
---
## Overview
เอกสารนี้รวบรวม implementation details สำหรับระบบ Document Numbering โดยผนวกข้อมูลจาก:
- `document-numbering.md` - Core implementation และ database schema
- `document-numbering-add.md` - Extended features (Reservation, Manual Override, Monitoring)
---
## Technology Stack
| Component | Technology |
| ----------------- | -------------------- |
| Backend Framework | NestJS 10.x |
| ORM | TypeORM 0.3.x |
| Database | MariaDB 11.8 |
| Cache/Lock | Redis 7.x + Redlock |
| Message Queue | BullMQ |
| Monitoring | Prometheus + Grafana |
---
## 1. Module Structure
```
backend/src/modules/document-numbering/
├── document-numbering.module.ts
├── controllers/
│ ├── document-numbering.controller.ts # General endpoints
│ ├── document-numbering-admin.controller.ts # Admin endpoints
│ └── numbering-metrics.controller.ts # Metrics endpoints
├── services/
│ ├── document-numbering.service.ts # Main orchestration
│ ├── document-numbering-lock.service.ts # Redis Lock
│ ├── counter.service.ts # Sequence counter logic
│ ├── reservation.service.ts # Two-phase commit
│ ├── manual-override.service.ts # Manual number handling
│ ├── format.service.ts # Template formatting
│ ├── template.service.ts # Template CRUD
│ ├── audit.service.ts # Audit logging
│ ├── metrics.service.ts # Prometheus metrics
│ └── migration.service.ts # Legacy import
├── entities/
│ ├── document-number-counter.entity.ts
│ ├── document-number-format.entity.ts
│ ├── document-number-audit.entity.ts
│ ├── document-number-error.entity.ts
│ └── document-number-reservation.entity.ts
├── dto/
│ ├── generate-number.dto.ts
│ ├── preview-number.dto.ts
│ ├── reserve-number.dto.ts
│ ├── confirm-reservation.dto.ts
│ ├── manual-override.dto.ts
│ ├── void-document.dto.ts
│ └── bulk-import.dto.ts
├── validators/
│ └── template.validator.ts
├── guards/
│ └── manual-override.guard.ts
├── decorators/
│ └── audit-numbering.decorator.ts
├── jobs/
│ └── counter-reset.job.ts
└── tests/
├── unit/
├── integration/
└── e2e/
```
---
## 2. Database Schema
### 2.1 Format Template Table
```sql
CREATE TABLE document_number_formats (
id INT AUTO_INCREMENT PRIMARY KEY,
project_id INT NOT NULL,
correspondence_type_id INT NULL, -- NULL = default format for project
format_template VARCHAR(100) NOT NULL,
reset_sequence_yearly TINYINT(1) DEFAULT 1,
description VARCHAR(255),
created_at DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6),
updated_at DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
UNIQUE KEY idx_unique_project_type (project_id, correspondence_type_id),
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY (correspondence_type_id) REFERENCES correspondence_types(id) ON DELETE CASCADE
) ENGINE=InnoDB COMMENT='Document Number Format Templates';
```
### 2.2 Counter Table
```sql
CREATE TABLE document_number_counters (
project_id INT NOT NULL,
correspondence_type_id INT NULL,
originator_organization_id INT NOT NULL,
recipient_organization_id INT NOT NULL DEFAULT 0, -- 0 = no recipient (RFA)
sub_type_id INT DEFAULT 0,
rfa_type_id INT DEFAULT 0,
discipline_id INT DEFAULT 0,
reset_scope VARCHAR(20) NOT NULL,
last_number INT DEFAULT 0 NOT NULL,
version INT DEFAULT 0 NOT NULL,
created_at DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6),
updated_at DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (
project_id,
originator_organization_id,
recipient_organization_id,
correspondence_type_id,
sub_type_id,
rfa_type_id,
discipline_id,
reset_scope
),
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY (originator_organization_id) REFERENCES organizations(id) ON DELETE CASCADE,
FOREIGN KEY (correspondence_type_id) REFERENCES correspondence_types(id) ON DELETE CASCADE,
INDEX idx_counter_lookup (project_id, correspondence_type_id, reset_scope),
INDEX idx_counter_org (originator_organization_id, reset_scope),
INDEX idx_counter_updated (updated_at),
CONSTRAINT chk_last_number_positive CHECK (last_number >= 0),
CONSTRAINT chk_reset_scope_format CHECK (
reset_scope = 'NONE' OR
reset_scope LIKE 'YEAR_%'
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
COMMENT='Running Number Counters';
```
### 2.3 Audit Table
```sql
CREATE TABLE document_number_audit (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
document_id INT NULL COMMENT 'FK to documents (NULL initially)',
document_type VARCHAR(50),
document_number VARCHAR(100) NOT NULL,
operation ENUM('RESERVE', 'CONFIRM', 'CANCEL', 'MANUAL_OVERRIDE', 'VOID', 'GENERATE') NOT NULL,
status ENUM('RESERVED', 'CONFIRMED', 'CANCELLED', 'VOID', 'MANUAL'),
counter_key JSON NOT NULL COMMENT 'Counter key used (JSON format)',
reservation_token VARCHAR(36) NULL,
originator_organization_id INT NULL,
recipient_organization_id INT NULL,
template_used VARCHAR(200) NOT NULL,
old_value TEXT NULL,
new_value TEXT NULL,
user_id INT NULL COMMENT 'FK to users (Allow NULL for system generation)',
ip_address VARCHAR(45),
user_agent TEXT,
is_success BOOLEAN DEFAULT TRUE,
retry_count INT DEFAULT 0,
lock_wait_ms INT COMMENT 'Lock acquisition time in milliseconds',
total_duration_ms INT COMMENT 'Total generation time',
fallback_used ENUM('NONE', 'DB_LOCK', 'RETRY') DEFAULT 'NONE',
metadata JSON NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_document_id (document_id),
INDEX idx_user_id (user_id),
INDEX idx_status (status),
INDEX idx_operation (operation),
INDEX idx_document_number (document_number),
INDEX idx_created_at (created_at),
FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id)
) ENGINE=InnoDB COMMENT='Document Number Generation Audit Trail';
```
### 2.4 Error Log Table
```sql
CREATE TABLE document_number_errors (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
error_type ENUM(
'LOCK_TIMEOUT',
'VERSION_CONFLICT',
'DB_ERROR',
'REDIS_ERROR',
'VALIDATION_ERROR',
'SEQUENCE_EXHAUSTED',
'RESERVATION_EXPIRED',
'DUPLICATE_NUMBER'
) NOT NULL,
error_message TEXT,
stack_trace TEXT,
context_data JSON COMMENT 'Request context (user, project, etc.)',
user_id INT,
ip_address VARCHAR(45),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
resolved_at TIMESTAMP NULL,
INDEX idx_error_type (error_type),
INDEX idx_created_at (created_at),
INDEX idx_user_id (user_id)
) ENGINE=InnoDB COMMENT='Document Numbering Error Log';
```
### 2.5 Reservation Table
```sql
CREATE TABLE document_number_reservations (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
-- Reservation Details
token VARCHAR(36) NOT NULL UNIQUE COMMENT 'UUID v4',
document_number VARCHAR(100) NOT NULL UNIQUE,
status ENUM('RESERVED', 'CONFIRMED', 'CANCELLED', 'VOID') NOT NULL DEFAULT 'RESERVED',
-- Linkage
document_id INT NULL COMMENT 'FK to documents (NULL until confirmed)',
-- Context (for debugging)
project_id INT NOT NULL,
correspondence_type_id INT NOT NULL,
originator_organization_id INT NOT NULL,
recipient_organization_id INT DEFAULT 0,
user_id INT NOT NULL,
-- Timestamps
reserved_at DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6),
expires_at DATETIME(6) NOT NULL,
confirmed_at DATETIME(6) NULL,
cancelled_at DATETIME(6) NULL,
-- Audit
ip_address VARCHAR(45),
user_agent TEXT,
metadata JSON NULL COMMENT 'Additional context',
-- Indexes
INDEX idx_token (token),
INDEX idx_status (status),
INDEX idx_status_expires (status, expires_at),
INDEX idx_document_id (document_id),
INDEX idx_user_id (user_id),
INDEX idx_reserved_at (reserved_at),
-- Foreign Keys
FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE SET NULL,
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY (correspondence_type_id) REFERENCES correspondence_types(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
COMMENT='Document Number Reservations - Two-Phase Commit';
```
---
## 3. Core Services
### 3.1 Number Generation Process
```mermaid
sequenceDiagram
participant C as Client
participant S as NumberingService
participant L as LockService
participant CS as CounterService
participant DB as Database
participant R as Redis
C->>S: generateDocumentNumber(dto)
S->>L: acquireLock(counterKey)
L->>R: REDLOCK acquire
R-->>L: lock acquired
L-->>S: lock handle
S->>CS: incrementCounter(counterKey)
CS->>DB: BEGIN TRANSACTION
CS->>DB: SELECT FOR UPDATE
CS->>DB: UPDATE last_number
CS->>DB: COMMIT
DB-->>CS: newNumber
CS-->>S: sequence
S->>S: formatNumber(template, seq)
S->>L: releaseLock()
L->>R: REDLOCK release
S-->>C: documentNumber
```
### 3.2 Two-Phase Commit (Reserve/Confirm)
```mermaid
sequenceDiagram
participant C as Client
participant RS as ReservationService
participant SS as SequenceService
participant R as Redis
Note over C,R: Phase 1: Reserve
C->>RS: reserve(documentType)
RS->>SS: getNextSequence()
SS-->>RS: documentNumber
RS->>R: SETEX reservation:{token} (TTL: 5min)
RS-->>C: {token, documentNumber, expiresAt}
Note over C,R: Phase 2: Confirm
C->>RS: confirm(token)
RS->>R: GET reservation:{token}
R-->>RS: reservationData
RS->>R: DEL reservation:{token}
RS-->>C: documentNumber (confirmed)
```
### 3.3 Counter Service Implementation
```typescript
// services/counter.service.ts
@Injectable()
export class CounterService {
private readonly logger = new Logger(CounterService.name);
constructor(
@InjectRepository(DocumentNumberCounter)
private counterRepo: Repository<DocumentNumberCounter>,
private dataSource: DataSource,
) {}
async incrementCounter(counterKey: CounterKey): Promise<number> {
const MAX_RETRIES = 2;
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
try {
return await this.dataSource.transaction(async (manager) => {
// ใช้ Optimistic Locking
const counter = await manager.findOne(DocumentNumberCounter, {
where: this.buildWhereClause(counterKey),
});
if (!counter) {
const newCounter = manager.create(DocumentNumberCounter, {
...counterKey,
lastNumber: 1,
version: 0,
});
await manager.save(newCounter);
return 1;
}
counter.lastNumber += 1;
await manager.save(counter); // Auto-check version
return counter.lastNumber;
});
} catch (error) {
if (error instanceof OptimisticLockVersionMismatchError) {
this.logger.warn(`Version conflict, retry ${attempt + 1}/${MAX_RETRIES}`);
if (attempt === MAX_RETRIES - 1) {
throw new ConflictException('เลขที่เอกสารถูกเปลี่ยน กรุณาลองใหม่');
}
continue;
}
throw error;
}
}
}
}
```
### 3.4 Redis Lock Service
```typescript
// services/document-numbering-lock.service.ts
@Injectable()
export class DocumentNumberingLockService {
private readonly logger = new Logger(DocumentNumberingLockService.name);
private redlock: Redlock;
constructor(@InjectRedis() private readonly redis: Redis) {
this.redlock = new Redlock([redis], {
driftFactor: 0.01,
retryCount: 5,
retryDelay: 100,
retryJitter: 50,
});
}
async acquireLock(counterKey: CounterKey): Promise<Redlock.Lock> {
const lockKey = this.buildLockKey(counterKey);
const ttl = 5000; // 5 seconds
try {
const lock = await this.redlock.acquire([lockKey], ttl);
this.logger.debug(`Acquired lock: ${lockKey}`);
return lock;
} catch (error) {
this.logger.error(`Failed to acquire lock: ${lockKey}`, error);
throw error;
}
}
async releaseLock(lock: Redlock.Lock): Promise<void> {
try {
await lock.release();
} catch (error) {
this.logger.warn('Failed to release lock (may have expired)', error);
}
}
private buildLockKey(key: CounterKey): string {
return `lock:docnum:${key.projectId}:${key.originatorOrgId}:` +
`${key.recipientOrgId ?? 0}:${key.correspondenceTypeId}:` +
`${key.subTypeId}:${key.rfaTypeId}:${key.disciplineId}:${key.year}`;
}
}
```
### 3.5 Reservation Service
```typescript
// services/reservation.service.ts
@Injectable()
export class ReservationService {
private readonly TTL = 300; // 5 minutes
constructor(
private redis: Redis,
private sequenceService: SequenceService,
private auditService: AuditService,
) {}
async reserve(
documentType: string,
scopeValue?: string,
metadata?: Record<string, any>,
): Promise<Reservation> {
// 1. Generate next number
const documentNumber = await this.sequenceService.getNextSequence(
documentType,
scopeValue,
);
// 2. Generate reservation token
const token = uuidv4();
const expiresAt = new Date(Date.now() + this.TTL * 1000);
// 3. Save to Redis
const reservation: Reservation = {
token,
document_number: documentNumber,
document_type: documentType,
scope_value: scopeValue,
expires_at: expiresAt,
metadata,
};
await this.redis.setex(
`reservation:${token}`,
this.TTL,
JSON.stringify(reservation),
);
// 4. Audit log
await this.auditService.log({
operation: 'RESERVE',
document_type: documentType,
document_number: documentNumber,
metadata: { token, scope_value: scopeValue },
});
return reservation;
}
async confirm(token: string, userId: number): Promise<string> {
const reservation = await this.getReservation(token);
if (!reservation) {
throw new ReservationExpiredError(
'Reservation not found or expired. Please reserve a new number.',
);
}
await this.redis.del(`reservation:${token}`);
await this.auditService.log({
operation: 'CONFIRM',
document_type: reservation.document_type,
document_number: reservation.document_number,
user_id: userId,
metadata: { token },
});
return reservation.document_number;
}
async cancel(token: string, userId: number): Promise<void> {
const reservation = await this.getReservation(token);
if (reservation) {
await this.redis.del(`reservation:${token}`);
await this.auditService.log({
operation: 'CANCEL',
document_type: reservation.document_type,
document_number: reservation.document_number,
user_id: userId,
metadata: { token },
});
}
}
@Cron('0 */5 * * * *') // Every 5 minutes
async cleanupExpired(): Promise<void> {
const keys = await this.redis.keys('reservation:*');
for (const key of keys) {
const ttl = await this.redis.ttl(key);
if (ttl <= 0) {
await this.redis.del(key);
}
}
}
}
```
---
## 4. Template System
### 4.1 Supported Tokens
| Token | Description | Example Output |
| -------------- | ---------------------------- | -------------- |
| `{PROJECT}` | Project Code | `LCBP3` |
| `{ORIGINATOR}` | Originator Organization Code | `คคง.` |
| `{RECIPIENT}` | Recipient Organization Code | `สคฉ.3` |
| `{CORR_TYPE}` | Correspondence Type Code | `L` |
| `{SUB_TYPE}` | Sub Type Code | `TD` |
| `{RFA_TYPE}` | RFA Type Code | `RFA` |
| `{DISCIPLINE}` | Discipline Code | `CV` |
| `{SEQ:n}` | Sequence Number (n digits) | `0001` |
| `{YEAR:CE}` | Year (Common Era) | `2025` |
| `{YEAR:BE}` | Year (Buddhist Era) | `2568` |
| `{REV}` | Revision Number | `A` |
### 4.2 Template Validation
```typescript
// validators/template.validator.ts
@Injectable()
export class TemplateValidator {
private readonly ALLOWED_TOKENS = [
'PROJECT', 'ORIGINATOR', 'RECIPIENT', 'CORR_TYPE',
'SUB_TYPE', 'RFA_TYPE', 'DISCIPLINE', 'SEQ', 'YEAR', 'REV',
];
validate(template: string, correspondenceType: string): ValidationResult {
const tokens = this.extractTokens(template);
const errors: string[] = [];
// ตรวจสอบ Token ที่ไม่รู้จัก
for (const token of tokens) {
if (!this.ALLOWED_TOKENS.includes(token.name)) {
errors.push(`Unknown token: {${token.name}}`);
}
}
// กฎพิเศษสำหรับแต่ละประเภท
if (correspondenceType === 'RFA') {
if (!tokens.some((t) => t.name === 'PROJECT')) {
errors.push('RFA template ต้องมี {PROJECT}');
}
if (!tokens.some((t) => t.name === 'DISCIPLINE')) {
errors.push('RFA template ต้องมี {DISCIPLINE}');
}
}
if (correspondenceType === 'TRANSMITTAL') {
if (!tokens.some((t) => t.name === 'SUB_TYPE')) {
errors.push('TRANSMITTAL template ต้องมี {SUB_TYPE}');
}
}
// ทุก template ต้องมี {SEQ}
if (!tokens.some((t) => t.name.startsWith('SEQ'))) {
errors.push('Template ต้องมี {SEQ:n}');
}
return { valid: errors.length === 0, errors };
}
}
```
---
## 5. API Endpoints
### 5.1 General Endpoints (`/document-numbering`)
| Endpoint | Method | Permission | Description |
| --------------- | ------ | ------------------------ | --------------------------------- |
| `/logs/audit` | GET | `system.view_logs` | Get audit logs |
| `/logs/errors` | GET | `system.view_logs` | Get error logs |
| `/sequences` | GET | `correspondence.read` | Get counter sequences |
| `/counters/:id` | PATCH | `system.manage_settings` | Update counter value |
| `/preview` | POST | `correspondence.read` | Preview number without generating |
| `/reserve` | POST | `correspondence.create` | Reserve a document number |
| `/confirm` | POST | `correspondence.create` | Confirm a reservation |
| `/cancel` | POST | `correspondence.create` | Cancel a reservation |
### 5.2 Admin Endpoints (`/admin/document-numbering`)
| Endpoint | Method | Permission | Description |
| ------------------- | ------ | ------------------------ | ----------------------- |
| `/templates` | GET | `system.manage_settings` | Get all templates |
| `/templates` | POST | `system.manage_settings` | Create/update template |
| `/templates/:id` | DELETE | `system.manage_settings` | Delete template |
| `/metrics` | GET | `system.view_logs` | Get metrics |
| `/manual-override` | POST | `system.manage_settings` | Override counter value |
| `/void-and-replace` | POST | `system.manage_settings` | Void and replace number |
| `/cancel` | POST | `system.manage_settings` | Cancel a number |
| `/bulk-import` | POST | `system.manage_settings` | Bulk import counters |
---
## 6. Monitoring & Observability
### 6.1 Prometheus Metrics
```typescript
@Injectable()
export class NumberingMetrics {
// Counter: Total numbers generated
private readonly numbersGenerated = new Counter({
name: 'numbering_sequences_total',
help: 'Total document numbers generated',
labelNames: ['document_type'],
});
// Gauge: Sequence utilization (%)
private readonly sequenceUtilization = new Gauge({
name: 'numbering_sequence_utilization',
help: 'Sequence utilization percentage',
labelNames: ['document_type'],
});
// Histogram: Lock wait time
private readonly lockWaitTime = new Histogram({
name: 'numbering_lock_wait_seconds',
help: 'Time spent waiting for lock acquisition',
labelNames: ['document_type'],
buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5],
});
// Counter: Lock failures
private readonly lockFailures = new Counter({
name: 'numbering_lock_failures_total',
help: 'Total lock acquisition failures',
labelNames: ['document_type', 'reason'],
});
}
```
### 6.2 Alert Rules
| Alert | Condition | Severity | Action |
| ------------------ | ------------------ | -------- | ---------------------- |
| `SequenceCritical` | Utilization > 95% | Critical | Extend max_value |
| `SequenceWarning` | Utilization > 90% | Warning | Plan extension |
| `HighLockWaitTime` | p95 > 1s | Warning | Check Redis health |
| `RedisUnavailable` | Redis cluster down | Critical | Switch to DB-only mode |
| `HighErrorRate` | > 10 errors/sec | Warning | Check logs |
---
## 7. Error Handling
### 7.1 Error Codes
| Code | Name | Description |
| ----- | --------------------------- | -------------------------- |
| NB001 | CONFIG_NOT_FOUND | Config not found for type |
| NB002 | SEQUENCE_EXHAUSTED | Sequence reached max value |
| NB003 | LOCK_TIMEOUT | Failed to acquire lock |
| NB004 | RESERVATION_EXPIRED | Reservation token expired |
| NB005 | DUPLICATE_NUMBER | Number already exists |
| NB006 | INVALID_FORMAT | Number format invalid |
| NB007 | MANUAL_OVERRIDE_NOT_ALLOWED | Manual override disabled |
| NB008 | REDIS_UNAVAILABLE | Redis connection failed |
### 7.2 Fallback Strategy
```mermaid
flowchart TD
A[Generate Number Request] --> B{Redis Available?}
B -->|Yes| C[Acquire Redlock]
B -->|No| D[Use DB-only Lock]
C --> E{Lock Acquired?}
E -->|Yes| F[Increment Counter]
E -->|No| G{Retry < 3?}
G -->|Yes| C
G -->|No| H[Fallback to DB Lock]
D --> F
H --> F
F --> I[Format Number]
I --> J[Return Number]
```
---
## 8. Testing
### 8.1 Unit Tests
```bash
# Run unit tests
pnpm test:watch -- --testPathPattern=document-numbering
```
### 8.2 Integration Tests
```bash
# Run integration tests
pnpm test:e2e -- --testPathPattern=numbering
```
### 8.3 Concurrency Test
```typescript
// tests/load/concurrency.spec.ts
it('should handle 1000 concurrent requests without duplicates', async () => {
const promises = Array.from({ length: 1000 }, () =>
request(app.getHttpServer())
.post('/document-numbering/reserve')
.send({ document_type: 'COR' })
);
const results = await Promise.all(promises);
const numbers = results.map(r => r.body.data.document_number);
const uniqueNumbers = new Set(numbers);
expect(uniqueNumbers.size).toBe(1000);
});
```
---
## 9. Best Practices
### 9.1 DO's ✅
- ✅ Always use two-phase commit (reserve + confirm)
- ✅ Implement fallback to DB-only if Redis fails
- ✅ Log every operation to audit trail
- ✅ Monitor sequence utilization (alert at 90%)
- ✅ Test under concurrent load (1000+ req/s)
- ✅ Use pessimistic locking in database
- ✅ Set reasonable TTL for reservations (5 min)
- ✅ Validate manual override format
- ✅ Skip cancelled numbers (never reuse)
### 9.2 DON'Ts ❌
- ❌ Never skip validation for manual override
- ❌ Never reuse cancelled numbers
- ❌ Never trust client-generated numbers
- ❌ Never increase sequence without transaction
- ❌ Never deploy without load testing
- ❌ Never modify sequence table directly
- ❌ Never skip audit logging
---
## 10. Environment Variables
```bash
# Redis Configuration
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_CLUSTER_NODES=redis-1:6379,redis-2:6379,redis-3:6379
# Database
DB_HOST=localhost
DB_PORT=3306
DB_USERNAME=lcbp3
DB_PASSWORD=
DB_DATABASE=lcbp3_db
DB_POOL_SIZE=20
# Numbering Configuration
NUMBERING_LOCK_TIMEOUT=5000 # 5 seconds
NUMBERING_RESERVATION_TTL=300 # 5 minutes
NUMBERING_RETRY_ATTEMPTS=3
NUMBERING_RETRY_DELAY=200 # milliseconds
# Monitoring
PROMETHEUS_PORT=9090
GRAFANA_PORT=3000
```
---
## References
- [Requirements](../01-requirements/01-03.11-document-numbering.md)
- [Operations Guide](../04-operations/04-08-document-numbering-operations.md)
- [ADR-018 Document Numbering](file:///d:/nap-dms.lcbp3/specs/05-decisions/adr-018-document-numbering.md)
- [Backend Guidelines](03-02-backend-guidelines.md)
---
**Document Version**: 2.0.0
**Created By**: Development Team
**Last Updated**: 2025-12-17

View File

@@ -1,423 +0,0 @@
# ADR-004: RBAC Implementation with 4-Level Scope
**Status:** Accepted
**Date:** 2025-11-30
**Decision Makers:** Development Team, Security Team
**Related Documents:**
- [System Architecture](../02-architecture/02-01-system-architecture.md)
- [Access Control Requirements](../01-requirements/01-04-access-control.md)
---
## Context and Problem Statement
LCBP3-DMS ต้องจัดการสิทธิ์การเข้าถึงที่ซับซ้อน:
- **Multi-Organization:** หลายองค์กรใช้ระบบร่วมกัน แต่ต้องแยกข้อมูล
- **Project-Based:** แต่ละ Project มี Contracts แยกกัน
- **Hierarchical Permissions:** สิทธิ์ระดับบนครอบคลุมระดับล่าง
- **Dynamic Roles:** Role และ Permission ต้องปรับได้โดยไม่ต้อง Deploy
### Key Requirements
1. User หนึ่งคนสามารถมีหลาย Roles ในหลาย Scopes
2. Permission Inheritance (Global → Organization → Project → Contract)
3. Fine-grained Access Control (e.g., "ดู Correspondence ได้เฉพาะ Project A")
4. Performance (Check permission ต้องเร็ว < 10ms)
---
## Decision Drivers
- **Security:** ป้องกันการเข้าถึงข้อมูลที่ไม่มีสิทธิ์
- **Flexibility:** ปรับ Roles/Permissions ได้ง่าย
- **Performance:** Check permission รวดเร็ว
- **Usability:** Admin กำหนดสิทธิ์ได้ง่าย
- **Scalability:** รองรับ Users/Organizations จำนวนมาก
---
## Considered Options
### Option 1: Simple Role-Based (No Scope)
**แนวทาง:** Users มี Roles (Admin, Editor, Viewer) เท่านั้น ไม่มี Scope
**Pros:**
- ✅ Very simple implementation
- ✅ Easy to understand
**Cons:**
- ❌ ไม่รองรับ Multi-organization
- ❌ Superadmin เห็นข้อมูลทุก Organization
- ❌ ไม่ยืดหยุ่น
### Option 2: Organization-Only Scope
**แนวทาง:** Roles ผูกกับ Organization เท่านั้น
**Pros:**
- ✅ แยกข้อมูลระหว่าง Organizations ได้
- ✅ Moderate complexity
**Cons:**
- ❌ ไม่รองรับ Project/Contract level permissions
- ❌ User ใน Organization เห็นทุก Project
### Option 3: **4-Level Hierarchical RBAC** ⭐ (Selected)
**แนวทาง:** Global → Organization → Project → Contract
**Pros:**
-**Maximum Flexibility:** ครอบคลุมทุก Use Case
-**Inheritance:** Global Admin เห็นทุกอย่าง
-**Isolation:** Project Manager เห็นแค่ Project ของตน
-**Fine-grained:** Contract Admin จัดการแค่ Contract เดียว
-**Dynamic:** Roles/Permissions configurable
**Cons:**
- ❌ Complex implementation
- ❌ Performance concern (need optimization)
- ❌ Learning curve for admins
---
## Decision Outcome
**Chosen Option:** Option 3 - 4-Level Hierarchical RBAC
### Rationale
เลือก 4-Level RBAC เนื่องจาก:
1. **Business Requirements:** Project มีหลาย Contracts ที่ต้องแยกสิทธิ์
2. **Future-proof:** รองรับการเติบโตในอนาคต
3. **CASL Integration:** ใช้ library ที่รองรับ complex permissions
4. **Redis Caching:** แก้ปัญหา Performance ด้วย Cache
---
## Implementation Details
### Database Schema
```sql
-- Roles with Scope
CREATE TABLE roles (
role_id INT PRIMARY KEY AUTO_INCREMENT,
role_name VARCHAR(100) NOT NULL,
scope ENUM('Global', 'Organization', 'Project', 'Contract') NOT NULL,
description TEXT,
is_system BOOLEAN DEFAULT FALSE
);
-- Permissions
CREATE TABLE permissions (
permission_id INT PRIMARY KEY AUTO_INCREMENT,
permission_name VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
module VARCHAR(50),
scope_level ENUM('GLOBAL', 'ORG', 'PROJECT')
);
-- Role-Permission Mapping
CREATE TABLE role_permissions (
role_id INT,
permission_id INT,
PRIMARY KEY (role_id, permission_id),
FOREIGN KEY (role_id) REFERENCES roles(role_id) ON DELETE CASCADE,
FOREIGN KEY (permission_id) REFERENCES permissions(permission_id) ON DELETE CASCADE
);
-- User Role Assignments with Scope Context
CREATE TABLE user_assignments (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
role_id INT NOT NULL,
organization_id INT NULL,
project_id INT NULL,
contract_id INT NULL,
assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
FOREIGN KEY (role_id) REFERENCES roles(role_id) ON DELETE CASCADE,
FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE,
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY (contract_id) REFERENCES contracts(id) ON DELETE CASCADE,
CONSTRAINT chk_scope CHECK (
(organization_id IS NOT NULL AND project_id IS NULL AND contract_id IS NULL) OR
(organization_id IS NULL AND project_id IS NOT NULL AND contract_id IS NULL) OR
(organization_id IS NULL AND project_id IS NULL AND contract_id IS NOT NULL) OR
(organization_id IS NULL AND project_id IS NULL AND contract_id IS NULL)
)
);
```
### CASL Ability Rules
```typescript
// ability.factory.ts
import { AbilityBuilder, PureAbility } from '@casl/ability';
export type AppAbility = PureAbility<[string, any]>;
@Injectable()
export class AbilityFactory {
async createForUser(user: User): Promise<AppAbility> {
const { can, cannot, build } = new AbilityBuilder<AppAbility>(PureAbility);
// Get user assignments (from cache or DB)
const assignments = await this.getUserAssignments(user.user_id);
for (const assignment of assignments) {
const role = await this.getRole(assignment.role_id);
const permissions = await this.getRolePermissions(role.role_id);
for (const permission of permissions) {
// permission format: 'correspondence.create', 'project.view'
const [subject, action] = permission.permission_name.split('.');
// Apply scope-based conditions
switch (assignment.scope) {
case 'Global':
can(action, subject);
break;
case 'Organization':
can(action, subject, {
organization_id: assignment.organization_id,
});
break;
case 'Project':
can(action, subject, {
project_id: assignment.project_id,
});
break;
case 'Contract':
can(action, subject, {
contract_id: assignment.contract_id,
});
break;
}
}
}
return build();
}
}
```
### Permission Guard
```typescript
// permission.guard.ts
@Injectable()
export class PermissionGuard implements CanActivate {
constructor(
private reflector: Reflector,
private abilityFactory: AbilityFactory,
private redis: Redis
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
// Get required permission from decorator
const permission = this.reflector.get<string>(
'permission',
context.getHandler()
);
if (!permission) return true;
const request = context.switchToHttp().getRequest();
const user = request.user;
// Check cache first (30 min TTL)
const cacheKey = `user:${user.user_id}:permissions`;
let ability = await this.redis.get(cacheKey);
if (!ability) {
ability = await this.abilityFactory.createForUser(user);
await this.redis.set(cacheKey, JSON.stringify(ability.rules), 'EX', 1800);
}
const [action, subject] = permission.split('.');
const resource = request.params || request.body;
return ability.can(action, subject, resource);
}
}
```
### Usage Example
```typescript
@Controller('correspondences')
@UseGuards(JwtAuthGuard, PermissionGuard)
export class CorrespondenceController {
@Post()
@RequirePermission('correspondence.create')
async create(@Body() dto: CreateCorrespondenceDto) {
// Only users with create permission can access
}
@Get(':id')
@RequirePermission('correspondence.view')
async findOne(@Param('id') id: string) {
// Check if user has view permission for this project
}
}
```
---
## Permission Checking Flow
```mermaid
sequenceDiagram
participant Client
participant Guard as Permission Guard
participant Redis as Redis Cache
participant Factory as Ability Factory
participant DB as Database
Client->>Guard: Request with JWT
Guard->>Redis: Get user permissions (cache)
alt Cache Hit
Redis-->>Guard: Cached permissions
else Cache Miss
Guard->>Factory: createForUser(user)
Factory->>DB: Get user_assignments
Factory->>DB: Get role_permissions
Factory->>Factory: Build CASL ability
Factory-->>Guard: Ability object
Guard->>Redis: Cache permissions (TTL: 30min)
end
Guard->>Guard: Check permission.can(action, subject, context)
alt Permission Granted
Guard-->>Client: Allow access
else Permission Denied
Guard-->>Client: 403 Forbidden
end
```
---
## 4-Level Scope Hierarchy
```
Global (ทั้งระบบ)
├─ Organization (ระดับองค์กร)
│ ├─ Project (ระดับโครงการ)
│ │ └─ Contract (ระดับสัญญา)
│ │
│ └─ Project B
│ └─ Contract B
└─ Organization 2
└─ Project C
```
### Example Assignments
```typescript
// User A: Superadmin (Global)
{
user_id: 1,
role_id: 1, // Superadmin
organization_id: null,
project_id: null,
contract_id: null
}
// Can access EVERYTHING
// User B: Document Control in TEAM Organization
{
user_id: 2,
role_id: 3, // Document Control
organization_id: 3, // TEAM
project_id: null,
contract_id: null
}
// Can manage documents in TEAM organization (all projects)
// User C: Project Manager for LCBP3
{
user_id: 3,
role_id: 6, // Project Manager
organization_id: null,
project_id: 1, // LCBP3
contract_id: null
}
// Can manage only LCBP3 project (all contracts within)
// User D: Contract Admin for Contract-1
{
user_id: 4,
role_id: 7, // Contract Admin
organization_id: null,
project_id: null,
contract_id: 5 // Contract-1
}
// Can manage only Contract-1
```
---
## Consequences
### Positive
1.**Fine-grained Control:** แยกสิทธิ์ได้ละเอียดมาก
2.**Flexible:** User มีหลาย Roles ใน Scopes ต่างกันได้
3.**Inheritance:** Global → Org → Project → Contract
4.**Performant:** Redis cache ทำให้เร็ว (< 10ms)
5.**Auditable:** ทุก Assignment บันทึกใน DB
### Negative
1.**Complexity:** ซับซ้อนในการ Setup และ Maintain
2.**Cache Invalidation:** ต้อง Invalidate ถูกต้องเมื่อเปลี่ยน Roles
3.**Learning Curve:** Admin ต้องเข้าใจ Scope hierarchy
4.**Testing:** ต้อง Test ทุก Combination
### Mitigation Strategies
- **Complexity:** สร้าง Admin UI ที่ใช้งานง่าย
- **Cache:** Auto-invalidate เมื่อมีการเปลี่ยนแปลง
- **Documentation:** เขียน Guide ชัดเจน
- **Testing:** Integration tests ครอบคลุม Permissions
---
## Compliance
เป็นไปตาม:
- [Requirements Section 4](../01-requirements/01-04-access-control.md) - Access Control
- [Backend Plan Section 2 RBAC](../../docs/2_Backend_Plan_V1_4_5.md#rbac)
---
## Related ADRs
- [ADR-005: Redis Usage Strategy](./ADR-005-redis-usage-strategy.md) - Permission caching
- [ADR-001: Unified Workflow Engine](./ADR-001-unified-workflow-engine.md) - Workflow permission guards
---
## References
- [CASL Documentation](https://casl.js.org/v6/en/guide/intro)
- [RBAC Best Practices](https://csrc.nist.gov/publications/detail/sp/800-162/final)

File diff suppressed because it is too large Load Diff