251202:2300 Prepare 1.5.1

This commit is contained in:
2025-12-03 01:16:27 +07:00
parent 9dcdba0bb3
commit 1817158f25
28 changed files with 7362 additions and 268 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+4 -3
View File
@@ -664,7 +664,9 @@
"password": "",
"database": "lcbp3"
}
]
],
"terminal.integrated.copyOnSelection": true,
"terminal.integrated.tabs.defaultColor": "terminal.ansiBlue"
},
// ========================================
@@ -927,8 +929,7 @@
"chakrounanas.turbo-console-log",
"wallabyjs.console-ninja",
"pkief.material-icon-theme",
"github.copilot",
"inferrinizzard.prettier-sql-vscode"
"github.copilot"
]
}
}
+29 -25
View File
@@ -1,9 +1,9 @@
# LCBP3-DMS - Project Overview
**Project Name:** Laem Chabang Port Phase 3 - Document Management System (LCBP3-DMS)
**Version:** 1.5.0
**Version:** 1.5.1
**Status:** Planning & Specification Phase
**Last Updated:** 2025-12-01
**Last Updated:** 2025-12-02
---
@@ -199,21 +199,24 @@ lcbp3/
### Documentation
| Category | Document | Description |
| ------------------ | --------------------------------------------------------------------------- | ------------------------------------- |
| **Overview** | [Glossary](./glossary.md) | Technical terminology & abbreviations |
| **Overview** | [Quick Start](./quick-start.md) | 5-minute getting started guide |
| **Requirements** | [Functional Requirements](../01-requirements/03-functional-requirements.md) | Feature specifications |
| **Architecture** | [System Architecture](../02-architecture/system-architecture.md) | Overall system design |
| **Architecture** | [Data Model](../02-architecture/data-model.md) | Database schema |
| **Architecture** | [API Design](../02-architecture/api-design.md) | REST API specifications |
| **Implementation** | [Backend Guidelines](../03-implementation/backend-guidelines.md) | Backend coding standards |
| **Implementation** | [Frontend Guidelines](../03-implementation/frontend-guidelines.md) | Frontend coding standards |
| **Implementation** | [Testing Strategy](../03-implementation/testing-strategy.md) | Testing approach |
| **Operations** | [Deployment Guide](../04-operations/deployment-guide.md) | How to deploy |
| **Operations** | [Monitoring](../04-operations/monitoring-alerting.md) | Monitoring & alerts |
| **Decisions** | [ADR Index](../05-decisions/README.md) | Architecture decisions |
| **Tasks** | [Backend Tasks](../06-tasks/README.md) | Development tasks |
| Category | Document | Description |
| ------------------ | ------------------------------------------------------------------------------------ | ------------------------------------- |
| **Overview** | [Glossary](./glossary.md) | Technical terminology & abbreviations |
| **Overview** | [Quick Start](./quick-start.md) | 5-minute getting started guide |
| **Requirements** | [Functional Requirements](../01-requirements/03-functional-requirements.md) | Feature specifications |
| **Requirements** | [Document Numbering](../01-requirements/03.11-document-numbering.md) | Document numbering requirements |
| **Architecture** | [System Architecture](../02-architecture/system-architecture.md) | Overall system design |
| **Architecture** | [Data Model](../02-architecture/data-model.md) | Database schema |
| **Architecture** | [API Design](../02-architecture/api-design.md) | REST API specifications |
| **Implementation** | [Backend Guidelines](../03-implementation/backend-guidelines.md) | Backend coding standards |
| **Implementation** | [Frontend Guidelines](../03-implementation/frontend-guidelines.md) | Frontend coding standards |
| **Implementation** | [Document Numbering Implementation](../03-implementation/document-numbering.md) | Document numbering implementation |
| **Implementation** | [Testing Strategy](../03-implementation/testing-strategy.md) | Testing approach |
| **Operations** | [Deployment Guide](../04-operations/deployment-guide.md) | How to deploy |
| **Operations** | [Monitoring](../04-operations/monitoring-alerting.md) | Monitoring & alerts |
| **Operations** | [Document Numbering Operations](../04-operations/document-numbering-operations.md) | Doc numbering ops guide |
| **Decisions** | [ADR Index](../05-decisions/README.md) | Architecture decisions |
| **Tasks** | [Backend Tasks](../06-tasks/README.md) | Development tasks |
### Key ADRs
@@ -371,7 +374,7 @@ lcbp3/
### Operations Support
- **Email:** ops-team@example.com
- **Email:** <ops-team@example.com>
- **Phone:** [Phone Number]
- **On-Call:** [On-Call Schedule]
@@ -379,9 +382,9 @@ lcbp3/
## 📝 Document Control
- **Version:** 1.5.0
- **Version:** 1.5.1
- **Status:** Active
- **Last Updated:** 2025-12-01
- **Last Updated:** 2025-12-02
- **Next Review:** 2026-01-01
- **Owner:** System Architect
- **Classification:** Internal Use Only
@@ -390,12 +393,13 @@ lcbp3/
## 🔄 Version History
| Version | Date | Description |
| ------- | ---------- | ------------------------------------------ |
| Version | Date | Description |
| ------- | ---------- | ----------------------------------------- |
| 1.6.0 | 2025-12-02 | Reorganized documentation structure |
| 1.5.0 | 2025-12-01 | Complete specification with ADRs and tasks |
| 1.4.5 | 2025-11-30 | Updated architecture documents |
| 1.4.4 | 2025-11-29 | Initial backend/frontend plans |
| 1.0.0 | 2025-11-01 | Initial requirements |
| 1.4.5 | 2025-11-30 | Updated architecture documents |
| 1.4.4 | 2025-11-29 | Initial backend/frontend plans |
| 1.0.0 | 2025-11-01 | Initial requirements |
---
+4 -4
View File
@@ -1,8 +1,8 @@
# Glossary - คำศัพท์และคำย่อทางเทคนิค
**Project:** LCBP3-DMS
**Version:** 1.5.0
**Last Updated:** 2025-12-01
**Version:** 1.5.1
**Last Updated:** 2025-12-02
---
@@ -491,6 +491,6 @@ Logging library สำหรับ Node.js
---
**Version:** 1.5.0
**Last Updated:** 2025-12-01
**Version:** 1.5.1
**Last Updated:** 2025-12-02
**Next Review:** 2026-03-01
+7 -7
View File
@@ -1,8 +1,8 @@
# Quick Start Guide
**Project:** LCBP3-DMS
**Version:** 1.5.0
**Last Updated:** 2025-12-01
**Version:** 1.5.1
**Last Updated:** 2025-12-02
---
@@ -355,7 +355,7 @@ git push origin feature/my-feature
### Resources
- **Documentation:** `/specs` directory
- **API Docs:** http://localhost:3000/api/docs
- **API Docs:** <http://localhost:3000/api/docs>
- **Issue Tracker:** [Link to issue tracker]
### Contact
@@ -373,8 +373,8 @@ git push origin feature/my-feature
- [ ] Setup environment variables
- [ ] Start Docker services
- [ ] Run migrations
- [ ] Access backend (http://localhost:3000/health)
- [ ] Access frontend (http://localhost:3001)
- [ ] Access backend (<http://localhost:3000/health>)
- [ ] Access frontend (<http://localhost:3001>)
- [ ] Login with default credentials
- [ ] Run tests
- [ ] Read [System Architecture](../02-architecture/system-architecture.md)
@@ -385,5 +385,5 @@ git push origin feature/my-feature
**Welcome aboard! 🎉**
**Version:** 1.5.0
**Last Updated:** 2025-12-01
**Version:** 1.5.1
**Last Updated:** 2025-12-02
File diff suppressed because it is too large Load Diff
+148 -33
View File
@@ -1,58 +1,173 @@
# 📋 Requirements Specification v1.5.0
# 📋 Requirements Specification
## Status: first-draft
**Version:** 1.5.1
**Status:** Active
**Last Updated:** 2025-12-02
**Date:** 2025-11-30
---
## 📖 Overview
This directory contains the functional and non-functional requirements for the LCBP3-DMS (Laem Chabang Port Phase 3 - Document Management System). The requirements are organized by functional area and feature.
---
## 📑 Table of Contents
1. [Objectives & Goals](./01-objectives.md)
2. [System Architecture & Technology](./02-architecture.md)
3. [Functional Requirements](./03-functional-requirements.md)
- [3.1 Project & Organization Management](./03.1-project-management.md)
- [3.2 Correspondence Management](./03.2-correspondence.md)
- [3.3 RFA Management](./03.3-rfa.md)
- [3.4 Contract Drawing Management](./03.4-contract-drawing.md)
- [3.5 Shop Drawing Management](./03.5-shop-drawing.md)
- [3.6 Unified Workflow](./03.6-unified-workflow.md)
- [3.7 Transmittals Management](./03.7-transmittals.md)
- [3.8 Circulation Sheet Management](./03.8-circulation-sheet.md)
- [3.9 Revisions Management](./03.9-revisions.md)
- [3.10 File Handling](./03.10-file-handling.md)
- [3.11 Document Numbering](./03.11-document-numbering.md)
- [3.12 JSON Details](./03.12-json-details.md)
4. [Access Control & RBAC](./04-access-control.md)
5. [UI/UX Requirements](./05-ui-ux.md)
6. [Non-Functional Requirements](./06-non-functional.md)
7. [Testing Requirements](./07-testing.md)
### Core Requirements
1. [Objectives & Goals](./01-objectives.md) - Project objectives and success criteria
2. [System Architecture & Technology](./02-architecture.md) - High-level architecture requirements
3. [Functional Requirements](./03-functional-requirements.md) - Detailed feature specifications
### Functional Areas
#### Document Management
- [3.1 Project & Organization Management](./03.1-project-management.md) - Projects, contracts, organizations
- [3.2 Correspondence Management](./03.2-correspondence.md) - Letters and communications
- [3.3 RFA Management](./03.3-rfa.md) - Request for Approval
- [3.4 Contract Drawing Management](./03.4-contract-drawing.md) - Contract drawings (แบบคู่สัญญา)
- [3.5 Shop Drawing Management](./03.5-shop-drawing.md) - Shop drawings (แบบก่อสร้าง)
#### Supporting Features
- [3.6 Unified Workflow](./03.6-unified-workflow.md) - Workflow engine and routing
- [3.7 Transmittals Management](./03.7-transmittals.md) - Document transmittals
- [3.8 Circulation Sheet Management](./03.8-circulation-sheet.md) - Document circulation
- [3.9 Revisions Management](./03.9-revisions.md) - Version control
- [3.10 File Handling](./03.10-file-handling.md) - File storage and processing
#### **⭐ Document Numbering System**
- [3.11 Document Numbering](./03.11-document-numbering.md) - **Requirements**
- Automatic number generation
- Template-based formatting
- Concurrent request handling
- Counter management
**Implementation & Operations:**
- 📘 [Implementation Guide](../03-implementation/document-numbering.md) - NestJS, TypeORM, Redis code examples
- 📗 [Operations Guide](../04-operations/document-numbering-operations.md) - Monitoring, troubleshooting, runbooks
#### Technical Details
- [3.12 JSON Details](./03.12-json-details.md) - JSON field specifications
### Cross-Cutting Concerns
4. [Access Control & RBAC](./04-access-control.md) - 4-level hierarchical RBAC
5. [UI/UX Requirements](./05-ui-ux.md) - User interface specifications
6. [Non-Functional Requirements](./06-non-functional.md) - Performance, security, scalability
7. [Testing Requirements](./07-testing.md) - Test strategy and coverage
---
## 🔄 Recent Changes
See [CHANGELOG.md](../../CHANGELOG.md) for detailed version history.
### v1.5.1 (2025-12-02)
### v1.4.5 (2025-11-30)
-**Reorganized Document Numbering documentation**
- Split into: Requirements → Implementation → Operations
- Created [document-numbering.md](../03-implementation/document-numbering.md) implementation guide
- Created [document-numbering-operations.md](../04-operations/document-numbering-operations.md) ops guide
- ✅ Updated schema to match v1.6.0 requirements
- ✅ Enhanced cross-references between documents
### v1.5.0 (2025-12-01)
- ✅ Added comprehensive security requirements
- ✅ Enhanced resilience patterns
- ✅ Added performance targets
- ⚠️ **Breaking:** Changed document numbering from stored procedure to app-level locking
---
### v1.4.5 (2025-11-30)
## 📊 Compliance Matrix
- ✅ Initial requirements documentation
- ✅ Functional requirements specified
| Requirement | Status | Owner | Target Release |
| ----------------------------- | ----------- | ------------ | -------------- |
| FR-001: Correspondence CRUD | ✅ Done | Backend Team | v1.0 |
| FR-002: RFA Workflow | In Progress | Backend Team | v1.1 |
| NFR-001: API Response < 200ms | Planned | DevOps | v1.2 |
See [CHANGELOG.md](../../CHANGELOG.md) for detailed version history.
---
## 📬 Feedback
## 📊 Requirements Traceability
Found issues? [Open an issue](https://github.com/your-org/lcbp3-dms/issues/new?template=spec-issue.md)
### By Feature Status
| Feature Area | Requirements Doc | Status | Implementation | Operations |
|----------------------------|----------------------------------------|-------------|----------------|------------|
| Correspondence Management | [03.2](./03.2-correspondence.md) | ✅ Complete | Planned | N/A |
| RFA Management | [03.3](./03.3-rfa.md) | ✅ Complete | Planned | N/A |
| Workflow Engine | [03.6](./03.6-unified-workflow.md) | ✅ Complete | Planned | N/A |
| **Document Numbering** | [03.11](./03.11-document-numbering.md) | ✅ Complete | [Guide](../03-implementation/document-numbering.md) | [Guide](../04-operations/document-numbering-operations.md) |
| Access Control | [04](./04-access-control.md) | ✅ Complete | Planned | N/A |
### By Priority
- **P0 (Critical):** Access Control, Document Numbering
- **P1 (High):** Correspondence, RFA, Workflow Engine
- **P2 (Medium):** Transmittals, Circulation, Search
- **P3 (Low):** Reporting, Analytics
---
## 🎯 Requirements Quality Checklist
All requirements documents must meet these criteria:
- [ ] **Clear:** Written in simple, unambiguous language
- [ ] **Testable:** Can be verified through testing
- [ ] **Traceable:** Linked to business objectives
- [ ] **Feasible:** Technically achievable within constraints
- [ ] **Complete:** All edge cases and scenarios covered
- [ ] **Consistent:** No contradictions with other requirements
---
## 📖 Reading Guide
### For Product Owners / Business Analysts
1. Start with [Objectives & Goals](./01-objectives.md)
2. Review [Functional Requirements](./03-functional-requirements.md)
3. Check specific feature requirements (3.1-3.12)
### For Developers
1. Read requirements document for your feature
2. Check [Implementation Guides](../03-implementation/) for technical details
3. Review [ADRs](../05-decisions/) for architectural decisions
4. Check [Tasks](../06-tasks/) for development breakdown
### For QA / Testers
1. Review [Testing Requirements](./07-testing.md)
2. Use requirements as test case source
3. Verify [Non-Functional Requirements](./06-non-functional.md)
### For Operations Team
1. Read [Non-Functional Requirements](./06-non-functional.md) for SLAs
2. Check [Operations Guides](../04-operations/) for specific features
3. Review monitoring and alerting requirements
---
## 📬 Feedback & Issues
**Found issues or have suggestions?**
- Requirements clarity issues → [Open Issue](https://github.com/your-org/lcbp3-dms/issues/new?template=spec-issue.md)
- Feature requests → Contact Product Owner
- Technical questions → Contact System Architect
---
## 📝 Document Control
- **Version:** 1.5.1
- **Owner:** System Architect (Nattanin Peancharoen)
- **Last Review:** 2025-12-02
- **Next Review:** 2026-01-01
- **Classification:** Internal Use Only
+34 -23
View File
@@ -1,4 +1,4 @@
# 📋 Architecture Specification v1.5.0
# 📋 Architecture Specification
> **สถาปัตยกรรมระบบ LCBP3-DMS**
>
@@ -10,9 +10,9 @@
| Attribute | Value |
| ------------------ | -------------------------------- |
| **Version** | 1.5.0 |
| **Status** | First Draft |
| **Last Updated** | 2025-11-30 |
| **Version** | 1.5.1 |
| **Status** | Active |
| **Last Updated** | 2025-12-02 |
| **Owner** | Nattanin Peancharoen |
| **Classification** | Internal Technical Documentation |
@@ -251,9 +251,34 @@ Layer 6: File Security (Virus Scanning, Access Control)
- Workflow Versioning
- Polymorphic Entity Relationships
**Related:** [specs/05-decisions/001-workflow-engine.md](../05-decisions/001-workflow-engine.md)
**Related:** [ADR-001](../05-decisions/ADR-001-unified-workflow-engine.md)
### ADR-002: Two-Phase File Storage
### ADR-002: Document Numbering Strategy
**Decision:** ใช้ Application-Level Locking แทน Database Stored Procedure
**Rationale:**
- ยืดหยุ่นกว่า (Template-Based Generator)
- ง่ายต่อการ Debug และ Monitoring
- รองรับ Complex Numbering Rules
- Support ทุกประเภทเอกสาร (LETTER, RFA, TRANSMITTAL, etc.)
**Implementation:**
- **Layer 1:** Redis Redlock (Distributed Lock)
- **Layer 2:** Optimistic Database Lock (`@VersionColumn()`)
- **Retry:** Exponential Backoff with Jitter
- **Counter Key:** Composite PK (8 columns)
**Documentation:**
- 📋 [Requirements](../01-requirements/03.11-document-numbering.md)
- 📘 [Implementation Guide](../03-implementation/document-numbering.md)
- 📗 [Operations Guide](../04-operations/document-numbering-operations.md)
**Related:** [ADR-002](../05-decisions/ADR-002-document-numbering-strategy.md)
### ADR-003: Two-Phase File Storage
**Decision:** แยกการอัปโหลดไฟล์เป็น 2 ขั้นตอน (Upload → Commit)
@@ -269,23 +294,7 @@ Layer 6: File Security (Virus Scanning, Access Control)
2. Phase 2: Commit to `permanent/` when operation succeeds
3. Cleanup: Cron Job ลบไฟล์ค้างใน `temp/` > 24h
**Related:** [specs/05-decisions/002-file-storage.md](../05-decisions/002-file-storage.md)
### ADR-003: Document Numbering Strategy
**Decision:** ใช้ Application-Level Locking แทน Database Stored Procedure
**Rationale:**
- ยืดหยุ่นกว่า (Token-Based Generator)
- ง่ายต่อการ Debug
- รองรับ Complex Numbering Rules
**Implementation:**
- Layer 1: Redis Distributed Lock
- Layer 2: Optimistic Database Lock (`@VersionColumn()`)
- Retry with Exponential Backoff
**Related:** [ADR-003](../05-decisions/ADR-003-file-storage-approach.md)
### ADR-004: 4-Level RBAC
@@ -303,6 +312,8 @@ Layer 6: File Security (Virus Scanning, Access Control)
- Redis Cache for Performance
- Permission Checking at Guard Level
**Related:** [ADR-004](../05-decisions/ADR-004-rbac-implementation.md)
---
## 📊 Architecture Diagrams
+6 -6
View File
@@ -3,10 +3,10 @@
---
**title:** 'API Design'
**version:** 1.5.0
**status:** first-draft
**version:** 1.5.1
**status:** active
**owner:** Nattanin Peancharoen
**last_updated:** 2025-11-30
**last_updated:** 2025-12-02
**related:**
- specs/01-requirements/02-architecture.md
@@ -545,7 +545,7 @@ X-API-Deprecation-Info: https://docs.np-dms.work/migration/v2
**Document Control:**
- **Version:** 1.5.0
- **Status:** First Draft
- **Last Updated:** 2025-11-30
- **Version:** 1.5.1
- **Status:** Active
- **Last Updated:** 2025-12-02
- **Owner:** Nattanin Peancharoen
+4 -4
View File
@@ -44,7 +44,7 @@
- RAM: 32GB
- Storage: /share/dms-data
- **IP Address:** 159.192.126.103
- **Domain:** np-dms.work, www.np-dms.work
- **Domain:** np-dms.work, <www.np-dms.work>
- **Containerization:** Docker & Docker Compose via Container Station
- **Development Environment:** VS Code/Cursor on Windows 11
@@ -943,9 +943,9 @@ graph LR
**Document Control:**
- **Version:** 1.5.0
- **Status:** First Draft
- **Last Updated:** 2025-11-30
- **Version:** 1.5.1
- **Status:** Active
- **Last Updated:** 2025-12-02
- **Owner:** Nattanin Peancharoen
```
@@ -0,0 +1,627 @@
# Document Numbering Implementation Guide
---
title: 'Implementation Guide: Document Numbering System'
version: 1.5.1
status: draft
owner: Development Team
last_updated: 2025-12-02
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 10.11
- **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_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 NOT NULL,
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 NOT NULL,
ip_address VARCHAR(45),
user_agent TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- 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. 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.3. 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
```typescript
// File: src/modules/document-numbering/controllers/document-numbering.controller.ts
import { Controller, Post, Put, Body, Param, UseGuards } from '@nestjs/common';
import { ThrottlerGuard } from '@nestjs/throttler';
import { Throttle } from '@nestjs/throttler';
import { DocumentNumberingService } from '../services/document-numbering.service';
import { Roles } from 'src/auth/decorators/roles.decorator';
@Controller('document-numbering')
@UseGuards(ThrottlerGuard)
export class DocumentNumberingController {
constructor(
private readonly documentNumberingService: DocumentNumberingService,
) {}
@Post('generate')
@Throttle(10, 60) // 10 requests per 60 seconds
async generateNumber(@Body() dto: GenerateNumberDto) {
const number = await this.documentNumberingService.generateDocumentNumber(dto);
return { documentNumber: number };
}
@Put('configs/:configId')
@Roles('PROJECT_ADMIN')
async updateTemplate(
@Param('configId') configId: number,
@Body() dto: UpdateTemplateDto,
) {
// Update template configuration
}
@Post('configs/:configId/reset-counter')
@Roles('SUPER_ADMIN')
async resetCounter(
@Param('configId') configId: number,
@Body() dto: ResetCounterDto,
) {
// Manual counter reset (requires approval)
}
}
```
## 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)
+3 -3
View File
@@ -1,8 +1,8 @@
# Operations Documentation
**Project:** LCBP3-DMS (Laem Chabang Port Phase 3 - Document Management System)
**Version:** 1.5.0
**Last Updated:** 2025-12-01
**Version:** 1.5.1
**Last Updated:** 2025-12-02
---
@@ -185,6 +185,6 @@ graph TB
---
**Version:** 1.5.0
**Version:** 1.5.1
**Status:** Active
**Classification:** Internal Use Only
+3 -3
View File
@@ -1,8 +1,8 @@
# Backup & Recovery Procedures
**Project:** LCBP3-DMS
**Version:** 1.5.0
**Last Updated:** 2025-12-01
**Version:** 1.5.1
**Last Updated:** 2025-12-02
---
@@ -369,6 +369,6 @@ WHERE created_at < DATE_SUB(NOW(), INTERVAL 1 YEAR);
---
**Version:** 1.5.0
**Version:** 1.5.1
**Last Review:** 2025-12-01
**Next Review:** 2026-03-01
+937
View File
@@ -0,0 +1,937 @@
# Deployment Guide: LCBP3-DMS
---
**Project:** LCBP3-DMS (Laem Chabang Port Phase 3 - Document Management System)
**Version:** 1.5.1
**Last Updated:** 2025-12-02
**Owner:** Operations Team
**Status:** Active
---
## 📋 Overview
This guide provides step-by-step instructions for deploying the LCBP3-DMS system on QNAP Container Station using Docker Compose with Blue-Green deployment strategy.
### Deployment Strategy
- **Platform:** QNAP TS-473A with Container Station
- **Orchestration:** Docker Compose
- **Deployment Method:** Blue-Green Deployment
- **Zero Downtime:** Yes
- **Rollback Capability:** Instant rollback via NGINX switch
---
## 🎯 Prerequisites
### Hardware Requirements
| Component | Minimum Specification |
| -------------- | -------------------------- |
| CPU | 4 cores @ 2.0 GHz |
| RAM | 16 GB |
| Storage | 500 GB SSD (System + Data) |
| Network | 1 Gbps Ethernet |
| QNAP Model | TS-473A or equivalent |
### Software Requirements
| Software | Version | Purpose |
| ----------------- | ------- | ------------------------ |
| QNAP QTS | 5.x+ | Operating System |
| Container Station | 3.x+ | Docker Management |
| Docker | 20.10+ | Container Runtime |
| Docker Compose | 2.x+ | Multi-container Orchestr |
### Network Requirements
- Static IP address for QNAP server
- Domain name (e.g., `lcbp3-dms.example.com`)
- SSL certificate (Let's Encrypt or commercial)
- Firewall rules:
- Port 80 (HTTP → HTTPS redirect)
- Port 443 (HTTPS)
- Port 22 (SSH for management)
---
## 🏗️ Infrastructure Setup
### 1. Directory Structure
Create the following directory structure on QNAP:
```bash
# SSH into QNAP
ssh admin@qnap-ip
# Create base directory
mkdir -p /volume1/lcbp3
# Create blue-green environments
mkdir -p /volume1/lcbp3/blue
mkdir -p /volume1/lcbp3/green
# Create shared directories
mkdir -p /volume1/lcbp3/shared/uploads
mkdir -p /volume1/lcbp3/shared/logs
mkdir -p /volume1/lcbp3/shared/backups
# Create persistent volumes
mkdir -p /volume1/lcbp3/volumes/mariadb-data
mkdir -p /volume1/lcbp3/volumes/redis-data
mkdir -p /volume1/lcbp3/volumes/elastic-data
# Create NGINX proxy directory
mkdir -p /volume1/lcbp3/nginx-proxy
# Set permissions
chmod -R 755 /volume1/lcbp3
chown -R admin:administrators /volume1/lcbp3
```
**Final Structure:**
```
/volume1/lcbp3/
├── blue/ # Blue environment
│ ├── docker-compose.yml
│ ├── .env.production
│ └── nginx.conf
├── green/ # Green environment
│ ├── docker-compose.yml
│ ├── .env.production
│ └── nginx.conf
├── nginx-proxy/ # Main reverse proxy
│ ├── docker-compose.yml
│ ├── nginx.conf
│ └── ssl/
│ ├── cert.pem
│ └── key.pem
├── shared/ # Shared across blue/green
│ ├── uploads/
│ ├── logs/
│ └── backups/
├── volumes/ # Persistent data
│ ├── mariadb-data/
│ ├── redis-data/
│ └── elastic-data/
├── scripts/ # Deployment scripts
│ ├── deploy.sh
│ ├── rollback.sh
│ └── health-check.sh
└── current # File containing "blue" or "green"
```
### 2. SSL Certificate Setup
```bash
# Option 1: Let's Encrypt (Recommended)
# Install certbot on QNAP
opkg install certbot
# Generate certificate
certbot certonly --standalone \
-d lcbp3-dms.example.com \
--email admin@example.com \
--agree-tos
# Copy to nginx-proxy
cp /etc/letsencrypt/live/lcbp3-dms.example.com/fullchain.pem \
/volume1/lcbp3/nginx-proxy/ssl/cert.pem
cp /etc/letsencrypt/live/lcbp3-dms.example.com/privkey.pem \
/volume1/lcbp3/nginx-proxy/ssl/key.pem
# Option 2: Commercial Certificate
# Upload cert.pem and key.pem to /volume1/lcbp3/nginx-proxy/ssl/
```
---
## 📝 Configuration Files
### 1. Environment Variables (.env.production)
Create `.env.production` in both `blue/` and `green/` directories:
```bash
# File: /volume1/lcbp3/blue/.env.production
# DO NOT commit this file to Git!
# Application
NODE_ENV=production
APP_NAME=LCBP3-DMS
APP_URL=https://lcbp3-dms.example.com
# Database
DB_HOST=lcbp3-mariadb
DB_PORT=3306
DB_USERNAME=lcbp3_user
DB_PASSWORD=<CHANGE_ME_STRONG_PASSWORD>
DB_DATABASE=lcbp3_dms
DB_POOL_SIZE=20
# Redis
REDIS_HOST=lcbp3-redis
REDIS_PORT=6379
REDIS_PASSWORD=<CHANGE_ME_STRONG_PASSWORD>
REDIS_DB=0
# JWT Authentication
JWT_SECRET=<CHANGE_ME_RANDOM_64_CHAR_STRING>
JWT_EXPIRES_IN=8h
JWT_REFRESH_EXPIRES_IN=7d
# File Storage
UPLOAD_PATH=/app/uploads
MAX_FILE_SIZE=52428800
ALLOWED_FILE_TYPES=.pdf,.doc,.docx,.xls,.xlsx,.dwg,.zip
# Email (SMTP)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USERNAME=<YOUR_EMAIL>
SMTP_PASSWORD=<YOUR_APP_PASSWORD>
SMTP_FROM=noreply@example.com
# Elasticsearch
ELASTICSEARCH_NODE=http://lcbp3-elasticsearch:9200
ELASTICSEARCH_USERNAME=elastic
ELASTICSEARCH_PASSWORD=<CHANGE_ME>
# Rate Limiting
THROTTLE_TTL=60
THROTTLE_LIMIT=100
# Logging
LOG_LEVEL=info
LOG_FILE_PATH=/app/logs
# ClamAV (Virus Scanning)
CLAMAV_HOST=lcbp3-clamav
CLAMAV_PORT=3310
```
### 2. Docker Compose - Blue Environment
```yaml
# File: /volume1/lcbp3/blue/docker-compose.yml
version: '3.8'
services:
backend:
image: lcbp3-backend:latest
container_name: lcbp3-blue-backend
restart: unless-stopped
env_file:
- .env.production
volumes:
- /volume1/lcbp3/shared/uploads:/app/uploads
- /volume1/lcbp3/shared/logs:/app/logs
depends_on:
- mariadb
- redis
- elasticsearch
networks:
- lcbp3-network
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
frontend:
image: lcbp3-frontend:latest
container_name: lcbp3-blue-frontend
restart: unless-stopped
environment:
- NEXT_PUBLIC_API_URL=https://lcbp3-dms.example.com/api
depends_on:
- backend
networks:
- lcbp3-network
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000']
interval: 30s
timeout: 10s
retries: 3
mariadb:
image: mariadb:10.11
container_name: lcbp3-mariadb
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
MYSQL_DATABASE: ${DB_DATABASE}
MYSQL_USER: ${DB_USERNAME}
MYSQL_PASSWORD: ${DB_PASSWORD}
volumes:
- /volume1/lcbp3/volumes/mariadb-data:/var/lib/mysql
networks:
- lcbp3-network
command: >
--character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci
--max_connections=200
--innodb_buffer_pool_size=2G
healthcheck:
test: ['CMD', 'mysqladmin', 'ping', '-h', 'localhost']
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: lcbp3-redis
restart: unless-stopped
command: >
redis-server
--requirepass ${REDIS_PASSWORD}
--appendonly yes
--appendfsync everysec
--maxmemory 2gb
--maxmemory-policy allkeys-lru
volumes:
- /volume1/lcbp3/volumes/redis-data:/data
networks:
- lcbp3-network
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 10s
timeout: 3s
retries: 3
elasticsearch:
image: elasticsearch:8.11.0
container_name: lcbp3-elasticsearch
restart: unless-stopped
environment:
- discovery.type=single-node
- xpack.security.enabled=true
- ELASTIC_PASSWORD=${ELASTICSEARCH_PASSWORD}
- "ES_JAVA_OPTS=-Xms2g -Xmx2g"
volumes:
- /volume1/lcbp3/volumes/elastic-data:/usr/share/elasticsearch/data
networks:
- lcbp3-network
healthcheck:
test: ['CMD-SHELL', 'curl -f http://localhost:9200/_cluster/health || exit 1']
interval: 30s
timeout: 10s
retries: 5
networks:
lcbp3-network:
name: lcbp3-blue-network
driver: bridge
```
### 3. Docker Compose - NGINX Proxy
```yaml
# File: /volume1/lcbp3/nginx-proxy/docker-compose.yml
version: '3.8'
services:
nginx:
image: nginx:alpine
container_name: lcbp3-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
- /volume1/lcbp3/shared/logs/nginx:/var/log/nginx
networks:
- lcbp3-blue-network
- lcbp3-green-network
healthcheck:
test: ['CMD', 'nginx', '-t']
interval: 30s
timeout: 10s
retries: 3
networks:
lcbp3-blue-network:
external: true
lcbp3-green-network:
external: true
```
### 4. NGINX Configuration
```nginx
# File: /volume1/lcbp3/nginx-proxy/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 50M;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss;
# Upstream backends (switch between blue/green)
upstream backend {
server lcbp3-blue-backend:3000 max_fails=3 fail_timeout=30s;
keepalive 32;
}
upstream frontend {
server lcbp3-blue-frontend:3000 max_fails=3 fail_timeout=30s;
keepalive 32;
}
# HTTP to HTTPS redirect
server {
listen 80;
server_name lcbp3-dms.example.com;
return 301 https://$server_name$request_uri;
}
# HTTPS server
server {
listen 443 ssl http2;
server_name lcbp3-dms.example.com;
# SSL configuration
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Frontend (Next.js)
location / {
proxy_pass http://frontend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
}
# Backend API
location /api {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeouts for file uploads
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
# Health check endpoint (no logging)
location /health {
proxy_pass http://backend/health;
access_log off;
}
# Static files caching
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
proxy_pass http://frontend;
expires 1y;
add_header Cache-Control "public, immutable";
}
}
}
```
---
## 🚀 Initial Deployment
### Step 1: Prepare Docker Images
```bash
# Build images (on development machine)
cd /path/to/lcbp3/backend
docker build -t lcbp3-backend:1.0.0 .
docker tag lcbp3-backend:1.0.0 lcbp3-backend:latest
cd /path/to/lcbp3/frontend
docker build -t lcbp3-frontend:1.0.0 .
docker tag lcbp3-frontend:1.0.0 lcbp3-frontend:latest
# Save images to tar files
docker save lcbp3-backend:latest | gzip > lcbp3-backend-latest.tar.gz
docker save lcbp3-frontend:latest | gzip > lcbp3-frontend-latest.tar.gz
# Transfer to QNAP
scp lcbp3-backend-latest.tar.gz admin@qnap-ip:/volume1/lcbp3/
scp lcbp3-frontend-latest.tar.gz admin@qnap-ip:/volume1/lcbp3/
# Load images on QNAP
ssh admin@qnap-ip
cd /volume1/lcbp3
docker load < lcbp3-backend-latest.tar.gz
docker load < lcbp3-frontend-latest.tar.gz
```
### Step 2: Initialize Database
```bash
# Start MariaDB only
cd /volume1/lcbp3/blue
docker-compose up -d mariadb
# Wait for MariaDB to be ready
docker exec lcbp3-mariadb mysqladmin ping -h localhost
# Run migrations
docker-compose up -d backend
docker exec lcbp3-blue-backend npm run migration:run
# Seed initial data (if needed)
docker exec lcbp3-blue-backend npm run seed
```
### Step 3: Start Blue Environment
```bash
cd /volume1/lcbp3/blue
# Start all services
docker-compose up -d
# Check status
docker-compose ps
# View logs
docker-compose logs -f
# Wait for health checks
sleep 30
# Test health endpoint
curl http://localhost:3000/health
```
### Step 4: Start NGINX Proxy
```bash
cd /volume1/lcbp3/nginx-proxy
# Create networks (if not exist)
docker network create lcbp3-blue-network
docker network create lcbp3-green-network
# Start NGINX
docker-compose up -d
# Test NGINX configuration
docker exec lcbp3-nginx nginx -t
# Check NGINX logs
docker logs lcbp3-nginx
```
### Step 5: Set Current Environment
```bash
# Mark blue as current
echo "blue" > /volume1/lcbp3/current
```
### Step 6: Verify Deployment
```bash
# Test HTTPS endpoint
curl -k https://lcbp3-dms.example.com/health
# Test API
curl -k https://lcbp3-dms.example.com/api/health
# Check all containers
docker ps --filter "name=lcbp3"
# Check logs for errors
docker-compose -f /volume1/lcbp3/blue/docker-compose.yml logs --tail=100
```
---
## 🔄 Blue-Green Deployment Process
### Deployment Script
```bash
# File: /volume1/lcbp3/scripts/deploy.sh
#!/bin/bash
set -e # Exit on error
# Configuration
LCBP3_DIR="/volume1/lcbp3"
CURRENT=$(cat $LCBP3_DIR/current)
TARGET=$([[ "$CURRENT" == "blue" ]] && echo "green" || echo "blue")
echo "========================================="
echo "LCBP3-DMS Blue-Green Deployment"
echo "========================================="
echo "Current environment: $CURRENT"
echo "Target environment: $TARGET"
echo "========================================="
# Step 1: Backup database
echo "[1/9] Creating database backup..."
BACKUP_FILE="$LCBP3_DIR/shared/backups/db-backup-$(date +%Y%m%d-%H%M%S).sql"
docker exec lcbp3-mariadb mysqldump -u root -p${DB_PASSWORD} lcbp3_dms > $BACKUP_FILE
gzip $BACKUP_FILE
echo "✓ Backup created: $BACKUP_FILE.gz"
# Step 2: Pull latest images
echo "[2/9] Pulling latest Docker images..."
cd $LCBP3_DIR/$TARGET
docker-compose pull
echo "✓ Images pulled"
# Step 3: Update configuration
echo "[3/9] Updating configuration..."
# Copy .env if changed
if [ -f "$LCBP3_DIR/.env.production.new" ]; then
cp $LCBP3_DIR/.env.production.new $LCBP3_DIR/$TARGET/.env.production
echo "✓ Configuration updated"
fi
# Step 4: Start target environment
echo "[4/9] Starting $TARGET environment..."
docker-compose up -d
echo "✓ $TARGET environment started"
# Step 5: Wait for services to be ready
echo "[5/9] Waiting for services to be healthy..."
sleep 10
# Check backend health
for i in {1..30}; do
if docker exec lcbp3-${TARGET}-backend curl -f http://localhost:3000/health > /dev/null 2>&1; then
echo "✓ Backend is healthy"
break
fi
if [ $i -eq 30 ]; then
echo "✗ Backend health check failed!"
docker-compose logs backend
exit 1
fi
sleep 2
done
# Step 6: Run database migrations
echo "[6/9] Running database migrations..."
docker exec lcbp3-${TARGET}-backend npm run migration:run
echo "✓ Migrations completed"
# Step 7: Switch NGINX to target environment
echo "[7/9] Switching NGINX to $TARGET..."
sed -i "s/lcbp3-${CURRENT}-backend/lcbp3-${TARGET}-backend/g" $LCBP3_DIR/nginx-proxy/nginx.conf
sed -i "s/lcbp3-${CURRENT}-frontend/lcbp3-${TARGET}-frontend/g" $LCBP3_DIR/nginx-proxy/nginx.conf
docker exec lcbp3-nginx nginx -t
docker exec lcbp3-nginx nginx -s reload
echo "✓ NGINX switched to $TARGET"
# Step 8: Verify new environment
echo "[8/9] Verifying new environment..."
sleep 5
if curl -f -k https://lcbp3-dms.example.com/health > /dev/null 2>&1; then
echo "✓ New environment is responding"
else
echo "✗ New environment verification failed!"
echo "Rolling back..."
./rollback.sh
exit 1
fi
# Step 9: Stop old environment
echo "[9/9] Stopping $CURRENT environment..."
cd $LCBP3_DIR/$CURRENT
docker-compose down
echo "✓ $CURRENT environment stopped"
# Update current pointer
echo "$TARGET" > $LCBP3_DIR/current
echo "========================================="
echo "✓ Deployment completed successfully!"
echo "Active environment: $TARGET"
echo "========================================="
# Send notification (optional)
# /scripts/send-notification.sh "Deployment completed: $TARGET is now active"
```
### Rollback Script
```bash
# File: /volume1/lcbp3/scripts/rollback.sh
#!/bin/bash
set -e
LCBP3_DIR="/volume1/lcbp3"
CURRENT=$(cat $LCBP3_DIR/current)
PREVIOUS=$([[ "$CURRENT" == "blue" ]] && echo "green" || echo "blue")
echo "========================================="
echo "LCBP3-DMS Rollback"
echo "========================================="
echo "Current: $CURRENT"
echo "Rolling back to: $PREVIOUS"
echo "========================================="
# Switch NGINX back
echo "[1/3] Switching NGINX to $PREVIOUS..."
sed -i "s/lcbp3-${CURRENT}-backend/lcbp3-${PREVIOUS}-backend/g" $LCBP3_DIR/nginx-proxy/nginx.conf
sed -i "s/lcbp3-${CURRENT}-frontend/lcbp3-${PREVIOUS}-frontend/g" $LCBP3_DIR/nginx-proxy/nginx.conf
docker exec lcbp3-nginx nginx -s reload
echo "✓ NGINX switched"
# Start previous environment if stopped
echo "[2/3] Ensuring $PREVIOUS environment is running..."
cd $LCBP3_DIR/$PREVIOUS
docker-compose up -d
sleep 10
echo "✓ $PREVIOUS environment is running"
# Verify
echo "[3/3] Verifying rollback..."
if curl -f -k https://lcbp3-dms.example.com/health > /dev/null 2>&1; then
echo "✓ Rollback successful"
echo "$PREVIOUS" > $LCBP3_DIR/current
else
echo "✗ Rollback verification failed!"
exit 1
fi
echo "========================================="
echo "✓ Rollback completed"
echo "Active environment: $PREVIOUS"
echo "========================================="
```
### Make Scripts Executable
```bash
chmod +x /volume1/lcbp3/scripts/deploy.sh
chmod +x /volume1/lcbp3/scripts/rollback.sh
```
---
## 📋 Deployment Checklist
### Pre-Deployment
- [ ] Backup current database
- [ ] Tag Docker images with version
- [ ] Update `.env.production` if needed
- [ ] Review migration scripts
- [ ] Notify stakeholders of deployment window
- [ ] Verify SSL certificate validity (> 30 days)
- [ ] Check disk space (> 20% free)
- [ ] Review recent error logs
### During Deployment
- [ ] Pull latest Docker images
- [ ] Start target environment (blue/green)
- [ ] Run database migrations
- [ ] Verify health checks pass
- [ ] Switch NGINX proxy
- [ ] Verify application responds correctly
- [ ] Check for errors in logs
- [ ] Monitor performance metrics
### Post-Deployment
- [ ] Monitor logs for 30 minutes
- [ ] Check performance metrics
- [ ] Verify all features working
- [ ] Test critical user flows
- [ ] Stop old environment
- [ ] Update deployment log
- [ ] Notify stakeholders of completion
- [ ] Archive old Docker images
---
## 🔍 Troubleshooting
### Common Issues
#### 1. Container Won't Start
```bash
# Check logs
docker logs lcbp3-blue-backend
# Check resource usage
docker stats
# Restart container
docker restart lcbp3-blue-backend
```
#### 2. Database Connection Failed
```bash
# Check MariaDB is running
docker ps | grep mariadb
# Test connection
docker exec lcbp3-mariadb mysql -u lcbp3_user -p -e "SELECT 1"
# Check environment variables
docker exec lcbp3-blue-backend env | grep DB_
```
#### 3. NGINX 502 Bad Gateway
```bash
# Check backend is running
curl http://localhost:3000/health
# Check NGINX configuration
docker exec lcbp3-nginx nginx -t
# Check NGINX logs
docker logs lcbp3-nginx
# Reload NGINX
docker exec lcbp3-nginx nginx -s reload
```
#### 4. Migration Failed
```bash
# Check migration status
docker exec lcbp3-blue-backend npm run migration:show
# Revert last migration
docker exec lcbp3-blue-backend npm run migration:revert
# Re-run migrations
docker exec lcbp3-blue-backend npm run migration:run
```
---
## 📊 Monitoring
### Health Checks
```bash
# Backend health
curl https://lcbp3-dms.example.com/health
# Database health
docker exec lcbp3-mariadb mysqladmin ping
# Redis health
docker exec lcbp3-redis redis-cli ping
# All containers status
docker ps --filter "name=lcbp3" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
```
### Performance Monitoring
```bash
# Container resource usage
docker stats --no-stream
# Disk usage
df -h /volume1/lcbp3
# Database size
docker exec lcbp3-mariadb mysql -u root -p -e "
SELECT table_schema AS 'Database',
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS 'Size (MB)'
FROM information_schema.tables
WHERE table_schema = 'lcbp3_dms'
GROUP BY table_schema;"
```
---
## 🔐 Security Best Practices
1. **Change Default Passwords:** Update all passwords in `.env.production`
2. **SSL/TLS:** Always use HTTPS in production
3. **Firewall:** Only expose ports 80, 443, and 22 (SSH)
4. **Regular Updates:** Keep Docker images updated
5. **Backup Encryption:** Encrypt database backups
6. **Access Control:** Limit SSH access to specific IPs
7. **Secrets Management:** Never commit `.env` files to Git
8. **Log Monitoring:** Review logs daily for suspicious activity
---
## 📚 Related Documentation
- [Environment Setup Guide](./environment-setup.md)
- [Backup & Recovery](./backup-recovery.md)
- [Monitoring & Alerting](./monitoring-alerting.md)
- [Maintenance Procedures](./maintenance-procedures.md)
- [ADR-015: Deployment Infrastructure](../05-decisions/ADR-015-deployment-infrastructure.md)
---
**Version:** 1.5.1
**Last Updated:** 2025-12-02
**Next Review:** 2026-06-01
View File
@@ -0,0 +1,684 @@
# Document Numbering Operations Guide
---
title: 'Operations Guide: Document Numbering System'
version: 1.6.0
status: draft
owner: Operations Team
last_updated: 2025-12-02
related:
- specs/01-requirements/03.11-document-numbering.md
- specs/03-implementation/document-numbering.md
- specs/04-operations/monitoring-alerting.md
---
## Overview
เอกสารนี้อธิบาย operations procedures, monitoring, และ troubleshooting สำหรับระบบ Document Numbering
## 1. Performance Requirements
### 1.1. Response Time Targets
| Metric | Target | Measurement |
|--------|--------|-------------|
| 95th percentile | ≤ 2 วินาที | ตั้งแต่ request ถึง response |
| 99th percentile | ≤ 5 วินาที | ตั้งแต่ request ถึง response |
| Normal operation | ≤ 500ms | ไม่มี retry |
### 1.2. Throughput Targets
| Load Level | Target | Notes |
|------------|--------|-------|
| Normal load | ≥ 50 req/s | ใช้งานปกติ |
| Peak load | ≥ 100 req/s | ช่วงเร่งงาน |
| Burst capacity | ≥ 200 req/s | Short duration (< 1 min) |
### 1.3. Availability SLA
- **Uptime**: ≥ 99.5% (excluding planned maintenance)
- **Maximum downtime**: ≤ 3.6 ชั่วโมง/เดือน (~ 8.6 นาที/วัน)
- **Recovery Time Objective (RTO)**: ≤ 30 นาที
- **Recovery Point Objective (RPO)**: ≤ 5 นาที
## 2. Infrastructure Setup
### 2.1. Database Configuration
#### MariaDB Connection Pool
```typescript
// ormconfig.ts
{
type: 'mysql',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
extra: {
connectionLimit: 20, // Pool size
queueLimit: 0, // Unlimited queue
acquireTimeout: 10000, // 10s timeout
retryAttempts: 3,
retryDelay: 1000
}
}
```
#### High Availability Setup
```yaml
# docker-compose.yml
services:
mariadb-master:
image: mariadb:10.11
environment:
MYSQL_REPLICATION_MODE: master
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
volumes:
- mariadb-master-data:/var/lib/mysql
networks:
- backend
mariadb-replica:
image: mariadb:10.11
environment:
MYSQL_REPLICATION_MODE: slave
MYSQL_MASTER_HOST: mariadb-master
MYSQL_MASTER_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
volumes:
- mariadb-replica-data:/var/lib/mysql
networks:
- backend
```
### 2.2. Redis Configuration
#### Redis Sentinel for High Availability
```yaml
# docker-compose.yml
services:
redis-master:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis-master-data:/data
networks:
- backend
redis-replica:
image: redis:7-alpine
command: redis-server --replicaof redis-master 6379 --appendonly yes
volumes:
- redis-replica-data:/data
networks:
- backend
redis-sentinel:
image: redis:7-alpine
command: >
redis-sentinel /etc/redis/sentinel.conf
--sentinel monitor mymaster redis-master 6379 2
--sentinel down-after-milliseconds mymaster 5000
--sentinel failover-timeout mymaster 10000
networks:
- backend
```
#### Redis Connection Pool
```typescript
// redis.config.ts
import IORedis from 'ioredis';
export const redisConfig = {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT) || 6379,
password: process.env.REDIS_PASSWORD,
maxRetriesPerRequest: 3,
enableReadyCheck: true,
lazyConnect: false,
poolSize: 10,
retryStrategy: (times: number) => {
if (times > 3) {
return null; // Stop retry
}
return Math.min(times * 100, 3000);
},
};
```
### 2.3. Load Balancing
#### Nginx Configuration
```nginx
# nginx.conf
upstream backend {
least_conn; # Least connections algorithm
server backend-1:3000 max_fails=3 fail_timeout=30s weight=1;
server backend-2:3000 max_fails=3 fail_timeout=30s weight=1;
server backend-3:3000 max_fails=3 fail_timeout=30s weight=1;
keepalive 32;
}
server {
listen 80;
server_name api.lcbp3.local;
location /api/v1/document-numbering/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_next_upstream error timeout;
proxy_connect_timeout 10s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
}
```
#### Docker Compose Scaling
```yaml
# docker-compose.yml
services:
backend:
image: lcbp3-backend:latest
deploy:
replicas: 3
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
environment:
NODE_ENV: production
DB_POOL_SIZE: 20
networks:
- backend
```
## 3. Monitoring & Metrics
### 3.1. Prometheus Metrics
#### Key Metrics to Collect
```typescript
// metrics.service.ts
import { Counter, Histogram, Gauge } from 'prom-client';
// Lock acquisition metrics
export const lockAcquisitionDuration = new Histogram({
name: 'docnum_lock_acquisition_duration_ms',
help: 'Lock acquisition time in milliseconds',
labelNames: ['project', 'type'],
buckets: [10, 50, 100, 200, 500, 1000, 2000, 5000],
});
export const lockAcquisitionFailures = new Counter({
name: 'docnum_lock_acquisition_failures_total',
help: 'Total number of lock acquisition failures',
labelNames: ['project', 'type', 'reason'],
});
// Generation metrics
export const generationDuration = new Histogram({
name: 'docnum_generation_duration_ms',
help: 'Total document number generation time',
labelNames: ['project', 'type', 'status'],
buckets: [100, 200, 500, 1000, 2000, 5000],
});
export const retryCount = new Histogram({
name: 'docnum_retry_count',
help: 'Number of retries per generation',
labelNames: ['project', 'type'],
buckets: [0, 1, 2, 3, 5, 10],
});
// Connection health
export const redisConnectionStatus = new Gauge({
name: 'docnum_redis_connection_status',
help: 'Redis connection status (1=up, 0=down)',
});
export const dbConnectionPoolUsage = new Gauge({
name: 'docnum_db_connection_pool_usage',
help: 'Database connection pool usage percentage',
});
```
### 3.2. Prometheus Alert Rules
```yaml
# prometheus/alerts.yml
groups:
- name: document_numbering_alerts
interval: 30s
rules:
# CRITICAL: Redis unavailable
- alert: RedisUnavailable
expr: docnum_redis_connection_status == 0
for: 1m
labels:
severity: critical
component: document-numbering
annotations:
summary: "Redis is unavailable for document numbering"
description: "System is falling back to DB-only locking. Performance degraded by 30-50%."
runbook_url: "https://wiki.lcbp3/runbooks/redis-unavailable"
# CRITICAL: High lock failure rate
- alert: HighLockFailureRate
expr: |
rate(docnum_lock_acquisition_failures_total[5m]) > 0.1
for: 5m
labels:
severity: critical
component: document-numbering
annotations:
summary: "Lock acquisition failure rate > 10%"
description: "Check Redis and database performance immediately"
runbook_url: "https://wiki.lcbp3/runbooks/high-lock-failure"
# WARNING: Elevated lock failure rate
- alert: ElevatedLockFailureRate
expr: |
rate(docnum_lock_acquisition_failures_total[5m]) > 0.05
for: 5m
labels:
severity: warning
component: document-numbering
annotations:
summary: "Lock acquisition failure rate > 5%"
description: "Monitor closely. May escalate to critical soon."
# WARNING: Slow lock acquisition
- alert: SlowLockAcquisition
expr: |
histogram_quantile(0.95,
rate(docnum_lock_acquisition_duration_ms_bucket[5m])
) > 1000
for: 5m
labels:
severity: warning
component: document-numbering
annotations:
summary: "P95 lock acquisition time > 1 second"
description: "Lock acquisition is slower than expected. Check Redis latency."
# WARNING: High retry count
- alert: HighRetryCount
expr: |
sum by (project) (
rate(docnum_retry_count_sum[1h])
) > 100
for: 1h
labels:
severity: warning
component: document-numbering
annotations:
summary: "Retry count > 100 per hour in project {{ $labels.project }}"
description: "High contention detected. Consider scaling."
# WARNING: Slow generation
- alert: SlowDocumentNumberGeneration
expr: |
histogram_quantile(0.95,
rate(docnum_generation_duration_ms_bucket[5m])
) > 2000
for: 5m
labels:
severity: warning
component: document-numbering
annotations:
summary: "P95 generation time > 2 seconds"
description: "Document number generation is slower than SLA target"
```
### 3.3. AlertManager Configuration
```yaml
# alertmanager/config.yml
global:
resolve_timeout: 5m
slack_api_url: ${SLACK_WEBHOOK_URL}
route:
group_by: ['alertname', 'severity', 'project']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: 'ops-team'
routes:
# CRITICAL alerts → PagerDuty + Slack
- match:
severity: critical
receiver: 'pagerduty-critical'
continue: true
- match:
severity: critical
receiver: 'slack-critical'
continue: false
# WARNING alerts → Slack only
- match:
severity: warning
receiver: 'slack-warnings'
receivers:
- name: 'pagerduty-critical'
pagerduty_configs:
- service_key: ${PAGERDUTY_SERVICE_KEY}
description: '{{ .GroupLabels.alertname }}: {{ .CommonAnnotations.summary }}'
details:
firing: '{{ .Alerts.Firing | len }}'
resolved: '{{ .Alerts.Resolved | len }}'
runbook: '{{ .CommonAnnotations.runbook_url }}'
- name: 'slack-critical'
slack_configs:
- channel: '#lcbp3-critical-alerts'
title: '🚨 CRITICAL: {{ .GroupLabels.alertname }}'
text: |
*Summary:* {{ .CommonAnnotations.summary }}
*Description:* {{ .CommonAnnotations.description }}
*Runbook:* {{ .CommonAnnotations.runbook_url }}
color: 'danger'
- name: 'slack-warnings'
slack_configs:
- channel: '#lcbp3-alerts'
title: '⚠️ WARNING: {{ .GroupLabels.alertname }}'
text: '{{ .CommonAnnotations.description }}'
color: 'warning'
- name: 'ops-team'
email_configs:
- to: 'ops@example.com'
subject: '[LCBP3] {{ .GroupLabels.alertname }}'
```
### 3.4. Grafana Dashboard
Dashboard panels ที่สำคัญ:
1. **Lock Acquisition Success Rate** (Gauge)
- Query: `1 - (rate(docnum_lock_acquisition_failures_total[5m]) / rate(docnum_lock_acquisition_total[5m]))`
- Alert threshold: < 95%
2. **Lock Acquisition Time Percentiles** (Graph)
- P50: `histogram_quantile(0.50, rate(docnum_lock_acquisition_duration_ms_bucket[5m]))`
- P95: `histogram_quantile(0.95, rate(docnum_lock_acquisition_duration_ms_bucket[5m]))`
- P99: `histogram_quantile(0.99, rate(docnum_lock_acquisition_duration_ms_bucket[5m]))`
3. **Generation Rate** (Stat)
- Query: `sum(rate(docnum_generation_duration_ms_count[1m])) * 60`
- Unit: documents/minute
4. **Error Rate by Type** (Graph)
- Query: `sum by (reason) (rate(docnum_lock_acquisition_failures_total[5m]))`
5. **Redis Connection Status** (Stat)
- Query: `docnum_redis_connection_status`
- Thresholds: 0 = red, 1 = green
6. **DB Connection Pool Usage** (Gauge)
- Query: `docnum_db_connection_pool_usage`
- Alert threshold: > 80%
## 4. Troubleshooting Runbooks
### 4.1. Scenario: Redis Unavailable
**Symptoms:**
- Alert: `RedisUnavailable`
- System falls back to DB-only locking
- Performance degraded 30-50%
**Action Steps:**
1. **Check Redis status:**
```bash
docker exec lcbp3-redis redis-cli ping
# Expected: PONG
```
2. **Check Redis logs:**
```bash
docker logs lcbp3-redis --tail=100
```
3. **Restart Redis (if needed):**
```bash
docker restart lcbp3-redis
```
4. **Verify failover (if using Sentinel):**
```bash
docker exec lcbp3-redis-sentinel redis-cli -p 26379 SENTINEL masters
```
5. **Monitor recovery:**
- Check metric: `docnum_redis_connection_status` returns to 1
- Check performance: P95 latency returns to normal (< 500ms)
### 4.2. Scenario: High Lock Failure Rate
**Symptoms:**
- Alert: `HighLockFailureRate` (> 10%)
- Users report "ระบบกำลังยุ่ง" errors
**Action Steps:**
1. **Check concurrent load:**
```bash
# Check current request rate
curl http://prometheus:9090/api/v1/query?query=rate(docnum_generation_duration_ms_count[1m])
```
2. **Check database connections:**
```sql
SHOW PROCESSLIST;
-- Look for waiting/locked queries
```
3. **Check Redis memory:**
```bash
docker exec lcbp3-redis redis-cli INFO memory
```
4. **Scale up if needed:**
```bash
# Increase backend replicas
docker-compose up -d --scale backend=5
```
5. **Check for deadlocks:**
```sql
SHOW ENGINE INNODB STATUS;
-- Look for LATEST DETECTED DEADLOCK section
```
### 4.3. Scenario: Slow Performance
**Symptoms:**
- Alert: `SlowDocumentNumberGeneration`
- P95 > 2 seconds
**Action Steps:**
1. **Check database query performance:**
```sql
SELECT * FROM document_number_counters USE INDEX (idx_counter_lookup)
WHERE project_id = 2 AND correspondence_type_id = 6 AND current_year = 2025;
-- Check execution plan
EXPLAIN SELECT ...;
```
2. **Check for missing indexes:**
```sql
SHOW INDEX FROM document_number_counters;
```
3. **Check Redis latency:**
```bash
docker exec lcbp3-redis redis-cli --latency
```
4. **Check network latency:**
```bash
ping mariadb-master
ping redis-master
```
5. **Review slow query log:**
```bash
docker exec lcbp3-mariadb-master cat /var/log/mysql/slow.log
```
### 4.4. Scenario: Version Conflicts
**Symptoms:**
- High retry count
- Users report "เลขที่เอกสารถูกเปลี่ยน" errors
**Action Steps:**
1. **Check concurrent requests to same counter:**
```sql
SELECT
project_id,
correspondence_type_id,
COUNT(*) as concurrent_requests
FROM document_number_audit
WHERE created_at > NOW() - INTERVAL 5 MINUTE
GROUP BY project_id, correspondence_type_id
HAVING COUNT(*) > 10
ORDER BY concurrent_requests DESC;
```
2. **Investigate specific counter:**
```sql
SELECT * FROM document_number_counters
WHERE project_id = X AND correspondence_type_id = Y;
-- Check audit trail
SELECT * FROM document_number_audit
WHERE counter_key LIKE '%project_id:X%'
ORDER BY created_at DESC
LIMIT 20;
```
3. **Check for application bugs:**
- Review error logs for stack traces
- Check if retry logic is working correctly
4. **Temporary mitigation:**
- Increase retry count in application config
- Consider manual counter adjustment (last resort)
## 5. Maintenance Procedures
### 5.1. Counter Reset (Manual)
**Requires:** SUPER_ADMIN role + 2-person approval
**Steps:**
1. **Request approval via API:**
```bash
POST /api/v1/document-numbering/configs/{configId}/reset-counter
{
"reason": "เหตุผลที่ชัดเจน อย่างน้อย 20 ตัวอักษร",
"approver_1": "user_id",
"approver_2": "user_id"
}
```
2. **Verify in audit log:**
```sql
SELECT * FROM document_number_config_history
WHERE config_id = X
ORDER BY changed_at DESC
LIMIT 1;
```
### 5.2. Template Update
**Best Practices:**
1. Always test template in staging first
2. Preview generated numbers before applying
3. Document reason for change
4. Template changes do NOT affect existing documents
**API Call:**
```bash
PUT /api/v1/document-numbering/configs/{configId}
{
"template": "{ORIGINATOR}-{RECIPIENT}-{SEQ:4}-{YEAR:B.E.}",
"change_reason": "เหตุผลในการเปลี่ยนแปลง"
}
```
### 5.3. Database Maintenance
**Weekly Tasks:**
- Check slow query log
- Optimize tables if needed:
```sql
OPTIMIZE TABLE document_number_counters;
OPTIMIZE TABLE document_number_audit;
```
**Monthly Tasks:**
- Review and archive old audit logs (> 2 years)
- Check index usage:
```sql
SELECT * FROM sys.schema_unused_indexes
WHERE object_schema = 'lcbp3_db';
```
## 6. Backup & Recovery
### 6.1. Backup Strategy
**Database:**
- Full backup: Daily at 02:00 AM
- Incremental backup: Every 4 hours
- Retention: 30 days
**Redis:**
- AOF (Append-Only File) enabled
- Snapshot every 1 hour
- Retention: 7 days
### 6.2. Recovery Procedures
See: [Backup & Recovery Guide](file:///e:/np-dms/lcbp3/specs/04-operations/backup-recovery.md)
## References
- [Requirements](file:///e:/np-dms/lcbp3/specs/01-requirements/03.11-document-numbering.md)
- [Implementation Guide](file:///e:/np-dms/lcbp3/specs/03-implementation/document-numbering.md)
- [Monitoring & Alerting](file:///e:/np-dms/lcbp3/specs/04-operations/monitoring-alerting.md)
- [Incident Response](file:///e:/np-dms/lcbp3/specs/04-operations/incident-response.md)
+3 -3
View File
@@ -1,8 +1,8 @@
# Environment Setup & Configuration
**Project:** LCBP3-DMS
**Version:** 1.5.0
**Last Updated:** 2025-12-01
**Version:** 1.5.1
**Last Updated:** 2025-12-02
---
@@ -458,6 +458,6 @@ docker exec lcbp3-backend env | grep NODE_ENV
---
**Version:** 1.5.0
**Version:** 1.5.1
**Last Review:** 2025-12-01
**Next Review:** 2026-03-01
+3 -3
View File
@@ -1,8 +1,8 @@
# Incident Response Procedures
**Project:** LCBP3-DMS
**Version:** 1.5.0
**Last Updated:** 2025-12-01
**Version:** 1.5.1
**Last Updated:** 2025-12-02
---
@@ -478,6 +478,6 @@ Database connection pool was exhausted due to slow queries not releasing connect
---
**Version:** 1.5.0
**Version:** 1.5.1
**Last Review:** 2025-12-01
**Next Review:** 2026-03-01
@@ -1,8 +1,8 @@
# Maintenance Procedures
**Project:** LCBP3-DMS
**Version:** 1.5.0
**Last Updated:** 2025-12-01
**Version:** 1.5.1
**Last Updated:** 2025-12-02
---
@@ -496,6 +496,6 @@ echo "Security maintenance completed: $(date)"
---
**Version:** 1.5.0
**Version:** 1.5.1
**Last Review:** 2025-12-01
**Next Review:** 2026-03-01
+3 -3
View File
@@ -1,8 +1,8 @@
# Monitoring & Alerting
**Project:** LCBP3-DMS
**Version:** 1.5.0
**Last Updated:** 2025-12-01
**Version:** 1.5.1
**Last Updated:** 2025-12-02
---
@@ -438,6 +438,6 @@ ab -n 1000 -c 10 \
---
**Version:** 1.5.0
**Version:** 1.5.1
**Last Review:** 2025-12-01
**Next Review:** 2026-03-01
View File
+3 -3
View File
@@ -1,8 +1,8 @@
# Security Operations
**Project:** LCBP3-DMS
**Version:** 1.5.0
**Last Updated:** 2025-12-01
**Version:** 1.5.1
**Last Updated:** 2025-12-02
---
@@ -439,6 +439,6 @@ echo "Account compromise response completed for User ID: $USER_ID"
---
**Version:** 1.5.0
**Version:** 1.5.1
**Last Review:** 2025-12-01
**Next Review:** 2026-03-01
@@ -197,6 +197,7 @@ CREATE TABLE document_number_audit (
#### 1. Correspondence (หนังสือราชการ)
**Letter Type (TYPE = 03):**
```
Template: {ORG}-{ORG}-{TYPE}-{SEQ:4}-{YEAR:B.E.}
Example: คคง.-สคฉ.3-0985-2568
@@ -204,6 +205,7 @@ Counter Key: project_id + doc_type_id + sub_type_id + year
```
**Other Correspondence:**
```
Template: {ORG}-{ORG}-{TYPE}-{SEQ:4}-{YEAR:B.E.}
Example: คคง.-สคฉ.3-STR-0001-2568
@@ -213,6 +215,7 @@ Counter Key: project_id + doc_type_id + sub_type_id + year
#### 2. Transmittal
**To Owner (Special Format):**
```
Template: {ORG}-{ORG}-{TYPE}-{SUB_TYPE}-{SEQ:4}-{YEAR:B.E.}
Example: คคง.-สคฉ.3-03-21-0117-2568
@@ -221,6 +224,7 @@ Note: recipient_type แยก counter จาก To Contractor
```
**To Contractor/Others:**
```
Template: {ORG}-{ORG}-{TYPE}-{SEQ:4}-{YEAR:B.E.}
Example: ผรม.2-คคง.-0117-2568
@@ -228,6 +232,7 @@ Counter Key: project_id + doc_type_id + recipient_type('CONTRACTOR') + year
```
**Alternative Project-based:**
```
Template: {PROJECT}-{ORG}-{TYPE}-{DISCIPLINE}-{SEQ:4}-{REV}
Example: LCBP3-TR-STR-0001-A
@@ -586,6 +591,7 @@ sequenceDiagram
**Trigger:** Redis connection error, Redis down
**Fallback:**
- ใช้ Database-only locking (`SELECT ... FOR UPDATE`)
- Log warning และแจ้ง ops team
- ระบบยังใช้งานได้แต่ performance ลดลง (slower)
@@ -595,6 +601,7 @@ sequenceDiagram
**Trigger:** หลาย requests แย่งชิง lock พร้อมกัน
**Retry Logic:**
- Retry 5 ครั้งด้วย exponential backoff: 1s, 2s, 4s, 8s, 16s (รวม ~31 วินาที)
- หลัง 5 ครั้ง: Return HTTP 503 "Service Temporarily Unavailable"
- Frontend: แสดง "ระบบกำลังยุ่ง กรุณาลองใหม่ภายหลัง"
@@ -604,6 +611,7 @@ sequenceDiagram
**Trigger:** Optimistic lock version mismatch
**Retry Logic:**
- Retry 2 ครั้ง (reload counter + retry transaction)
- หลัง 2 ครั้ง: Return HTTP 409 Conflict
- Frontend: แสดง "เลขที่เอกสารถูกเปลี่ยน กรุณาลองใหม่"
@@ -613,6 +621,7 @@ sequenceDiagram
**Trigger:** Database connection timeout, connection pool exhausted
**Retry Logic:**
- Retry 3 ครั้งด้วย exponential backoff: 1s, 2s, 4s
- หลัง 3 ครั้ง: Return HTTP 500 "Internal Server Error"
- Frontend: แสดง "เกิดข้อผิดพลาดในระบบ กรุณาติดต่อผู้ดูแลระบบ"
+8 -4
View File
@@ -1,6 +1,7 @@
# Architecture Decision Records (ADRs)
**Last Updated:** 2025-11-30
**Version:** 1.5.1
**Last Updated:** 2025-12-02
**Project:** LCBP3-DMS (Laem Chabang Port Phase 3 - Document Management System)
---
@@ -81,7 +82,10 @@ Architecture Decision Records (ADRs) เป็นเอกสารที่บ
### 2. Data Integrity & Concurrency
- **ADR-002:** Document Numbering - Double-lock เพื่อป้องกัน Race Condition
- **ADR-002:** Document Numbering - Double-lock (Redis Redlock + DB Optimistic) เพื่อป้องกัน Race Condition
- 📋 [Requirements](../01-requirements/03.11-document-numbering.md)
- 📘 [Implementation Guide](../03-implementation/document-numbering.md)
- 📗 [Operations Guide](../04-operations/document-numbering-operations.md)
- **ADR-003:** File Storage - Two-phase เพื่อ Transaction safety
- **ADR-009:** Database Migration - TypeORM Migrations พร้อม Blue-Green Deployment
@@ -352,5 +356,5 @@ graph TB
---
**Version:** 1.5.0
**Last Review:** 2025-11-30
**Version:** 1.5.1
**Last Review:** 2025-12-02
+14 -7
View File
@@ -1,8 +1,8 @@
# Development Tasks
**Project:** LCBP3-DMS (Laem Chabang Port Phase 3 - Document Management System)
**Version:** 1.5.0
**Last Updated:** 2025-12-01
**Version:** 1.5.1
**Last Updated:** 2025-12-02
---
@@ -280,10 +280,17 @@ graph TB
- **Type:** Core Service
- **Key Deliverables:**
- Double-lock mechanism (Redis + DB)
- Template-based generator
- Concurrent-safe implementation
- Double-lock mechanism (Redis Redlock + DB Optimistic Lock)
- Template-based generator (10 token types)
- Concurrent-safe implementation (100+ concurrent requests)
- Comprehensive error handling (4 scenarios)
- Monitoring & alerting (Prometheus + Grafana)
- **Documentation:**
- 📋 [Requirements](../01-requirements/03.11-document-numbering.md)
- 📘 [Implementation Guide](../03-implementation/document-numbering.md)
- 📗 [Operations Guide](../04-operations/document-numbering-operations.md)
- **Related ADR:** [ADR-002](../05-decisions/ADR-002-document-numbering-strategy.md)
- **Task Details:** [TASK-BE-004](./TASK-BE-004-document-numbering.md)
### TASK-BE-006: Workflow Engine
@@ -619,5 +626,5 @@ Add these features when:
---
**Version:** 1.5.0
**Last Updated:** 2025-11-30
**Version:** 1.5.1
**Last Updated:** 2025-12-02
@@ -10,21 +10,26 @@
## 📋 Overview
สร้าง DocumentNumberingService ที่ใช้ Double-Lock mechanism (Redis + DB Optimistic Lock) สำหรับการสร้างเลขที่เอกสารอัตโนมัติ พร้อม comprehensive error handling, monitoring, และ audit logging
สร้าง DocumentNumberingService ที่ใช้ Double-Lock mechanism (Redis + DB Optimistic Lock) สำหรับการสร้างเลขที่เอกสารอัตโนมัติ ตาม requirements ใน [03.11-document-numbering.md](file:///e:/np-dms/lcbp3/specs/01-requirements/03.11-document-numbering.md)
### เอกสารอ้างอิง
- **Requirements**: [03.11-document-numbering.md](file:///e:/np-dms/lcbp3/specs/01-requirements/03.11-document-numbering.md)
- **Implementation Guide**: [document-numbering.md](file:///e:/np-dms/lcbp3/specs/03-implementation/document-numbering.md)
- **Operations Guide**: [document-numbering-operations.md](file:///e:/np-dms/lcbp3/specs/04-operations/document-numbering-operations.md)
---
## 🎯 Objectives
- ✅ Template-Based Number Generation (รองรับ 9 token types)
- ✅ Double-Lock Protection (Redis + DB Optimistic Lock)
- ✅ Template-Based Number Generation (รองรับ 10 token types)
- ✅ Double-Lock Protection (Redis Redlock + DB Optimistic Lock)
- ✅ Concurrent-Safe (No duplicate numbers, tested with 100+ concurrent requests)
- ✅ Support 4 Document Types (Correspondence, RFA, Transmittal, Drawing)
- ✅ Year-Based Reset (พ.ศ. และ ค.ศ.)
- ✅ Transmittal Special Logic (To Owner vs To Contractor)
- ✅ Support All Document Types (LETTER, RFA, TRANSMITTAL, RFI, MEMO, etc.)
- ✅ Year-Based Auto Reset (ปี ค.ศ.)
- ✅ 4 Error Scenarios with Fallback Strategies
- ✅ Comprehensive Audit Logging
- ✅ Monitoring & Alerting
- ✅ Monitoring & Alerting (Prometheus + Grafana)
- ✅ Rate Limiting & Security
---
@@ -34,42 +39,44 @@
### 1. Number Generation
- ✅ Generate unique sequential numbers
- ✅ Support all 9 token types: `{PROJECT}`, `{ORG}`, `{TYPE}`, `{SUB_TYPE}`, `{DISCIPLINE}`, `{CATEGORY}`, `{SEQ:n}`, `{YEAR:B.E.}`, `{YEAR:A.D}`, `{REV}`
- ✅ Support all 10 token types: `{PROJECT}`, `{ORIGINATOR}`, `{RECIPIENT}`, `{CORR_TYPE}`, `{SUB_TYPE}`, `{RFA_TYPE}`, `{DISCIPLINE}`, `{SEQ:n}`, `{YEAR:B.E.}`, `{YEAR:A.D.}`, `{REV}`
- ✅ No duplicates even with 100+ concurrent requests
- ✅ Performance: <500ms (normal), <2s (p95), <5s (p99)
### 2. Lock Mechanism
- ✅ Redis distributed lock (TTL: 5 seconds)
- ✅ DB optimistic lock with version column
- ✅ Redis Redlock distributed lock (TTL: 5 seconds)
- ✅ DB optimistic lock with `version` column
- ✅ Fallback to DB pessimistic lock when Redis unavailable
- ✅ Retry with exponential backoff (5 retries max for lock, 2 for version conflict, 3 for DB errors)
### 3. Document Types Support
- ✅ Correspondence (Letter Type และ Other Types)
- ✅ RFA with Discipline
- ✅ Transmittal (To Owner vs To Contractor with different formats)
- ✅ Drawing with Category
- ✅ LETTER / RFI / MEMO / EMAIL / MOM / INSTRUCTION / NOTICE / OTHER
- Counter Key: `(project_id, originator_org_id, recipient_org_id, corr_type_id, 0, 0, 0, year)`
- ✅ TRANSMITTAL
- Counter Key: `(project_id, originator_org_id, recipient_org_id, corr_type_id, sub_type_id, 0, 0, year)`
- ✅ RFA
- Counter Key: `(project_id, originator_org_id, NULL, corr_type_id, 0, rfa_type_id, discipline_id, year)`
### 4. Error Handling
- ✅ Scenario 1: Redis Unavailable → Fallback to DB lock
- ✅ Scenario 1: Redis Unavailable → Fallback to DB pessimistic lock
- ✅ Scenario 2: Lock Timeout → Retry 5x with exponential backoff
- ✅ Scenario 3: Version Conflict → Retry 2x
- ✅ Scenario 4: DB Connection Error → Retry 3x
- ✅ Scenario 3: Version Conflict → Retry 2x immediately
- ✅ Scenario 4: DB Connection Error → Retry 3x with exponential backoff
### 5. Audit & Monitoring
- ✅ Audit log for every generated number
- ✅ Track lock wait times, retry counts, errors
- ✅ Metrics collection for monitoring dashboard
- ✅ Audit log for every generated number (with performance metrics)
- ✅ Error logging with classification (LOCK_TIMEOUT, VERSION_CONFLICT, etc.)
- ✅ Prometheus metrics collection
- ✅ Alerting on failures >5%
### 6. Security
- ✅ Rate limiting: 10 req/min per user, 50 req/min per IP
- ✅ Authorization checks
- ✅ Rate limiting: 10 req/min per user, 50 req/min per IP (using @nestjs/throttler)
- ✅ Authorization checks (JWT + Roles)
- ✅ IP address logging
---
@@ -136,37 +143,56 @@ export class DocumentNumberConfig {
import { Entity, PrimaryColumn, Column, UpdateDateColumn, VersionColumn } from 'typeorm';
/**
* ตาราง document_number_counters
* Composite PK: (project_id, originator_organization_id, recipient_organization_id,
* correspondence_type_id, sub_type_id, rfa_type_id, discipline_id, current_year)
*
* References: specs/01-requirements/03.11-document-numbering.md#counter-key-components
*/
@Entity('document_number_counters')
export class DocumentNumberCounter {
@PrimaryColumn()
project_id: number;
@PrimaryColumn({ name: 'project_id' })
projectId: number;
@PrimaryColumn()
doc_type_id: number;
@PrimaryColumn({ name: 'originator_organization_id' })
originatorOrganizationId: number;
@PrimaryColumn({ default: 0 })
sub_type_id: number; // สำหรับ Correspondence types
@PrimaryColumn({ name: 'recipient_organization_id', nullable: true })
recipientOrganizationId: number | null; // NULL for RFA
@PrimaryColumn({ default: 0 })
discipline_id: number; // สำหรับ RFA, Drawing
@PrimaryColumn({ name: 'correspondence_type_id' })
correspondenceTypeId: number;
@PrimaryColumn({ type: 'varchar', length: 20, nullable: true, default: null })
recipient_type: string; // สำหรับ Transmittal: 'OWNER', 'CONTRACTOR', 'CONSULTANT', 'OTHER'
@PrimaryColumn({ name: 'sub_type_id', default: 0 })
subTypeId: number; // for TRANSMITTAL only
@PrimaryColumn()
year: number; // ปี พ.ศ. หรือ ค.ศ.
@PrimaryColumn({ name: 'rfa_type_id', default: 0 })
rfaTypeId: number; // for RFA only
@Column({ default: 0 })
last_number: number;
@PrimaryColumn({ name: 'discipline_id', default: 0 })
disciplineId: number; // for RFA only
@VersionColumn({ comment: 'Optimistic Lock version' })
@PrimaryColumn({ name: 'current_year' })
currentYear: number; // ปี ค.ศ.
@Column({ name: 'last_number', default: 0 })
lastNumber: number;
@VersionColumn({ name: 'version', comment: 'Optimistic Lock version' })
version: number;
@UpdateDateColumn()
updated_at: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
}
```
> **⚠️ หมายเหตุ Schema:**
>
> - Primary Key ใช้ `COALESCE(recipient_organization_id, 0)` ในการสร้าง constraint (ดู migration file)
> - `sub_type_id`, `rfa_type_id`, `discipline_id` ใช้ `0` แทน NULL
> - Counter reset อัตโนมัติทุกปี (แยก counter ตาม `current_year`)
#### 1.3 Document Number Audit Entity
```typescript
@@ -1179,6 +1205,7 @@ ensure:
## 📦 Deliverables
### Core Implementation
- [x] DocumentNumberingService with all 4 error scenarios
- [x] DocumentNumberCounter Entity (with sub_type_id, recipient_type)
- [x] DocumentNumberConfig Entity
@@ -1188,12 +1215,14 @@ ensure:
- [x] Retry Logic with Exponential Backoff
### API & Security
- [x] DocumentNumberingController with 4 endpoints
- [x] Rate Limiting Guard (10/min per user, 50/min per IP)
- [x] Authorization Guards
- [x] API Documentation (Swagger)
### Testing
- [x] Unit Tests (targeting 90%+ coverage)
- [x] Concurrent Tests (100+ simultaneous requests)
- [x] Error Scenario Tests (all 4 scenarios)
@@ -1202,6 +1231,7 @@ ensure:
- [x] Load Tests (Artillery config for 50-100 req/sec)
### Monitoring & Documentation
- [x] Metrics Collection Integration
- [x] Audit Logging
- [x] Implementation Documentation
@@ -1225,11 +1255,13 @@ ensure:
## 📌 Implementation Notes
### Performance Targets
- **Normal Operation:** <500ms (no conflicts, Redis available)
- **95th Percentile:** <2 seconds (including retries)
- **99th Percentile:** <5 seconds (worst case scenarios)
### Lock Configuration
- **Redis Lock TTL:** 5 seconds (auto-release)
- **Lock Acquisition Timeout:** 10 seconds
- **Max Retries (Lock):** 5 times with exponential backoff (1s, 2s, 4s, 8s, 16s)
@@ -1237,18 +1269,22 @@ ensure:
- **Max Retries (DB Error):** 3 times with exponential backoff (1s, 2s, 4s)
### Rate Limiting
- **Per User:** 10 requests/minute
- **Per IP:** 50 requests/minute
- **Global:** 5000 requests/minute
### Format Templates
Stored in database (`document_number_configs` table), configurable per:
- Project
- Document Type
- Sub Type (optional, use 0 for fallback)
- Discipline (optional, use 0 for fallback)
### Counter Reset
- Automatic reset per year (based on `{YEAR:B.E.}` or `{YEAR:A.D.}` in template)
- Manual reset available (Super Admin only, with audit log)