251215:1719 Docunment Number Rule not correct
Some checks failed
Spec Validation / validate-markdown (push) Has been cancelled
Spec Validation / validate-diagrams (push) Has been cancelled
Spec Validation / check-todos (push) Has been cancelled

This commit is contained in:
admin
2025-12-15 17:19:40 +07:00
parent ec35521258
commit 78370fb590
23 changed files with 1461 additions and 1609 deletions

View File

@@ -0,0 +1,53 @@
# Refactoring Document Numbering เพิ่ม features ให้กับ Document Numbering เพื่อรองรับหลักการ Immutability, Audit Trail และ Advanced Operations
Template Management ต้องคงหน้านี้ไว้ (ไม่มีการปรับปรุง)
## รายละเอียด
### 1. ปรับปรุง Logic การออกเลขให้เป็นแบบ Assign Once
* 1.1 ตรวจสอบให้แน่ใจว่า `generateNextNumber` จะถูกเรียกเฉพาะตอน Create (POST) เท่านั้น
* 1.2 ห้ามเรียกตอน Update (PATCH/PUT) ยกเว้นกรณีมีการเปลี่ยนค่าสำคัญ (Project, Type, Discipline, Recipient) ในสถานะ Draft เท่านั้น หากค่าเหล่านี้ไม่เปลี่ยน **ต้อง** ใช้เลขเดิมเสมอ
* 1.3 ในกรณีที่มีการเปลี่ยนค่าสำคัญ (Project, Type, Discipline, Recipient) ถ้ายังไม่ได้ออกเลขถัดไป ต้องคืนเลขเดิม (-1 counter) ถ้าออกเลขถัดไปแล้ว ให้บันทึกเลขนี้ เป็น void_replace
* 1.4 ใช้ **Redlock** (Redis Distributed Lock) คลุม Logic การดึงและอัปเดต Counter ร่วมกับ **Optimistic Locking** (Version column) ใน Database
* 1.5 **Audit Logging:** แก้ไขฟังก์ชัน `logAudit` ให้บันทึก `operation` type (reserve, confirm, manual_override, void_replace) ให้ครบถ้วน
* 1.6 **Implement New Methods:**
* `manualOverride()`: บันทึกเลขและขยับ Counter ถ้าเลขมากกว่าปัจจุบัน
* `NumberingMetrics`: Interface สำหรับ Monitoring Dashboard
* `cancelNumber()`: บันทึก Audit ว่ายกเลิก (Skip) โดยไม่นำกลับมาใช้ใหม่
* `voidAndReplace()`: ออกเลขใหม่ให้เอกสารเดิม และบันทึกความเชื่อมโยง
* `bulkImport()`: สำหรับนำเข้าข้อมูลและตั้งค่า Counter เริ่มต้น
* `confirmNumber()`: บันทึกเลขและขยับ Counter ถ้าเลขมากกว่าปัจจุบัน
* `audit()`: บันทึก Audit ว่ายกเลิก (Skip) โดยไม่นำกลับมาใช้ใหม่
### 2. เพิ่มฟีเจอร์สำหรับ Admin
* 2.1 เพิ่ม Endpoints สำหรับ Admin (ควรติด Guard `RequirePermission`)
* 2.2 `GET /admin/document-numbering/metrics`
* 2.3 `POST /admin/document-numbering/manual-override`
* 2.4 `POST /admin/document-numbering/bulk-import`
* 2.5 `POST /admin/document-numbering/void-and-replace`
* 2.6 `POST /admin/document-numbering/cancel-number`
* 2.7 `POST /admin/document-numbering/confirm-number`
* 2.8 `POST /admin/document-numbering/audit`
* 2.9 `POST /admin/document-numbering/audit`
### 3. ปรับปรุง UI เพื่อป้องกัน User แก้ไขเลขที่เอกสาร
* 3.1 แสดง "Auto Generated" หรือ Preview เลขที่เอกสาร (ถ้ามี)
* 3.2 ช่อง `Document No` ต้องเป็น **Read-Only** หรือ **Disabled** เสมอ User เห็นแต่แก้ไม่ได้
* 3.3 **API Integration:** ตัดการส่ง field `documentNumber` กลับไปหา Backend ในหน้า Edit เพื่อป้องกันการเขียนทับโดยบังเอิญ
### 4. ปรับปรุง Database เพื่อรองรับฟีเจอร์ใหม่
* 4.1 Schema Update* ตรวจสอบตาราง `document_number_audit` ว่ามีคอลัมน์รองรับ `operation` (Enum) และ `metadata` (JSON) หรือไม่ หากไม่มีให้สร้าง Migration file
* 4.2 Data Seeding / Migration* ใช้ `BulkImportDto` ในการเขียน Script ดึงข้อมูลเลขที่เอกสารล่าสุดจากระบบเก่า
* 4.2.1 รัน Script ผ่าน Endpoint `bulk-import` เพื่อให้ระบบคำนวณและตั้งค่า `Last Number` ของแต่ละ Series ให้ถูกต้องทันทีที่ขึ้นระบบใหม่
### 5. Frontend Implementation (UI/UX)เป้าหมาย: ป้องกัน User แก้ไขเลขที่เอกสาร และสร้างเครื่องมือให้ Admin
### 5.1 User Mode (Create/Edit Forms)* **Create Mode:** แสดง "Auto Generated" หรือ Preview เลขที่เอกสาร (ถ้ามี)
* **Edit Mode (Strict Rule):** ช่อง `Document No` ต้องเป็น **Read-Only** หรือ **Disabled** เสมอ User เห็นแต่แก้ไม่ได้
* **API Integration:** ตัดการส่ง field `documentNumber` กลับไปหา Backend ในหน้า Edit เพื่อป้องกันการเขียนทับโดยบังเอิญ
### 5.2 Admin Dashboard (Monitoring & Tools)* **Numbering Dashboard:**
* Template Management: ต้องคงหน้านี้ไว้ ทำให้เป็นหน้าแรก ของ Numbering Dashboard
* สร้างหน้ากราฟแสดง `sequence_utilization` และ `failed_lock_attempts` จาก API Metrics ทำให้เป็น เมนูย่อย ของ Numbering Dashboard
* Management Tools: สร้าง Modal หรือ Form สำหรับ:
* Manual Override: กรณีต้องออกเลขย้อนหลังหรือเลขพิเศษ: ทำให้เป็น เมนูย่อย ของ Numbering Dashboard
* Void/Replace: ปุ่มกดเพื่อ Void เอกสารและออกเลขใหม่: ทำให้เป็น เมนูย่อย ของ Numbering Dashboard

View File

@@ -456,11 +456,14 @@ CREATE TABLE correspondences (
deleted_at DATETIME NULL COMMENT 'สำหรับ Soft Delete',
FOREIGN KEY (correspondence_type_id) REFERENCES correspondence_types (id) ON DELETE RESTRICT,
FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE,
FOREIGN KEY (originator_id) REFERENCES organizations (id) ON DELETE SET NULL,
FOREIGN KEY (created_by) REFERENCES users (user_id) ON DELETE SET NULL,
-- Foreign Key ที่รวมเข้ามาจาก ALTER (ระบุชื่อ Constraint ตามที่ต้องการ)
CONSTRAINT fk_corr_discipline FOREIGN KEY (discipline_id) REFERENCES disciplines (id) ON DELETE SET NULL,
UNIQUE KEY uq_corr_no_per_project (project_id, correspondence_number)
FOREIGN KEY (originator_id) REFERENCES organizations (id) ON DELETE
SET NULL,
FOREIGN KEY (created_by) REFERENCES users (user_id) ON DELETE
SET NULL,
-- Foreign Key ที่รวมเข้ามาจาก ALTER (ระบุชื่อ Constraint ตามที่ต้องการ)
CONSTRAINT fk_corr_discipline FOREIGN KEY (discipline_id) REFERENCES disciplines (id) ON DELETE
SET NULL,
UNIQUE KEY uq_corr_no_per_project (project_id, correspondence_number)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง "แม่" ของเอกสารโต้ตอบ เก็บข้อมูลที่ไม่เปลี่ยนตาม Revision';
-- ตารางเชื่อมผู้รับ (TO/CC) สำหรับเอกสารแต่ละฉบับ (M:N)
@@ -480,51 +483,46 @@ CREATE TABLE correspondence_recipients (
-- ตาราง "ลูก" เก็บประวัติการแก้ไข (Revisions) ของ correspondences (1:N)
CREATE TABLE correspondence_revisions (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของ Revision',
correspondence_id INT NOT NULL COMMENT 'Master ID',
revision_number INT NOT NULL COMMENT 'หมายเลข Revision (0, 1, 2...)',
revision_label VARCHAR(10) COMMENT 'Revision ที่แสดง (เช่น A, B, 1.1)',
is_current BOOLEAN DEFAULT FALSE COMMENT '(1 = Revision ปัจจุบัน)',
-- ข้อมูลเนื้อหาที่เปลี่ยนได้
correspondence_status_id INT NOT NULL COMMENT 'สถานะของ Revision นี้',
subject VARCHAR(500) NOT NULL COMMENT 'หัวข้อเรื่อง',
description TEXT COMMENT 'คำอธิบายการแก้ไขใน Revision นี้',
body TEXT NULL COMMENT 'เนื้อความ (ถ้ามี)',
remarks TEXT COMMENT 'หมายเหตุ',
document_date DATE COMMENT 'วันที่ในเอกสาร',
issued_date DATETIME COMMENT 'วันที่ออกเอกสาร',
received_date DATETIME COMMENT 'วันที่ลงรับเอกสาร',
due_date DATETIME COMMENT 'วันที่ครบกำหนด',
-- Standard Meta Columns
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้างเอกสาร',
created_by INT COMMENT 'ผู้สร้าง',
updated_by INT COMMENT 'ผู้แก้ไขล่าสุด',
-- ส่วนของ JSON และ Schema Version
details JSON COMMENT 'ข้อมูลเฉพาะ (เช่น LETTET details)',
schema_version INT DEFAULT 1 COMMENT 'เวอร์ชันของ Schema ที่ใช้กับ details',
-- Generated Virtual Columns (ดึงค่าจาก JSON โดยอัตโนมัติ)
v_ref_project_id INT GENERATED ALWAYS AS (
CAST(JSON_UNQUOTE(JSON_EXTRACT(details, '$.projectId')) AS UNSIGNED)
CAST(
JSON_UNQUOTE(JSON_EXTRACT(details, '$.projectId')) AS UNSIGNED
)
) VIRTUAL COMMENT 'Virtual Column: Project ID จาก JSON',
v_doc_subtype VARCHAR(50) GENERATED ALWAYS AS (
JSON_UNQUOTE(JSON_EXTRACT(details, '$.subType'))
) VIRTUAL COMMENT 'Virtual Column: Document Subtype จาก JSON',
FOREIGN KEY (correspondence_id) REFERENCES correspondences (id) ON DELETE CASCADE,
FOREIGN KEY (correspondence_status_id) REFERENCES correspondence_status (id) ON DELETE RESTRICT,
FOREIGN KEY (created_by) REFERENCES users (user_id) ON DELETE SET NULL,
FOREIGN KEY (updated_by) REFERENCES users (user_id) ON DELETE SET NULL,
UNIQUE KEY uq_master_revision_number (correspondence_id, revision_number ),
UNIQUE KEY uq_master_current (correspondence_id, is_current),
INDEX idx_corr_rev_v_project (v_ref_project_id),
INDEX idx_corr_rev_v_subtype (v_doc_subtype)
FOREIGN KEY (created_by) REFERENCES users (user_id) ON DELETE
SET NULL,
FOREIGN KEY (updated_by) REFERENCES users (user_id) ON DELETE
SET NULL,
UNIQUE KEY uq_master_revision_number (correspondence_id, revision_number),
UNIQUE KEY uq_master_current (correspondence_id, is_current),
INDEX idx_corr_rev_v_project (v_ref_project_id),
INDEX idx_corr_rev_v_subtype (v_doc_subtype)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง "ลูก" เก็บประวัติการแก้ไข (Revisions) ของ correspondences (1 :N)';
-- ตาราง Master เก็บ Tags ทั้งหมดที่ใช้ในระบบ
@@ -599,27 +597,27 @@ CREATE TABLE rfa_approve_codes (
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master สำหรับรหัสผลการอนุมัติ RFA';
CREATE TABLE rfas (
id INT PRIMARY KEY COMMENT 'ID ของตาราง (RFA Master ID)', -- ❌ ไม่มี AUTO_INCREMENT
id INT PRIMARY KEY COMMENT 'ID ของตาราง (RFA Master ID)',
-- ❌ ไม่มี AUTO_INCREMENT
rfa_type_id INT NOT NULL COMMENT 'ประเภท RFA',
-- discipline_id INT NULL COMMENT 'สาขางาน (ถ้ามี)',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง',
created_by INT COMMENT 'ผู้สร้าง',
deleted_at DATETIME NULL COMMENT 'สำหรับ Soft Delete',
FOREIGN KEY (rfa_type_id) REFERENCES rfa_types (id),
FOREIGN KEY (created_by) REFERENCES users (user_id) ON DELETE SET NULL,
-- CONSTRAINT fk_rfa_discipline FOREIGN KEY (discipline_id) REFERENCES disciplines (id) ON DELETE SET NULL,
CONSTRAINT fk_rfas_parent FOREIGN KEY (id) REFERENCES correspondences (id) ON DELETE CASCADE
FOREIGN KEY (created_by) REFERENCES users (user_id) ON DELETE
SET NULL,
-- CONSTRAINT fk_rfa_discipline FOREIGN KEY (discipline_id) REFERENCES disciplines (id) ON DELETE SET NULL,
CONSTRAINT fk_rfas_parent FOREIGN KEY (id) REFERENCES correspondences (id) ON DELETE CASCADE
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง "แม่" ของ RFA (มีความสัมพันธ์ 1 :N กับ rfa_revisions)';
-- ตาราง "ลูก" เก็บประวัติ (Revisions) ของ rfas (1:N)
CREATE TABLE rfa_revisions (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของ Revision',
rfa_id INT NOT NULL COMMENT 'Master ID ของ RFA',
revision_number INT NOT NULL COMMENT 'หมายเลข Revision (0, 1, 2...)',
revision_label VARCHAR(10) COMMENT 'Revision ที่แสดง (เช่น A, B, 1.1)',
is_current BOOLEAN DEFAULT FALSE COMMENT '(1 = Revision ปัจจุบัน)',
-- ข้อมูลเฉพาะของ RFA Revision ที่ซับซ้อน
rfa_status_code_id INT NOT NULL COMMENT 'สถานะ RFA',
rfa_approve_code_id INT COMMENT 'ผลการอนุมัติ',
@@ -627,36 +625,34 @@ CREATE TABLE rfa_revisions (
description TEXT COMMENT 'คำอธิบายการแก้ไขใน Revision นี้',
body TEXT NULL COMMENT 'เนื้อความ (ถ้ามี)',
remarks TEXT COMMENT 'หมายเหตุ',
document_date DATE COMMENT 'วันที่ในเอกสาร',
issued_date DATE COMMENT 'วันที่ส่งขออนุมัติ',
received_date DATETIME COMMENT 'วันที่ลงรับเอกสาร',
due_date DATETIME COMMENT 'วันที่ครบกำหนด',
approved_date DATE COMMENT 'วันที่อนุมัติ',
-- Standard Meta Columns
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้างเอกสาร',
created_by INT COMMENT 'ผู้สร้าง',
updated_by INT COMMENT 'ผู้แก้ไขล่าสุด',
-- ส่วนของ JSON และ Schema Version
details JSON NULL COMMENT 'RFA Specific Details',
schema_version INT DEFAULT 1 COMMENT 'Version ของ JSON Schema',
-- Generated Virtual Columns (ดึงค่าจาก JSON โดยอัตโนมัติ)
v_ref_drawing_count INT GENERATED ALWAYS AS (
JSON_UNQUOTE(
JSON_EXTRACT(details, '$.drawingCount')
)
) VIRTUAL,
FOREIGN KEY (rfa_id) REFERENCES rfas (id) ON DELETE CASCADE,
FOREIGN KEY (rfa_status_code_id) REFERENCES rfa_status_codes (id),
FOREIGN KEY (rfa_approve_code_id) REFERENCES rfa_approve_codes (id) ON DELETE SET NULL,
FOREIGN KEY (created_by) REFERENCES users (user_id) ON DELETE SET NULL,
FOREIGN KEY (updated_by) REFERENCES users (user_id) ON DELETE SET NULL,
UNIQUE KEY uq_rr_rev_number (rfa_id, revision_number),
UNIQUE KEY uq_rr_current (rfa_id, is_current)
FOREIGN KEY (rfa_approve_code_id) REFERENCES rfa_approve_codes (id) ON DELETE
SET NULL,
FOREIGN KEY (created_by) REFERENCES users (user_id) ON DELETE
SET NULL,
FOREIGN KEY (updated_by) REFERENCES users (user_id) ON DELETE
SET NULL,
UNIQUE KEY uq_rr_rev_number (rfa_id, revision_number),
UNIQUE KEY uq_rr_current (rfa_id, is_current)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง "ลูก" เก็บประวัติ (Revisions) ของ rfas (1 :N)';
-- ตารางเชื่อมระหว่าง rfa_revisions (ที่เป็นประเภท DWG) กับ shop_drawing_revisions (M:N)
@@ -968,15 +964,21 @@ CREATE TABLE document_number_formats (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง',
project_id INT NOT NULL COMMENT 'โครงการ',
correspondence_type_id INT NOT NULL COMMENT 'ประเภทเอกสาร',
format_template VARCHAR(255) NOT NULL COMMENT 'รูปแบบ Template (เช่น { ORG_CODE } - { TYPE_CODE } - { SEQ :4 })',
discipline_id INT DEFAULT 0 COMMENT 'สาขางาน (0 = ทุกสาขา/ไม่ระบุ)',
format_template VARCHAR(255) NOT NULL COMMENT 'รูปแบบ Template (เช่น {ORG_CODE}-{TYPE_CODE}-{SEQ:4})',
example_number VARCHAR(100) COMMENT 'ตัวอย่างเลขที่ได้จาก Template',
padding_length INT DEFAULT 4 COMMENT 'ความยาวของลำดับเลข (Padding)',
reset_annually BOOLEAN DEFAULT TRUE COMMENT 'เริ่มนับใหม่ทุกปี',
is_active BOOLEAN DEFAULT TRUE COMMENT 'สถานะการใช้งาน',
description TEXT COMMENT 'คำอธิบายรูปแบบนี้',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'วันที่แก้ไขล่าสุด',
FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE,
FOREIGN KEY (correspondence_type_id) REFERENCES correspondence_types (id) ON DELETE CASCADE,
UNIQUE KEY uk_project_type (
UNIQUE KEY uk_proj_type_disc (
project_id,
correspondence_type_id
correspondence_type_id,
discipline_id
)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master เก็บ "รูปแบบ" Template ของเลขที่เอกสาร';
@@ -1061,7 +1063,15 @@ CREATE TABLE document_number_audit (
-- Document Info
document_id INT NOT NULL COMMENT 'ID ของเอกสารที่สร้างเลขที่ (correspondences.id)',
generated_number VARCHAR(100) NOT NULL COMMENT 'เลขที่เอกสารที่สร้าง (ผลลัพธ์)',
operation ENUM(
'RESERVE',
'CONFIRM',
'MANUAL_OVERRIDE',
'VOID_REPLACE',
'CANCEL'
) NOT NULL DEFAULT 'CONFIRM' COMMENT 'ประเภทการดำเนินการ',
counter_key JSON NOT NULL COMMENT 'Counter key ที่ใช้ (JSON format) - 8 fields',
metadata JSON COMMENT 'Additional context data',
template_used VARCHAR(200) NOT NULL COMMENT 'Template ที่ใช้ในการสร้าง',
-- User Info
user_id INT NOT NULL COMMENT 'ผู้ขอสร้างเลขที่',
@@ -1507,15 +1517,16 @@ WHERE cr.is_current = TRUE
-- View แสดง Revision "ปัจจุบัน" ของ rfa_revisions ทั้งหมด
CREATE VIEW v_current_rfas AS
SELECT
r.id AS rfa_id,
SELECT r.id AS rfa_id,
r.rfa_type_id,
rt.type_code AS rfa_type_code,
rt.type_name_th AS rfa_type_name_th,
rt.type_name_en AS rfa_type_name_en,
c.correspondence_number,
c.discipline_id, -- ✅ ดึงจาก Correspondences
d.discipline_code, -- ✅ Join เพิ่มเพื่อแสดง code
c.discipline_id,
-- ✅ ดึงจาก Correspondences
d.discipline_code,
-- ✅ Join เพิ่มเพื่อแสดง code
c.project_id,
p.project_code,
p.project_name,
@@ -1540,10 +1551,8 @@ SELECT
rr.created_at AS revision_created_at
FROM rfas r
INNER JOIN rfa_types rt ON r.rfa_type_id = rt.id
INNER JOIN rfa_revisions rr ON r.id = rr.rfa_id
-- RFA uses shared primary key with correspondences (1:1)
INNER JOIN correspondences c ON r.id = c.id
-- [FIX 1] เพิ่มการ Join ตาราง disciplines
INNER JOIN rfa_revisions rr ON r.id = rr.rfa_id -- RFA uses shared primary key with correspondences (1:1)
INNER JOIN correspondences c ON r.id = c.id -- [FIX 1] เพิ่มการ Join ตาราง disciplines
LEFT JOIN disciplines d ON c.discipline_id = d.id
INNER JOIN projects p ON c.project_id = p.id
INNER JOIN organizations org ON c.originator_id = org.id

File diff suppressed because it is too large Load Diff