251217:1704 Docunment Number: Update to 1.6.2
This commit is contained in:
682
docs/backup/03.11-document-numbering-add.md
Normal file
682
docs/backup/03.11-document-numbering-add.md
Normal file
@@ -0,0 +1,682 @@
|
||||
# Document Numbering Requirements
|
||||
|
||||
**Version**: 1.6.1
|
||||
**Last Updated**: 2025-01-16
|
||||
**Status**: draft
|
||||
**Related ADRs**: ADR-018-document-numbering-strategy
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
### 1.1 Purpose
|
||||
ระบบ Document Numbering สำหรับสร้างเลขที่เอกสารอัตโนมัติที่มีความเป็นเอกลักษณ์ (unique) และสามารถติดตามได้ (traceable) สำหรับเอกสารทุกประเภทในระบบ LCBP3-DMS
|
||||
|
||||
### 1.2 Scope
|
||||
- Auto-generation ของเลขที่เอกสารตามรูปแบบที่กำหนด
|
||||
- Manual override สำหรับการ import เอกสารเก่า
|
||||
- Cancelled number handling (ไม่ reuse)
|
||||
- Void & Replace pattern สำหรับการแทนที่เอกสาร
|
||||
- Distributed locking เพื่อป้องกัน race condition
|
||||
- Complete audit trail สำหรับทุก operation
|
||||
|
||||
### 1.3 Document Types Supported
|
||||
- Correspondences (COR)
|
||||
- Request for Approvals (RFA)
|
||||
- Contract Drawings (CD)
|
||||
- Shop Drawings (SD)
|
||||
- Transmittals (TRN)
|
||||
- Circulation Sheets (CIR)
|
||||
|
||||
---
|
||||
|
||||
## 2. Functional Requirements
|
||||
|
||||
### 2.1 Auto Number Generation
|
||||
|
||||
#### FR-DN-001: Generate Sequential Number
|
||||
**Priority**: CRITICAL
|
||||
**Status**: Required
|
||||
|
||||
**Description**:
|
||||
ระบบต้องสามารถสร้างเลขที่เอกสารอัตโนมัติตามลำดับ (sequential) โดยไม่ซ้ำกัน
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- เลขที่เอกสารต้องเป็น unique ใน scope ที่กำหนด
|
||||
- ต้องเพิ่มขึ้นทีละ 1 (increment by 1)
|
||||
- ต้องรองรับ concurrent requests โดยไม่มีเลขที่ซ้ำ
|
||||
- Response time < 100ms (p95)
|
||||
|
||||
**Example**:
|
||||
```
|
||||
COR-00001-2025
|
||||
COR-00002-2025
|
||||
COR-00003-2025
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### FR-DN-002: Configurable Number Format
|
||||
**Priority**: HIGH
|
||||
**Status**: Required
|
||||
|
||||
**Description**:
|
||||
ระบบต้องรองรับการกำหนดรูปแบบเลขที่เอกสารที่หลากหลาย
|
||||
|
||||
**Format Tokens**:
|
||||
- `{PREFIX}` - คำนำหน้าตามประเภทเอกสาร (e.g., COR, RFA)
|
||||
- `{YYYY}` - ปี 4 หลัก (e.g., 2025)
|
||||
- `{YY}` - ปี 2 หลัก (e.g., 25)
|
||||
- `{MM}` - เดือน 2 หลัก (e.g., 01-12)
|
||||
- `{SEQ:n}` - sequence number ความยาว n หลัก (e.g., {SEQ:5} = 00001)
|
||||
- `{PROJECT}` - รหัสโครงการ
|
||||
- `{CONTRACT}` - รหัสสัญญา
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- รองรับ format tokens ที่ระบุ
|
||||
- Admin สามารถกำหนด format ผ่าน UI ได้
|
||||
- Validate format ก่อน save
|
||||
- แสดง preview ของเลขที่ที่จะถูกสร้าง
|
||||
|
||||
**Examples**:
|
||||
```typescript
|
||||
// Correspondence format
|
||||
"COR-{YYYY}-{SEQ:5}"
|
||||
→ COR-2025-00001
|
||||
|
||||
// RFA format with project
|
||||
"RFA-{PROJECT}-{YYYY}{MM}-{SEQ:4}"
|
||||
→ RFA-LCBP3-202501-0001
|
||||
|
||||
// Drawing format
|
||||
"{CONTRACT}-CD-{SEQ:6}"
|
||||
→ C001-CD-000001
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### FR-DN-003: Scope-based Sequences
|
||||
**Priority**: HIGH
|
||||
**Status**: Required
|
||||
|
||||
**Description**:
|
||||
ระบบต้องรองรับการสร้าง sequence ที่แยกตาม scope ที่ต่างกัน
|
||||
|
||||
**Scopes**:
|
||||
1. **Global**: Sequence ระดับระบบทั้งหมด
|
||||
2. **Project**: Sequence แยกตามโครงการ
|
||||
3. **Contract**: Sequence แยกตามสัญญา
|
||||
4. **Yearly**: Sequence reset ทุกปี
|
||||
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
|
||||
|
||||
Yearly Reset:
|
||||
COR-2024-00999 (Dec 2024)
|
||||
COR-2025-00001 (Jan 2025)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Manual Override
|
||||
|
||||
#### FR-DN-004: Manual Number Assignment
|
||||
**Priority**: HIGH
|
||||
**Status**: Required
|
||||
|
||||
**Description**:
|
||||
ระบบต้องรองรับการกำหนดเลขที่เอกสารด้วยตนเอง (manual override)
|
||||
|
||||
**Use Cases**:
|
||||
1. Import เอกสารเก่าจากระบบเดิม
|
||||
2. External documents จาก client/consultant
|
||||
3. Correction หลังพบความผิดพลาด
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- ตรวจสอบ duplicate ก่อน save
|
||||
- Validate format ตามรูปแบบที่กำหนด
|
||||
- Auto-update sequence counter ถ้าเลขที่สูงกว่า current
|
||||
- บันทึก audit log ว่าเป็น manual override
|
||||
- ต้องมีสิทธิ์ Admin ขึ้นไปเท่านั้น
|
||||
|
||||
**Validation Rules**:
|
||||
```typescript
|
||||
interface ManualNumberValidation {
|
||||
format_match: boolean; // ตรง format หรือไม่
|
||||
not_duplicate: boolean; // ไม่ซ้ำ
|
||||
in_valid_range: boolean; // อยู่ในช่วงที่กำหนด
|
||||
permission_granted: boolean; // มีสิทธิ์
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### FR-DN-005: Bulk Import Support
|
||||
**Priority**: MEDIUM
|
||||
**Status**: Required
|
||||
|
||||
**Description**:
|
||||
ระบบต้องรองรับการ import เอกสารหลายรายการพร้อมกัน
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- รองรับไฟล์ CSV/Excel
|
||||
- Validate ทุกรายการก่อน import
|
||||
- แสดง preview ก่อน confirm
|
||||
- Rollback ทั้งหมดถ้ามีรายการใดผิดพลาด (transactional)
|
||||
- Auto-update sequence counters หลัง import
|
||||
- Generate import report
|
||||
|
||||
**CSV Format**:
|
||||
```csv
|
||||
document_type,document_number,created_at,metadata
|
||||
COR,COR-2024-00001,2024-01-01,{"imported":true}
|
||||
COR,COR-2024-00002,2024-01-05,{"imported":true}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Cancelled & Void Handling
|
||||
|
||||
#### FR-DN-006: Skip Cancelled Numbers
|
||||
**Priority**: HIGH
|
||||
**Status**: Required
|
||||
|
||||
**Description**:
|
||||
เลขที่เอกสารที่ถูกยกเลิกต้องไม่ถูก 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)
|
||||
2025-00003 ✅ ACTIVE (created 2025-01-04)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### FR-DN-007: Void and Replace
|
||||
**Priority**: HIGH
|
||||
**Status**: Required
|
||||
|
||||
**Description**:
|
||||
ระบบต้องรองรับการ void เอกสารและสร้างเอกสารใหม่แทน
|
||||
|
||||
**Workflow**:
|
||||
1. User เลือกเอกสารที่ต้องการ void
|
||||
2. ระบุเหตุผล (required)
|
||||
3. ระบบเปลี่ยน status เอกสารเดิมเป็น VOID
|
||||
4. สร้างเอกสารใหม่ด้วยเลขที่ใหม่
|
||||
5. Link เอกสารใหม่กับเดิม (voided_from_id)
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- เอกสารเดิม status = VOID (ไม่ลบ)
|
||||
- เอกสารใหม่ได้เลขที่ต่อเนื่องจาก sequence
|
||||
- มี reference link ระหว่างเอกสาร
|
||||
- บันทึก void reason
|
||||
- แสดง void history chain (A→B→C)
|
||||
|
||||
**Database Relationship**:
|
||||
```sql
|
||||
-- Original document
|
||||
id: 100
|
||||
document_number: COR-2025-00005
|
||||
status: VOID
|
||||
void_reason: "ข้อมูลผิด"
|
||||
voided_at: 2025-01-10
|
||||
|
||||
-- Replacement document
|
||||
id: 101
|
||||
document_number: COR-2025-00006
|
||||
status: ACTIVE
|
||||
voided_from_id: 100
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.4 Concurrency & Performance
|
||||
|
||||
#### FR-DN-008: Prevent Race Conditions
|
||||
**Priority**: CRITICAL
|
||||
**Status**: Required
|
||||
|
||||
**Description**:
|
||||
ระบบต้องป้องกันการสร้างเลขที่ซ้ำเมื่อมีการ 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
|
||||
requests_per_second: 500
|
||||
test_duration: 5 minutes
|
||||
expected_duplicates: 0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### FR-DN-009: Two-Phase Commit
|
||||
**Priority**: HIGH
|
||||
**Status**: Required
|
||||
|
||||
**Description**:
|
||||
ใช้ 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
|
||||
});
|
||||
|
||||
// Do some work...
|
||||
|
||||
// Phase 2
|
||||
await confirmReservation(token);
|
||||
// OR
|
||||
await cancelReservation(token);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.5 Monitoring & Audit
|
||||
|
||||
#### FR-DN-010: Complete Audit Trail
|
||||
**Priority**: HIGH
|
||||
**Status**: Required
|
||||
|
||||
**Description**:
|
||||
บันทึกทุก operation ที่เกิดขึ้นกับเลขที่เอกสาร
|
||||
|
||||
**Events to Log**:
|
||||
- Number reserved
|
||||
- Number confirmed
|
||||
- Number cancelled
|
||||
- Manual override
|
||||
- Void document
|
||||
- Sequence adjusted
|
||||
- Format changed
|
||||
|
||||
**Audit Fields**:
|
||||
```typescript
|
||||
interface AuditLog {
|
||||
id: number;
|
||||
operation: string;
|
||||
document_type: string;
|
||||
document_number: string;
|
||||
old_value?: any;
|
||||
new_value?: any;
|
||||
user_id: number;
|
||||
ip_address: string;
|
||||
user_agent: string;
|
||||
timestamp: Date;
|
||||
metadata: Record<string, any>;
|
||||
}
|
||||
```
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- Log ทุก operation
|
||||
- Searchable by user, date, type
|
||||
- Export to CSV
|
||||
- Retain for 7 years
|
||||
|
||||
---
|
||||
|
||||
#### FR-DN-011: Metrics & Alerting
|
||||
**Priority**: MEDIUM
|
||||
**Status**: Required
|
||||
|
||||
**Description**:
|
||||
แสดงสถิติและส่ง alert เมื่อเกิดปัญหา
|
||||
|
||||
**Metrics**:
|
||||
- Sequence utilization (% of max)
|
||||
- Average lock wait time
|
||||
- Failed lock attempts
|
||||
- Numbers generated per day
|
||||
- Manual overrides per day
|
||||
|
||||
**Alerts**:
|
||||
- Sequence >90% used (WARNING)
|
||||
- Sequence >95% used (CRITICAL)
|
||||
- Lock wait time >1s (WARNING)
|
||||
- Redis unavailable (CRITICAL)
|
||||
- High error rate (WARNING)
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- Real-time dashboard (Grafana)
|
||||
- Email/LINE notifications
|
||||
- Alert history tracking
|
||||
- Configurable thresholds
|
||||
|
||||
---
|
||||
|
||||
## 3. Non-Functional Requirements
|
||||
|
||||
### 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
|
||||
|
||||
### 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)
|
||||
|
||||
### 3.4 Scalability
|
||||
|
||||
#### NFR-DN-007: Capacity Planning
|
||||
- Support 10,000 documents/day
|
||||
- Store 10M+ historical numbers
|
||||
- Archive old audit logs (>2 years)
|
||||
|
||||
### 3.5 Maintainability
|
||||
|
||||
#### NFR-DN-008: Code Quality
|
||||
- Unit test coverage: >70%
|
||||
- Integration test coverage: >50%
|
||||
- E2E test coverage: >20 critical paths
|
||||
- Documentation: Complete API docs
|
||||
|
||||
---
|
||||
|
||||
## 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)
|
||||
- Replacement is optional
|
||||
|
||||
---
|
||||
|
||||
## 5. Data Model
|
||||
|
||||
### 5.1 Numbering Configuration
|
||||
```sql
|
||||
CREATE TABLE document_numbering_configs (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
document_type VARCHAR(50) NOT NULL,
|
||||
format VARCHAR(200) NOT NULL,
|
||||
scope ENUM('GLOBAL','PROJECT','CONTRACT','YEARLY','MONTHLY'),
|
||||
allow_manual_override BOOLEAN DEFAULT FALSE,
|
||||
max_value INT DEFAULT 999999,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY (document_type, scope)
|
||||
);
|
||||
```
|
||||
|
||||
### 5.2 Sequence Counter
|
||||
```sql
|
||||
CREATE TABLE document_numbering_sequences (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
config_id INT NOT NULL,
|
||||
scope_value VARCHAR(50), -- project_id, contract_id, year, etc.
|
||||
current_value INT DEFAULT 0,
|
||||
last_used_at TIMESTAMP NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (config_id) REFERENCES document_numbering_configs(id),
|
||||
UNIQUE KEY (config_id, scope_value)
|
||||
);
|
||||
```
|
||||
|
||||
### 5.3 Audit Log
|
||||
```sql
|
||||
CREATE TABLE document_numbering_audit_logs (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
operation VARCHAR(50) NOT NULL,
|
||||
document_type VARCHAR(50),
|
||||
document_number VARCHAR(50),
|
||||
old_value TEXT,
|
||||
new_value TEXT,
|
||||
user_id INT NOT NULL,
|
||||
ip_address VARCHAR(45),
|
||||
user_agent VARCHAR(500),
|
||||
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
metadata JSON,
|
||||
INDEX idx_document_number (document_number),
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_timestamp (timestamp)
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. API Specifications
|
||||
|
||||
### 6.1 Reserve Number
|
||||
```http
|
||||
POST /api/document-numbering/reserve
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"document_type": "COR",
|
||||
"project_id": 1,
|
||||
"contract_id": null,
|
||||
"metadata": {}
|
||||
}
|
||||
|
||||
Response 201:
|
||||
{
|
||||
"token": "uuid-v4",
|
||||
"document_number": "COR-2025-00042",
|
||||
"expires_at": "2025-01-16T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 Confirm Reservation
|
||||
```http
|
||||
POST /api/document-numbering/confirm
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"token": "uuid-v4"
|
||||
}
|
||||
|
||||
Response 200:
|
||||
{
|
||||
"document_number": "COR-2025-00042",
|
||||
"confirmed_at": "2025-01-16T10:25:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 Manual Override
|
||||
```http
|
||||
POST /api/document-numbering/manual
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer <admin-token>
|
||||
|
||||
{
|
||||
"document_type": "COR",
|
||||
"document_number": "COR-2024-99999",
|
||||
"reason": "Import from legacy system",
|
||||
"skip_validation": false
|
||||
}
|
||||
|
||||
Response 201:
|
||||
{
|
||||
"document_number": "COR-2024-99999",
|
||||
"is_manual": true,
|
||||
"created_at": "2025-01-16T10:25:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
- Admin configuration UI
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
4. Update sequence counters to max values
|
||||
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
|
||||
- Week 5+: Monitor and optimize
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
- ✅ System stability >99.9%
|
||||
|
||||
---
|
||||
|
||||
## 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/)
|
||||
|
||||
---
|
||||
|
||||
**Approval Sign-off**:
|
||||
|
||||
| Role | Name | Date | Signature |
|
||||
|------|------|------|-----------|
|
||||
| Product Owner | ___________ | _______ | _________ |
|
||||
| Tech Lead | ___________ | _______ | _________ |
|
||||
| QA Lead | ___________ | _______ | _________ |
|
||||
1871
docs/backup/03.11-document-numbering.md
Normal file
1871
docs/backup/03.11-document-numbering.md
Normal file
File diff suppressed because it is too large
Load Diff
103
docs/backup/03.11-document-numbering_schema_section.md
Normal file
103
docs/backup/03.11-document-numbering_schema_section.md
Normal file
@@ -0,0 +1,103 @@
|
||||
## 3.11.15. Database Schema Requirements
|
||||
|
||||
### 3.11.15.1. Counter Table Schema
|
||||
|
||||
ตาราง `document_number_counters` ต้องมีโครงสร้างดังนี้:
|
||||
|
||||
```sql
|
||||
CREATE TABLE document_number_counters (
|
||||
project_id INT NOT NULL,
|
||||
originator_organization_id INT NOT NULL,
|
||||
recipient_organization_id INT NULL, -- NULL for RFA
|
||||
correspondence_type_id INT NOT NULL,
|
||||
sub_type_id INT DEFAULT 0, -- for TRANSMITTAL
|
||||
rfa_type_id INT DEFAULT 0, -- for RFA
|
||||
discipline_id INT DEFAULT 0, -- for RFA
|
||||
current_year INT NOT NULL,
|
||||
version INT DEFAULT 0 NOT NULL, -- Optimistic Lock
|
||||
last_number INT DEFAULT 0,
|
||||
|
||||
PRIMARY KEY (
|
||||
project_id,
|
||||
originator_organization_id,
|
||||
COALESCE(recipient_organization_id, 0),
|
||||
correspondence_type_id,
|
||||
sub_type_id,
|
||||
rfa_type_id,
|
||||
discipline_id,
|
||||
current_year
|
||||
),
|
||||
|
||||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (originator_organization_id) REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (recipient_organization_id) REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (correspondence_type_id) REFERENCES correspondence_types(id) ON DELETE CASCADE
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci
|
||||
COMMENT = 'ตารางเก็บ Running Number Counters';
|
||||
```
|
||||
|
||||
### 3.11.15.2. Index Requirements
|
||||
|
||||
```sql
|
||||
-- Index สำหรับ Performance
|
||||
CREATE INDEX idx_counter_lookup
|
||||
ON document_number_counters (
|
||||
project_id,
|
||||
correspondence_type_id,
|
||||
current_year
|
||||
);
|
||||
|
||||
-- Index สำหรับ Originator lookup
|
||||
CREATE INDEX idx_counter_org
|
||||
ON document_number_counters (
|
||||
originator_organization_id,
|
||||
current_year
|
||||
);
|
||||
```
|
||||
|
||||
### 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` ก่อน
|
||||
|
||||
### 3.11.15.4. Example Counter Records
|
||||
|
||||
```sql
|
||||
-- Example: LETTER from คคง. to สคฉ.3 in LCBP3-C2 year 2025
|
||||
INSERT INTO document_number_counters (
|
||||
project_id, originator_organization_id, recipient_organization_id,
|
||||
correspondence_type_id, sub_type_id, rfa_type_id, discipline_id,
|
||||
current_year, version, last_number
|
||||
) VALUES (
|
||||
2, -- LCBP3-C2
|
||||
22, -- คคง.
|
||||
10, -- สคฉ.3
|
||||
6, -- LETTER
|
||||
0, 0, 0,
|
||||
2025, 0, 0
|
||||
);
|
||||
|
||||
-- Example: RFA from ผรม.2 in LCBP3-C2, discipline TER, type RPT, year 2025
|
||||
INSERT INTO document_number_counters (
|
||||
project_id, originator_organization_id, recipient_organization_id,
|
||||
correspondence_type_id, sub_type_id, rfa_type_id, discipline_id,
|
||||
current_year, version, last_number
|
||||
) VALUES (
|
||||
2, -- LCBP3-C2
|
||||
42, -- ผรม.2
|
||||
NULL, -- RFA ไม่มี specific recipient
|
||||
1, -- RFA
|
||||
0,
|
||||
18, -- RPT (Report)
|
||||
5, -- TER (Terminal)
|
||||
2025, 0, 0
|
||||
);
|
||||
```
|
||||
377
docs/backup/1.bak
Normal file
377
docs/backup/1.bak
Normal file
@@ -0,0 +1,377 @@
|
||||
ผมได้ตรวจสอบไฟล์ `FullStackJS_Guidelines01.md` และ `01_lcbp3_v1_1_0.sql` ของคุณแล้ว และได้อัปเดตไฟล์ Guidelines (Markdown) ให้สอดคล้องกับโครงสร้างฐานข้อมูล (SQL) ที่คุณกำหนดไว้อย่างสมบูรณ์ครับ
|
||||
|
||||
การเปลี่ยนแปลงที่สำคัญคือ:
|
||||
|
||||
1. **การจัดการ UI (UI Conflict):** แก้ไขความขัดแย้งในไฟล์เดิมที่ระบุทั้ง **Tailwind/Shadcn** และ **Bootstrap** ผมได้รวมศูนย์การพัฒนา UI ไปที่ **Tailwind/Shadcn** ตามที่ระบุไว้ในโปรไฟล์นักพัฒนา (Frontend Developer Profile) และลบส่วนของ Bootstrap ที่ซ้ำซ้อนออกไป
|
||||
2. **DMS-Specific Conventions:** อัปเดตส่วนนี้ทั้งหมดเพื่อให้ตรงกับสคีม SQL ของคุณ
|
||||
* เปลี่ยนชื่อโปรเจกต์อ้างอิงเป็น **LCBP3-DMS**
|
||||
* ปรับปรุงแผนผัง **Domain Modules** ให้สะท้อนโครงสร้างจริง (เช่น `correspondences` เป็นศูนย์กลาง, เพิ่ม `circulations`)
|
||||
* อัปเดตตาราง **AuditLog** ให้ตรงกับฟิลด์ใน SQL (เช่น เพิ่ม `entity_type`, `details_json`, `ip_address`)
|
||||
* อัปเดตตัวอย่าง **RBAC Permissions** ให้ตรงกับสิทธิ์ที่มีในตาราง `permissions` ของคุณ
|
||||
* ชี้แจง
|
||||
เรื่อง **File Handling** ว่าตาราง `attachments` จะเชื่อมโยงผ่าน `correspondence_id`
|
||||
* อัปเดตส่วน **Reporting** เพื่ออ้างอิงถึง `VIEWS` (เช่น `v_current_rfas`) ที่คุณได้สร้างไว้ใน SQL
|
||||
* ย้ายส่วน **"Recommended Enhancements"** (เช่น soft delete, indexes) ที่มีอยู่แล้วใน SQL ไปยังส่วนใหม่ชื่อ **"Implemented Standards"** เพื่อสะท้อนว่างานส่วนนี้เสร็จแล้ว
|
||||
|
||||
นี่คือไฟล์ `FullStackJS_Guidelines01.md` ฉบับที่อัปเดตแล้วครับ:
|
||||
|
||||
-----
|
||||
|
||||
# FullStackJS Development Guidelines
|
||||
|
||||
## 🧠 General Philosophy
|
||||
|
||||
Unified best practices for **NestJS Backend**, **NextJS Frontend**, and **Tailwind-based UI/UX** development in TypeScript environments.
|
||||
Focus on **clarity**, **maintainability**, **consistency**, and **accessibility** across the entire stack.
|
||||
|
||||
-----
|
||||
|
||||
## ⚙️ TypeScript General Guidelines
|
||||
|
||||
### Basic Principles
|
||||
|
||||
- Use **English** for all code and documentation.
|
||||
- Explicitly type all variables, parameters, and return values.
|
||||
- Avoid `any`; create custom types or interfaces.
|
||||
- Use **JSDoc** for public classes and methods.
|
||||
- Export only **one main symbol** per file.
|
||||
- Avoid blank lines within functions.
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
| Entity | Convention | Example |
|
||||
|:--|:--|:--|
|
||||
| Classes | PascalCase | `UserService` |
|
||||
| Variables & Functions | camelCase | `getUserInfo` |
|
||||
| Files & Folders | kebab-case | `user-service.ts` |
|
||||
| Environment Variables | UPPERCASE | `DATABASE_URL` |
|
||||
| Booleans | Verb + Noun | `isActive`, `canDelete`, `hasPermission` |
|
||||
|
||||
Use full words — no abbreviations — except for standard ones (`API`, `URL`, `req`, `res`, `err`, `ctx`).
|
||||
|
||||
-----
|
||||
|
||||
## 🧩 Functions
|
||||
|
||||
- Write short, single-purpose functions (\<20 lines).
|
||||
- Use **early returns** to reduce nesting.
|
||||
- Use **map**, **filter**, **reduce** instead of loops when suitable.
|
||||
- Prefer **arrow functions** for short logic, **named functions** otherwise.
|
||||
- Use **default parameters** over null checks.
|
||||
- Group multiple parameters into a single object (RO-RO pattern).
|
||||
- Return typed objects, not primitives.
|
||||
- Maintain a single abstraction level per function.
|
||||
|
||||
-----
|
||||
|
||||
## 🧱 Data Handling
|
||||
|
||||
- Encapsulate data in composite types.
|
||||
- Use **immutability** with `readonly` and `as const`.
|
||||
- Perform validations in classes or DTOs, not within business functions.
|
||||
- Always validate data using typed DTOs.
|
||||
|
||||
-----
|
||||
|
||||
## 🧰 Classes
|
||||
|
||||
- Follow **SOLID** principles.
|
||||
- Prefer **composition over inheritance**.
|
||||
- Define **interfaces** for contracts.
|
||||
- Keep classes focused and small (\<200 lines, \<10 methods, \<10 properties).
|
||||
|
||||
-----
|
||||
|
||||
## 🚨 Error Handling
|
||||
|
||||
- Use exceptions for unexpected errors.
|
||||
- Catch only to fix or add context; otherwise, use global error handlers.
|
||||
- Always provide meaningful error messages.
|
||||
|
||||
-----
|
||||
|
||||
## 🧪 Testing (General)
|
||||
|
||||
- Use the **Arrange–Act–Assert** pattern.
|
||||
- Use descriptive test variable names (`inputData`, `expectedOutput`).
|
||||
- Write **unit tests** for all public methods.
|
||||
- Mock external dependencies.
|
||||
- Add **acceptance tests** per module using Given–When–Then.
|
||||
|
||||
-----
|
||||
|
||||
# 🏗️ Backend (NestJS)
|
||||
|
||||
### Principles
|
||||
|
||||
- **Modular architecture**:
|
||||
- One module per domain.
|
||||
- Controller → Service → Repository (Model) structure.
|
||||
- DTOs validated with **class-validator**.
|
||||
- Use **MikroORM** (or TypeORM/Prisma) for persistence, aligning with the MariaDB schema.
|
||||
- Encapsulate reusable code in a **common module** (`@app/common`):
|
||||
- Configs, decorators, DTOs, guards, interceptors, notifications, shared services, types, validators.
|
||||
|
||||
### Core Functionalities
|
||||
|
||||
- Global **filters** for exception handling.
|
||||
- **Middlewares** for request handling.
|
||||
- **Guards** for permissions and RBAC.
|
||||
- **Interceptors** for response transformation and logging.
|
||||
|
||||
### Testing
|
||||
|
||||
- Use **Jest** for testing.
|
||||
- Test each controller and service.
|
||||
- Add `admin/test` endpoint as a smoke test.
|
||||
|
||||
-----
|
||||
|
||||
# 🖥️ Frontend (NextJS / React / UI)
|
||||
|
||||
### Developer Profile
|
||||
|
||||
Senior-level TypeScript + React/NextJS engineer.
|
||||
Expert in **TailwindCSS**, **Shadcn/UI**, and **Radix** for UI development.
|
||||
|
||||
### Code Implementation Guidelines
|
||||
|
||||
- Use **early returns** for clarity.
|
||||
- Always style with **TailwindCSS** classes.
|
||||
- Prefer `class:` conditional syntax (or `clsx` utility) over ternary operators in class strings.
|
||||
- Use **const arrow functions** for components and handlers.
|
||||
- Event handlers start with `handle...` (e.g., `handleClick`, `handleSubmit`).
|
||||
- Include accessibility attributes:
|
||||
`tabIndex="0"`, `aria-label`, `onKeyDown`, etc.
|
||||
- Ensure all code is **complete**, **tested**, and **DRY**.
|
||||
- Always import required modules explicitly.
|
||||
|
||||
### UI/UX with React
|
||||
|
||||
- Use **semantic HTML**.
|
||||
- Apply **responsive Tailwind** classes (`sm:`, `md:`, `lg:`).
|
||||
- Maintain visual hierarchy with typography and spacing.
|
||||
- Use **Shadcn** components (Button, Input, Card, etc.) for consistent UI.
|
||||
- Keep components small and focused.
|
||||
- Use utility classes for quick styling (spacing, colors, text, etc.).
|
||||
- Ensure **ARIA compliance** and semantic markup.
|
||||
|
||||
### Form Validation & Errors
|
||||
|
||||
- Use client-side libraries like `zod` and `react-hook-form`.
|
||||
- Show errors with **alert components** or inline messages.
|
||||
- Include labels, placeholders, and feedback messages.
|
||||
|
||||
-----
|
||||
|
||||
# 🔗 Full Stack Integration Guidelines
|
||||
|
||||
| Aspect | Backend (NestJS) | Frontend (NextJS) | UI Layer (Tailwind/Shadcn) |
|
||||
|:--|:--|:--|:--|
|
||||
| API | REST / GraphQL Controllers | API hooks via fetch/axios/SWR | Components consuming data |
|
||||
| Validation | `class-validator` DTOs | `zod` / `react-hook-form` | Shadcn form/input states |
|
||||
| Auth | Guards, JWT | NextAuth / cookies | Auth UI states (loading, signed in) |
|
||||
| Errors | Global filters | Toasts / modals | Alerts / feedback text |
|
||||
| Testing | Jest (unit/e2e) | Vitest / Playwright | Visual regression |
|
||||
| Styles | Scoped modules (if needed) | Tailwind / Shadcn | Tailwind utilities |
|
||||
| Accessibility | Guards + filters | ARIA attributes | Semantic HTML |
|
||||
|
||||
-----
|
||||
|
||||
# 🗂️ DMS-Specific Conventions (LCBP3-DMS)
|
||||
|
||||
This section extends the general FullStackJS guidelines for the **LCBP3-DMS** project, focusing on document approval workflows (Correspondence, RFA, Drawing, Contract, Transmittal, Circulation).
|
||||
|
||||
## 🧱 Backend Domain Modules
|
||||
|
||||
Use a modular domain structure reflecting the SQL schema. `correspondences` should act as the central hub.
|
||||
|
||||
```
|
||||
src/
|
||||
├─ modules/
|
||||
│ ├─ correspondences/ (Core: Master documents, Revisions, Attachments)
|
||||
│ ├─ rfas/ (RFA logic, Revisions, Workflows, Items)
|
||||
│ ├─ drawings/ (ShopDrawings, ContractDrawings, Categories)
|
||||
│ ├─ circulations/ (Internal circulation, Templates, Assignees)
|
||||
│ ├─ transmittals/ (Transmittal logic, Items)
|
||||
│ ├─ projects-contracts/ (Projects, Contracts, Organizations, Parties)
|
||||
│ ├─ users-auth/ (Users, Roles, Permissions, Auth)
|
||||
│ ├─ audit-log/
|
||||
│ └─ common/
|
||||
```
|
||||
|
||||
### Naming Convention
|
||||
|
||||
| Entity | Example (from SQL) |
|
||||
|:--|:--|
|
||||
| Table | `correspondences`, `rfa_revisions`, `contract_parties` |
|
||||
| Column | `correspondence_id`, `created_by`, `is_current` |
|
||||
| DTO | `CreateRfaDto`, `UpdateCorrespondenceDto` |
|
||||
| Controller | `rfas.controller.ts` |
|
||||
| Service | `correspondences.service.ts` |
|
||||
|
||||
-----
|
||||
|
||||
## 🧩 RBAC & Permission Control
|
||||
|
||||
Use decorators to enforce access rights, referencing permissions from the `permissions` table.
|
||||
|
||||
```ts
|
||||
@RequirePermission('rfas.respond') // Must match a 'permission_code'
|
||||
@Put(':id')
|
||||
updateRFA(@Param('id') id: string) {
|
||||
return this.rfaService.update(id);
|
||||
}
|
||||
```
|
||||
|
||||
### Roles
|
||||
|
||||
- **SUPER\_ADMIN**: Full system access (from `roles` table).
|
||||
- **ADMIN**: Organization-level access.
|
||||
- **EDITOR**: Module-specific write access.
|
||||
- **VIEWER**: Read-only access.
|
||||
|
||||
### Example Permissions (from `permissions` table)
|
||||
|
||||
- `rfas.view`, `rfas.create`, `rfas.respond`, `rfas.delete`
|
||||
- `drawings.view`, `drawings.upload`, `drawings.delete`
|
||||
- `corr.view`, `corr.manage`
|
||||
- `transmittals.manage`
|
||||
- `cirs.manage`
|
||||
- `project_parties.manage`
|
||||
|
||||
Seed mapping between roles and permissions via seeder scripts (as seen in SQL file).
|
||||
|
||||
-----
|
||||
|
||||
## 🧾 AuditLog Standard
|
||||
|
||||
Log all CRUD and mapping operations to the `audit_logs` table.
|
||||
|
||||
| Field | Type (from SQL) | Description |
|
||||
|:--|:--|:--|
|
||||
| `audit_id` | `BIGINT` | Primary Key |
|
||||
| `user_id` | `INT` | User performing the action (FK -\> users) |
|
||||
| `action` | `VARCHAR(100)` | `rfa.create`, `correspondence.update`, `login.success` |
|
||||
| `entity_type`| `VARCHAR(50)` | Table/module name, e.g., 'rfa', 'correspondence' |
|
||||
| `entity_id` | `VARCHAR(50)` | Primary ID of the affected record |
|
||||
| `details_json`| `JSON` | Contextual data (e.g., changed fields) |
|
||||
| `ip_address` | `VARCHAR(45)` | Actor's IP address |
|
||||
| `user_agent` | `VARCHAR(255)`| Actor's User Agent |
|
||||
| `created_at` | `TIMESTAMP` | UTC timestamp |
|
||||
|
||||
Example implementation:
|
||||
|
||||
```ts
|
||||
await this.auditLogService.log({
|
||||
userId: user.id,
|
||||
action: 'rfa.update_status',
|
||||
entityType: 'rfa_revisions',
|
||||
entityId: rfaRevision.id,
|
||||
detailsJson: { from: 'DFT', to: 'FAP' },
|
||||
ipAddress: req.ip,
|
||||
});
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
## 📂 File Handling
|
||||
|
||||
### File Upload Standard
|
||||
|
||||
- Centralize all uploads via the `attachments` table.
|
||||
- Upload path (convention): `/storage/{year}/{month}/`
|
||||
- Stored filename: Use `stored_filename` (e.g., UUID or hash) to prevent conflicts. `original_filename` is for display.
|
||||
- Allowed types: `pdf, dwg, docx, xlsx, zip`
|
||||
- Max size: **50 MB**
|
||||
- Store outside webroot.
|
||||
- Serve via secure endpoint `/files/:attachment_id/download`.
|
||||
|
||||
### Access Control
|
||||
|
||||
File access is not direct. The `/files/:attachment_id/download` endpoint must:
|
||||
|
||||
1. Find the `attachment` record.
|
||||
2. Get its `correspondence_id`.
|
||||
3. Verify the user has permission to view that specific `correspondence` (or its related RFA, Transmittal, etc.).
|
||||
|
||||
-----
|
||||
|
||||
## 📊 Reporting & Exports
|
||||
|
||||
### Reporting Views (from SQL)
|
||||
|
||||
Reports should be built primarily from the pre-defined database Views:
|
||||
|
||||
- `v_current_correspondences`: For all current non-RFA document revisions.
|
||||
- `v_current_rfas`: For all current RFA revisions and their master data.
|
||||
- `v_contract_parties_all`: For auditing project/contract/organization relationships.
|
||||
|
||||
These views serve as the primary source for server-side reporting and data exports.
|
||||
|
||||
### Export Rules
|
||||
|
||||
- Export formats: CSV, Excel, PDF.
|
||||
- Provide print view.
|
||||
- Include source entity link (e.g., `/rfas/:id`).
|
||||
|
||||
-----
|
||||
|
||||
## 🧮 Frontend: DataTable & Form Patterns
|
||||
|
||||
### DataTable (Server‑Side)
|
||||
|
||||
- Endpoint: `/api/{module}?page=1&pageSize=20&sort=...&filter=...`
|
||||
- Must support: pagination, sorting, search, filters.
|
||||
- Always display latest revision inline (for RFA/Drawing).
|
||||
|
||||
### Form Standards
|
||||
|
||||
- Dependent dropdowns must be implemented (as supported by schema):
|
||||
- Project → Contract Drawing Volumes
|
||||
- Contract Drawing Category → Sub-Category
|
||||
- RFA (Shop Drawing type) → Linkable Shop Drawing Revisions
|
||||
- File upload: preview + validation (via `attachments` logic).
|
||||
- Submit via API with toast feedback.
|
||||
|
||||
-----
|
||||
|
||||
## 🧭 Dashboard & Activity Feed
|
||||
|
||||
### Dashboard Cards
|
||||
|
||||
- Show latest Correspondences, RFAs, Circulations.
|
||||
- Include KPI summaries (e.g., "RFAs Pending Approval").
|
||||
- Include quick links to modules.
|
||||
|
||||
### Activity Feed
|
||||
|
||||
- Display recent `audit_logs` actions (10 latest) relevant to the user.
|
||||
|
||||
<!-- end list -->
|
||||
|
||||
```ts
|
||||
// Example API response
|
||||
[
|
||||
{ user: 'editor01', action: 'Updated RFA (LCBP3C1-RFA-001)', time: '2025-11-04T09:30Z' }
|
||||
]
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
## ✅ Implemented Standards (from SQL v1.1.0)
|
||||
|
||||
This section confirms that the following best practices are already part of the database design and should be leveraged, not re-implemented.
|
||||
|
||||
- ✅ **Soft Delete:** Implemented via `deleted_at` columns on key tables (e.g., `correspondences`, `rfas`, `project_parties`). Logic must filter for `deleted_at IS NULL`.
|
||||
- ✅ **Database Indexes:** The schema is heavily indexed on foreign keys and common query columns (e.g., `idx_rr_rfa`, `idx_cor_project`, `idx_cr_is_current`) for performance.
|
||||
- ✅ **RBAC Structure:** A comprehensive `users`, `roles`, `permissions`, `user_roles`, and `user_project_roles` system is in place.
|
||||
- ✅ **Data Seeding:** Master data (roles, permissions, organization\_roles, initial users, project parties) is included in the schema script.
|
||||
|
||||
## 🧩 Recommended Enhancements (Future)
|
||||
|
||||
- ✅ Implement Fulltext search on fields like `correspondence_revisions.title` or `details`.
|
||||
- ✅ Create a background job (using **n8n** as noted in SQL comments) for RFA deadline reminders based on `due_date`.
|
||||
- ✅ Add a periodic cleanup job for `attachments` that are not linked to any `correspondence_id` (orphaned files).
|
||||
|
||||
-----
|
||||
777
docs/backup/DMS README.bak
Normal file
777
docs/backup/DMS README.bak
Normal file
@@ -0,0 +1,777 @@
|
||||
# 📝 0. Project Title: Document Management System (DMS) Web Application for Laem Chabang Port Development Project, Phase 3
|
||||
|
||||
## 0. Project
|
||||
|
||||
### 📌 0.1 Project Overview / Description
|
||||
|
||||
- ระบบ Document Management System (DMS) เป็นเว็บแอปพลิเคชันที่ออกแบบมาเพื่อจัดการเอกสารภายในโครงการอย่างมีประสิทธิภาพ
|
||||
- โดยมีฟังก์ชันหลักในการอัปโหลด จัดเก็บ ค้นหา แชร์ และควบคุมสิทธิ์การเข้าถึงเอกสาร
|
||||
- ระบบนี้จะช่วยลดการใช้เอกสารกระดาษ เพิ่มความปลอดภัยในการจัดเก็บข้อมูล
|
||||
- เพิ่มความสะดวกในการทำงานร่วมกันระหว่างองกรณ์
|
||||
|
||||
### 🎯 0.2 Objectives
|
||||
|
||||
- พัฒนาระบบที่สามารถจัดการเอกสารได้อย่างเป็นระบบ
|
||||
- ลดความซ้ำซ้อนในการจัดเก็บเอกสาร
|
||||
- เพิ่มความปลอดภัยในการเข้าถึงและจัดการเอกสาร
|
||||
- รองรับการทำงานร่วมกันแบบออนไลน์
|
||||
|
||||
### 📦 0.3 Scope of Work
|
||||
|
||||
ระบบจะครอบคลุมฟีเจอร์หลักดังนี้:
|
||||
|
||||
- การลงทะเบียนและเข้าสู่ระบบ ของผู้ใช้งาน
|
||||
- การอัปโหลดและจัดเก็บเอกสารในรูปแบบต่าง ๆ (PDF, DOCX, XLSX ฯลฯ)
|
||||
- การจัดหมวดหมู่และแท็กเอกสาร
|
||||
- การค้นหาเอกสารด้วยคำสำคัญหรือฟิลเตอร์
|
||||
- การกำหนดสิทธิ์การเข้าถึงเอกสาร (เช่น อ่านอย่างเดียว, แก้ไข, ลบ)
|
||||
- การบันทึกประวัติการใช้งานเอกสาร (Audit Trail)
|
||||
- การมอบหมายงานให้กับผู้เกี่ยวข้อง และแจ้งเตือนเมื่อมีการมอบหมายงาน
|
||||
- การแจ้งเตือนเมื่อถึงกำหนดวันที่ต้องส่งเอกสารต่อให้ ผู้เกี่ยวข้องอื่นๆ
|
||||
- การแจ้งเตือนเมื่อมีการเปลี่ยนแปลงเอกสาร
|
||||
|
||||
### 👥 0.4 Target Users
|
||||
|
||||
- พนักงานภายใน ขององค์กร
|
||||
- พนักงานควบคุมเอกสาร (Document Control)/ ผู้ดูแลระบบขององค์กร (admin)
|
||||
- ผู้จัดการฝ่ายเอกสาร ขององค์กร
|
||||
- ผู้จัดการโครงการ ขององค์กร
|
||||
- คณะกรรมการ ของโครงการ
|
||||
- ผู้ดูแลระบบ IT ของโครงการ (superadmin)
|
||||
|
||||
### 📈 0.5 Expected Outcomes
|
||||
|
||||
- ลดเวลาในการค้นหาเอกสารลงอย่างน้อย 50%
|
||||
- ลดเวลาในการจัดทำรายงานเอกสาร ประจำวัน, ประจำสัปดาห์, ประจำเดือน, ประจำปี และ รายงานเอกสารทั้งโครงการ
|
||||
- ลดการใช้เอกสารกระดาษในองค์กร
|
||||
- เพิ่มความปลอดภัยในการจัดเก็บข้อมูล
|
||||
- รองรับการทำงานแบบ Remote Work
|
||||
|
||||
### 📘 0.6 Requirements Use Cases
|
||||
|
||||
#### 📘 Use Case: Upload Document
|
||||
|
||||
Actor: พนักงานควบคุมเอกสาร (Document Control)
|
||||
Description: พนักงานควบคุมเอกสารสามารถอัปโหลดเอกสารเข้าสู่ระบบเพื่อจัดเก็บและใช้งานในภายหลัง
|
||||
Preconditions: พนักงานควบคุมเอกสารต้องเข้าสู่ระบบก่อน
|
||||
Main Flow:
|
||||
|
||||
พนักงานควบคุมเอกสารเลือกเมนู “อัปโหลดเอกสาร”
|
||||
เลือกไฟล์จากเครื่องคอมพิวเตอร์
|
||||
กรอกข้อมูลประกอบ เช่น ชื่อเอกสาร หมวดหมู่ แท็ก
|
||||
กดปุ่ม “อัปโหลด”
|
||||
ระบบบันทึกเอกสารและแสดงผลการอัปโหลดสำเร็จ
|
||||
|
||||
Postconditions: เอกสารถูกจัดเก็บในระบบและสามารถค้นหาได้
|
||||
|
||||
#### 📘 Use Case: Assign Users to Document
|
||||
|
||||
Actor: พนักงานควบคุมเอกสาร (Document Control)
|
||||
Description: พนักงานควบคุมเอกสารสามารถ มอบหมายงานให้กับ Users
|
||||
Preconditions: พนักงานควบคุมเอกสารต้องเข้าสู่ระบบก่อน, เอกสารต้องอัปโหลดเรียบร้อยแล้ว
|
||||
Main Flow:
|
||||
|
||||
พนักงานควบคุมเอกสารเลือกเมนู “มอบหมายงาน”
|
||||
เลือกเอกสารในระบบ
|
||||
เลือก Users กำหนดวันสิ้นสุดงาน
|
||||
กดปุ่ม “มอบหมายงาน”
|
||||
ระบบบันทึกเอกสารและแสดงผลการมอบหมายงานสำเร็จ
|
||||
|
||||
Postconditions: งานที่มอยหมาย จัดเก็บในระบบและสามารถค้นหาได้
|
||||
|
||||
#### 📘 Use Case: Search Document
|
||||
|
||||
Actor: ผู้ใช้งานทั่วไป
|
||||
Description: ผู้ใช้งานสามารถค้นหาเอกสารจากระบบด้วยคำสำคัญหรือฟิลเตอร์
|
||||
Preconditions: ผู้ใช้งานต้องเข้าสู่ระบบ
|
||||
Main Flow:
|
||||
|
||||
ผู้ใช้งานกรอกคำค้นหรือเลือกฟิลเตอร์ (หมวดหมู่, วันที่, ผู้สร้าง, ผู้ได้รับมอบหมายงาน, สถานะ, title, subject)
|
||||
กดปุ่ม “ค้นหา”
|
||||
ระบบแสดงรายการเอกสารที่ตรงกับเงื่อนไข
|
||||
|
||||
Postconditions: ผู้ใช้งานสามารถเปิดดูหรือดาวน์โหลดเอกสารที่ค้นพบได้
|
||||
|
||||
#### 📘 Use Case: Manage Access
|
||||
|
||||
Actor: ผู้ดูแลระบบโครงการ (superadmin) / ผู้ดูแลระบบขององค์กร (admin)
|
||||
Description: ผู้ดูแลระบบสามารถกำหนดสิทธิ์การเข้าถึงเอกสารให้กับผู้ใช้งาน
|
||||
Preconditions: ผู้ดูแลระบบต้องเข้าสู่ระบบ
|
||||
Main Flow:
|
||||
|
||||
ผู้ดูแลระบบเลือกเอกสาร
|
||||
กด “จัดการสิทธิ์”
|
||||
เลือกผู้ใช้งานและกำหนดสิทธิ์ (อ่าน, แก้ไข, ลบ)
|
||||
กด “บันทึก”
|
||||
|
||||
Postconditions: สิทธิ์การเข้าถึงเอกสารถูกปรับตามที่กำหนด
|
||||
|
||||
#### 📘 Use Case: View Document History
|
||||
|
||||
Actor: ผู้ใช้งานทั่วไป / ผู้ดูแลระบบ
|
||||
Description: ผู้ใช้งานสามารถดูประวัติการใช้งานเอกสาร เช่น การแก้ไข การดาวน์โหลด
|
||||
Preconditions: ผู้ใช้งานต้องมีสิทธิ์เข้าถึงเอกสาร
|
||||
Main Flow:
|
||||
|
||||
ผู้ใช้งานเปิดเอกสาร
|
||||
เลือก “ดูประวัติ”
|
||||
ระบบแสดงรายการกิจกรรมที่เกี่ยวข้องกับเอกสาร
|
||||
|
||||
Postconditions: ผู้ใช้งานสามารถตรวจสอบการเปลี่ยนแปลงย้อนหลังได้
|
||||
|
||||
### 🔄 0.7 Workflow อัตโนมัติในระบบ DMS
|
||||
|
||||
✅ ประโยชน์ของ Workflow อัตโนมัติใน DMS
|
||||
|
||||
- ลดภาระงานซ้ำ ๆ ของผู้ใช้งาน
|
||||
- เพิ่มความปลอดภัยและการควบคุมเอกสาร
|
||||
- เพิ่มความเร็วในการดำเนินงาน
|
||||
- ลดข้อผิดพลาดจากการทำงานด้วยมือ
|
||||
|
||||
#### 🧩 Workflow: 1. Document treat Workflow
|
||||
|
||||
กรณี: เมื่อมีการอัปโหลดเอกสารต้องได้รับการมอบหมายงานให้กับ พนักงานภายในองค์กรณ์
|
||||
ขั้นตอนอัตโนมัติ:
|
||||
|
||||
1. ผู้ใช้งานอัปโหลดเอกสารและเลือก “มอบหมายงาน”
|
||||
2. ระบบส่งแจ้งเตือนไปยังผู้ได้รับมอบหมายงาน
|
||||
3. ผู้อนุมัติสามารถตรวจสอบและกด “ตรวจสอบแล้ว”
|
||||
4. ระบบบันทึกสถานะเอกสารและ ส่งต่อ ไปยัง องกรณือื่น ตามลำดับ เมื่อได้ผลและจัดทำเอกสารตอบแล้ว จึงแจ้งผลกลับไปยังผู้ส่ง
|
||||
|
||||
#### 📥 Workflow: 2. Auto Tagging & Categorization
|
||||
|
||||
กรณี: เอกสารที่อัปโหลดมีชื่อหรือเนื้อหาที่ตรงกับหมวดหมู่ที่กำหนดไว้
|
||||
ขั้นตอนอัตโนมัติ:
|
||||
|
||||
เมื่ออัปโหลดเอกสาร ระบบวิเคราะห์ชื่อไฟล์หรือเนื้อหา
|
||||
ระบบกำหนดหมวดหมู่และแท็กให้โดยอัตโนมัติ เช่น “ใบเสนอราคา” → หมวด “การเงิน”
|
||||
ผู้ใช้งานสามารถแก้ไขได้หากต้องการ
|
||||
|
||||
#### 🔐 Workflow: 3. Access Control Workflow
|
||||
|
||||
กรณี: เอกสารที่มีความลับสูงต้องจำกัดการเข้าถึง
|
||||
ขั้นตอนอัตโนมัติ:
|
||||
|
||||
เมื่ออัปโหลดเอกสารที่มีคำว่า “ลับ” หรือ “Confidential”
|
||||
ระบบกำหนดสิทธิ์เริ่มต้นให้เฉพาะผู้ใช้งานระดับผู้จัดการขึ้นไป
|
||||
ระบบแจ้งเตือนผู้ดูแลระบบให้ตรวจสอบสิทธิ์เพิ่มเติม
|
||||
|
||||
#### 📤 Workflow: 4. Expiry & Archiving Workflow
|
||||
|
||||
กรณี: เอกสารที่มีอายุการใช้งาน เช่น สัญญา หรือใบอนุญาต
|
||||
ขั้นตอนอัตโนมัติ:
|
||||
|
||||
เมื่ออัปโหลดเอกสาร ผู้ใช้งานระบุวันหมดอายุ
|
||||
ระบบแจ้งเตือนก่อนหมดอายุล่วงหน้า เช่น 30 วัน
|
||||
เมื่อถึงวันหมดอายุ ระบบย้ายเอกสารไปยังหมวด “Archive” โดยอัตโนมัติ
|
||||
|
||||
#### 📊 Workflow: 5. Audit Trail & Notification Workflow
|
||||
|
||||
กรณี: มีการแก้ไขหรือดาวน์โหลดเอกสารสำคัญ
|
||||
ขั้นตอนอัตโนมัติ:
|
||||
|
||||
ทุกการกระทำกับเอกสาร (เปิด, แก้ไข, ลบ) จะถูกบันทึกใน Audit Log
|
||||
หากเอกสารถูกแก้ไขโดยผู้ใช้งานที่ไม่ใช่เจ้าของ ระบบแจ้งเตือนเจ้าของเอกสารทันที
|
||||
|
||||
## 🛠️ 1. DMS Architecture Deep Dive (Backend + Frontend)
|
||||
|
||||
### 1.1 Executive Summary
|
||||
|
||||
- Reverse proxy (Nginx/NPM) เผยแพร่ Frontend (Next.js) และ Backend (Node.js/Express) ผ่าน HTTPS (HSTS)
|
||||
- Backend เชื่อม MariaDB 10.11 (ข้อมูลหลัก DMS) และแยก n8n + Postgres 16 สำหรับ workflow
|
||||
- RBAC/ABAC ถูกบังคับใช้งานใน middleware + มีชุด SQL (tables → triggers → procedures → views → seed)
|
||||
- ไฟล์จริง (PDF/DWG) เก็บนอก webroot ที่ /share/dms‑data พร้อมมาตรฐานการตั้งชื่อ+โฟลเดอร์
|
||||
- Dev/Prod แยกชัดเจนผ่าน Docker multi‑stage + docker‑compose + โฟลเดอร์ persist logs/config/certs
|
||||
|
||||
### 1.2 Runtime Topology & Trust Boundaries
|
||||
|
||||
```text
|
||||
Internet Clients (Browser)
|
||||
│ HTTPS 443 (HSTS) [QNAP mgmt = 8443]
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Reverse Proxy Layer │
|
||||
│ ├─ Nginx (Alpine) or Nginx Proxy Manager (NPM) │
|
||||
│ ├─ TLS (LE cert; SAN multi‑subdomain) │
|
||||
│ └─ Routes: │
|
||||
│ • /, /_next/* → Frontend (Next.js :3000) │
|
||||
│ • /api/* → Backend (Express :3001) │
|
||||
│ • /pma/* → phpMyAdmin │
|
||||
│ • /n8n/* → n8n (Workflows) │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
│ │
|
||||
│ └──────────┐
|
||||
▼ │
|
||||
Frontend (Next.js) │
|
||||
│ Cookie-based Auth (HttpOnly) │
|
||||
▼ ▼
|
||||
Backend (Node/Express ESM) ─────────► MariaDB 10.11
|
||||
│ │
|
||||
└────────────────────────────────────┘
|
||||
Project data (.pdf/.dwg) @ /share/dms-data
|
||||
|
||||
n8n (workflows) ──► Postgres 16 (separate DB for automations)
|
||||
```
|
||||
|
||||
==Trust Boundaries==
|
||||
|
||||
- Public zone: Internet ↔ Reverse proxy
|
||||
- App zone: Reverse proxy ↔ FE/BE containers (internal Docker network)
|
||||
- # Data zone: Backend ↔ Databases (MariaDB, Postgres) + /share/dms-data
|
||||
|
||||
### 1.3 Frontend: Next.js (ESM) / React.js
|
||||
|
||||
#### 1.3.1 Stack & Key libs
|
||||
|
||||
- Next.js (App Router), React, ESM
|
||||
- Tailwind CSS, PostCSS, shadcn/ui (components.json)
|
||||
- Fetch API (credentials include) → Cookie Auth (HttpOnly)
|
||||
|
||||
#### 1.3.2 Directory Layout
|
||||
|
||||
```text
|
||||
/frontend/
|
||||
├─ app/
|
||||
│ ├─ login/
|
||||
│ ├─ dashboard/
|
||||
│ ├─ users/
|
||||
│ ├─ correspondences/
|
||||
│ ├─ health/
|
||||
│ └─ layout.tsx / page.tsx (ตาม App Router)
|
||||
├─ public/
|
||||
├─ Dockerfile (multi-stage: dev/prod)
|
||||
├─ package.json
|
||||
├─ next.config.js
|
||||
└─ ...
|
||||
```
|
||||
|
||||
#### 1.3.3 Routing & Layouts
|
||||
|
||||
- Public /login, /health
|
||||
- Protected: /dashboard, /users, /correspondences, ... (client-side guard)
|
||||
- เก็บ middleware.ts (ของเดิม) เพื่อหลีกเลี่ยง regression; ใช้ client‑guard + server action อย่างระมัดระวัง
|
||||
|
||||
#### 1.3.4 Auth Flow (Cookie-based)
|
||||
|
||||
1. ผู้ใช้ submit form /login → POST /api/auth/login (Backend)
|
||||
2. Backend set HttpOnly cookie (JWT) + SameSite=Lax/Strict, Secure
|
||||
3. หน้า protected เรียก GET /api/auth/me เพื่อตรวจสอบสถานะ
|
||||
4. หาก 401 → redirect → /login
|
||||
|
||||
**CORS/Fetch**: เเปิด credentials: 'include' ทุกครั้ง, ตั้ง NEXT_PUBLIC_API_BASE เป็น origin ของ backend ผ่าน proxy (เช่น https://lcbp3.np-dms.work)
|
||||
|
||||
#### 1.3.5 UI/UX
|
||||
|
||||
- Sea‑blue palette, sidebar พับได้, card‑based KPI
|
||||
- ตารางข้อมูลเตรียมรองรับ server‑side DataTables\*\*
|
||||
- shadcn/ui: Button, Card, Badge, Tabs, Dropdown, Tooltip, Switch, etc.
|
||||
|
||||
#### 1.3.6 Config & ENV
|
||||
|
||||
- NEXT_PUBLIC_API_BAS (ex: https://lcbp3.np-dms.work)
|
||||
- Build output แยก dev/prod; ระวัง EACCES บน QNAP → ใช้ user node + ปรับสิทธิ์โวลุ่ม .next/\*
|
||||
|
||||
#### 1.3.7 Error Handling & Observability (FE)
|
||||
|
||||
- Global error boundary (app router) + toast/alert patterns
|
||||
- Network layer: แยก handler สำหรับ 401/403/500 + retry/backoff ที่จำเป็น
|
||||
- Metrics (optional): web‑vitals, UX timing (เก็บฝั่ง n8n หรือ simple logging)
|
||||
|
||||
---
|
||||
|
||||
### 1.4 Backend Architecture (Node.js ESM / Express)
|
||||
|
||||
#### 1.4.1 Stack & Structure
|
||||
|
||||
- Node 20.x, ESM modules, Express\*\*
|
||||
- mysql2/promise, jsonwebtoken, cookie-parser, cors, helmet, winston/morgan
|
||||
|
||||
```text
|
||||
/backend/
|
||||
├─ src/
|
||||
│ ├─ index.js # bootstrap server, CORS, cookies, health
|
||||
│ ├─ routes/
|
||||
│ │ ├─ auth.js # /api/auth/* (login, me, logout)
|
||||
│ │ ├─ users.js # /api/users/*
|
||||
│ │ ├─ correspondences.js # /api/correspondences/*
|
||||
│ │ ├─ drawings.js # /api/drawings/*
|
||||
│ │ ├─ rfas.js # /api/rfas/*
|
||||
│ │ └─ transmittals.js # /api/transmittals/*
|
||||
│ ├─ middleware/
|
||||
│ │ ├─ authGuard.js # verify JWT from cookie
|
||||
│ │ ├─ requirePermission.js# RBAC/ABAC enforcement
|
||||
│ │ ├─ errorHandler.js
|
||||
│ │ └─ requestLogger.js
|
||||
│ ├─ db/
|
||||
│ │ ├─ pool.js # createPool, sane defaults
|
||||
│ │ └─ models/ # query builders (User, Drawing, ...)
|
||||
│ ├─ utils/
|
||||
│ │ ├─ hash.js (bcrypt/argon2)
|
||||
│ │ ├─ jwt.js
|
||||
│ │ ├─ pagination.js
|
||||
│ │ └─ responses.js
|
||||
│ └─ config/
|
||||
│ └─ index.js # env, constants
|
||||
├─ Dockerfile
|
||||
└─ package.json
|
||||
```
|
||||
|
||||
#### 1.4.2 Request Lifecycle
|
||||
|
||||
1. helmet + cors (allow specific origin; credentials true)
|
||||
2. cookie-parser, json limit (e.g., 2MB)
|
||||
3. requestLogger → trace + response time
|
||||
4. Route handler → authGuard (protected) → requirePermission (per‑route) → Controller
|
||||
5. Error bubbles → errorHandler (JSON shape, status map)
|
||||
|
||||
#### 1.4.3 Auth & RBAC/ABAC
|
||||
|
||||
- JWT ใน HttpOnly cookie; Claims: sub (user_id), roles, exp
|
||||
- authGuard: ตรวจ token → แนบ req.user
|
||||
- requirePermission: เช็ค permission ตามเส้นทาง/วิธี; แผนขยาย ABAC (เช่น project scope, owner, doc state)
|
||||
- Roles/Permissions ถูก seed ใน SQL; มี view เมทริกซ์ เพื่อ debug (เช่น v_role_permission_matrix)
|
||||
|
||||
\*\*ตัวอย่าง pseudo requirePermission(permission)
|
||||
|
||||
```js
|
||||
export const requirePermission = (perm) => async (req, res, next) => {
|
||||
if (!req.user) return res.status(401).json({ error: "Unauthenticated" });
|
||||
const ok = await checkPermission(req.user.user_id, perm, req.context);
|
||||
if (!ok) return res.status(403).json({ error: "Forbidden" });
|
||||
return next();
|
||||
};
|
||||
```
|
||||
|
||||
#### 1.4.4 Database Access & Pooling
|
||||
|
||||
- createPool({ connectionLimit: 10~25, queueLimit: 0, waitForConnections: true })
|
||||
- ใช้ parameterized queries เสมอ; ปรับ sql_mode ที่จำเป็นใน my.cnf
|
||||
|
||||
#### 1.4.5 File Storage & Secure Download
|
||||
|
||||
- Root: /share/dms‑data
|
||||
- โครงโฟลเดอร์: {module}/{yyyy}/{mm}/{entityId}/ + ชื่อไฟล์ตามมาตรฐาน (เช่น DRW-code-REV-rev.pdf)
|
||||
- Endpoint download: ตรวจสิทธิ์ (RBAC/ABAC) → res.sendFile()/stream; ป้องกัน path traversal
|
||||
- MIME allowlist + size limit + virus scan (optional; ภายหลัง)
|
||||
|
||||
#### 1.4.6 Health & Readiness
|
||||
|
||||
- GET /api/health → { ok: true }
|
||||
- (optional) /api/ready ตรวจ DB ping + disk space (dms‑data)
|
||||
|
||||
#### 1.4.7 Config & ENV (BE)
|
||||
|
||||
- DB_HOST, DB_PORT, DB_USER, DB_PASS, DB_NAME
|
||||
- JWT_SECRET, COOKIE_NAME, COOKIE_SAMESITE, COOKIE_SECURE
|
||||
- CORS_ORIGIN, LOG_LEVEL, APP_BASE_URL
|
||||
- FILE_ROOT=/share/dms-data
|
||||
|
||||
#### 1.4.8 Logging
|
||||
|
||||
- Access log (morgan) + App log (winston) → /share/Container/dms/logs/backend/
|
||||
- รูปแบบ JSON (timestamp, level, msg, reqId) + daily rotation (logrotate/container‑side)
|
||||
|
||||
### 1.5 Database (MariaDB 10.11)
|
||||
|
||||
#### 1.5.1 Schema Overview (ย่อ)
|
||||
|
||||
- RBAC core: users, roles, permissions, user_roles, role_permissions
|
||||
- Domain: drawings, contracts, correspondences, rfas, transmittals, organizations, projects, ...
|
||||
- Audit: audit_logs (แผนขยาย), deleted_at (soft delete, แผนงาน)
|
||||
|
||||
```text
|
||||
[users]──<user_roles>──[roles]──<role_permissions>──[permissions]
|
||||
│
|
||||
└── activities/audit_logs (future expansion)
|
||||
|
||||
[drawings]──<mapping>──[contracts]
|
||||
[rfas]──<links>──[drawings]
|
||||
[correspondences] (internal/external flag)
|
||||
```
|
||||
|
||||
#### 1.5.2 Init SQL Pipeline
|
||||
|
||||
1. 01\_\*\_deploy_table_rbac.sql — สร้างตารางหลักทั้งหมด + RBAC
|
||||
2. 02\_\*\_triggers.sql — บังคับ data rules, auto‑audit fields
|
||||
3. 03\_\*\_procedures_handlers.sql — upsert/bulk handlers (เช่น sp_bulk_import_contract_dwg)
|
||||
4. 04\_\*\_views.sql — รายงาน/เมทริกซ์สิทธิ์ (v_role_permission_matrix, etc.)
|
||||
5. 05\_\*\_seed_data.sql — ค่าพื้นฐาน domain (project, categories, statuses)
|
||||
6. 06\_\*\_seed_users.sql — บัญชีเริ่มต้น (superadmin, editors, viewers)
|
||||
7. 07\_\*\_seed_contract_dwg.sql — ข้อมูลตัวอย่างแบบสัญญา
|
||||
|
||||
#### 1.5.3 Indexing & Performance
|
||||
|
||||
- Composite indexes ตามคอลัมน์ filter/sort (เช่น (project_id, updated_at DESC))
|
||||
- Full‑text index (optional) สำหรับ advanced search
|
||||
- Query plan review (EXPLAIN) + เพิ่ม covering index ตามรายงาน
|
||||
|
||||
#### 1.5.4 MySQL/MariaDB Config (my.cnf — แนวทาง)
|
||||
|
||||
```conf
|
||||
[mysqld]
|
||||
innodb_buffer_pool_size = 4G # ปรับตาม RAM/QNAP
|
||||
innodb_log_file_size = 512M
|
||||
innodb_flush_log_at_trx_commit = 1
|
||||
max_connections = 200
|
||||
sql_mode = STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
|
||||
character-set-server = utf8mb4
|
||||
collation-server = utf8mb4_unicode_ci
|
||||
```
|
||||
|
||||
> ปรับค่าให้เหมาะกับ workload จริง + เฝ้าดู IO/CPU ของ QNAP
|
||||
|
||||
#### 1.5.5 Backup/Restore
|
||||
|
||||
- Logical backup: mysqldump --routines --triggers --single-transaction
|
||||
- Physical (snapshot QNAP) + schedule ผ่าน n8n/cron
|
||||
- เก็บสำเนา off‑NAS (encrypted)
|
||||
|
||||
### 1.6 Reverse Proxy & TLS
|
||||
|
||||
#### 1.6.1 Nginx (Alpine) — ตัวอย่าง server block
|
||||
|
||||
> สำคัญ: บนสภาพแวดล้อมนี้ ให้ใช้คนละบรรทัด:
|
||||
> listen 443 ssl;
|
||||
> http2 on;
|
||||
> หลีกเลี่ยง listen 443 ssl http2;
|
||||
|
||||
```conf
|
||||
server {
|
||||
listen 80;
|
||||
server_name lcbp3.np-dms.work;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
http2 on;
|
||||
server_name lcbp3.np-dms.work;
|
||||
|
||||
ssl_certificate /etc/nginx/certs/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/certs/privkey.pem;
|
||||
add_header Strict-Transport-Security "max-age=63072000; preload" always;
|
||||
|
||||
# Frontend
|
||||
location / {
|
||||
proxy_pass http://frontend:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Next.js static
|
||||
location /_next/ {
|
||||
proxy_pass http://frontend:3000;
|
||||
}
|
||||
|
||||
# Backend API
|
||||
location /api/ {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
proxy_pass http://backend:3001;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# phpMyAdmin (sub-path)
|
||||
location /pma/ {
|
||||
proxy_pass http://phpmyadmin:80/;
|
||||
}
|
||||
|
||||
# n8n
|
||||
location /n8n/ {
|
||||
proxy_pass http://n8n:5678/;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.6.2 Nginx Proxy Manager (NPM) — Tips
|
||||
|
||||
- ระวังอย่าใส่ proxy_http_version ซ้ำซ้อน (duplicate directive) ใน Advanced
|
||||
- ถ้าต้องแก้ไฟล์ด้านใน NPM → ระวังไฟล์ใน /data/nginx/proxy_host/\*.conf
|
||||
- จัดการ certificate / SAN หลาย sub‑domain ใน UI แต่ mainten ดีเรื่อง symlink/renew
|
||||
|
||||
#### 1.6.3 TLS & Certificates
|
||||
|
||||
- Let’s Encrypt (HTTP‑01 webroot/standalone) + HSTS
|
||||
- QNAP mgmt เปลี่ยนเป็น 8443 → พอร์ต 443 public ว่างสำหรับ Nginx/NPM
|
||||
|
||||
### 1.7 Docker Compose Topology
|
||||
|
||||
#### 1.7.1 Services (สรุป)
|
||||
|
||||
- frontend (Next.js) :3000
|
||||
- backend (Express) :3001
|
||||
- mariadb (10.11) :3306 (internal)
|
||||
- phpmyadmin :80 (internal)
|
||||
- nginx or npm :80/443 (published)
|
||||
- n8n :5678 (internal)
|
||||
- postgres_n8n (16-alpine)
|
||||
- pgadmin4
|
||||
|
||||
#### 1.7.2 Volumes & Paths
|
||||
|
||||
```text
|
||||
/share/Container/dms/
|
||||
├─ mariadb/data
|
||||
├─ mariadb/init/*.sql
|
||||
├─ backend/ (code)
|
||||
├─ frontend/ (code)
|
||||
├─ phpmyadmin/{sessions,tmp,config.user.inc.php}
|
||||
├─ nginx/{nginx.conf,dms.conf,certs/}
|
||||
├─ n8n, n8n-postgres, n8n-cache
|
||||
└─ logs/{backend,frontend,nginx,pgadmin,phpmyadmin,postgres_n8n}
|
||||
/share/dms-data (pdf/dwg storage)
|
||||
```
|
||||
|
||||
#### 1.7.3 Healthchecks (suggested)
|
||||
|
||||
- backend:
|
||||
|
||||
```sh
|
||||
curl http://localhost:3001/api/health
|
||||
```
|
||||
|
||||
- frontend: curl /health (simple JSON)
|
||||
- mariadb: mysqladmin ping with credentials
|
||||
- nginx: nginx -t at startup
|
||||
|
||||
#### 1.7.4 Security Hardening
|
||||
|
||||
- รัน container ด้วย user non‑root (user: node สำหรับ FE/BE)
|
||||
- จำกัด capabilities; read‑only FS (ยกเว้นโวลุ่มจำเป็น)
|
||||
- เฉพาะ backend เมานต์ /share/dms-data
|
||||
|
||||
### 1.8 Observability, Ops, and Troubleshooting
|
||||
|
||||
#### 1.8.1 Logs
|
||||
|
||||
- Frontend → /logs/frontend/\*
|
||||
- Backend → /logs/backend/\* (app/access/error)
|
||||
- Nginx/NPM → /logs/nginx/\*
|
||||
- MariaDB → default datadir log + slow query (เปิดใน my.cnf หากต้องการ)
|
||||
|
||||
#### 1.8.2 Common Issues & Playbooks
|
||||
|
||||
- 401 Unauthenticated: ตรวจ authGuard → JWT cookie มี/หมดอายุ → เวลา server/FE sync → CORS credentials: true
|
||||
- EACCES Next.js: สิทธิ์ .next/\* + run as`node, โวลุ่ม map ถูก user:group
|
||||
- NPM duplicate directive: ลบซ้ำ proxy_http_version ใน Advanced / ตรวจ proxy_host/\*.conf
|
||||
- LE cert path/symlink: ตรวจ /etc/letsencrypt/live/npm-\* symlink ชี้ถูก
|
||||
- DB field not found: ตรวจ schema vs code (migration/init SQL) → sync ให้ตรง
|
||||
|
||||
#### 1.8.3 Performance Guides
|
||||
|
||||
- Backend: keep‑alive, gzip/deflate at proxy, pool 10–25, paginate, avoid N+1
|
||||
- Frontend: prefetch critical routes, cache static, image optimization
|
||||
- DB: เพิ่ม index จุด filter, analyze query (EXPLAIN), ปรับ buffer pool
|
||||
|
||||
### 1.9 Security & Compliance
|
||||
|
||||
- HTTPS only + HSTS (preload)
|
||||
- CORS: allow list เฉพาะ FE origin; Access-Control-Allow-Credentials: true
|
||||
- Cookie: HttpOnly, Secure, SameSite=Lax/Strict
|
||||
- Input Validation: celebrate/zod (optional) + sanitize
|
||||
- Rate limiting: per IP/route (optional)
|
||||
- AuditLog: วางแผนเพิ่ม ครอบคลุม CRUD + mapping (actor, action, entity, before/after)
|
||||
- Backups: DB + /share/dms-data + config (encrypted off‑NAS)
|
||||
|
||||
### 1.10 Backlog → Architecture Mapping
|
||||
|
||||
1. RBAC Enforcement ครบ → เติม requirePermission ทุก route + test matrix ผ่าน view
|
||||
2. AuditLog ครบ CRUD/Mapping → trigger + table audit_logs + BE hook
|
||||
3. Upload/Download จริงของ Drawing Revisions → BE endpoints + virus scan (optional)
|
||||
4. Dashboard KPI → BE summary endpoints + FE cards/charts
|
||||
5. Server‑side DataTables → paging/sort/filter + indexesรองรับ
|
||||
6. รายงาน Export CSV/Excel/PDF → BE export endpoints + FE buttons
|
||||
7. Soft delete (deleted_at) → BE filter default scope + restore endpoint
|
||||
8. Validation เข้ม → celebrate/zod schema + consistent error shape
|
||||
9. Indexing/Perf → slow query log + EXPLAIN review
|
||||
10. Job/Cron Deadline Alerts → n8n schedule + SMTP
|
||||
|
||||
### 1.11 Port & ENV Matrix (Quick Ref)
|
||||
|
||||
| Component | Ports | Key ENV |
|
||||
| Nginx/NPM | 80/443 (public) | SSL paths, HSTS |
|
||||
| Frontend | 3000 (internal) | NEXT*PUBLIC_API_BASE |
|
||||
| Backend | 3001 (internal) | DB*\*, JWT*SECRET, CORS_ORIGIN, FILE_ROOT |
|
||||
| MariaDB | 3306 (internal) | MY_CNF, credentials |
|
||||
| n8n | 5678 (internal) | N8N*, webhook URL under /n8n/ |
|
||||
| Postgres | 5432 (internal) | n8n DB |
|
||||
|
||||
QNAP mgmt: 8443 (already moved)
|
||||
|
||||
### 1.12 Sample Snippets
|
||||
|
||||
#### 1.12.1 Backend CORS (credentials)
|
||||
|
||||
```js
|
||||
app.use(
|
||||
cors({
|
||||
origin: ["https://lcbp3.np-dms.work"],
|
||||
credentials: true,
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
#### 1.12.2 Secure Download (guarded)
|
||||
|
||||
```js
|
||||
router.get(
|
||||
"/files/:module/:id/:filename",
|
||||
authGuard,
|
||||
requirePermission("file.read"),
|
||||
async (req, res) => {
|
||||
const { module, id, filename } = req.params;
|
||||
// 1) ABAC: verify user can access this module/entity
|
||||
const ok = await canReadFile(req.user.user_id, module, id);
|
||||
if (!ok) return res.status(403).json({ error: "Forbidden" });
|
||||
|
||||
const abs = path.join(FILE_ROOT, module, id, filename);
|
||||
if (!abs.startsWith(FILE_ROOT))
|
||||
return res.status(400).json({ error: "Bad path" });
|
||||
return res.sendFile(abs);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
#### 1.12.3 Healthcheck
|
||||
|
||||
```js
|
||||
router.get("/health", (req, res) => res.json({ ok: true }));
|
||||
```
|
||||
|
||||
### 13 Deployment Workflow (Suggested)
|
||||
|
||||
1. Git (Gitea) branch strategy feature/\* → PR → main
|
||||
2. Build images (dev/prod) via Dockerfile multi‑stage; pin Node/MariaDB versions
|
||||
3. docker compose up -d --build จาก /share/Container/dms
|
||||
4. Validate: /health, /api/health, login roundtrip
|
||||
5. Monitor logs + baseline perf; run SQL smoke tests (views/triggers/procs)
|
||||
|
||||
### 14 Appendix
|
||||
|
||||
- Naming conventions: snake_case DB, camelCase JS
|
||||
- Timezones: store UTC in DB; display in app TZ (+07:00)
|
||||
- Character set: UTF‑8 (utf8mb4_unicode_ci)
|
||||
- Large file policy: size limit (e.g., 50–200MB), allowlist extensions
|
||||
- Retention: archive strategy for old revisions (optional)
|
||||
|
||||
## บทบาท: คุณคือ Programmer และ Document Engineer ที่เชี่ยวชาญ
|
||||
|
||||
1. การพัฒนาเว็บแอป (Web Application Development)
|
||||
2. Configuration of Container Station on QNAP
|
||||
3. Database: mariadb:10.11
|
||||
4. Database management: phpmyadmin:5-apache
|
||||
5. Backend: node:.js (ESM)
|
||||
6. Frontend: next.js, react
|
||||
7. Workflow automation: n8n:
|
||||
8. Workflow database: postgres:16-alpine
|
||||
9. Workflow database management: pgadmin4
|
||||
10. Reverse proxy: nginx:1.27-alpine
|
||||
11. linux on QNAP
|
||||
12. การจัดการฐานข้อมูล (Database Management)
|
||||
13. การวิเคราะห์ฐานข้อมูล (Database Analysis)
|
||||
14. การจัดการฐานข้อมูลเชิงสัมพันธ์ (Relational Databases)
|
||||
15. ภาษา SQL
|
||||
16. RBAC
|
||||
|
||||
## 2. ระบบที่ใช้
|
||||
|
||||
## Server
|
||||
|
||||
- ใช้ Container Station เป็น SERVER บน QNAP (Model: TS-473A, RAM: 32GB, CPU: AMD Ryzen V1500B 4 cores 8 threads) **เปลี่ยน port 443 ของ QNAP เป็น 8443 แล้ว**
|
||||
|
||||
## การพัฒนาโครงการ
|
||||
|
||||
- ด้วย Visual Studio Code บน Windows 11
|
||||
- ใช้ ๊ UI ของ Container Station เป็นหลัก
|
||||
|
||||
## โครงสร้างโฟลเดอร์ (บน QNAP)
|
||||
|
||||
/share/Container/dms/
|
||||
├─ docker-compose.yml # Create โดย UI Container Station
|
||||
├─ mariadb/
|
||||
│ ├─ data/ # ข้อมูลจริงของ MariaDB
|
||||
│ ├─ init/ # ข้อมูลเริ่มต้นของ MariaDB
|
||||
│ │ ├─ 01_dms_data_v5_1_deploy_table_rbac.sql # Create all data table & RBAC table here!
|
||||
│ │ ├─ 02_dms_data_v5_1_triggers.sql # Create all triggers here!
|
||||
│ │ ├─ 03_dms_data_v5_1_procedures_handlers.sql # Create all procedures here!
|
||||
│ │ ├─ 04_dms_data_v5_1_views.sql # Create all views here!
|
||||
│ │ ├─ 05 dms_data_v5_1_seeก_data.sql # Seed nescesary data here!
|
||||
│ │ ├─ 06_dms_data_v5_1_seed_users.sql # Seed users data here!
|
||||
│ │ └─ 07_dms_data_v5_1_seed_contract_dwg.sql # Seed contract drawing data here!
|
||||
│ └─ my.cnf
|
||||
├─ backend/
|
||||
│ ├─ app/
|
||||
│ ├─ src/
|
||||
│ │ ├─ db/
|
||||
│ │ │ └─models/
|
||||
│ │ ├─ middleware/
|
||||
│ │ ├─ routes/
|
||||
│ │ ├─ utils/
|
||||
│ │ └─ index.js
|
||||
│ ├─ Dockerfile
|
||||
│ ├─ package.json
|
||||
│ └─ package-lock.json # ไม่มี
|
||||
├─ frontend/
|
||||
│ ├─ app/
|
||||
│ │ ├─ correspondences/
|
||||
│ │ ├─ dashboard/
|
||||
│ │ ├─ health/
|
||||
│ │ ├─ login/
|
||||
│ │ └─ users/
|
||||
│ ├─ public/
|
||||
│ ├─ Dockerfile
|
||||
│ ├─ package.json
|
||||
│ ├─ package-lock.json # ไม่มี
|
||||
│ ├─ next.config.js
|
||||
│ └─ page.jsx
|
||||
├─ phpmyadmin/
|
||||
│ ├─ sessions/ # โฟลเดอร์เซสชันถาวรของ phpMyAdmin
|
||||
│ ├─ tmp/
|
||||
│ ├─ config.user.inc.php
|
||||
│ └─ zzz-custom.ini
|
||||
├─ nginx/
|
||||
│ ├─ certs/
|
||||
│ ├─ nginx.conf
|
||||
│ └─ dms.conf
|
||||
├─ n8n/
|
||||
├─ n8n-cache/
|
||||
├─ n8n-postgres/
|
||||
└─ logs/
|
||||
├─ backend/
|
||||
├─ frontend/
|
||||
├─ nginx/
|
||||
├─ pgadmin/
|
||||
├─ phpmyadmin/
|
||||
└─ postgres_n8n/
|
||||
/share/dms-data # เก็บข้อมมูล .pdf, .dwg แยกตาม correspondences, documents
|
||||
|
||||
# งานที่ต้องการ:
|
||||
|
||||
- ไม่ใช้ .env เด็ดขาด Container Station ไม่รองรับ และ docker-compose.yml ได้ทดสอบ รันบน Container station มาแล้ว
|
||||
- Code ของ backend ทั้งหมด
|
||||
- การทดสอบระบบ backend ทุกส่วน ให้พร้อม สำหรับ frontend
|
||||
|
||||
# กรณี 2: มี Git อยู่แล้ว (มี main อยู่)
|
||||
|
||||
2.1 อัปเดต main ให้ตรงล่าสุดก่อนแตกบร้านช์
|
||||
|
||||
cd /share/Container/dms
|
||||
git checkout main
|
||||
git pull --ff-only # ถ้าเชื่อม remote อยู่
|
||||
git tag -f stable-$(date +%F) # tag จุดเสถียรปัจจุบัน
|
||||
|
||||
2.2 แตก branch งาน Dashboard
|
||||
git checkout -b feature/dashboard-update-$(date +%y%m%d)
|
||||
git checkout -b feature/dashboard-update-251004
|
||||
|
||||
2.3 ทำงาน/คอมมิตตามปกติ
|
||||
|
||||
# แก้ไฟล์ frontend/app/dashboard/\* และที่เกี่ยวข้อง
|
||||
|
||||
git add frontend/app/dashboard
|
||||
git commit -m "feat(dashboard): เพิ่มส่วนจัดการ user"
|
||||
git push -u origin feature/dashboard-update-251004
|
||||
134
docs/backup/NestJS01.bak
Normal file
134
docs/backup/NestJS01.bak
Normal file
@@ -0,0 +1,134 @@
|
||||
You are a senior TypeScript programmer with experience in the NestJS framework and a preference for clean programming and design patterns.
|
||||
|
||||
Generate code, corrections, and refactorings that comply with the basic principles and nomenclature.
|
||||
|
||||
## TypeScript General Guidelines
|
||||
|
||||
### Basic Principles
|
||||
|
||||
- Use English for all code and documentation.
|
||||
- Always declare the type of each variable and function (parameters and return value).
|
||||
- Avoid using any.
|
||||
- Create necessary types.
|
||||
- Use JSDoc to document public classes and methods.
|
||||
- Don't leave blank lines within a function.
|
||||
- One export per file.
|
||||
|
||||
### Nomenclature
|
||||
|
||||
- Use PascalCase for classes.
|
||||
- Use camelCase for variables, functions, and methods.
|
||||
- Use kebab-case for file and directory names.
|
||||
- Use UPPERCASE for environment variables.
|
||||
- Avoid magic numbers and define constants.
|
||||
- Start each function with a verb.
|
||||
- Use verbs for boolean variables. Example: isLoading, hasError, canDelete, etc.
|
||||
- Use complete words instead of abbreviations and correct spelling.
|
||||
- Except for standard abbreviations like API, URL, etc.
|
||||
- Except for well-known abbreviations:
|
||||
- i, j for loops
|
||||
- err for errors
|
||||
- ctx for contexts
|
||||
- req, res, next for middleware function parameters
|
||||
|
||||
### Functions
|
||||
|
||||
- In this context, what is understood as a function will also apply to a method.
|
||||
- Write short functions with a single purpose. Less than 20 instructions.
|
||||
- Name functions with a verb and something else.
|
||||
- If it returns a boolean, use isX or hasX, canX, etc.
|
||||
- If it doesn't return anything, use executeX or saveX, etc.
|
||||
- Avoid nesting blocks by:
|
||||
- Early checks and returns.
|
||||
- Extraction to utility functions.
|
||||
- Use higher-order functions (map, filter, reduce, etc.) to avoid function nesting.
|
||||
- Use arrow functions for simple functions (less than 3 instructions).
|
||||
- Use named functions for non-simple functions.
|
||||
- Use default parameter values instead of checking for null or undefined.
|
||||
- Reduce function parameters using RO-RO
|
||||
- Use an object to pass multiple parameters.
|
||||
- Use an object to return results.
|
||||
- Declare necessary types for input arguments and output.
|
||||
- Use a single level of abstraction.
|
||||
|
||||
### Data
|
||||
|
||||
- Don't abuse primitive types and encapsulate data in composite types.
|
||||
- Avoid data validations in functions and use classes with internal validation.
|
||||
- Prefer immutability for data.
|
||||
- Use readonly for data that doesn't change.
|
||||
- Use as const for literals that don't change.
|
||||
|
||||
### Classes
|
||||
|
||||
- Follow SOLID principles.
|
||||
- Prefer composition over inheritance.
|
||||
- Declare interfaces to define contracts.
|
||||
- Write small classes with a single purpose.
|
||||
- Less than 200 instructions.
|
||||
- Less than 10 public methods.
|
||||
- Less than 10 properties.
|
||||
|
||||
### Exceptions
|
||||
|
||||
- Use exceptions to handle errors you don't expect.
|
||||
- If you catch an exception, it should be to:
|
||||
- Fix an expected problem.
|
||||
- Add context.
|
||||
- Otherwise, use a global handler.
|
||||
|
||||
### Testing
|
||||
|
||||
- Follow the Arrange-Act-Assert convention for tests.
|
||||
- Name test variables clearly.
|
||||
- Follow the convention: inputX, mockX, actualX, expectedX, etc.
|
||||
- Write unit tests for each public function.
|
||||
- Use test doubles to simulate dependencies.
|
||||
- Except for third-party dependencies that are not expensive to execute.
|
||||
- Write acceptance tests for each module.
|
||||
- Follow the Given-When-Then convention.
|
||||
|
||||
|
||||
## Specific to NestJS
|
||||
|
||||
### Basic Principles
|
||||
|
||||
- Use modular architecture.
|
||||
- Encapsulate the API in modules.
|
||||
- One module per main domain/route.
|
||||
- One controller for its route.
|
||||
- And other controllers for secondary routes.
|
||||
- A models folder with data types.
|
||||
- DTOs validated with class-validator for inputs.
|
||||
- Declare simple types for outputs.
|
||||
- A services module with business logic and persistence.
|
||||
- Entities with MikroORM for data persistence.
|
||||
- One service per entity.
|
||||
|
||||
- Common Module: Create a common module (e.g., @app/common) for shared, reusable code across the application.
|
||||
- This module should include:
|
||||
- Configs: Global configuration settings.
|
||||
- Decorators: Custom decorators for reusability.
|
||||
- DTOs: Common data transfer objects.
|
||||
- Guards: Guards for role-based or permission-based access control.
|
||||
- Interceptors: Shared interceptors for request/response manipulation.
|
||||
- Notifications: Modules for handling app-wide notifications.
|
||||
- Services: Services that are reusable across modules.
|
||||
- Types: Common TypeScript types or interfaces.
|
||||
- Utils: Helper functions and utilities.
|
||||
- Validators: Custom validators for consistent input validation.
|
||||
|
||||
- Core module functionalities:
|
||||
- Global filters for exception handling.
|
||||
- Global middlewares for request management.
|
||||
- Guards for permission management.
|
||||
- Interceptors for request processing.
|
||||
|
||||
### Testing
|
||||
|
||||
- Use the standard Jest framework for testing.
|
||||
- Write tests for each controller and service.
|
||||
- Write end to end tests for each api module.
|
||||
- Add a admin/test method to each controller as a smoke test.
|
||||
|
||||
|
||||
33
docs/backup/NextJS01.bak
Normal file
33
docs/backup/NextJS01.bak
Normal file
@@ -0,0 +1,33 @@
|
||||
You are a Senior Front-End Developer and an Expert in ReactJS, NextJS, JavaScript, TypeScript, HTML, CSS and modern UI/UX frameworks (e.g., TailwindCSS, Shadcn, Radix). You are thoughtful, give nuanced answers, and are brilliant at reasoning. You carefully provide accurate, factual, thoughtful answers, and are a genius at reasoning.
|
||||
|
||||
- Follow the user’s requirements carefully & to the letter.
|
||||
- First think step-by-step - describe your plan for what to build in pseudocode, written out in great detail.
|
||||
- Confirm, then write code!
|
||||
- Always write correct, best practice, DRY principle (Dont Repeat Yourself), bug free, fully functional and working code also it should be aligned to listed rules down below at Code Implementation Guidelines .
|
||||
- Focus on easy and readability code, over being performant.
|
||||
- Fully implement all requested functionality.
|
||||
- Leave NO todo’s, placeholders or missing pieces.
|
||||
- Ensure code is complete! Verify thoroughly finalised.
|
||||
- Include all required imports, and ensure proper naming of key components.
|
||||
- Be concise Minimize any other prose.
|
||||
- If you think there might not be a correct answer, you say so.
|
||||
- If you do not know the answer, say so, instead of guessing.
|
||||
|
||||
### Coding Environment
|
||||
The user asks questions about the following coding languages:
|
||||
- ReactJS
|
||||
- NextJS
|
||||
- JavaScript
|
||||
- TypeScript
|
||||
- TailwindCSS
|
||||
- HTML
|
||||
- CSS
|
||||
|
||||
### Code Implementation Guidelines
|
||||
Follow these rules when you write code:
|
||||
- Use early returns whenever possible to make the code more readable.
|
||||
- Always use Tailwind classes for styling HTML elements; avoid using CSS or tags.
|
||||
- Use “class:” instead of the tertiary operator in class tags whenever possible.
|
||||
- Use descriptive variable and function/const names. Also, event functions should be named with a “handle” prefix, like “handleClick” for onClick and “handleKeyDown” for onKeyDown.
|
||||
- Implement accessibility features on elements. For example, a tag should have a tabindex=“0”, aria-label, on:click, and on:keydown, and similar attributes.
|
||||
- Use consts instead of functions, for example, “const toggle = () =>”. Also, define a type if possible.
|
||||
171
docs/backup/backend_setup.bak
Normal file
171
docs/backup/backend_setup.bak
Normal file
@@ -0,0 +1,171 @@
|
||||
|
||||
# การติดตั้งและสร้างโปรเจกต์ (Project Initialization)
|
||||
|
||||
1. ข้อกำหนดเบื้องต้น (Prerequisites)
|
||||
ก่อนเริ่ม, ตรวจสอบให้แน่ใจว่าคุณมีเครื่องมือเหล่านี้ติดตั้งบน Windows 11 ของคุณแล้ว:
|
||||
Node.js: เวอร์ชัน 18.x หรือสูงกว่า
|
||||
NPM หรือ Yarn: ตัวจัดการ Package (มักจะมาพร้อมกับ Node.js)
|
||||
NestJS CLI: เครื่องมือ Command-line สำหรับ NestJS
|
||||
หากยังไม่ได้ติดตั้ง NestJS CLI, ให้เปิด VS Code Terminal หรือ Command Prompt แล้วรันคำสั่ง:
|
||||
|
||||
bash
|
||||
npm install -g @nestjs/cli
|
||||
|
||||
## 2.1 สร้างโปรเจกต์ NestJS ใหม่
|
||||
ไปที่ Directory ที่คุณต้องการเก็บโปรเจกต์ (เช่น C:\Users\YourUser\Development\)
|
||||
ใช้ NestJS CLI เพื่อสร้างโปรเจกต์ใหม่ ผมจะตั้งชื่อว่า backend-np-dms นะครับ:
|
||||
|
||||
Bash
|
||||
nest new backend
|
||||
|
||||
ระบบจะถามว่าต้องการใช้ Package Manager ตัวไหน แนะนำให้เลือก npm, รอจนกว่ากระบวนการจะเสร็จสิ้น คุณจะได้โฟลเดอร์ backend ที่มีโครงสร้างพื้นฐานของ NestJS พร้อมใช้งาน
|
||||
|
||||
## 2.2 ติดตั้ง Dependencies ที่จำเป็น
|
||||
ตอนนี้เราจะติดตั้ง Modules ทั้งหมดที่คุณวางแผนไว้ในคราวเดียว เพื่อให้โปรเจกต์พร้อมสำหรับการพัฒนา
|
||||
|
||||
เปิด Terminal ใน VS Code ภายในโฟลเดอร์ backend (cd backend) แล้วรันคำสั่งต่อไปนี้:
|
||||
|
||||
* Database & ORM (TypeORM for MariaDB/MySQL)
|
||||
Bash
|
||||
npm install @nestjs/typeorm typeorm mysql2
|
||||
|
||||
* Configuration Management (สำหรับ .env)
|
||||
npm install @nestjs/config
|
||||
|
||||
* API Documentation
|
||||
npm install @nestjs/swagger
|
||||
|
||||
* Validation & Transformation
|
||||
npm install class-validator class-transformer
|
||||
|
||||
* Security
|
||||
npm install helmet
|
||||
|
||||
* Authentication (JWT)
|
||||
npm install @nestjs/passport passport passport-jwt @nestjs/jwt
|
||||
npm install --save-dev @types/passport-jwt
|
||||
|
||||
คำอธิบาย:
|
||||
@nestjs/typeorm typeorm mysql2: สำหรับเชื่อมต่อและจัดการฐานข้อมูล MariaDB
|
||||
@nestjs/config: สำหรับจัดการ Environment Variables (เช่น ข้อมูลการเชื่อมต่อ DB) ผ่านไฟล์ .env
|
||||
@nestjs/swagger: สำหรับสร้างหน้าเอกสาร API (Swagger/OpenAPI) โดยอัตโนมัติ
|
||||
class-validator class-transformer: ใช้สำหรับตรวจสอบความถูกต้องของข้อมูลที่ส่งเข้ามาใน API (Request Body Validation)
|
||||
helmet: ช่วยเพิ่มความปลอดภัยพื้นฐานโดยการตั้งค่า HTTP Headers ที่เหมาะสม
|
||||
@nestjs/passport, @nestjs/jwt: เครื่องมือมาตรฐานสำหรับทำระบบ Authentication
|
||||
|
||||
## 2.3: ตั้งค่าพื้นฐานในโปรเจกต์ (Initial Configuration)
|
||||
ตอนนี้เราจะแก้ไขไฟล์หลักๆ เพื่อเปิดใช้งาน Modules ที่ติดตั้งไป
|
||||
|
||||
1. สร้างไฟล์ Environment (.env)
|
||||
ที่ราก (root) ของโปรเจกต์ backend-np-dms, สร้างไฟล์ใหม่ชื่อ .env และใส่ข้อมูลการเชื่อมต่อฐานข้อมูลของคุณ (ข้อมูลนี้จะไม่ถูกเก็บใน Git):
|
||||
|
||||
.env
|
||||
|
||||
Code snippet
|
||||
|
||||
* Database Configuration
|
||||
DB_TYPE=mysql
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_USERNAME=your_db_user # <-- แก้ไขเป็น user ของคุณ
|
||||
DB_PASSWORD=your_db_password # <-- แก้ไขเป็น password ของคุณ
|
||||
DB_DATABASE=dms_db # <-- แก้ไขเป็นชื่อ database ของคุณ
|
||||
|
||||
* Application
|
||||
API_PORT=3001
|
||||
💡 Tip: หากคุณรัน MariaDB ผ่าน Docker, DB_HOST อาจจะเป็นชื่อ Service ของ Docker container (เช่น mariadb-container) หรือ IP ของ QNAP ของคุณ
|
||||
|
||||
2. แก้ไข app.module.ts เพื่อเชื่อมต่อ Database และ Config
|
||||
เปิดไฟล์ src/app.module.ts และแก้ไขให้เป็นตามนี้เพื่อ import ConfigModule และ TypeOrmModule:
|
||||
|
||||
src/app.module.ts
|
||||
|
||||
```TypeScript
|
||||
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
// 1. Config Module - สำหรับอ่าน .env (ต้องอยู่บนสุด)
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true, // ทำให้ ConfigService พร้อมใช้งานทั่วทั้งแอป
|
||||
envFilePath: '.env',
|
||||
}),
|
||||
|
||||
// 2. TypeORM Module - สำหรับเชื่อมต่อ Database
|
||||
TypeOrmModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
inject: [ConfigService],
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
type: 'mysql',
|
||||
host: configService.get<string>('DB_HOST'),
|
||||
port: configService.get<int>('DB_PORT'),
|
||||
username: configService.get<string>('DB_USERNAME'),
|
||||
password: configService.get<string>('DB_PASSWORD'),
|
||||
database: configService.get<string>('DB_DATABASE'),
|
||||
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
|
||||
synchronize: true, // สำหรับ Development เท่านั้น! จะสร้างตารางให้อัตโนมัติ
|
||||
logging: true,
|
||||
}),
|
||||
}),
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
})
|
||||
export class AppModule {}
|
||||
```
|
||||
⚠️ คำเตือน: synchronize: true สะดวกมากในช่วงพัฒนาเพราะมันจะปรับโครงสร้างตารางตาม Entity ให้อัตโนมัติ ห้ามใช้ใน Production เด็ดขาด เพราะอาจทำให้ข้อมูลหายได้ ใน Production ควรใช้ระบบ Migration แทน
|
||||
|
||||
3. แก้ไข main.ts เพื่อเปิดใช้งาน Swagger, Validation และ Security
|
||||
เปิดไฟล์ src/main.ts และเพิ่มการตั้งค่าต่างๆ เข้าไป:
|
||||
|
||||
src/main.ts
|
||||
|
||||
TypeScript
|
||||
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
import helmet from 'helmet';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
// เปิดใช้งาน CORS (Cross-Origin Resource Sharing)
|
||||
app.enableCors();
|
||||
|
||||
// เพิ่ม Helmet เพื่อความปลอดภัย
|
||||
app.use(helmet());
|
||||
|
||||
// ตั้งค่า Global Validation Pipe
|
||||
app.useGlobalPipes(new ValidationPipe({
|
||||
whitelist: true, // ตัด property ที่ไม่มีใน DTO ออก
|
||||
transform: true, // แปลงข้อมูลให้เป็น type ที่ระบุใน DTO
|
||||
}));
|
||||
|
||||
// ตั้งค่า Swagger API Documentation
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle('LCBP3-DMS API')
|
||||
.setDescription('The Document Management System API for LCBP3 Project')
|
||||
.setVersion('1.0')
|
||||
.addBearerAuth() // สำหรับ JWT
|
||||
.build();
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup('api-docs', app, document); // เข้าถึงได้ที่ /api-docs
|
||||
|
||||
// เริ่มรัน Server
|
||||
const port = process.env.API_PORT || 3001;
|
||||
await app.listen(port);
|
||||
console.log(`Application is running on: ${await app.getUrl()}`);
|
||||
}
|
||||
bootstrap();
|
||||
|
||||
curl -i -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username": "superadmin", "password": "Center#2025"}' \
|
||||
https://backend.np-dms.work/api/auth/login
|
||||
1728
docs/backup/data-dictionary-v1.5.1.md
Normal file
1728
docs/backup/data-dictionary-v1.5.1.md
Normal file
File diff suppressed because it is too large
Load Diff
1813
docs/backup/document-numbering-add.md
Normal file
1813
docs/backup/document-numbering-add.md
Normal file
File diff suppressed because it is too large
Load Diff
813
docs/backup/document-numbering.md
Normal file
813
docs/backup/document-numbering.md
Normal file
@@ -0,0 +1,813 @@
|
||||
# Document Numbering Implementation Guide
|
||||
|
||||
---
|
||||
title: 'Implementation Guide: Document Numbering System'
|
||||
version: 1.6.1
|
||||
status: implemented
|
||||
owner: Development Team
|
||||
last_updated: 2025-12-16
|
||||
related:
|
||||
|
||||
- specs/01-requirements/03.11-document-numbering.md
|
||||
- specs/04-operations/document-numbering-operations.md
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
เอกสารนี้อธิบาย implementation details สำหรับระบบ Document Numbering ตาม requirements ใน [03.11-document-numbering.md](file:///e:/np-dms/lcbp3/specs/01-requirements/03.11-document-numbering.md)
|
||||
|
||||
## Technology Stack
|
||||
|
||||
- **Backend Framework**: NestJS 10.x
|
||||
- **ORM**: TypeORM 0.3.x
|
||||
- **Database**: MariaDB 11.8
|
||||
- **Cache/Lock**: Redis 7.x + Redlock
|
||||
- **Message Queue**: BullMQ
|
||||
- **Monitoring**: Prometheus + Grafana
|
||||
|
||||
## 1. Database Implementation
|
||||
|
||||
### 1.1. Counter Table Schema
|
||||
|
||||
```sql
|
||||
CREATE TABLE document_number_formats (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
project_id INT NOT NULL,
|
||||
correspondence_type_id INT NULL, -- NULL indicates default format for the project
|
||||
format_template VARCHAR(100) NOT NULL,
|
||||
reset_sequence_yearly TINYINT(1) DEFAULT 1,
|
||||
description VARCHAR(255),
|
||||
created_at DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6),
|
||||
updated_at DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
|
||||
UNIQUE KEY idx_unique_project_type (project_id, correspondence_type_id),
|
||||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (correspondence_type_id) REFERENCES correspondence_types(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE document_number_counters (
|
||||
project_id INT NOT NULL,
|
||||
originator_organization_id INT NOT NULL,
|
||||
recipient_organization_id INT NULL,
|
||||
correspondence_type_id INT NOT NULL,
|
||||
sub_type_id INT DEFAULT 0,
|
||||
rfa_type_id INT DEFAULT 0,
|
||||
discipline_id INT DEFAULT 0,
|
||||
current_year INT NOT NULL,
|
||||
version INT DEFAULT 0 NOT NULL,
|
||||
last_number INT DEFAULT 0,
|
||||
|
||||
PRIMARY KEY (
|
||||
project_id,
|
||||
originator_organization_id,
|
||||
COALESCE(recipient_organization_id, 0),
|
||||
correspondence_type_id,
|
||||
sub_type_id,
|
||||
rfa_type_id,
|
||||
discipline_id,
|
||||
current_year
|
||||
),
|
||||
|
||||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (originator_organization_id) REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (recipient_organization_id) REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (correspondence_type_id) REFERENCES correspondence_types(id) ON DELETE CASCADE,
|
||||
|
||||
INDEX idx_counter_lookup (project_id, correspondence_type_id, current_year),
|
||||
INDEX idx_counter_org (originator_organization_id, current_year),
|
||||
|
||||
CONSTRAINT chk_last_number_positive CHECK (last_number >= 0),
|
||||
CONSTRAINT chk_current_year_valid CHECK (current_year BETWEEN 2020 AND 2100)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||||
COMMENT='ตารางเก็บ Running Number Counters';
|
||||
```
|
||||
|
||||
### 1.2. Audit Table Schema
|
||||
|
||||
```sql
|
||||
CREATE TABLE document_number_audit (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
document_id INT NULL COMMENT 'FK to documents (NULL initially, updated after doc creation)',
|
||||
generated_number VARCHAR(100) NOT NULL,
|
||||
counter_key JSON NOT NULL COMMENT 'Counter key used (JSON format)',
|
||||
template_used VARCHAR(200) NOT NULL,
|
||||
user_id INT NULL COMMENT 'FK to users (Allow NULL for system generation)',
|
||||
ip_address VARCHAR(45),
|
||||
user_agent TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
is_success BOOLEAN DEFAULT TRUE COMMENT 'Track success/failure status',
|
||||
|
||||
-- Performance & Error Tracking
|
||||
retry_count INT DEFAULT 0,
|
||||
lock_wait_ms INT COMMENT 'Lock acquisition time in milliseconds',
|
||||
total_duration_ms INT COMMENT 'Total generation time',
|
||||
fallback_used ENUM('NONE', 'DB_LOCK', 'RETRY') DEFAULT 'NONE',
|
||||
|
||||
INDEX idx_document_id (document_id),
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_created_at (created_at),
|
||||
FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
) ENGINE=InnoDB COMMENT='Document Number Generation Audit Trail';
|
||||
```
|
||||
|
||||
### 1.3. Error Log Table
|
||||
|
||||
```sql
|
||||
CREATE TABLE document_number_errors (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
error_type ENUM(
|
||||
'LOCK_TIMEOUT',
|
||||
'VERSION_CONFLICT',
|
||||
'DB_ERROR',
|
||||
'REDIS_ERROR',
|
||||
'VALIDATION_ERROR'
|
||||
) NOT NULL,
|
||||
error_message TEXT,
|
||||
stack_trace TEXT,
|
||||
context_data JSON COMMENT 'Request context (user, project, etc.)',
|
||||
user_id INT,
|
||||
ip_address VARCHAR(45),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
resolved_at TIMESTAMP NULL,
|
||||
|
||||
INDEX idx_error_type (error_type),
|
||||
INDEX idx_created_at (created_at),
|
||||
INDEX idx_user_id (user_id)
|
||||
) ENGINE=InnoDB COMMENT='Document Numbering Error Log';
|
||||
```
|
||||
|
||||
## 2. NestJS Implementation
|
||||
|
||||
### 2.1. Module Structure
|
||||
|
||||
```
|
||||
src/modules/document-numbering/
|
||||
├── document-numbering.module.ts
|
||||
├── controllers/
|
||||
│ └── document-numbering.controller.ts
|
||||
├── services/
|
||||
│ ├── document-numbering.service.ts
|
||||
│ ├── document-numbering-lock.service.ts
|
||||
│ ├── counter.service.ts
|
||||
│ ├── template.service.ts
|
||||
│ └── audit.service.ts
|
||||
├── entities/
|
||||
│ ├── document-number-counter.entity.ts
|
||||
│ ├── document-number-audit.entity.ts
|
||||
│ └── document-number-error.entity.ts
|
||||
├── dto/
|
||||
│ ├── generate-number.dto.ts
|
||||
│ └── update-template.dto.ts
|
||||
├── validators/
|
||||
│ └── template.validator.ts
|
||||
├── jobs/
|
||||
│ └── counter-reset.job.ts
|
||||
└── metrics/
|
||||
└── metrics.service.ts
|
||||
```
|
||||
|
||||
### 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)
|
||||
|
||||
#### 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).
|
||||
|
||||
#### 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
|
||||
|
||||
#### 2.2.4. Validate Number:
|
||||
* 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.
|
||||
|
||||
#### 2.2.6. Generate Audit Record:
|
||||
* 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.
|
||||
|
||||
### 2.3. TypeORM Entity
|
||||
|
||||
```typescript
|
||||
// File: src/modules/document-numbering/entities/document-number-counter.entity.ts
|
||||
import { Entity, Column, PrimaryColumn, VersionColumn } from 'typeorm';
|
||||
|
||||
@Entity('document_number_counters')
|
||||
export class DocumentNumberCounter {
|
||||
@PrimaryColumn({ name: 'project_id' })
|
||||
projectId: number;
|
||||
|
||||
@PrimaryColumn({ name: 'originator_organization_id' })
|
||||
originatorOrganizationId: number;
|
||||
|
||||
@PrimaryColumn({ name: 'recipient_organization_id', nullable: true })
|
||||
recipientOrganizationId: number | null;
|
||||
|
||||
@PrimaryColumn({ name: 'correspondence_type_id' })
|
||||
correspondenceTypeId: number;
|
||||
|
||||
@PrimaryColumn({ name: 'sub_type_id', default: 0 })
|
||||
subTypeId: number;
|
||||
|
||||
@PrimaryColumn({ name: 'rfa_type_id', default: 0 })
|
||||
rfaTypeId: number;
|
||||
|
||||
@PrimaryColumn({ name: 'discipline_id', default: 0 })
|
||||
disciplineId: number;
|
||||
|
||||
@PrimaryColumn({ name: 'current_year' })
|
||||
currentYear: number;
|
||||
|
||||
@VersionColumn({ name: 'version' })
|
||||
version: number;
|
||||
|
||||
@Column({ name: 'last_number', default: 0 })
|
||||
lastNumber: number;
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4. Redis Lock Service
|
||||
|
||||
```typescript
|
||||
// File: src/modules/document-numbering/services/document-numbering-lock.service.ts
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import Redlock from 'redlock';
|
||||
import { InjectRedis } from '@nestjs-modules/ioredis';
|
||||
import { Redis } from 'ioredis';
|
||||
|
||||
interface CounterKey {
|
||||
projectId: number;
|
||||
originatorOrgId: number;
|
||||
recipientOrgId: number | null;
|
||||
correspondenceTypeId: number;
|
||||
subTypeId: number;
|
||||
rfaTypeId: number;
|
||||
disciplineId: number;
|
||||
year: number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class DocumentNumberingLockService {
|
||||
private readonly logger = new Logger(DocumentNumberingLockService.name);
|
||||
private redlock: Redlock;
|
||||
|
||||
constructor(@InjectRedis() private readonly redis: Redis) {
|
||||
this.redlock = new Redlock([redis], {
|
||||
driftFactor: 0.01,
|
||||
retryCount: 5,
|
||||
retryDelay: 100,
|
||||
retryJitter: 50,
|
||||
});
|
||||
}
|
||||
|
||||
async acquireLock(counterKey: CounterKey): Promise<Redlock.Lock> {
|
||||
const lockKey = this.buildLockKey(counterKey);
|
||||
const ttl = 5000; // 5 วินาที
|
||||
|
||||
try {
|
||||
const lock = await this.redlock.acquire([lockKey], ttl);
|
||||
this.logger.debug(`Acquired lock: ${lockKey}`);
|
||||
return lock;
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to acquire lock: ${lockKey}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async releaseLock(lock: Redlock.Lock): Promise<void> {
|
||||
try {
|
||||
await lock.release();
|
||||
this.logger.debug('Released lock');
|
||||
} catch (error) {
|
||||
this.logger.warn('Failed to release lock (may have expired)', error);
|
||||
}
|
||||
}
|
||||
|
||||
private buildLockKey(key: CounterKey): string {
|
||||
return `lock:docnum:${key.projectId}:${key.originatorOrgId}:` +
|
||||
`${key.recipientOrgId ?? 0}:${key.correspondenceTypeId}:` +
|
||||
`${key.subTypeId}:${key.rfaTypeId}:${key.disciplineId}:${key.year}`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4. Counter Service
|
||||
|
||||
```typescript
|
||||
// File: src/modules/document-numbering/services/counter.service.ts
|
||||
import { Injectable, ConflictException, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, DataSource } from 'typeorm';
|
||||
import { DocumentNumberCounter } from '../entities/document-number-counter.entity';
|
||||
import { OptimisticLockVersionMismatchError } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class CounterService {
|
||||
private readonly logger = new Logger(CounterService.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(DocumentNumberCounter)
|
||||
private counterRepo: Repository<DocumentNumberCounter>,
|
||||
private dataSource: DataSource,
|
||||
) {}
|
||||
|
||||
async incrementCounter(counterKey: CounterKey): Promise<number> {
|
||||
const MAX_RETRIES = 2;
|
||||
|
||||
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
||||
try {
|
||||
return await this.dataSource.transaction(async (manager) => {
|
||||
// ใช้ Optimistic Locking
|
||||
const counter = await manager.findOne(DocumentNumberCounter, {
|
||||
where: this.buildWhereClause(counterKey),
|
||||
});
|
||||
|
||||
if (!counter) {
|
||||
// สร้าง counter ใหม่
|
||||
const newCounter = manager.create(DocumentNumberCounter, {
|
||||
...counterKey,
|
||||
lastNumber: 1,
|
||||
version: 0,
|
||||
});
|
||||
await manager.save(newCounter);
|
||||
return 1;
|
||||
}
|
||||
|
||||
counter.lastNumber += 1;
|
||||
await manager.save(counter); // Auto-check version
|
||||
return counter.lastNumber;
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof OptimisticLockVersionMismatchError) {
|
||||
this.logger.warn(
|
||||
`Version conflict, retry ${attempt + 1}/${MAX_RETRIES}`,
|
||||
);
|
||||
if (attempt === MAX_RETRIES - 1) {
|
||||
throw new ConflictException('เลขที่เอกสารถูกเปลี่ยน กรุณาลองใหม่');
|
||||
}
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private buildWhereClause(key: CounterKey) {
|
||||
return {
|
||||
projectId: key.projectId,
|
||||
originatorOrganizationId: key.originatorOrgId,
|
||||
recipientOrganizationId: key.recipientOrgId,
|
||||
correspondenceTypeId: key.correspondenceTypeId,
|
||||
subTypeId: key.subTypeId,
|
||||
rfaTypeId: key.rfaTypeId,
|
||||
disciplineId: key.disciplineId,
|
||||
currentYear: key.year,
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.5. Main Service with Retry Logic
|
||||
|
||||
```typescript
|
||||
// File: src/modules/document-numbering/services/document-numbering.service.ts
|
||||
import { Injectable, ServiceUnavailableException, Logger } from '@nestjs/common';
|
||||
import { DocumentNumberingLockService } from './document-numbering-lock.service';
|
||||
import { CounterService } from './counter.service';
|
||||
import { AuditService } from './audit.service';
|
||||
import { RedisConnectionError } from 'ioredis';
|
||||
|
||||
@Injectable()
|
||||
export class DocumentNumberingService {
|
||||
private readonly logger = new Logger(DocumentNumberingService.name);
|
||||
|
||||
constructor(
|
||||
private lockService: DocumentNumberingLockService,
|
||||
private counterService: CounterService,
|
||||
private auditService: AuditService,
|
||||
) {}
|
||||
|
||||
async generateDocumentNumber(dto: GenerateNumberDto): Promise<string> {
|
||||
const startTime = Date.now();
|
||||
let lockWaitMs = 0;
|
||||
let retryCount = 0;
|
||||
let fallbackUsed = 'NONE';
|
||||
|
||||
try {
|
||||
// พยายามใช้ Redis lock ก่อน
|
||||
return await this.generateWithRedisLock(dto);
|
||||
} catch (error) {
|
||||
if (error instanceof RedisConnectionError) {
|
||||
// Fallback: ใช้ database lock
|
||||
this.logger.warn('Redis unavailable, falling back to DB lock');
|
||||
fallbackUsed = 'DB_LOCK';
|
||||
return await this.generateWithDbLock(dto);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
// บันทึก audit log
|
||||
await this.auditService.logGeneration({
|
||||
documentId: dto.documentId,
|
||||
counterKey: dto.counterKey,
|
||||
lockWaitMs,
|
||||
totalDurationMs: Date.now() - startTime,
|
||||
fallbackUsed,
|
||||
retryCount,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async generateWithRedisLock(dto: GenerateNumberDto): Promise<string> {
|
||||
const lock = await this.lockService.acquireLock(dto.counterKey);
|
||||
|
||||
try {
|
||||
const nextNumber = await this.counterService.incrementCounter(dto.counterKey);
|
||||
return this.formatNumber(dto.template, nextNumber, dto.counterKey);
|
||||
} finally {
|
||||
await this.lockService.releaseLock(lock);
|
||||
}
|
||||
}
|
||||
|
||||
private async generateWithDbLock(dto: GenerateNumberDto): Promise<string> {
|
||||
// ใช้ pessimistic lock
|
||||
// Implementation details...
|
||||
}
|
||||
|
||||
private formatNumber(template: string, seq: number, key: CounterKey): string {
|
||||
// Template formatting logic
|
||||
// Example: `คคง.-สคฉ.3-0001-2568`
|
||||
return template
|
||||
.replace('{SEQ:4}', seq.toString().padStart(4, '0'))
|
||||
.replace('{YEAR:B.E.}', (key.year + 543).toString());
|
||||
// ... more replacements
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Template Validation
|
||||
|
||||
```typescript
|
||||
// File: src/modules/document-numbering/validators/template.validator.ts
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
interface ValidationResult {
|
||||
valid: boolean;
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class TemplateValidator {
|
||||
private readonly ALLOWED_TOKENS = [
|
||||
'PROJECT', 'ORIGINATOR', 'RECIPIENT', 'CORR_TYPE',
|
||||
'SUB_TYPE', 'RFA_TYPE', 'DISCIPLINE', 'SEQ', 'YEAR', 'REV',
|
||||
];
|
||||
|
||||
validate(template: string, correspondenceType: string): ValidationResult {
|
||||
const tokens = this.extractTokens(template);
|
||||
const errors: string[] = [];
|
||||
|
||||
// ตรวจสอบ Token ที่ไม่รู้จัก
|
||||
for (const token of tokens) {
|
||||
if (!this.ALLOWED_TOKENS.includes(token.name)) {
|
||||
errors.push(`Unknown token: {${token.name}}`);
|
||||
}
|
||||
}
|
||||
|
||||
// กฎพิเศษสำหรับแต่ละประเภท
|
||||
if (correspondenceType === 'RFA') {
|
||||
if (!tokens.some((t) => t.name === 'PROJECT')) {
|
||||
errors.push('RFA template ต้องมี {PROJECT}');
|
||||
}
|
||||
if (!tokens.some((t) => t.name === 'DISCIPLINE')) {
|
||||
errors.push('RFA template ต้องมี {DISCIPLINE}');
|
||||
}
|
||||
}
|
||||
|
||||
if (correspondenceType === 'TRANSMITTAL') {
|
||||
if (!tokens.some((t) => t.name === 'SUB_TYPE')) {
|
||||
errors.push('TRANSMITTAL template ต้องมี {SUB_TYPE}');
|
||||
}
|
||||
}
|
||||
|
||||
// ทุก template ต้องมี {SEQ}
|
||||
if (!tokens.some((t) => t.name.startsWith('SEQ'))) {
|
||||
errors.push('Template ต้องมี {SEQ:n}');
|
||||
}
|
||||
|
||||
return { valid: errors.length === 0, errors };
|
||||
}
|
||||
|
||||
private extractTokens(template: string) {
|
||||
const regex = /\{([^}]+)\}/g;
|
||||
const tokens: Array<{ name: string; full: string }> = [];
|
||||
let match;
|
||||
|
||||
while ((match = regex.exec(template)) !== null) {
|
||||
const tokenName = match[1].split(':')[0]; // SEQ:4 → SEQ
|
||||
tokens.push({ name: tokenName, full: match[1] });
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. BullMQ Job for Counter Reset
|
||||
|
||||
```typescript
|
||||
// File: src/modules/document-numbering/jobs/counter-reset.job.ts
|
||||
import { Processor, WorkerHost } from '@nestjs/bullmq';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Cron } from '@nestjs/schedule';
|
||||
|
||||
@Processor('document-numbering')
|
||||
@Injectable()
|
||||
export class CounterResetJob extends WorkerHost {
|
||||
private readonly logger = new Logger(CounterResetJob.name);
|
||||
|
||||
@Cron('0 0 1 1 *') // 1 Jan every year at 00:00
|
||||
async handleYearlyReset() {
|
||||
const newYear = new Date().getFullYear();
|
||||
|
||||
// ไม่ต้อง reset counter เพราะ counter แยกตาม current_year อยู่แล้ว
|
||||
// แค่เตรียม counter สำหรับปีใหม่
|
||||
this.logger.log(`Year changed to ${newYear}, counters are ready`);
|
||||
|
||||
// สามารถทำ cleanup counter ปีเก่าได้ (optional)
|
||||
// await this.cleanupOldCounters(newYear - 5); // เก็บ 5 ปี
|
||||
}
|
||||
|
||||
async process() {
|
||||
// BullMQ job processing
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. API Controller
|
||||
|
||||
### 5.1. Main Controller (`/document-numbering`)
|
||||
|
||||
```typescript
|
||||
// File: src/modules/document-numbering/document-numbering.controller.ts
|
||||
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';
|
||||
import { DocumentNumberingService } from './document-numbering.service';
|
||||
import { PreviewNumberDto } from './dto/preview-number.dto';
|
||||
|
||||
@Controller('document-numbering')
|
||||
@UseGuards(JwtAuthGuard, RbacGuard)
|
||||
export class DocumentNumberingController {
|
||||
constructor(private readonly numberingService: DocumentNumberingService) {}
|
||||
|
||||
// --- Logs ---
|
||||
|
||||
@Get('logs/audit')
|
||||
@RequirePermission('system.view_logs')
|
||||
getAuditLogs(@Query('limit') limit?: number) {
|
||||
return this.numberingService.getAuditLogs(limit ? Number(limit) : 100);
|
||||
}
|
||||
|
||||
@Get('logs/errors')
|
||||
@RequirePermission('system.view_logs')
|
||||
getErrorLogs(@Query('limit') limit?: number) {
|
||||
return this.numberingService.getErrorLogs(limit ? Number(limit) : 100);
|
||||
}
|
||||
|
||||
// --- Sequences / Counters ---
|
||||
|
||||
@Get('sequences')
|
||||
@RequirePermission('correspondence.read')
|
||||
getSequences(@Query('projectId') projectId?: number) {
|
||||
return this.numberingService.getSequences(projectId ? Number(projectId) : undefined);
|
||||
}
|
||||
|
||||
@Patch('counters/:id')
|
||||
@RequirePermission('system.manage_settings')
|
||||
async updateCounter(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body('sequence') sequence: number
|
||||
) {
|
||||
return this.numberingService.setCounterValue(id, sequence);
|
||||
}
|
||||
|
||||
// --- Preview ---
|
||||
|
||||
@Post('preview')
|
||||
@RequirePermission('correspondence.read')
|
||||
async previewNumber(@Body() dto: PreviewNumberDto) {
|
||||
return this.numberingService.previewNumber(dto);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2. Admin Controller (`/admin/document-numbering`)
|
||||
|
||||
```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 { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||
import { RbacGuard } from '../../common/guards/rbac.guard';
|
||||
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
|
||||
import { DocumentNumberingService } from './document-numbering.service';
|
||||
|
||||
@Controller('admin/document-numbering')
|
||||
@UseGuards(JwtAuthGuard, RbacGuard)
|
||||
export class DocumentNumberingAdminController {
|
||||
constructor(private readonly service: DocumentNumberingService) {}
|
||||
|
||||
// --- Template Management ---
|
||||
|
||||
@Get('templates')
|
||||
@RequirePermission('system.manage_settings')
|
||||
async getTemplates(@Query('projectId') projectId?: number) {
|
||||
if (projectId) {
|
||||
return this.service.getTemplatesByProject(projectId);
|
||||
}
|
||||
return this.service.getTemplates();
|
||||
}
|
||||
|
||||
@Post('templates')
|
||||
@RequirePermission('system.manage_settings')
|
||||
async saveTemplate(@Body() dto: any) {
|
||||
return this.service.saveTemplate(dto);
|
||||
}
|
||||
|
||||
@Delete('templates/:id')
|
||||
@RequirePermission('system.manage_settings')
|
||||
async deleteTemplate(@Param('id', ParseIntPipe) id: number) {
|
||||
await this.service.deleteTemplate(id);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
// --- Metrics ---
|
||||
|
||||
@Get('metrics')
|
||||
@RequirePermission('system.view_logs')
|
||||
async getMetrics() {
|
||||
const audit = await this.service.getAuditLogs(50);
|
||||
const errors = await this.service.getErrorLogs(50);
|
||||
return { audit, errors };
|
||||
}
|
||||
|
||||
// --- Admin Operations ---
|
||||
|
||||
@Post('manual-override')
|
||||
@RequirePermission('system.manage_settings')
|
||||
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;
|
||||
}) {
|
||||
return this.service.voidAndReplace(dto);
|
||||
}
|
||||
|
||||
@Post('cancel')
|
||||
@RequirePermission('system.manage_settings')
|
||||
async cancelNumber(@Body() dto: {
|
||||
documentNumber: string;
|
||||
reason: string;
|
||||
}) {
|
||||
return this.service.cancelNumber(dto);
|
||||
}
|
||||
|
||||
@Post('bulk-import')
|
||||
@RequirePermission('system.manage_settings')
|
||||
async bulkImport(@Body() items: any[]) {
|
||||
return this.service.bulkImport(items);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3. API Endpoints Summary
|
||||
|
||||
| Endpoint | Method | Permission | Description |
|
||||
| -------------------------------------------- | ------ | ------------------------ | --------------------------------- |
|
||||
| `/document-numbering/logs/audit` | GET | `system.view_logs` | Get audit logs |
|
||||
| `/document-numbering/logs/errors` | GET | `system.view_logs` | Get error logs |
|
||||
| `/document-numbering/sequences` | GET | `correspondence.read` | Get counter sequences |
|
||||
| `/document-numbering/counters/:id` | PATCH | `system.manage_settings` | Update counter value |
|
||||
| `/document-numbering/preview` | POST | `correspondence.read` | Preview number without generating |
|
||||
| `/admin/document-numbering/templates` | GET | `system.manage_settings` | Get all templates |
|
||||
| `/admin/document-numbering/templates` | POST | `system.manage_settings` | Create/update template |
|
||||
| `/admin/document-numbering/templates/:id` | DELETE | `system.manage_settings` | Delete template |
|
||||
| `/admin/document-numbering/metrics` | GET | `system.view_logs` | Get metrics (audit + errors) |
|
||||
| `/admin/document-numbering/manual-override` | POST | `system.manage_settings` | Override counter value |
|
||||
| `/admin/document-numbering/void-and-replace` | POST | `system.manage_settings` | Void and replace number |
|
||||
| `/admin/document-numbering/cancel` | POST | `system.manage_settings` | Cancel a number |
|
||||
| `/admin/document-numbering/bulk-import` | POST | `system.manage_settings` | Bulk import counters |
|
||||
|
||||
## 6. Module Configuration
|
||||
|
||||
```typescript
|
||||
// File: src/modules/document-numbering/document-numbering.module.ts
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { BullModule } from '@nestjs/bullmq';
|
||||
import { ThrottlerModule } from '@nestjs/throttler';
|
||||
import { DocumentNumberCounter } from './entities/document-number-counter.entity';
|
||||
import { DocumentNumberAudit } from './entities/document-number-audit.entity';
|
||||
import { DocumentNumberError } from './entities/document-number-error.entity';
|
||||
import { DocumentNumberingService } from './services/document-numbering.service';
|
||||
import { DocumentNumberingLockService } from './services/document-numbering-lock.service';
|
||||
import { CounterService } from './services/counter.service';
|
||||
import { AuditService } from './services/audit.service';
|
||||
import { TemplateValidator } from './validators/template.validator';
|
||||
import { CounterResetJob } from './jobs/counter-reset.job';
|
||||
import { DocumentNumberingController } from './controllers/document-numbering.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
DocumentNumberCounter,
|
||||
DocumentNumberAudit,
|
||||
DocumentNumberError,
|
||||
]),
|
||||
BullModule.registerQueue({
|
||||
name: 'document-numbering',
|
||||
}),
|
||||
ThrottlerModule.forRoot({
|
||||
ttl: 60,
|
||||
limit: 10,
|
||||
}),
|
||||
],
|
||||
controllers: [DocumentNumberingController],
|
||||
providers: [
|
||||
DocumentNumberingService,
|
||||
DocumentNumberingLockService,
|
||||
CounterService,
|
||||
AuditService,
|
||||
TemplateValidator,
|
||||
CounterResetJob,
|
||||
],
|
||||
exports: [DocumentNumberingService],
|
||||
})
|
||||
export class DocumentNumberingModule {}
|
||||
```
|
||||
|
||||
## 7. Environment Configuration
|
||||
|
||||
```typescript
|
||||
// .env.example
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_USERNAME=lcbp3
|
||||
DB_PASSWORD=
|
||||
DB_DATABASE=lcbp3_db
|
||||
DB_POOL_SIZE=20
|
||||
|
||||
# Prometheus
|
||||
PROMETHEUS_PORT=9090
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [Requirements](file:///e:/np-dms/lcbp3/specs/01-requirements/03.11-document-numbering.md)
|
||||
- [Operations Guide](file:///e:/np-dms/lcbp3/specs/04-operations/document-numbering-operations.md)
|
||||
- [Backend Guidelines](file:///e:/np-dms/lcbp3/specs/03-implementation/backend-guidelines.md)
|
||||
17
docs/backup/features.bak
Normal file
17
docs/backup/features.bak
Normal file
@@ -0,0 +1,17 @@
|
||||
ฟีเจอร์ขั้นสูง (Advanced Features)
|
||||
Error Handling: ใช้ Global Exception Filter เพื่อจัดการ Error และส่ง Response ที่เป็นมาตรฐาน
|
||||
|
||||
Logging: ใช้ Winston สำหรับ Structured Logging และบันทึก Error ลงไฟล์
|
||||
|
||||
Testing: มีโครงสร้างสำหรับ Unit Test และ E2E Test ด้วย Jest และ Supertest
|
||||
|
||||
Performance: ใช้ CacheModule สำหรับ Caching ข้อมูลที่เรียกใช้บ่อย
|
||||
|
||||
Security:
|
||||
|
||||
Rate Limiting: ใช้ ThrottlerModule เพื่อป้องกัน Brute-force attack
|
||||
|
||||
Secret Management: แนะนำให้ใช้ Environment Variable ของ Docker แทนไฟล์ .env ใน Production
|
||||
|
||||
API Documentation: สร้างเอกสาร API อัตโนมัติด้วย @nestjs/swagger และเข้าถึงได้ที่ /api-docs
|
||||
|
||||
1875
docs/backup/lcbp3-v1.5.1-schema.sql
Normal file
1875
docs/backup/lcbp3-v1.5.1-schema.sql
Normal file
File diff suppressed because it is too large
Load Diff
3225
docs/backup/lcbp3-v1.5.1-seed-basic.sql
Normal file
3225
docs/backup/lcbp3-v1.5.1-seed-basic.sql
Normal file
File diff suppressed because it is too large
Load Diff
2569
docs/backup/lcbp3-v1.5.1-seed-contractdrawing.sql
Normal file
2569
docs/backup/lcbp3-v1.5.1-seed-contractdrawing.sql
Normal file
File diff suppressed because it is too large
Load Diff
1067
docs/backup/lcbp3-v1.5.1-seed-permissions.sql
Normal file
1067
docs/backup/lcbp3-v1.5.1-seed-permissions.sql
Normal file
File diff suppressed because it is too large
Load Diff
192
docs/backup/workflow.bak
Normal file
192
docs/backup/workflow.bak
Normal file
@@ -0,0 +1,192 @@
|
||||
## Workflow ระดับ Project (correspondence_routing_steps, technical_doc_workflows): คือ "การเดินทาง" ของเอกสาร ระหว่างองค์กร (เช่น จากผู้รับเหมา -> ไปยังที่ปรึกษา -> ไปยังเจ้าของโครงการ)
|
||||
|
||||
## Workflow ระดับ Organization (Circulation): คือ "การแจกจ่าย" เอกสาร ภายในองค์กรของคุณเอง หลังจากที่คุณได้รับเอกสารนั้นมาแล้ว (เช่น เอกสารมาถึง Document Control แล้วต้องส่งต่อให้ใครบ้างในบริษัท)
|
||||
|
||||
circulation_templates: ตารางหลักสำหรับเก็บชื่อแม่แบบ
|
||||
circulation_template_assignees: ตารางสำหรับเก็บ "รายชื่อผู้รับผิดชอบ" ที่ถูกกำหนดไว้ในแต่ละแม่แบบ
|
||||
|
||||
Workflow การทำงานใน Frontend
|
||||
1. หน้า Admin Panel: จะต้องมีเมนูใหม่สำหรับให้ Admin หรือผู้มีสิทธิ์ เข้าไป สร้าง/แก้ไข/ลบ แม่แบบใบเวียน (circulation_templates) สำหรับองค์กรของตนเองได้
|
||||
|
||||
2. หน้าที่สร้างใบเวียน (Create Circulation Dialog):
|
||||
* ที่ด้านบนสุดของฟอร์ม จะมี Dropdown ใหม่ ปรากฏขึ้นมา เขียนว่า "ใช้แม่แบบ (Use Template)"
|
||||
* ใน Dropdown นี้ จะแสดงรายชื่อแม่แบบทั้งหมดที่องค์กรนั้นๆ สร้างไว้
|
||||
* เมื่อผู้ใช้เลือกแม่แบบ:
|
||||
** ระบบจะยิง API ไปดึงรายชื่อผู้รับผิดชอบจากตาราง circulation_template_assignees
|
||||
** จากนั้น JavaScript จะทำการเติมข้อมูล (Auto-populate) ลงในช่อง "Main", "Action", และ "Information" ให้โดยอัตโนมัติ
|
||||
* ผู้ใช้ยังสามารถ แก้ไข/เพิ่มเติม/ลบ รายชื่อผู้รับผิดชอบได้ตามต้องการ ก่อนที่จะกดสร้างใบเวียนจริง
|
||||
|
||||
|
||||
การจัดการข้อมูล JSON จะเกิดขึ้นใน 3 ส่วนหลักๆ คือ Backend, Frontend, และ Database ครับ
|
||||
|
||||
## 1. การจัดการในฝั่ง Backend (NestJS)
|
||||
นี่คือส่วนที่ทำหน้าที่หลักในการสร้างและอ่านข้อมูล JSON อย่างเป็นระบบและปลอดภัย
|
||||
|
||||
1.1 การแก้ไข Entity
|
||||
เราจะแก้ไข Correspondence entity โดยเพิ่มคอลัมน์ details เข้าไป และลบ Entity ย่อยๆ ที่ไม่ใช้ออก
|
||||
|
||||
src/correspondences/entities/correspondence.entity.ts
|
||||
@Entity('correspondences')
|
||||
export class Correspondence {
|
||||
// ... (คอลัมน์เดิมทั้งหมด: id, document_number, title, etc.)
|
||||
|
||||
@Column({
|
||||
type: 'json', // ◀️ กำหนดประเภทข้อมูลเป็น JSON
|
||||
nullable: true,
|
||||
comment: 'เก็บข้อมูลเฉพาะของเอกสารแต่ละประเภทในรูปแบบ JSON'
|
||||
})
|
||||
details: any; // ◀️ ใช้ type 'any' หรือสร้าง Interface/Type ที่ซับซ้อนขึ้น
|
||||
}
|
||||
|
||||
1.2 การสร้าง DTOs สำหรับแต่ละประเภทเอกสาร
|
||||
เพื่อรักษาความถูกต้องของข้อมูล (Validation) เราจะสร้าง DTO แยกสำหรับเอกสารแต่ละประเภท
|
||||
|
||||
ตัวอย่าง src/correspondences/dto/create-letter.dto.ts:
|
||||
|
||||
TypeScript
|
||||
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
// DTO สำหรับข้อมูลใน details ของ Letter
|
||||
class LetterDetailsDto {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
attention_to: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
signatory_name: string;
|
||||
}
|
||||
|
||||
// DTO หลักสำหรับสร้าง Letter
|
||||
export class CreateLetterDto {
|
||||
@ApiProperty({ description: "ข้อมูลพื้นฐานของเอกสาร" })
|
||||
@ValidateNested() // ◀️ บอกให้ class-validator ตรวจสอบ object ข้างในด้วย
|
||||
@Type(() => CreateCorrespondenceDto) // ◀️ ใช้ DTO พื้นฐานร่วมกัน
|
||||
base_data: CreateCorrespondenceDto;
|
||||
|
||||
@ApiProperty({ description: "ข้อมูลเฉพาะของ Letter" })
|
||||
@ValidateNested()
|
||||
@Type(() => LetterDetailsDto)
|
||||
details: LetterDetailsDto;
|
||||
}
|
||||
|
||||
1.3 การสร้าง API Endpoint และ Logic ใน Service
|
||||
เราจะสร้าง Endpoint แยกสำหรับสร้างเอกสารแต่ละประเภทเพื่อความชัดเจน
|
||||
|
||||
ใน CorrespondencesController:
|
||||
|
||||
TypeScript
|
||||
|
||||
@Post('letter')
|
||||
@ApiOperation({ summary: 'Create a new Letter' })
|
||||
createLetter(@Body() createLetterDto: CreateLetterDto, @Req() req: Request) {
|
||||
const user = req.user as any;
|
||||
return this.correspondencesService.createTypedCorrespondence(
|
||||
createLetterDto.base_data,
|
||||
createLetterDto.details,
|
||||
user
|
||||
);
|
||||
}
|
||||
ใน CorrespondencesService:
|
||||
|
||||
TypeScript
|
||||
|
||||
async createTypedCorrespondence(baseData: CreateCorrespondenceDto, details: any, user: User): Promise<Correspondence> {
|
||||
// ... (โค้ดตรวจสอบสิทธิ์เหมือนเดิม)
|
||||
|
||||
const newCorrespondence = this.correspondenceRepository.create({
|
||||
...baseData, // ข้อมูลพื้นฐาน (เลขที่เอกสาร, ชื่อเรื่อง, etc.)
|
||||
details: details, // ◀️ นำ object ของ details มาใส่ในคอลัมน์ JSON โดยตรง
|
||||
created_by_user_id: user.user_id,
|
||||
originator_org_id: user.org_id,
|
||||
status_id: 1, // 'Draft'
|
||||
});
|
||||
|
||||
return this.correspondenceRepository.save(newCorrespondence);
|
||||
}
|
||||
## 2. การจัดการในฝั่ง Frontend (Next.js / React)
|
||||
Frontend จะทำหน้าที่แสดงฟอร์มที่ถูกต้องตามประเภทเอกสาร และส่งข้อมูลในรูปแบบที่ Backend ต้องการ
|
||||
|
||||
2.1 การแสดงฟอร์มแบบไดนามิก (Dynamic Forms)
|
||||
ในหน้า "Create Correspondence" เมื่อผู้ใช้เลือกประเภทเอกสารจาก Dropdown เราจะใช้ State เพื่อแสดงฟอร์มที่ถูกต้อง
|
||||
|
||||
TypeScript
|
||||
|
||||
const [docType, setDocType] = useState('LETTER');
|
||||
|
||||
// ...
|
||||
|
||||
const renderDetailFields = () => {
|
||||
switch (docType) {
|
||||
case 'LETTER':
|
||||
return (
|
||||
<>
|
||||
{/* ฟิลด์สำหรับ Attention To, Signatory */}
|
||||
</>
|
||||
);
|
||||
case 'RFI':
|
||||
return (
|
||||
<>
|
||||
{/* ฟิลด์สำหรับ Question, Required By Date */}
|
||||
</>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form>
|
||||
{/* ฟิลด์พื้นฐาน (Document Number, Title) */}
|
||||
{/* Dropdown เลือกประเภทเอกสาร */}
|
||||
{renderDetailFields()} {/* ◀️ แสดงฟิลด์เฉพาะทางที่นี่ */}
|
||||
</form>
|
||||
);
|
||||
2.2 การส่งข้อมูล
|
||||
เมื่อผู้ใช้กด Submit เราจะรวบรวมข้อมูลจากฟอร์มให้เป็นโครงสร้าง JSON ที่ Backend ต้องการ
|
||||
|
||||
JavaScript
|
||||
|
||||
const handleSubmit = () => {
|
||||
// รวบรวมข้อมูลพื้นฐาน
|
||||
const base_data = {
|
||||
document_number: '...',
|
||||
title: '...',
|
||||
// ...
|
||||
};
|
||||
|
||||
// รวบรวมข้อมูลเฉพาะทาง
|
||||
const details = {
|
||||
attention_to: '...',
|
||||
signatory_name: '...',
|
||||
};
|
||||
|
||||
// ส่งข้อมูลไปที่ API Endpoint ที่ถูกต้อง
|
||||
fetch('/api/correspondences/letter', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ base_data, details }),
|
||||
});
|
||||
}
|
||||
## 3. การจัดการในระดับฐานข้อมูล (MariaDB)
|
||||
แม้ว่าเราจะไม่ค่อยได้ Query ข้อมูล JSON โดยตรงผ่าน SQL บ่อยนัก แต่ก็สามารถทำได้เมื่อจำเป็น (เช่น สำหรับการทำรายงานที่ซับซ้อน)
|
||||
|
||||
ตัวอย่าง: ค้นหาเอกสาร Letter ทั้งหมดที่ส่งถึง "Mr. John Doe"
|
||||
|
||||
SQL
|
||||
|
||||
SELECT
|
||||
corr_id,
|
||||
document_number,
|
||||
details
|
||||
FROM
|
||||
correspondences
|
||||
WHERE
|
||||
type_id = (SELECT type_id FROM correspondence_types WHERE type_code = 'LETTER') -- กรองเฉพาะ Letter
|
||||
AND JSON_VALUE(details, '$.attention_to') = 'Mr. John Doe'; -- ◀️ ค้นหาค่าใน JSON
|
||||
การจัดการข้อมูลด้วยวิธีนี้จะทำให้ระบบของคุณมีความ
|
||||
|
||||
|
||||
Reference in New Issue
Block a user