260223:1415 20260223 nextJS & nestJS Best pratices
All checks were successful
Build and Deploy / deploy (push) Successful in 4m44s
All checks were successful
Build and Deploy / deploy (push) Successful in 4m44s
This commit is contained in:
@@ -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: -
|
||||
|
||||
---
|
||||
|
||||
@@ -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).
|
||||
@@ -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 UTF‑8** (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';
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
43
specs/01-requirements/01-03-modules/01-03-00-index.md
Normal file
43
specs/01-requirements/01-03-modules/01-03-00-index.md
Normal 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)
|
||||
@@ -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
|
||||
|
||||
---
|
||||
|
||||
@@ -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. การอ้างอิงและจัดกลุ่ม:
|
||||
|
||||
@@ -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. การอ้างอิงและจัดกลุ่ม:
|
||||
|
||||
@@ -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
|
||||
|
||||
---
|
||||
|
||||
@@ -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
|
||||
|
||||
---
|
||||
|
||||
@@ -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:
|
||||
@@ -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
|
||||
|
||||
---
|
||||
|
||||
@@ -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
|
||||
|
||||
---
|
||||
|
||||
@@ -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
|
||||
|
||||
---
|
||||
|
||||
@@ -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 ได้โดยไม่ต้องแก้โค้ด
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user