# 3.11 Document Numbering Management (การจัดการเลขที่เอกสาร) --- title: 'Functional Requirements: Document Numbering Management' version: 1.6.0 status: draft owner: Nattanin Peancharoen last_updated: 2025-12-02 related: - specs/01-requirements/01-objectives.md - specs/01-requirements/02-architecture.md - specs/01-requirements/03-functional-requirements.md - specs/04-data-dictionary/4_Data_Dictionary_V1_4_4.md --- ## 3.11.1. วัตถุประสงค์ - ระบบต้องสามารถสร้างเลขที่เอกสาร (Running Number) ได้โดยอัตโนมัติและยืดหยุ่นสูง - ระบบต้องสามารถกำหนดรูปแบบ (template) เลขที่เอกสารได้ สำหรับแต่ละโครงการ, ชนิดเอกสาร, ประเภทเอกสาร - ระบบต้องรับประกัน Uniqueness ของเลขที่เอกสารในทุกสถานการณ์ - ระบบต้องรองรับการทำงานแบบ concurrent ได้อย่างปลอดภัย ## 3.11.2. Logic การนับเลข (Counter Logic) การนับเลขจะแยกตาม **Counter Key** ที่ประกอบด้วยหลายส่วน ขึ้นกับประเภทเอกสาร ### Counter Key Components | Component | Required? | Description | Database Source | Default if NULL | |-----------|-----------|-------------|-----------------|-----------------| | `project_id` | ✅ Yes | ID โครงการ | Derived from user context or organization | - | | `originator_organization_id` | ✅ Yes | ID องค์กรผู้ส่ง | `correspondences.originator_id` | - | | `recipient_organization_id` | Depends on type | ID องค์กรผู้รับหลัก (TO) | `correspondence_recipients` where `recipient_type = 'TO'` | NULL for RFA | | `correspondence_type_id` | ✅ Yes | ID ประเภทเอกสาร | `correspondence_types.id` | - | | `sub_type_id` | TRANSMITTAL only | ID ประเภทย่อย | `correspondence_sub_types.id` | 0 | | `rfa_type_id` | RFA only | ID ประเภท RFA | `rfa_types.id` | 0 | | `discipline_id` | RFA only | ID สาขางาน | `disciplines.id` | 0 | | `current_year` | ✅ Yes | ปี ค.ศ. | System year (ปัจจุบัน) | - | ### Counter Key แยกตามประเภทเอกสาร **LETTER / RFI / MEMO / EMAIL / MOM / INSTRUCTION / NOTICE / OTHER**: ``` (project_id, originator_organization_id, recipient_organization_id, correspondence_type_id, 0, 0, 0, current_year) ``` *หมายเหตุ*: ไม่ใช้ `discipline_id`, `sub_type_id`, `rfa_type_id` **TRANSMITTAL**: ``` (project_id, originator_organization_id, recipient_organization_id, correspondence_type_id, sub_type_id, 0, 0, current_year) ``` *หมายเหตุ*: ใช้ `sub_type_id` เพิ่มเติม **RFA**: ``` (project_id, originator_organization_id, NULL, correspondence_type_id, 0, rfa_type_id, discipline_id, current_year) ``` *หมายเหตุ*: RFA ไม่ใช้ `recipient_organization_id` เพราะเป็นเอกสารโครงการ (CONTRACTOR → CONSULTANT → OWNER) ### วิธีการหา 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 ### Fallback สำหรับค่า NULL - `discipline_id`: ใช้ `0` (ไม่ระบุสาขางาน) - `sub_type_id`: ใช้ `0` (ไม่มีประเภทย่อย) - `rfa_type_id`: ใช้ `0` (ไม่ระบุประเภท RFA) - `recipient_organization_id`: ใช้ `NULL` สำหรับ RFA, Required สำหรับ LETTER/TRANSMITTAL ## 3.11.3. Format Templates by Correspondence Type > **📝 หมายเหตุสำคัญ** > - Templates ด้านล่างเป็น **ตัวอย่าง** สำหรับประเภทเอกสารหลัก > - ระบบรองรับ **ทุกประเภทเอกสาร** ที่อยู่ใน `correspondence_types` table > - หากมีการเพิ่มประเภทใหม่ในอนาคต สามารถใช้งานได้โดยอัตโนมัติ > - Admin สามารถกำหนด Template เฉพาะสำหรับแต่ละประเภทผ่าน Admin Panel ### 3.11.3.1. Letter (TYPE = LETTER) **Template**: ``` {ORIGINATOR}-{RECIPIENT}-{SEQ:4}-{YEAR:B.E.} ``` **Example**: `คคง.-สคฉ.3-0001-2568` **Token Breakdown**: - `คคง.` = {ORIGINATOR} = รหัสองค์กรผู้ส่ง - `สคฉ.3` = {RECIPIENT} = รหัสองค์กรผู้รับหลัก (TO) - `0001` = {SEQ:4} = Running number (เริ่ม 0001, 0002, ...) - `2568` = {YEAR:B.E.} = ปี พ.ศ. > **⚠️ Template vs Counter Separation** > - {CORR_TYPE} **ไม่แสดง**ใน template เพื่อความกระชับ > - แต่ระบบ**ยังใช้ correspondence_type_id ใน Counter Key** เพื่อแยก counter > - LETTER, MEMO, RFI **มี counter แยกกัน** แม้ template format เหมือนกัน **Counter Key**: `(project_id, originator_org_id, recipient_org_id, corr_type_id, 0, 0, 0, year)` --- ### 3.11.3.2. Transmittal (TYPE = TRANSMITTAL) **Template**: ``` {ORIGINATOR}-{RECIPIENT}-{SUB_TYPE}-{SEQ:4}-{YEAR:B.E.} ``` **Example**: `คคง.-สคฉ.3-21-0117-2568` **Token Breakdown**: - `คคง.` = {ORIGINATOR} - `สคฉ.3` = {RECIPIENT} - `21` = {SUB_TYPE} = หมายเลขประเภทย่อย (11=MAT, 12=SHP, 13=DWG, 14=MET, ...) - `0117` = {SEQ:4} - `2568` = {YEAR:B.E.} > **⚠️ Template vs Counter Separation** > - {CORR_TYPE} **ไม่แสดง**ใน template (เหมือน LETTER) > - TRANSMITTAL มี counter แยกจาก LETTER **Counter Key**: `(project_id, originator_org_id, recipient_org_id, corr_type_id, sub_type_id, 0, 0, year)` --- ### 3.11.3.3. RFA (Request for Approval) **Template**: ``` {PROJECT}-{CORR_TYPE}-{DISCIPLINE}-{RFA_TYPE}-{SEQ:4}-{REV} ``` **Example**: `LCBP3-C2-RFA-TER-RPT-0001-A` **Token Breakdown**: - `LCBP3-C2` = {PROJECT} = รหัสโครงการ - `RFA` = {CORR_TYPE} = ประเภทเอกสาร (**แสดง**ใน RFA template) - `TER` = {DISCIPLINE} = รหัสสาขางาน (TER=Terminal, STR=Structure, ...) - `RPT` = {RFA_TYPE} = ประเภท RFA (RPT=Report, SDW=Shop Drawing, ...) - `0001` = {SEQ:4} - `A` = {REV} = Revision code > **📋 RFA Workflow** > - RFA เป็น **เอกสารโครงการ** (Project-level document) > - Workflow: **CONTRACTOR → CONSULTANT → OWNER** > - ไม่มี specific `recipient_id` เพราะเป็น workflow ที่กำหนดไว้แล้ว **Counter Key**: `(project_id, originator_org_id, NULL, corr_type_id, 0, rfa_type_id, discipline_id, year)` --- ### 3.11.3.4. Drawing **Status**: 🚧 **To Be Determined** Drawing Numbering ยังไม่ได้กำหนด Template เนื่องจาก: - มีความซับซ้อนสูง (Contract Drawing และ Shop Drawing มีกฎต่างกัน) - อาจต้องใช้ระบบ Numbering แยกต่างหาก - ต้องพิจารณาร่วมกับ RFA ที่เกี่ยวข้อง --- ### 3.11.3.5. Other Correspondence Types **Applicable to**: RFI, MEMO, EMAIL, MOM, INSTRUCTION, NOTICE, OTHER **Template**: ``` {ORIGINATOR}-{RECIPIENT}-{SEQ:4}-{YEAR:B.E.} ``` **Example (RFI)**: `คคง.-สคฉ.3-0042-2568` **Example (MEMO)**: `คคง.-ผรม.1-0001-2568` > **🔑 Counter Separation** > - แม้ template format **เหมือนกับ LETTER** > - แต่แต่ละ type มี **counter แยกกัน** ผ่าน `correspondence_type_id` > - RFI counter ≠ MEMO counter ≠ LETTER counter **Counter Key**: `(project_id, originator_org_id, recipient_org_id, corr_type_id, 0, 0, 0, year)` **หมายเหตุ**: ทุกประเภทที่ไม่ได้ระบุเฉพาะจะใช้ Template นี้ ถ้ามีการเพิ่ม correspondence type ใหม่ใน `correspondence_types` table จะใช้ 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` | | `{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` | ### Token Usage Notes **{SEQ:n}**: - `n` = จำนวนหลักที่ต้องการ (typically 4) - Counter **เริ่มจาก 0001** และเพิ่มทีละ 1 (0001, 0002, 0003, ...) - Padding ด้วย 0 ทางซ้าย - Reset ทุกปี (ตาม `current_year` ใน 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. กลไกความปลอดภัย (Concurrency Control) ### 3.11.6.1. Redis Distributed Lock - ใช้ Redis Distributed Lock เพื่อป้องกัน race condition - Lock key format: `lock:docnum:{project_id}:{doc_type_id}:{...counter_key_parts}` - Lock TTL: 5 วินาที (auto-release เมื่อ timeout) - Lock acquisition timeout: 10 วินาที ### 3.11.6.2. Optimistic Locking - ใช้ `version` column ในตาราง `document_number_configs` - ตรวจสอบ version ก่อน update counter - หาก version conflict เกิดขึ้น → retry transaction ### 3.11.6.3. Database Constraints - Unique constraint บน `document_number` column - Foreign key constraints เพื่อความสัมพันธ์ข้อมูล - Check constraints สำหรับ business rules ## 3.11.7. Retry Mechanism & Error Handling ### 3.11.7.1. Scenario 1: Redis Unavailable - **Fallback**: ใช้ database-only locking (pessimistic lock) - **Action**: - ใช้ `SELECT ... FOR UPDATE` แทน Redis lock - Log warning พร้อม alert ops team - ระบบยังใช้งานได้แต่ performance ลดลง ### 3.11.7.2. Scenario 2: Lock Acquisition Timeout - **Retry**: 5 ครั้งด้วย exponential backoff - Attempt 1: wait 1s - Attempt 2: wait 2s - Attempt 3: wait 4s - Attempt 4: wait 8s - Attempt 5: wait 16s (รวม ~31 วินาที) - **Failure**: Return HTTP 503 "Service Temporarily Unavailable" - **Frontend**: แสดงข้อความ "ระบบกำลังยุ่ง กรุณาลองใหม่ภายหลัง" ### 3.11.7.3. Scenario 3: Version Conflict After Lock - **Retry**: 2 ครั้ง (reload counter + retry transaction) - **Failure**: Log error พร้อม context และ return HTTP 409 Conflict - **Frontend**: แสดงข้อความ "เลขที่เอกสารถูกเปลี่ยน กรุณาลองใหม่" ### 3.11.7.4. Scenario 4: Database Connection Error - **Retry**: 3 ครั้งด้วย exponential backoff (1s, 2s, 4s) - **Failure**: Return HTTP 500 "Internal Server Error" - **Frontend**: แสดงข้อความ "เกิดข้อผิดพลาดในระบบ กรุณาติดต่อผู้ดูแลระบบ" ## 3.11.8. Configuration Management ### 3.11.8.1. Admin Panel Configuration - Project Admin สามารถกำหนด/แก้ไข template ผ่าน Admin Panel - การเปลี่ยนแปลง template จะไม่ส่งผลต่อเอกสารที่สร้างไว้แล้ว - ต้องมีการ validate template ก่อนบันทึก (ตรวจสอบ token ที่ใช้ถูกต้อง) ### 3.11.8.2. Template Versioning - เก็บ history ของ template changes - บันทึก user, timestamp, และเหตุผลในการเปลี่ยนแปลง - สามารถ rollback ไปเวอร์ชันก่อนหน้าได้ ### 3.11.8.3. Counter Reset Policy - Counter reset ตามปี (yearly reset) - Counter reset ตาม project phase (optional) - Admin สามารถ manual reset counter ได้ (require approval + audit log) ## 3.11.9. Audit Trail ### 3.11.9.1. การบันทึก Audit Log บันทึกทุกการ generate เลขที่เอกสารใน `document_number_audit` table: - `document_id` - เอกสารที่ถูกสร้าง - `generated_number` - เลขที่ถูกสร้าง - `counter_key` - key ที่ใช้ในการนับ - `template_used` - template ที่ใช้ - `user_id` - ผู้ที่ request - `ip_address` - IP address ของผู้ request - `timestamp` - เวลาที่สร้าง - `retry_count` - จำนวนครั้งที่ retry (ถ้ามี) ### 3.11.9.2. Conflict & Error Logging - บันทึก version conflicts และ กลไก retry ที่ใช้ - บันทึก lock timeouts และ failure reasons - บันทึก fallback scenarios (เช่น Redis unavailable) ## 3.11.10. Performance Requirements ### 3.11.10.1. Response Time - Document number generation ต้องเสร็จภายใน **2 วินาที** (95th percentile) - Document number generation ต้องเสร็จภายใน **5 วินาที** (99th percentile) - ในกรณี normal operation (ไม่มี retry) ควรเสร็จภายใน **500ms** ### 3.11.10.2. Throughput - ระบบรองรับ concurrent requests อย่างน้อย **50 requests/second** - Peak load รองรับได้ถึง **100 requests/second** (ช่วงเวลาเร่งงาน) ### 3.11.10.3. Availability - Uptime ≥ 99.5% (exclude planned maintenance) - Maximum downtime ต่อเดือน ≤ 3.6 ชั่วโมง ## 3.11.11. Monitoring & Alerting ### 3.11.11.1. Metrics to Monitor - Lock acquisition time (p50, p95, p99) - Lock acquisition failure rate - Counter generation latency - Retry count distribution - Redis connection status - Database connection pool usage ### 3.11.11.2. Alert Conditions - 🔴 **Critical**: Redis unavailable > 1 minute - 🔴 **Critical**: Lock acquisition failures > 10% in 5 minutes - 🟡 **Warning**: Lock acquisition failures > 5% in 5 minutes - 🟡 **Warning**: Average lock wait time > 1 second - 🟡 **Warning**: Retry count > 100 per hour ### 3.11.11.3. Dashboard - Real-time lock acquisition success rate - Lock wait time percentiles (p50, p95, p99) - Counter generation rate (per minute) - Error rate breakdown (by error type) - Redis/Database health status ## 3.11.12. API Reference เอกสารนี้อ้างอิงถึง API endpoints ต่อไปนี้ (รายละเอียดใน `specs/02-architecture/api-design.md`): - `POST /api/v1/documents/{documentId}/generate-number` - สร้างเลขที่เอกสาร - `GET /api/v1/document-numbering/configs` - ดูการตั้งค่า template - `PUT /api/v1/document-numbering/configs/{configId}` - แก้ไข template (Admin only) - `POST /api/v1/document-numbering/configs/{configId}/reset-counter` - Reset counter (Admin only) ## 3.11.13. Database Schema Reference เอกสารนี้อ้างอิงถึง tables ต่อไปนี้ (รายละเอียดใน `specs/04-data-dictionary/4_Data_Dictionary_V1_4_4.md`): - `document_number_configs` - เก็บ template และ counter configuration - `document_number_counters` - เก็บ current counter value - `document_number_audit` - เก็บ audit trail - `documents` - เก็บ document number ที่ถูกสร้าง ## 3.11.14. Database Schema Requirements ### 3.11.14.1. Counter Table Schema ตาราง `document_number_counters` ต้องมีโครงสร้างดังนี้: ```sql CREATE TABLE document_number_counters ( project_id INT NOT NULL, originator_organization_id INT NOT NULL, recipient_organization_id INT NULL, -- NULL for RFA correspondence_type_id INT NOT NULL, sub_type_id INT DEFAULT 0, -- for TRANSMITTAL rfa_type_id INT DEFAULT 0, -- for RFA discipline_id INT DEFAULT 0, -- for RFA current_year INT NOT NULL, version INT DEFAULT 0 NOT NULL, -- Optimistic Lock last_number INT DEFAULT 0, PRIMARY KEY ( project_id, originator_organization_id, COALESCE(recipient_organization_id, 0), correspondence_type_id, sub_type_id, rfa_type_id, discipline_id, current_year ), FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE, FOREIGN KEY (originator_organization_id) REFERENCES organizations(id) ON DELETE CASCADE, FOREIGN KEY (recipient_organization_id) REFERENCES organizations(id) ON DELETE CASCADE, FOREIGN KEY (correspondence_type_id) REFERENCES correspondence_types(id) ON DELETE CASCADE ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางเก็บ Running Number Counters'; ``` ### 3.11.14.2. Index Requirements ```sql -- Index สำหรับ Performance CREATE INDEX idx_counter_lookup ON document_number_counters ( project_id, correspondence_type_id, current_year ); -- Index สำหรับ Originator lookup CREATE INDEX idx_counter_org ON document_number_counters ( originator_organization_id, current_year ); ``` ### 3.11.14.3. Important Notes > **💡 Counter Key Design** > - ใช้ `COALESCE(recipient_organization_id, 0)` ใน Primary Key เพื่อรองรับ NULL > - `version` column สำหรับ Optimistic Locking (ป้องกัน race condition) > - `last_number` เริ่มจาก 0 และเพิ่มขึ้นทีละ 1 > - Counter reset ทุกปี (เมื่อ `current_year` เปลี่ยน) > **⚠️ Migration Notes** > - ไม่มีข้อมูลเก่า ไม่ต้องทำ backward compatibility > - สามารถสร้าง table ใหม่ได้เลยตาม schema ข้างต้น > - ต้องมี seed data สำหรับ `correspondence_types`, `rfa_types`, `disciplines` ก่อน ### 3.11.14.4. Example Counter Records ```sql -- Example: LETTER from คคง. to สคฉ.3 in LCBP3-C2 year 2025 INSERT INTO document_number_counters ( project_id, originator_organization_id, recipient_organization_id, correspondence_type_id, sub_type_id, rfa_type_id, discipline_id, current_year, version, last_number ) VALUES ( 2, -- LCBP3-C2 22, -- คคง. 10, -- สคฉ.3 6, -- LETTER 0, 0, 0, 2025, 0, 0 ); -- Example: RFA from ผรม.2 in LCBP3-C2, discipline TER, type RPT, year 2025 INSERT INTO document_number_counters ( project_id, originator_organization_id, recipient_organization_id, correspondence_type_id, sub_type_id, rfa_type_id, discipline_id, current_year, version, last_number ) VALUES ( 2, -- LCBP3-C2 42, -- ผรม.2 NULL, -- RFA ไม่มี specific recipient 1, -- RFA 0, 18, -- RPT (Report) 5, -- TER (Terminal) 2025, 0, 0 ); ``` ## 3.11.15. Security Considerations ### 3.11.14.1. Authorization - เฉพาะ authenticated users เท่านั้นที่สามารถ request document number - เฉพาะ Project Admin เท่านั้นที่แก้ไข template ได้ - เฉพาะ Super Admin เท่านั้นที่ reset counter ได้ ### 3.11.14.2. Rate Limiting - Limit ต่อ user: **10 requests/minute** (prevent abuse) - Limit ต่อ IP: **50 requests/minute** ### 3.11.14.3. Audit & Compliance - บันทึกทุก API call ที่เกี่ยวข้องกับ document numbering - เก็บ audit log อย่างน้อย **7 ปี** (ตาม พ.ร.บ. ข้อมูลอิเล็กทรอนิกส์)