260322:1648 Correct Coresspondence / Doing RFA / Correct CI
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# 3.11 Document Numbering Management (การจัดการเลขที่เอกสาร)
|
||||
|
||||
---
|
||||
|
||||
title: 'Functional Requirements: Document Numbering Management'
|
||||
version: 1.6.0
|
||||
status: draft
|
||||
@@ -33,6 +34,7 @@ related:
|
||||
|
||||
การนับเลขจะแยกตาม **Counter Key** ที่ประกอบด้วยหลายส่วน ขึ้นกับประเภทเอกสาร
|
||||
**Scopes**:
|
||||
|
||||
1. **Global**: Sequence ระดับระบบทั้งหมด
|
||||
2. **Organization**: Sequence แยกตามองค์กรผู้ส่ง
|
||||
3. **Project**: Sequence แยกตามโครงการ
|
||||
@@ -41,16 +43,16 @@ related:
|
||||
|
||||
### Counter Key Components
|
||||
|
||||
| Component | Required? | Description | Database Source | Default if NULL |
|
||||
| ---------------------------- | ---------------- | ------------------- | --------------------------------------------------------- | --------------- |
|
||||
| `project_id` | ✅ Yes | ID โครงการ | Derived from user context or organization | - |
|
||||
| `originator_organization_id` | ✅ Yes | ID องค์กรผู้ส่ง | `correspondences.originator_id` | - |
|
||||
| Component | Required? | Description | Database Source | Default if NULL |
|
||||
| ---------------------------- | ---------------- | ------------------------ | --------------------------------------------------------- | --------------- |
|
||||
| `project_id` | ✅ Yes | ID โครงการ | Derived from user context or organization | - |
|
||||
| `originator_organization_id` | ✅ Yes | ID องค์กรผู้ส่ง | `correspondences.originator_id` | - |
|
||||
| `recipient_organization_id` | Depends on type | ID องค์กรผู้รับหลัก (TO) | `correspondence_recipients` where `recipient_type = 'TO'` | NULL for RFA |
|
||||
| `correspondence_type_id` | ✅ Yes | ID ประเภทเอกสาร | `correspondence_types.id` | 0 |
|
||||
| `sub_type_id` | TRANSMITTAL only | ID ประเภทย่อย | `correspondence_sub_types.id` | 0 |
|
||||
| `rfa_type_id` | RFA only | ID ประเภท RFA | `rfa_types.id` | 0 |
|
||||
| `discipline_id` | RFA only | ID สาขางาน | `disciplines.id` | 0 |
|
||||
| `current_year` | ✅ Yes | ปี ค.ศ. | System year (ปัจจุบัน) | - |
|
||||
| `correspondence_type_id` | ✅ Yes | ID ประเภทเอกสาร | `correspondence_types.id` | 0 |
|
||||
| `sub_type_id` | TRANSMITTAL only | ID ประเภทย่อย | `correspondence_sub_types.id` | 0 |
|
||||
| `rfa_type_id` | RFA only | ID ประเภท RFA | `rfa_types.id` | 0 |
|
||||
| `discipline_id` | RFA only | ID สาขางาน | `disciplines.id` | 0 |
|
||||
| `current_year` | ✅ Yes | ปี ค.ศ. | System year (ปัจจุบัน) | - |
|
||||
|
||||
### Counter Key แยกตามประเภทเอกสาร (correspondence_type_id)
|
||||
|
||||
@@ -61,8 +63,9 @@ related:
|
||||
```
|
||||
|
||||
**หมายเหตุ**:
|
||||
- ไม่ใช้ `discipline_id`, `sub_type_id`, `rfa_type_id`ทุกประเภทที่ไม่ได้ระบุเฉพาะจะใช้ Template นี้
|
||||
- ถ้ามีการเพิ่ม - correspondence type ใหม่ใน `correspondence_types` table จะใช้ Template นี้โดยอัตโนมัติ
|
||||
|
||||
- ไม่ใช้ `discipline_id`, `sub_type_id`, `rfa_type_id`ทุกประเภทที่ไม่ได้ระบุเฉพาะจะใช้ Template นี้
|
||||
- ถ้ามีการเพิ่ม - correspondence type ใหม่ใน `correspondence_types` table จะใช้ Template นี้โดยอัตโนมัติ
|
||||
|
||||
#### **TRANSMITTAL**:
|
||||
|
||||
@@ -71,7 +74,7 @@ related:
|
||||
correspondence_type_id, sub_type_id, 0, 0, current_year)
|
||||
```
|
||||
|
||||
*หมายเหตุ*: ใช้ `sub_type_id` เพิ่มเติม
|
||||
_หมายเหตุ_: ใช้ `sub_type_id` เพิ่มเติม
|
||||
|
||||
#### **RFA**:
|
||||
|
||||
@@ -80,7 +83,7 @@ related:
|
||||
correspondence_type_id, 0, rfa_type_id, discipline_id, current_year)
|
||||
```
|
||||
|
||||
*หมายเหตุ*: RFA ไม่ใช้ `recipient_organization_id` เพราะเป็นเอกสารโครงการ (CONTRACTOR → CONSULTANT → OWNER)
|
||||
_หมายเหตุ_: RFA ไม่ใช้ `recipient_organization_id` เพราะเป็นเอกสารโครงการ (CONTRACTOR → CONSULTANT → OWNER)
|
||||
|
||||
##### วิธีการหา project_id
|
||||
|
||||
@@ -110,6 +113,7 @@ related:
|
||||
## 3.11.3. Format Templates by Correspondence Type
|
||||
|
||||
> **📝 หมายเหตุสำคัญ**
|
||||
>
|
||||
> - Templates ด้านล่างเป็น **ตัวอย่าง** สำหรับประเภทเอกสารหลัก
|
||||
> - ระบบรองรับ **ทุกประเภทเอกสาร** ที่อยู่ใน `correspondence_types` table
|
||||
> - หากมีการเพิ่มประเภทใหม่ในอนาคต สามารถใช้งานได้โดยอัตโนมัติ
|
||||
@@ -212,19 +216,19 @@ Drawing Numbering ยังไม่ได้กำหนด Template เนื
|
||||
|
||||
## 3.11.4. Supported Token Types
|
||||
|
||||
| Token | Description | Example | Database Source |
|
||||
| -------------- | ---------------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------- |
|
||||
| `{PROJECT}` | รหัสโครงการ | `LCBP3`, `LCBP3-C2` | `projects.project_code` |
|
||||
| `{ORIGINATOR}` | รหัสองค์กรผู้ส่ง | `คคง.`, `ผรม.1` | `organizations.organization_code` via `correspondences.originator_id` |
|
||||
| `{RECIPIENT}` | รหัสองค์กรผู้รับหลัก (TO) | `สคฉ.3`, `กทท.` | `organizations.organization_code` via `correspondence_recipients` where `recipient_type = 'TO'` |
|
||||
| `{CORR_TYPE}` | รหัสประเภทเอกสาร | `RFA`, `TRANSMITTAL`, `LETTER` | `correspondence_types.type_code` |
|
||||
| `{SUB_TYPE}` | หมายเลขประเภทย่อย | `11`, `12`, `21` | `correspondence_sub_types.sub_type_number` |
|
||||
| `{RFA_TYPE}` | รหัสประเภท RFA | `SDW`, `RPT`, `MAT` | `rfa_types.type_code` |
|
||||
| Token | Description | Example | Database Source |
|
||||
| -------------- | ------------------------------ | ------------------------------ | ----------------------------------------------------------------------------------------------- |
|
||||
| `{PROJECT}` | รหัสโครงการ | `LCBP3`, `LCBP3-C2` | `projects.project_code` |
|
||||
| `{ORIGINATOR}` | รหัสองค์กรผู้ส่ง | `คคง.`, `ผรม.1` | `organizations.organization_code` via `correspondences.originator_id` |
|
||||
| `{RECIPIENT}` | รหัสองค์กรผู้รับหลัก (TO) | `สคฉ.3`, `กทท.` | `organizations.organization_code` via `correspondence_recipients` where `recipient_type = 'TO'` |
|
||||
| `{CORR_TYPE}` | รหัสประเภทเอกสาร | `RFA`, `TRANSMITTAL`, `LETTER` | `correspondence_types.type_code` |
|
||||
| `{SUB_TYPE}` | หมายเลขประเภทย่อย | `11`, `12`, `21` | `correspondence_sub_types.sub_type_number` |
|
||||
| `{RFA_TYPE}` | รหัสประเภท RFA | `SDW`, `RPT`, `MAT` | `rfa_types.type_code` |
|
||||
| `{DISCIPLINE}` | รหัสสาขาวิชา | `STR`, `TER`, `GEO` | `disciplines.discipline_code` |
|
||||
| `{SEQ:n}` | Running number (n = จำนวนหลัก) | `0001`, `0029`, `0985` | Based on `document_number_counters.last_number + 1` |
|
||||
| `{YEAR:B.E.}` | ปี พ.ศ. | `2568` | `document_number_counters.current_year + 543` |
|
||||
| `{YEAR:A.D.}` | ปี ค.ศ. | `2025` | `document_number_counters.current_year` |
|
||||
| `{REV}` | Revision Code | `A`, `B`, `AA` | `correspondence_revisions.revision_label` |
|
||||
| `{YEAR:B.E.}` | ปี พ.ศ. | `2568` | `document_number_counters.current_year + 543` |
|
||||
| `{YEAR:A.D.}` | ปี ค.ศ. | `2025` | `document_number_counters.current_year` |
|
||||
| `{REV}` | Revision Code | `A`, `B`, `AA` | `correspondence_revisions.revision_label` |
|
||||
|
||||
### Token Usage Notes
|
||||
|
||||
@@ -343,11 +347,13 @@ Drawing Numbering ยังไม่ได้กำหนด Template เนื
|
||||
ระบบต้องรองรับการกำหนดเลขที่เอกสารด้วยตนเอง (manual override)
|
||||
|
||||
**Use Cases**:
|
||||
|
||||
- 1. Import เอกสารเก่าจากระบบเดิม
|
||||
- 2. External documents จาก client/consultant
|
||||
- 3. Correction หลังพบความผิดพลาด
|
||||
|
||||
**Implementation Details:**
|
||||
|
||||
- ตรวจสอบ duplicate ก่อน save
|
||||
- Validate format ตามรูปแบบที่กำหนด
|
||||
- Auto-update sequence counter ถ้าเลขที่สูงกว่า current
|
||||
@@ -360,11 +366,13 @@ Drawing Numbering ยังไม่ได้กำหนด Template เนื
|
||||
ระบบต้องรองรับการกำหนดเลขที่เอกสารอัตโนมัติ (auto override)
|
||||
|
||||
**Use Cases**:
|
||||
|
||||
- 1. Import เอกสารเก่าจากระบบเดิม
|
||||
- 2. External documents จาก client/consultant
|
||||
- 3. Correction หลังพบความผิดพลาด
|
||||
|
||||
**Implementation Details:**
|
||||
|
||||
- ตรวจสอบ duplicate ก่อน save
|
||||
- Validate format ตามรูปแบบที่กำหนด
|
||||
- Auto-update sequence counter ถ้าเลขที่สูงกว่า current
|
||||
@@ -372,6 +380,7 @@ Drawing Numbering ยังไม่ได้กำหนด Template เนื
|
||||
- ต้องมีสิทธิ์ Admin ขึ้นไปเท่านั้น
|
||||
|
||||
###
|
||||
|
||||
## 3.11.9 Audit Trail Requirements
|
||||
|
||||
### 3.11.9.1. Audit Logging
|
||||
@@ -409,18 +418,18 @@ Drawing Numbering ยังไม่ได้กำหนด Template เนื
|
||||
|
||||
**SLA Targets:**
|
||||
|
||||
| Metric | Target | Notes |
|
||||
| ---------------- | -------- | ------------------------ |
|
||||
| Metric | Target | Notes |
|
||||
| ---------------- | ---------- | ---------------------------- |
|
||||
| 95th percentile | ≤ 2 วินาที | ตั้งแต่ request ถึง response |
|
||||
| 99th percentile | ≤ 5 วินาที | รวม retry attempts |
|
||||
| Normal operation | ≤ 500ms | ไม่มี retry |
|
||||
| 99th percentile | ≤ 5 วินาที | รวม retry attempts |
|
||||
| Normal operation | ≤ 500ms | ไม่มี retry |
|
||||
|
||||
### 3.11.10.2. Throughput
|
||||
|
||||
**Capacity Targets:**
|
||||
|
||||
| Load Level | Target | Notes |
|
||||
| ----------- | ----------- | --------- |
|
||||
| Load Level | Target | Notes |
|
||||
| ----------- | ----------- | ----------- |
|
||||
| Normal load | ≥ 50 req/s | ใช้งานปกติ |
|
||||
| Peak load | ≥ 100 req/s | ช่วงเร่งงาน |
|
||||
|
||||
@@ -456,8 +465,8 @@ Drawing Numbering ยังไม่ได้กำหนด Template เนื
|
||||
|
||||
ระบบ**ต้อง**alert สำหรับ conditions ต่อไปนี้:
|
||||
|
||||
| Severity | Condition | Action |
|
||||
| ---------- | ---------------------------- | ----------------- |
|
||||
| Severity | Condition | Action |
|
||||
| ----------- | ---------------------------- | ----------------- |
|
||||
| 🔴 Critical | Redis unavailable > 1 minute | PagerDuty + Slack |
|
||||
| 🔴 Critical | Lock failures > 10% in 5 min | PagerDuty + Slack |
|
||||
| 🟡 Warning | Lock failures > 5% in 5 min | Slack |
|
||||
@@ -477,7 +486,6 @@ Drawing Numbering ยังไม่ได้กำหนด Template เนื
|
||||
|
||||
**Operations Details:** ดู [Operations Guide - Section 3](file:///e:/np-dms/lcbp3/specs/04-operations/document-numbering-operations.md#3-monitoring--metrics)
|
||||
|
||||
|
||||
## 3.11.12 API Reference
|
||||
|
||||
เอกสารนี้อ้างอิงถึง API endpoints ต่อไปนี้:
|
||||
@@ -567,11 +575,13 @@ Reset counter (Super Admin only, requires approval)
|
||||
**Primary Table**: `document_number_counters`
|
||||
|
||||
**Required Columns:**
|
||||
|
||||
- Composite primary key: `(project_id, originator_organization_id, recipient_organization_id, correspondence_type_id, sub_type_id, rfa_type_id, discipline_id, current_year)`
|
||||
- `version` - สำหรับ optimistic locking
|
||||
- `last_number` - counter value (เริ่มจาก 0)
|
||||
|
||||
**Important Notes:**
|
||||
|
||||
- ใช้ `COALESCE(recipient_organization_id, 0)` ใน Primary Key เพื่อรองรับ NULL
|
||||
- Counter reset ทุกปี (เมื่อ `current_year` เปลี่ยน)
|
||||
- ต้องมี seed data สำหรับ `correspondence_types`, `rfa_types`, `disciplines` ก่อน
|
||||
@@ -583,6 +593,7 @@ Reset counter (Super Admin only, requires approval)
|
||||
**Primary Table**: `document_number_audit`
|
||||
|
||||
**Required Columns:**
|
||||
|
||||
- `document_id`, `generated_number`, `counter_key` (JSON)
|
||||
- `template_used`, `user_id`, `ip_address`
|
||||
- Performance metrics: `retry_count`, `lock_wait_ms`, `total_duration_ms`
|
||||
@@ -595,6 +606,7 @@ Reset counter (Super Admin only, requires approval)
|
||||
**Primary Table**: `document_number_errors`
|
||||
|
||||
**Required Columns:**
|
||||
|
||||
- `error_type` - ENUM classification
|
||||
- `error_message`, `stack_trace`, `context_data` (JSON)
|
||||
- `user_id`, `ip_address`, `created_at`, `resolved_at`
|
||||
@@ -610,6 +622,7 @@ Reset counter (Super Admin only, requires approval)
|
||||
### 3.11.15.2 Rate Limiting
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- Limit ต่อ user: **10 requests/minute** (prevent abuse)
|
||||
- Limit ต่อ IP: **50 requests/minute**
|
||||
|
||||
@@ -618,6 +631,7 @@ Reset counter (Super Admin only, requires approval)
|
||||
### 3.11.15.3 Audit & Compliance
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- บันทึกทุก API call ที่เกี่ยวข้องกับ document numbering
|
||||
- เก็บ audit log อย่างน้อย **7 ปี** (ตาม พ.ร.บ. ข้อมูลอิเล็กทรอนิกส์)
|
||||
- Audit log **ต้องไม่**สามารถแก้ไขได้ (immutable)
|
||||
@@ -631,7 +645,6 @@ Reset counter (Super Admin only, requires approval)
|
||||
- [API Design](file:///e:/np-dms/lcbp3/specs/02-architecture/api-design.md)
|
||||
- [Data Dictionary](file:///e:/np-dms/lcbp3/specs/04-data-dictionary/4_Data_Dictionary_V1_4_4.md)
|
||||
|
||||
|
||||
```
|
||||
lock:docnum:{project_id}:{org_id}:{recip_id}:{type_id}:{sub}:{rfa}:{disc}:{year}
|
||||
```
|
||||
@@ -660,9 +673,11 @@ export class DocumentNumberingLockService {
|
||||
}
|
||||
|
||||
private buildLockKey(key: CounterKey): string {
|
||||
return `lock:docnum:${key.projectId}:${key.originatorOrgId}:` +
|
||||
`${key.recipientOrgId ?? 0}:${key.correspondenceTypeId}:` +
|
||||
`${key.subTypeId}:${key.rfaTypeId}:${key.disciplineId}:${key.year}`;
|
||||
return (
|
||||
`lock:docnum:${key.projectId}:${key.originatorOrgId}:` +
|
||||
`${key.recipientOrgId ?? 0}:${key.correspondenceTypeId}:` +
|
||||
`${key.subTypeId}:${key.rfaTypeId}:${key.disciplineId}:${key.year}`
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -717,7 +732,7 @@ export class DocumentNumberCounter {
|
||||
// ใช้ TypeORM Transaction + Optimistic Lock
|
||||
await this.connection.transaction(async (manager) => {
|
||||
const counter = await manager.findOne(DocumentNumberCounter, {
|
||||
where: counterKey
|
||||
where: counterKey,
|
||||
});
|
||||
|
||||
counter.lastNumber += 1;
|
||||
@@ -951,8 +966,16 @@ import { Injectable } from '@nestjs/common';
|
||||
@Injectable()
|
||||
export class TemplateValidator {
|
||||
private readonly ALLOWED_TOKENS = [
|
||||
'PROJECT', 'ORIGINATOR', 'RECIPIENT', 'CORR_TYPE',
|
||||
'SUB_TYPE', 'RFA_TYPE', 'DISCIPLINE', 'SEQ', 'YEAR', 'REV'
|
||||
'PROJECT',
|
||||
'ORIGINATOR',
|
||||
'RECIPIENT',
|
||||
'CORR_TYPE',
|
||||
'SUB_TYPE',
|
||||
'RFA_TYPE',
|
||||
'DISCIPLINE',
|
||||
'SEQ',
|
||||
'YEAR',
|
||||
'REV',
|
||||
];
|
||||
|
||||
validate(template: string, correspondenceType: string): ValidationResult {
|
||||
@@ -968,13 +991,13 @@ export class TemplateValidator {
|
||||
|
||||
// กฎพิเศษสำหรับแต่ละประเภท
|
||||
if (correspondenceType === 'RFA') {
|
||||
if (!tokens.some(t => t.name === 'PROJECT')) {
|
||||
if (!tokens.some((t) => t.name === 'PROJECT')) {
|
||||
errors.push('RFA template ต้องมี {PROJECT}');
|
||||
}
|
||||
}
|
||||
|
||||
if (correspondenceType === 'TRANSMITTAL') {
|
||||
if (!tokens.some(t => t.name === 'SUB_TYPE')) {
|
||||
if (!tokens.some((t) => t.name === 'SUB_TYPE')) {
|
||||
errors.push('TRANSMITTAL template ต้องมี {SUB_TYPE}');
|
||||
}
|
||||
}
|
||||
@@ -1045,14 +1068,14 @@ export class ConfigHistoryService {
|
||||
templateBefore: oldTemplate,
|
||||
templateAfter: newTemplate,
|
||||
changedBy: userId,
|
||||
changeReason: reason
|
||||
changeReason: reason,
|
||||
});
|
||||
}
|
||||
|
||||
async rollback(configId: number, historyId: number): Promise<void> {
|
||||
const history = await this.historyRepo.findOne({ where: { id: historyId }});
|
||||
const history = await this.historyRepo.findOne({ where: { id: historyId } });
|
||||
await this.configService.update(configId, {
|
||||
template: history.templateBefore
|
||||
template: history.templateBefore,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1108,6 +1131,7 @@ async resetCounter(
|
||||
await this.counterService.reset(configId);
|
||||
}
|
||||
```
|
||||
|
||||
## 3.11.21 Audit Trail
|
||||
|
||||
### 3.11.21.1 การบันทึก Audit Log
|
||||
@@ -1161,7 +1185,7 @@ export class DocumentNumberAuditService {
|
||||
retryCount: data.retryCount ?? 0,
|
||||
lockWaitMs: data.lockWaitMs,
|
||||
totalDurationMs: data.totalDurationMs,
|
||||
fallbackUsed: data.fallbackUsed ?? 'NONE'
|
||||
fallbackUsed: data.fallbackUsed ?? 'NONE',
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1194,7 +1218,7 @@ export class DocumentNumberingService {
|
||||
retryCount,
|
||||
lockWaitMs,
|
||||
totalDurationMs: Date.now() - startTime,
|
||||
fallbackUsed
|
||||
fallbackUsed,
|
||||
});
|
||||
|
||||
return number;
|
||||
@@ -1249,7 +1273,7 @@ export class ErrorLogService {
|
||||
stackTrace: error.stack,
|
||||
contextData: JSON.stringify(context),
|
||||
userId: context.userId,
|
||||
ipAddress: context.ipAddress
|
||||
ipAddress: context.ipAddress,
|
||||
});
|
||||
|
||||
// Alert if critical
|
||||
@@ -1257,7 +1281,7 @@ export class ErrorLogService {
|
||||
await this.alertService.sendAlert({
|
||||
severity: 'CRITICAL',
|
||||
title: `Document Numbering Error: ${errorType}`,
|
||||
details: error.message
|
||||
details: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1271,11 +1295,13 @@ export class ErrorLogService {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3.11.22 Performance Requirements
|
||||
|
||||
### 3.11.22.1 Response Time
|
||||
|
||||
**Target Response Times**:
|
||||
|
||||
- **95th percentile**: ≤ 2 วินาที
|
||||
- **99th percentile**: ≤ 5 วินาที
|
||||
- **Normal operation** (ไม่มี retry): ≤ 500ms
|
||||
@@ -1327,7 +1353,7 @@ const generationDuration = new Histogram({
|
||||
name: 'docnum_generation_duration_seconds',
|
||||
help: 'Document number generation duration',
|
||||
labelNames: ['project', 'type', 'status'],
|
||||
buckets: [0.1, 0.5, 1, 2, 5, 10]
|
||||
buckets: [0.1, 0.5, 1, 2, 5, 10],
|
||||
});
|
||||
|
||||
// Usage
|
||||
@@ -1357,7 +1383,7 @@ services:
|
||||
backend:
|
||||
image: lcbp3-backend:latest
|
||||
deploy:
|
||||
replicas: 3 # 3 instances
|
||||
replicas: 3 # 3 instances
|
||||
resources:
|
||||
limits:
|
||||
cpus: '1.0'
|
||||
@@ -1371,7 +1397,7 @@ services:
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||
ports:
|
||||
- "80:80"
|
||||
- '80:80'
|
||||
```
|
||||
|
||||
```nginx
|
||||
@@ -1402,7 +1428,7 @@ import { ThrottlerGuard } from '@nestjs/throttler';
|
||||
@Controller('document-numbering')
|
||||
@UseGuards(ThrottlerGuard)
|
||||
export class DocumentNumberingController {
|
||||
@Throttle(10, 60) // 10 requests per 60 seconds per user
|
||||
@Throttle(10, 60) // 10 requests per 60 seconds per user
|
||||
@Post('generate')
|
||||
async generate(@Body() dto: GenerateNumberDto) {
|
||||
return await this.service.generate(dto);
|
||||
@@ -1466,17 +1492,18 @@ export class HealthController {
|
||||
return this.health.check([
|
||||
() => this.db.pingCheck('database'),
|
||||
() => this.redis.pingCheck('redis'),
|
||||
() => this.customHealthCheck()
|
||||
() => this.customHealthCheck(),
|
||||
]);
|
||||
}
|
||||
|
||||
private async customHealthCheck() {
|
||||
// ทดสอบ generate document number
|
||||
const canGenerate = await this.testGeneration();
|
||||
return { documentNumbering: { status: canGenerate ? 'up' : 'down' }};
|
||||
return { documentNumbering: { status: canGenerate ? 'up' : 'down' } };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3.11.23 Monitoring & Alerting
|
||||
|
||||
### 3.11.23.1 Metrics Collection
|
||||
@@ -1495,13 +1522,13 @@ export class DocumentNumberingMetrics {
|
||||
name: 'docnum_lock_acquisition_duration_ms',
|
||||
help: 'Lock acquisition time in milliseconds',
|
||||
labelNames: ['project', 'type'],
|
||||
buckets: [10, 50, 100, 200, 500, 1000, 2000, 5000]
|
||||
buckets: [10, 50, 100, 200, 500, 1000, 2000, 5000],
|
||||
});
|
||||
|
||||
private lockAcquisitionFailures = new Counter({
|
||||
name: 'docnum_lock_acquisition_failures_total',
|
||||
help: 'Total number of lock acquisition failures',
|
||||
labelNames: ['project', 'type', 'reason']
|
||||
labelNames: ['project', 'type', 'reason'],
|
||||
});
|
||||
|
||||
// Generation metrics
|
||||
@@ -1509,25 +1536,25 @@ export class DocumentNumberingMetrics {
|
||||
name: 'docnum_generation_duration_ms',
|
||||
help: 'Total document number generation time',
|
||||
labelNames: ['project', 'type', 'status'],
|
||||
buckets: [100, 200, 500, 1000, 2000, 5000]
|
||||
buckets: [100, 200, 500, 1000, 2000, 5000],
|
||||
});
|
||||
|
||||
private retryCount = new Histogram({
|
||||
name: 'docnum_retry_count',
|
||||
help: 'Number of retries per generation',
|
||||
labelNames: ['project', 'type'],
|
||||
buckets: [0, 1, 2, 3, 5, 10]
|
||||
buckets: [0, 1, 2, 3, 5, 10],
|
||||
});
|
||||
|
||||
// Connection health
|
||||
private redisConnectionStatus = new Gauge({
|
||||
name: 'docnum_redis_connection_status',
|
||||
help: 'Redis connection status (1=up, 0=down)'
|
||||
help: 'Redis connection status (1=up, 0=down)',
|
||||
});
|
||||
|
||||
private dbConnectionPoolUsage = new Gauge({
|
||||
name: 'docnum_db_connection_pool_usage',
|
||||
help: 'Database connection pool usage percentage'
|
||||
help: 'Database connection pool usage percentage',
|
||||
});
|
||||
}
|
||||
```
|
||||
@@ -1549,8 +1576,8 @@ groups:
|
||||
severity: critical
|
||||
component: document-numbering
|
||||
annotations:
|
||||
summary: "Redis is unavailable for document numbering"
|
||||
description: "System is falling back to DB-only locking"
|
||||
summary: 'Redis is unavailable for document numbering'
|
||||
description: 'System is falling back to DB-only locking'
|
||||
|
||||
# CRITICAL: High lock failure rate
|
||||
- alert: HighLockFailureRate
|
||||
@@ -1560,8 +1587,8 @@ groups:
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Lock acquisition failure rate > 10%"
|
||||
description: "Check Redis and database performance"
|
||||
summary: 'Lock acquisition failure rate > 10%'
|
||||
description: 'Check Redis and database performance'
|
||||
|
||||
# WARNING: Elevated lock failure rate
|
||||
- alert: ElevatedLockFailureRate
|
||||
@@ -1571,7 +1598,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Lock acquisition failure rate > 5%"
|
||||
summary: 'Lock acquisition failure rate > 5%'
|
||||
|
||||
# WARNING: Slow lock acquisition
|
||||
- alert: SlowLockAcquisition
|
||||
@@ -1583,7 +1610,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "P95 lock acquisition time > 1 second"
|
||||
summary: 'P95 lock acquisition time > 1 second'
|
||||
|
||||
# WARNING: High retry count
|
||||
- alert: HighRetryCount
|
||||
@@ -1595,7 +1622,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Retry count > 100 per hour in project {{ $labels.project }}"
|
||||
summary: 'Retry count > 100 per hour in project {{ $labels.project }}'
|
||||
|
||||
# WARNING: Slow generation
|
||||
- alert: SlowDocumentNumberGeneration
|
||||
@@ -1607,7 +1634,7 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "P95 generation time > 2 seconds"
|
||||
summary: 'P95 generation time > 2 seconds'
|
||||
```
|
||||
|
||||
**AlertManager Configuration** (`alertmanager/config.yml`):
|
||||
@@ -1659,9 +1686,11 @@ receivers:
|
||||
"panels": [
|
||||
{
|
||||
"title": "Lock Acquisition Success Rate",
|
||||
"targets": [{
|
||||
"expr": "1 - (rate(docnum_lock_acquisition_failures_total[5m]) / rate(docnum_lock_acquisition_total[5m]))"
|
||||
}],
|
||||
"targets": [
|
||||
{
|
||||
"expr": "1 - (rate(docnum_lock_acquisition_failures_total[5m]) / rate(docnum_lock_acquisition_total[5m]))"
|
||||
}
|
||||
],
|
||||
"type": "graph",
|
||||
"gridPos": { "x": 0, "y": 0, "w": 12, "h": 8 }
|
||||
},
|
||||
@@ -1686,17 +1715,21 @@ receivers:
|
||||
},
|
||||
{
|
||||
"title": "Generation Rate (per minute)",
|
||||
"targets": [{
|
||||
"expr": "sum(rate(docnum_generation_duration_ms_count[1m])) * 60"
|
||||
}],
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(docnum_generation_duration_ms_count[1m])) * 60"
|
||||
}
|
||||
],
|
||||
"type": "stat",
|
||||
"gridPos": { "x": 0, "y": 8, "w": 6, "h": 4 }
|
||||
},
|
||||
{
|
||||
"title": "Redis Connection Status",
|
||||
"targets": [{
|
||||
"expr": "docnum_redis_connection_status"
|
||||
}],
|
||||
"targets": [
|
||||
{
|
||||
"expr": "docnum_redis_connection_status"
|
||||
}
|
||||
],
|
||||
"type": "stat",
|
||||
"gridPos": { "x": 6, "y": 8, "w": 6, "h": 4 },
|
||||
"thresholds": {
|
||||
@@ -1709,9 +1742,11 @@ receivers:
|
||||
},
|
||||
{
|
||||
"title": "Error Rate by Type",
|
||||
"targets": [{
|
||||
"expr": "sum by (reason) (rate(docnum_lock_acquisition_failures_total[5m]))"
|
||||
}],
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (reason) (rate(docnum_lock_acquisition_failures_total[5m]))"
|
||||
}
|
||||
],
|
||||
"type": "graph",
|
||||
"gridPos": { "x": 12, "y": 8, "w": 12, "h": 8 }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user