From 79344ef4b19565a2915cf7cdff20a35d36f77224 Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 2 Dec 2025 17:29:42 +0700 Subject: [PATCH] 251202:1700 Prepare to version 1.6 --- docs/8_lcbp3_v1_4_5_seed.sql | 18 +- .../03.11-document-numbering.md | 363 ++++++++++++++---- ...03.11-document-numbering_schema_section.md | 103 +++++ 3 files changed, 407 insertions(+), 77 deletions(-) create mode 100644 specs/01-requirements/03.11-document-numbering_schema_section.md diff --git a/docs/8_lcbp3_v1_4_5_seed.sql b/docs/8_lcbp3_v1_4_5_seed.sql index 29083a8..ed3d37c 100644 --- a/docs/8_lcbp3_v1_4_5_seed.sql +++ b/docs/8_lcbp3_v1_4_5_seed.sql @@ -42,14 +42,14 @@ VALUES (1, 'กทท.', 'การท่าเรือแห่งประเ 'สคฉ.3-xx', 'ตรวจรับพัสดุ ที่ปรึกษาออกแบบ ส่วนที่ 4' ), -(21, 'TEAM', 'Designer Consulting Ltd.'), -(22, 'คคง.', 'Construction Supervision Ltd.'), -(41, 'ผรม.1', 'Contractor งานทางทะเล'), -(42, 'ผรม.2', 'Contractor อาคารและระบบ'), -(43, 'ผรม.3', 'Contractor #3 Ltd.'), -(44, 'ผรม.4', 'Contractor #4 Ltd.'), -(31, 'EN', 'Third Party Environment'), -(32, 'CAR', 'Third Party Fishery Care'); + (21, 'TEAM', 'Designer Consulting Ltd.'), + (22, 'คคง.', 'Construction Supervision Ltd.'), + (41, 'ผรม.1', 'Contractor งานทางทะเล'), + (42, 'ผรม.2', 'Contractor อาคารและระบบ'), + (43, 'ผรม.3', 'Contractor #3 Ltd.'), + (44, 'ผรม.4', 'Contractor #4 Ltd.'), + (31, 'EN', 'Third Party Environment'), + (32, 'CAR', 'Third Party Fishery Care'); -- Seed project INSERT INTO projects (project_code, project_name) VALUES ( @@ -156,7 +156,7 @@ VALUES ( ); -- Seed user -- Initial SUPER_ADMIN user -INSERT INTO `users` ( +INSERT INTO users ( `user_id`, `username`, `password_hash`, diff --git a/specs/01-requirements/03.11-document-numbering.md b/specs/01-requirements/03.11-document-numbering.md index 72b7c8c..51f243c 100644 --- a/specs/01-requirements/03.11-document-numbering.md +++ b/specs/01-requirements/03.11-document-numbering.md @@ -2,8 +2,8 @@ --- title: 'Functional Requirements: Document Numbering Management' -version: 1.5.0 -status: first-draft +version: 1.6.0 +status: draft owner: Nattanin Peancharoen last_updated: 2025-12-02 related: @@ -22,104 +22,227 @@ related: ## 3.11.2. Logic การนับเลข (Counter Logic) -การนับเลขจะแยกตาม **Counter Key** ที่ประกอบด้วย: +การนับเลขจะแยกตาม **Counter Key** ที่ประกอบด้วยหลายส่วน ขึ้นกับประเภทเอกสาร -- `project_id` - รหัสโครงการ -- `doc_type_id` - ชนิดเอกสาร (Correspondence, RFA, Transmittal, Drawing) -- `sub_type_id` - ประเภทย่อยของเอกสาร (nullable) -- `discipline_id` - สาขาวิชา/งาน (nullable) -- `year` - ปี พ.ศ. หรือ ค.ศ. ตามที่กำหนดใน template +### Counter Key Components -### ตัวอย่าง Counter Key +| 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 (ปัจจุบัน) | - | -```text -Correspondence: project_id + doc_type_id + sub_type_id + year -RFA: project_id + doc_type_id + discipline_id + year -Transmittal: project_id + doc_type_id + recipient_type + year -Drawing: project_id + doc_type_id + discipline_id + 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` หรือ `sub_type_id` เป็น NULL ให้ใช้ค่า Default `0` ในการจัดกลุ่ม Counter -- ป้องกัน Error และรับประกันความถูกต้องของ Running Number (Uniqueness Guarantee) +- `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 Document Type -ระบบรองรับการกำหนดรูปแบบด้วย **Token Replacement** +## 3.11.3. Format Templates by Correspondence Type -### 3.11.3.1. Correspondence (หนังสือราชการ) +> **📝 หมายเหตุสำคัญ** +> - Templates ด้านล่างเป็น **ตัวอย่าง** สำหรับประเภทเอกสารหลัก +> - ระบบรองรับ **ทุกประเภทเอกสาร** ที่อยู่ใน `correspondence_types` table +> - หากมีการเพิ่มประเภทใหม่ในอนาคต สามารถใช้งานได้โดยอัตโนมัติ +> - Admin สามารถกำหนด Template เฉพาะสำหรับแต่ละประเภทผ่าน Admin Panel -#### Letter Type (TYPE = 03) +### 3.11.3.1. Letter (TYPE = LETTER) -- **Template**: `{ORG}-{ORG}-{TYPE}-{SEQ:4}-{YEAR:B.E.}` -- **Example**: `คคง.-สคฉ.3-0985-2568` -- **Counter Key**: `project_id + doc_type_id + sub_type_id + year` +**Template**: +``` +{ORIGINATOR}-{RECIPIENT}-{SEQ:4}-{YEAR:B.E.} +``` -#### Other Correspondence Types +**Example**: `คคง.-สคฉ.3-0001-2568` -- **Template**: `{ORG}-{ORG}-{TYPE}-{SEQ:4}-{YEAR:B.E.}` -- **Example**: `คคง.-สคฉ.3-STR-0001-2568` -- **Counter Key**: `project_id + doc_type_id + sub_type_id + year` +**Token Breakdown**: +- `คคง.` = {ORIGINATOR} = รหัสองค์กรผู้ส่ง +- `สคฉ.3` = {RECIPIENT} = รหัสองค์กรผู้รับหลัก (TO) +- `0001` = {SEQ:4} = Running number (เริ่ม 0001, 0002, ...) +- `2568` = {YEAR:B.E.} = ปี พ.ศ. -### 3.11.3.2. Transmittal +> **⚠️ Template vs Counter Separation** +> - {CORR_TYPE} **ไม่แสดง**ใน template เพื่อความกระชับ +> - แต่ระบบ**ยังใช้ correspondence_type_id ใน Counter Key** เพื่อแยก counter +> - LETTER, MEMO, RFI **มี counter แยกกัน** แม้ template format เหมือนกัน -#### Transmittal to Owner +**Counter Key**: `(project_id, originator_org_id, recipient_org_id, corr_type_id, 0, 0, 0, year)` -- **Template**: `{ORG}-{ORG}-{TYPE}-{SUB_TYPE}-{SEQ:4}-{YEAR:B.E.}` -- **Example**: `คคง.-สคฉ.3-03-21-0117-2568` -- **Counter Key**: `project_id + doc_type_id + recipient_type + year` -- **Note**: `recipient_type = 'OWNER'` +--- -#### Transmittal to Contractor/Others +### 3.11.3.2. Transmittal (TYPE = TRANSMITTAL) -- **Template**: `{ORG}-{ORG}-{TYPE}-{SEQ:4}-{YEAR:B.E.}` -- **Example**: `ผรม.2-คคง.-0117-2568` -- **Counter Key**: `project_id + doc_type_id + recipient_type + year` -- **Note**: `recipient_type = 'CONTRACTOR' | 'CONSULTANT' | 'OTHER'` +**Template**: +``` +{ORIGINATOR}-{RECIPIENT}-{SUB_TYPE}-{SEQ:4}-{YEAR:B.E.} +``` -#### Alternative Project-based Format +**Example**: `คคง.-สคฉ.3-21-0117-2568` -- **Template**: `{PROJECT}-{ORG}-{TYPE}-{DISCIPLINE}-{SEQ:4}-{REV}` -- **Example**: `LCBP3-TR-STR-0001-A` -- **Counter Key**: `project_id + doc_type_id + discipline_id + year` +**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}-{ORG}-{TYPE}-{DISCIPLINE}-{SEQ:4}-{REV}` -- **Example**: `LCBP3-C2-RFI-ROW-0029-A` -- **Counter Key**: `project_id + doc_type_id + discipline_id + year` -- **Note**: `{REV}` คือ revision code (A, B, C, ..., AA, AB, ...) +**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 -- **Template**: `{PROJECT}-{DISCIPLINE}-{CATEGORY}-{SEQ:4}-{REV}` -- **Example**: `LCBP3-STR-DRW-0001-A` -- **Counter Key**: `project_id + doc_type_id + discipline_id + category + year` +**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 | -|-------|-------------|---------| -| `{PROJECT}` | รหัสโครงการ | `LCBP3` | -| `{ORG}` | รหัสหน่วยงาน | `คคง.`, `สคฉ.3` | -| `{TYPE}` | รหัสชนิดเอกสาร | `RFI`, `03` | -| `{SUB_TYPE}` | รหัสประเภทย่อย | `21` | -| `{DISCIPLINE}` | รหัสสาขาวิชา | `STR`, `ROW` | -| `{CATEGORY}` | หมวดหมู่ | `DRW` | -| `{SEQ:n}` | Running number (n = จำนวนหลัก) | `0001`, `0029` | -| `{YEAR:B.E.}` | ปี พ.ศ. | `2568` | -| `{YEAR:A.D.}` | ปี ค.ศ. | `2025` | -| `{REV}` | Revision Code | `A`, `B`, `AA` | +| 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` | -## 3.11.5. Transmittal Special Logic +### Token Usage Notes -- Transmittal มีเงื่อนไขพิเศษที่เลขอาจเปลี่ยนตามผู้รับ: - - **To Owner**: ใช้ format พิเศษที่มี sub_type รหัสโครงการ - - **To Contractor/Others**: ใช้ format ทั่วไป -- Counter Key จะแยกตาม `recipient_type` เพื่อให้แต่ละประเภทมี running number อิสระ +**{SEQ:n}**: +- `n` = จำนวนหลักที่ต้องการ (typically 4) +- Counter **เริ่มจาก 0001** และเพิ่มทีละ 1 (0001, 0002, 0003, ...) +- Padding ด้วย 0 ทางซ้าย +- Reset ทุกปี (ตาม `current_year` ใน Counter Key) -## 3.11.6. กลไกความปลอดภัย (Concurrency Control) +**{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 @@ -277,7 +400,111 @@ Drawing: project_id + doc_type_id + discipline_id + year - `document_number_audit` - เก็บ audit trail - `documents` - เก็บ document number ที่ถูกสร้าง -## 3.11.14. Security Considerations +## 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 diff --git a/specs/01-requirements/03.11-document-numbering_schema_section.md b/specs/01-requirements/03.11-document-numbering_schema_section.md new file mode 100644 index 0000000..21d6dc6 --- /dev/null +++ b/specs/01-requirements/03.11-document-numbering_schema_section.md @@ -0,0 +1,103 @@ +## 3.11.15. Database Schema Requirements + +### 3.11.15.1. Counter Table Schema + +ตาราง `document_number_counters` ต้องมีโครงสร้างดังนี้: + +```sql +CREATE TABLE document_number_counters ( + project_id INT NOT NULL, + originator_organization_id INT NOT NULL, + recipient_organization_id INT NULL, -- NULL for RFA + correspondence_type_id INT NOT NULL, + sub_type_id INT DEFAULT 0, -- for TRANSMITTAL + rfa_type_id INT DEFAULT 0, -- for RFA + discipline_id INT DEFAULT 0, -- for RFA + current_year INT NOT NULL, + version INT DEFAULT 0 NOT NULL, -- Optimistic Lock + last_number INT DEFAULT 0, + + PRIMARY KEY ( + project_id, + originator_organization_id, + COALESCE(recipient_organization_id, 0), + correspondence_type_id, + sub_type_id, + rfa_type_id, + discipline_id, + current_year + ), + + FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY (originator_organization_id) REFERENCES organizations(id) ON DELETE CASCADE, + FOREIGN KEY (recipient_organization_id) REFERENCES organizations(id) ON DELETE CASCADE, + FOREIGN KEY (correspondence_type_id) REFERENCES correspondence_types(id) ON DELETE CASCADE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci + COMMENT = 'ตารางเก็บ Running Number Counters'; +``` + +### 3.11.15.2. Index Requirements + +```sql +-- Index สำหรับ Performance +CREATE INDEX idx_counter_lookup +ON document_number_counters ( + project_id, + correspondence_type_id, + current_year +); + +-- Index สำหรับ Originator lookup +CREATE INDEX idx_counter_org +ON document_number_counters ( + originator_organization_id, + current_year +); +``` + +### 3.11.15.3. Important Notes + +> **💡 Counter Key Design** +> - ใช้ `COALESCE(recipient_organization_id, 0)` ใน Primary Key เพื่อรองรับ NULL +> - `version` column สำหรับ Optimistic Locking (ป้องกัน race condition) +> - `last_number` เริ่มจาก 0 และเพิ่มขึ้นทีละ 1 +> - Counter reset ทุกปี (เมื่อ `current_year` เปลี่ยน) + +> **⚠️ Migration Notes** +> - ไม่มีข้อมูลเก่า ไม่ต้องทำ backward compatibility +> - สามารถสร้าง table ใหม่ได้เลยตาม schema ข้างต้น +> - ต้องมี seed data สำหรับ `correspondence_types`, `rfa_types`, `disciplines` ก่อน + +### 3.11.15.4. Example Counter Records + +```sql +-- Example: LETTER from คคง. to สคฉ.3 in LCBP3-C2 year 2025 +INSERT INTO document_number_counters ( + project_id, originator_organization_id, recipient_organization_id, + correspondence_type_id, sub_type_id, rfa_type_id, discipline_id, + current_year, version, last_number +) VALUES ( + 2, -- LCBP3-C2 + 22, -- คคง. + 10, -- สคฉ.3 + 6, -- LETTER + 0, 0, 0, + 2025, 0, 0 +); + +-- Example: RFA from ผรม.2 in LCBP3-C2, discipline TER, type RPT, year 2025 +INSERT INTO document_number_counters ( + project_id, originator_organization_id, recipient_organization_id, + correspondence_type_id, sub_type_id, rfa_type_id, discipline_id, + current_year, version, last_number +) VALUES ( + 2, -- LCBP3-C2 + 42, -- ผรม.2 + NULL, -- RFA ไม่มี specific recipient + 1, -- RFA + 0, + 18, -- RPT (Report) + 5, -- TER (Terminal) + 2025, 0, 0 +); +```