260322:1648 Correct Coresspondence / Doing RFA / Correct CI
This commit is contained in:
@@ -10,9 +10,11 @@
|
||||
## 1. Overview
|
||||
|
||||
### 1.1 Purpose
|
||||
|
||||
ระบบ Document Numbering สำหรับสร้างเลขที่เอกสารอัตโนมัติที่มีความเป็นเอกลักษณ์ (unique) และสามารถติดตามได้ (traceable) สำหรับเอกสารทุกประเภทในระบบ LCBP3-DMS
|
||||
|
||||
### 1.2 Scope
|
||||
|
||||
- Auto-generation ของเลขที่เอกสารตามรูปแบบที่กำหนด
|
||||
- Manual override สำหรับการ import เอกสารเก่า
|
||||
- Cancelled number handling (ไม่ reuse)
|
||||
@@ -21,6 +23,7 @@
|
||||
- Complete audit trail สำหรับทุก operation
|
||||
|
||||
### 1.3 Document Types Supported
|
||||
|
||||
- Correspondences (COR)
|
||||
- Request for Approvals (RFA)
|
||||
- Contract Drawings (CD)
|
||||
@@ -35,6 +38,7 @@
|
||||
### 2.1 Auto Number Generation
|
||||
|
||||
#### FR-DN-001: Generate Sequential Number
|
||||
|
||||
**Priority**: CRITICAL
|
||||
**Status**: Required
|
||||
|
||||
@@ -42,12 +46,14 @@
|
||||
ระบบต้องสามารถสร้างเลขที่เอกสารอัตโนมัติตามลำดับ (sequential) โดยไม่ซ้ำกัน
|
||||
|
||||
**Acceptance Criteria**:
|
||||
|
||||
- เลขที่เอกสารต้องเป็น unique ใน scope ที่กำหนด
|
||||
- ต้องเพิ่มขึ้นทีละ 1 (increment by 1)
|
||||
- ต้องรองรับ concurrent requests โดยไม่มีเลขที่ซ้ำ
|
||||
- Response time < 100ms (p95)
|
||||
|
||||
**Example**:
|
||||
|
||||
```
|
||||
COR-00001-2025
|
||||
COR-00002-2025
|
||||
@@ -57,6 +63,7 @@ COR-00003-2025
|
||||
---
|
||||
|
||||
#### FR-DN-002: Configurable Number Format
|
||||
|
||||
**Priority**: HIGH
|
||||
**Status**: Required
|
||||
|
||||
@@ -64,6 +71,7 @@ COR-00003-2025
|
||||
ระบบต้องรองรับการกำหนดรูปแบบเลขที่เอกสารที่หลากหลาย
|
||||
|
||||
**Format Tokens**:
|
||||
|
||||
- `{PREFIX}` - คำนำหน้าตามประเภทเอกสาร (e.g., COR, RFA)
|
||||
- `{YYYY}` - ปี 4 หลัก (e.g., 2025)
|
||||
- `{YY}` - ปี 2 หลัก (e.g., 25)
|
||||
@@ -73,12 +81,14 @@ COR-00003-2025
|
||||
- `{CONTRACT}` - รหัสสัญญา
|
||||
|
||||
**Acceptance Criteria**:
|
||||
|
||||
- รองรับ format tokens ที่ระบุ
|
||||
- Admin สามารถกำหนด format ผ่าน UI ได้
|
||||
- Validate format ก่อน save
|
||||
- แสดง preview ของเลขที่ที่จะถูกสร้าง
|
||||
|
||||
**Examples**:
|
||||
|
||||
```typescript
|
||||
// Correspondence format
|
||||
"COR-{YYYY}-{SEQ:5}"
|
||||
@@ -96,6 +106,7 @@ COR-00003-2025
|
||||
---
|
||||
|
||||
#### FR-DN-003: Scope-based Sequences
|
||||
|
||||
**Priority**: HIGH
|
||||
**Status**: Required
|
||||
|
||||
@@ -103,6 +114,7 @@ COR-00003-2025
|
||||
ระบบต้องรองรับการสร้าง sequence ที่แยกตาม scope ที่ต่างกัน
|
||||
|
||||
**Scopes**:
|
||||
|
||||
1. **Global**: Sequence ระดับระบบทั้งหมด
|
||||
2. **Project**: Sequence แยกตามโครงการ
|
||||
3. **Contract**: Sequence แยกตามสัญญา
|
||||
@@ -110,11 +122,13 @@ COR-00003-2025
|
||||
5. **Monthly**: Sequence reset ทุกเดือน
|
||||
|
||||
**Acceptance Criteria**:
|
||||
|
||||
- เลขที่ไม่ซ้ำภายใน scope เดียวกัน
|
||||
- Scope ที่ต่างกันสามารถมีเลขที่เดียวกันได้
|
||||
- Support multiple active scopes
|
||||
|
||||
**Example**:
|
||||
|
||||
```
|
||||
Project A: COR-A-2025-00001, COR-A-2025-00002
|
||||
Project B: COR-B-2025-00001, COR-B-2025-00002
|
||||
@@ -129,6 +143,7 @@ COR-2025-00001 (Jan 2025)
|
||||
### 2.2 Manual Override
|
||||
|
||||
#### FR-DN-004: Manual Number Assignment
|
||||
|
||||
**Priority**: HIGH
|
||||
**Status**: Required
|
||||
|
||||
@@ -136,11 +151,13 @@ COR-2025-00001 (Jan 2025)
|
||||
ระบบต้องรองรับการกำหนดเลขที่เอกสารด้วยตนเอง (manual override)
|
||||
|
||||
**Use Cases**:
|
||||
|
||||
1. Import เอกสารเก่าจากระบบเดิม
|
||||
2. External documents จาก client/consultant
|
||||
3. Correction หลังพบความผิดพลาด
|
||||
|
||||
**Acceptance Criteria**:
|
||||
|
||||
- ตรวจสอบ duplicate ก่อน save
|
||||
- Validate format ตามรูปแบบที่กำหนด
|
||||
- Auto-update sequence counter ถ้าเลขที่สูงกว่า current
|
||||
@@ -148,11 +165,12 @@ COR-2025-00001 (Jan 2025)
|
||||
- ต้องมีสิทธิ์ Admin ขึ้นไปเท่านั้น
|
||||
|
||||
**Validation Rules**:
|
||||
|
||||
```typescript
|
||||
interface ManualNumberValidation {
|
||||
format_match: boolean; // ตรง format หรือไม่
|
||||
not_duplicate: boolean; // ไม่ซ้ำ
|
||||
in_valid_range: boolean; // อยู่ในช่วงที่กำหนด
|
||||
format_match: boolean; // ตรง format หรือไม่
|
||||
not_duplicate: boolean; // ไม่ซ้ำ
|
||||
in_valid_range: boolean; // อยู่ในช่วงที่กำหนด
|
||||
permission_granted: boolean; // มีสิทธิ์
|
||||
}
|
||||
```
|
||||
@@ -160,6 +178,7 @@ interface ManualNumberValidation {
|
||||
---
|
||||
|
||||
#### FR-DN-005: Bulk Import Support
|
||||
|
||||
**Priority**: MEDIUM
|
||||
**Status**: Required
|
||||
|
||||
@@ -167,6 +186,7 @@ interface ManualNumberValidation {
|
||||
ระบบต้องรองรับการ import เอกสารหลายรายการพร้อมกัน
|
||||
|
||||
**Acceptance Criteria**:
|
||||
|
||||
- รองรับไฟล์ CSV/Excel
|
||||
- Validate ทุกรายการก่อน import
|
||||
- แสดง preview ก่อน confirm
|
||||
@@ -175,6 +195,7 @@ interface ManualNumberValidation {
|
||||
- Generate import report
|
||||
|
||||
**CSV Format**:
|
||||
|
||||
```csv
|
||||
document_type,document_number,created_at,metadata
|
||||
COR,COR-2024-00001,2024-01-01,{"imported":true}
|
||||
@@ -186,6 +207,7 @@ COR,COR-2024-00002,2024-01-05,{"imported":true}
|
||||
### 2.3 Cancelled & Void Handling
|
||||
|
||||
#### FR-DN-006: Skip Cancelled Numbers
|
||||
|
||||
**Priority**: HIGH
|
||||
**Status**: Required
|
||||
|
||||
@@ -193,17 +215,20 @@ COR,COR-2024-00002,2024-01-05,{"imported":true}
|
||||
เลขที่เอกสารที่ถูกยกเลิกต้องไม่ถูก reuse
|
||||
|
||||
**Rationale**:
|
||||
|
||||
- รักษา audit trail ที่ชัดเจน
|
||||
- ป้องกันความสับสน
|
||||
- Legal compliance
|
||||
|
||||
**Acceptance Criteria**:
|
||||
|
||||
- Cancelled number ยังคงอยู่ในฐานข้อมูลพร้อม status
|
||||
- ระบบข้าม (skip) cancelled number เมื่อสร้างเลขที่ใหม่
|
||||
- บันทึกเหตุผลการยกเลิก
|
||||
- แสดง cancelled numbers ใน audit trail
|
||||
|
||||
**Example Timeline**:
|
||||
|
||||
```
|
||||
2025-00001 ✅ ACTIVE (created 2025-01-01)
|
||||
2025-00002 ❌ CANCELLED (created 2025-01-02, cancelled 2025-01-03)
|
||||
@@ -213,6 +238,7 @@ COR,COR-2024-00002,2024-01-05,{"imported":true}
|
||||
---
|
||||
|
||||
#### FR-DN-007: Void and Replace
|
||||
|
||||
**Priority**: HIGH
|
||||
**Status**: Required
|
||||
|
||||
@@ -220,6 +246,7 @@ COR,COR-2024-00002,2024-01-05,{"imported":true}
|
||||
ระบบต้องรองรับการ void เอกสารและสร้างเอกสารใหม่แทน
|
||||
|
||||
**Workflow**:
|
||||
|
||||
1. User เลือกเอกสารที่ต้องการ void
|
||||
2. ระบุเหตุผล (required)
|
||||
3. ระบบเปลี่ยน status เอกสารเดิมเป็น VOID
|
||||
@@ -227,6 +254,7 @@ COR,COR-2024-00002,2024-01-05,{"imported":true}
|
||||
5. Link เอกสารใหม่กับเดิม (voided_from_id)
|
||||
|
||||
**Acceptance Criteria**:
|
||||
|
||||
- เอกสารเดิม status = VOID (ไม่ลบ)
|
||||
- เอกสารใหม่ได้เลขที่ต่อเนื่องจาก sequence
|
||||
- มี reference link ระหว่างเอกสาร
|
||||
@@ -234,6 +262,7 @@ COR,COR-2024-00002,2024-01-05,{"imported":true}
|
||||
- แสดง void history chain (A→B→C)
|
||||
|
||||
**Database Relationship**:
|
||||
|
||||
```sql
|
||||
-- Original document
|
||||
id: 100
|
||||
@@ -254,6 +283,7 @@ voided_from_id: 100
|
||||
### 2.4 Concurrency & Performance
|
||||
|
||||
#### FR-DN-008: Prevent Race Conditions
|
||||
|
||||
**Priority**: CRITICAL
|
||||
**Status**: Required
|
||||
|
||||
@@ -261,17 +291,20 @@ voided_from_id: 100
|
||||
ระบบต้องป้องกันการสร้างเลขที่ซ้ำเมื่อมีการ request พร้อมกัน
|
||||
|
||||
**Solution**:
|
||||
|
||||
- Distributed locking (Redlock)
|
||||
- Database pessimistic locking
|
||||
- Two-phase commit pattern
|
||||
|
||||
**Acceptance Criteria**:
|
||||
|
||||
- Zero duplicate numbers ภายใต้ concurrent load (1000 req/s)
|
||||
- Lock acquisition time < 50ms (avg)
|
||||
- Automatic retry on lock failure (max 3 times)
|
||||
- Timeout handling (30 seconds)
|
||||
|
||||
**Load Test Requirements**:
|
||||
|
||||
```bash
|
||||
# Must pass without duplicates
|
||||
concurrent_users: 100
|
||||
@@ -283,6 +316,7 @@ expected_duplicates: 0
|
||||
---
|
||||
|
||||
#### FR-DN-009: Two-Phase Commit
|
||||
|
||||
**Priority**: HIGH
|
||||
**Status**: Required
|
||||
|
||||
@@ -290,26 +324,30 @@ expected_duplicates: 0
|
||||
ใช้ Two-phase commit pattern เพื่อความสมบูรณ์ของข้อมูล
|
||||
|
||||
**Phase 1: Reserve**
|
||||
|
||||
- ล็อกเลขที่และ reserve ไว้ชั่วคราว
|
||||
- Set TTL 5 นาที
|
||||
- Return reservation token
|
||||
|
||||
**Phase 2: Confirm or Cancel**
|
||||
|
||||
- Confirm: บันทึกลงฐานข้อมูลถาวร
|
||||
- Cancel: คืน lock และ reservation
|
||||
|
||||
**Acceptance Criteria**:
|
||||
|
||||
- Reservation ต้อง expire หลัง 5 นาที
|
||||
- Auto-cleanup expired reservations
|
||||
- Support explicit cancel
|
||||
- Idempotent confirmation
|
||||
|
||||
**API Flow**:
|
||||
|
||||
```typescript
|
||||
// Phase 1
|
||||
const { token, number } = await reserveNumber({
|
||||
document_type: 'COR',
|
||||
project_id: 1
|
||||
project_id: 1,
|
||||
});
|
||||
|
||||
// Do some work...
|
||||
@@ -325,6 +363,7 @@ await cancelReservation(token);
|
||||
### 2.5 Monitoring & Audit
|
||||
|
||||
#### FR-DN-010: Complete Audit Trail
|
||||
|
||||
**Priority**: HIGH
|
||||
**Status**: Required
|
||||
|
||||
@@ -332,6 +371,7 @@ await cancelReservation(token);
|
||||
บันทึกทุก operation ที่เกิดขึ้นกับเลขที่เอกสาร
|
||||
|
||||
**Events to Log**:
|
||||
|
||||
- Number reserved
|
||||
- Number confirmed
|
||||
- Number cancelled
|
||||
@@ -341,6 +381,7 @@ await cancelReservation(token);
|
||||
- Format changed
|
||||
|
||||
**Audit Fields**:
|
||||
|
||||
```typescript
|
||||
interface AuditLog {
|
||||
id: number;
|
||||
@@ -358,6 +399,7 @@ interface AuditLog {
|
||||
```
|
||||
|
||||
**Acceptance Criteria**:
|
||||
|
||||
- Log ทุก operation
|
||||
- Searchable by user, date, type
|
||||
- Export to CSV
|
||||
@@ -366,6 +408,7 @@ interface AuditLog {
|
||||
---
|
||||
|
||||
#### FR-DN-011: Metrics & Alerting
|
||||
|
||||
**Priority**: MEDIUM
|
||||
**Status**: Required
|
||||
|
||||
@@ -373,6 +416,7 @@ interface AuditLog {
|
||||
แสดงสถิติและส่ง alert เมื่อเกิดปัญหา
|
||||
|
||||
**Metrics**:
|
||||
|
||||
- Sequence utilization (% of max)
|
||||
- Average lock wait time
|
||||
- Failed lock attempts
|
||||
@@ -380,6 +424,7 @@ interface AuditLog {
|
||||
- Manual overrides per day
|
||||
|
||||
**Alerts**:
|
||||
|
||||
- Sequence >90% used (WARNING)
|
||||
- Sequence >95% used (CRITICAL)
|
||||
- Lock wait time >1s (WARNING)
|
||||
@@ -387,6 +432,7 @@ interface AuditLog {
|
||||
- High error rate (WARNING)
|
||||
|
||||
**Acceptance Criteria**:
|
||||
|
||||
- Real-time dashboard (Grafana)
|
||||
- Email/LINE notifications
|
||||
- Alert history tracking
|
||||
@@ -399,22 +445,26 @@ interface AuditLog {
|
||||
### 3.1 Performance
|
||||
|
||||
#### NFR-DN-001: Response Time
|
||||
|
||||
- Number generation: <100ms (p95)
|
||||
- Lock acquisition: <50ms (avg)
|
||||
- Bulk import: <5s per 100 records
|
||||
|
||||
#### NFR-DN-002: Throughput
|
||||
|
||||
- Support >500 req/s
|
||||
- Scale horizontally (add Redis nodes)
|
||||
|
||||
### 3.2 Reliability
|
||||
|
||||
#### NFR-DN-003: Availability
|
||||
|
||||
- System uptime: 99.9%
|
||||
- Graceful degradation (fallback to DB-only)
|
||||
- Auto-recovery from Redis failure
|
||||
|
||||
#### NFR-DN-004: Data Integrity
|
||||
|
||||
- Zero duplicate numbers (100% guarantee)
|
||||
- ACID transactions
|
||||
- Backup & restore procedures
|
||||
@@ -422,12 +472,14 @@ interface AuditLog {
|
||||
### 3.3 Security
|
||||
|
||||
#### NFR-DN-005: Access Control
|
||||
|
||||
- Admin only: Format configuration, sequence adjustment
|
||||
- Manager+: Manual override, void document
|
||||
- User: Auto-generate only
|
||||
- Audit all operations
|
||||
|
||||
#### NFR-DN-006: Data Protection
|
||||
|
||||
- Encrypt sensitive data (audit logs)
|
||||
- Secure Redis connections (TLS)
|
||||
- Rate limiting (100 req/min per user)
|
||||
@@ -435,6 +487,7 @@ interface AuditLog {
|
||||
### 3.4 Scalability
|
||||
|
||||
#### NFR-DN-007: Capacity Planning
|
||||
|
||||
- Support 10,000 documents/day
|
||||
- Store 10M+ historical numbers
|
||||
- Archive old audit logs (>2 years)
|
||||
@@ -442,6 +495,7 @@ interface AuditLog {
|
||||
### 3.5 Maintainability
|
||||
|
||||
#### NFR-DN-008: Code Quality
|
||||
|
||||
- Unit test coverage: >70%
|
||||
- Integration test coverage: >50%
|
||||
- E2E test coverage: >20 critical paths
|
||||
@@ -452,24 +506,28 @@ interface AuditLog {
|
||||
## 4. Business Rules
|
||||
|
||||
### BR-DN-001: Sequence Scope Rules
|
||||
|
||||
- Correspondence: Project-level + Yearly reset
|
||||
- RFA: Contract-level + Yearly reset
|
||||
- Drawings: Contract-level + No reset
|
||||
- Transmittal: Project-level + Monthly reset
|
||||
|
||||
### BR-DN-002: Number Format Rules
|
||||
|
||||
- Min length: 10 characters
|
||||
- Max length: 50 characters
|
||||
- Must include {SEQ} token
|
||||
- Must be ASCII only (no Thai/Chinese)
|
||||
|
||||
### BR-DN-003: Manual Override Rules
|
||||
|
||||
- Only for document_types with allow_manual_override=true
|
||||
- Must validate format
|
||||
- Must check duplicate
|
||||
- Requires Admin permission
|
||||
|
||||
### BR-DN-004: Void Rules
|
||||
|
||||
- Can only void ACTIVE documents
|
||||
- Cannot void already-VOID documents
|
||||
- Must provide reason (min 10 chars)
|
||||
@@ -480,6 +538,7 @@ interface AuditLog {
|
||||
## 5. Data Model
|
||||
|
||||
### 5.1 Numbering Configuration
|
||||
|
||||
```sql
|
||||
CREATE TABLE document_numbering_configs (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
@@ -495,6 +554,7 @@ CREATE TABLE document_numbering_configs (
|
||||
```
|
||||
|
||||
### 5.2 Sequence Counter
|
||||
|
||||
```sql
|
||||
CREATE TABLE document_numbering_sequences (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
@@ -509,6 +569,7 @@ CREATE TABLE document_numbering_sequences (
|
||||
```
|
||||
|
||||
### 5.3 Audit Log
|
||||
|
||||
```sql
|
||||
CREATE TABLE document_numbering_audit_logs (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
@@ -533,6 +594,7 @@ CREATE TABLE document_numbering_audit_logs (
|
||||
## 6. API Specifications
|
||||
|
||||
### 6.1 Reserve Number
|
||||
|
||||
```http
|
||||
POST /api/document-numbering/reserve
|
||||
Content-Type: application/json
|
||||
@@ -553,6 +615,7 @@ Response 201:
|
||||
```
|
||||
|
||||
### 6.2 Confirm Reservation
|
||||
|
||||
```http
|
||||
POST /api/document-numbering/confirm
|
||||
Content-Type: application/json
|
||||
@@ -569,6 +632,7 @@ Response 200:
|
||||
```
|
||||
|
||||
### 6.3 Manual Override
|
||||
|
||||
```http
|
||||
POST /api/document-numbering/manual
|
||||
Content-Type: application/json
|
||||
@@ -594,24 +658,28 @@ Response 201:
|
||||
## 7. Testing Requirements
|
||||
|
||||
### 7.1 Unit Tests
|
||||
|
||||
- Format parsing and validation
|
||||
- Sequence increment logic
|
||||
- Manual override validation
|
||||
- Scope resolution
|
||||
|
||||
### 7.2 Integration Tests
|
||||
|
||||
- Redis locking mechanism
|
||||
- Database transactions
|
||||
- Two-phase commit flow
|
||||
- Bulk import
|
||||
|
||||
### 7.3 Load Tests
|
||||
|
||||
- Concurrent number generation (1000 req/s)
|
||||
- Lock contention under load
|
||||
- Redis failover scenarios
|
||||
- Database connection pool exhaustion
|
||||
|
||||
### 7.4 E2E Tests
|
||||
|
||||
- Complete document creation flow
|
||||
- Void and replace workflow
|
||||
- Bulk import with validation
|
||||
@@ -622,6 +690,7 @@ Response 201:
|
||||
## 8. Migration Plan
|
||||
|
||||
### 8.1 Legacy Data Import
|
||||
|
||||
1. Export existing document numbers from old system
|
||||
2. Validate format and detect duplicates
|
||||
3. Bulk import using manual override API
|
||||
@@ -629,6 +698,7 @@ Response 201:
|
||||
5. Verify data integrity
|
||||
|
||||
### 8.2 Rollout Strategy
|
||||
|
||||
- Week 1-2: Deploy to staging, test with dummy data
|
||||
- Week 3: Deploy to production, enable for test project
|
||||
- Week 4: Enable for all projects
|
||||
@@ -639,18 +709,21 @@ Response 201:
|
||||
## 9. Success Criteria
|
||||
|
||||
### 9.1 Functional Success
|
||||
|
||||
- ✅ All FRs implemented and tested
|
||||
- ✅ Zero duplicate numbers in production
|
||||
- ✅ Migration of 50,000+ legacy documents
|
||||
- ✅ UAT approved by stakeholders
|
||||
|
||||
### 9.2 Performance Success
|
||||
|
||||
- ✅ Response time <100ms (p95)
|
||||
- ✅ Throughput >500 req/s
|
||||
- ✅ Lock acquisition <50ms (avg)
|
||||
- ✅ Zero downtime during deployment
|
||||
|
||||
### 9.3 Business Success
|
||||
|
||||
- ✅ Document creation speed +30%
|
||||
- ✅ Manual numbering errors -80%
|
||||
- ✅ User satisfaction >4.5/5
|
||||
@@ -661,12 +734,14 @@ Response 201:
|
||||
## 10. Appendix
|
||||
|
||||
### 10.1 Glossary
|
||||
|
||||
- **Sequence**: ลำดับตัวเลขที่เพิ่มขึ้นอัตโนมัติ
|
||||
- **Scope**: ขอบเขตที่ sequence แยกตาม (project, contract, etc.)
|
||||
- **Token**: Format placeholder (e.g., {YYYY}, {SEQ})
|
||||
- **Redlock**: Distributed locking algorithm สำหรับ Redis
|
||||
|
||||
### 10.2 References
|
||||
|
||||
- [ADR-018: Document Numbering Strategy](../05-decisions/adr-018-document-numbering.md)
|
||||
- [Two-Phase Commit Pattern](https://en.wikipedia.org/wiki/Two-phase_commit_protocol)
|
||||
- [Redlock Algorithm](https://redis.io/docs/manual/patterns/distributed-locks/)
|
||||
@@ -675,8 +750,8 @@ Response 201:
|
||||
|
||||
**Approval Sign-off**:
|
||||
|
||||
| Role | Name | Date | Signature |
|
||||
|------|------|------|-----------|
|
||||
| Product Owner | ___________ | _______ | _________ |
|
||||
| Tech Lead | ___________ | _______ | _________ |
|
||||
| QA Lead | ___________ | _______ | _________ |
|
||||
| Role | Name | Date | Signature |
|
||||
| ------------- | -------------- | ---------- | ---------- |
|
||||
| Product Owner | ****\_\_\_**** | **\_\_\_** | ****\_**** |
|
||||
| Tech Lead | ****\_\_\_**** | **\_\_\_** | ****\_**** |
|
||||
| QA Lead | ****\_\_\_**** | **\_\_\_** | ****\_**** |
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# 3.11 Document Numbering Management (การจัดการเลขที่เอกสาร)
|
||||
|
||||
---
|
||||
|
||||
title: 'Functional Requirements: Document Numbering Management'
|
||||
version: 1.6.0
|
||||
status: draft
|
||||
@@ -33,6 +34,7 @@ related:
|
||||
|
||||
การนับเลขจะแยกตาม **Counter Key** ที่ประกอบด้วยหลายส่วน ขึ้นกับประเภทเอกสาร
|
||||
**Scopes**:
|
||||
|
||||
1. **Global**: Sequence ระดับระบบทั้งหมด
|
||||
2. **Organization**: Sequence แยกตามองค์กรผู้ส่ง
|
||||
3. **Project**: Sequence แยกตามโครงการ
|
||||
@@ -41,16 +43,16 @@ related:
|
||||
|
||||
### Counter Key Components
|
||||
|
||||
| Component | Required? | Description | Database Source | Default if NULL |
|
||||
| ---------------------------- | ---------------- | ------------------- | --------------------------------------------------------- | --------------- |
|
||||
| `project_id` | ✅ Yes | ID โครงการ | Derived from user context or organization | - |
|
||||
| `originator_organization_id` | ✅ Yes | ID องค์กรผู้ส่ง | `correspondences.originator_id` | - |
|
||||
| Component | Required? | Description | Database Source | Default if NULL |
|
||||
| ---------------------------- | ---------------- | ------------------------ | --------------------------------------------------------- | --------------- |
|
||||
| `project_id` | ✅ Yes | ID โครงการ | Derived from user context or organization | - |
|
||||
| `originator_organization_id` | ✅ Yes | ID องค์กรผู้ส่ง | `correspondences.originator_id` | - |
|
||||
| `recipient_organization_id` | Depends on type | ID องค์กรผู้รับหลัก (TO) | `correspondence_recipients` where `recipient_type = 'TO'` | NULL for RFA |
|
||||
| `correspondence_type_id` | ✅ Yes | ID ประเภทเอกสาร | `correspondence_types.id` | 0 |
|
||||
| `sub_type_id` | TRANSMITTAL only | ID ประเภทย่อย | `correspondence_sub_types.id` | 0 |
|
||||
| `rfa_type_id` | RFA only | ID ประเภท RFA | `rfa_types.id` | 0 |
|
||||
| `discipline_id` | RFA only | ID สาขางาน | `disciplines.id` | 0 |
|
||||
| `current_year` | ✅ Yes | ปี ค.ศ. | System year (ปัจจุบัน) | - |
|
||||
| `correspondence_type_id` | ✅ Yes | ID ประเภทเอกสาร | `correspondence_types.id` | 0 |
|
||||
| `sub_type_id` | TRANSMITTAL only | ID ประเภทย่อย | `correspondence_sub_types.id` | 0 |
|
||||
| `rfa_type_id` | RFA only | ID ประเภท RFA | `rfa_types.id` | 0 |
|
||||
| `discipline_id` | RFA only | ID สาขางาน | `disciplines.id` | 0 |
|
||||
| `current_year` | ✅ Yes | ปี ค.ศ. | System year (ปัจจุบัน) | - |
|
||||
|
||||
### Counter Key แยกตามประเภทเอกสาร (correspondence_type_id)
|
||||
|
||||
@@ -61,8 +63,9 @@ related:
|
||||
```
|
||||
|
||||
**หมายเหตุ**:
|
||||
- ไม่ใช้ `discipline_id`, `sub_type_id`, `rfa_type_id`ทุกประเภทที่ไม่ได้ระบุเฉพาะจะใช้ Template นี้
|
||||
- ถ้ามีการเพิ่ม - correspondence type ใหม่ใน `correspondence_types` table จะใช้ Template นี้โดยอัตโนมัติ
|
||||
|
||||
- ไม่ใช้ `discipline_id`, `sub_type_id`, `rfa_type_id`ทุกประเภทที่ไม่ได้ระบุเฉพาะจะใช้ Template นี้
|
||||
- ถ้ามีการเพิ่ม - correspondence type ใหม่ใน `correspondence_types` table จะใช้ Template นี้โดยอัตโนมัติ
|
||||
|
||||
#### **TRANSMITTAL**:
|
||||
|
||||
@@ -71,7 +74,7 @@ related:
|
||||
correspondence_type_id, sub_type_id, 0, 0, current_year)
|
||||
```
|
||||
|
||||
*หมายเหตุ*: ใช้ `sub_type_id` เพิ่มเติม
|
||||
_หมายเหตุ_: ใช้ `sub_type_id` เพิ่มเติม
|
||||
|
||||
#### **RFA**:
|
||||
|
||||
@@ -80,7 +83,7 @@ related:
|
||||
correspondence_type_id, 0, rfa_type_id, discipline_id, current_year)
|
||||
```
|
||||
|
||||
*หมายเหตุ*: RFA ไม่ใช้ `recipient_organization_id` เพราะเป็นเอกสารโครงการ (CONTRACTOR → CONSULTANT → OWNER)
|
||||
_หมายเหตุ_: RFA ไม่ใช้ `recipient_organization_id` เพราะเป็นเอกสารโครงการ (CONTRACTOR → CONSULTANT → OWNER)
|
||||
|
||||
##### วิธีการหา project_id
|
||||
|
||||
@@ -110,6 +113,7 @@ related:
|
||||
## 3.11.3. Format Templates by Correspondence Type
|
||||
|
||||
> **📝 หมายเหตุสำคัญ**
|
||||
>
|
||||
> - Templates ด้านล่างเป็น **ตัวอย่าง** สำหรับประเภทเอกสารหลัก
|
||||
> - ระบบรองรับ **ทุกประเภทเอกสาร** ที่อยู่ใน `correspondence_types` table
|
||||
> - หากมีการเพิ่มประเภทใหม่ในอนาคต สามารถใช้งานได้โดยอัตโนมัติ
|
||||
@@ -212,19 +216,19 @@ Drawing Numbering ยังไม่ได้กำหนด Template เนื
|
||||
|
||||
## 3.11.4. Supported Token Types
|
||||
|
||||
| Token | Description | Example | Database Source |
|
||||
| -------------- | ---------------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------- |
|
||||
| `{PROJECT}` | รหัสโครงการ | `LCBP3`, `LCBP3-C2` | `projects.project_code` |
|
||||
| `{ORIGINATOR}` | รหัสองค์กรผู้ส่ง | `คคง.`, `ผรม.1` | `organizations.organization_code` via `correspondences.originator_id` |
|
||||
| `{RECIPIENT}` | รหัสองค์กรผู้รับหลัก (TO) | `สคฉ.3`, `กทท.` | `organizations.organization_code` via `correspondence_recipients` where `recipient_type = 'TO'` |
|
||||
| `{CORR_TYPE}` | รหัสประเภทเอกสาร | `RFA`, `TRANSMITTAL`, `LETTER` | `correspondence_types.type_code` |
|
||||
| `{SUB_TYPE}` | หมายเลขประเภทย่อย | `11`, `12`, `21` | `correspondence_sub_types.sub_type_number` |
|
||||
| `{RFA_TYPE}` | รหัสประเภท RFA | `SDW`, `RPT`, `MAT` | `rfa_types.type_code` |
|
||||
| Token | Description | Example | Database Source |
|
||||
| -------------- | ------------------------------ | ------------------------------ | ----------------------------------------------------------------------------------------------- |
|
||||
| `{PROJECT}` | รหัสโครงการ | `LCBP3`, `LCBP3-C2` | `projects.project_code` |
|
||||
| `{ORIGINATOR}` | รหัสองค์กรผู้ส่ง | `คคง.`, `ผรม.1` | `organizations.organization_code` via `correspondences.originator_id` |
|
||||
| `{RECIPIENT}` | รหัสองค์กรผู้รับหลัก (TO) | `สคฉ.3`, `กทท.` | `organizations.organization_code` via `correspondence_recipients` where `recipient_type = 'TO'` |
|
||||
| `{CORR_TYPE}` | รหัสประเภทเอกสาร | `RFA`, `TRANSMITTAL`, `LETTER` | `correspondence_types.type_code` |
|
||||
| `{SUB_TYPE}` | หมายเลขประเภทย่อย | `11`, `12`, `21` | `correspondence_sub_types.sub_type_number` |
|
||||
| `{RFA_TYPE}` | รหัสประเภท RFA | `SDW`, `RPT`, `MAT` | `rfa_types.type_code` |
|
||||
| `{DISCIPLINE}` | รหัสสาขาวิชา | `STR`, `TER`, `GEO` | `disciplines.discipline_code` |
|
||||
| `{SEQ:n}` | Running number (n = จำนวนหลัก) | `0001`, `0029`, `0985` | Based on `document_number_counters.last_number + 1` |
|
||||
| `{YEAR:B.E.}` | ปี พ.ศ. | `2568` | `document_number_counters.current_year + 543` |
|
||||
| `{YEAR:A.D.}` | ปี ค.ศ. | `2025` | `document_number_counters.current_year` |
|
||||
| `{REV}` | Revision Code | `A`, `B`, `AA` | `correspondence_revisions.revision_label` |
|
||||
| `{YEAR:B.E.}` | ปี พ.ศ. | `2568` | `document_number_counters.current_year + 543` |
|
||||
| `{YEAR:A.D.}` | ปี ค.ศ. | `2025` | `document_number_counters.current_year` |
|
||||
| `{REV}` | Revision Code | `A`, `B`, `AA` | `correspondence_revisions.revision_label` |
|
||||
|
||||
### Token Usage Notes
|
||||
|
||||
@@ -343,11 +347,13 @@ Drawing Numbering ยังไม่ได้กำหนด Template เนื
|
||||
ระบบต้องรองรับการกำหนดเลขที่เอกสารด้วยตนเอง (manual override)
|
||||
|
||||
**Use Cases**:
|
||||
|
||||
- 1. Import เอกสารเก่าจากระบบเดิม
|
||||
- 2. External documents จาก client/consultant
|
||||
- 3. Correction หลังพบความผิดพลาด
|
||||
|
||||
**Implementation Details:**
|
||||
|
||||
- ตรวจสอบ duplicate ก่อน save
|
||||
- Validate format ตามรูปแบบที่กำหนด
|
||||
- Auto-update sequence counter ถ้าเลขที่สูงกว่า current
|
||||
@@ -360,11 +366,13 @@ Drawing Numbering ยังไม่ได้กำหนด Template เนื
|
||||
ระบบต้องรองรับการกำหนดเลขที่เอกสารอัตโนมัติ (auto override)
|
||||
|
||||
**Use Cases**:
|
||||
|
||||
- 1. Import เอกสารเก่าจากระบบเดิม
|
||||
- 2. External documents จาก client/consultant
|
||||
- 3. Correction หลังพบความผิดพลาด
|
||||
|
||||
**Implementation Details:**
|
||||
|
||||
- ตรวจสอบ duplicate ก่อน save
|
||||
- Validate format ตามรูปแบบที่กำหนด
|
||||
- Auto-update sequence counter ถ้าเลขที่สูงกว่า current
|
||||
@@ -372,6 +380,7 @@ Drawing Numbering ยังไม่ได้กำหนด Template เนื
|
||||
- ต้องมีสิทธิ์ Admin ขึ้นไปเท่านั้น
|
||||
|
||||
###
|
||||
|
||||
## 3.11.9 Audit Trail Requirements
|
||||
|
||||
### 3.11.9.1. Audit Logging
|
||||
@@ -409,18 +418,18 @@ Drawing Numbering ยังไม่ได้กำหนด Template เนื
|
||||
|
||||
**SLA Targets:**
|
||||
|
||||
| Metric | Target | Notes |
|
||||
| ---------------- | -------- | ------------------------ |
|
||||
| Metric | Target | Notes |
|
||||
| ---------------- | ---------- | ---------------------------- |
|
||||
| 95th percentile | ≤ 2 วินาที | ตั้งแต่ request ถึง response |
|
||||
| 99th percentile | ≤ 5 วินาที | รวม retry attempts |
|
||||
| Normal operation | ≤ 500ms | ไม่มี retry |
|
||||
| 99th percentile | ≤ 5 วินาที | รวม retry attempts |
|
||||
| Normal operation | ≤ 500ms | ไม่มี retry |
|
||||
|
||||
### 3.11.10.2. Throughput
|
||||
|
||||
**Capacity Targets:**
|
||||
|
||||
| Load Level | Target | Notes |
|
||||
| ----------- | ----------- | --------- |
|
||||
| Load Level | Target | Notes |
|
||||
| ----------- | ----------- | ----------- |
|
||||
| Normal load | ≥ 50 req/s | ใช้งานปกติ |
|
||||
| Peak load | ≥ 100 req/s | ช่วงเร่งงาน |
|
||||
|
||||
@@ -456,8 +465,8 @@ Drawing Numbering ยังไม่ได้กำหนด Template เนื
|
||||
|
||||
ระบบ**ต้อง**alert สำหรับ conditions ต่อไปนี้:
|
||||
|
||||
| Severity | Condition | Action |
|
||||
| ---------- | ---------------------------- | ----------------- |
|
||||
| Severity | Condition | Action |
|
||||
| ----------- | ---------------------------- | ----------------- |
|
||||
| 🔴 Critical | Redis unavailable > 1 minute | PagerDuty + Slack |
|
||||
| 🔴 Critical | Lock failures > 10% in 5 min | PagerDuty + Slack |
|
||||
| 🟡 Warning | Lock failures > 5% in 5 min | Slack |
|
||||
@@ -477,7 +486,6 @@ Drawing Numbering ยังไม่ได้กำหนด Template เนื
|
||||
|
||||
**Operations Details:** ดู [Operations Guide - Section 3](file:///e:/np-dms/lcbp3/specs/04-operations/document-numbering-operations.md#3-monitoring--metrics)
|
||||
|
||||
|
||||
## 3.11.12 API Reference
|
||||
|
||||
เอกสารนี้อ้างอิงถึง API endpoints ต่อไปนี้:
|
||||
@@ -567,11 +575,13 @@ Reset counter (Super Admin only, requires approval)
|
||||
**Primary Table**: `document_number_counters`
|
||||
|
||||
**Required Columns:**
|
||||
|
||||
- Composite primary key: `(project_id, originator_organization_id, recipient_organization_id, correspondence_type_id, sub_type_id, rfa_type_id, discipline_id, current_year)`
|
||||
- `version` - สำหรับ optimistic locking
|
||||
- `last_number` - counter value (เริ่มจาก 0)
|
||||
|
||||
**Important Notes:**
|
||||
|
||||
- ใช้ `COALESCE(recipient_organization_id, 0)` ใน Primary Key เพื่อรองรับ NULL
|
||||
- Counter reset ทุกปี (เมื่อ `current_year` เปลี่ยน)
|
||||
- ต้องมี seed data สำหรับ `correspondence_types`, `rfa_types`, `disciplines` ก่อน
|
||||
@@ -583,6 +593,7 @@ Reset counter (Super Admin only, requires approval)
|
||||
**Primary Table**: `document_number_audit`
|
||||
|
||||
**Required Columns:**
|
||||
|
||||
- `document_id`, `generated_number`, `counter_key` (JSON)
|
||||
- `template_used`, `user_id`, `ip_address`
|
||||
- Performance metrics: `retry_count`, `lock_wait_ms`, `total_duration_ms`
|
||||
@@ -595,6 +606,7 @@ Reset counter (Super Admin only, requires approval)
|
||||
**Primary Table**: `document_number_errors`
|
||||
|
||||
**Required Columns:**
|
||||
|
||||
- `error_type` - ENUM classification
|
||||
- `error_message`, `stack_trace`, `context_data` (JSON)
|
||||
- `user_id`, `ip_address`, `created_at`, `resolved_at`
|
||||
@@ -610,6 +622,7 @@ Reset counter (Super Admin only, requires approval)
|
||||
### 3.11.15.2 Rate Limiting
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- Limit ต่อ user: **10 requests/minute** (prevent abuse)
|
||||
- Limit ต่อ IP: **50 requests/minute**
|
||||
|
||||
@@ -618,6 +631,7 @@ Reset counter (Super Admin only, requires approval)
|
||||
### 3.11.15.3 Audit & Compliance
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- บันทึกทุก API call ที่เกี่ยวข้องกับ document numbering
|
||||
- เก็บ audit log อย่างน้อย **7 ปี** (ตาม พ.ร.บ. ข้อมูลอิเล็กทรอนิกส์)
|
||||
- Audit log **ต้องไม่**สามารถแก้ไขได้ (immutable)
|
||||
@@ -631,7 +645,6 @@ Reset counter (Super Admin only, requires approval)
|
||||
- [API Design](file:///e:/np-dms/lcbp3/specs/02-architecture/api-design.md)
|
||||
- [Data Dictionary](file:///e:/np-dms/lcbp3/specs/04-data-dictionary/4_Data_Dictionary_V1_4_4.md)
|
||||
|
||||
|
||||
```
|
||||
lock:docnum:{project_id}:{org_id}:{recip_id}:{type_id}:{sub}:{rfa}:{disc}:{year}
|
||||
```
|
||||
@@ -660,9 +673,11 @@ export class DocumentNumberingLockService {
|
||||
}
|
||||
|
||||
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}`;
|
||||
return (
|
||||
`lock:docnum:${key.projectId}:${key.originatorOrgId}:` +
|
||||
`${key.recipientOrgId ?? 0}:${key.correspondenceTypeId}:` +
|
||||
`${key.subTypeId}:${key.rfaTypeId}:${key.disciplineId}:${key.year}`
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -717,7 +732,7 @@ export class DocumentNumberCounter {
|
||||
// ใช้ TypeORM Transaction + Optimistic Lock
|
||||
await this.connection.transaction(async (manager) => {
|
||||
const counter = await manager.findOne(DocumentNumberCounter, {
|
||||
where: counterKey
|
||||
where: counterKey,
|
||||
});
|
||||
|
||||
counter.lastNumber += 1;
|
||||
@@ -951,8 +966,16 @@ import { Injectable } from '@nestjs/common';
|
||||
@Injectable()
|
||||
export class TemplateValidator {
|
||||
private readonly ALLOWED_TOKENS = [
|
||||
'PROJECT', 'ORIGINATOR', 'RECIPIENT', 'CORR_TYPE',
|
||||
'SUB_TYPE', 'RFA_TYPE', 'DISCIPLINE', 'SEQ', 'YEAR', 'REV'
|
||||
'PROJECT',
|
||||
'ORIGINATOR',
|
||||
'RECIPIENT',
|
||||
'CORR_TYPE',
|
||||
'SUB_TYPE',
|
||||
'RFA_TYPE',
|
||||
'DISCIPLINE',
|
||||
'SEQ',
|
||||
'YEAR',
|
||||
'REV',
|
||||
];
|
||||
|
||||
validate(template: string, correspondenceType: string): ValidationResult {
|
||||
@@ -968,13 +991,13 @@ export class TemplateValidator {
|
||||
|
||||
// กฎพิเศษสำหรับแต่ละประเภท
|
||||
if (correspondenceType === 'RFA') {
|
||||
if (!tokens.some(t => t.name === 'PROJECT')) {
|
||||
if (!tokens.some((t) => t.name === 'PROJECT')) {
|
||||
errors.push('RFA template ต้องมี {PROJECT}');
|
||||
}
|
||||
}
|
||||
|
||||
if (correspondenceType === 'TRANSMITTAL') {
|
||||
if (!tokens.some(t => t.name === 'SUB_TYPE')) {
|
||||
if (!tokens.some((t) => t.name === 'SUB_TYPE')) {
|
||||
errors.push('TRANSMITTAL template ต้องมี {SUB_TYPE}');
|
||||
}
|
||||
}
|
||||
@@ -1045,14 +1068,14 @@ export class ConfigHistoryService {
|
||||
templateBefore: oldTemplate,
|
||||
templateAfter: newTemplate,
|
||||
changedBy: userId,
|
||||
changeReason: reason
|
||||
changeReason: reason,
|
||||
});
|
||||
}
|
||||
|
||||
async rollback(configId: number, historyId: number): Promise<void> {
|
||||
const history = await this.historyRepo.findOne({ where: { id: historyId }});
|
||||
const history = await this.historyRepo.findOne({ where: { id: historyId } });
|
||||
await this.configService.update(configId, {
|
||||
template: history.templateBefore
|
||||
template: history.templateBefore,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1108,6 +1131,7 @@ async resetCounter(
|
||||
await this.counterService.reset(configId);
|
||||
}
|
||||
```
|
||||
|
||||
## 3.11.21 Audit Trail
|
||||
|
||||
### 3.11.21.1 การบันทึก Audit Log
|
||||
@@ -1161,7 +1185,7 @@ export class DocumentNumberAuditService {
|
||||
retryCount: data.retryCount ?? 0,
|
||||
lockWaitMs: data.lockWaitMs,
|
||||
totalDurationMs: data.totalDurationMs,
|
||||
fallbackUsed: data.fallbackUsed ?? 'NONE'
|
||||
fallbackUsed: data.fallbackUsed ?? 'NONE',
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1194,7 +1218,7 @@ export class DocumentNumberingService {
|
||||
retryCount,
|
||||
lockWaitMs,
|
||||
totalDurationMs: Date.now() - startTime,
|
||||
fallbackUsed
|
||||
fallbackUsed,
|
||||
});
|
||||
|
||||
return number;
|
||||
@@ -1249,7 +1273,7 @@ export class ErrorLogService {
|
||||
stackTrace: error.stack,
|
||||
contextData: JSON.stringify(context),
|
||||
userId: context.userId,
|
||||
ipAddress: context.ipAddress
|
||||
ipAddress: context.ipAddress,
|
||||
});
|
||||
|
||||
// Alert if critical
|
||||
@@ -1257,7 +1281,7 @@ export class ErrorLogService {
|
||||
await this.alertService.sendAlert({
|
||||
severity: 'CRITICAL',
|
||||
title: `Document Numbering Error: ${errorType}`,
|
||||
details: error.message
|
||||
details: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1271,11 +1295,13 @@ export class ErrorLogService {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3.11.22 Performance Requirements
|
||||
|
||||
### 3.11.22.1 Response Time
|
||||
|
||||
**Target Response Times**:
|
||||
|
||||
- **95th percentile**: ≤ 2 วินาที
|
||||
- **99th percentile**: ≤ 5 วินาที
|
||||
- **Normal operation** (ไม่มี retry): ≤ 500ms
|
||||
@@ -1327,7 +1353,7 @@ const generationDuration = new Histogram({
|
||||
name: 'docnum_generation_duration_seconds',
|
||||
help: 'Document number generation duration',
|
||||
labelNames: ['project', 'type', 'status'],
|
||||
buckets: [0.1, 0.5, 1, 2, 5, 10]
|
||||
buckets: [0.1, 0.5, 1, 2, 5, 10],
|
||||
});
|
||||
|
||||
// Usage
|
||||
@@ -1357,7 +1383,7 @@ services:
|
||||
backend:
|
||||
image: lcbp3-backend:latest
|
||||
deploy:
|
||||
replicas: 3 # 3 instances
|
||||
replicas: 3 # 3 instances
|
||||
resources:
|
||||
limits:
|
||||
cpus: '1.0'
|
||||
@@ -1371,7 +1397,7 @@ services:
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||
ports:
|
||||
- "80:80"
|
||||
- '80:80'
|
||||
```
|
||||
|
||||
```nginx
|
||||
@@ -1402,7 +1428,7 @@ import { ThrottlerGuard } from '@nestjs/throttler';
|
||||
@Controller('document-numbering')
|
||||
@UseGuards(ThrottlerGuard)
|
||||
export class DocumentNumberingController {
|
||||
@Throttle(10, 60) // 10 requests per 60 seconds per user
|
||||
@Throttle(10, 60) // 10 requests per 60 seconds per user
|
||||
@Post('generate')
|
||||
async generate(@Body() dto: GenerateNumberDto) {
|
||||
return await this.service.generate(dto);
|
||||
@@ -1466,17 +1492,18 @@ export class HealthController {
|
||||
return this.health.check([
|
||||
() => this.db.pingCheck('database'),
|
||||
() => this.redis.pingCheck('redis'),
|
||||
() => this.customHealthCheck()
|
||||
() => this.customHealthCheck(),
|
||||
]);
|
||||
}
|
||||
|
||||
private async customHealthCheck() {
|
||||
// ทดสอบ generate document number
|
||||
const canGenerate = await this.testGeneration();
|
||||
return { documentNumbering: { status: canGenerate ? 'up' : 'down' }};
|
||||
return { documentNumbering: { status: canGenerate ? 'up' : 'down' } };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3.11.23 Monitoring & Alerting
|
||||
|
||||
### 3.11.23.1 Metrics Collection
|
||||
@@ -1495,13 +1522,13 @@ export class DocumentNumberingMetrics {
|
||||
name: 'docnum_lock_acquisition_duration_ms',
|
||||
help: 'Lock acquisition time in milliseconds',
|
||||
labelNames: ['project', 'type'],
|
||||
buckets: [10, 50, 100, 200, 500, 1000, 2000, 5000]
|
||||
buckets: [10, 50, 100, 200, 500, 1000, 2000, 5000],
|
||||
});
|
||||
|
||||
private lockAcquisitionFailures = new Counter({
|
||||
name: 'docnum_lock_acquisition_failures_total',
|
||||
help: 'Total number of lock acquisition failures',
|
||||
labelNames: ['project', 'type', 'reason']
|
||||
labelNames: ['project', 'type', 'reason'],
|
||||
});
|
||||
|
||||
// Generation metrics
|
||||
@@ -1509,25 +1536,25 @@ export class DocumentNumberingMetrics {
|
||||
name: 'docnum_generation_duration_ms',
|
||||
help: 'Total document number generation time',
|
||||
labelNames: ['project', 'type', 'status'],
|
||||
buckets: [100, 200, 500, 1000, 2000, 5000]
|
||||
buckets: [100, 200, 500, 1000, 2000, 5000],
|
||||
});
|
||||
|
||||
private retryCount = new Histogram({
|
||||
name: 'docnum_retry_count',
|
||||
help: 'Number of retries per generation',
|
||||
labelNames: ['project', 'type'],
|
||||
buckets: [0, 1, 2, 3, 5, 10]
|
||||
buckets: [0, 1, 2, 3, 5, 10],
|
||||
});
|
||||
|
||||
// Connection health
|
||||
private redisConnectionStatus = new Gauge({
|
||||
name: 'docnum_redis_connection_status',
|
||||
help: 'Redis connection status (1=up, 0=down)'
|
||||
help: 'Redis connection status (1=up, 0=down)',
|
||||
});
|
||||
|
||||
private dbConnectionPoolUsage = new Gauge({
|
||||
name: 'docnum_db_connection_pool_usage',
|
||||
help: 'Database connection pool usage percentage'
|
||||
help: 'Database connection pool usage percentage',
|
||||
});
|
||||
}
|
||||
```
|
||||
@@ -1549,8 +1576,8 @@ groups:
|
||||
severity: critical
|
||||
component: document-numbering
|
||||
annotations:
|
||||
summary: "Redis is unavailable for document numbering"
|
||||
description: "System is falling back to DB-only locking"
|
||||
summary: 'Redis is unavailable for document numbering'
|
||||
description: 'System is falling back to DB-only locking'
|
||||
|
||||
# CRITICAL: High lock failure rate
|
||||
- alert: HighLockFailureRate
|
||||
@@ -1560,8 +1587,8 @@ groups:
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Lock acquisition failure rate > 10%"
|
||||
description: "Check Redis and database performance"
|
||||
summary: 'Lock acquisition failure rate > 10%'
|
||||
description: 'Check Redis and database performance'
|
||||
|
||||
# WARNING: Elevated lock failure rate
|
||||
- alert: ElevatedLockFailureRate
|
||||
@@ -1571,7 +1598,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Lock acquisition failure rate > 5%"
|
||||
summary: 'Lock acquisition failure rate > 5%'
|
||||
|
||||
# WARNING: Slow lock acquisition
|
||||
- alert: SlowLockAcquisition
|
||||
@@ -1583,7 +1610,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "P95 lock acquisition time > 1 second"
|
||||
summary: 'P95 lock acquisition time > 1 second'
|
||||
|
||||
# WARNING: High retry count
|
||||
- alert: HighRetryCount
|
||||
@@ -1595,7 +1622,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Retry count > 100 per hour in project {{ $labels.project }}"
|
||||
summary: 'Retry count > 100 per hour in project {{ $labels.project }}'
|
||||
|
||||
# WARNING: Slow generation
|
||||
- alert: SlowDocumentNumberGeneration
|
||||
@@ -1607,7 +1634,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "P95 generation time > 2 seconds"
|
||||
summary: 'P95 generation time > 2 seconds'
|
||||
```
|
||||
|
||||
**AlertManager Configuration** (`alertmanager/config.yml`):
|
||||
@@ -1659,9 +1686,11 @@ receivers:
|
||||
"panels": [
|
||||
{
|
||||
"title": "Lock Acquisition Success Rate",
|
||||
"targets": [{
|
||||
"expr": "1 - (rate(docnum_lock_acquisition_failures_total[5m]) / rate(docnum_lock_acquisition_total[5m]))"
|
||||
}],
|
||||
"targets": [
|
||||
{
|
||||
"expr": "1 - (rate(docnum_lock_acquisition_failures_total[5m]) / rate(docnum_lock_acquisition_total[5m]))"
|
||||
}
|
||||
],
|
||||
"type": "graph",
|
||||
"gridPos": { "x": 0, "y": 0, "w": 12, "h": 8 }
|
||||
},
|
||||
@@ -1686,17 +1715,21 @@ receivers:
|
||||
},
|
||||
{
|
||||
"title": "Generation Rate (per minute)",
|
||||
"targets": [{
|
||||
"expr": "sum(rate(docnum_generation_duration_ms_count[1m])) * 60"
|
||||
}],
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(docnum_generation_duration_ms_count[1m])) * 60"
|
||||
}
|
||||
],
|
||||
"type": "stat",
|
||||
"gridPos": { "x": 0, "y": 8, "w": 6, "h": 4 }
|
||||
},
|
||||
{
|
||||
"title": "Redis Connection Status",
|
||||
"targets": [{
|
||||
"expr": "docnum_redis_connection_status"
|
||||
}],
|
||||
"targets": [
|
||||
{
|
||||
"expr": "docnum_redis_connection_status"
|
||||
}
|
||||
],
|
||||
"type": "stat",
|
||||
"gridPos": { "x": 6, "y": 8, "w": 6, "h": 4 },
|
||||
"thresholds": {
|
||||
@@ -1709,9 +1742,11 @@ receivers:
|
||||
},
|
||||
{
|
||||
"title": "Error Rate by Type",
|
||||
"targets": [{
|
||||
"expr": "sum by (reason) (rate(docnum_lock_acquisition_failures_total[5m]))"
|
||||
}],
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (reason) (rate(docnum_lock_acquisition_failures_total[5m]))"
|
||||
}
|
||||
],
|
||||
"type": "graph",
|
||||
"gridPos": { "x": 12, "y": 8, "w": 12, "h": 8 }
|
||||
}
|
||||
|
||||
@@ -58,12 +58,14 @@ ON document_number_counters (
|
||||
### 3.11.15.3. Important Notes
|
||||
|
||||
> **💡 Counter Key Design**
|
||||
>
|
||||
> - ใช้ `COALESCE(recipient_organization_id, 0)` ใน Primary Key เพื่อรองรับ NULL
|
||||
> - `version` column สำหรับ Optimistic Locking (ป้องกัน race condition)
|
||||
> - `last_number` เริ่มจาก 0 และเพิ่มขึ้นทีละ 1
|
||||
> - Counter reset ทุกปี (เมื่อ `current_year` เปลี่ยน)
|
||||
|
||||
> **⚠️ Migration Notes**
|
||||
>
|
||||
> - ไม่มีข้อมูลเก่า ไม่ต้องทำ backward compatibility
|
||||
> - สามารถสร้าง table ใหม่ได้เลยตาม schema ข้างต้น
|
||||
> - ต้องมี seed data สำหรับ `correspondence_types`, `rfa_types`, `disciplines` ก่อน
|
||||
|
||||
@@ -50,14 +50,12 @@
|
||||
### **2.3 Core Services:**
|
||||
|
||||
- **Code Hosting:** Gitea (Self-hosted on QNAP)
|
||||
|
||||
- Application name: git
|
||||
- Service name: gitea
|
||||
- Domain: `git.np-dms.work`
|
||||
- หน้าที่: เป็นศูนย์กลางในการเก็บและจัดการเวอร์ชันของโค้ด (Source Code) สำหรับทุกส่วน
|
||||
|
||||
- **Backend / Data Platform:** NestJS
|
||||
|
||||
- Application name: lcbp3-backend
|
||||
- Service name: backend
|
||||
- Domain: `backend.np-dms.work`
|
||||
@@ -65,7 +63,6 @@
|
||||
- หน้าที่: จัดการโครงสร้างข้อมูล (Data Models), สร้าง API, จัดการสิทธิ์ผู้ใช้ (Roles & Permissions), และสร้าง Workflow ทั้งหมดของระบบ
|
||||
|
||||
- **Database:** MariaDB 10.11
|
||||
|
||||
- Application name: lcbp3-db
|
||||
- Service name: mariadb
|
||||
- Domain: `db.np-dms.work`
|
||||
@@ -73,7 +70,6 @@
|
||||
- Tooling: DBeaver (Community Edition), phpmyadmin สำหรับการออกแบบและจัดการฐานข้อมูล
|
||||
|
||||
- **Database Management:** phpMyAdmin
|
||||
|
||||
- Application name: lcbp3-db
|
||||
- Service: phpmyadmin:5-apache
|
||||
- Service name: pma
|
||||
@@ -81,7 +77,6 @@
|
||||
- หน้าที่: จัดการฐานข้อมูล mariadb ผ่าน Web UI
|
||||
|
||||
- **Frontend:** Next.js
|
||||
|
||||
- Application name: lcbp3-frontend
|
||||
- Service name: frontend
|
||||
- Domain: `lcbp3.np-dms.work`
|
||||
@@ -91,7 +86,6 @@
|
||||
- หน้าที่: สร้างหน้าตาเว็บแอปพลิเคชันสำหรับให้ผู้ใช้งานเข้ามาดู Dashboard, จัดการเอกสาร, และติดตามงาน โดยจะสื่อสารกับ Backend ผ่าน API
|
||||
|
||||
- **Workflow Automation:** n8n
|
||||
|
||||
- Application name: lcbp3-n8n
|
||||
- Service: n8nio/n8n:latest
|
||||
- Service name: n8n
|
||||
@@ -99,7 +93,6 @@
|
||||
- หน้าที่: จัดการ workflow ระหว่าง Backend และ Line
|
||||
|
||||
- **Reverse Proxy:** Nginx Proxy Manager
|
||||
|
||||
- Application name: lcbp3-npm
|
||||
- Service: Nginx Proxy Manager (nginx-proxy-manage: latest)
|
||||
- Service name: npm
|
||||
@@ -112,20 +105,16 @@
|
||||
### **2.4 Business Logic & Consistency (ปรับปรุง):**
|
||||
|
||||
- **2.4.1 Unified Workflow Engine (หลัก):**
|
||||
|
||||
- ระบบการเดินเอกสารทั้งหมด (Correspondence, RFA, Circulation) ต้อง ใช้ Engine กลางเดียวกัน โดยกำหนด Logic ผ่าน Workflow DSL (JSON Configuration) แทนการเขียน Hard-coded ลงในตาราง
|
||||
- Workflow Versioning (เพิ่ม): ระบบต้องรองรับการกำหนด Version ของ Workflow Definition โดยเอกสารที่เริ่มกระบวนการไปแล้ว (In-progress instances) จะต้องใช้ Workflow Version เดิม จนกว่าจะสิ้นสุดกระบวนการ หรือได้รับคำสั่ง Migrate จาก Admin เพื่อป้องกันความขัดแย้งของ State
|
||||
|
||||
- **2.4.2 Separation of Concerns:**
|
||||
|
||||
- Module ต่างๆ (Correspondence, RFA, Circulation) จะเก็บเฉพาะข้อมูลของเอกสาร (Data) ส่วนสถานะและการเปลี่ยนสถานะ (State Transition) จะถูกจัดการโดย Workflow Engine
|
||||
|
||||
- **2.4.3 Idempotency & Locking:**
|
||||
|
||||
- ใช้กลไกเดิมในการป้องกันการทำรายการซ้ำ
|
||||
|
||||
- **2.4.4 Optimistic Locking (ใหม่):**
|
||||
|
||||
- ใช้ Version Column ใน Database ควบคู่กับ Redis Lock สำหรับการสร้างเลขที่เอกสาร เพื่อเป็น Safety Net ชั้นสุดท้าย
|
||||
|
||||
- **2.4.5** **จะไม่มีการใช้ SQL Triggers**
|
||||
@@ -207,27 +196,22 @@
|
||||
### **3.6. การจัดการ Workflow (Unified Workflow)**
|
||||
|
||||
- 3.6.1 Workflow Definition:
|
||||
|
||||
- Admin ต้องสามารถสร้าง/แก้ไข Workflow Rule ได้ผ่านหน้าจอ UI (DSL Editor)
|
||||
- รองรับการกำหนด State, Transition, Required Role, Condition (JS Expression)
|
||||
|
||||
- 3.6.2 Workflow Execution:
|
||||
|
||||
- ระบบต้องรองรับการสร้าง Instance ของ Workflow ผูกกับเอกสาร (Polymorphic)
|
||||
- รองรับการเปลี่ยนสถานะ (Action) เช่น Approve, Reject, Comment, Return
|
||||
- Auto-Action: รองรับการเปลี่ยนสถานะอัตโนมัติเมื่อครบเงื่อนไข (เช่น Review ครบทุกคน)
|
||||
|
||||
- 3.6.3 Flexibility:
|
||||
|
||||
- รองรับ Parallel Review (ส่งให้หลายคนตรวจพร้อมกัน)
|
||||
- รองรับ Conditional Flow (เช่น ถ้ายอดเงิน > X ให้เพิ่มผู้อนุมัติ)
|
||||
|
||||
- 3.6.4 Workflow การอนุมัติ:
|
||||
|
||||
- รองรับกระบวนการอนุมัติที่ซับซ้อนและเป็นลำดับ เช่น ส่งจาก Originator -> Organization 1 -> Organization 2 -> Organization 3 แล้วส่งผลกลับตามลำดับเดิม (โดยถ้า องกรณ์ใดใน Workflow ให้ส่งกลับ ก็สามารถส่งผลกลับตามลำดับเดิมโดยไม่ต้องรอให้ถึง องกรณืในลำดับถัดไป)
|
||||
|
||||
- 3.6.5 การจัดการ:
|
||||
|
||||
- สามารถกำหนดวันแล้วเสร็จ (Deadline) สำหรับผู้รับผิดชอบของ องกรณ์ ที่อยู่ใน Workflow ได้
|
||||
- มีระบบแจ้งเตือน ให้ผู้รับผิดชอบของ องกรณ์ ที่อยู่ใน Workflow ทราบ เมื่อมี RFA ใหม่ หรือมีการเปลี่ยนสถานะ
|
||||
- สามารถข้ามขั้นตอนได้ในกรณีพิเศษ (โดยผู้มีสิทธิ์)
|
||||
@@ -259,13 +243,11 @@
|
||||
### **3.10. การจัดเก็บไฟล์ (File Handling - ปรับปรุงใหญ่)**
|
||||
|
||||
- **3.10.1 Two-Phase Storage Strategy:**
|
||||
|
||||
1. **Phase 1 (Upload):** ไฟล์ถูกอัปโหลดเข้าโฟลเดอร์ `temp/` และได้รับ `temp_id`
|
||||
2. **Phase 2 (Commit):** เมื่อ User กด Submit ฟอร์มสำเร็จ ระบบจะย้ายไฟล์จาก `temp/` ไปยัง `permanent/{YYYY}/{MM}/` และบันทึกลง Database ภายใน Transaction เดียวกัน
|
||||
3. **Cleanup:** มี Cron Job ลบไฟล์ใน `temp/` ที่ค้างเกิน 24 ชม. (Orphan Files)
|
||||
|
||||
- **3.10.2 Security:**
|
||||
|
||||
- Virus Scan (ClamAV) ก่อนย้ายเข้า Permanent
|
||||
- Whitelist File Types: PDF, DWG, DOCX, XLSX, ZIP
|
||||
- Max Size: 50MB
|
||||
@@ -313,14 +295,12 @@
|
||||
### **3.12. การจัดการ JSON Details (JSON & Performance - ปรับปรุง)**
|
||||
|
||||
- **3.12.1 วัตถุประสงค์**
|
||||
|
||||
- จัดเก็บข้อมูลแบบไดนามิกที่เฉพาะเจาะจงกับแต่ละประเภทของเอกสาร
|
||||
- รองรับการขยายตัวของระบบโดยไม่ต้องเปลี่ยนแปลง database schema
|
||||
- จัดการ metadata และข้อมูลประกอบสำหรับ correspondence, routing, และ workflows
|
||||
|
||||
- **3.12.2 โครงสร้าง JSON Schema**
|
||||
ระบบต้องมี predefined JSON schemas สำหรับประเภทเอกสารต่างๆ:
|
||||
|
||||
- 3.12.2.1 Correspondence Types
|
||||
- GENERIC: ข้อมูลพื้นฐานสำหรับเอกสารทั่วไป
|
||||
- RFI: รายละเอียดคำถามและข้อมูลทางเทคนิค
|
||||
@@ -337,32 +317,27 @@
|
||||
- SECURITY_SCAN: ผลการตรวจสอบความปลอดภัย
|
||||
|
||||
- **3.12.3 Virtual Columns (ปรับปรุง):**
|
||||
|
||||
- สำหรับ Field ใน JSON ที่ต้องใช้ในการค้นหา (Search) หรือจัดเรียง (Sort) บ่อยๆ ต้องสร้าง Generated Column (Virtual Column) ใน Database และทำ Index ไว้ เพื่อประสิทธิภาพสูงสุด
|
||||
- Schema Consistency: Field ที่ถูกกำหนดเป็น Virtual Column ห้าม เปลี่ยนแปลง Key Name หรือ Data Type ใน JSON Schema Version ถัดไป หากจำเป็นต้องเปลี่ยน ต้องมีแผนการ Re-index หรือ Migration ข้อมูลเดิมที่ชัดเจน
|
||||
|
||||
- **3.12.4 Validation Rules**
|
||||
|
||||
- ต้องมี JSON schema validation สำหรับแต่ละประเภท
|
||||
- ต้องรองรับ versioning ของ schema
|
||||
- ต้องมี default values สำหรับ field ที่ไม่บังคับ
|
||||
- ต้องตรวจสอบ data types และ format ให้ถูกต้อง
|
||||
|
||||
- **3.12.5 Performance Requirements**
|
||||
|
||||
- JSON field ต้องมีขนาดไม่เกิน 50KB
|
||||
- ต้องรองรับ indexing สำหรับ field ที่ใช้ค้นหาบ่อย
|
||||
- ต้องมี compression สำหรับ JSON ขนาดใหญ่
|
||||
|
||||
- **3.12.6 Security Requirements**
|
||||
|
||||
- ต้อง sanitize JSON input เพื่อป้องกัน injection attacks
|
||||
- ต้อง validate JSON structure ก่อนบันทึก
|
||||
- ต้อง encrypt sensitive data ใน JSON fields
|
||||
|
||||
- 3.12.7 JSON Schema Migration Strategy (เพิ่มเติม)
|
||||
สำหรับ Schema Breaking Changes:
|
||||
|
||||
- Phase 1 - Add New Column
|
||||
ALTER TABLE correspondence_revisions
|
||||
ADD COLUMN ref_project_id_v2 INT GENERATED ALWAYS AS
|
||||
@@ -374,7 +349,6 @@
|
||||
- Phase 3 - Switch Application Code
|
||||
- Deploy code ที่ใช้ path ใหม่
|
||||
- Phase 4 - Remove Old Column
|
||||
|
||||
- หลังจาก verify แล้วว่าไม่มี error
|
||||
- Drop old virtual column
|
||||
|
||||
@@ -537,9 +511,7 @@
|
||||
### 6.1. การบันทึกการกระทำ (Audit Log)
|
||||
|
||||
- ทุกการกระทำที่สำคัญของผู้ใช้ (สร้าง, แก้ไข, ลบ, ส่ง) จะถูกบันทึกไว้ใน audit_logs เพื่อการตรวจสอบย้อนหลัง
|
||||
|
||||
- 6.1.1 ขอบเขตการบันทึก Audit Log:
|
||||
|
||||
- ทุกการสร้าง/แก้ไข/ลบ ข้อมูลสำคัญ (correspondences, RFAs, drawings, users, permissions)
|
||||
- ทุกการเข้าถึงข้อมูล sensitive (user data, financial information)
|
||||
- ทุกการเปลี่ยนสถานะ workflow (status transitions)
|
||||
|
||||
@@ -8,13 +8,16 @@
|
||||
## 📌 1. Objectives
|
||||
|
||||
# 📌 Section 1: Objectives (วัตถุประสงค์)
|
||||
|
||||
---
|
||||
|
||||
title: 'Objectives'
|
||||
version: 1.5.0
|
||||
status: first-draft
|
||||
owner: Nattanin Peancharoen
|
||||
last_updated: 2025-11-30
|
||||
related: -
|
||||
|
||||
---
|
||||
|
||||
สร้างเว็บแอปพลิเคชันสำหรับระบบบริหารจัดการเอกสารโครงการ (Document Management System - DMS) แบบครบวงจร ที่เน้นความปลอดภัยสูงสุด ความถูกต้องของข้อมูล (Data Integrity) และรองรับการขยายตัวในอนาคต (Scalability) โดยแก้ไขปัญหา Race Condition และเพิ่มความเสถียรในการจัดการไฟล์ และใช้ Unified Workflow Engine ในการจัดการกระบวนการอนุมัติทั้งหมดเพื่อความยืดหยุ่น
|
||||
@@ -29,18 +32,22 @@ related: -
|
||||
## 🛠️ 2. System Architecture
|
||||
|
||||
# 🛠️ Section 2: System Architecture (สถาปัตยกรรมและเทคโนโลยี)
|
||||
|
||||
---
|
||||
|
||||
title: 'System Architecture'
|
||||
version: 1.5.0
|
||||
status: first-draft
|
||||
owner: Nattanin Peancharoen
|
||||
last_updated: 2025-11-30
|
||||
related: - specs/01-objectives.md
|
||||
|
||||
---
|
||||
|
||||
ชื่อกำหนด สถาปัตยกรรมแบบ Headless/API-First ที่ทันสมัย ทำงานทั้งหมดบน QNAP Server ผ่าน Container Station เพื่อความสะดวกในการจัดการและบำรุงรักษา
|
||||
|
||||
### 2.1 Infrastructure & Environment
|
||||
|
||||
- Domain: `np-dms.work`, `www.np-dms.work`
|
||||
- IP: 159.192.126.103
|
||||
- Server: QNAP (Model: TS-473A, RAM: 32GB, CPU: AMD Ryzen V1500B)
|
||||
@@ -50,6 +57,7 @@ related: - specs/01-objectives.md
|
||||
- ข้อจำกัด: ไม่สามารถใช้ .env ในการกำหนดตัวแปรภายนอกได้ ต้องกำหนดใน docker-compose.yml เท่านั้น
|
||||
|
||||
### 2.2 Configuration Management
|
||||
|
||||
- ใช้ docker-compose.yml สำหรับ environment variables ตามข้อจำกัดของ QNAP
|
||||
- Secrets Management: ใช้ docker-compose.override.yml (gitignore) สำหรับ secret injection, Docker secrets หรือ Hashicorp Vault, encrypted env vars
|
||||
- Development environment ยังใช้ .env ได้ แต่ต้องไม่ commit เข้า version control
|
||||
@@ -58,6 +66,7 @@ related: - specs/01-objectives.md
|
||||
- Docker Network: lcbp3
|
||||
|
||||
### 2.3 Core Services
|
||||
|
||||
- Code Hosting: Gitea (`git.np-dms.work`)
|
||||
- Backend / Data Platform: NestJS (`backend.np-dms.work`)
|
||||
- Database: MariaDB 10.11 (`db.np-dms.work`)
|
||||
@@ -69,6 +78,7 @@ related: - specs/01-objectives.md
|
||||
- Cache: Redis
|
||||
|
||||
### 2.4 Business Logic & Consistency
|
||||
|
||||
- Unified Workflow Engine (central) with DSL JSON configuration
|
||||
- Versioning of workflow definitions, optimistic locking with Redis lock for document numbering
|
||||
- No SQL triggers; all business logic in NestJS services
|
||||
@@ -78,14 +88,18 @@ related: - specs/01-objectives.md
|
||||
### 3.1 Project Management
|
||||
|
||||
# 3.1 Project Management (การจัดการโครงสร้างโครงการและองค์กร)
|
||||
|
||||
---
|
||||
|
||||
title: "Functional Requirements: Project Management"
|
||||
version: 1.5.0
|
||||
status: first-draft
|
||||
owner: Nattanin Peancharoen
|
||||
last_updated: 2025-11-30
|
||||
related: -
|
||||
|
||||
---
|
||||
|
||||
- 3.1.1. โครงการ (Projects): ระบบต้องสามารถจัดการเอกสารภายในหลายโครงการได้ (ปัจจุบันมี 4 โครงการ และจะเพิ่มขึ้นในอนาคต)
|
||||
- 3.1.2. สัญญา (Contracts): ระบบต้องสามารถจัดการเอกสารภายในแต่ละสัญญาได้ ในแต่ละโครงการ มีได้หลายสัญญา หรืออย่างน้อย 1 สัญญา
|
||||
- 3.1.3. องค์กร (Organizations):
|
||||
@@ -95,14 +109,18 @@ related: -
|
||||
### 3.2 Correspondence Management
|
||||
|
||||
# 3.2 การจัดการเอกสารโต้ตอบ (Correspondence Management)
|
||||
|
||||
---
|
||||
|
||||
title: 'Functional Requirements: Correspondence Management'
|
||||
version: 1.5.0
|
||||
status: first-draft
|
||||
owner: Nattanin Peancharoen
|
||||
last_updated: 2025-11-30
|
||||
related: -
|
||||
|
||||
---
|
||||
|
||||
- 3.2.1. วัตถุประสงค์: เอกสารโต้ตอบระหว่างองค์กรภายในและภายนอกโครงการ, รองรับ To และ CC หลายองค์กร
|
||||
- 3.2.2. ประเภทเอกสาร: PDF, ZIP; Types include Letter, Email, RFI, RFA (with revisions)
|
||||
- 3.2.3. การสร้างเอกสาร: ผู้ใช้ที่มีสิทธิ์สร้าง Draft, Submit requires Admin approval
|
||||
@@ -112,14 +130,18 @@ related: -
|
||||
### 3.3 RFA Management
|
||||
|
||||
# 3.3 RFA Management (การจัดการเอกสาขออนุมัติ)
|
||||
|
||||
---
|
||||
|
||||
title: 'Functional Requirements: RFA Management'
|
||||
version: 1.5.0
|
||||
status: first-draft
|
||||
owner: Nattanin Peancharoen
|
||||
last_updated: 2025-11-30
|
||||
related: -
|
||||
|
||||
---
|
||||
|
||||
- 3.3.1. วัตถุประสงค์: เอกสารขออนุมัติภายในโครงการ
|
||||
- 3.3.2. ประเภทเอกสาร: PDF, รองรับหลาย revision และหลายประเภท RFA
|
||||
- 3.3.3. การสร้างเอกสาร: Draft creation by Document Control, Submit requires Admin
|
||||
@@ -129,14 +151,18 @@ related: -
|
||||
### 3.4 Contract Drawing Management
|
||||
|
||||
# 3.4 Contract Drawing Management (การจัดการแบบคู่สัญญา)
|
||||
|
||||
---
|
||||
|
||||
title: 'Functional Requirements: Contract Drawing Management'
|
||||
version: 1.5.0
|
||||
status: first-draft
|
||||
owner: Nattanin Peancharoen
|
||||
last_updated: 2025-11-30
|
||||
related: -
|
||||
|
||||
---
|
||||
|
||||
- 3.4.1. วัตถุประสงค์: ใช้เพื่ออ้างอิงและตรวจสอบ
|
||||
- 3.4.2. ประเภทเอกสาร: PDF
|
||||
- 3.4.3. การสร้างเอกสาร: ผู้มีสิทธิ์สร้างและแก้ไข
|
||||
@@ -145,14 +171,18 @@ related: -
|
||||
### 3.5 Shop Drawing Management
|
||||
|
||||
# 3.5 Shop Drawing Management (การจัดการแบบก่อสร้าง)
|
||||
|
||||
---
|
||||
|
||||
title: 'Functional Requirements: Shop Drawing Management'
|
||||
version: 1.5.0
|
||||
status: first-draft
|
||||
owner: Nattanin Peancharoen
|
||||
last_updated: 2025-11-30
|
||||
related: -
|
||||
|
||||
---
|
||||
|
||||
- 3.5.1. วัตถุประสงค์: ใช้ในการตรวจสอบและจัดส่งด้วย RFA
|
||||
- 3.5.2. ประเภทเอกสาร: PDF, DWG, ZIP
|
||||
- 3.5.3. การสร้างเอกสาร: ผู้มีสิทธิ์สร้าง/แก้ไข, Draft visibility control
|
||||
@@ -161,14 +191,18 @@ related: -
|
||||
### 3.6 Unified Workflow Management
|
||||
|
||||
# 3.6 Unified Workflow Management (การจัดการ Workflow)
|
||||
|
||||
---
|
||||
|
||||
title: 'Functional Requirements: Unified Workflow Management'
|
||||
version: 1.5.0
|
||||
status: first-draft
|
||||
owner: Nattanin Peancharoen
|
||||
last_updated: 2025-11-30
|
||||
related: -
|
||||
|
||||
---
|
||||
|
||||
- 3.6.1 Workflow Definition: Admin can create/edit rules via UI DSL Editor, define State, Transition, Role, Condition
|
||||
- 3.6.2 Workflow Execution: Create instances polymorphic to documents, support actions Approve, Reject, Comment, Return, auto-actions
|
||||
- 3.6.3 Flexibility: Parallel Review, Conditional Flow
|
||||
@@ -176,101 +210,134 @@ related: -
|
||||
- 3.6.5 Management: Deadline setting, notifications, step skipping, backtrack
|
||||
|
||||
# 3.7 Transmittals Management (การจัดการเอกสารนำส่ง)
|
||||
|
||||
---
|
||||
|
||||
title: 'Functional Requirements: Transmittals 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-functional-requirements.md
|
||||
|
||||
- specs/01-requirements/01-objectives.md
|
||||
- specs/01-requirements/02-architecture.md
|
||||
- specs/01-requirements/03-functional-requirements.md
|
||||
|
||||
---
|
||||
|
||||
## 3.7.1. วัตถุประสงค์:
|
||||
|
||||
- เอกสารนำส่ง ใช้สำหรับ นำส่ง Request for Approval (RFAS) หลายฉบับ ไปยังองค์กรอื่น
|
||||
|
||||
## 3.7.2. ประเภทเอกสาร:
|
||||
|
||||
- ไฟล์ PDF
|
||||
|
||||
## 3.7.3. การสร้างเอกสาร:
|
||||
|
||||
- ผู้ใช้ที่มีสิทธิ์ สามารถสร้างและแก้ไขได้
|
||||
|
||||
## 3.7.4. การอ้างอิงและจัดกลุ่ม:
|
||||
|
||||
- เอกสารนำส่ง เป็นส่วนหนึ่งใน Correspondence
|
||||
|
||||
# 3.8 Circulation Sheet Management (การจัดการใบเวียนเอกสาร)
|
||||
|
||||
---
|
||||
|
||||
title: 'Functional Requirements: Circulation Sheet 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-functional-requirements.md
|
||||
|
||||
- specs/01-requirements/01-objectives.md
|
||||
- specs/01-requirements/02-architecture.md
|
||||
- specs/01-requirements/03-functional-requirements.md
|
||||
|
||||
---
|
||||
|
||||
## 3.8.1. วัตถุประสงค์:
|
||||
|
||||
- การสื่อสาร เอกสาร (Correspondence) ทุกฉบับ จะมีใบเวียนเอกสารเพื่อควบคุมและมอบหมายงานภายในองค์กร (สามารถดูและแก้ไขได้เฉพาะคนในองค์กร)
|
||||
|
||||
## 3.8.2. ประเภทเอกสาร:
|
||||
|
||||
- ไฟล์ PDF
|
||||
|
||||
## 3.8.3. การสร้างเอกสาร:
|
||||
|
||||
- ผู้ใช้ที่มีสิทธิ์ในองค์กรนั้น สามารถสร้างและแก้ไขได้
|
||||
|
||||
## 3.8.4. การอ้างอิงและจัดกลุ่ม:
|
||||
|
||||
- การระบุผู้รับผิดชอบ:
|
||||
- ผู้รับผิดชอบหลัก (Main): มีได้หลายคน
|
||||
- ผู้ร่วมปฏิบัติงาน (Action): มีได้หลายคน
|
||||
- ผู้ที่ต้องรับทราบ (Information): มีได้หลายคน
|
||||
|
||||
## 3.8.5. การติดตามงาน:
|
||||
|
||||
- สามารถกำหนดวันแล้วเสร็จ (Deadline) สำหรับผู้รับผิดชอบประเภท Main และ Action ได้
|
||||
- มีระบบแจ้งเตือนเมื่อมี Circulation ใหม่ และแจ้งเตือนล่วงหน้าก่อนถึงวันแล้วเสร็จ
|
||||
- สามารถปิด Circulation ได้เมื่อดำเนินการตอบกลับไปยังองค์กรผู้ส่ง (Originator) แล้ว หรือ รับทราบแล้ว (For Information)
|
||||
|
||||
# 3.9 Logs Management (ประวัติการแก้ไข)
|
||||
|
||||
---
|
||||
|
||||
title: 'Functional Requirements: Logs 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-functional-requirements.md
|
||||
|
||||
- specs/01-requirements/01-objectives.md
|
||||
- specs/01-requirements/02-architecture.md
|
||||
- specs/01-requirements/03-functional-requirements.md
|
||||
|
||||
---
|
||||
|
||||
## 3.9.1. วัตถุประสงค์:
|
||||
|
||||
- เพื่อ บันทึกการกระทำ CRUD ของเอกสารทั้งหมด รวมถึงการ เข้าใช้งาน ของ users
|
||||
- admin สามารถดูประวัติการแก้ไขของเอกสารทั้งหมด พร้อม จัดทำรายงายตามข้อกำหนดที่ ต้องการได้
|
||||
|
||||
# 3.10 File Handling Management (การจัดการไฟล์)
|
||||
|
||||
---
|
||||
|
||||
title: 'Functional Requirements: File Handling 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-functional-requirements.md
|
||||
|
||||
- specs/01-requirements/01-objectives.md
|
||||
- specs/01-requirements/02-architecture.md
|
||||
- specs/01-requirements/03-functional-requirements.md
|
||||
|
||||
---
|
||||
|
||||
## 3.10.1 Two-Phase Storage Strategy:
|
||||
|
||||
1. Phase 1 (Upload): ไฟล์ถูกอัปโหลดเข้าโฟลเดอร์ temp/ และได้รับ temp_id
|
||||
2. Phase 2 (Commit): เมื่อ User กด Submit ฟอร์มสำเร็จ ระบบจะย้ายไฟล์จาก temp/ ไปยัง permanent/{YYYY}/{MM}/ และบันทึกลง Database ภายใน Transaction เดียวกัน
|
||||
3. Cleanup: มี Cron Job ลบไฟล์ใน temp/ ที่ค้างเกิน 24 ชม. (Orphan Files)
|
||||
|
||||
## 3.10.2 Security:
|
||||
|
||||
- Virus Scan (ClamAV) ก่อนย้ายเข้า permanent
|
||||
- Whitelist File Types: PDF, DWG, DOCX, XLSX, ZIP
|
||||
- Max Size: 50MB
|
||||
- Access Control: ตรวจสอบสิทธิ์ผ่าน Junction Table ก่อนให้ Download Link
|
||||
|
||||
## 3.10.3 ความปลอดภัยของการจัดเก็บไฟล์:
|
||||
|
||||
- ต้องมีการ scan virus สำหรับไฟล์ที่อัปโหลดทั้งหมด โดยใช้ ClamAV หรือบริการ third-party
|
||||
- จำกัดประเภทไฟล์ที่อนุญาต: PDF, DWG, DOCX, XLSX, ZIP (ต้องระบุรายการที่ชัดเจน)
|
||||
- ขนาดไฟล์สูงสุด: 50MB ต่อไฟล์
|
||||
@@ -280,64 +347,80 @@ related:
|
||||
- ต้องบันทึก audit log ทุกครั้งที่มีการดาวน์โหลดไฟล์สำคัญ
|
||||
|
||||
# 3.11 Document Numbering Management (การจัดการเลขที่เอกสาร)
|
||||
|
||||
---
|
||||
|
||||
title: 'Functional Requirements: Document Numbering Management'
|
||||
version: 1.6.0
|
||||
status: draft
|
||||
owner: Nattanin Peancharoen
|
||||
last_updated: 2025-12-02
|
||||
related:
|
||||
- specs/01-requirements/01-objectives.md
|
||||
- specs/01-requirements/02-architecture.md
|
||||
- specs/01-requirements/03-functional-requirements.md
|
||||
- specs/03-implementation/document-numbering.md
|
||||
- specs/04-operations/document-numbering-operations.md
|
||||
- specs/04-data-dictionary/4_Data_Dictionary_V1_4_4.md
|
||||
|
||||
- specs/01-requirements/01-objectives.md
|
||||
- specs/01-requirements/02-architecture.md
|
||||
- specs/01-requirements/03-functional-requirements.md
|
||||
- specs/03-implementation/document-numbering.md
|
||||
- specs/04-operations/document-numbering-operations.md
|
||||
- specs/04-data-dictionary/4_Data_Dictionary_V1_4_4.md
|
||||
|
||||
---
|
||||
|
||||
## 3.11.1 วัตถุประสงค์:
|
||||
|
||||
- ระบบต้องสามารถสร้างเลขที่เอกสาร (Running Number) ได้โดยอัตโนมัติและยืดหยุ่นสูง
|
||||
- ระบบต้องสามารถกำหนดรูปแบบ (template) เลขที่เอกสารได้ สำหรับแต่ละโครงการ, ชนิดเอกสาร, ประเภทเอกสาร
|
||||
- ระบบต้องรับประกัน Uniqueness ของเลขที่เอกสารในทุกสถานการณ์
|
||||
- ระบบต้องรองรับการทำงานแบบ concurrent ได้อย่างปลอดภัย
|
||||
|
||||
## 3.11.2 Logic การนับเลข (Counter Logic)
|
||||
|
||||
การนับเลขจะแยกตาม **Counter Key** ที่ประกอบด้วยหลายส่วน ขึ้นกับประเภทเอกสาร
|
||||
|
||||
### Counter Key Components
|
||||
| Component | Required? | Description | Database Source | Default if NULL |
|
||||
| ---------------------------- | ---------------- | ------------------- | --------------------------------------------------------- | --------------- |
|
||||
| `project_id` | ✅ Yes | ID โครงการ | Derived from user context or organization | - |
|
||||
| `originator_organization_id` | ✅ Yes | ID องค์กรผู้ส่ง | `correspondences.originator_id` | - |
|
||||
|
||||
| Component | Required? | Description | Database Source | Default if NULL |
|
||||
| ---------------------------- | ---------------- | ------------------------ | --------------------------------------------------------- | --------------- |
|
||||
| `project_id` | ✅ Yes | ID โครงการ | Derived from user context or organization | - |
|
||||
| `originator_organization_id` | ✅ Yes | ID องค์กรผู้ส่ง | `correspondences.originator_id` | - |
|
||||
| `recipient_organization_id` | Depends on type | ID องค์กรผู้รับหลัก (TO) | `correspondence_recipients` where `recipient_type = 'TO'` | NULL for RFA |
|
||||
| `correspondence_type_id` | ✅ Yes | ID ประเภทเอกสาร | `correspondence_types.id` | - |
|
||||
| `sub_type_id` | TRANSMITTAL only | ID ประเภทย่อย | `correspondence_sub_types.id` | 0 |
|
||||
| `rfa_type_id` | RFA only | ID ประเภท RFA | `rfa_types.id` | 0 |
|
||||
| `discipline_id` | RFA only | ID สาขางาน | `disciplines.id` | 0 |
|
||||
| `current_year` | ✅ Yes | ปี ค.ศ. | System year (ปัจจุบัน) | - |
|
||||
| `correspondence_type_id` | ✅ Yes | ID ประเภทเอกสาร | `correspondence_types.id` | - |
|
||||
| `sub_type_id` | TRANSMITTAL only | ID ประเภทย่อย | `correspondence_sub_types.id` | 0 |
|
||||
| `rfa_type_id` | RFA only | ID ประเภท RFA | `rfa_types.id` | 0 |
|
||||
| `discipline_id` | RFA only | ID สาขางาน | `disciplines.id` | 0 |
|
||||
| `current_year` | ✅ Yes | ปี ค.ศ. | System year (ปัจจุบัน) | - |
|
||||
|
||||
### Counter Key แยกตามประเภทเอกสาร
|
||||
|
||||
**LETTER / RFI / MEMO / EMAIL / MOM / INSTRUCTION / NOTICE / OTHER**:
|
||||
|
||||
```
|
||||
(project_id, originator_organization_id, recipient_organization_id,
|
||||
correspondence_type_id, 0, 0, 0, current_year)
|
||||
```
|
||||
*หมายเหตุ*: ไม่ใช้ `discipline_id`, `sub_type_id`, `rfa_type_id`
|
||||
|
||||
_หมายเหตุ_: ไม่ใช้ `discipline_id`, `sub_type_id`, `rfa_type_id`
|
||||
|
||||
**TRANSMITTAL**:
|
||||
|
||||
```
|
||||
(project_id, originator_organization_id, recipient_organization_id,
|
||||
correspondence_type_id, sub_type_id, 0, 0, current_year)
|
||||
```
|
||||
*หมายเหตุ*: ใช้ `sub_type_id` เพิ่มเติม
|
||||
|
||||
_หมายเหตุ_: ใช้ `sub_type_id` เพิ่มเติม
|
||||
|
||||
**RFA**:
|
||||
|
||||
```
|
||||
(project_id, originator_organization_id, NULL,
|
||||
correspondence_type_id, 0, rfa_type_id, discipline_id, current_year)
|
||||
```
|
||||
*หมายเหตุ*: RFA ไม่ใช้ `recipient_organization_id` เพราะเป็นเอกสารโครงการ (CONTRACTOR → CONSULTANT → OWNER)
|
||||
|
||||
_หมายเหตุ_: RFA ไม่ใช้ `recipient_organization_id` เพราะเป็นเอกสารโครงการ (CONTRACTOR → CONSULTANT → OWNER)
|
||||
|
||||
### วิธีการหา project_id
|
||||
|
||||
1. **User Context** (แนะนำ):
|
||||
- เมื่อ User สร้างเอกสาร UI จะให้เลือก Project/Contract ก่อน
|
||||
- ใช้ `project_id` จาก Context ที่เลือก
|
||||
@@ -350,97 +433,135 @@ related:
|
||||
- ตรวจสอบว่า project/contract เป็น active
|
||||
|
||||
### Fallback สำหรับค่า NULL
|
||||
|
||||
- `discipline_id`: ใช้ `0` (ไม่ระบุสาขางาน)
|
||||
- `sub_type_id`: ใช้ `0` (ไม่มีประเภทย่อย)
|
||||
- `rfa_type_id`: ใช้ `0` (ไม่ระบุประเภท RFA)
|
||||
- `recipient_organization_id`: ใช้ `NULL` สำหรับ RFA, Required สำหรับ LETTER/TRANSMITTAL
|
||||
|
||||
## 3.11.3 Format Templates by Correspondence Type
|
||||
|
||||
### 3.11.3.1. Letter (TYPE = LETTER)
|
||||
|
||||
**Template**:
|
||||
|
||||
```
|
||||
{ORIGINATOR}-{RECIPIENT}-{SEQ:4}-{YEAR:B.E.}
|
||||
```
|
||||
|
||||
**Example**: `คคง.-สคฉ.3-0001-2568`
|
||||
**Token Breakdown**:
|
||||
|
||||
- `คคง.` = {ORIGINATOR} = รหัสองค์กรผู้ส่ง
|
||||
- `สคฉ.3` = {RECIPIENT} = รหัสองค์กรผู้รับหลัก (TO)
|
||||
- `0001` = {SEQ:4} = Running number (เริ่ม 0001, 0002, ...)
|
||||
- `2568` = {YEAR:B.E.} = ปี พ.ศ.
|
||||
> **⚠️ Template vs Counter Separation**
|
||||
> **⚠️ Template vs Counter Separation**
|
||||
- {CORR_TYPE} **ไม่แสดง**ใน template เพื่อความกระชับ
|
||||
- แต่ระบบ**ยังใช้ correspondence_type_id ใน Counter Key** เพื่อแยก counter
|
||||
- LETTER, MEMO, RFI **มี counter แยกกัน** แม้ template format เหมือนกัน
|
||||
**Counter Key**: `(project_id, originator_org_id, recipient_org_id, corr_type_id, 0, 0, 0, year)`
|
||||
**Counter Key**: `(project_id, originator_org_id, recipient_org_id, corr_type_id, 0, 0, 0, year)`
|
||||
|
||||
---
|
||||
|
||||
### 3.11.3.2. Transmittal (TYPE = TRANSMITTAL)
|
||||
|
||||
**Template**:
|
||||
|
||||
```
|
||||
{ORIGINATOR}-{RECIPIENT}-{SUB_TYPE}-{SEQ:4}-{YEAR:B.E.}
|
||||
```
|
||||
|
||||
**Example**: `คคง.-สคฉ.3-21-0117-2568`
|
||||
**Token Breakdown**:
|
||||
|
||||
- `คคง.` = {ORIGINATOR}
|
||||
- `สคฉ.3` = {RECIPIENT}
|
||||
- `21` = {SUB_TYPE} = หมายเลขประเภทย่อย (11=MAT, 12=SHP, 13=DWG, 14=MET, ...)
|
||||
- `0117` = {SEQ:4}
|
||||
- `2568` = {YEAR:B.E.}
|
||||
> **⚠️ Template vs Counter Separation**
|
||||
> **⚠️ Template vs Counter Separation**
|
||||
- {CORR_TYPE} **ไม่แสดง**ใน template (เหมือน LETTER)
|
||||
- TRANSMITTAL มี counter แยกจาก LETTER
|
||||
**Counter Key**: `(project_id, originator_org_id, recipient_org_id, corr_type_id, sub_type_id, 0, 0, year)`
|
||||
**Counter Key**: `(project_id, originator_org_id, recipient_org_id, corr_type_id, sub_type_id, 0, 0, year)`
|
||||
|
||||
---
|
||||
|
||||
### 3.11.3.3. RFA (Request for Approval)
|
||||
|
||||
**Template**:
|
||||
|
||||
```
|
||||
{PROJECT}-{CORR_TYPE}-{DISCIPLINE}-{RFA_TYPE}-{SEQ:4}-{REV}
|
||||
```
|
||||
|
||||
**Example**: `LCBP3-C2-RFA-TER-RPT-0001-A`
|
||||
**Token Breakdown**:
|
||||
|
||||
- `LCBP3-C2` = {PROJECT} = รหัสโครงการ
|
||||
- `RFA` = {CORR_TYPE} = ประเภทเอกสาร (แสดงใน RFA template)
|
||||
- `TER` = {DISCIPLINE} = รหัสสาขางาน (TER=Terminal, STR=Structure, ...)
|
||||
- `RPT` = {RFA_TYPE} = ประเภท RFA (RPT=Report, SDW=Shop Drawing, ...)
|
||||
- `0001` = {SEQ:4}
|
||||
- `A` = {REV} = Revision code
|
||||
> **📋 RFA Workflow**
|
||||
> **📋 RFA Workflow**
|
||||
- RFA เป็น **เอกสารโครงการ** (Project-level document)
|
||||
- Workflow: **CONTRACTOR → CONSULTANT → OWNER**
|
||||
- ไม่มี specific `recipient_id` เพราะเป็น workflow ที่กำหนดไว้แล้ว
|
||||
**Counter Key**: `(project_id, originator_org_id, NULL, corr_type_id, 0, rfa_type_id, discipline_id, year)`
|
||||
**Counter Key**: `(project_id, originator_org_id, NULL, corr_type_id, 0, rfa_type_id, discipline_id, year)`
|
||||
|
||||
---
|
||||
|
||||
## 3.11.4. Security & Data Integrity Requirements
|
||||
|
||||
### 3.11.4.1. Concurrency Control
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- ระบบ**ต้อง**ป้องกัน race condition เมื่อมีการสร้างเลขที่เอกสารพร้อมกัน
|
||||
- ระบบ**ต้อง**รับประกัน uniqueness ของเลขที่เอกสารในทุกสถานการณ์
|
||||
- ระบบ**ควร**ใช้ Distributed Lock (Redis) เป็นกลไก primary
|
||||
- ระบบ**ต้อง**มี fallback mechanism เมื่อ Redis ไม่พร้อมใช้งาน
|
||||
|
||||
### 3.11.4.2. Data Integrity
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- ระบบ**ต้อง**ใช้ Optimistic Locking เพื่อตรวจจับ concurrent updates
|
||||
- ระบบ**ต้อง**มี database constraints เพื่อป้องกันข้อมูลผิดพลาด:
|
||||
- Unique constraint บน `document_number`
|
||||
- Foreign key constraints ทุก relationship
|
||||
- Check constraints สำหรับ business rules
|
||||
|
||||
---
|
||||
|
||||
## 3.11.5. Validation Rules
|
||||
|
||||
- ต้องมี JSON schema validation สำหรับแต่ละประเภท
|
||||
- ต้องรองรับ versioning ของ schema
|
||||
- ต้องมี default values สำหรับ field ที่ไม่บังคับ
|
||||
- ต้องตรวจสอบ data types และ format ให้ถูกต้อง
|
||||
|
||||
---
|
||||
|
||||
## 3.11.6. Performance Requirements
|
||||
|
||||
- JSON field ต้องมีขนาดไม่เกิน 50KB
|
||||
- ต้องรองรับ indexing สำหรับ field ที่ใช้ค้นหาบ่อย
|
||||
- ต้องมี compression สำหรับ JSON ขนาดใหญ่
|
||||
|
||||
---
|
||||
|
||||
## 3.11.7. Security Requirements
|
||||
|
||||
- ต้อง sanitize JSON input เพื่อป้องกัน injection attacks
|
||||
- ต้อง validate JSON structure ก่อนบันทึก
|
||||
- ต้อง encrypt sensitive data ใน JSON fields
|
||||
|
||||
---
|
||||
|
||||
## 3.11.8. JSON Schema Migration Strategy
|
||||
|
||||
- สำหรับ Schema Breaking Changes:
|
||||
- Phase 1 - Add New Column
|
||||
ALTER TABLE correspondence_revisions
|
||||
@@ -457,24 +578,34 @@ related:
|
||||
- สำหรับ Non-Breaking Changes
|
||||
- เพิ่ม optional field ใน schema
|
||||
- Old records ที่ไม่มี field = ใช้ default value
|
||||
|
||||
---
|
||||
|
||||
# 3.12 JSON Details Management (การจัดการ JSON Details)
|
||||
|
||||
---
|
||||
|
||||
title: 'Functional Requirements: JSON Details 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-functional-requirements.md
|
||||
|
||||
- specs/01-requirements/01-objectives.md
|
||||
- specs/01-requirements/02-architecture.md
|
||||
- specs/01-requirements/03-functional-requirements.md
|
||||
|
||||
---
|
||||
|
||||
## 3.12.1 วัตถุประสงค์
|
||||
|
||||
- จัดเก็บข้อมูลแบบไดนามิกที่เฉพาะเจาะจงกับแต่ละประเภทของเอกสาร
|
||||
- รองรับการขยายตัวของระบบโดยไม่ต้องเปลี่ยนแปลง database schema
|
||||
- จัดการ metadata และข้อมูลประกอบสำหรับ correspondence, routing, และ workflows
|
||||
|
||||
## 3.12.2 โครงสร้าง JSON Schema
|
||||
|
||||
- ระบบต้องมี predefined JSON schemas สำหรับประเภทเอกสารต่างๆ:
|
||||
- 3.12.2.1 Correspondence Types
|
||||
- GENERIC: ข้อมูลพื้นฐานสำหรับเอกสารทั่วไป
|
||||
@@ -490,27 +621,37 @@ related:
|
||||
- 3.12.2.3 Audit Types
|
||||
- AUDIT_LOG: ข้อมูลการตรวจสอบ
|
||||
- SECURITY_SCAN: ผลการตรวจสอบความปลอดภัย
|
||||
|
||||
## 3.12.3 Virtual Columns (ปรับปรุง)
|
||||
|
||||
- สำหรับ Field ใน JSON ที่ต้องใช้ในการค้นหา (Search) หรือจัดเรียง (Sort) บ่อยๆ ต้องสร้าง Generated Column (Virtual Column) ใน Database และทำ Index ไว้ เพื่อประสิทธิภาพสูงสุด
|
||||
- Schema Consistency: Field ที่ถูกกำหนดเป็น Virtual Column ห้าม เปลี่ยนแปลง Key Name หรือ Data Type ใน JSON Schema Version ถัดไป หากจำเป็นต้องเปลี่ยน ต้องมีแผนการ Re-index หรือ Migration ข้อมูลเดิมที่ชัดเจน
|
||||
|
||||
## 3.12.4 Validation Rules
|
||||
|
||||
- ต้องมี JSON schema validation สำหรับแต่ละประเภท
|
||||
- ต้องรองรับ versioning ของ schema
|
||||
- ต้องมี default values สำหรับ field ที่ไม่บังคับ
|
||||
- ต้องตรวจสอบ data types และ format ให้ถูกต้อง
|
||||
|
||||
## 3.12.5 Performance Requirements
|
||||
|
||||
- JSON field ต้องมีขนาดไม่เกิน 50KB
|
||||
- ต้องรองรับ indexing สำหรับ field ที่ใช้ค้นหาบ่อย
|
||||
- ต้องมี compression สำหรับ JSON ขนาดใหญ่
|
||||
|
||||
## 3.12.6 Security Requirements
|
||||
|
||||
- ต้อง sanitize JSON input เพื่อป้องกัน injection attacks
|
||||
- ต้อง validate JSON structure ก่อนบันทึก
|
||||
- ต้อง encrypt sensitive data ใน JSON fields
|
||||
|
||||
---
|
||||
|
||||
## 📂 4. Non‑Functional Requirements
|
||||
|
||||
# 4.1 Access Control
|
||||
|
||||
# 🔐 Section 4: Access Control (ข้อกำหนดด้านสิทธิ์และการเข้าถึง)
|
||||
|
||||
## 4.1. Overview:
|
||||
@@ -567,14 +708,14 @@ related:
|
||||
|
||||
### **4.7. Master Data Management**
|
||||
|
||||
| Master Data | Manager | Scope |
|
||||
| :-------------------------------------- | :------------------------------ | :------------------------------ |
|
||||
| Document Type (Correspondence, RFA) | **Superadmin** | Global |
|
||||
| Document Status (Draft, Approved, etc.) | **Superadmin** | Global |
|
||||
| Master Data | Manager | Scope |
|
||||
| :-------------------------------------- | :------------------------------ | :--------------------------------- |
|
||||
| Document Type (Correspondence, RFA) | **Superadmin** | Global |
|
||||
| Document Status (Draft, Approved, etc.) | **Superadmin** | Global |
|
||||
| Shop Drawing Category | **Project Manager** | Project (สร้างใหม่ได้ภายในโครงการ) |
|
||||
| Tags | **Org Admin / Project Manager** | Organization / Project |
|
||||
| Custom Roles | **Superadmin / Org Admin** | Global / Organization |
|
||||
| Document Numbering Formats | **Superadmin / Admin** | Global / Organization |
|
||||
| Tags | **Org Admin / Project Manager** | Organization / Project |
|
||||
| Custom Roles | **Superadmin / Org Admin** | Global / Organization |
|
||||
| Document Numbering Formats | **Superadmin / Admin** | Global / Organization |
|
||||
|
||||
## 4.8. การบันทึกการกระทำ (Audit Log)
|
||||
|
||||
@@ -616,7 +757,6 @@ related:
|
||||
- มีการใช้ Caching กับข้อมูลที่เรียกใช้บ่อย และใช้ Pagination ในตารางข้อมูลเพื่อจัดการข้อมูลจำนวนมาก
|
||||
|
||||
- ตัวชี้วัดประสิทธิภาพ:
|
||||
|
||||
- **API Response Time:** < 200ms (90th percentile) สำหรับ operation ทั่วไป
|
||||
- **Search Query Performance:** < 500ms สำหรับการค้นหาขั้นสูง
|
||||
- **File Upload Performance:** < 30 seconds สำหรับไฟล์ขนาด 50MB
|
||||
@@ -626,7 +766,6 @@ related:
|
||||
- **Application Startup Time:** < 30 seconds
|
||||
|
||||
- Caching Strategy:
|
||||
|
||||
- **Master Data Cache:** Roles, Permissions, Organizations, Project metadata (TTL: 1 hour)
|
||||
- **User Session Cache:** User permissions และ profile data (TTL: 30 minutes)
|
||||
- **Search Result Cache:** Frequently searched queries (TTL: 15 minutes)
|
||||
@@ -716,7 +855,7 @@ related:
|
||||
|
||||
## 4.15. กลยุทธ์การแจ้งเตือน (Notification Strategy - ปรับปรุง)
|
||||
|
||||
- ระบบจะส่งการแจ้งเตือน (ผ่าน Email หรือ Line [cite: 2.7]) เมื่อมีการกระทำที่สำคัญ** ดังนี้:
|
||||
- ระบบจะส่งการแจ้งเตือน (ผ่าน Email หรือ Line [cite: 2.7]) เมื่อมีการกระทำที่สำคัญ\*\* ดังนี้:
|
||||
1. เมื่อมีเอกสารใหม่ (Correspondence, RFA) ถูกส่งมาถึงองค์กรณ์ของเรา
|
||||
2. เมื่อมีใบเวียน (Circulation) ใหม่ มอบหมายงานมาที่เรา
|
||||
3. (ทางเลือก) เมื่อเอกสารที่เราส่งไป ถูกดำเนินการ (เช่น อนุมัติ/ปฏิเสธ)
|
||||
@@ -773,10 +912,12 @@ related:
|
||||
- ต้องมี default fallback values
|
||||
- ต้องบันทึก error logs สำหรับ validation failures
|
||||
|
||||
|
||||
# 5. UI/UX Guidelines
|
||||
|
||||
# 👥 Section 5: UI/UX Requirements (ข้อกำหนดด้านผู้ใช้งาน)
|
||||
|
||||
---
|
||||
|
||||
title: 'UI/UX Requirements'
|
||||
version: 1.5.0
|
||||
status: first-draft
|
||||
@@ -786,6 +927,7 @@ related:
|
||||
|
||||
- specs/02-architecture/data-model.md#correspondence
|
||||
- specs/03-implementation/backend-guidelines.md#correspondencemodule
|
||||
|
||||
---
|
||||
|
||||
## 5.1. Layout หลัก
|
||||
@@ -860,6 +1002,7 @@ related:
|
||||
- 5.13.3 Performance: ต้องรองรับการส่งข้อมูลแบบ Streaming (Range Requests) เพื่อให้เปิดดูไฟล์ขนาดใหญ่ (เช่น แบบแปลน 50MB+) ได้รวดเร็วโดยไม่ต้องรอโหลดเสร็จทั้งไฟล์
|
||||
|
||||
## 🧪 6. Testing Requirements
|
||||
|
||||
## 6.1 Unit Testing
|
||||
|
||||
- ต้องมี unit tests สำหรับ business logic ทั้งหมด
|
||||
@@ -907,5 +1050,7 @@ related:
|
||||
- **Permission Test:** ทดสอบ CASL Integration ทั้งฝั่ง Backend และ Frontend ให้ตรงกัน
|
||||
|
||||
---
|
||||
*Version History*
|
||||
|
||||
_Version History_
|
||||
|
||||
- **v1.5.1** – 2025‑12‑04 – Consolidated requirement specifications into single document.
|
||||
|
||||
@@ -487,13 +487,11 @@ Backend (NestJS) ควรเป็น **Stateless** (ไม่เก็บส
|
||||
1. User สร้างเอกสาร → เลือก routing template
|
||||
2. System สร้าง routing instances ตาม template
|
||||
3. สำหรับแต่ละ routing step:
|
||||
|
||||
- กำหนด due date (จาก expected_days)
|
||||
- ส่ง notification ไปยังองค์กรผู้รับ
|
||||
- อัพเดทสถานะเป็น SENT
|
||||
|
||||
4. เมื่อองค์กรผู้รับดำเนินการ:
|
||||
|
||||
- อัพเดทสถานะเป็น ACTIONED/FORWARDED/REPLIED
|
||||
- บันทึก processed_by และ processed_at
|
||||
- ส่ง notification ไปยังขั้นตอนต่อไป (ถ้ามี)
|
||||
@@ -707,27 +705,23 @@ export function QueryProvider({ children }: { children: React.ReactNode }) {
|
||||
สำหรับ Next.js App Router เราจะใช้ State Management แบบ Simplified โดยแบ่งเป็น 3 ระดับหลัก:
|
||||
|
||||
- 4.10.ๅ. **Server State (สถานะข้อมูลจากเซิร์ฟเวอร์)**
|
||||
|
||||
- **เครื่องมือ:** **TanStack Query (React Query)**
|
||||
- **ใช้เมื่อ:** จัดการข้อมูลที่ดึงมาจาก NestJS API ทั้งหมด
|
||||
- **ครอบคลุม:** รายการ correspondences, rfas, drawings, users, permissions
|
||||
- **ประโยชน์:** จัดการ Caching, Re-fetching, Background Sync อัตโนมัติ
|
||||
|
||||
- 4.10.2. **Form State (สถานะของฟอร์ม):**
|
||||
|
||||
- **เครื่องมือ:** **React Hook Form** + **Zod** (สำหรับ validation)
|
||||
- **ใช้เมื่อ:** จัดการฟอร์มที่ซับซ้อนทั้งหมด
|
||||
- **ครอบคลุม:** ฟอร์มสร้าง/แก้ไข RFA, Correspondence, Circulation
|
||||
- **รวมฟีเจอร์:** Auto-save drafts ลง LocalStorage
|
||||
|
||||
- 4.10.3. **UI State (สถานะ UI ชั่วคราว):**
|
||||
|
||||
- **เครื่องมือ:** **useState**, **useReducer** (ใน Component)
|
||||
- **ใช้เมื่อ:** จัดการสถานะเฉพาะ Component
|
||||
- **ครอบคลุม:** Modal เปิด/ปิด, Dropdown state, Loading states
|
||||
|
||||
- **ยกเลิกการใช้:**
|
||||
|
||||
- ❌ Zustand (ไม่จำเป็น เนื่องจากใช้ React Query และ React Hook Form)
|
||||
- ❌ Context API สำหรับ Server State (ใช้ React Query แทน)
|
||||
|
||||
|
||||
@@ -48,14 +48,14 @@
|
||||
|
||||
### **2.4 ข้อตกลงในการตั้งชื่อ (Naming Conventions)**
|
||||
|
||||
| Entity (สิ่งที่ตั้งชื่อ) | Convention (รูปแบบ) | Example (ตัวอย่าง) |
|
||||
| :-------------------- | :----------------- | :--------------------------------- |
|
||||
| Classes | PascalCase | UserService |
|
||||
| Property | snake_case | user_id |
|
||||
| Variables & Functions | camelCase | getUserInfo |
|
||||
| Files & Folders | kebab-case | user-service.ts |
|
||||
| Environment Variables | UPPERCASE | DATABASE_URL |
|
||||
| Booleans | Verb + Noun | isActive, canDelete, hasPermission |
|
||||
| Entity (สิ่งที่ตั้งชื่อ) | Convention (รูปแบบ) | Example (ตัวอย่าง) |
|
||||
| :----------------------- | :------------------ | :--------------------------------- |
|
||||
| Classes | PascalCase | UserService |
|
||||
| Property | snake_case | user_id |
|
||||
| Variables & Functions | camelCase | getUserInfo |
|
||||
| Files & Folders | kebab-case | user-service.ts |
|
||||
| Environment Variables | UPPERCASE | DATABASE_URL |
|
||||
| Booleans | Verb + Noun | isActive, canDelete, hasPermission |
|
||||
|
||||
ใช้คำเต็ม — ไม่ใช้อักษรย่อ — ยกเว้นคำมาตรฐาน (เช่น API, URL, req, res, err, ctx)
|
||||
|
||||
@@ -504,34 +504,34 @@ export class IdempotencyInterceptor implements NestInterceptor {
|
||||
|
||||
### **3.13 เเทคโนโลยีที่ใช้ (Technology Stack)**
|
||||
|
||||
| ส่วน | Library/Tool | หมายเหตุ |
|
||||
| ----------------------- | ---------------------------------------------------- | -------------------------------------- |
|
||||
| **Framework** | `@nestjs/core`, `@nestjs/common` | Core Framework |
|
||||
| **Language** | `TypeScript` | ใช้ TypeScript ทั้งระบบ |
|
||||
| **Database** | `MariaDB 10.11` | ฐานข้อมูลหลัก |
|
||||
| **ORM** | `@nestjs/typeorm`, `typeorm` | 🗃️จัดการการเชื่อมต่อและ Query ฐานข้อมูล |
|
||||
| **Validation** | `class-validator`, `class-transformer` | 📦ตรวจสอบและแปลงข้อมูลใน DTO |
|
||||
| **Auth** | `@nestjs/jwt`, `@nestjs/passport`, `passport-jwt` | 🔐การยืนยันตัวตนด้วย JWT |
|
||||
| **Authorization** | `casl` | 🔐จัดการสิทธิ์แบบ RBAC |
|
||||
| **File Upload** | `multer` | 📁จัดการการอัปโหลดไฟล์ |
|
||||
| **Search** | `@nestjs/elasticsearch` | 🔍สำหรับการค้นหาขั้นสูง |
|
||||
| **Notification** | `nodemailer` | 📬ส่งอีเมลแจ้งเตือน |
|
||||
| ส่วน | Library/Tool | หมายเหตุ |
|
||||
| ----------------------- | ---------------------------------------------------- | -------------------------------------------- |
|
||||
| **Framework** | `@nestjs/core`, `@nestjs/common` | Core Framework |
|
||||
| **Language** | `TypeScript` | ใช้ TypeScript ทั้งระบบ |
|
||||
| **Database** | `MariaDB 10.11` | ฐานข้อมูลหลัก |
|
||||
| **ORM** | `@nestjs/typeorm`, `typeorm` | 🗃️จัดการการเชื่อมต่อและ Query ฐานข้อมูล |
|
||||
| **Validation** | `class-validator`, `class-transformer` | 📦ตรวจสอบและแปลงข้อมูลใน DTO |
|
||||
| **Auth** | `@nestjs/jwt`, `@nestjs/passport`, `passport-jwt` | 🔐การยืนยันตัวตนด้วย JWT |
|
||||
| **Authorization** | `casl` | 🔐จัดการสิทธิ์แบบ RBAC |
|
||||
| **File Upload** | `multer` | 📁จัดการการอัปโหลดไฟล์ |
|
||||
| **Search** | `@nestjs/elasticsearch` | 🔍สำหรับการค้นหาขั้นสูง |
|
||||
| **Notification** | `nodemailer` | 📬ส่งอีเมลแจ้งเตือน |
|
||||
| **Scheduling** | `@nestjs/schedule` | 📬สำหรับ Cron Jobs (เช่น แจ้งเตือน Deadline) |
|
||||
| **Logging** | `winston` | 📊บันทึก Log ที่มีประสิทธิภาพ |
|
||||
| **Testing** | `@nestjs/testing`, `jest`, `supertest` | 🧪ทดสอบ Unit, Integration และ E2E |
|
||||
| **Documentation** | `@nestjs/swagger` | 🌐สร้าง API Documentation อัตโนมัติ |
|
||||
| **Security** | `helmet`, `rate-limiter-flexible` | 🛡️เพิ่มความปลอดภัยให้ API |
|
||||
| **Resilience** | `@nestjs/circuit-breaker` | 🔄 Circuit breaker pattern |
|
||||
| **Caching** | `@nestjs/cache-manager`, `cache-manager-redis-store` | 💾 Distributed caching |
|
||||
| **Security** | `helmet`, `csurf`, `rate-limiter-flexible` | 🛡️ Security enhancements |
|
||||
| **Validation** | `class-validator`, `class-transformer` | ✅ Input validation |
|
||||
| **Monitoring** | `@nestjs/monitoring`, `winston` | 📊 Application monitoring |
|
||||
| **File Processing** | `clamscan` | 🦠 Virus scanning |
|
||||
| **Cryptography** | `bcrypt`, `crypto` | 🔐 Password hashing และ checksums |
|
||||
| **JSON Validation** | `ajv`, `ajv-formats` | 🎯 JSON schema validation |
|
||||
| **JSON Processing** | `jsonpath`, `json-schema-ref-parser` | 🔧 JSON manipulation |
|
||||
| **Data Transformation** | `class-transformer` | 🔄 Object transformation |
|
||||
| **Compression** | `compression` | 📦 JSON compression |
|
||||
| **Logging** | `winston` | 📊บันทึก Log ที่มีประสิทธิภาพ |
|
||||
| **Testing** | `@nestjs/testing`, `jest`, `supertest` | 🧪ทดสอบ Unit, Integration และ E2E |
|
||||
| **Documentation** | `@nestjs/swagger` | 🌐สร้าง API Documentation อัตโนมัติ |
|
||||
| **Security** | `helmet`, `rate-limiter-flexible` | 🛡️เพิ่มความปลอดภัยให้ API |
|
||||
| **Resilience** | `@nestjs/circuit-breaker` | 🔄 Circuit breaker pattern |
|
||||
| **Caching** | `@nestjs/cache-manager`, `cache-manager-redis-store` | 💾 Distributed caching |
|
||||
| **Security** | `helmet`, `csurf`, `rate-limiter-flexible` | 🛡️ Security enhancements |
|
||||
| **Validation** | `class-validator`, `class-transformer` | ✅ Input validation |
|
||||
| **Monitoring** | `@nestjs/monitoring`, `winston` | 📊 Application monitoring |
|
||||
| **File Processing** | `clamscan` | 🦠 Virus scanning |
|
||||
| **Cryptography** | `bcrypt`, `crypto` | 🔐 Password hashing และ checksums |
|
||||
| **JSON Validation** | `ajv`, `ajv-formats` | 🎯 JSON schema validation |
|
||||
| **JSON Processing** | `jsonpath`, `json-schema-ref-parser` | 🔧 JSON manipulation |
|
||||
| **Data Transformation** | `class-transformer` | 🔄 Object transformation |
|
||||
| **Compression** | `compression` | 📦 JSON compression |
|
||||
|
||||
### **3.14 Security Testing:**
|
||||
|
||||
@@ -587,13 +587,11 @@ Backend (NestJS) ควรเป็น **Stateless** (ไม่เก็บส
|
||||
1. User สร้างเอกสาร → เลือก routing template
|
||||
2. System สร้าง routing instances ตาม template
|
||||
3. สำหรับแต่ละ routing step:
|
||||
|
||||
- กำหนด due date (จาก expected_days)
|
||||
- ส่ง notification ไปยังองค์กรผู้รับ
|
||||
- อัพเดทสถานะเป็น SENT
|
||||
|
||||
4. เมื่อองค์กรผู้รับดำเนินการ:
|
||||
|
||||
- อัพเดทสถานะเป็น ACTIONED/FORWARDED/REPLIED
|
||||
- บันทึก processed_by และ processed_at
|
||||
- ส่ง notification ไปยังขั้นตอนต่อไป (ถ้ามี)
|
||||
@@ -730,24 +728,20 @@ export function DynamicForm({ schemaName, onSubmit }: DynamicFormProps) {
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
{Object.entries(schema.schema_definition.properties).map(
|
||||
([key, prop]: [string, any]) => (
|
||||
<FormField
|
||||
key={key}
|
||||
control={form.control}
|
||||
name={key}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{prop.title || key}</FormLabel>
|
||||
<FormControl>
|
||||
{renderFieldByType(prop.type, field)}
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
{Object.entries(schema.schema_definition.properties).map(([key, prop]: [string, any]) => (
|
||||
<FormField
|
||||
key={key}
|
||||
control={form.control}
|
||||
name={key}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{prop.title || key}</FormLabel>
|
||||
<FormControl>{renderFieldByType(prop.type, field)}</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
<Button type="submit">บันทึก</Button>
|
||||
</form>
|
||||
</Form>
|
||||
@@ -808,9 +802,7 @@ export function ResponsiveTable({ data }: { data: Correspondence[] }) {
|
||||
<Card key={item.id}>
|
||||
<CardContent className="pt-6">
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
เลขที่เอกสาร
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">เลขที่เอกสาร</div>
|
||||
<div className="font-medium">{item.doc_number}</div>
|
||||
<div className="text-sm text-muted-foreground">เรื่อง</div>
|
||||
<div>{item.title}</div>
|
||||
@@ -933,10 +925,7 @@ export function useCreateCorrespondence() {
|
||||
await queryClient.cancelQueries({ queryKey: ['correspondences'] });
|
||||
const previous = queryClient.getQueryData(['correspondences']);
|
||||
|
||||
queryClient.setQueryData(['correspondences'], (old: any) => [
|
||||
...old,
|
||||
newCorrespondence,
|
||||
]);
|
||||
queryClient.setQueryData(['correspondences'], (old: any) => [...old, newCorrespondence]);
|
||||
|
||||
return { previous };
|
||||
},
|
||||
@@ -1100,24 +1089,17 @@ export function FileUploadZone({
|
||||
className={`
|
||||
border-2 border-dashed rounded-lg p-8 text-center cursor-pointer
|
||||
transition-colors
|
||||
${
|
||||
isDragActive
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-muted-foreground/25'
|
||||
}
|
||||
${isDragActive ? 'border-primary bg-primary/5' : 'border-muted-foreground/25'}
|
||||
hover:border-primary hover:bg-primary/5
|
||||
`}
|
||||
>
|
||||
<input {...getInputProps()} />
|
||||
<Upload className="mx-auto h-12 w-12 text-muted-foreground" />
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
{isDragActive
|
||||
? 'วางไฟล์ที่นี่...'
|
||||
: 'ลากไฟล์มาวางที่นี่ หรือคลิกเพื่อเลือกไฟล์'}
|
||||
{isDragActive ? 'วางไฟล์ที่นี่...' : 'ลากไฟล์มาวางที่นี่ หรือคลิกเพื่อเลือกไฟล์'}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
รองรับ: {acceptedTypes.join(', ')} (สูงสุด {maxFiles} ไฟล์,{' '}
|
||||
{maxSize / 1024 / 1024}MB/ไฟล์)
|
||||
รองรับ: {acceptedTypes.join(', ')} (สูงสุด {maxFiles} ไฟล์, {maxSize / 1024 / 1024}MB/ไฟล์)
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
@@ -1198,15 +1180,15 @@ updateRFA(@Param('id') id: string) {
|
||||
|
||||
## 🔗 **7. แนวทางการบูรณาการ Full Stack (Full Stack Integration Guidelines)**
|
||||
|
||||
| Aspect (แง่มุม) | Backend (NestJS) | Frontend (NextJS) | UI Layer (Tailwind/Shadcn) |
|
||||
| :----------------------- | :------------------------- | :---------------------------- | :------------------------------------- |
|
||||
| API | REST / GraphQL Controllers | API hooks ผ่าน fetch/axios/SWR | Components ที่รับข้อมูล |
|
||||
| Validation (การตรวจสอบ) | class-validator DTOs | zod / react-hook-form | สถานะของฟอร์ม/input ใน Shadcn |
|
||||
| Auth (การยืนยันตัวตน) | Guards, JWT | NextAuth / cookies | สถานะ UI ของ Auth (loading, signed in) |
|
||||
| Errors (ข้อผิดพลาด) | Global filters | Toasts / modals | Alerts / ข้อความ feedback |
|
||||
| Testing (การทดสอบ) | Jest (unit/e2e) | Vitest / Playwright | Visual regression |
|
||||
| Styles (สไตล์) | Scoped modules (ถ้าจำเป็น) | Tailwind / Shadcn | Tailwind utilities |
|
||||
| Accessibility (การเข้าถึง) | Guards + filters | ARIA attributes | Semantic HTML |
|
||||
| Aspect (แง่มุม) | Backend (NestJS) | Frontend (NextJS) | UI Layer (Tailwind/Shadcn) |
|
||||
| :------------------------- | :------------------------- | :----------------------------- | :------------------------------------- |
|
||||
| API | REST / GraphQL Controllers | API hooks ผ่าน fetch/axios/SWR | Components ที่รับข้อมูล |
|
||||
| Validation (การตรวจสอบ) | class-validator DTOs | zod / react-hook-form | สถานะของฟอร์ม/input ใน Shadcn |
|
||||
| Auth (การยืนยันตัวตน) | Guards, JWT | NextAuth / cookies | สถานะ UI ของ Auth (loading, signed in) |
|
||||
| Errors (ข้อผิดพลาด) | Global filters | Toasts / modals | Alerts / ข้อความ feedback |
|
||||
| Testing (การทดสอบ) | Jest (unit/e2e) | Vitest / Playwright | Visual regression |
|
||||
| Styles (สไตล์) | Scoped modules (ถ้าจำเป็น) | Tailwind / Shadcn | Tailwind utilities |
|
||||
| Accessibility (การเข้าถึง) | Guards + filters | ARIA attributes | Semantic HTML |
|
||||
|
||||
## 🗂️ **8. ข้อตกลงเฉพาะสำหรับ DMS (LCBP3-DMS)**
|
||||
|
||||
@@ -1216,17 +1198,17 @@ updateRFA(@Param('id') id: string) {
|
||||
|
||||
บันทึกการดำเนินการ CRUD และการจับคู่ทั้งหมดลงในตาราง audit_logs
|
||||
|
||||
| Field (ฟิลด์) | Type (จาก SQL) | Description (คำอธิบาย) |
|
||||
| :----------- | :------------- | :----------------------------------------------- |
|
||||
| audit_id | BIGINT | Primary Key |
|
||||
| user_id | INT | ผู้ใช้ที่ดำเนินการ (FK -> users) |
|
||||
| action | VARCHAR(100) | rfa.create, correspondence.update, login.success |
|
||||
| entity_type | VARCHAR(50) | ชื่อตาราง/โมดูล เช่น 'rfa', 'correspondence' |
|
||||
| entity_id | VARCHAR(50) | Primary ID ของระเบียนที่ได้รับผลกระทบ |
|
||||
| details_json | JSON | ข้อมูลบริบท (เช่น ฟิลด์ที่มีการเปลี่ยนแปลง) |
|
||||
| ip_address | VARCHAR(45) | IP address ของผู้ดำเนินการ |
|
||||
| user_agent | VARCHAR(255) | User Agent ของผู้ดำเนินการ |
|
||||
| created_at | TIMESTAMP | Timestamp (UTC) |
|
||||
| Field (ฟิลด์) | Type (จาก SQL) | Description (คำอธิบาย) |
|
||||
| :------------ | :------------- | :----------------------------------------------- |
|
||||
| audit_id | BIGINT | Primary Key |
|
||||
| user_id | INT | ผู้ใช้ที่ดำเนินการ (FK -> users) |
|
||||
| action | VARCHAR(100) | rfa.create, correspondence.update, login.success |
|
||||
| entity_type | VARCHAR(50) | ชื่อตาราง/โมดูล เช่น 'rfa', 'correspondence' |
|
||||
| entity_id | VARCHAR(50) | Primary ID ของระเบียนที่ได้รับผลกระทบ |
|
||||
| details_json | JSON | ข้อมูลบริบท (เช่น ฟิลด์ที่มีการเปลี่ยนแปลง) |
|
||||
| ip_address | VARCHAR(45) | IP address ของผู้ดำเนินการ |
|
||||
| user_agent | VARCHAR(255) | User Agent ของผู้ดำเนินการ |
|
||||
| created_at | TIMESTAMP | Timestamp (UTC) |
|
||||
|
||||
### 📂**8.2 การจัดการไฟล์ (File Handling)**
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# “Phase 6A + Technical Design Document : Workflow DSL (Mini-Language)”**
|
||||
# “Phase 6A + Technical Design Document : Workflow DSL (Mini-Language)”\*\*
|
||||
|
||||
ออกแบบสำหรับระบบ Workflow Engine กลางของโครงการ
|
||||
**ไม่มีโค้ดผูกกับ Framework** เพื่อให้สามารถนำไป Implement ใน NestJS หรือ Microservice ใด ๆ ได้
|
||||
|
||||
@@ -10,20 +11,19 @@
|
||||
|
||||
ใน Phase นี้ จะเริ่มสร้าง “Workflow DSL (Domain-Specific Language)” สำหรับนิยามกฎการเดินงาน (Workflow Transition Rules) ให้สามารถ:
|
||||
|
||||
* แยก **Business Workflow Logic** ออกจาก Source Code
|
||||
* แก้ไขกฎ Workflow ได้โดย **ไม่ต้องแก้โค้ดและไม่ต้อง Deploy ใหม่**
|
||||
* รองรับ Document หลายประเภท เช่น
|
||||
- แยก **Business Workflow Logic** ออกจาก Source Code
|
||||
- แก้ไขกฎ Workflow ได้โดย **ไม่ต้องแก้โค้ดและไม่ต้อง Deploy ใหม่**
|
||||
- รองรับ Document หลายประเภท เช่น
|
||||
- Correspondence
|
||||
- RFA
|
||||
- Internal Circulation
|
||||
- Document Transmittal
|
||||
|
||||
* Correspondence
|
||||
* RFA
|
||||
* Internal Circulation
|
||||
* Document Transmittal
|
||||
* รองรับ Multi-step routing, skip, reject, rollback, parallel assignments
|
||||
* สามารถนำไปใช้งานทั้งใน
|
||||
|
||||
* Backend (NestJS)
|
||||
* Frontend (UI Driven)
|
||||
* External Microservices
|
||||
- รองรับ Multi-step routing, skip, reject, rollback, parallel assignments
|
||||
- สามารถนำไปใช้งานทั้งใน
|
||||
- Backend (NestJS)
|
||||
- Frontend (UI Driven)
|
||||
- External Microservices
|
||||
|
||||
---
|
||||
|
||||
@@ -35,12 +35,12 @@
|
||||
|
||||
### 🧩 Output ของ Phase 6A
|
||||
|
||||
* DSL Specification (Grammar)
|
||||
* JSON Schema for Workflow Definition
|
||||
* Workflow Rule Interpreter (Parser + Executor)
|
||||
* Validation Engine (Compile-time and Runtime)
|
||||
* Storage (DB Table / Registry)
|
||||
* Execution API:
|
||||
- DSL Specification (Grammar)
|
||||
- JSON Schema for Workflow Definition
|
||||
- Workflow Rule Interpreter (Parser + Executor)
|
||||
- Validation Engine (Compile-time and Runtime)
|
||||
- Storage (DB Table / Registry)
|
||||
- Execution API:
|
||||
|
||||
| Action | Description |
|
||||
| -------------------------------- | ------------------------------- |
|
||||
@@ -59,22 +59,22 @@
|
||||
|
||||
#### Functional Requirements
|
||||
|
||||
* นิยาม Workflow เป็นภาษาคล้าย State Machine
|
||||
* แต่ละเอกสารมี **State, Actions, Entry/Exit Events**
|
||||
* สามารถมี:
|
||||
|
||||
* Required approvals
|
||||
* Conditional transition
|
||||
* Auto-transition
|
||||
* Parallel approval
|
||||
* Return/rollback
|
||||
- นิยาม Workflow เป็นภาษาคล้าย State Machine
|
||||
- แต่ละเอกสารมี **State, Actions, Entry/Exit Events**
|
||||
- สามารถมี:
|
||||
- Required approvals
|
||||
- Conditional transition
|
||||
- Auto-transition
|
||||
- Parallel approval
|
||||
- Return/rollback
|
||||
|
||||
####
|
||||
* Running time: < 20ms ต่อคำสั่ง
|
||||
* Hot reload ไม่ต้อง Compile ใหม่ทั้ง Backend
|
||||
* DSL ต้อง Debug ได้ง่าย
|
||||
* ต้อง Versioned
|
||||
* ต้องรองรับ Audit 100%
|
||||
|
||||
- Running time: < 20ms ต่อคำสั่ง
|
||||
- Hot reload ไม่ต้อง Compile ใหม่ทั้ง Backend
|
||||
- DSL ต้อง Debug ได้ง่าย
|
||||
- ต้อง Versioned
|
||||
- ต้องรองรับ Audit 100%
|
||||
|
||||
---
|
||||
|
||||
@@ -122,12 +122,8 @@ states:
|
||||
"transitions": {
|
||||
"SUBMIT": {
|
||||
"to": "IN_REVIEW",
|
||||
"requirements": [
|
||||
{ "role": "ENGINEER" }
|
||||
],
|
||||
"events": [
|
||||
{ "type": "notify", "target": "reviewer" }
|
||||
]
|
||||
"requirements": [{ "role": "ENGINEER" }],
|
||||
"events": [{ "type": "notify", "target": "reviewer" }]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -136,9 +132,7 @@ states:
|
||||
"APPROVE": { "to": "APPROVED" },
|
||||
"REJECT": {
|
||||
"to": "DRAFT",
|
||||
"events": [
|
||||
{ "type": "notify", "target": "creator" }
|
||||
]
|
||||
"events": [{ "type": "notify", "target": "creator" }]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -162,14 +156,14 @@ version = "version" ":" number ;
|
||||
states = "states:" state_list ;
|
||||
state_list = { state } ;
|
||||
|
||||
state = "- name:" identifier
|
||||
[ "initial:" boolean ]
|
||||
state = "- name:" identifier
|
||||
[ "initial:" boolean ]
|
||||
[ "terminal:" boolean ]
|
||||
[ "on:" transition_list ] ;
|
||||
|
||||
transition_list = { transition } ;
|
||||
|
||||
transition = action ":"
|
||||
transition = action ":"
|
||||
indent "to:" identifier
|
||||
[ indent "require:" requirements ]
|
||||
[ indent "events:" event_list ] ;
|
||||
@@ -186,23 +180,23 @@ event = "- notify:" identifier ;
|
||||
|
||||
#### 5.1 State Rules
|
||||
|
||||
* ต้องมีอย่างน้อย 1 state ที่ `initial: true`
|
||||
* หาก `terminal: true` → ต้องไม่มี transition ต่อไป
|
||||
- ต้องมีอย่างน้อย 1 state ที่ `initial: true`
|
||||
- หาก `terminal: true` → ต้องไม่มี transition ต่อไป
|
||||
|
||||
#### 5.2 Transition Rules
|
||||
|
||||
ตรวจสอบว่า:
|
||||
|
||||
* `to` ชี้ไปยัง state ที่มีอยู่
|
||||
* `require.role` ต้องเป็น role ที่ระบบรู้จัก
|
||||
* Action name ต้องเป็น **UPPER_CASE**
|
||||
- `to` ชี้ไปยัง state ที่มีอยู่
|
||||
- `require.role` ต้องเป็น role ที่ระบบรู้จัก
|
||||
- Action name ต้องเป็น **UPPER_CASE**
|
||||
|
||||
#### 5.3 Version Safety
|
||||
|
||||
* ทุกชุด Workflow DSL ต้องขึ้นกับ version
|
||||
* แก้ไขต้องสร้าง version ใหม่
|
||||
* ไม่ overwrite version เก่า
|
||||
* “Document ที่กำลังอยู่ใน step เดิมยังต้องใช้กฎเดิมได้”
|
||||
- ทุกชุด Workflow DSL ต้องขึ้นกับ version
|
||||
- แก้ไขต้องสร้าง version ใหม่
|
||||
- ไม่ overwrite version เก่า
|
||||
- “Document ที่กำลังอยู่ใน step เดิมยังต้องใช้กฎเดิมได้”
|
||||
|
||||
---
|
||||
|
||||
@@ -240,14 +234,13 @@ interface WorkflowContext {
|
||||
|
||||
```ts
|
||||
class WorkflowEngine {
|
||||
|
||||
load(dsl: string | object): CompiledWorkflow
|
||||
load(dsl: string | object): CompiledWorkflow;
|
||||
|
||||
compile(dsl: string | object): CompiledWorkflow
|
||||
compile(dsl: string | object): CompiledWorkflow;
|
||||
|
||||
evaluate(state: string, action: string, context: WorkflowContext): EvalResult
|
||||
evaluate(state: string, action: string, context: WorkflowContext): EvalResult;
|
||||
|
||||
getAvailableActions(state: string, context: WorkflowContext): string[]
|
||||
getAvailableActions(state: string, context: WorkflowContext): string[];
|
||||
}
|
||||
```
|
||||
|
||||
@@ -328,21 +321,21 @@ flowchart TD
|
||||
|
||||
#### Unit Tests
|
||||
|
||||
* Parse DSL → JSON
|
||||
* Invalid syntax → throw error
|
||||
* Invalid transitions → throw error
|
||||
- Parse DSL → JSON
|
||||
- Invalid syntax → throw error
|
||||
- Invalid transitions → throw error
|
||||
|
||||
#### Integration Tests
|
||||
|
||||
* Evaluate() ผ่าน 20+ cases
|
||||
* RFA ย้อนกลับ
|
||||
* Approve chain
|
||||
* Parallel review
|
||||
- Evaluate() ผ่าน 20+ cases
|
||||
- RFA ย้อนกลับ
|
||||
- Approve chain
|
||||
- Parallel review
|
||||
|
||||
#### Load Tests
|
||||
|
||||
* 1,000 documents running workflow
|
||||
* Evaluate < 20ms ต่อ action
|
||||
- 1,000 documents running workflow
|
||||
- Evaluate < 20ms ต่อ action
|
||||
|
||||
---
|
||||
|
||||
@@ -350,9 +343,9 @@ flowchart TD
|
||||
|
||||
#### Hot Reload Options
|
||||
|
||||
* DSL stored in DB
|
||||
* Cache in Redis
|
||||
* Touched timestamp triggers:
|
||||
- DSL stored in DB
|
||||
- Cache in Redis
|
||||
- Touched timestamp triggers:
|
||||
|
||||
```
|
||||
invalidate cache → recompile
|
||||
@@ -366,9 +359,9 @@ invalidate cache → recompile
|
||||
|
||||
DSL Engine แยกเป็น:
|
||||
|
||||
* `workflow-engine-core` → Pure JS library
|
||||
* `workflow-service` → NestJS module
|
||||
* API public:
|
||||
- `workflow-engine-core` → Pure JS library
|
||||
- `workflow-service` → NestJS module
|
||||
- API public:
|
||||
|
||||
```
|
||||
POST /workflow/evaluate
|
||||
@@ -378,9 +371,9 @@ POST /workflow/compile
|
||||
|
||||
ภายหลังสามารถนำไปวางบน:
|
||||
|
||||
* Kubernetes
|
||||
* Worker Node
|
||||
* API Gateway
|
||||
- Kubernetes
|
||||
- Worker Node
|
||||
- API Gateway
|
||||
|
||||
---
|
||||
|
||||
@@ -394,4 +387,3 @@ POST /workflow/compile
|
||||
✔ Execution API สำหรับ Backend และ Frontend
|
||||
✔ รองรับ Business Workflow ซับซ้อนทั้งหมด
|
||||
✔ Ready สำหรับ Microservice model ในอนาคต
|
||||
|
||||
|
||||
@@ -373,7 +373,6 @@
|
||||
### **Phase 0: Tasks**
|
||||
|
||||
- **[ ] T0.1 Secure Configuration Setup**
|
||||
|
||||
- [ ] ปรับปรุง `ConfigModule` ให้รองรับการอ่านค่าจาก Environment Variables
|
||||
- [ ] สร้าง Template `docker-compose.override.yml.example` สำหรับ Dev
|
||||
- [ ] Validate Config ด้วย Joi/Zod ตอน Start App (Throw error ถ้าขาด Secrets)
|
||||
@@ -382,7 +381,6 @@
|
||||
- [ ] **Dependencies:** None (Task เริ่มต้น)
|
||||
|
||||
- **[ ] T0.2 Redis & Queue Infrastructure**
|
||||
|
||||
- [ ] Setup Redis Container
|
||||
- [ ] Setup BullMQ Module ใน NestJS สำหรับจัดการ Background Jobs
|
||||
- [ ] Setup Redis Client สำหรับ Distributed Lock (Redlock)
|
||||
@@ -391,7 +389,6 @@
|
||||
- [ ] **Dependencies:** T0.1
|
||||
|
||||
- **[ ] T0.3 Setup Database Connection**
|
||||
|
||||
- [ ] Import SQL Schema v1.4.2 เข้า MariaDB
|
||||
- [ ] Run Seed Data (organizations, users, roles, permissions)
|
||||
- [ ] Configure TypeORM ใน AppModule
|
||||
@@ -401,7 +398,6 @@
|
||||
- [ ] **Dependencies:** T0.1
|
||||
|
||||
- **[ ] T0.4 Setup Git Repository**
|
||||
|
||||
- [ ] สร้าง Repository ใน Gitea (git.np-dms.work)
|
||||
- [ ] Setup .gitignore, README.md, SECURITY.md
|
||||
- [ ] Commit Initial Project
|
||||
@@ -417,7 +413,6 @@
|
||||
### **Phase 1: Tasks**
|
||||
|
||||
- **[ ] T1.1 CommonModule - Base Infrastructure**
|
||||
|
||||
- [ ] สร้าง Base Entity (id, created_at, updated_at, deleted_at)
|
||||
- [ ] สร้าง Global Exception Filter (ไม่เปิดเผย sensitive information)
|
||||
- [ ] สร้าง Response Transform Interceptor
|
||||
@@ -432,7 +427,6 @@
|
||||
- [ ] **Dependencies:** T0.2, T0.3
|
||||
|
||||
- **[ ] T1.2 AuthModule - JWT Authentication**
|
||||
|
||||
- [ ] สร้าง Entity: User
|
||||
- [ ] สร้าง AuthService:
|
||||
- [ ] login(username, password) → JWT Token
|
||||
@@ -451,7 +445,6 @@
|
||||
- [ ] **Dependencies:** T1.1, T0.3
|
||||
|
||||
- **[ ] T1.3 UserModule - User Management**
|
||||
|
||||
- [ ] สร้าง Entities: User, Role, Permission, UserRole, UserAssignment, **UserPreference**
|
||||
- [ ] สร้าง UserService CRUD (พร้อม soft delete)
|
||||
- [ ] สร้าง RoleService CRUD
|
||||
@@ -472,7 +465,6 @@
|
||||
- [ ] **Dependencies:** T1.1, T1.2
|
||||
|
||||
- **[ ] T1.4 RBAC Guard - 4-Level Authorization**
|
||||
|
||||
- [ ] สร้าง @RequirePermission() Decorator
|
||||
- [ ] สร้าง RbacGuard ที่ตรวจสอบ 4 ระดับ:
|
||||
- [ ] Global Permissions
|
||||
@@ -486,7 +478,6 @@
|
||||
- [ ] **Dependencies:** T1.1, T1.3
|
||||
|
||||
- **[ ] T1.5 ProjectModule - Base Structures**
|
||||
|
||||
- [ ] สร้าง Entities:
|
||||
- [ ] Organization
|
||||
- [ ] Project
|
||||
@@ -511,7 +502,6 @@
|
||||
### **Phase 2: Tasks**
|
||||
|
||||
- **[ ] T2.1 Virtual Columns for JSON**
|
||||
|
||||
- [ ] ออกแบบ Migration Script สำหรับตารางที่มี JSON Details
|
||||
- [ ] เพิ่ม **Generated Columns (Virtual)** สำหรับฟิลด์ที่ใช้ Search บ่อยๆ (เช่น `project_id`, `type`) พร้อม Index
|
||||
- [ ] **Security:** Implement admin-only access สำหรับ master data
|
||||
@@ -519,7 +509,6 @@
|
||||
- [ ] **Dependencies:** T0.3, T1.1, T1.5
|
||||
|
||||
- **[ ] T2.2 FileStorageService - Two-Phase Storage**
|
||||
|
||||
- [ ] สร้าง Attachment Entity
|
||||
- [ ] สร้าง FileStorageService:
|
||||
- [ ] **Phase 1 (Upload):** API รับไฟล์ → Scan Virus → Save ลง `temp/` → Return `temp_id`
|
||||
@@ -537,7 +526,6 @@
|
||||
- [ ] **Dependencies:** T1.1, T1.4
|
||||
|
||||
- **[ ] T2.3 DocumentNumberingModule - Double-Lock Mechanism**
|
||||
|
||||
- [ ] สร้าง Entities:
|
||||
- [ ] DocumentNumberFormat
|
||||
- [ ] DocumentNumberCounter
|
||||
@@ -558,7 +546,6 @@
|
||||
- [ ] **Deliverable:** Flexible Numbering System
|
||||
|
||||
- **[ ] T2.4 SecurityModule - Enhanced Security**
|
||||
|
||||
- [ ] สร้าง Input Validation Service:
|
||||
- [ ] XSS Prevention
|
||||
- [ ] SQL Injection Prevention
|
||||
@@ -572,7 +559,6 @@
|
||||
- [ ] **Dependencies:** T1.1
|
||||
|
||||
- **[ ] T2.5 JSON Details & Schema Management**
|
||||
|
||||
- [ ] T2.5.1 JsonSchemaModule - Schema Management: สร้าง Service สำหรับ Validate, get, register JSON schemas
|
||||
- [ ] T2.5.2 DetailsService - Data Processing: สร้าง Service สำหรับ sanitize, transform, compress/decompress JSON
|
||||
- [ ] T2.5.3 JSON Security & Validation: Implement security checks และ validation rules
|
||||
@@ -674,11 +660,7 @@ export class JsonSchemaService {
|
||||
this.registerCustomValidators();
|
||||
}
|
||||
|
||||
async validateData(
|
||||
schemaName: string,
|
||||
data: any,
|
||||
options: ValidationOptions = {}
|
||||
): Promise<ValidationResult> {
|
||||
async validateData(schemaName: string, data: any, options: ValidationOptions = {}): Promise<ValidationResult> {
|
||||
const schema = await this.getSchema(schemaName);
|
||||
const validate = this.ajv.compile(schema);
|
||||
|
||||
@@ -702,11 +684,7 @@ export class JsonSchemaService {
|
||||
};
|
||||
}
|
||||
|
||||
private async sanitizeData(
|
||||
data: any,
|
||||
schema: any,
|
||||
options: ValidationOptions
|
||||
): Promise<any> {
|
||||
private async sanitizeData(data: any, schema: any, options: ValidationOptions): Promise<any> {
|
||||
const sanitized = { ...data };
|
||||
|
||||
// Remove unknown properties if not allowed
|
||||
@@ -765,10 +743,7 @@ export class VirtualColumnService {
|
||||
private configService: ConfigService
|
||||
) {}
|
||||
|
||||
async setupVirtualColumns(
|
||||
tableName: string,
|
||||
schemaConfig: VirtualColumnConfig[]
|
||||
): Promise<void> {
|
||||
async setupVirtualColumns(tableName: string, schemaConfig: VirtualColumnConfig[]): Promise<void> {
|
||||
const connection = this.dataSource.manager.connection;
|
||||
|
||||
for (const config of schemaConfig) {
|
||||
@@ -776,10 +751,7 @@ export class VirtualColumnService {
|
||||
}
|
||||
}
|
||||
|
||||
private async createVirtualColumn(
|
||||
tableName: string,
|
||||
config: VirtualColumnConfig
|
||||
): Promise<void> {
|
||||
private async createVirtualColumn(tableName: string, config: VirtualColumnConfig): Promise<void> {
|
||||
const columnDefinition = this.generateColumnDefinition(config);
|
||||
|
||||
const sql = `
|
||||
@@ -802,10 +774,7 @@ export class VirtualColumnService {
|
||||
return `${dataType} GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(details, '${jsonPath}'))) VIRTUAL`;
|
||||
}
|
||||
|
||||
private async createIndex(
|
||||
tableName: string,
|
||||
config: VirtualColumnConfig
|
||||
): Promise<void> {
|
||||
private async createIndex(tableName: string, config: VirtualColumnConfig): Promise<void> {
|
||||
const indexName = `idx_${tableName}_${config.column_name}`;
|
||||
const sql = `
|
||||
CREATE ${config.index_type} INDEX ${indexName}
|
||||
@@ -922,13 +891,7 @@ const rfaDwgSchema: UiSchema = {
|
||||
widget: 'select',
|
||||
title: 'Discipline',
|
||||
enum: ['CIVIL', 'STRUCTURAL', 'MECHANICAL', 'ELECTRICAL', 'PLUMBING'],
|
||||
enumNames: [
|
||||
'Civil',
|
||||
'Structural',
|
||||
'Mechanical',
|
||||
'Electrical',
|
||||
'Plumbing',
|
||||
],
|
||||
enumNames: ['Civil', 'Structural', 'Mechanical', 'Electrical', 'Plumbing'],
|
||||
},
|
||||
drawingReferences: {
|
||||
type: 'array',
|
||||
@@ -961,21 +924,11 @@ const rfaDwgSchema: UiSchema = {
|
||||
```typescript
|
||||
@Injectable()
|
||||
export class SchemaMigrationService {
|
||||
async migrateData(
|
||||
entityType: string,
|
||||
entityId: string,
|
||||
targetVersion: number
|
||||
): Promise<MigrationResult> {
|
||||
async migrateData(entityType: string, entityId: string, targetVersion: number): Promise<MigrationResult> {
|
||||
const currentData = await this.getCurrentData(entityType, entityId);
|
||||
const currentVersion = await this.getCurrentSchemaVersion(
|
||||
entityType,
|
||||
entityId
|
||||
);
|
||||
const currentVersion = await this.getCurrentSchemaVersion(entityType, entityId);
|
||||
|
||||
const migrationPath = await this.findMigrationPath(
|
||||
currentVersion,
|
||||
targetVersion
|
||||
);
|
||||
const migrationPath = await this.findMigrationPath(currentVersion, targetVersion);
|
||||
|
||||
let migratedData = currentData;
|
||||
|
||||
@@ -984,24 +937,13 @@ export class SchemaMigrationService {
|
||||
}
|
||||
|
||||
// Validate migrated data against target schema
|
||||
const validationResult = await this.validateAgainstSchema(
|
||||
migratedData,
|
||||
targetVersion
|
||||
);
|
||||
const validationResult = await this.validateAgainstSchema(migratedData, targetVersion);
|
||||
|
||||
if (!validationResult.isValid) {
|
||||
throw new MigrationError(
|
||||
'MIGRATION_VALIDATION_FAILED',
|
||||
validationResult.errors
|
||||
);
|
||||
throw new MigrationError('MIGRATION_VALIDATION_FAILED', validationResult.errors);
|
||||
}
|
||||
|
||||
await this.saveMigratedData(
|
||||
entityType,
|
||||
entityId,
|
||||
migratedData,
|
||||
targetVersion
|
||||
);
|
||||
await this.saveMigratedData(entityType, entityId, migratedData, targetVersion);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -1011,10 +953,7 @@ export class SchemaMigrationService {
|
||||
};
|
||||
}
|
||||
|
||||
private async applyMigrationStep(
|
||||
step: MigrationStep,
|
||||
data: any
|
||||
): Promise<any> {
|
||||
private async applyMigrationStep(step: MigrationStep, data: any): Promise<any> {
|
||||
switch (step.type) {
|
||||
case 'FIELD_RENAME':
|
||||
return this.renameField(data, step.config);
|
||||
@@ -1070,11 +1009,7 @@ const migrationSteps = [
|
||||
```typescript
|
||||
@Injectable()
|
||||
export class JsonSecurityService {
|
||||
async applyFieldLevelSecurity(
|
||||
data: any,
|
||||
schema: any,
|
||||
userContext: UserContext
|
||||
): Promise<any> {
|
||||
async applyFieldLevelSecurity(data: any, schema: any, userContext: UserContext): Promise<any> {
|
||||
const securedData = { ...data };
|
||||
const securityRules = await this.getSecurityRules(schema.name);
|
||||
|
||||
@@ -1101,10 +1036,7 @@ export class JsonSecurityService {
|
||||
for (const fieldPath of sensitiveFields) {
|
||||
const fieldValue = this.getFieldValue(data, fieldPath);
|
||||
if (fieldValue) {
|
||||
const encrypted = await this.cryptoService.encrypt(
|
||||
fieldValue,
|
||||
'field-level'
|
||||
);
|
||||
const encrypted = await this.cryptoService.encrypt(fieldValue, 'field-level');
|
||||
this.setFieldValue(encryptedData, fieldPath, encrypted);
|
||||
}
|
||||
}
|
||||
@@ -1149,15 +1081,8 @@ export class JsonSecurityService {
|
||||
export class JsonSchemaController {
|
||||
@Post('validate/:schemaName')
|
||||
@RequirePermission('schema.validate')
|
||||
async validateData(
|
||||
@Param('schemaName') schemaName: string,
|
||||
@Body() dto: ValidateDataDto
|
||||
): Promise<ValidationResult> {
|
||||
return this.jsonSchemaService.validateData(
|
||||
schemaName,
|
||||
dto.data,
|
||||
dto.options
|
||||
);
|
||||
async validateData(@Param('schemaName') schemaName: string, @Body() dto: ValidateDataDto): Promise<ValidationResult> {
|
||||
return this.jsonSchemaService.validateData(schemaName, dto.data, dto.options);
|
||||
}
|
||||
|
||||
@Post('schemas')
|
||||
@@ -1173,18 +1098,12 @@ export class JsonSchemaController {
|
||||
@Param('entityId') entityId: string,
|
||||
@Body() dto: MigrateDataDto
|
||||
): Promise<MigrationResult> {
|
||||
return this.migrationService.migrateData(
|
||||
entityType,
|
||||
entityId,
|
||||
dto.targetVersion
|
||||
);
|
||||
return this.migrationService.migrateData(entityType, entityId, dto.targetVersion);
|
||||
}
|
||||
|
||||
@Get('ui-schema/:schemaName')
|
||||
@RequirePermission('schema.view')
|
||||
async getUiSchema(
|
||||
@Param('schemaName') schemaName: string
|
||||
): Promise<UiSchema> {
|
||||
async getUiSchema(@Param('schemaName') schemaName: string): Promise<UiSchema> {
|
||||
return this.schemaService.getUiSchema(schemaName);
|
||||
}
|
||||
}
|
||||
@@ -1206,32 +1125,22 @@ export class CorrespondenceService {
|
||||
private detailsService: DetailsService
|
||||
) {}
|
||||
|
||||
async createCorrespondence(
|
||||
dto: CreateCorrespondenceDto
|
||||
): Promise<Correspondence> {
|
||||
async createCorrespondence(dto: CreateCorrespondenceDto): Promise<Correspondence> {
|
||||
// 1. Validate details against schema
|
||||
const validationResult = await this.jsonSchemaService.validateData(
|
||||
`CORRESPONDENCE_${dto.type}`,
|
||||
dto.details
|
||||
);
|
||||
const validationResult = await this.jsonSchemaService.validateData(`CORRESPONDENCE_${dto.type}`, dto.details);
|
||||
|
||||
if (!validationResult.isValid) {
|
||||
throw new ValidationError('INVALID_DETAILS', validationResult.errors);
|
||||
}
|
||||
|
||||
// 2. Apply security and sanitization
|
||||
const secureDetails = await this.detailsService.sanitizeDetails(
|
||||
validationResult.sanitizedData,
|
||||
dto.type
|
||||
);
|
||||
const secureDetails = await this.detailsService.sanitizeDetails(validationResult.sanitizedData, dto.type);
|
||||
|
||||
// 3. Create correspondence entity
|
||||
const correspondence = this.correspondenceRepository.create({
|
||||
...dto,
|
||||
details: secureDetails,
|
||||
schema_version: await this.getCurrentSchemaVersion(
|
||||
`CORRESPONDENCE_${dto.type}`
|
||||
),
|
||||
schema_version: await this.getCurrentSchemaVersion(`CORRESPONDENCE_${dto.type}`),
|
||||
});
|
||||
|
||||
// 4. Setup virtual columns for performance
|
||||
@@ -1240,9 +1149,7 @@ export class CorrespondenceService {
|
||||
return this.correspondenceRepository.save(correspondence);
|
||||
}
|
||||
|
||||
async searchCorrespondences(
|
||||
filters: SearchFilters
|
||||
): Promise<Correspondence[]> {
|
||||
async searchCorrespondences(filters: SearchFilters): Promise<Correspondence[]> {
|
||||
// Use virtual columns for efficient filtering
|
||||
const query = this.correspondenceRepository.createQueryBuilder('c');
|
||||
|
||||
@@ -1363,7 +1270,6 @@ describe('VirtualColumnService', () => {
|
||||
### **Phase 3: Tasks**
|
||||
|
||||
- **[ ] T3.1 WorkflowEngineModule (New)**
|
||||
|
||||
- [ ] ออกแบบ Generic Schema สำหรับ Workflow State Machine
|
||||
- [ ] Implement Service: `initializeWorkflow()`, `processAction()`, `getNextStep()`
|
||||
- [ ] รองรับ Logic การ "ข้ามขั้นตอน" และ "ส่งกลับ" ภายใน Engine เดียว
|
||||
@@ -1438,7 +1344,6 @@ states:
|
||||
```
|
||||
|
||||
- **[ ] T3.1.2 Workflow Core Entities & Database Schema**
|
||||
|
||||
- [ ] WorkflowDefinition Entity
|
||||
- [ ] WorkflowInstance Entity
|
||||
- [ ] WorkflowHistory Entity
|
||||
@@ -1505,7 +1410,6 @@ export class WorkflowInstance {
|
||||
```
|
||||
|
||||
- **[ ] T3.1.3 DSL Parser & Compiler Service**
|
||||
|
||||
- [ ] YAML Parser สำหรับอ่าน DSL definitions
|
||||
- [ ] Syntax Validator สำหรับ compile-time validation
|
||||
- [ ] Schema Compiler สำหรับแปลง DSL → Normalized JSON
|
||||
@@ -1537,9 +1441,7 @@ export class WorkflowDslService {
|
||||
// Terminal states ต้องไม่มี transitions
|
||||
() => !definition.states.filter((s) => s.terminal).some((s) => s.on),
|
||||
// State names must be unique
|
||||
() =>
|
||||
new Set(definition.states.map((s) => s.name)).size ===
|
||||
definition.states.length,
|
||||
() => new Set(definition.states.map((s) => s.name)).size === definition.states.length,
|
||||
// Transition targets must exist
|
||||
() => this.validateTransitionTargets(definition),
|
||||
];
|
||||
@@ -1683,11 +1585,7 @@ interface StateWithTimeout {
|
||||
```typescript
|
||||
@Injectable()
|
||||
export class WorkflowEventService {
|
||||
async executeEvents(
|
||||
events: WorkflowEvent[],
|
||||
instance: WorkflowInstance,
|
||||
context: WorkflowContext
|
||||
): Promise<void> {
|
||||
async executeEvents(events: WorkflowEvent[], instance: WorkflowInstance, context: WorkflowContext): Promise<void> {
|
||||
for (const event of events) {
|
||||
switch (event.type) {
|
||||
case 'notify':
|
||||
@@ -1711,16 +1609,8 @@ export class WorkflowEventService {
|
||||
instance: WorkflowInstance,
|
||||
context: WorkflowContext
|
||||
): Promise<void> {
|
||||
const recipients = await this.resolveRecipients(
|
||||
event.target,
|
||||
instance,
|
||||
context
|
||||
);
|
||||
const message = await this.renderTemplate(
|
||||
event.template,
|
||||
instance,
|
||||
context
|
||||
);
|
||||
const recipients = await this.resolveRecipients(event.target, instance, context);
|
||||
const message = await this.renderTemplate(event.template, instance, context);
|
||||
|
||||
await this.notificationService.send({
|
||||
type: 'workflow',
|
||||
@@ -1752,35 +1642,24 @@ export class WorkflowEngineController {
|
||||
@Param('id') instanceId: string,
|
||||
@Body() dto: WorkflowTransitionDto
|
||||
): Promise<TransitionResult> {
|
||||
return this.workflowEngine.processTransition(
|
||||
instanceId,
|
||||
dto.action,
|
||||
dto.context
|
||||
);
|
||||
return this.workflowEngine.processTransition(instanceId, dto.action, dto.context);
|
||||
}
|
||||
|
||||
@Get('instances/:id/actions')
|
||||
@RequirePermission('workflow.view')
|
||||
async getAvailableActions(
|
||||
@Param('id') instanceId: string,
|
||||
@Query() context: WorkflowContext
|
||||
): Promise<string[]> {
|
||||
async getAvailableActions(@Param('id') instanceId: string, @Query() context: WorkflowContext): Promise<string[]> {
|
||||
return this.workflowEngine.getAvailableActions(instanceId, context);
|
||||
}
|
||||
|
||||
@Post('definitions')
|
||||
@RequirePermission('workflow.manage')
|
||||
async createWorkflowDefinition(
|
||||
@Body() dto: CreateWorkflowDefinitionDto
|
||||
): Promise<WorkflowDefinition> {
|
||||
async createWorkflowDefinition(@Body() dto: CreateWorkflowDefinitionDto): Promise<WorkflowDefinition> {
|
||||
return this.workflowDslService.compileAndSave(dto.dslContent);
|
||||
}
|
||||
|
||||
@Get('instances/:id/history')
|
||||
@RequirePermission('workflow.view')
|
||||
async getWorkflowHistory(
|
||||
@Param('id') instanceId: string
|
||||
): Promise<WorkflowHistory[]> {
|
||||
async getWorkflowHistory(@Param('id') instanceId: string): Promise<WorkflowHistory[]> {
|
||||
return this.workflowHistoryService.getHistory(instanceId);
|
||||
}
|
||||
}
|
||||
@@ -1801,13 +1680,8 @@ export class CorrespondenceWorkflowService {
|
||||
private correspondenceService: CorrespondenceService
|
||||
) {}
|
||||
|
||||
async submitCorrespondence(
|
||||
correspondenceId: string,
|
||||
userId: string
|
||||
): Promise<void> {
|
||||
const correspondence = await this.correspondenceService.findById(
|
||||
correspondenceId
|
||||
);
|
||||
async submitCorrespondence(correspondenceId: string, userId: string): Promise<void> {
|
||||
const correspondence = await this.correspondenceService.findById(correspondenceId);
|
||||
|
||||
// Create workflow instance
|
||||
const instance = await this.workflowEngine.createInstance({
|
||||
@@ -1830,7 +1704,6 @@ export class CorrespondenceWorkflowService {
|
||||
```
|
||||
|
||||
- [ ] **T3.1.9 Cleanup Legacy Code:**
|
||||
|
||||
- [ ] ลบ Entity/Repository ของตารางเก่า (_\_routings, _\_templates) ออกจาก Codebase
|
||||
- [ ] อัปเดต SQL Migration ให้ Drop ตารางเก่าทิ้ง
|
||||
|
||||
@@ -1849,11 +1722,7 @@ describe('WorkflowEngineService', () => {
|
||||
const context = { userId: 'user1', roles: ['APPROVER'] };
|
||||
|
||||
// Act
|
||||
const result = await workflowEngine.processTransition(
|
||||
instance.id,
|
||||
'APPROVE',
|
||||
context
|
||||
);
|
||||
const result = await workflowEngine.processTransition(instance.id, 'APPROVE', context);
|
||||
|
||||
// Assert
|
||||
expect(result.success).toBe(true);
|
||||
@@ -1866,23 +1735,19 @@ describe('WorkflowEngineService', () => {
|
||||
const context = { userId: 'user2', roles: ['VIEWER'] };
|
||||
|
||||
// Act & Assert
|
||||
await expect(
|
||||
workflowEngine.processTransition(instance.id, 'APPROVE', context)
|
||||
).rejects.toThrow(WorkflowError);
|
||||
await expect(workflowEngine.processTransition(instance.id, 'APPROVE', context)).rejects.toThrow(WorkflowError);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- **🔗 Critical Dependencies of T3.1.1-T3.1.8**
|
||||
|
||||
- T1.1 (Common Module) - สำหรับ base entities และ shared services
|
||||
- T1.4 (RBAC Guard) - สำหรับ permission checking
|
||||
- T2.5 (JSON Schema) - สำหรับ DSL validation
|
||||
- T6.2 (Notification) - สำหรับ event handling
|
||||
|
||||
- **🎯 Success Metrics**
|
||||
|
||||
- ✅ Support ทั้ง Correspondence Routing และ RFA Workflow
|
||||
- ✅ DSL ที่ human-readable และ editable โดยไม่ต้องแก้โค้ด
|
||||
- ✅ Performance: < 50ms ต่อ state transition
|
||||
@@ -1890,7 +1755,6 @@ describe('WorkflowEngineService', () => {
|
||||
- ✅ Complete audit trail สำหรับทุก workflow instance
|
||||
|
||||
- **[ ] T3.2 CorrespondenceModule - Basic CRUD**
|
||||
|
||||
- [ ] สร้าง Entities (Correspondence, Revision, Recipient, Tag, Reference, Attachment)
|
||||
- [ ] สร้าง CorrespondenceService (Create with Document Numbering, Update with new Revision, Soft Delete)
|
||||
- [ ] สร้าง Controllers (POST/GET/PUT/DELETE /correspondences)
|
||||
@@ -1900,7 +1764,6 @@ describe('WorkflowEngineService', () => {
|
||||
- [ ] **Dependencies:** T1.1, T1.2, T1.3, T1.4, T1.5, T2.3, T2.2, T2.5
|
||||
|
||||
- **[ ] T3.3 CorrespondenceModule - Advanced Features**
|
||||
|
||||
- [ ] Implement Status Transitions (DRAFT → SUBMITTED)
|
||||
- [ ] Implement References (Link Documents)
|
||||
- [ ] Implement Search (Basic)
|
||||
@@ -1909,7 +1772,6 @@ describe('WorkflowEngineService', () => {
|
||||
- [ ] **Dependencies:** T3.2
|
||||
|
||||
- **[ ] T3.4 Correspondence Integration with Workflow**
|
||||
|
||||
- [ ] เชื่อมต่อ `CorrespondenceService` เข้ากับ `WorkflowEngineModule`
|
||||
- [ ] ย้าย Logic การ Routing เดิมมาใช้ Engine ใหม่
|
||||
- [ ] สร้าง API endpoints สำหรับ Frontend (Templates, Pending Tasks, Bulk Action)
|
||||
@@ -1926,7 +1788,6 @@ describe('WorkflowEngineService', () => {
|
||||
### **Phase 4: Tasks**
|
||||
|
||||
- **[ ] T4.1 DrawingModule - Contract Drawings**
|
||||
|
||||
- [ ] สร้าง Entities (ContractDrawing, Volume, Category, SubCategory, Attachment)
|
||||
- [ ] สร้าง ContractDrawingService CRUD
|
||||
- [ ] สร้าง Controllers (GET/POST /drawings/contract)
|
||||
@@ -1935,7 +1796,6 @@ describe('WorkflowEngineService', () => {
|
||||
- [ ] **Dependencies:** T1.1, T1.2, T1.4, T1.5, T2.2
|
||||
|
||||
- **[ ] T4.2 DrawingModule - Shop Drawings**
|
||||
|
||||
- [ ] สร้าง Entities (ShopDrawing, Revision, Main/SubCategory, ContractRef, RevisionAttachment)
|
||||
- [ ] สร้าง ShopDrawingService CRUD (รวมการสร้าง Revision)
|
||||
- [ ] สร้าง Controllers (GET/POST /drawings/shop, /drawings/shop/:id/revisions)
|
||||
@@ -1953,7 +1813,6 @@ describe('WorkflowEngineService', () => {
|
||||
### **Phase 5: Tasks**
|
||||
|
||||
- **[ ] T5.1 RfaModule with Unified Workflow**
|
||||
|
||||
- [ ] สร้าง Entities (Rfa, RfaRevision, RfaItem)
|
||||
- [ ] สร้าง RfaService (Create RFA, Link Shop Drawings)
|
||||
- [ ] Implement RFA Workflow โดยใช้ Configuration ของ `WorkflowEngineModule`
|
||||
@@ -1963,7 +1822,6 @@ describe('WorkflowEngineService', () => {
|
||||
- [ ] **Dependencies:** T3.2, T4.2, T2.5, T6.2
|
||||
|
||||
- **[ ] T5.2 CirculationModule - Internal Routing**
|
||||
|
||||
- [ ] สร้าง Entities (Circulation, Template, Routing, Attachment)
|
||||
- [ ] สร้าง CirculationService (Create 1:1 with Correspondence, Assign User, Complete/Close Step)
|
||||
- [ ] สร้าง Controllers (POST/GET /circulations, POST /circulations/:id/steps/...)
|
||||
@@ -1972,7 +1830,6 @@ describe('WorkflowEngineService', () => {
|
||||
- [ ] **Dependencies:** T3.2, T2.5, T6.2
|
||||
|
||||
- **[ ] T5.3 TransmittalModule - Document Forwarding**
|
||||
|
||||
- [ ] สร้าง Entities (Transmittal, TransmittalItem)
|
||||
- [ ] สร้าง TransmittalService (Create Correspondence + Transmittal, Link Multiple Correspondences)
|
||||
- [ ] สร้าง Controllers (POST/GET /transmittals)
|
||||
@@ -1989,7 +1846,6 @@ describe('WorkflowEngineService', () => {
|
||||
### **Phase 6: Tasks**
|
||||
|
||||
- **[ ] T6.1 SearchModule - Elasticsearch Integration**
|
||||
|
||||
- [ ] Setup Elasticsearch Container
|
||||
- [ ] สร้าง SearchService (index/update/delete documents, search)
|
||||
- [ ] Index ทุก Document Type
|
||||
@@ -1999,7 +1855,6 @@ describe('WorkflowEngineService', () => {
|
||||
- [ ] **Dependencies:** T3.2, T5.1, T4.2, T5.2, T5.3
|
||||
|
||||
- **[ ] T6.2 Notification Queue & Digest**
|
||||
|
||||
- [ ] สร้าง NotificationService (sendEmail/Line/System)
|
||||
- [ ] **Producer:** Push Event ลง BullMQ Queue
|
||||
- [ ] **Consumer:** จัดกลุ่ม Notification (Digest Message) และส่งผ่าน Email/Line
|
||||
@@ -2010,7 +1865,6 @@ describe('WorkflowEngineService', () => {
|
||||
- [ ] **Dependencies:** T1.1, T6.4
|
||||
|
||||
- **[ ] T6.3 MonitoringModule - Observability**
|
||||
|
||||
- [ ] สร้าง Health Check Controller (GET /health)
|
||||
- [ ] สร้าง Metrics Service (API response times, Error rates)
|
||||
- [ ] สร้าง Performance Interceptor (Track request duration)
|
||||
@@ -2019,7 +1873,6 @@ describe('WorkflowEngineService', () => {
|
||||
- [ ] **Dependencies:** T1.1
|
||||
|
||||
- **[ ] T6.4 ResilienceModule - Circuit Breaker & Retry**
|
||||
|
||||
- [ ] สร้าง Circuit Breaker Service (@CircuitBreaker() decorator)
|
||||
- [ ] สร้าง Retry Service (@Retry() decorator)
|
||||
- [ ] สร้าง Fallback Strategies
|
||||
@@ -2028,7 +1881,6 @@ describe('WorkflowEngineService', () => {
|
||||
- [ ] **Dependencies:** T1.1
|
||||
|
||||
- **[ ] T6.5 Data Partitioning Strategy**
|
||||
|
||||
- [ ] ออกแบบ Table Partitioning สำหรับ `audit_logs` และ `notifications` (แบ่งตาม Range: Year)
|
||||
- [ ] เขียน Raw SQL Migration สำหรับสร้าง Partition Table
|
||||
- [ ] **Deliverable:** Database Performance และ Scalability ดีขึ้น
|
||||
@@ -2043,21 +1895,18 @@ describe('WorkflowEngineService', () => {
|
||||
### **Phase 7: Tasks**
|
||||
|
||||
- **[ ] T7.1 Concurrency Testing**
|
||||
|
||||
- [ ] เขียน Test Scenarios ยิง Request ขอเลขที่เอกสารพร้อมกัน 100 Request (ต้องไม่ซ้ำและไม่ข้าม)
|
||||
- [ ] ทดสอบ Optimistic Lock ทำงานถูกต้องเมื่อ Redis ถูกปิด
|
||||
- [ ] ทดสอบ File Upload พร้อมกันหลายไฟล์
|
||||
- [ ] **Deliverable:** ระบบทนทานต่อ Concurrency Issues
|
||||
|
||||
- **[ ] T7.2 Transaction Integrity Testing**
|
||||
|
||||
- [ ] ทดสอบ Upload ไฟล์แล้ว Kill Process ก่อน Commit
|
||||
- [ ] ทดสอบ Two-Phase File Storage ทำงานถูกต้อง
|
||||
- [ ] ทดสอบ Database Transaction Rollback Scenarios
|
||||
- [ ] **Deliverable:** Data Integrity รับประกันได้
|
||||
|
||||
- **[ ] T7.3 Security & Idempotency Test**
|
||||
|
||||
- [ ] ทดสอบ Replay Attack โดยใช้ `Idempotency-Key` ซ้ำ
|
||||
- [ ] ทดสอบ Maintenance Mode Block API ได้จริง
|
||||
- [ ] ทดสอบ RBAC 4-Level ทำงานถูกต้อง 100%
|
||||
@@ -2070,7 +1919,6 @@ describe('WorkflowEngineService', () => {
|
||||
- **[ ] T7.6 E2E Testing**
|
||||
|
||||
- **[ ] T7.7 Performance Testing**
|
||||
|
||||
- [ ] Load Testing: 100 concurrent users
|
||||
- [ ] **(สำคัญ)** การจูนและทดสอบ Load Test จะต้องทำในสภาพแวดล้อมที่จำลอง Spec ของ QNAP Server (TS-473A, AMD Ryzen V1500B) เพื่อให้ได้ค่า Response Time และ Connection Pool ที่เที่ยงตรง
|
||||
- [ ] Stress Testing
|
||||
@@ -2078,14 +1926,12 @@ describe('WorkflowEngineService', () => {
|
||||
- [ ] **Deliverable:** Performance targets บรรลุ
|
||||
|
||||
- **[ ] T7.8 Security Testing**
|
||||
|
||||
- [ ] Penetration Testing (OWASP Top 10)
|
||||
- [ ] Security Audit (Code review, Dependency scanning)
|
||||
- [ ] File Upload Security Testing
|
||||
- [ ] **Deliverable:** Security tests ผ่าน
|
||||
|
||||
- **[ ] T7.9 Performance Optimization**
|
||||
|
||||
- [ ] Implement Caching (Master Data, User Permissions, Search Results)
|
||||
- [ ] Database Optimization (Review Indexes, Query Optimization, Pagination)
|
||||
- [ ] **Deliverable:** Response Time < 200ms (90th percentile)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
## 🎯 **ภาพรวมโครงการ (Project Overview)**
|
||||
|
||||
พัฒนา Backend สำหรับระบบบริหารจัดการเอกสารโครงการ (Document Management System) เวอร์ชัน 1.5.1 โดยเน้นการปรับปรุงสถาปัตยกรรมหลัก 3 ส่วนสำคัญ:
|
||||
|
||||
1. **Unified Workflow Engine:** ระบบ Workflow แบบ Dynamic ที่ยืดหยุ่น รองรับการกำหนด Rule ผ่าน DSL
|
||||
2. **Advanced Document Numbering:** ระบบสร้างเลขที่เอกสารที่ซับซ้อน (8-component key) พร้อม Double-Lock Mechanism ป้องกัน Race Condition
|
||||
3. **Enhanced Master Data:** การจัดการข้อมูลหลักที่ครอบคลุม (Discipline, SubType) และ JSON Schema Management
|
||||
@@ -20,14 +21,14 @@
|
||||
|
||||
### **Technology Stack**
|
||||
|
||||
- **Framework:** NestJS (TypeScript, ESM)
|
||||
- **Database:** MariaDB 10.11 (ใช้ Virtual Columns & Partitioning)
|
||||
- **ORM:** TypeORM (Optimistic Locking)
|
||||
- **Workflow Engine:** Custom DSL-based Engine (State Machine)
|
||||
- **Queue:** BullMQ (Redis) สำหรับ Async Jobs & Notifications
|
||||
- **Locking:** Redis (Redlock) + DB Pessimistic Fallback
|
||||
- **Search:** Elasticsearch
|
||||
- **Validation:** Zod / Class-validator / AJV (JSON Schema)
|
||||
- **Framework:** NestJS (TypeScript, ESM)
|
||||
- **Database:** MariaDB 10.11 (ใช้ Virtual Columns & Partitioning)
|
||||
- **ORM:** TypeORM (Optimistic Locking)
|
||||
- **Workflow Engine:** Custom DSL-based Engine (State Machine)
|
||||
- **Queue:** BullMQ (Redis) สำหรับ Async Jobs & Notifications
|
||||
- **Locking:** Redis (Redlock) + DB Pessimistic Fallback
|
||||
- **Search:** Elasticsearch
|
||||
- **Validation:** Zod / Class-validator / AJV (JSON Schema)
|
||||
|
||||
### **โครงสร้างโมดูล (Module Structure)**
|
||||
|
||||
@@ -64,23 +65,25 @@
|
||||
**Goal:** เตรียมโครงสร้างพื้นฐานและข้อมูลหลักให้พร้อมสำหรับโมดูลอื่น
|
||||
|
||||
#### **[ ] T1.1 Master Data Module (Enhanced)**
|
||||
- **Objective:** จัดการข้อมูลหลักทั้งหมดรวมถึงตารางใหม่ใน v1.5.1
|
||||
- **Tasks:**
|
||||
- [ ] Implement `OrganizationService` (CRUD)
|
||||
- [ ] Implement `ProjectService` & `ContractService`
|
||||
- [ ] Implement `TypeService` (Correspondence, RFA, Drawing)
|
||||
- [ ] **[NEW]** Implement `DisciplineService` (CRUD for `disciplines` table)
|
||||
- [ ] **[NEW]** Implement `CorrespondenceSubTypeService`
|
||||
- [ ] **[NEW]** Implement `CodeService` (RFA Approve Codes, Status Codes)
|
||||
- **Deliverables:** API สำหรับจัดการ Master Data ทั้งหมด
|
||||
|
||||
- **Objective:** จัดการข้อมูลหลักทั้งหมดรวมถึงตารางใหม่ใน v1.5.1
|
||||
- **Tasks:**
|
||||
- [ ] Implement `OrganizationService` (CRUD)
|
||||
- [ ] Implement `ProjectService` & `ContractService`
|
||||
- [ ] Implement `TypeService` (Correspondence, RFA, Drawing)
|
||||
- [ ] **[NEW]** Implement `DisciplineService` (CRUD for `disciplines` table)
|
||||
- [ ] **[NEW]** Implement `CorrespondenceSubTypeService`
|
||||
- [ ] **[NEW]** Implement `CodeService` (RFA Approve Codes, Status Codes)
|
||||
- **Deliverables:** API สำหรับจัดการ Master Data ทั้งหมด
|
||||
|
||||
#### **[ ] T1.2 User & Auth Module**
|
||||
- **Objective:** ระบบผู้ใช้งานและสิทธิ์ (RBAC)
|
||||
- **Tasks:**
|
||||
- [ ] Implement `AuthService` (Login, Refresh Token)
|
||||
- [ ] Implement `UserService` & `UserPreferenceService`
|
||||
- [ ] Implement RBAC Guards (Global, Org, Project, Contract scopes)
|
||||
- **Deliverables:** Secure Authentication & Authorization
|
||||
|
||||
- **Objective:** ระบบผู้ใช้งานและสิทธิ์ (RBAC)
|
||||
- **Tasks:**
|
||||
- [ ] Implement `AuthService` (Login, Refresh Token)
|
||||
- [ ] Implement `UserService` & `UserPreferenceService`
|
||||
- [ ] Implement RBAC Guards (Global, Org, Project, Contract scopes)
|
||||
- **Deliverables:** Secure Authentication & Authorization
|
||||
|
||||
---
|
||||
|
||||
@@ -89,23 +92,25 @@
|
||||
**Goal:** ระบบเลขที่เอกสารที่ถูกต้องแม่นยำและระบบไฟล์ที่ปลอดภัย
|
||||
|
||||
#### **[ ] T2.1 Document Numbering Module (Major Update)**
|
||||
- **Objective:** ระบบสร้างเลขที่เอกสารแบบ 8-component key พร้อม Double-Lock
|
||||
- **Tasks:**
|
||||
- [ ] Update `DocumentNumberCounter` entity (8-column PK)
|
||||
- [ ] Implement `DocumentNumberingService` with **Redlock**
|
||||
- [ ] Implement **DB Optimistic Lock** fallback strategy
|
||||
- [ ] Implement Token Parser (`{DISCIPLINE}`, `{SUB_TYPE}`, `{RFA_TYPE}`)
|
||||
- [ ] Create `DocumentNumberAudit` & `DocumentNumberError` tables
|
||||
- **Deliverables:** Race-condition free numbering system
|
||||
|
||||
- **Objective:** ระบบสร้างเลขที่เอกสารแบบ 8-component key พร้อม Double-Lock
|
||||
- **Tasks:**
|
||||
- [ ] Update `DocumentNumberCounter` entity (8-column PK)
|
||||
- [ ] Implement `DocumentNumberingService` with **Redlock**
|
||||
- [ ] Implement **DB Optimistic Lock** fallback strategy
|
||||
- [ ] Implement Token Parser (`{DISCIPLINE}`, `{SUB_TYPE}`, `{RFA_TYPE}`)
|
||||
- [ ] Create `DocumentNumberAudit` & `DocumentNumberError` tables
|
||||
- **Deliverables:** Race-condition free numbering system
|
||||
|
||||
#### **[ ] T2.2 File Storage Service**
|
||||
- **Objective:** Two-Phase Storage Strategy
|
||||
- **Tasks:**
|
||||
- [ ] Implement `Upload` (Phase 1: Temp storage)
|
||||
- [ ] Implement `Commit` (Phase 2: Move to permanent)
|
||||
- [ ] Integrate **ClamAV** for virus scanning
|
||||
- [ ] Implement Cleanup Job for orphan files
|
||||
- **Deliverables:** Secure file upload system
|
||||
|
||||
- **Objective:** Two-Phase Storage Strategy
|
||||
- **Tasks:**
|
||||
- [ ] Implement `Upload` (Phase 1: Temp storage)
|
||||
- [ ] Implement `Commit` (Phase 2: Move to permanent)
|
||||
- [ ] Integrate **ClamAV** for virus scanning
|
||||
- [ ] Implement Cleanup Job for orphan files
|
||||
- **Deliverables:** Secure file upload system
|
||||
|
||||
---
|
||||
|
||||
@@ -114,22 +119,24 @@
|
||||
**Goal:** ระบบ Workflow กลางที่ยืดหยุ่นและ Configurable
|
||||
|
||||
#### **[ ] T3.1 Workflow Engine Core**
|
||||
- **Objective:** สร้าง Engine สำหรับรัน Workflow ตาม DSL
|
||||
- **Tasks:**
|
||||
- [ ] Design DSL Schema (JSON)
|
||||
- [ ] Implement `DslParserService` & Validator
|
||||
- [ ] Implement `WorkflowEngineService` (State Machine)
|
||||
- [ ] Implement `GuardExecutor` (Permission/Condition checks)
|
||||
- [ ] Implement `EffectExecutor` (Actions after transition)
|
||||
- **Deliverables:** Functional Workflow Engine
|
||||
|
||||
- **Objective:** สร้าง Engine สำหรับรัน Workflow ตาม DSL
|
||||
- **Tasks:**
|
||||
- [ ] Design DSL Schema (JSON)
|
||||
- [ ] Implement `DslParserService` & Validator
|
||||
- [ ] Implement `WorkflowEngineService` (State Machine)
|
||||
- [ ] Implement `GuardExecutor` (Permission/Condition checks)
|
||||
- [ ] Implement `EffectExecutor` (Actions after transition)
|
||||
- **Deliverables:** Functional Workflow Engine
|
||||
|
||||
#### **[ ] T3.2 Workflow Integration**
|
||||
- **Objective:** เชื่อมต่อ Engine เข้ากับ Business Modules
|
||||
- **Tasks:**
|
||||
- [ ] Create Standard Workflow Definitions (Correspondence, RFA)
|
||||
- [ ] Implement `WorkflowInstance` creation logic
|
||||
- [ ] Create API for Workflow Actions (Approve, Reject, Comment)
|
||||
- **Deliverables:** Integrated Workflow System
|
||||
|
||||
- **Objective:** เชื่อมต่อ Engine เข้ากับ Business Modules
|
||||
- **Tasks:**
|
||||
- [ ] Create Standard Workflow Definitions (Correspondence, RFA)
|
||||
- [ ] Implement `WorkflowInstance` creation logic
|
||||
- [ ] Create API for Workflow Actions (Approve, Reject, Comment)
|
||||
- **Deliverables:** Integrated Workflow System
|
||||
|
||||
---
|
||||
|
||||
@@ -138,42 +145,47 @@
|
||||
**Goal:** ฟังก์ชันการทำงานหลักของระบบเอกสาร
|
||||
|
||||
#### **[ ] T4.1 Correspondence Module**
|
||||
- **Objective:** จัดการหนังสือโต้ตอบ
|
||||
- **Tasks:**
|
||||
- [ ] Update Entity to support `discipline_id`
|
||||
- [ ] Integrate with **Document Numbering**
|
||||
- [ ] Integrate with **Workflow Engine**
|
||||
- [ ] Implement CRUD & Revision handling
|
||||
|
||||
- **Objective:** จัดการหนังสือโต้ตอบ
|
||||
- **Tasks:**
|
||||
- [ ] Update Entity to support `discipline_id`
|
||||
- [ ] Integrate with **Document Numbering**
|
||||
- [ ] Integrate with **Workflow Engine**
|
||||
- [ ] Implement CRUD & Revision handling
|
||||
|
||||
#### **[ ] T4.2 RFA Module**
|
||||
- **Objective:** จัดการเอกสารขออนุมัติ
|
||||
- **Tasks:**
|
||||
- [ ] Update Entity to support `discipline_id`
|
||||
- [ ] Implement RFA-specific workflow logic
|
||||
- [ ] Implement RFA Item linking (Drawings)
|
||||
|
||||
- **Objective:** จัดการเอกสารขออนุมัติ
|
||||
- **Tasks:**
|
||||
- [ ] Update Entity to support `discipline_id`
|
||||
- [ ] Implement RFA-specific workflow logic
|
||||
- [ ] Implement RFA Item linking (Drawings)
|
||||
|
||||
#### **[ ] T4.3 Drawing Module**
|
||||
- **Objective:** จัดการแบบก่อสร้าง (Shop Drawing, Contract Drawing)
|
||||
- **Tasks:**
|
||||
- [ ] Implement `ShopDrawingService` & `ContractDrawingService`
|
||||
- [ ] Implement Revision Control for Drawings
|
||||
- [ ] Implement Drawing Numbering Logic
|
||||
|
||||
- **Objective:** จัดการแบบก่อสร้าง (Shop Drawing, Contract Drawing)
|
||||
- **Tasks:**
|
||||
- [ ] Implement `ShopDrawingService` & `ContractDrawingService`
|
||||
- [ ] Implement Revision Control for Drawings
|
||||
- [ ] Implement Drawing Numbering Logic
|
||||
|
||||
#### **[ ] T4.4 Transmittal Module**
|
||||
- **Objective:** จัดการใบนำส่งเอกสาร (Transmittal)
|
||||
- **Tasks:**
|
||||
- [ ] Implement `TransmittalService` (Create, View, PDF)
|
||||
- [ ] Implement `TransmittalItem` linking (Correspondence, RFA, Drawing)
|
||||
- [ ] Implement Transmittal Numbering (Type 901)
|
||||
- [ ] Generate PDF Transmittal Letter
|
||||
|
||||
- **Objective:** จัดการใบนำส่งเอกสาร (Transmittal)
|
||||
- **Tasks:**
|
||||
- [ ] Implement `TransmittalService` (Create, View, PDF)
|
||||
- [ ] Implement `TransmittalItem` linking (Correspondence, RFA, Drawing)
|
||||
- [ ] Implement Transmittal Numbering (Type 901)
|
||||
- [ ] Generate PDF Transmittal Letter
|
||||
|
||||
#### **[ ] T4.5 Circulation Module**
|
||||
- **Objective:** จัดการใบเวียนภายใน (Circulation Sheet)
|
||||
- **Tasks:**
|
||||
- [ ] Implement `CirculationService` (Create, Assign, Complete)
|
||||
- [ ] Implement `CirculationAssignee` tracking (Multiple users)
|
||||
- [ ] Implement Circulation Numbering (Type 900)
|
||||
- [ ] Integrate with Workflow for completion tracking
|
||||
|
||||
- **Objective:** จัดการใบเวียนภายใน (Circulation Sheet)
|
||||
- **Tasks:**
|
||||
- [ ] Implement `CirculationService` (Create, Assign, Complete)
|
||||
- [ ] Implement `CirculationAssignee` tracking (Multiple users)
|
||||
- [ ] Implement Circulation Numbering (Type 900)
|
||||
- [ ] Integrate with Workflow for completion tracking
|
||||
|
||||
---
|
||||
|
||||
@@ -182,18 +194,20 @@
|
||||
**Goal:** ระบบสนับสนุนและการตรวจสอบ
|
||||
|
||||
#### **[ ] T5.1 JSON Schema & Preferences**
|
||||
- **Objective:** จัดการ Dynamic Data และ User Settings
|
||||
- **Tasks:**
|
||||
- [ ] Implement `JsonSchemaService` (Registry & Validation)
|
||||
- [ ] Implement `UserPreferenceService`
|
||||
- [ ] Implement Virtual Column management
|
||||
|
||||
- **Objective:** จัดการ Dynamic Data และ User Settings
|
||||
- **Tasks:**
|
||||
- [ ] Implement `JsonSchemaService` (Registry & Validation)
|
||||
- [ ] Implement `UserPreferenceService`
|
||||
- [ ] Implement Virtual Column management
|
||||
|
||||
#### **[ ] T5.2 Search & Logs**
|
||||
- **Objective:** การค้นหาและตรวจสอบ
|
||||
- **Tasks:**
|
||||
- [ ] Implement **Elasticsearch** Sync
|
||||
- [ ] Implement **Audit Log** with Partitioning
|
||||
- [ ] Setup **Prometheus/Grafana** metrics
|
||||
|
||||
- **Objective:** การค้นหาและตรวจสอบ
|
||||
- **Tasks:**
|
||||
- [ ] Implement **Elasticsearch** Sync
|
||||
- [ ] Implement **Audit Log** with Partitioning
|
||||
- [ ] Setup **Prometheus/Grafana** metrics
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -206,7 +206,6 @@
|
||||
### **Phase 0: Tasks**
|
||||
|
||||
- **[ ] F0.1 Project Setup & Tooling**
|
||||
|
||||
- [ ] Initialize Next.js 14+ project with TypeScript
|
||||
- [ ] Configure pnpm workspace
|
||||
- [ ] Setup ESLint, Prettier, and pre-commit hooks
|
||||
@@ -217,7 +216,6 @@
|
||||
- [ ] **Dependencies:** None
|
||||
|
||||
- **[ ] F0.2 Design System & UI Components**
|
||||
|
||||
- [ ] Setup color palette and design tokens
|
||||
- [ ] Create responsive design breakpoints
|
||||
- [ ] Implement core shadcn/ui components:
|
||||
@@ -233,7 +231,6 @@
|
||||
- [ ] **Dependencies:** F0.1
|
||||
|
||||
- **[ ] F0.3 API Client & Authentication**
|
||||
|
||||
- [ ] Setup Axios client with interceptors:
|
||||
- [ ] Idempotency-Key header injection
|
||||
- [ ] Authentication token management
|
||||
@@ -282,7 +279,6 @@
|
||||
### **Phase 1: Tasks**
|
||||
|
||||
- **[ ] F1.1 Main Layout & Navigation**
|
||||
|
||||
- [ ] Create App Shell layout:
|
||||
- [ ] Navbar with user menu and notifications
|
||||
- [ ] Collapsible sidebar with navigation
|
||||
@@ -299,7 +295,6 @@
|
||||
- [ ] **Dependencies:** F0.2, F0.3
|
||||
|
||||
- **[ ] F1.2 Authentication Pages**
|
||||
|
||||
- [ ] Create login page with form validation
|
||||
- [ ] Implement forgot password flow
|
||||
- [ ] Create registration page (admin-only)
|
||||
@@ -310,7 +305,6 @@
|
||||
- [ ] **Dependencies:** F0.3, F1.1
|
||||
|
||||
- **[ ] F1.3 Dashboard & Landing**
|
||||
|
||||
- [ ] Create public landing page for non-authenticated users
|
||||
- [ ] Implement main dashboard with:
|
||||
- [ ] KPI cards (document counts, pending tasks)
|
||||
@@ -356,7 +350,6 @@
|
||||
### **Phase 2: Tasks**
|
||||
|
||||
- **[ ] F2.1 User Profile & Settings**
|
||||
|
||||
- [ ] Create user profile page:
|
||||
- [ ] Personal information display/edit
|
||||
- [ ] Password change functionality
|
||||
@@ -368,7 +361,6 @@
|
||||
- [ ] **Dependencies:** F1.1, F0.4
|
||||
|
||||
- **[ ] F2.2 Admin Panel - User Management**
|
||||
|
||||
- [ ] Create user list with search and filters
|
||||
- [ ] Implement user creation form
|
||||
- [ ] Create user edit interface
|
||||
@@ -379,7 +371,6 @@
|
||||
- [ ] **Dependencies:** F1.1, F2.1
|
||||
|
||||
- **[ ] F2.3 Admin Panel - Role Management**
|
||||
|
||||
- [ ] Create role list and management interface
|
||||
- [ ] Implement role creation and editing
|
||||
- [ ] Create permission assignment interface
|
||||
@@ -432,7 +423,6 @@
|
||||
### **Phase 3: Tasks**
|
||||
|
||||
- **[ ] F3.1 Project Management UI**
|
||||
|
||||
- [ ] Create project list with search and filters
|
||||
- [ ] Implement project creation and editing
|
||||
- [ ] Create project detail view
|
||||
@@ -443,7 +433,6 @@
|
||||
- [ ] **Dependencies:** F1.1, F2.4
|
||||
|
||||
- **[ ] F3.2 Organization Management**
|
||||
|
||||
- [ ] Create organization list and management
|
||||
- [ ] Implement organization creation and editing
|
||||
- [ ] Create organization detail view
|
||||
@@ -479,7 +468,6 @@
|
||||
### **Phase 4: Tasks**
|
||||
|
||||
- **[ ] F4.1 Correspondence List & Search**
|
||||
|
||||
- [ ] Create correspondence list with advanced filtering:
|
||||
- [ ] Filter by type, status, project, organization
|
||||
- [ ] Search by title, document number, content
|
||||
@@ -494,7 +482,6 @@
|
||||
- [ ] **Dependencies:** F1.1, F3.1
|
||||
|
||||
- **[ ] F4.2 Correspondence Creation Form**
|
||||
|
||||
- [ ] Create dynamic form generator based on JSON schema
|
||||
- [ ] Implement form with multiple sections:
|
||||
- [ ] Basic information (type, title, recipients)
|
||||
@@ -512,7 +499,6 @@
|
||||
- [ ] **Dependencies:** F0.4, F4.1
|
||||
|
||||
- **[ ] F4.3 Correspondence Detail View**
|
||||
|
||||
- [ ] Create comprehensive detail page:
|
||||
- [ ] Document header with metadata
|
||||
- [ ] Content display based on type
|
||||
@@ -544,7 +530,6 @@
|
||||
### **Phase 4: Testing - Correspondence System**
|
||||
|
||||
- **[ ] F4.T1 Correspondence Test Suite**
|
||||
|
||||
- [ ] **Unit Tests:** Form validation, file upload components
|
||||
- [ ] **Integration Tests:** Complete document lifecycle, file attachment flow
|
||||
- [ ] **E2E Tests:** End-to-end correspondence creation and management
|
||||
@@ -563,7 +548,6 @@
|
||||
### **Phase 5: Tasks**
|
||||
|
||||
- **[ ] F5.1 Workflow Visualization Component**
|
||||
|
||||
- [ ] Create horizontal workflow progress visualization
|
||||
- [ ] Implement step status indicators (pending, active, completed, skipped)
|
||||
- [ ] Add due date and assignee information
|
||||
@@ -574,7 +558,6 @@
|
||||
- [ ] **Dependencies:** F4.3
|
||||
|
||||
- **[ ] F5.2 Routing Template Management**
|
||||
|
||||
- [ ] Create routing template list and editor
|
||||
- [ ] Implement drag-and-drop step configuration
|
||||
- [ ] Add step configuration (purpose, duration, assignee rules)
|
||||
@@ -585,7 +568,6 @@
|
||||
- [ ] **Dependencies:** F3.1, F4.2
|
||||
|
||||
- **[ ] F5.3 Workflow Step Actions**
|
||||
|
||||
- [ ] Create step action interface:
|
||||
- [ ] Approve, reject, request changes
|
||||
- [ ] Add comments and attachments
|
||||
@@ -623,7 +605,6 @@
|
||||
### **Phase 6: Tasks**
|
||||
|
||||
- **[ ] F6.1 Contract Drawings Management**
|
||||
|
||||
- [ ] Create contract drawing list with categorization
|
||||
- [ ] Implement drawing upload and metadata management
|
||||
- [ ] Create drawing preview and viewer
|
||||
@@ -634,7 +615,6 @@
|
||||
- [ ] **Dependencies:** F3.1, F4.4
|
||||
|
||||
- **[ ] F6.2 Shop Drawings Management**
|
||||
|
||||
- [ ] Create shop drawing list with revision tracking
|
||||
- [ ] Implement shop drawing creation and revision system
|
||||
- [ ] Create drawing comparison interface
|
||||
@@ -645,7 +625,6 @@
|
||||
- [ ] **Dependencies:** F6.1
|
||||
|
||||
- **[ ] F6.3 Drawing Revision System**
|
||||
|
||||
- [ ] Create revision history interface
|
||||
- [ ] Implement revision comparison functionality
|
||||
- [ ] Add revision notes and change tracking
|
||||
@@ -681,7 +660,6 @@
|
||||
### **Phase 7: Tasks**
|
||||
|
||||
- **[ ] F7.1 RFA List & Dashboard**
|
||||
|
||||
- [ ] Create RFA dashboard with status overview
|
||||
- [ ] Implement advanced RFA filtering and search
|
||||
- [ ] Create RFA calendar view for deadlines
|
||||
@@ -692,7 +670,6 @@
|
||||
- [ ] **Dependencies:** F4.1, F5.1
|
||||
|
||||
- **[ ] F7.2 RFA Creation with Dynamic Forms**
|
||||
|
||||
- [ ] Create RFA type-specific form generator
|
||||
- [ ] Implement dynamic form fields based on RFA type:
|
||||
- [ ] RFA_DWG: Shop drawing selection
|
||||
@@ -712,7 +689,6 @@
|
||||
- [ ] **Dependencies:** F4.2, F6.2
|
||||
|
||||
- **[ ] F7.3 RFA Workflow Integration**
|
||||
|
||||
- [ ] Integrate RFA with unified workflow engine
|
||||
- [ ] Create RFA-specific workflow steps and actions
|
||||
- [ ] Implement RFA approval interface
|
||||
@@ -748,7 +724,6 @@
|
||||
### **Phase 8: Tasks**
|
||||
|
||||
- **[ ] F8.1 Circulation Management**
|
||||
|
||||
- [ ] Create circulation list and management interface
|
||||
- [ ] Implement circulation creation from correspondence
|
||||
- [ ] Create circulation template management
|
||||
@@ -759,7 +734,6 @@
|
||||
- [ ] **Dependencies:** F4.1, F5.2
|
||||
|
||||
- **[ ] F8.2 Task Assignment Interface**
|
||||
|
||||
- [ ] Create task assignment interface with user selection
|
||||
- [ ] Implement task priority and deadline setting
|
||||
- [ ] Add task dependency management
|
||||
@@ -795,7 +769,6 @@
|
||||
### **Phase 9: Tasks**
|
||||
|
||||
- **[ ] F9.1 Advanced Search Interface**
|
||||
|
||||
- [ ] Create unified search interface across all document types
|
||||
- [ ] Implement faceted search with multiple filters
|
||||
- [ ] Add search result highlighting and relevance scoring
|
||||
@@ -806,7 +779,6 @@
|
||||
- [ ] **Dependencies:** F4.1, F7.1
|
||||
|
||||
- **[ ] F9.2 Notification System**
|
||||
|
||||
- [ ] Create notification center with real-time updates
|
||||
- [ ] Implement notification preferences management
|
||||
- [ ] Add notification grouping and digest views
|
||||
@@ -817,7 +789,6 @@
|
||||
- [ ] **Dependencies:** F1.3, F5.4
|
||||
|
||||
- **[ ] F9.3 Reporting & Analytics**
|
||||
|
||||
- [ ] Create reporting dashboard with customizable widgets
|
||||
- [ ] Implement data visualization components (charts, graphs)
|
||||
- [ ] Add report scheduling and export
|
||||
@@ -853,7 +824,6 @@
|
||||
### **Phase 10: Tasks**
|
||||
|
||||
- **[ ] F10.1 Comprehensive Testing**
|
||||
|
||||
- [ ] Idempotency Testing: เพิ่มการทดสอบเฉพาะสำหรับ Axios Interceptor เพื่อจำลองการส่ง Request POST/PUT/DELETE ที่มี Idempotency-Key ซ้ำไปยัง Mock API (MSW) เพื่อยืนยันว่า Client-side ไม่ส่ง Key ซ้ำในการทำงานปกติ และไม่เกิด Side Effect จากการ Replay Attack.
|
||||
- [ ] Write unit tests for all components and utilities
|
||||
- [ ] Create integration tests for critical user flows
|
||||
@@ -865,7 +835,6 @@
|
||||
- [ ] **Dependencies:** All previous phases
|
||||
|
||||
- **[ ] F10.2 Performance Optimization**
|
||||
|
||||
- [ ] Implement code splitting and lazy loading
|
||||
- [ ] Optimize bundle size and asset delivery
|
||||
- [ ] Add performance monitoring and metrics
|
||||
@@ -876,7 +845,6 @@
|
||||
- [ ] **Dependencies:** F10.1
|
||||
|
||||
- **[ ] F10.3 Security Hardening**
|
||||
|
||||
- [ ] Conduct security audit and penetration testing
|
||||
- [ ] Implement Content Security Policy (CSP)
|
||||
- [ ] Add security headers and protections
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
|
||||
### **Technology Stack**
|
||||
|
||||
- **Framework:** Next.js 14+ (App Router, React 18, TypeScript)
|
||||
- **Styling:** Tailwind CSS + Shadcn/UI
|
||||
- **State Management:**
|
||||
- **Server:** TanStack Query (React Query)
|
||||
- **Client:** Zustand
|
||||
- **Form:** React Hook Form + Zod
|
||||
- **Workflow Visualization:** ReactFlow (สำหรับ Workflow Builder)
|
||||
- **Editor:** Monaco Editor (สำหรับ DSL Editing)
|
||||
- **Validation:** Zod + AJV (JSON Schema)
|
||||
- **Framework:** Next.js 14+ (App Router, React 18, TypeScript)
|
||||
- **Styling:** Tailwind CSS + Shadcn/UI
|
||||
- **State Management:**
|
||||
- **Server:** TanStack Query (React Query)
|
||||
- **Client:** Zustand
|
||||
- **Form:** React Hook Form + Zod
|
||||
- **Workflow Visualization:** ReactFlow (สำหรับ Workflow Builder)
|
||||
- **Editor:** Monaco Editor (สำหรับ DSL Editing)
|
||||
- **Validation:** Zod + AJV (JSON Schema)
|
||||
|
||||
### **โครงสร้างโมดูล (Module Structure)**
|
||||
|
||||
@@ -61,17 +61,19 @@
|
||||
**Goal:** เตรียมโครงสร้างโปรเจกต์และ Component พื้นฐาน
|
||||
|
||||
#### **[ ] F1.1 Project Setup & Design System**
|
||||
- **Tasks:**
|
||||
- [ ] Setup Next.js 14 + Tailwind + Shadcn/UI
|
||||
- [ ] Configure Axios with **Idempotency Interceptor**
|
||||
- [ ] Implement Base Layout (Sidebar, Navbar, Breadcrumbs)
|
||||
- [ ] Setup **TanStack Query** & **Zustand**
|
||||
|
||||
- **Tasks:**
|
||||
- [ ] Setup Next.js 14 + Tailwind + Shadcn/UI
|
||||
- [ ] Configure Axios with **Idempotency Interceptor**
|
||||
- [ ] Implement Base Layout (Sidebar, Navbar, Breadcrumbs)
|
||||
- [ ] Setup **TanStack Query** & **Zustand**
|
||||
|
||||
#### **[ ] F1.2 Authentication UI**
|
||||
- **Tasks:**
|
||||
- [ ] Login Page with Form Validation
|
||||
- [ ] Integrate NextAuth.js with Backend
|
||||
- [ ] Implement RBAC Guard (Protect Routes based on Permissions)
|
||||
|
||||
- **Tasks:**
|
||||
- [ ] Login Page with Form Validation
|
||||
- [ ] Integrate NextAuth.js with Backend
|
||||
- [ ] Implement RBAC Guard (Protect Routes based on Permissions)
|
||||
|
||||
---
|
||||
|
||||
@@ -80,27 +82,30 @@
|
||||
**Goal:** ระบบจัดการผู้ใช้และข้อมูลหลัก (รองรับ v1.5.1 Requirements)
|
||||
|
||||
#### **[ ] F2.1 User & Role Management**
|
||||
- **Tasks:**
|
||||
- [ ] User List & CRUD (Admin only)
|
||||
- [ ] Role Assignment UI
|
||||
- [ ] Permission Matrix Viewer
|
||||
|
||||
- **Tasks:**
|
||||
- [ ] User List & CRUD (Admin only)
|
||||
- [ ] Role Assignment UI
|
||||
- [ ] Permission Matrix Viewer
|
||||
|
||||
#### **[ ] F2.2 Enhanced Master Data UI**
|
||||
- **Tasks:**
|
||||
- [ ] **Organization Management:** CRUD + Logo Upload
|
||||
- [ ] **Project & Contract Management:** CRUD + Relations
|
||||
- [ ] **[NEW] Discipline Management:** CRUD for `disciplines`
|
||||
- [ ] **[NEW] Sub-Type Management:** CRUD for `correspondence_sub_types`
|
||||
|
||||
- **Tasks:**
|
||||
- [ ] **Organization Management:** CRUD + Logo Upload
|
||||
- [ ] **Project & Contract Management:** CRUD + Relations
|
||||
- [ ] **[NEW] Discipline Management:** CRUD for `disciplines`
|
||||
- [ ] **[NEW] Sub-Type Management:** CRUD for `correspondence_sub_types`
|
||||
|
||||
#### **[ ] F2.3 System Configuration**
|
||||
- **Tasks:**
|
||||
- [ ] **[NEW] Document Numbering Config:**
|
||||
- Template Editor (Monaco/Visual)
|
||||
- Sequence Viewer
|
||||
- [ ] **[NEW] Workflow Configuration:**
|
||||
- Workflow List
|
||||
- DSL Editor (Monaco)
|
||||
- Visual Builder (ReactFlow)
|
||||
|
||||
- **Tasks:**
|
||||
- [ ] **[NEW] Document Numbering Config:**
|
||||
- Template Editor (Monaco/Visual)
|
||||
- Sequence Viewer
|
||||
- [ ] **[NEW] Workflow Configuration:**
|
||||
- Workflow List
|
||||
- DSL Editor (Monaco)
|
||||
- Visual Builder (ReactFlow)
|
||||
|
||||
---
|
||||
|
||||
@@ -109,23 +114,25 @@
|
||||
**Goal:** โมดูลหลัก Correspondence และ RFA
|
||||
|
||||
#### **[ ] F3.1 Correspondence Module**
|
||||
- **Tasks:**
|
||||
- [ ] List View with Advanced Filters
|
||||
- [ ] **Create/Edit Form:**
|
||||
- Add **Discipline Selector** (Dynamic based on Contract)
|
||||
- Add **Sub-Type Selector** (Dynamic based on Type)
|
||||
- File Upload (Two-Phase)
|
||||
- [ ] Detail View with History & Comments
|
||||
|
||||
- **Tasks:**
|
||||
- [ ] List View with Advanced Filters
|
||||
- [ ] **Create/Edit Form:**
|
||||
- Add **Discipline Selector** (Dynamic based on Contract)
|
||||
- Add **Sub-Type Selector** (Dynamic based on Type)
|
||||
- File Upload (Two-Phase)
|
||||
- [ ] Detail View with History & Comments
|
||||
|
||||
#### **[ ] F3.2 RFA Module**
|
||||
- **Tasks:**
|
||||
- [ ] RFA List & Dashboard
|
||||
- [ ] **Dynamic RFA Form:**
|
||||
- Fields change based on RFA Type (DWG, MAT, MES)
|
||||
- Item List Management
|
||||
- [ ] **Approval Interface:**
|
||||
- Approve/Reject/Comment Actions
|
||||
- Workflow Status Visualization
|
||||
|
||||
- **Tasks:**
|
||||
- [ ] RFA List & Dashboard
|
||||
- [ ] **Dynamic RFA Form:**
|
||||
- Fields change based on RFA Type (DWG, MAT, MES)
|
||||
- Item List Management
|
||||
- [ ] **Approval Interface:**
|
||||
- Approve/Reject/Comment Actions
|
||||
- Workflow Status Visualization
|
||||
|
||||
---
|
||||
|
||||
@@ -134,22 +141,25 @@
|
||||
**Goal:** โมดูล Drawing, Transmittal และ Circulation
|
||||
|
||||
#### **[ ] F4.1 Drawing Module**
|
||||
- **Tasks:**
|
||||
- [ ] Shop Drawing & Contract Drawing Lists
|
||||
- [ ] Revision Management UI
|
||||
- [ ] Drawing Viewer (PDF/Image)
|
||||
|
||||
- **Tasks:**
|
||||
- [ ] Shop Drawing & Contract Drawing Lists
|
||||
- [ ] Revision Management UI
|
||||
- [ ] Drawing Viewer (PDF/Image)
|
||||
|
||||
#### **[ ] F4.2 Transmittal Module**
|
||||
- **Tasks:**
|
||||
- [ ] Transmittal Creation Form (Select Documents to send)
|
||||
- [ ] Transmittal Letter Preview (PDF Generation)
|
||||
- [ ] Transmittal History
|
||||
|
||||
- **Tasks:**
|
||||
- [ ] Transmittal Creation Form (Select Documents to send)
|
||||
- [ ] Transmittal Letter Preview (PDF Generation)
|
||||
- [ ] Transmittal History
|
||||
|
||||
#### **[ ] F4.3 Circulation Module**
|
||||
- **Tasks:**
|
||||
- [ ] Circulation Sheet Creation (Select Assignees)
|
||||
- [ ] "My Tasks" Dashboard for Circulation
|
||||
- [ ] Completion & Tracking UI
|
||||
|
||||
- **Tasks:**
|
||||
- [ ] Circulation Sheet Creation (Select Assignees)
|
||||
- [ ] "My Tasks" Dashboard for Circulation
|
||||
- [ ] Completion & Tracking UI
|
||||
|
||||
---
|
||||
|
||||
@@ -158,15 +168,17 @@
|
||||
**Goal:** การค้นหาและหน้า Dashboard
|
||||
|
||||
#### **[ ] F5.1 Advanced Search**
|
||||
- **Tasks:**
|
||||
- [ ] Unified Search Interface (Elasticsearch)
|
||||
- [ ] Faceted Filters (Type, Date, Project, Status)
|
||||
|
||||
- **Tasks:**
|
||||
- [ ] Unified Search Interface (Elasticsearch)
|
||||
- [ ] Faceted Filters (Type, Date, Project, Status)
|
||||
|
||||
#### **[ ] F5.2 Dashboard & Monitoring**
|
||||
- **Tasks:**
|
||||
- [ ] Personal Dashboard (My Tasks, Pending Approvals)
|
||||
- [ ] Project Dashboard (KPIs, Stats)
|
||||
- [ ] **Admin Audit Logs Viewer**
|
||||
|
||||
- **Tasks:**
|
||||
- [ ] Personal Dashboard (My Tasks, Pending Approvals)
|
||||
- [ ] Project Dashboard (KPIs, Stats)
|
||||
- [ ] **Admin Audit Logs Viewer**
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -2159,28 +2159,23 @@ WHERE user_id = ?
|
||||
**Additional Performance Indexes**:
|
||||
|
||||
1. **Correspondence Tables**:
|
||||
|
||||
- `idx_correspondences_type_project` on (correspondence_type_id, project_id)
|
||||
- `idx_corr_revisions_current_status` on (is_current, correspondence_status_id)
|
||||
- `idx_corr_revisions_correspondence_current` on (correspondence_id, is_current)
|
||||
- `idx_correspondences_project_type` on (project_id, correspondence_type_id)
|
||||
|
||||
2. **RFA Tables**:
|
||||
|
||||
- `idx_rfa_revisions_current_status` on (is_current, rfa_status_code_id)
|
||||
- `idx_rfa_revisions_rfa_current` on (rfa_id, is_current)
|
||||
|
||||
3. **Circulation Tables**:
|
||||
|
||||
- `idx_circulation_routings_status_assigned` on (status, assigned_to)
|
||||
- `idx_circulation_routings_circulation_status` on (circulation_id, status)
|
||||
|
||||
4. **Document Numbering**:
|
||||
|
||||
- `idx_doc_counter_composite` on (project_id, originator_organization_id, correspondence_type_id, current_year)
|
||||
|
||||
5. **Audit & Notifications**:
|
||||
|
||||
- `idx_audit_logs_reporting` on (created_at, entity_type, action)
|
||||
- `idx_notifications_user_unread` on (user_id, is_read, created_at)
|
||||
|
||||
@@ -2208,12 +2203,10 @@ WHERE user_id = ?
|
||||
### Unique Constraints
|
||||
|
||||
1. **Globally Unique**:
|
||||
|
||||
- usernames, emails
|
||||
- shop_drawing.drawing_number
|
||||
|
||||
2. **Unique Within Scope**:
|
||||
|
||||
- (project_id, correspondence_number)
|
||||
- (project_id, condwg_no)
|
||||
- (correspondence_id, revision_number)
|
||||
@@ -2232,13 +2225,11 @@ WHERE user_id = ?
|
||||
### Business Rule Constraints
|
||||
|
||||
1. **Soft Delete Pattern**:
|
||||
|
||||
- deleted_at timestamp instead of hard delete
|
||||
- Preserves audit trail and relationships
|
||||
- Applied to: correspondences, rfas, shop_drawings, contract_drawings
|
||||
|
||||
2. **Current Revision Pattern**:
|
||||
|
||||
- is_current flag with UNIQUE constraint
|
||||
- Ensures only one current revision per document
|
||||
|
||||
@@ -2423,13 +2414,11 @@ ANALYZE TABLE correspondences;
|
||||
### Business Logic Validation
|
||||
|
||||
1. **Document Workflow**:
|
||||
|
||||
- Cannot edit submitted documents (unless Document Control)
|
||||
- Cannot skip workflow steps (unless forced)
|
||||
- Must provide approval comments
|
||||
|
||||
2. **User Management**:
|
||||
|
||||
- Cannot delete users with active assignments
|
||||
- Cannot deactivate own account
|
||||
- Must have valid organization for non-Global roles
|
||||
@@ -2545,19 +2534,16 @@ ANALYZE TABLE correspondences;
|
||||
### Integration Points
|
||||
|
||||
1. **Document Numbering**:
|
||||
|
||||
- Call DocumentNumberingService.generateNextNumber() (NestJS) which handles Redis locking and retry logic
|
||||
- Format with template from document_number_formats
|
||||
- Store in correspondences.correspondence_number
|
||||
|
||||
2. **File Upload**:
|
||||
|
||||
- Upload to QNAP /share/dms-data/
|
||||
- Create attachment record
|
||||
- Link via junction table
|
||||
|
||||
3. **Workflow Execution**:
|
||||
|
||||
- Check rfa_workflow_templates
|
||||
- Create rfa_workflows records
|
||||
- Update status as steps complete
|
||||
|
||||
@@ -123,16 +123,17 @@
|
||||
|
||||
**Purpose**: เก็บข้อมูลสาขางาน (Disciplines) แยกตามสัญญา (Req 6B)
|
||||
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| :-------------- | :----------- | :----------- | :--------------------- |
|
||||
| id | INT | PK, AI | Unique identifier |
|
||||
| contract_id | INT | FK, NOT NULL | ผูกกับสัญญา |
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| :-------------- | :----------- | :----------- | :----------------------- |
|
||||
| id | INT | PK, AI | Unique identifier |
|
||||
| contract_id | INT | FK, NOT NULL | ผูกกับสัญญา |
|
||||
| discipline_code | VARCHAR(10) | NOT NULL | รหัสสาขา (เช่น GEN, STR) |
|
||||
| code_name_th | VARCHAR(255) | NULL | ชื่อไทย |
|
||||
| code_name_en | VARCHAR(255) | NULL | ชื่ออังกฤษ |
|
||||
| is_active | TINYINT(1) | DEFAULT 1 | สถานะการใช้งาน |
|
||||
| code_name_en | VARCHAR(255) | NULL | ชื่ออังกฤษ |
|
||||
| is_active | TINYINT(1) | DEFAULT 1 | สถานะการใช้งาน |
|
||||
|
||||
**Indexes**:
|
||||
|
||||
- UNIQUE (contract_id, discipline_code)
|
||||
|
||||
---
|
||||
@@ -334,13 +335,13 @@
|
||||
|
||||
**Purpose**: เก็บการตั้งค่าส่วนตัวของผู้ใช้ (Req 5.5, 6.8.3)
|
||||
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| :----------- | :---------- | :-------------- | :-------------- |
|
||||
| user_id | INT | PK, FK | User ID |
|
||||
| notify_email | BOOLEAN | DEFAULT TRUE | รับอีเมลแจ้งเตือน |
|
||||
| notify_line | BOOLEAN | DEFAULT TRUE | รับไลน์แจ้งเตือน |
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| :----------- | :---------- | :-------------- | :----------------- |
|
||||
| user_id | INT | PK, FK | User ID |
|
||||
| notify_email | BOOLEAN | DEFAULT TRUE | รับอีเมลแจ้งเตือน |
|
||||
| notify_line | BOOLEAN | DEFAULT TRUE | รับไลน์แจ้งเตือน |
|
||||
| digest_mode | BOOLEAN | DEFAULT FALSE | รับแจ้งเตือนแบบรวม |
|
||||
| ui_theme | VARCHAR(20) | DEFAULT 'light' | UI Theme |
|
||||
| ui_theme | VARCHAR(20) | DEFAULT 'light' | UI Theme |
|
||||
|
||||
---
|
||||
|
||||
@@ -375,13 +376,13 @@
|
||||
|
||||
**Purpose**: เก็บประเภทหนังสือย่อย (Sub Types) สำหรับ Mapping เลขรหัส (Req 6B)
|
||||
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| :--------------------- | :----------- | :----------- | :------------------------ |
|
||||
| id | INT | PK, AI | Unique identifier |
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| :--------------------- | :----------- | :----------- | :--------------------------- |
|
||||
| id | INT | PK, AI | Unique identifier |
|
||||
| contract_id | INT | FK, NOT NULL | ผูกกับสัญญา |
|
||||
| correspondence_type_id | INT | FK, NOT NULL | ผูกกับประเภทเอกสารหลัก |
|
||||
| sub_type_code | VARCHAR(20) | NOT NULL | รหัสย่อย (เช่น MAT, SHP) |
|
||||
| sub_type_name | VARCHAR(255) | NULL | ชื่อประเภทหนังสือย่อย |
|
||||
| sub_type_name | VARCHAR(255) | NULL | ชื่อประเภทหนังสือย่อย |
|
||||
| sub_type_number | VARCHAR(10) | NULL | เลขรหัสสำหรับ Running Number |
|
||||
|
||||
---
|
||||
@@ -395,7 +396,7 @@
|
||||
| id | INT | PRIMARY KEY, AUTO_INCREMENT | Master correspondence ID |
|
||||
| correspondence_number | VARCHAR(100) | NOT NULL | Document number (from numbering system) |
|
||||
| correspondence_type_id | INT | NOT NULL, FK | Reference to correspondence_types |
|
||||
| **discipline_id** | **INT** | **NULL, FK** | **[NEW] สาขางาน (ถ้ามี)** |
|
||||
| **discipline_id** | **INT** | **NULL, FK** | **[NEW] สาขางาน (ถ้ามี)** |
|
||||
| is_internal_communication | TINYINT(1) | DEFAULT 0 | Internal (1) or external (0) communication |
|
||||
| project_id | INT | NOT NULL, FK | Reference to projects table |
|
||||
| originator_id | INT | NULL, FK | Originating organization |
|
||||
@@ -427,28 +428,28 @@
|
||||
|
||||
**Purpose**: Child table storing revision history of correspondences (1:N)
|
||||
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| ------------------------ | ------------ | --------------------------------- | -------------------------------------------------------- |
|
||||
| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique revision ID |
|
||||
| correspondence_id | INT | NOT NULL, FK | Master correspondence ID |
|
||||
| revision_number | INT | NOT NULL | Revision sequence (0, 1, 2...) |
|
||||
| revision_label | VARCHAR(10) | NULL | Display revision (A, B, 1.1...) |
|
||||
| is_current | BOOLEAN | DEFAULT FALSE | Current revision flag |
|
||||
| correspondence_status_id | INT | NOT NULL, FK | Current status of this revision |
|
||||
| title | VARCHAR(255) | NOT NULL | Document title |
|
||||
| document_date | DATE | NULL | Document date |
|
||||
| issued_date | DATETIME | NULL | Issue date |
|
||||
| received_date | DATETIME | NULL | Received date |
|
||||
| due_date | DATETIME | NULL | Due date for response |
|
||||
| description | TEXT | NULL | Revision description |
|
||||
| details | JSON | NULL | Type-specific details (e.g., RFI questions) |
|
||||
| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | Revision creation timestamp |
|
||||
| created_by | INT | NULL, FK | User who created revision |
|
||||
| updated_by | INT | NULL, FK | User who last updated |
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| ------------------------ | ------------ | --------------------------------- | ------------------------------------------------------------ |
|
||||
| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique revision ID |
|
||||
| correspondence_id | INT | NOT NULL, FK | Master correspondence ID |
|
||||
| revision_number | INT | NOT NULL | Revision sequence (0, 1, 2...) |
|
||||
| revision_label | VARCHAR(10) | NULL | Display revision (A, B, 1.1...) |
|
||||
| is_current | BOOLEAN | DEFAULT FALSE | Current revision flag |
|
||||
| correspondence_status_id | INT | NOT NULL, FK | Current status of this revision |
|
||||
| title | VARCHAR(255) | NOT NULL | Document title |
|
||||
| document_date | DATE | NULL | Document date |
|
||||
| issued_date | DATETIME | NULL | Issue date |
|
||||
| received_date | DATETIME | NULL | Received date |
|
||||
| due_date | DATETIME | NULL | Due date for response |
|
||||
| description | TEXT | NULL | Revision description |
|
||||
| details | JSON | NULL | Type-specific details (e.g., RFI questions) |
|
||||
| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | Revision creation timestamp |
|
||||
| created_by | INT | NULL, FK | User who created revision |
|
||||
| updated_by | INT | NULL, FK | User who last updated |
|
||||
| v_ref_project_id | INT | GENERATED ALWAYS AS (...) VIRTUAL | Virtual Column ดึง Project ID จาก JSON details เพื่อทำ Index |
|
||||
| v_ref_type | VARCHAR(50) | GENERATED ALWAYS AS (...) VIRTUAL | Virtual Column ดึง Type จาก JSON details |
|
||||
| v_doc_subtype | VARCHAR(50) | GENERATED ALWAYS AS (...) VIRTUAL | Virtual Column ดึง Type จาก JSON details |
|
||||
| schema_version | INT | DEFAULT 1 | Version of the schema used with this details |
|
||||
| v_ref_type | VARCHAR(50) | GENERATED ALWAYS AS (...) VIRTUAL | Virtual Column ดึง Type จาก JSON details |
|
||||
| v_doc_subtype | VARCHAR(50) | GENERATED ALWAYS AS (...) VIRTUAL | Virtual Column ดึง Type จาก JSON details |
|
||||
| schema_version | INT | DEFAULT 1 | Version of the schema used with this details |
|
||||
|
||||
**Indexes**:
|
||||
|
||||
@@ -649,7 +650,7 @@
|
||||
| ----------------- | --------- | --------------------------- | --------------------------- |
|
||||
| id | INT | PRIMARY KEY, AUTO_INCREMENT | Master RFA ID |
|
||||
| rfa_type_id | INT | NOT NULL, FK | Reference to rfa_types |
|
||||
| **discipline_id** | **INT** | **NULL, FK** | **[NEW] สาขางาน (ถ้ามี)** |
|
||||
| **discipline_id** | **INT** | **NULL, FK** | **[NEW] สาขางาน (ถ้ามี)** |
|
||||
| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp |
|
||||
| created_by | INT | NULL, FK | User who created the record |
|
||||
| deleted_at | DATETIME | NULL | Soft delete timestamp |
|
||||
@@ -674,28 +675,28 @@
|
||||
|
||||
**Purpose**: Child table storing revision history of RFAs (1:N)
|
||||
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| ------------------- | ------------ | --------------------------------- | ----------------------------------------------------------- |
|
||||
| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique revision ID |
|
||||
| correspondence_id | INT | NOT NULL, FK | Link to correspondence (RFA as correspondence) |
|
||||
| rfa_id | INT | NOT NULL, FK | Master RFA ID |
|
||||
| revision_number | INT | NOT NULL | Revision sequence (0, 1, 2...) |
|
||||
| revision_label | VARCHAR(10) | NULL | Display revision (A, B, 1.1...) |
|
||||
| is_current | BOOLEAN | DEFAULT FALSE | Current revision flag |
|
||||
| rfa_status_code_id | INT | NOT NULL, FK | Current RFA status |
|
||||
| rfa_approve_code_id | INT | NULL, FK | Approval result code |
|
||||
| title | VARCHAR(255) | NOT NULL | RFA title |
|
||||
| document_date | DATE | NULL | Document date |
|
||||
| issued_date | DATE | NULL | Issue date for approval |
|
||||
| received_date | DATETIME | NULL | Received date |
|
||||
| approved_date | DATE | NULL | Approval date |
|
||||
| description | TEXT | NULL | Revision description |
|
||||
| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | Revision creation timestamp |
|
||||
| created_by | INT | NULL, FK | User who created revision |
|
||||
| updated_by | INT | NULL, FK | User who last updated |
|
||||
| details | JSON | NULL | Type-specific details (e.g., RFI questions) |
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| ------------------- | ------------ | --------------------------------- | --------------------------------------------------------------- |
|
||||
| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique revision ID |
|
||||
| correspondence_id | INT | NOT NULL, FK | Link to correspondence (RFA as correspondence) |
|
||||
| rfa_id | INT | NOT NULL, FK | Master RFA ID |
|
||||
| revision_number | INT | NOT NULL | Revision sequence (0, 1, 2...) |
|
||||
| revision_label | VARCHAR(10) | NULL | Display revision (A, B, 1.1...) |
|
||||
| is_current | BOOLEAN | DEFAULT FALSE | Current revision flag |
|
||||
| rfa_status_code_id | INT | NOT NULL, FK | Current RFA status |
|
||||
| rfa_approve_code_id | INT | NULL, FK | Approval result code |
|
||||
| title | VARCHAR(255) | NOT NULL | RFA title |
|
||||
| document_date | DATE | NULL | Document date |
|
||||
| issued_date | DATE | NULL | Issue date for approval |
|
||||
| received_date | DATETIME | NULL | Received date |
|
||||
| approved_date | DATE | NULL | Approval date |
|
||||
| description | TEXT | NULL | Revision description |
|
||||
| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | Revision creation timestamp |
|
||||
| created_by | INT | NULL, FK | User who created revision |
|
||||
| updated_by | INT | NULL, FK | User who last updated |
|
||||
| details | JSON | NULL | Type-specific details (e.g., RFI questions) |
|
||||
| v_ref_drawing_count | INT | GENERATED ALWAYS AS (...) VIRTUAL | Virtual Column ดึง Drawing Count จาก JSON details เพื่อทำ Index |
|
||||
| schema_version | INT | DEFAULT 1 | Version of the schema used with this details |
|
||||
| schema_version | INT | DEFAULT 1 | Version of the schema used with this details |
|
||||
|
||||
**Indexes**:
|
||||
|
||||
@@ -752,20 +753,20 @@
|
||||
|
||||
**Purpose**: Transaction log table tracking actual RFA approval workflow execution
|
||||
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| --------------- | --------- | ----------------------------------- | ------------------------------------------------- |
|
||||
| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique workflow log ID |
|
||||
| rfa_revision_id | INT | NOT NULL, FK | Reference to RFA revision |
|
||||
| step_number | INT | NOT NULL | Current step number |
|
||||
| organization_id | INT | NOT NULL, FK | Organization responsible |
|
||||
| assigned_to | INT | NULL, FK | Assigned user ID |
|
||||
| action_type | ENUM | NULL | Action type: REVIEW, APPROVE, ACKNOWLEDGE |
|
||||
| status | ENUM | NULL | Status: PENDING, IN_PROGRESS, COMPLETED, REJECTED |
|
||||
| comments | TEXT | NULL | Comments/remarks |
|
||||
| completed_at | DATETIME | NULL | Completion timestamp |
|
||||
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp |
|
||||
| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE | Last update timestamp |
|
||||
| state_context | JSON\* | NULL | เก็บข้อมูล Context ของ Workflow ณ ขณะนั้น (Snapshot) |
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| --------------- | --------- | ----------------------------------- | ---------------------------------------------------- |
|
||||
| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique workflow log ID |
|
||||
| rfa_revision_id | INT | NOT NULL, FK | Reference to RFA revision |
|
||||
| step_number | INT | NOT NULL | Current step number |
|
||||
| organization_id | INT | NOT NULL, FK | Organization responsible |
|
||||
| assigned_to | INT | NULL, FK | Assigned user ID |
|
||||
| action_type | ENUM | NULL | Action type: REVIEW, APPROVE, ACKNOWLEDGE |
|
||||
| status | ENUM | NULL | Status: PENDING, IN_PROGRESS, COMPLETED, REJECTED |
|
||||
| comments | TEXT | NULL | Comments/remarks |
|
||||
| completed_at | DATETIME | NULL | Completion timestamp |
|
||||
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp |
|
||||
| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE | Last update timestamp |
|
||||
| state_context | JSON\* | NULL | เก็บข้อมูล Context ของ Workflow ณ ขณะนั้น (Snapshot) |
|
||||
|
||||
**Indexes**:
|
||||
|
||||
@@ -1293,20 +1294,20 @@
|
||||
|
||||
**Purpose**: Central repository for all file attachments in the system
|
||||
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| ------------------- | ------------ | --------------------------- | -------------------------------------------------------------- |
|
||||
| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique attachment ID |
|
||||
| original_filename | VARCHAR(255) | NOT NULL | Original filename from upload |
|
||||
| stored_filename | VARCHAR(255) | NOT NULL | System-generated unique filename |
|
||||
| file_path | VARCHAR(500) | NOT NULL | Full file path on server (/share/dms-data/) |
|
||||
| mime_type | VARCHAR(100) | NOT NULL | MIME type (application/pdf, image/jpeg, etc.) |
|
||||
| file_size | INT | NOT NULL | File size in bytes |
|
||||
| uploaded_by_user_id | INT | NOT NULL, FK | User who uploaded file |
|
||||
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Upload timestamp |
|
||||
| is_temporary | BOOLEAN | DEFAULT TRUE | ระบุว่าเป็นไฟล์ชั่วคราว (ยังไม่ได้ Commit) |
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| ------------------- | ------------ | --------------------------- | -------------------------------------------------------------------------- |
|
||||
| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique attachment ID |
|
||||
| original_filename | VARCHAR(255) | NOT NULL | Original filename from upload |
|
||||
| stored_filename | VARCHAR(255) | NOT NULL | System-generated unique filename |
|
||||
| file_path | VARCHAR(500) | NOT NULL | Full file path on server (/share/dms-data/) |
|
||||
| mime_type | VARCHAR(100) | NOT NULL | MIME type (application/pdf, image/jpeg, etc.) |
|
||||
| file_size | INT | NOT NULL | File size in bytes |
|
||||
| uploaded_by_user_id | INT | NOT NULL, FK | User who uploaded file |
|
||||
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Upload timestamp |
|
||||
| is_temporary | BOOLEAN | DEFAULT TRUE | ระบุว่าเป็นไฟล์ชั่วคราว (ยังไม่ได้ Commit) |
|
||||
| temp_id\* | VARCHAR(100) | NULL | ID ชั่วคราวสำหรับอ้างอิงตอน Upload Phase 1 (อาจใช้ร่วมกับ id หรือแยกก็ได้) |
|
||||
| expires_at | DATETIME | NULL | เวลาหมดอายุของไฟล์ Temp (เพื่อให้ Cron Job ลบออก) |
|
||||
| checksum | VARCHAR(64) | NULL | SHA-256 Checksum สำหรับ Verify File Integrity [Req 3.9.3] |
|
||||
| expires_at | DATETIME | NULL | เวลาหมดอายุของไฟล์ Temp (เพื่อให้ Cron Job ลบออก) |
|
||||
| checksum | VARCHAR(64) | NULL | SHA-256 Checksum สำหรับ Verify File Integrity [Req 3.9.3] |
|
||||
|
||||
**Indexes**:
|
||||
|
||||
@@ -1482,17 +1483,17 @@
|
||||
|
||||
**Purpose**: Transaction table tracking running numbers (High Concurrency)
|
||||
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| -------------------------- | --------- | ------------- | -------------------------------------------- |
|
||||
| project_id | INT | PK, NOT NULL | โครงการ |
|
||||
| originator_organization_id | INT | PK, NOT NULL | องค์กรผู้ส่ง |
|
||||
| recipient_organization_id | INT | PK, NOT NULL | [NEW] องค์กรผู้รับ (-1 = ทุกองค์กร) |
|
||||
| correspondence_type_id | INT | PK, NOT NULL | ประเภทเอกสาร |
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| -------------------------- | --------- | ------------- | ------------------------------------------------- |
|
||||
| project_id | INT | PK, NOT NULL | โครงการ |
|
||||
| originator_organization_id | INT | PK, NOT NULL | องค์กรผู้ส่ง |
|
||||
| recipient_organization_id | INT | PK, NOT NULL | [NEW] องค์กรผู้รับ (-1 = ทุกองค์กร) |
|
||||
| correspondence_type_id | INT | PK, NOT NULL | ประเภทเอกสาร |
|
||||
| sub_type_id | INT | PK, DEFAULT 0 | [NEW] ประเภทย่อย สำหรับ TRANSMITTAL (0 = ไม่ระบุ) |
|
||||
| rfa_type_id | INT | PK, DEFAULT 0 | [NEW] ประเภท RFA (0 = ไม่ใช่ RFA) |
|
||||
| discipline_id | INT | PK, DEFAULT 0 | [NEW] สาขางาน (0 = ไม่ระบุ) |
|
||||
| rfa_type_id | INT | PK, DEFAULT 0 | [NEW] ประเภท RFA (0 = ไม่ใช่ RFA) |
|
||||
| discipline_id | INT | PK, DEFAULT 0 | [NEW] สาขางาน (0 = ไม่ระบุ) |
|
||||
| current_year | INT | PK, NOT NULL | ปี ค.ศ. ของตัวนับ (auto-reset ทุกปี) |
|
||||
| last_number | INT | DEFAULT 0 | เลขล่าสุดที่ถูกใช้งานไปแล้ว |
|
||||
| last_number | INT | DEFAULT 0 | เลขล่าสุดที่ถูกใช้งานไปแล้ว |
|
||||
| updated_at | TIMESTAMP | ON UPDATE | เวลาที่อัปเดตล่าสุด |
|
||||
|
||||
**Indexes**:
|
||||
@@ -1511,15 +1512,15 @@
|
||||
|
||||
**Purpose**: Audit log for document number generation (Debugging & Tracking)
|
||||
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| :---------------- | :----------- | :---------- | :---------------------------------- |
|
||||
| id | BIGINT | PK, AI | Unique ID |
|
||||
| transaction_id | VARCHAR(36) | NOT NULL | UUID ของ Transaction การขอเลข |
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| :---------------- | :----------- | :---------- | :---------------------------------------- |
|
||||
| id | BIGINT | PK, AI | Unique ID |
|
||||
| transaction_id | VARCHAR(36) | NOT NULL | UUID ของ Transaction การขอเลข |
|
||||
| counter_key_json | JSON | NOT NULL | ค่า Key ที่ใช้ในการ Query (เก็บเป็น JSON) |
|
||||
| generated_number | VARCHAR(100) | NOT NULL | เลขที่ได้ |
|
||||
| requested_by | INT | FK | User ที่ขอเลข |
|
||||
| requested_at | TIMESTAMP | DEFAULT NOW | เวลาที่ขอ |
|
||||
| execution_time_ms | INT | NULL | เวลาที่ใช้ในการประมวลผล (ms) |
|
||||
| generated_number | VARCHAR(100) | NOT NULL | เลขที่ได้ |
|
||||
| requested_by | INT | FK | User ที่ขอเลข |
|
||||
| requested_at | TIMESTAMP | DEFAULT NOW | เวลาที่ขอ |
|
||||
| execution_time_ms | INT | NULL | เวลาที่ใช้ในการประมวลผล (ms) |
|
||||
|
||||
---
|
||||
|
||||
@@ -1527,14 +1528,14 @@
|
||||
|
||||
**Purpose**: Error log for failed document number generation
|
||||
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| :--------------- | :---------- | :---------- | :------------------------------- |
|
||||
| id | BIGINT | PK, AI | Unique ID |
|
||||
| transaction_id | VARCHAR(36) | NOT NULL | UUID ของ Transaction |
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| :--------------- | :---------- | :---------- | :--------------------------------- |
|
||||
| id | BIGINT | PK, AI | Unique ID |
|
||||
| transaction_id | VARCHAR(36) | NOT NULL | UUID ของ Transaction |
|
||||
| error_code | VARCHAR(50) | NOT NULL | รหัส Error (เช่น ERR_LOCK_TIMEOUT) |
|
||||
| error_message | TEXT | NOT NULL | รายละเอียด Error |
|
||||
| counter_key_json | JSON | NULL | ค่า Key ที่พยายามใช้ |
|
||||
| occurred_at | TIMESTAMP | DEFAULT NOW | เวลาที่เกิด Error |
|
||||
| error_message | TEXT | NOT NULL | รายละเอียด Error |
|
||||
| counter_key_json | JSON | NULL | ค่า Key ที่พยายามใช้ |
|
||||
| occurred_at | TIMESTAMP | DEFAULT NOW | เวลาที่เกิด Error |
|
||||
|
||||
---
|
||||
|
||||
@@ -1544,18 +1545,19 @@
|
||||
|
||||
**Purpose**: เก็บแม่แบบ (Template) ของ Workflow (Req 3.6)
|
||||
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| :------------ | :----------- | :----------- | :--------------------------------------------- |
|
||||
| id | INT | PK, AI | Unique ID |
|
||||
| workflow_code | VARCHAR(50) | UNIQUE | รหัส Workflow (เช่น WF-RFA-GENERIC) |
|
||||
| workflow_name | VARCHAR(255) | NOT NULL | ชื่อ Workflow |
|
||||
| description | TEXT | NULL | คำอธิบาย |
|
||||
| module | VARCHAR(50) | NOT NULL | ใช้กับ Module ไหน (RFA, CORRESPONDENCE) |
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| :------------ | :----------- | :----------- | :------------------------------------------------ |
|
||||
| id | INT | PK, AI | Unique ID |
|
||||
| workflow_code | VARCHAR(50) | UNIQUE | รหัส Workflow (เช่น WF-RFA-GENERIC) |
|
||||
| workflow_name | VARCHAR(255) | NOT NULL | ชื่อ Workflow |
|
||||
| description | TEXT | NULL | คำอธิบาย |
|
||||
| module | VARCHAR(50) | NOT NULL | ใช้กับ Module ไหน (RFA, CORRESPONDENCE) |
|
||||
| steps_config | JSON | NOT NULL | การตั้งค่า Step (Sequence, Approvers, Conditions) |
|
||||
| is_active | BOOLEAN | DEFAULT TRUE | สถานะการใช้งาน |
|
||||
| version | INT | DEFAULT 1 | เวอร์ชันของ Definition |
|
||||
| is_active | BOOLEAN | DEFAULT TRUE | สถานะการใช้งาน |
|
||||
| version | INT | DEFAULT 1 | เวอร์ชันของ Definition |
|
||||
|
||||
**Business Rules**:
|
||||
|
||||
- `steps_config` เก็บ Logic ของ Workflow ทั้งหมดในรูปแบบ JSON เพื่อความยืดหยุ่น
|
||||
|
||||
---
|
||||
@@ -1564,16 +1566,16 @@
|
||||
|
||||
**Purpose**: เก็บสถานะของ Workflow ที่กำลังรันอยู่จริง (Runtime)
|
||||
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| :--------------------- | :----------- | :----------- | :----------------------------------------- |
|
||||
| id | BIGINT | PK, AI | Unique ID |
|
||||
| workflow_definition_id | INT | FK, NOT NULL | อ้างอิง Definition |
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| :--------------------- | :----------- | :----------- | :------------------------------------------------ |
|
||||
| id | BIGINT | PK, AI | Unique ID |
|
||||
| workflow_definition_id | INT | FK, NOT NULL | อ้างอิง Definition |
|
||||
| business_key | VARCHAR(100) | INDEX | ID ของเอกสารที่ผูกกับ Workflow นี้ (เช่น RFA-001) |
|
||||
| current_step_name | VARCHAR(100) | NOT NULL | ชื่อ Step ปัจจุบัน |
|
||||
| status | ENUM | NOT NULL | IN_PROGRESS, COMPLETED, TERMINATED |
|
||||
| context_data | JSON | NULL | ข้อมูลประกอบการตัดสินใจ (Variables) |
|
||||
| started_at | TIMESTAMP | DEFAULT NOW | เวลาที่เริ่ม |
|
||||
| completed_at | TIMESTAMP | NULL | เวลาที่จบ |
|
||||
| current_step_name | VARCHAR(100) | NOT NULL | ชื่อ Step ปัจจุบัน |
|
||||
| status | ENUM | NOT NULL | IN_PROGRESS, COMPLETED, TERMINATED |
|
||||
| context_data | JSON | NULL | ข้อมูลประกอบการตัดสินใจ (Variables) |
|
||||
| started_at | TIMESTAMP | DEFAULT NOW | เวลาที่เริ่ม |
|
||||
| completed_at | TIMESTAMP | NULL | เวลาที่จบ |
|
||||
|
||||
---
|
||||
|
||||
@@ -1581,15 +1583,15 @@
|
||||
|
||||
**Purpose**: เก็บประวัติการดำเนินการในแต่ละ Step (Audit Trail)
|
||||
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| :------------------- | :----------- | :----------- | :--------------------------------- |
|
||||
| id | BIGINT | PK, AI | Unique ID |
|
||||
| workflow_instance_id | BIGINT | FK, NOT NULL | อ้างอิง Instance |
|
||||
| step_name | VARCHAR(100) | NOT NULL | ชื่อ Step |
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| :------------------- | :----------- | :----------- | :---------------------------------- |
|
||||
| id | BIGINT | PK, AI | Unique ID |
|
||||
| workflow_instance_id | BIGINT | FK, NOT NULL | อ้างอิง Instance |
|
||||
| step_name | VARCHAR(100) | NOT NULL | ชื่อ Step |
|
||||
| action | VARCHAR(50) | NOT NULL | การกระทำ (APPROVE, REJECT, COMMENT) |
|
||||
| actor_id | INT | FK, NULL | User ที่กระทำ |
|
||||
| comments | TEXT | NULL | ความเห็นเพิ่มเติม |
|
||||
| performed_at | TIMESTAMP | DEFAULT NOW | เวลาที่กระทำ |
|
||||
| actor_id | INT | FK, NULL | User ที่กระทำ |
|
||||
| comments | TEXT | NULL | ความเห็นเพิ่มเติม |
|
||||
| performed_at | TIMESTAMP | DEFAULT NOW | เวลาที่กระทำ |
|
||||
|
||||
---
|
||||
|
||||
@@ -1602,9 +1604,9 @@
|
||||
| Column Name | Data Type | Constraints | Description |
|
||||
| :---------- | :---------- | :----------- | :------------------------------------- |
|
||||
| id | INT | PK, AI | Unique ID |
|
||||
| schema_code | VARCHAR(50) | UNIQUE | รหัส Schema (เช่น RFA_DETAILS_V1) |
|
||||
| schema_code | VARCHAR(50) | UNIQUE | รหัส Schema (เช่น RFA_DETAILS_V1) |
|
||||
| schema_body | JSON | NOT NULL | JSON Schema Draft 7/2020-12 definition |
|
||||
| description | TEXT | NULL | คำอธิบาย |
|
||||
| description | TEXT | NULL | คำอธิบาย |
|
||||
| is_active | BOOLEAN | DEFAULT TRUE | สถานะ |
|
||||
|
||||
---
|
||||
@@ -1636,6 +1638,7 @@
|
||||
- INDEX (entity_id)
|
||||
|
||||
**Partitioning**:
|
||||
|
||||
- **PARTITION BY RANGE (YEAR(created_at))**: แบ่ง Partition รายปี เพื่อประสิทธิภาพในการเก็บข้อมูลระยะยาว
|
||||
|
||||
---
|
||||
@@ -1663,6 +1666,7 @@
|
||||
- INDEX (created_at)
|
||||
|
||||
**Partitioning**:
|
||||
|
||||
- **PARTITION BY RANGE (YEAR(created_at))**: แบ่ง Partition รายปี
|
||||
|
||||
---
|
||||
@@ -1861,6 +1865,3 @@ LEFT JOIN rfa_approve_codes rac ON rr.rfa_approve_code_id = rac.id;
|
||||
---
|
||||
|
||||
**End of Data Dictionary V1.5.1**
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@
|
||||
## 1. Architecture Overview
|
||||
|
||||
### 1.1 Module Structure
|
||||
|
||||
```
|
||||
backend/src/modules/document-numbering/
|
||||
├── document-numbering.module.ts
|
||||
@@ -53,6 +54,7 @@ backend/src/modules/document-numbering/
|
||||
## 2. Core Entities
|
||||
|
||||
### 2.1 Numbering Configuration
|
||||
|
||||
```typescript
|
||||
// entities/numbering-config.entity.ts
|
||||
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
|
||||
@@ -71,7 +73,7 @@ export class NumberingConfig {
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: ['GLOBAL', 'PROJECT', 'CONTRACT', 'YEARLY', 'MONTHLY'],
|
||||
default: 'GLOBAL'
|
||||
default: 'GLOBAL',
|
||||
})
|
||||
scope: string;
|
||||
|
||||
@@ -90,16 +92,17 @@ export class NumberingConfig {
|
||||
@Column({
|
||||
type: 'timestamp',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
onUpdate: 'CURRENT_TIMESTAMP'
|
||||
onUpdate: 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
updated_at: Date;
|
||||
|
||||
@OneToMany(() => NumberingSequence, sequence => sequence.config)
|
||||
@OneToMany(() => NumberingSequence, (sequence) => sequence.config)
|
||||
sequences: NumberingSequence[];
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 Sequence Counter
|
||||
|
||||
```typescript
|
||||
// entities/numbering-sequence.entity.ts
|
||||
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, JoinColumn } from 'typeorm';
|
||||
@@ -134,6 +137,7 @@ export class NumberingSequence {
|
||||
```
|
||||
|
||||
### 2.3 Audit Log
|
||||
|
||||
```typescript
|
||||
// entities/numbering-audit-log.entity.ts
|
||||
import { Entity, Column, PrimaryGeneratedColumn, Index } from 'typeorm';
|
||||
@@ -184,6 +188,7 @@ export class NumberingAuditLog {
|
||||
## 3. Core Services
|
||||
|
||||
### 3.1 Sequence Service
|
||||
|
||||
```typescript
|
||||
// services/sequence.service.ts
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
@@ -206,16 +211,13 @@ export class SequenceService {
|
||||
|
||||
private dataSource: DataSource,
|
||||
private redlock: Redlock,
|
||||
private formatService: FormatService,
|
||||
private formatService: FormatService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get next sequence number with distributed locking
|
||||
*/
|
||||
async getNextSequence(
|
||||
documentType: string,
|
||||
scopeValue?: string,
|
||||
): Promise<string> {
|
||||
async getNextSequence(documentType: string, scopeValue?: string): Promise<string> {
|
||||
// 1. Get configuration
|
||||
const config = await this.getConfig(documentType);
|
||||
|
||||
@@ -238,11 +240,7 @@ export class SequenceService {
|
||||
/**
|
||||
* Get sequence with Redlock + Database pessimistic lock
|
||||
*/
|
||||
private async getSequenceWithRedlock(
|
||||
config: NumberingConfig,
|
||||
scopeValue: string,
|
||||
lockKey: string,
|
||||
): Promise<string> {
|
||||
private async getSequenceWithRedlock(config: NumberingConfig, scopeValue: string, lockKey: string): Promise<string> {
|
||||
// Acquire distributed lock
|
||||
const lock = await this.redlock.acquire([lockKey], 5000, {
|
||||
retryCount: 3,
|
||||
@@ -266,11 +264,7 @@ export class SequenceService {
|
||||
}
|
||||
|
||||
// Increment sequence
|
||||
const nextValue = await this.incrementSequence(
|
||||
manager,
|
||||
sequence,
|
||||
config,
|
||||
);
|
||||
const nextValue = await this.incrementSequence(manager, sequence, config);
|
||||
|
||||
// Format number
|
||||
return this.formatService.formatNumber(config.format, nextValue, {
|
||||
@@ -287,10 +281,7 @@ export class SequenceService {
|
||||
/**
|
||||
* Fallback: Database-only locking (no Redis)
|
||||
*/
|
||||
private async getSequenceWithDbLock(
|
||||
config: NumberingConfig,
|
||||
scopeValue: string,
|
||||
): Promise<string> {
|
||||
private async getSequenceWithDbLock(config: NumberingConfig, scopeValue: string): Promise<string> {
|
||||
return await this.dataSource.transaction(async (manager) => {
|
||||
let sequence = await manager.findOne(NumberingSequence, {
|
||||
where: {
|
||||
@@ -304,11 +295,7 @@ export class SequenceService {
|
||||
sequence = await this.createSequence(manager, config, scopeValue);
|
||||
}
|
||||
|
||||
const nextValue = await this.incrementSequence(
|
||||
manager,
|
||||
sequence,
|
||||
config,
|
||||
);
|
||||
const nextValue = await this.incrementSequence(manager, sequence, config);
|
||||
|
||||
return this.formatService.formatNumber(config.format, nextValue, {
|
||||
documentType: config.document_type,
|
||||
@@ -323,7 +310,7 @@ export class SequenceService {
|
||||
private async incrementSequence(
|
||||
manager: EntityManager,
|
||||
sequence: NumberingSequence,
|
||||
config: NumberingConfig,
|
||||
config: NumberingConfig
|
||||
): Promise<number> {
|
||||
let nextValue = sequence.current_value + 1;
|
||||
|
||||
@@ -335,9 +322,7 @@ export class SequenceService {
|
||||
|
||||
// Check max value
|
||||
if (nextValue > config.max_value) {
|
||||
throw new SequenceExhaustedError(
|
||||
`Sequence exhausted for ${config.document_type}. Max: ${config.max_value}`,
|
||||
);
|
||||
throw new SequenceExhaustedError(`Sequence exhausted for ${config.document_type}. Max: ${config.max_value}`);
|
||||
}
|
||||
|
||||
// Update sequence
|
||||
@@ -351,11 +336,7 @@ export class SequenceService {
|
||||
/**
|
||||
* Check if number is cancelled
|
||||
*/
|
||||
private async isCancelledNumber(
|
||||
manager: EntityManager,
|
||||
config: NumberingConfig,
|
||||
value: number,
|
||||
): Promise<boolean> {
|
||||
private async isCancelledNumber(manager: EntityManager, config: NumberingConfig, value: number): Promise<boolean> {
|
||||
const count = await manager.count(NumberingAuditLog, {
|
||||
where: {
|
||||
document_type: config.document_type,
|
||||
@@ -372,7 +353,7 @@ export class SequenceService {
|
||||
private async createSequence(
|
||||
manager: EntityManager,
|
||||
config: NumberingConfig,
|
||||
scopeValue: string,
|
||||
scopeValue: string
|
||||
): Promise<NumberingSequence> {
|
||||
const sequence = manager.create(NumberingSequence, {
|
||||
config_id: config.id,
|
||||
@@ -395,8 +376,7 @@ export class SequenceService {
|
||||
* Check if error is Redis unavailable
|
||||
*/
|
||||
private isRedisUnavailable(error: any): boolean {
|
||||
return error.message?.includes('Redis') ||
|
||||
error.message?.includes('ECONNREFUSED');
|
||||
return error.message?.includes('Redis') || error.message?.includes('ECONNREFUSED');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -409,9 +389,7 @@ export class SequenceService {
|
||||
});
|
||||
|
||||
if (!config) {
|
||||
throw new ConfigNotFoundError(
|
||||
`Numbering config not found for ${documentType}`,
|
||||
);
|
||||
throw new ConfigNotFoundError(`Numbering config not found for ${documentType}`);
|
||||
}
|
||||
|
||||
return config;
|
||||
@@ -422,6 +400,7 @@ export class SequenceService {
|
||||
---
|
||||
|
||||
### 3.2 Reservation Service
|
||||
|
||||
```typescript
|
||||
// services/reservation.service.ts
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
@@ -447,22 +426,15 @@ export class ReservationService {
|
||||
constructor(
|
||||
private redis: Redis,
|
||||
private sequenceService: SequenceService,
|
||||
private auditService: AuditService,
|
||||
private auditService: AuditService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Reserve a document number
|
||||
*/
|
||||
async reserve(
|
||||
documentType: string,
|
||||
scopeValue?: string,
|
||||
metadata?: Record<string, any>,
|
||||
): Promise<Reservation> {
|
||||
async reserve(documentType: string, scopeValue?: string, metadata?: Record<string, any>): Promise<Reservation> {
|
||||
// 1. Generate next number
|
||||
const documentNumber = await this.sequenceService.getNextSequence(
|
||||
documentType,
|
||||
scopeValue,
|
||||
);
|
||||
const documentNumber = await this.sequenceService.getNextSequence(documentType, scopeValue);
|
||||
|
||||
// 2. Generate reservation token
|
||||
const token = uuidv4();
|
||||
@@ -480,11 +452,7 @@ export class ReservationService {
|
||||
metadata,
|
||||
};
|
||||
|
||||
await this.redis.setex(
|
||||
`reservation:${token}`,
|
||||
this.TTL,
|
||||
JSON.stringify(reservation),
|
||||
);
|
||||
await this.redis.setex(`reservation:${token}`, this.TTL, JSON.stringify(reservation));
|
||||
|
||||
// 5. Audit log
|
||||
await this.auditService.log({
|
||||
@@ -507,9 +475,7 @@ export class ReservationService {
|
||||
const reservation = await this.getReservation(token);
|
||||
|
||||
if (!reservation) {
|
||||
throw new ReservationExpiredError(
|
||||
'Reservation not found or expired. Please reserve a new number.',
|
||||
);
|
||||
throw new ReservationExpiredError('Reservation not found or expired. Please reserve a new number.');
|
||||
}
|
||||
|
||||
// 2. Save to database (via document creation)
|
||||
@@ -584,6 +550,7 @@ export class ReservationService {
|
||||
---
|
||||
|
||||
### 3.3 Manual Override Service
|
||||
|
||||
```typescript
|
||||
// services/manual-override.service.ts
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
@@ -606,7 +573,7 @@ export class ManualOverrideService {
|
||||
|
||||
private dataSource: DataSource,
|
||||
private formatService: FormatService,
|
||||
private auditService: AuditService,
|
||||
private auditService: AuditService
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -617,7 +584,7 @@ export class ManualOverrideService {
|
||||
manualNumber: string,
|
||||
userId: number,
|
||||
reason: string,
|
||||
skipValidation = false,
|
||||
skipValidation = false
|
||||
): Promise<void> {
|
||||
// 1. Get configuration
|
||||
const config = await this.configRepo.findOne({
|
||||
@@ -629,9 +596,7 @@ export class ManualOverrideService {
|
||||
}
|
||||
|
||||
if (!config.allow_manual_override) {
|
||||
throw new ManualOverrideNotAllowedError(
|
||||
`Manual override not allowed for ${documentType}`,
|
||||
);
|
||||
throw new ManualOverrideNotAllowedError(`Manual override not allowed for ${documentType}`);
|
||||
}
|
||||
|
||||
// 2. Validate
|
||||
@@ -642,17 +607,11 @@ export class ManualOverrideService {
|
||||
// 3. Check duplicate
|
||||
const exists = await this.checkDuplicate(manualNumber);
|
||||
if (exists) {
|
||||
throw new DuplicateNumberError(
|
||||
`Number ${manualNumber} already exists`,
|
||||
);
|
||||
throw new DuplicateNumberError(`Number ${manualNumber} already exists`);
|
||||
}
|
||||
|
||||
// 4. Update sequence if higher
|
||||
await this.updateSequenceIfHigher(
|
||||
documentType,
|
||||
manualNumber,
|
||||
config,
|
||||
);
|
||||
await this.updateSequenceIfHigher(documentType, manualNumber, config);
|
||||
|
||||
// 5. Audit log
|
||||
await this.auditService.log({
|
||||
@@ -669,19 +628,11 @@ export class ManualOverrideService {
|
||||
/**
|
||||
* Validate manual number format
|
||||
*/
|
||||
private async validate(
|
||||
manualNumber: string,
|
||||
config: NumberingConfig,
|
||||
): Promise<void> {
|
||||
const isValid = this.formatService.matchesFormat(
|
||||
manualNumber,
|
||||
config.format,
|
||||
);
|
||||
private async validate(manualNumber: string, config: NumberingConfig): Promise<void> {
|
||||
const isValid = this.formatService.matchesFormat(manualNumber, config.format);
|
||||
|
||||
if (!isValid) {
|
||||
throw new InvalidFormatError(
|
||||
`Number ${manualNumber} does not match format ${config.format}`,
|
||||
);
|
||||
throw new InvalidFormatError(`Number ${manualNumber} does not match format ${config.format}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -701,7 +652,7 @@ export class ManualOverrideService {
|
||||
SELECT document_number FROM drawings WHERE document_number = ?
|
||||
) AS all_docs
|
||||
`,
|
||||
[number, number, number],
|
||||
[number, number, number]
|
||||
);
|
||||
|
||||
return count[0].count > 0;
|
||||
@@ -713,13 +664,10 @@ export class ManualOverrideService {
|
||||
private async updateSequenceIfHigher(
|
||||
documentType: string,
|
||||
manualNumber: string,
|
||||
config: NumberingConfig,
|
||||
config: NumberingConfig
|
||||
): Promise<void> {
|
||||
// Extract sequence value from manual number
|
||||
const sequenceValue = this.formatService.extractSequence(
|
||||
manualNumber,
|
||||
config.format,
|
||||
);
|
||||
const sequenceValue = this.formatService.extractSequence(manualNumber, config.format);
|
||||
|
||||
if (!sequenceValue) {
|
||||
this.logger.warn(`Could not extract sequence from ${manualNumber}`);
|
||||
@@ -738,9 +686,7 @@ export class ManualOverrideService {
|
||||
sequence.last_used_at = new Date();
|
||||
await manager.save(sequence);
|
||||
|
||||
this.logger.log(
|
||||
`Updated sequence for ${documentType} to ${sequenceValue}`,
|
||||
);
|
||||
this.logger.log(`Updated sequence for ${documentType} to ${sequenceValue}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -752,12 +698,10 @@ export class ManualOverrideService {
|
||||
## 4. Controllers
|
||||
|
||||
### 4.1 Main Numbering Controller
|
||||
|
||||
```typescript
|
||||
// controllers/numbering.controller.ts
|
||||
import {
|
||||
Controller, Post, Body, UseGuards,
|
||||
HttpCode, HttpStatus
|
||||
} from '@nestjs/common';
|
||||
import { Controller, Post, Body, UseGuards, HttpCode, HttpStatus } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { JwtAuthGuard } from '../../auth/guards';
|
||||
import { CurrentUser } from '../../common/decorators';
|
||||
@@ -769,22 +713,13 @@ import { ReserveNumberDto, ConfirmReservationDto } from '../dto';
|
||||
@Controller('document-numbering')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class NumberingController {
|
||||
constructor(
|
||||
private reservationService: ReservationService,
|
||||
) {}
|
||||
constructor(private reservationService: ReservationService) {}
|
||||
|
||||
@Post('reserve')
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
@ApiOperation({ summary: 'Reserve a document number' })
|
||||
async reserve(
|
||||
@Body() dto: ReserveNumberDto,
|
||||
@CurrentUser() user: any,
|
||||
) {
|
||||
const reservation = await this.reservationService.reserve(
|
||||
dto.document_type,
|
||||
dto.scope_value,
|
||||
dto.metadata,
|
||||
);
|
||||
async reserve(@Body() dto: ReserveNumberDto, @CurrentUser() user: any) {
|
||||
const reservation = await this.reservationService.reserve(dto.document_type, dto.scope_value, dto.metadata);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -795,14 +730,8 @@ export class NumberingController {
|
||||
@Post('confirm')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: 'Confirm a reservation' })
|
||||
async confirm(
|
||||
@Body() dto: ConfirmReservationDto,
|
||||
@CurrentUser() user: any,
|
||||
) {
|
||||
const documentNumber = await this.reservationService.confirm(
|
||||
dto.token,
|
||||
user.id,
|
||||
);
|
||||
async confirm(@Body() dto: ConfirmReservationDto, @CurrentUser() user: any) {
|
||||
const documentNumber = await this.reservationService.confirm(dto.token, user.id);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -813,10 +742,7 @@ export class NumberingController {
|
||||
@Post('cancel')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: 'Cancel a reservation' })
|
||||
async cancel(
|
||||
@Body() dto: ConfirmReservationDto,
|
||||
@CurrentUser() user: any,
|
||||
) {
|
||||
async cancel(@Body() dto: ConfirmReservationDto, @CurrentUser() user: any) {
|
||||
await this.reservationService.cancel(dto.token, user.id);
|
||||
|
||||
return {
|
||||
@@ -832,41 +758,37 @@ export class NumberingController {
|
||||
## 5. Integration with Document Creation
|
||||
|
||||
### 5.1 Correspondence Example
|
||||
|
||||
```typescript
|
||||
// modules/correspondence/services/correspondence.service.ts
|
||||
@Injectable()
|
||||
export class CorrespondenceService {
|
||||
constructor(
|
||||
private reservationService: ReservationService,
|
||||
private dataSource: DataSource,
|
||||
private dataSource: DataSource
|
||||
) {}
|
||||
|
||||
async create(dto: CreateCorrespondenceDto, userId: number) {
|
||||
// Phase 1: Reserve number
|
||||
const { token, document_number } = await this.reservationService.reserve(
|
||||
'COR',
|
||||
dto.project_id.toString(),
|
||||
);
|
||||
const { token, document_number } = await this.reservationService.reserve('COR', dto.project_id.toString());
|
||||
|
||||
try {
|
||||
// Phase 2: Create document in transaction
|
||||
const correspondence = await this.dataSource.transaction(
|
||||
async (manager) => {
|
||||
// Create correspondence
|
||||
const corr = manager.create(Correspondence, {
|
||||
document_number,
|
||||
...dto,
|
||||
created_by: userId,
|
||||
});
|
||||
const correspondence = await this.dataSource.transaction(async (manager) => {
|
||||
// Create correspondence
|
||||
const corr = manager.create(Correspondence, {
|
||||
document_number,
|
||||
...dto,
|
||||
created_by: userId,
|
||||
});
|
||||
|
||||
await manager.save(corr);
|
||||
await manager.save(corr);
|
||||
|
||||
// Confirm reservation
|
||||
await this.reservationService.confirm(token, userId);
|
||||
// Confirm reservation
|
||||
await this.reservationService.confirm(token, userId);
|
||||
|
||||
return corr;
|
||||
},
|
||||
);
|
||||
return corr;
|
||||
});
|
||||
|
||||
return correspondence;
|
||||
} catch (error) {
|
||||
@@ -883,6 +805,7 @@ export class CorrespondenceService {
|
||||
## 6. Testing
|
||||
|
||||
### 6.1 Unit Tests
|
||||
|
||||
```typescript
|
||||
// tests/unit/sequence.service.spec.ts
|
||||
describe('SequenceService', () => {
|
||||
@@ -927,14 +850,13 @@ describe('SequenceService', () => {
|
||||
it('should throw on sequence exhausted', async () => {
|
||||
await setSequence('COR', 999999); // Max value
|
||||
|
||||
await expect(
|
||||
service.getNextSequence('COR')
|
||||
).rejects.toThrow(SequenceExhaustedError);
|
||||
await expect(service.getNextSequence('COR')).rejects.toThrow(SequenceExhaustedError);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 6.2 Integration Tests
|
||||
|
||||
```typescript
|
||||
// tests/integration/reservation.spec.ts
|
||||
describe('Reservation Flow (Integration)', () => {
|
||||
@@ -992,27 +914,26 @@ describe('Reservation Flow (Integration)', () => {
|
||||
```
|
||||
|
||||
### 6.3 Load Tests
|
||||
|
||||
```typescript
|
||||
// tests/load/concurrency.spec.ts
|
||||
describe('Concurrency Test', () => {
|
||||
it('should handle 1000 concurrent requests without duplicates', async () => {
|
||||
const promises = Array.from({ length: 1000 }, (_, i) =>
|
||||
request(app.getHttpServer())
|
||||
.post('/document-numbering/reserve')
|
||||
.send({ document_type: 'COR' })
|
||||
request(app.getHttpServer()).post('/document-numbering/reserve').send({ document_type: 'COR' })
|
||||
);
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
// Extract all document numbers
|
||||
const numbers = results.map(r => r.body.data.document_number);
|
||||
const numbers = results.map((r) => r.body.data.document_number);
|
||||
|
||||
// Check for duplicates
|
||||
const uniqueNumbers = new Set(numbers);
|
||||
expect(uniqueNumbers.size).toBe(1000);
|
||||
|
||||
// Verify sequential
|
||||
const sequences = numbers.map(n => extractSeq(n)).sort((a, b) => a - b);
|
||||
const sequences = numbers.map((n) => extractSeq(n)).sort((a, b) => a - b);
|
||||
expect(sequences[0]).toBe(1);
|
||||
expect(sequences[999]).toBe(1000);
|
||||
});
|
||||
@@ -1024,6 +945,7 @@ describe('Concurrency Test', () => {
|
||||
## 7. Deployment Checklist
|
||||
|
||||
### 7.1 Pre-Deployment
|
||||
|
||||
- [ ] Run all tests (unit, integration, E2E)
|
||||
- [ ] Load test (1000 req/s for 5 min)
|
||||
- [ ] Setup Redis cluster (3 nodes)
|
||||
@@ -1036,6 +958,7 @@ describe('Concurrency Test', () => {
|
||||
- [ ] Document rollback procedure
|
||||
|
||||
### 7.2 Deployment Steps
|
||||
|
||||
1. Deploy Redis cluster to staging
|
||||
2. Run migrations on staging database
|
||||
3. Deploy backend service to staging
|
||||
@@ -1047,6 +970,7 @@ describe('Concurrency Test', () => {
|
||||
9. Gradual rollout (10% → 50% → 100%)
|
||||
|
||||
### 7.3 Post-Deployment
|
||||
|
||||
- [ ] Verify all metrics green
|
||||
- [ ] Check error rates (<0.1%)
|
||||
- [ ] Validate audit logs working
|
||||
@@ -1060,6 +984,7 @@ describe('Concurrency Test', () => {
|
||||
## 8. Monitoring & Observability
|
||||
|
||||
### 8.1 Prometheus Metrics
|
||||
|
||||
```typescript
|
||||
// metrics/numbering.metrics.ts
|
||||
import { Injectable } from '@nestjs/common';
|
||||
@@ -1137,6 +1062,7 @@ export class NumberingMetrics {
|
||||
```
|
||||
|
||||
### 8.2 Grafana Dashboard
|
||||
|
||||
```json
|
||||
{
|
||||
"dashboard": {
|
||||
@@ -1181,6 +1107,7 @@ export class NumberingMetrics {
|
||||
```
|
||||
|
||||
### 8.3 Alert Rules
|
||||
|
||||
```yaml
|
||||
# alerts/numbering.yml
|
||||
groups:
|
||||
@@ -1194,8 +1121,8 @@ groups:
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Sequence {{ $labels.document_type }} >95% used"
|
||||
description: "Current: {{ $value }}%. Extend max_value immediately."
|
||||
summary: 'Sequence {{ $labels.document_type }} >95% used'
|
||||
description: 'Current: {{ $value }}%. Extend max_value immediately.'
|
||||
|
||||
# Warning: Sequence >90% used
|
||||
- alert: SequenceWarning
|
||||
@@ -1204,8 +1131,8 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Sequence {{ $labels.document_type }} >90% used"
|
||||
description: "Current: {{ $value }}%. Plan to extend max_value."
|
||||
summary: 'Sequence {{ $labels.document_type }} >90% used'
|
||||
description: 'Current: {{ $value }}%. Plan to extend max_value.'
|
||||
|
||||
# Critical: High lock wait time
|
||||
- alert: HighLockWaitTime
|
||||
@@ -1214,8 +1141,8 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Lock wait time >1s (p95)"
|
||||
description: "p95: {{ $value }}s. Check Redis cluster health."
|
||||
summary: 'Lock wait time >1s (p95)'
|
||||
description: 'p95: {{ $value }}s. Check Redis cluster health.'
|
||||
|
||||
# Critical: Redis down
|
||||
- alert: RedisUnavailable
|
||||
@@ -1224,8 +1151,8 @@ groups:
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Redis cluster unavailable"
|
||||
description: "Numbering system using DB-only fallback mode."
|
||||
summary: 'Redis cluster unavailable'
|
||||
description: 'Numbering system using DB-only fallback mode.'
|
||||
|
||||
# Warning: High error rate
|
||||
- alert: HighErrorRate
|
||||
@@ -1234,8 +1161,8 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "High error rate in numbering system"
|
||||
description: "{{ $value }} errors/sec. Check logs."
|
||||
summary: 'High error rate in numbering system'
|
||||
description: '{{ $value }} errors/sec. Check logs.'
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1245,9 +1172,11 @@ groups:
|
||||
### 9.1 Common Issues
|
||||
|
||||
#### Issue 1: Duplicate Numbers Generated
|
||||
|
||||
**Symptoms**: Same document number appears twice
|
||||
|
||||
**Diagnosis**:
|
||||
|
||||
```sql
|
||||
-- Find duplicates
|
||||
SELECT document_number, COUNT(*) as count
|
||||
@@ -1263,11 +1192,13 @@ HAVING count > 1;
|
||||
```
|
||||
|
||||
**Root Causes**:
|
||||
|
||||
- Redis cluster failure during generation
|
||||
- Database deadlock
|
||||
- Bug in locking logic
|
||||
|
||||
**Resolution**:
|
||||
|
||||
1. Identify affected documents
|
||||
2. Manually reassign one with new number
|
||||
3. Update audit log
|
||||
@@ -1277,9 +1208,11 @@ HAVING count > 1;
|
||||
---
|
||||
|
||||
#### Issue 2: Sequence Exhausted
|
||||
|
||||
**Symptoms**: Error "Sequence exhausted for COR"
|
||||
|
||||
**Diagnosis**:
|
||||
|
||||
```sql
|
||||
-- Check current vs max
|
||||
SELECT
|
||||
@@ -1293,6 +1226,7 @@ WHERE s.current_value >= c.max_value * 0.9;
|
||||
```
|
||||
|
||||
**Resolution**:
|
||||
|
||||
```sql
|
||||
-- Extend max_value
|
||||
UPDATE document_numbering_configs
|
||||
@@ -1312,9 +1246,11 @@ WHERE config_id = (
|
||||
---
|
||||
|
||||
#### Issue 3: Lock Timeout
|
||||
|
||||
**Symptoms**: "Failed to acquire lock after 3 retries"
|
||||
|
||||
**Diagnosis**:
|
||||
|
||||
```bash
|
||||
# Check Redis cluster health
|
||||
redis-cli --cluster check localhost:7000
|
||||
@@ -1325,12 +1261,14 @@ redis-cli GET "numbering:COR:project-1"
|
||||
```
|
||||
|
||||
**Root Causes**:
|
||||
|
||||
- High concurrent load
|
||||
- Redis node down
|
||||
- Network latency
|
||||
- Deadlock in database
|
||||
|
||||
**Resolution**:
|
||||
|
||||
1. Check Redis cluster health
|
||||
2. Increase lock timeout (5s → 10s)
|
||||
3. Add more Redis nodes
|
||||
@@ -1340,9 +1278,11 @@ redis-cli GET "numbering:COR:project-1"
|
||||
---
|
||||
|
||||
#### Issue 4: Reservation Expired
|
||||
|
||||
**Symptoms**: User gets "Reservation expired" error
|
||||
|
||||
**Diagnosis**:
|
||||
|
||||
```bash
|
||||
# Check Redis TTL
|
||||
redis-cli TTL "reservation:uuid-here"
|
||||
@@ -1352,11 +1292,13 @@ redis-cli KEYS "reservation:*"
|
||||
```
|
||||
|
||||
**Root Causes**:
|
||||
|
||||
- User took >5 minutes to complete form
|
||||
- Network issue during confirmation
|
||||
- Browser closed/refreshed
|
||||
|
||||
**Resolution**:
|
||||
|
||||
1. Reserve new number
|
||||
2. Consider increasing TTL (5 min → 10 min)
|
||||
3. Add progress auto-save
|
||||
@@ -1398,6 +1340,7 @@ npm run cli numbering:check-duplicates
|
||||
## 10. Performance Optimization
|
||||
|
||||
### 10.1 Database Indexes
|
||||
|
||||
```sql
|
||||
-- Composite index for faster lookups
|
||||
CREATE INDEX idx_sequence_lookup
|
||||
@@ -1420,6 +1363,7 @@ ON drawings(document_number);
|
||||
```
|
||||
|
||||
### 10.2 Connection Pooling
|
||||
|
||||
```typescript
|
||||
// config/database.config.ts
|
||||
export default {
|
||||
@@ -1430,17 +1374,18 @@ export default {
|
||||
|
||||
// Connection pool settings
|
||||
extra: {
|
||||
connectionLimit: 20, // Max connections
|
||||
queueLimit: 0, // Unlimited queue
|
||||
connectionLimit: 20, // Max connections
|
||||
queueLimit: 0, // Unlimited queue
|
||||
waitForConnections: true,
|
||||
acquireTimeout: 30000, // 30s timeout
|
||||
idleTimeout: 10000, // 10s idle timeout
|
||||
maxIdle: 5, // Max idle connections
|
||||
acquireTimeout: 30000, // 30s timeout
|
||||
idleTimeout: 10000, // 10s idle timeout
|
||||
maxIdle: 5, // Max idle connections
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 10.3 Redis Optimization
|
||||
|
||||
```typescript
|
||||
// config/redis.config.ts
|
||||
export default {
|
||||
@@ -1469,6 +1414,7 @@ export default {
|
||||
```
|
||||
|
||||
### 10.4 Caching Strategy
|
||||
|
||||
```typescript
|
||||
// Cache configuration
|
||||
@Cacheable({
|
||||
@@ -1499,6 +1445,7 @@ async updateConfig(documentType: string, data: any) {
|
||||
## 11. Security Considerations
|
||||
|
||||
### 11.1 Access Control
|
||||
|
||||
```typescript
|
||||
// guards/manual-override.guard.ts
|
||||
@Injectable()
|
||||
@@ -1525,6 +1472,7 @@ async manualOverride(@Body() dto: ManualOverrideDto) {
|
||||
```
|
||||
|
||||
### 11.2 Rate Limiting
|
||||
|
||||
```typescript
|
||||
// Apply rate limit to prevent abuse
|
||||
@Throttle(100, 60) // 100 requests per minute
|
||||
@@ -1535,6 +1483,7 @@ async reserve(@Body() dto: ReserveNumberDto) {
|
||||
```
|
||||
|
||||
### 11.3 Audit Logging
|
||||
|
||||
```typescript
|
||||
// decorators/audit-numbering.decorator.ts
|
||||
export function AuditNumbering(operation: string) {
|
||||
@@ -1575,6 +1524,7 @@ async manualOverride(dto: ManualOverrideDto, user: User) {
|
||||
## 12. Migration Scripts
|
||||
|
||||
### 12.1 Import Legacy Documents
|
||||
|
||||
```typescript
|
||||
// scripts/import-legacy-numbers.ts
|
||||
import { DataSource } from 'typeorm';
|
||||
@@ -1638,14 +1588,17 @@ async function updateSequenceCounters(dataSource: DataSource) {
|
||||
|
||||
const maxSeq = result[0].max_seq;
|
||||
|
||||
await dataSource.query(`
|
||||
await dataSource.query(
|
||||
`
|
||||
UPDATE document_numbering_sequences
|
||||
SET current_value = ?
|
||||
WHERE config_id = (
|
||||
SELECT id FROM document_numbering_configs
|
||||
WHERE document_type = 'COR'
|
||||
)
|
||||
`, [maxSeq]);
|
||||
`,
|
||||
[maxSeq]
|
||||
);
|
||||
|
||||
console.log(`Updated COR sequence to ${maxSeq}`);
|
||||
}
|
||||
@@ -1658,6 +1611,7 @@ importLegacyNumbers().catch(console.error);
|
||||
## 13. CLI Tools
|
||||
|
||||
### 13.1 Status Command
|
||||
|
||||
```typescript
|
||||
// cli/commands/numbering-status.command.ts
|
||||
import { Command, CommandRunner } from 'nest-commander';
|
||||
@@ -1691,7 +1645,7 @@ export class NumberingStatusCommand extends CommandRunner {
|
||||
console.log(`Format: ${config.format}`);
|
||||
console.log(`Current Value: ${sequence.current_value}`);
|
||||
console.log(`Max Value: ${config.max_value}`);
|
||||
console.log(`Utilization: ${(sequence.current_value / config.max_value * 100).toFixed(2)}%`);
|
||||
console.log(`Utilization: ${((sequence.current_value / config.max_value) * 100).toFixed(2)}%`);
|
||||
console.log(`Last Used: ${sequence.last_used_at}`);
|
||||
console.log(`Manual Override: ${config.allow_manual_override ? 'Yes' : 'No'}`);
|
||||
}
|
||||
@@ -1705,9 +1659,9 @@ export class NumberingStatusCommand extends CommandRunner {
|
||||
Type: c.document_type,
|
||||
Current: c.sequence?.current_value || 0,
|
||||
Max: c.max_value,
|
||||
'Utilization (%)': ((c.sequence?.current_value || 0) / c.max_value * 100).toFixed(2),
|
||||
'Utilization (%)': (((c.sequence?.current_value || 0) / c.max_value) * 100).toFixed(2),
|
||||
'Last Used': c.sequence?.last_used_at || 'Never',
|
||||
})),
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1718,6 +1672,7 @@ export class NumberingStatusCommand extends CommandRunner {
|
||||
## 14. Best Practices Summary
|
||||
|
||||
### 14.1 DO's ✅
|
||||
|
||||
- ✅ Always use two-phase commit (reserve + confirm)
|
||||
- ✅ Implement fallback to DB-only if Redis fails
|
||||
- ✅ Log every operation to audit trail
|
||||
@@ -1730,6 +1685,7 @@ export class NumberingStatusCommand extends CommandRunner {
|
||||
- ✅ Implement exponential backoff on retry
|
||||
|
||||
### 14.2 DON'Ts ❌
|
||||
|
||||
- ❌ Never skip validation for manual override
|
||||
- ❌ Never reuse cancelled numbers
|
||||
- ❌ Never trust client-generated numbers
|
||||
@@ -1746,6 +1702,7 @@ export class NumberingStatusCommand extends CommandRunner {
|
||||
## 15. Appendix
|
||||
|
||||
### 15.1 Error Codes
|
||||
|
||||
```typescript
|
||||
export enum NumberingErrorCode {
|
||||
CONFIG_NOT_FOUND = 'NB001',
|
||||
@@ -1760,6 +1717,7 @@ export enum NumberingErrorCode {
|
||||
```
|
||||
|
||||
### 15.2 Environment Variables
|
||||
|
||||
```bash
|
||||
# Redis Configuration
|
||||
REDIS_HOST=localhost
|
||||
@@ -1778,6 +1736,7 @@ GRAFANA_PORT=3000
|
||||
```
|
||||
|
||||
### 15.3 Useful Queries
|
||||
|
||||
```sql
|
||||
-- Find next available number
|
||||
SELECT MAX(CAST(SUBSTRING_INDEX(document_number, '-', -1) AS UNSIGNED)) + 1
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Document Numbering Implementation Guide
|
||||
|
||||
---
|
||||
|
||||
title: 'Implementation Guide: Document Numbering System'
|
||||
version: 1.6.1
|
||||
status: implemented
|
||||
@@ -171,45 +172,52 @@ src/modules/document-numbering/
|
||||
### 2.2. Number Generation Process
|
||||
|
||||
#### 2.2.1. Resolve Format Template:
|
||||
* Query document_number_formats by project_id + type_id.
|
||||
* If no result, query by project_id + NULL (Default Project Format).
|
||||
* If still no result, apply System Default Template: `{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR:BE}`.
|
||||
* Determine resetSequenceYearly flag from the found format (default: true)
|
||||
|
||||
- Query document_number_formats by project_id + type_id.
|
||||
- If no result, query by project_id + NULL (Default Project Format).
|
||||
- If still no result, apply System Default Template: `{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR:BE}`.
|
||||
- Determine resetSequenceYearly flag from the found format (default: true)
|
||||
|
||||
#### 2.2.2. Determine Counter Key:
|
||||
* If resetSequenceYearly is True: Use Current Year (e.g., 2025).
|
||||
* If resetSequenceYearly is False: Use 0 (Continuous).
|
||||
* Use type_id from the resolved format (Specific ID or NULL).
|
||||
|
||||
- If resetSequenceYearly is True: Use Current Year (e.g., 2025).
|
||||
- If resetSequenceYearly is False: Use 0 (Continuous).
|
||||
- Use type_id from the resolved format (Specific ID or NULL).
|
||||
|
||||
#### 2.2.3. Generate Number:
|
||||
* Use format template to generate number.
|
||||
* Replace tokens with actual values:
|
||||
* {PROJECT} -> Project Code
|
||||
* {ORG} -> Originator Organization Code
|
||||
* {RECIPIENT} -> Recipient Organization Code
|
||||
* {TYPE} -> Type Code
|
||||
* {YEAR} -> Current Year
|
||||
* {SEQ} -> Sequence Number
|
||||
* {REV} -> Revision Number
|
||||
|
||||
- Use format template to generate number.
|
||||
- Replace tokens with actual values:
|
||||
- {PROJECT} -> Project Code
|
||||
- {ORG} -> Originator Organization Code
|
||||
- {RECIPIENT} -> Recipient Organization Code
|
||||
- {TYPE} -> Type Code
|
||||
- {YEAR} -> Current Year
|
||||
- {SEQ} -> Sequence Number
|
||||
- {REV} -> Revision Number
|
||||
|
||||
#### 2.2.4. Validate Number:
|
||||
* Check if generated number is unique.
|
||||
* If not unique, increment sequence and retry.
|
||||
|
||||
- Check if generated number is unique.
|
||||
- If not unique, increment sequence and retry.
|
||||
|
||||
#### 2.2.5. Update Counter:
|
||||
* Update document_number_counters with new sequence.
|
||||
|
||||
- Update document_number_counters with new sequence.
|
||||
|
||||
#### 2.2.6. Generate Audit Record:
|
||||
* Create audit record with:
|
||||
* Generated number
|
||||
* Counter key used
|
||||
* Template used
|
||||
* User ID
|
||||
* IP Address
|
||||
* User Agent
|
||||
|
||||
- Create audit record with:
|
||||
- Generated number
|
||||
- Counter key used
|
||||
- Template used
|
||||
- User ID
|
||||
- IP Address
|
||||
- User Agent
|
||||
|
||||
#### 2.2.7. Return Generated Number:
|
||||
* Return generated number to caller.
|
||||
|
||||
- Return generated number to caller.
|
||||
|
||||
### 2.3. TypeORM Entity
|
||||
|
||||
@@ -309,9 +317,11 @@ export class DocumentNumberingLockService {
|
||||
}
|
||||
|
||||
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}`;
|
||||
return (
|
||||
`lock:docnum:${key.projectId}:${key.originatorOrgId}:` +
|
||||
`${key.recipientOrgId ?? 0}:${key.correspondenceTypeId}:` +
|
||||
`${key.subTypeId}:${key.rfaTypeId}:${key.disciplineId}:${key.year}`
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -333,7 +343,7 @@ export class CounterService {
|
||||
constructor(
|
||||
@InjectRepository(DocumentNumberCounter)
|
||||
private counterRepo: Repository<DocumentNumberCounter>,
|
||||
private dataSource: DataSource,
|
||||
private dataSource: DataSource
|
||||
) {}
|
||||
|
||||
async incrementCounter(counterKey: CounterKey): Promise<number> {
|
||||
@@ -364,9 +374,7 @@ export class CounterService {
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof OptimisticLockVersionMismatchError) {
|
||||
this.logger.warn(
|
||||
`Version conflict, retry ${attempt + 1}/${MAX_RETRIES}`,
|
||||
);
|
||||
this.logger.warn(`Version conflict, retry ${attempt + 1}/${MAX_RETRIES}`);
|
||||
if (attempt === MAX_RETRIES - 1) {
|
||||
throw new ConflictException('เลขที่เอกสารถูกเปลี่ยน กรุณาลองใหม่');
|
||||
}
|
||||
@@ -409,7 +417,7 @@ export class DocumentNumberingService {
|
||||
constructor(
|
||||
private lockService: DocumentNumberingLockService,
|
||||
private counterService: CounterService,
|
||||
private auditService: AuditService,
|
||||
private auditService: AuditService
|
||||
) {}
|
||||
|
||||
async generateDocumentNumber(dto: GenerateNumberDto): Promise<string> {
|
||||
@@ -464,7 +472,7 @@ export class DocumentNumberingService {
|
||||
return template
|
||||
.replace('{SEQ:4}', seq.toString().padStart(4, '0'))
|
||||
.replace('{YEAR:B.E.}', (key.year + 543).toString());
|
||||
// ... more replacements
|
||||
// ... more replacements
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -483,8 +491,16 @@ interface ValidationResult {
|
||||
@Injectable()
|
||||
export class TemplateValidator {
|
||||
private readonly ALLOWED_TOKENS = [
|
||||
'PROJECT', 'ORIGINATOR', 'RECIPIENT', 'CORR_TYPE',
|
||||
'SUB_TYPE', 'RFA_TYPE', 'DISCIPLINE', 'SEQ', 'YEAR', 'REV',
|
||||
'PROJECT',
|
||||
'ORIGINATOR',
|
||||
'RECIPIENT',
|
||||
'CORR_TYPE',
|
||||
'SUB_TYPE',
|
||||
'RFA_TYPE',
|
||||
'DISCIPLINE',
|
||||
'SEQ',
|
||||
'YEAR',
|
||||
'REV',
|
||||
];
|
||||
|
||||
validate(template: string, correspondenceType: string): ValidationResult {
|
||||
@@ -574,10 +590,7 @@ export class CounterResetJob extends WorkerHost {
|
||||
|
||||
```typescript
|
||||
// File: src/modules/document-numbering/document-numbering.controller.ts
|
||||
import {
|
||||
Controller, Get, Post, Patch,
|
||||
Body, Param, Query, UseGuards, ParseIntPipe,
|
||||
} from '@nestjs/common';
|
||||
import { Controller, Get, Post, Patch, Body, Param, Query, UseGuards, ParseIntPipe } from '@nestjs/common';
|
||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||
import { RbacGuard } from '../../common/guards/rbac.guard';
|
||||
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
|
||||
@@ -613,10 +626,7 @@ export class DocumentNumberingController {
|
||||
|
||||
@Patch('counters/:id')
|
||||
@RequirePermission('system.manage_settings')
|
||||
async updateCounter(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body('sequence') sequence: number
|
||||
) {
|
||||
async updateCounter(@Param('id', ParseIntPipe) id: number, @Body('sequence') sequence: number) {
|
||||
return this.numberingService.setCounterValue(id, sequence);
|
||||
}
|
||||
|
||||
@@ -634,10 +644,7 @@ export class DocumentNumberingController {
|
||||
|
||||
```typescript
|
||||
// File: src/modules/document-numbering/document-numbering-admin.controller.ts
|
||||
import {
|
||||
Controller, Get, Post, Delete, Body, Param, Query,
|
||||
UseGuards, ParseIntPipe,
|
||||
} from '@nestjs/common';
|
||||
import { Controller, Get, Post, Delete, Body, Param, Query, UseGuards, ParseIntPipe } from '@nestjs/common';
|
||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||
import { RbacGuard } from '../../common/guards/rbac.guard';
|
||||
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
|
||||
@@ -686,30 +693,21 @@ export class DocumentNumberingAdminController {
|
||||
|
||||
@Post('manual-override')
|
||||
@RequirePermission('system.manage_settings')
|
||||
async manualOverride(@Body() dto: {
|
||||
projectId: number;
|
||||
correspondenceTypeId: number | null;
|
||||
year: number;
|
||||
newValue: number;
|
||||
}) {
|
||||
async manualOverride(
|
||||
@Body() dto: { projectId: number; correspondenceTypeId: number | null; year: number; newValue: number }
|
||||
) {
|
||||
return this.service.manualOverride(dto);
|
||||
}
|
||||
|
||||
@Post('void-and-replace')
|
||||
@RequirePermission('system.manage_settings')
|
||||
async voidAndReplace(@Body() dto: {
|
||||
documentId: number;
|
||||
reason: string;
|
||||
}) {
|
||||
async voidAndReplace(@Body() dto: { documentId: number; reason: string }) {
|
||||
return this.service.voidAndReplace(dto);
|
||||
}
|
||||
|
||||
@Post('cancel')
|
||||
@RequirePermission('system.manage_settings')
|
||||
async cancelNumber(@Body() dto: {
|
||||
documentNumber: string;
|
||||
reason: string;
|
||||
}) {
|
||||
async cancelNumber(@Body() dto: { documentNumber: string; reason: string }) {
|
||||
return this.service.cancelNumber(dto);
|
||||
}
|
||||
|
||||
@@ -760,11 +758,7 @@ import { DocumentNumberingController } from './controllers/document-numbering.co
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
DocumentNumberCounter,
|
||||
DocumentNumberAudit,
|
||||
DocumentNumberError,
|
||||
]),
|
||||
TypeOrmModule.forFeature([DocumentNumberCounter, DocumentNumberAudit, DocumentNumberError]),
|
||||
BullModule.registerQueue({
|
||||
name: 'document-numbering',
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user