# 3.11 Document Numbering Management (การจัดการเลขที่เอกสาร)

---
title: 'Functional Requirements: Document Numbering Management'
version: 1.6.2
status: draft
owner: Nattanin Peancharoen
last_updated: 2025-12-17
related:
  - specs/01-requirements/01-01-objectives.md
  - specs/01-requirements/01-02-architecture.md
  - specs/01-requirements/01-03-functional-requirements.md
  - specs/01-requirements/01-03.11-document-numbering.md
  - specs/03-implementation/03-04-document-numbering.md
  - specs/04-operations/04-08-document-numbering-operations.md
  - specs/07-database/07-01-data-dictionary-v1.7.0.md
  - specs/05-decisions/ADR-002-document-numbering-strategy.md
Clean Version v1.6.2 – Scope of Changes:
  - เลือกใช้ Single Numbering System (Option A)
  - แก้ Primary Key design ให้ implement ได้จริง
  - ปรับ Character Rule เป็น UTF‑8 printable
  - Bind Reset Policy ชัดเจน (Yearly reset, RFA no reset)
  - เพิ่ม Number State Machine
  - เพิ่ม Idempotency Key
  - Drawing ใช้ separate counter namespace
  - เพิ่ม Formal Token Validation Grammar
---

> **📖 เอกสารที่เกี่ยวข้อง**
>
> - **Implementation Guide**: [03-implementation/03-04-document-numbering.md](../03-implementation/03-04-document-numbering.md) - รายละเอียดการ implement ด้วย NestJS, TypeORM, Redis
> - **Operations Guide**: [04-operations/04-08-document-numbering-operations.md](../04-operations/04-08-document-numbering-operations.md) - Monitoring, Troubleshooting, Maintenance Procedures

---

## 3.11.1 Overview & วัตถุประสงค์

### 3.11.1.1 Purpose

ระบบ Document Numbering สำหรับสร้างเลขที่เอกสารอัตโนมัติที่มีความเป็นเอกลักษณ์ (unique) และสามารถติดตามได้ (traceable) สำหรับเอกสารทุกประเภทในระบบ LCBP3-DMS

### 3.11.1.2 Requirements Summary

- ระบบต้องสามารถสร้างเลขที่เอกสาร (Running Number) ได้โดยอัตโนมัติ, ที่มีความเป็นเอกลักษณ์ (unique) และยืดหยุ่นสูง
- ระบบต้องสามารถกำหนดรูปแบบ (template) เลขที่เอกสารได้ สำหรับแต่ละโครงการ, ชนิดเอกสาร, ประเภทเอกสาร
- ระบบต้องรับประกัน Uniqueness ของเลขที่เอกสารในทุกสถานการณ์
- ระบบต้องรองรับการทำงานแบบ concurrent ได้อย่างปลอดภัย

### 3.11.1.3 Scope

- Auto-generation ของเลขที่เอกสารตามรูปแบบที่กำหนด
- Manual override สำหรับการ import เอกสารเก่า
- Cancelled number handling (ไม่ reuse)
- Void & Replace pattern สำหรับการแทนที่เอกสาร
- Distributed locking เพื่อป้องกัน race condition
- Complete audit trail สำหรับทุก operation

### 3.11.1.4 Document Types Supported

- Request for Approvals (RFA)
- Request for Information (RFI)
- Transmittal (TRANSMITTAL)
- Email (EMAIL)
- Instruction (INSTRUCTION)
- Letter (LETTER)
- Memorandum (MEMO)
- Minutes of Meeting (MOM)
- Notice (NOTICE)
- Other (OTHER)
- Contract Drawings (COD)
- Shop Drawings (SHD)
- Circulation Sheets (CIR)

### 3.11.1.5 Architectural Decision (Updated)
AD-DN-001: Single Source of Truth for Numbering ระบบ เลือกใช้ Option A:
  - document_number_counters เป็น Core / Authoritative Counter System
  - document_numbering_configs ใช้เฉพาะ:
    - Template format
    - Permission / policy
  - ยกเลิกการใช้ document_numbering_sequences เป็น counter จริง
เหตุผล: ลดความซ้ำซ้อน, ป้องกัน counter mismatch, debug ง่าย, ops ชัดเจน
---
## 3.11.2 Counter Logic & Reset Policy
### 3.11.2 Counter Logic (Logic การนับเลข)

การนับเลขจะแยกตาม **Counter Key** ที่ประกอบด้วยหลายส่วน ขึ้นกับประเภทเอกสาร

| Document Type | Reset Policy |
| ------------- | ------------ |
| Correspondence (LETTER, MEMO, RFI, etc.) | Yearly reset |
| Transmittal | Yearly reset |
| RFA | No reset (continuous) |
| Drawing | Separate namespace (see 3.11.8) |

### 3.11.2.2 Counter Key Fields (Revised)
```
(project_id,
 originator_organization_id,
 recipient_organization_id,
 correspondence_type_id,
 sub_type_id,
 rfa_type_id,
 discipline_id,
 reset_scope)
```

* `reset_scope`:
  * `YEAR_2025`, `YEAR_2026`, ...
  * `NONE` (สำหรับ RFA)

### 3.11.2.3 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`                           | -               |
| `recipient_organization_id`  | Depends on type  | ID องค์กรผู้รับหลัก (TO) | `correspondence_recipients` where `recipient_type = 'TO'` | 0 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               |
| `reset_scope`                | ✅ Yes            | ขอบเขต reset        | System derived                                            | -               |

### 3.11.2.4 Counter Key by Document Type

#### **Global (LETTER / MEMO / RFI / EMAIL / INSTRUCTION / NOTICE / OTHER)**:
```
(project_id, originator_organization_id, recipient_organization_id,
 correspondence_type_id, 0, 0, 0, 'YEAR_2025')
```

**หมายเหตุ**:
- ไม่ใช้ `discipline_id`, `sub_type_id`, `rfa_type_id`
- ถ้ามีการเพิ่ม correspondence type ใหม่ใน `correspondence_types` table จะใช้ Template นี้โดยอัตโนมัติ

#### **TRANSMITTAL**:
```
(project_id, originator_organization_id, recipient_organization_id,
 correspondence_type_id, sub_type_id, 0, 0, 'YEAR_2025')
```

*หมายเหตุ*: ใช้ `sub_type_id` เพิ่มเติม

#### **RFA**:
```
(project_id, originator_organization_id, 0,
 correspondence_type_id, 0, rfa_type_id, discipline_id, 'NONE')
```

*หมายเหตุ*:
- RFA ไม่ใช้ `recipient_organization_id` (ใช้ 0) เพราะเป็นเอกสารโครงการ (CONTRACTOR → CONSULTANT → OWNER)
- ไม่มี yearly reset (`reset_scope = 'NONE'`)

### 3.11.2.5 วิธีการหา project_id

เนื่องจาก Template ของ LETTER/TRANSMITTAL ไม่มี `{PROJECT}` token ระบบจะหา `project_id` จาก:

1. **User Context** (แนะนำ):
   - เมื่อ User สร้างเอกสาร UI จะให้เลือก Project/Contract ก่อน
   - ใช้ `project_id` จาก Context ที่เลือก

2. **จาก Organization**:
   - Query `project_organizations` หรือ `contract_organizations`
   - ใช้ `originator_organization_id` หา project ที่เกี่ยวข้อง
   - ถ้ามีหลาย project ให้ User เลือก

3. **Validation**:
   - ตรวจสอบว่า organization มีสิทธิ์ใน project นั้น
   - ตรวจสอบว่า project/contract เป็น active

### 3.11.2.6 Fallback สำหรับค่า NULL

- `correspondence_type_id`: ใช้ `0` (ไม่ระบุประเภทเอกสาร)
- `discipline_id`: ใช้ `0` (ไม่ระบุสาขางาน)
- `sub_type_id`: ใช้ `0` (ไม่มีประเภทย่อย)
- `rfa_type_id`: ใช้ `0` (ไม่ระบุประเภท RFA)
- `recipient_organization_id`: ใช้ `0` สำหรับ RFA, Required สำหรับ LETTER/TRANSMITTAL

---

## 3.11.3 Format Templates by Correspondence Type

> **📝 หมายเหตุสำคัญ**
> - Templates ด้านล่างเป็น **ตัวอย่าง** สำหรับประเภทเอกสารหลัก
> - ระบบรองรับ **ทุกประเภทเอกสาร** ที่อยู่ใน `correspondence_types` table
> - หากมีการเพิ่มประเภทใหม่ในอนาคต สามารถใช้งานได้โดยอัตโนมัติ
> - Admin สามารถกำหนด Template เฉพาะสำหรับแต่ละประเภทผ่าน Admin Panel

### 3.11.3.1 Global (correspondence_type_id = defined)

**Template**:
```
{ORIGINATOR}-{RECIPIENT}-{SEQ:4}-{YEAR:B.E.}
```

**Example**: `คคง.-สคฉ.3-0001-2568`

**Token Breakdown**:

- `คคง.` = {ORIGINATOR} = รหัสองค์กรผู้ส่ง
- `สคฉ.3` = {RECIPIENT} = รหัสองค์กรผู้รับหลัก (TO)
- `0001` = {SEQ:4} = Running number (เริ่ม 0001, 0002, ...)
- `2568` = {YEAR:B.E.} = ปี พ.ศ.

> **⚠️ Template vs Counter Separation**
>
> - {CORR_TYPE} **ไม่แสดง**ใน template เพื่อความกระชับ
> - แต่ระบบ**ยังใช้ correspondence_type_id ใน Counter Key** เพื่อแยก counter
> - LETTER, MEMO, RFI **มี counter แยกกัน** แม้ template format เหมือนกัน

**Counter Key**: `(project_id, originator_org_id, recipient_org_id, correspondence_type_id, 0, 0, 0, 'YEAR_2025')`

---

### 3.11.3.2 Transmittal (TYPE = TRANSMITTAL)

**Template**:
```
{ORIGINATOR}-{RECIPIENT}-{SUB_TYPE}-{SEQ:4}-{YEAR:B.E.}
```

**Example**: `คคง.-สคฉ.3-21-0117-2568`

**Token Breakdown**:

- `คคง.` = {ORIGINATOR}
- `สคฉ.3` = {RECIPIENT}
- `21` = {SUB_TYPE} = หมายเลขประเภทย่อย (11=MAT, 12=SHP, 13=DWG, 14=MET, ...)
- `0117` = {SEQ:4}
- `2568` = {YEAR:B.E.}

**Counter Key**: `(project_id, originator_org_id, recipient_org_id, correspondence_type_id, sub_type_id, 0, 0, 'YEAR_2025')`

---

### 3.11.3.3 RFA (Request for Approval)

**Example**: `LCBP3-C2-RFA-TER-RPT-0001-A`

**Token Breakdown**:

- `LCBP3-C2` = {PROJECT} = รหัสโครงการ
- `RFA` = {CORR_TYPE} = ประเภทเอกสาร (**แสดง**ใน RFA template)
- `TER` = {DISCIPLINE} = รหัสสาขางาน (TER=Terminal, STR=Structure, ...)
- `RPT` = {RFA_TYPE} = ประเภท RFA (RPT=Report, SDW=Shop Drawing, ...)
- `0001` = {SEQ:4}
- `A` = {REV} = Revision code

> **📋 RFA Workflow**
>
> - RFA เป็น **เอกสารโครงการ** (Project-level document)
> - Workflow: **CONTRACTOR → CONSULTANT → OWNER**
> - ไม่มี specific `recipient_id` เพราะเป็น workflow ที่กำหนดไว้แล้ว

**Counter Key**: `(project_id, originator_org_id, 0, correspondence_type_id, 0, rfa_type_id, discipline_id, 'NONE')`

---


### 3.11.3.4 Drawing

#### 3.11.3.4.1 Shop Drawing
**Example**: `LCBP3-C2-SDW-SW-BST-002-1`

**Token Breakdown**:

- `LCBP3-C2` = {PROJECT} = รหัสโครงการ
- `SDW` = {DRAWING_TYPE} = ประเภทแบบ (SDW=Shop Drawing)
- `SW` = {main_category} = รหัสหมวดหลัก
- `BST` = {sub_category} = รหัสหมวดย่อย
- `002` = {SEQ:3}
- `1` = {REV} = Revision code

**Counter Key**: `(project_id, DRAWING_TYPE, main_category, sub_category)`

#### 3.11.3.4.2 As Built Drawing

**Example**: `LCBP3-C2-ASB-SW-BST-002-1`

**Token Breakdown**:

- `LCBP3-C2` = {PROJECT} = รหัสโครงการ
- `ASB` = {DRAWING_TYPE} = ประเภทแบบ (ASB=As Built Arawing)
- `SW` = {main_category} = รหัสหมวดหลัก
- `BST` = {sub_category} = รหัสหมวดย่อย
- `002` = {SEQ:3}
- `1` = {REV} = Revision code

**Counter Key**: `(project_id, DRAWING_TYPE, main_category, sub_category)`

---

## 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`                                                                           |
| `{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`                         | `reset_scope` + 543                                                                             |
| `{YEAR:A.D.}`  | ปี ค.ศ.                       | `2025`                         | `reset_scope`                                                                                   |
| `{REV}`        | Revision Code                | `A`, `B`, `AA`                 | `correspondence_revisions.revision_label`                                                       |
| `{PREFIX}`     | คำนำหน้าตามประเภทเอกสาร         | `COR`, `RFA`                   | Configurable prefix                                                                             |
| `{YYYY}`       | ปี 4 หลัก                      | `2025`                         | Current year                                                                                    |
| `{YY}`         | ปี 2 หลัก                      | `25`                           | Current year (short)                                                                            |
| `{MM}`         | เดือน 2 หลัก                   | `01-12`                        | Current month                                                                                   |
| `{CONTRACT}`   | รหัสสัญญา                      | `C001`                         | `contracts.contract_code`                                                                       |

### Token Usage Notes

**{SEQ:n}**:

- `n` = จำนวนหลักที่ต้องการ (typically 4-6)
- Counter **เริ่มจาก 0001** และเพิ่มทีละ 1 (0001, 0002, 0003, ...)
- Padding ด้วย 0 ทางซ้าย
- Reset ทุกปี (ตาม `reset_scope` ใน Counter Key)

**{RECIPIENT}**:

- ใช้เฉพาะผู้รับที่มี `recipient_type = 'TO'` เท่านั้น
- ถ้ามีหลาย TO ให้ใช้คนแรก (ตาม sort order)
- **ไม่ใช้สำหรับ RFA** (RFA ไม่มี {RECIPIENT} ใน template)

**{CORR_TYPE}**:

- รองรับทุกค่าจาก `correspondence_types.type_code`
- ถ้ามีการเพิ่มประเภทใหม่ จะใช้งานได้ทันที
- **แสดงใน template**: RFA only
- **ไม่แสดงแต่ใช้ใน counter**: LETTER, TRANSMITTAL, และ Other types

**Deprecated Tokens** (ไม่ควรใช้):

- ~~`{ORG}`~~ → ใช้ `{ORIGINATOR}` หรือ `{RECIPIENT}` แทน
- ~~`{TYPE}`~~ → ใช้ `{CORR_TYPE}`, `{SUB_TYPE}`, หรือ `{RFA_TYPE}` แทน (ตามบริบท)
- ~~`{CATEGORY}`~~ → ไม่ได้ใช้งานในระบบปัจจุบัน

---

## 3.11.5 Character & Format Rules (Updated)

### BR-DN-002 (Revised)

* Document number **must be printable UTF‑8**
* Disallowed:
  * Control characters
  * Newlines / tabs
* Allowed:
  * Thai
  * English
  * Numbers
  * `-`, `_`, `.`

### BR-DN-003: Number Format Rules
- Min length: 10 characters
- Max length: 50 characters
- Must include {SEQ:n} token exactly once

---

## 3.11.6 Number State Machine (New)

### States

```text
RESERVED → CONFIRMED → VOID
           ↘ CANCELLED
```

### Rules

* **RESERVED**:
  * TTL 5 minutes
  * Auto-expire → CANCELLED
* **CONFIRMED**:
  * Linked to document_id
* **VOID**:
  * Only CONFIRMED numbers
  * Replacement creates new number

---

## 3.11.7 Idempotency (New)

### API Requirement

* All number generation APIs **must** support:

```http
Idempotency-Key: UUID
```

### Behavior

* Same key + same payload → return same number
* Prevents double submit / retry duplication

---

## 3.11.8 Drawing Numbering (Clarified)

* Drawing numbering **does not use** this counter table
* Uses **separate counter namespace**:

```text
DRAWING::<project>::<contract>
```

* Prevents collision with correspondence/RFA

---

## 3.11.9 Token Validation Grammar (New)

### EBNF

```ebnf
TEMPLATE     := TOKEN ("-" TOKEN)*
TOKEN        := SIMPLE | PARAM
SIMPLE       := "{PROJECT}" | "{ORIGINATOR}" | "{RECIPIENT}" |
                "{CORR_TYPE}" | "{DISCIPLINE}" | "{RFA_TYPE}" |
                "{REV}" | "{YYYY}" | "{YY}" | "{MM}"
PARAM        := "{SEQ:" DIGIT+ "}"
DIGIT        := "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
```

### Validation Rules

* Must include `{SEQ:n}` exactly once
* Unknown tokens → validation error
* Max template length: 50 chars

---

## 3.11.10 Functional Requirements

### 3.11.10.1 Auto Number Generation

#### FR-DN-001: Generate Sequential Number
**Priority**: CRITICAL | **Status**: Required

**Description**:
ระบบต้องสามารถสร้างเลขที่เอกสารอัตโนมัติตามลำดับ (sequential) โดยไม่ซ้ำกัน

**Acceptance Criteria**:
- เลขที่เอกสารต้องเป็น unique ในscope ที่กำหนด
- ต้องเพิ่มขึ้นทีละ 1 (increment by 1)
- ต้องรองรับ concurrent requests โดยไม่มีเลขที่ซ้ำ
- Response time < 100ms (p95)

---

#### FR-DN-002: Configurable Number Format
**Priority**: HIGH | **Status**: Required

**Description**:
ระบบต้องรองรับการกำหนดรูปแบบเลขที่เอกสารที่หลากหลาย

**Acceptance Criteria**:
- รองรับ format tokens ที่ระบุ
- Admin สามารถกำหนด format ผ่าน UI ได้
- Validate format ก่อน save
- แสดง preview ของเลขที่ที่จะถูกสร้าง

---

#### FR-DN-003: Scope-based Sequences
**Priority**: HIGH | **Status**: Required

**Description**:
ระบบต้องรองรับการสร้าง sequence ที่แยกตาม scope ที่ต่างกัน

**Acceptance Criteria**:
- เลขที่ไม่ซ้ำภายใน scope เดียวกัน
- Scope ที่ต่างกันสามารถมีเลขที่เดียวกันได้
- Support multiple active scopes

---

### 3.11.10.2 Manual Override

#### FR-DN-004: Manual Number Assignment
**Priority**: HIGH | **Status**: Required

**Description**:
ระบบต้องรองรับการกำหนดเลขที่เอกสารด้วยตนเอง (manual override)

**Use Cases**:
1. Import เอกสารเก่าจากระบบเดิม
2. External documents จาก client/consultant
3. Correction หลังพบความผิดพลาด

**Acceptance Criteria**:
- ตรวจสอบ duplicate ก่อน save
- Validate format ตามรูปแบบที่กำหนด
- Auto-update sequence counter ถ้าเลขที่สูงกว่า current
- บันทึก audit log ว่าเป็น manual override
- ต้องมีสิทธิ์ Admin ขึ้นไปเท่านั้น

---

#### FR-DN-005: Bulk Import Support
**Priority**: MEDIUM | **Status**: Required

**Description**:
ระบบต้องรองรับการ import เอกสารหลายรายการพร้อมกัน

**Acceptance Criteria**:
- รองรับไฟล์ CSV/Excel
- Validate ทุกรายการก่อน import
- แสดง preview ก่อน confirm
- Rollback ทั้งหมดถ้ามีรายการใดผิดพลาด (transactional)
- Auto-update sequence counters หลัง import
- Generate import report

---

### 3.11.10.3 Cancelled & Void Handling

#### FR-DN-006: Skip Cancelled Numbers
**Priority**: HIGH | **Status**: Required

**Description**:
เลขที่เอกสารที่ถูกยกเลิกต้องไม่ถูก reuse

**Rationale**:
- รักษา audit trail ที่ชัดเจน
- ป้องกันความสับสน
- Legal compliance

**Acceptance Criteria**:
- Cancelled number ยังคงอยู่ในฐานข้อมูลพร้อม status
- ระบบข้าม (skip) cancelled number เมื่อสร้างเลขที่ใหม่
- บันทึกเหตุผลการยกเลิก
- แสดง cancelled numbers ใน audit trail

---

#### FR-DN-007: Void and Replace
**Priority**: HIGH | **Status**: Required

**Description**:
ระบบต้องรองรับการ void เอกสารและสร้างเอกสารใหม่แทน

**Workflow**:
1. User เลือกเอกสารที่ต้องการ void
2. ระบุเหตุผล (required)
3. ระบบเปลี่ยน status เอกสารเดิมเป็น VOID
4. สร้างเอกสารใหม่ด้วยเลขที่ใหม่
5. Link เอกสารใหม่กับเดิม (voided_from_id)

**Acceptance Criteria**:
- เอกสารเดิม status = VOID (ไม่ลบ)
- เอกสารใหม่ได้เลขที่ต่อเนื่องจาก sequence
- มี reference link ระหว่างเอกสาร
- บันทึก void reason
- แสดง void history chain (A→B→C)

---

### 3.11.10.4 Concurrency & Performance

#### FR-DN-008: Prevent Race Conditions
**Priority**: CRITICAL | **Status**: Required

**Description**:
ระบบต้องป้องกันการสร้างเลขที่ซ้ำเมื่อมีการ request พร้อมกัน

**Solution**:
- Distributed locking (Redlock)
- Database pessimistic locking
- Two-phase commit pattern

**Acceptance Criteria**:
- Zero duplicate numbers ภายใต้ concurrent load (1000 req/s)
- Lock acquisition time < 50ms (avg)
- Automatic retry on lock failure (max 3 times)
- Timeout handling (30 seconds)

---

#### FR-DN-009: Two-Phase Commit
**Priority**: HIGH | **Status**: Required

**Description**:
ใช้ Two-phase commit pattern เพื่อความสมบูรณ์ของข้อมูล

**Phase 1: Reserve**
- ล็อกเลขที่และ reserve ไว้ชั่วคราว
- Set TTL 5 นาที
- Return reservation token

**Phase2: Confirm or Cancel**
- Confirm: บันทึกลงฐานข้อมูลถาวร
- Cancel: คืน lock และ reservation

**Acceptance Criteria**:
- Reservation ต้อง expire หลัง 5 นาที
- Auto-cleanup expired reservations
- Support explicit cancel
- Idempotent confirmation

---
### 3.11.10.5 Monitoring & Audit

#### FR-DN-010: Complete Audit Trail
**Priority**: HIGH | **Status**: Required

**Description**:
บันทึกทุก operation ที่เกิดขึ้นกับเลขที่เอกสาร

**Events to Log**:
- Number reserved
- Number confirmed
- Number cancelled
- Manual override
- Void document
- Sequence adjusted
- Format changed

**Acceptance Criteria**:
- Log ทุก operation
- Searchable by user, date, type
- Export to CSV
- Retain for 7 years

---

#### FR-DN-011: Metrics & Alerting
**Priority**: MEDIUM | **Status**: Required

**Description**:
แสดงสถิติและส่ง alert เมื่อเกิดปัญหา

**Metrics**:
- Sequence utilization (% of max)
- Average lock wait time
- Failed lock attempts
- Numbers generated per day
- Manual overrides per day

**Alerts**:
- Sequence >90% used (WARNING)
- Sequence >95% used (CRITICAL)
- Lock wait time >1s (WARNING)
- Redis unavailable (CRITICAL)
- High error rate (WARNING)

---

## 3.11.11 Database Schema (Corrected)

### 3.11.11.1 document_number_counters

```sql
CREATE TABLE document_number_counters (
  project_id INT NOT NULL,
  correspondence_type_id INT NULL,-- NULL = default format for project
  originator_organization_id INT NOT NULL,
  recipient_organization_id INT NOT NULL DEFAULT 0, -- 0 = no recipient (RFA)
  sub_type_id INT DEFAULT 0,
  rfa_type_id INT DEFAULT 0,
  discipline_id INT DEFAULT 0,
  reset_scope VARCHAR(20) NOT NULL,
  last_number INT DEFAULT 0 NOT NULL,
  version INT DEFAULT 0 NOT NULL,
  created_at DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6),
  updated_at DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
  PRIMARY KEY (
    project_id,
    originator_organization_id,
    recipient_organization_id,
    correspondence_type_id,
    sub_type_id,
    rfa_type_id,
    discipline_id,
    reset_scope
  ),

  FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
  FOREIGN KEY (originator_organization_id) REFERENCES organizations(id) ON DELETE CASCADE,
  FOREIGN KEY (correspondence_type_id) REFERENCES correspondence_types(id) ON DELETE CASCADE
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci
  COMMENT = 'ตารางเก็บ Running Number Counters';
```

### Rules

* RFA → `recipient_organization_id = 0`
* Reset:
  * Yearly: `reset_scope = 'YEAR_2025'`
  * No reset: `reset_scope = 'NONE'`

### 3.11.11.2 Index Requirements

```sql
-- Index สำหรับ Performance
CREATE INDEX idx_counter_lookup
ON document_number_counters (
  project_id,
  correspondence_type_id,
  reset_scope
);

-- Index สำหรับ Originator lookup
CREATE INDEX idx_counter_org
ON document_number_counters (
  originator_organization_id,
  reset_scope
);

-- Index สำหรับ updated_at
CREATE INDEX idx_counter_updated
ON document_number_counters (
  updated_at
);
```

### 3.11.11.3 Numbering Configuration Table

```sql
CREATE TABLE document_numbering_configs (
  id INT PRIMARY KEY AUTO_INCREMENT,
  document_type VARCHAR(50) NOT NULL,
  format VARCHAR(200) NOT NULL,
  scope ENUM('GLOBAL','PROJECT','CONTRACT','YEARLY','MONTHLY'),
  allow_manual_override BOOLEAN DEFAULT FALSE,
  max_value INT DEFAULT 999999,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY (document_type, scope)
);
```

### 3.11.11.4 Audit Log Table

```sql
CREATE TABLE document_number_audit (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  document_id INT NOT NULL,
  document_type VARCHAR(50),
  document_number VARCHAR(100) NOT NULL,
  operation ENUM('RESERVE', 'CONFIRM', 'CANCEL', 'MANUAL_OVERRIDE', 'VOID', 'GENERATE') NOT NULL,
  status ENUM('RESERVED', 'CONFIRMED', 'CANCELLED', 'VOID', 'MANUAL'),
  counter_key JSON NOT NULL COMMENT 'Counter key used (JSON format)',
  reservation_token VARCHAR(36) NULL,
  originator_organization_id INT NULL,
  recipient_organization_id INT NULL,

  template_used VARCHAR(200) NOT NULL,
  old_value TEXT NULL,
  new_value TEXT NULL,

  user_id INT NOT NULL,
  ip_address VARCHAR(45),
  user_agent TEXT,
  is_success BOOLEAN DEFAULT TRUE,

  -- 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',
  metadata JSON NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

  INDEX idx_document_id (document_id),
  INDEX idx_user_id (user_id),
  INDEX idx_status (status),
  INDEX idx_operation (operation),
  INDEX idx_document_number (document_number),
  INDEX idx_reservation_token (reservation_token);
  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';
```

### 3.11.11.5 Reservation Table
```sql
CREATE TABLE document_number_reservations (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,

  -- Reservation Details
  token VARCHAR(36) NOT NULL UNIQUE COMMENT 'UUID v4',
  document_number VARCHAR(100) NOT NULL UNIQUE,
  status ENUM('RESERVED', 'CONFIRMED', 'CANCELLED', 'VOID') NOT NULL DEFAULT 'RESERVED',

  -- Linkage
  document_id INT NULL COMMENT 'FK to documents (NULL until confirmed)',

  -- Context (for debugging)
  project_id INT NOT NULL,
  correspondence_type_id INT NOT NULL,
  originator_organization_id INT NOT NULL,
  recipient_organization_id INT DEFAULT 0,
  user_id INT NOT NULL,

  -- Timestamps
  reserved_at DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6),
  expires_at DATETIME(6) NOT NULL,
  confirmed_at DATETIME(6) NULL,
  cancelled_at DATETIME(6) NULL,

  -- Audit
  ip_address VARCHAR(45),
  user_agent TEXT,
  metadata JSON NULL COMMENT 'Additional context',

  -- Indexes
  INDEX idx_token (token),
  INDEX idx_status (status),
  INDEX idx_status_expires (status, expires_at),
  INDEX idx_document_id (document_id),
  INDEX idx_user_id (user_id),
  INDEX idx_reserved_at (reserved_at),

  -- Foreign Keys
  FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE SET NULL,
  FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
  FOREIGN KEY (correspondence_type_id) REFERENCES correspondence_types(id) ON DELETE CASCADE,
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
  COMMENT='Document Number Reservations - Two-Phase Commit';

```
### 3.11.11.5 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';
```

### 3.11.11.6 Config History Table

```sql
CREATE TABLE document_number_config_history (
  id INT AUTO_INCREMENT PRIMARY KEY,
  config_id INT NOT NULL,
  template_before TEXT,
  template_after TEXT NOT NULL,
  changed_by INT NOT NULL,
  changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  change_reason TEXT,

  FOREIGN KEY (config_id) REFERENCES document_number_configs(id),
  FOREIGN KEY (changed_by) REFERENCES users(id)
) ENGINE=InnoDB COMMENT='Template Change History';
```

### 3.11.11.7 Important Notes

> **💡 Counter Key Design**
> - `recipient_organization_id` ใช้ `0` สำหรับ RFA (ไม่มี specific recipient)
> - `version` column สำหรับ Optimistic Locking (ป้องกัน race condition)
> - `last_number` เริ่มจาก 0 และเพิ่มขึ้นทีละ 1
> - Counter reset ทุกปี (เมื่อ `reset_scope` เปลี่ยน)

> **⚠️ Migration Notes**
> - ไม่มีข้อมูลเก่า ไม่ต้องทำ backward compatibility
> - สามารถสร้าง table ใหม่ได้เลยตาม schema ข้างต้น
> - ต้องมี seed data สำหรับ `correspondence_types`, `rfa_types`, `disciplines` ก่อน

### 3.11.11.8 Example Counter Records

```sql
-- Example: LETTER from คคง. to สคฉ.3 in LCBP3-C2 year 2025
INSERT INTO document_number_counters (
  project_id, originator_organization_id, recipient_organization_id,
  correspondence_type_id, sub_type_id, rfa_type_id, discipline_id,
  reset_scope, version, last_number
) VALUES (
  2,      -- LCBP3-C2
  22,     -- คคง.
  10,     -- สคฉ.3
  6,      -- LETTER
  0, 0, 0,
  'YEAR_2025', 0, 0
);

-- Example: RFA from ผรม.2 in LCBP3-C2, discipline TER, type RPT, year 2025
INSERT INTO document_number_counters (
  project_id, originator_organization_id, recipient_organization_id,
  correspondence_type_id, sub_type_id, rfa_type_id, discipline_id,
  reset_scope, version, last_number
) VALUES (
  2,      -- LCBP3-C2
  42,     -- ผรม.2
  0,      -- RFA ไม่มี specific recipient
  1,      -- RFA
  0,
  18,     -- RPT (Report)
  5,      -- TER (Terminal)
  'NONE', 0, 0  -- No yearly reset for RFA
);
```

---

## 3.11.12 Security & Data Integrity Requirements

### 3.11.12.1 Concurrency Control

**Requirements:**

- ระบบ**ต้อง**ป้องกัน race condition เมื่อมีการสร้างเลขที่เอกสารพร้อมกัน
- ระบบ**ต้อง**รับประกัน uniqueness ของเลขที่เอกสารในทุกสถานการณ์
- ระบบ**ควร**ใช้ Distributed Lock (Redis) เป็นกลไก primary
- ระบบ**ต้อง**มี fallback mechanism เมื่อ Redis ไม่พร้อมใช้งาน

### 3.11.12.2 Data Integrity

**Requirements:**

- ระบบ**ต้อง**ใช้ Optimistic Locking เพื่อตรวจจับ concurrent updates
- ระบบ**ต้อง**มี database constraints เพื่อป้องกันข้อมูลผิดพลาด:
  - Unique constraint บน `document_number`
  - Foreign key constraints ทุก relationship
  - Check constraints สำหรับ business rules

### 3.11.12.3 Authorization

**Requirements:**

- เฉพาะ **authenticated users** เท่านั้นที่สามารถ request document number
- เฉพาะ **Project Admin** เท่านั้นที่แก้ไข template ได้
- เฉพาะ **Super Admin** เท่านั้นที่ reset counter ได้ (requires approval)

### 3.11.12.4 Rate Limiting

**Requirements:**
- Limit ต่อ user: **10 requests/minute** (prevent abuse)
- Limit ต่อ IP: **50 requests/minute**

---

## 3.11.13 Error Handling Requirements

### 3.11.13.1 Retry Mechanism

**Requirements:**

ระบบ**ต้อง**จัดการ error scenarios ต่อไปนี้:

| Scenario            | Strategy            | Max Retries | Expected Response               |
| ------------------- | ------------------- | ----------- | ------------------------------- |
| Redis Unavailable   | Fallback to DB Lock | 0           | Continue (degraded performance) |
| Lock Timeout        | Exponential Backoff | 5           | HTTP 503 after final retry      |
| Version Conflict    | Immediate Retry     | 2           | HTTP 409 after final retry      |
| DB Connection Error | Exponential Backoff | 3           | HTTP 500 after final retry      |

### 3.11.13.2 User Experience

**Requirements:**

- Error messages **ต้อง**เป็นภาษาไทย และเข้าใจง่าย
- HTTP status codes **ต้อง**สื่อความหมายที่ถูกต้อง
- Frontend **ควร**แสดง retry option สำหรับ transient errors

---

## 3.11.14 Configuration Management Requirements

### 3.11.14.1 Template Management

**Requirements:**

- Project Admin **ต้อง**สามารถกำหนด/แก้ไข template ผ่าน Admin Panel
- ระบบ**ต้อง**validate template ก่อนบันทึก
- การเปลี่ยนแปลง template **ต้องไม่**ส่งผลต่อเอกสารที่สร้างไว้แล้ว

### 3.11.14.2 Template Versioning

**Requirements:**

- ระบบ**ต้อง**เก็บ history ของ template changes
- ระบบ**ต้อง**บันทึก user, timestamp, และเหตุผลในการเปลี่ยนแปลง
- ระบบ**ควร**สามารถ rollback ไปเวอร์ชันก่อนหน้าได้

### 3.11.14.3 Counter Reset Policy

**Requirements:**

- Counter **ต้อง**reset ตามปี (อัตโนมัติ)
- Admin **ต้อง**สามารถ manual reset counter ได้ (require approval + audit log)

---

## 3.11.15 Audit Trail Requirements

### 3.11.15.1 Audit Logging

**Requirements:**

ระบบ**ต้อง**บันทึกข้อมูลต่อไปนี้สำหรับทุก document number generation:

- `document_id` - เอกสารที่ถูกสร้าง
- `generated_number` - เลขที่ถูกสร้าง
- `counter_key` - key ที่ใช้ในการนับ (JSON format)
- `template_used` - template ที่ใช้
- `user_id` - ผู้ที่ request
- `ip_address` - IP address ของผู้ request
- `timestamp` - เวลาที่สร้าง
- `retry_count` - จำนวนครั้งที่ retry
- `performance_metrics` - Lock wait time, total duration

### 3.11.15.2 Error Logging

**Requirements:**

- ระบบ**ต้อง**บันทึก error แยกต่างหาก พร้อม error type classification
- ระบบ**ควร**alert ops team สำหรับ critical errors

### 3.11.15.3 Retention Policy

**Requirements:**

- Audit log **ต้อง**เก็บอย่างน้อย **7 ปี** (ตาม พ.ร.บ. ข้อมูลอิเล็กทรอนิกส์)

---

## 3.11.16 Performance Requirements

### 3.11.16.1 Response Time

**SLA Targets:**

| Metric            | Target   | Notes                    |
| ----------------- | -------- | ------------------------ |
| 95th percentile   | ≤ 2 วินาที | ตั้งแต่ request ถึง response |
| 99th percentile   | ≤ 5 วินาที | รวม retry attempts       |
| Normal operation  | ≤ 500ms  | ไม่มี retry                |
| Number generation | < 100ms  | (p95)                    |
| Lock acquisition  | < 50ms   | (avg)                    |
| Bulk import       | < 5s     | per 100 records          |

### 3.11.16.2 Throughput

**Capacity Targets:**

| Load Level  | Target      | Notes              |
| ----------- | ----------- | ------------------ |
| Normal load | ≥ 50 req/s  | ใช้งานปกติ           |
| Peak load   | ≥ 100 req/s | ช่วงเร่งงาน          |
| Burst       | ≥ 200 req/s | short duration     |
| Support     | > 500 req/s | Scale horizontally |

### 3.11.16.3 Availability

**SLA Targets:**

- **Uptime**: ≥ 99.5% (excluding planned maintenance)
- **Maximum downtime**: ≤ 3.6 ชั่วโมง/เดือน
- **RTO**: ≤ 30 นาที
- **RPO**: ≤ 5 นาที
- **Graceful degradation**: Fallback to DB-only
- **Auto-recovery**: From Redis failure

---

## 3.11.17 Monitoring & Alerting Requirements

### 3.11.17.1 Metrics

**Requirements:**

ระบบ**ต้อง**collect metrics ต่อไปนี้:

- Lock acquisition time (p50, p95, p99)
- Lock acquisition success/failure rate
- Counter generation latency
- Retry count distribution
- Redis connection status
- Database connection pool usage
- Sequence utilization (% of max)
- Numbers generated per day
- Manual overrides per day

### 3.11.17.2 Alerts

**Requirements:**

ระบบ**ต้อง**alert สำหรับ conditions ต่อไปนี้:

| Severity   | Condition                    | Action            |
| ---------- | ---------------------------- | ----------------- |
| 🔴 Critical | Redis unavailable > 1 minute | PagerDuty + Slack |
| 🔴 Critical | Lock failures > 10% in 5 min | PagerDuty + Slack |
| 🔴 Critical | Sequence >95% used           | PagerDuty + Slack |
| 🟡 Warning  | Lock failures > 5% in 5 min  | Slack             |
| 🟡 Warning  | Avg lock wait time > 1 sec   | Slack             |
| 🟡 Warning  | Retry count > 100/hour       | Slack             |
| 🟡 Warning  | Sequence >90% used           | Slack             |
| 🟡 Warning  | High error rate              | Slack             |

### 3.11.17.3 Dashboard

**Requirements:**

- Ops team **ต้อง**มี real-time dashboard (Grafana) แสดง:
  - Lock acquisition success rate
  - Lock wait time percentiles
  - Generation rate (per minute)
  - Error rate by type
  - Connection health status
  - Retry distribution

---

## 3.11.18 API Reference

### Document Number Generation

```http
POST /api/v1/documents/{documentId}/generate-number
```

สร้างเลขที่เอกสารสำหรับ document ที่ระบุ

**Request Body:**

```json
{
  "counterKey": {
    "projectId": 2,
    "originatorOrgId": 22,
    "recipientOrgId": 10,
    "correspondenceTypeId": 6,
    "subTypeId": 0,
    "rfaTypeId": 0,
    "disciplineId": 0,
    "resetScope": "YEAR_2025"
  }
}
```

**Response:**

```json
{
  "documentNumber": "คคง.-สคฉ.3-0001-2568",
  "generatedAt": "2025-12-02T15:30:00Z"
}
```

### Reserve Number

```http
POST /api/document-numbering/reserve
```

**Request:**
```json
{
  "document_type": "COR",
  "project_id": 1,
  "contract_id": null,
  "metadata": {}
}
```

**Response 201:**
```json
{
  "token": "uuid-v4",
  "document_number": "COR-2025-00042",
  "expires_at": "2025-01-16T10:30:00Z"
}
```

### Confirm Reservation

```http
POST /api/document-numbering/confirm
```

**Request:**
```json
{
  "token": "uuid-v4"
}
```

**Response 200:**
```json
{
  "document_number": "COR-2025-00042",
  "confirmed_at": "2025-01-16T10:25:00Z"
}
```

### Manual Override

```http
POST /api/document-numbering/manual
Authorization: Bearer <admin-token>
```

**Request:**
```json
{
  "document_type": "COR",
  "document_number": "COR-2024-99999",
  "reason": "Import from legacy system",
  "skip_validation": false
}
```

**Response 201:**
```json
{
  "document_number": "COR-2024-99999",
  "is_manual": true,
  "created_at": "2025-01-16T10:25:00Z"
}
```

### Template Management

```http
GET /api/v1/document-numbering/configs
```

ดูรายการ template configuration ทั้งหมด

```http
PUT /api/v1/document-numbering/configs/{configId}
```

แก้ไข template (Project Admin only)

```http
POST /api/v1/document-numbering/configs/{configId}/reset-counter
```

Reset counter (Super Admin only, requires approval)

---

## 3.11.19 Testing Requirements

### 3.11.19.1 Unit Tests
- Format parsing and validation
- Sequence increment logic
- Manual override validation
- Scope resolution

### 3.11.19.2 Integration Tests
- Redis locking mechanism
- Database transactions
- Two-phase commit flow
- Bulk import

### 3.11.19.3 Load Tests

```bash
# Must pass without duplicates
concurrent_users: 100
requests_per_second: 500
test_duration: 5 minutes
expected_duplicates: 0
```

- Concurrent number generation (1000 req/s)
- Lock contention under load
- Redis failover scenarios
- Database connection pool exhaustion

### 3.11.19.4 E2E Tests
- Complete document creation flow
- Void and replace workflow
- Bulk import with validation
- Admin configuration UI

---

## 3.11.20 Versioning Note

* Existing documents **not affected**
* New rules apply to documents generated after upgrade to v1.6.2

---

## 3.11.21 Migration Plan

### 3.11.21.1 Legacy Data Import
1. Export existing document numbers from old system
2. Validate format and detect duplicates
3. Bulk import using manual override API
4. Update sequence counters to max values
5. Verify data integrity

### 3.11.21.2 Rollout Strategy
- Week 1-2: Deploy to staging, test with dummy data
- Week 3: Deploy to production, enable for test project
- Week 4: Enable for all projects
- Week 5+: Monitor and optimize

---

## 3.11.22 Success Criteria

### 3.11.22.1 Functional Success
- ✅ All FRs implemented and tested
- ✅ Zero duplicate numbers in production
- ✅ Migration of 50,000+ legacy documents
- ✅ UAT approved by stakeholders

### 3.11.22.2 Performance Success
- ✅ Response time <100ms (p95)
- ✅ Throughput >500 req/s
- ✅ Lock acquisition <50ms (avg)
- ✅ Zero downtime during deployment

### 3.11.22.3 Business Success
- ✅ Document creation speed +30%
- ✅ Manual numbering errors -80%
- ✅ User satisfaction >4.5/5
- ✅ System stability >99.9%

---

## 3.11.23 References

- [Implementation Guide](../03-implementation/03-04-document-numbering.md)
- [Operations Guide](../04-operations/04-08-document-numbering-operations.md)
- [API Design](../02-architecture/02-02-api-design.md)
- [Data Dictionary](file:///d:/nap-dms.lcbp3/specs/04-data-dictionary/4_Data_Dictionary_V1_4_4.md)
- [ADR-018: Document Numbering Strategy](file:///d:/nap-dms.lcbp3/specs/05-decisions/adr-018-document-numbering.md)
- [Two-Phase Commit Pattern](https://en.wikipedia.org/wiki/Two-phase_commit_protocol)
- [Redlock Algorithm](https://redis.io/docs/manual/patterns/distributed-locks/)

---

## 3.11.24 Appendix

### 3.11.24.1 Glossary
- **Sequence**: ลำดับตัวเลขที่เพิ่มขึ้นอัตโนมัติ
- **Scope**: ขอบเขตที่ sequence แยกตาม (project, contract, etc.)
- **Token**: Format placeholder (e.g., {YYYY}, {SEQ})
- **Redlock**: Distributed locking algorithm สำหรับ Redis

---

**Approval Sign-off**:

| Role          | Name        | Date    | Signature |
| ------------- | ----------- | ------- | --------- |
| Product Owner | ___________ | _______ | _________ |
| Tech Lead     | ___________ | _______ | _________ |
| QA Lead       | ___________ | _______ | _________ |

**End of Document v1.6.2**
