From 0ce895c96a2f224af1c19fe5f0b2c961304fa324 Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 25 Nov 2025 00:28:33 +0700 Subject: [PATCH] 251125:0000 Phase 6 wait start dev Check --- 01_lcbp3_v1_4_3 copy.sql | 2432 +++++++++++++++++ 01_lcbp3_v1_4_3.sql | 75 +- 5_Backend_Folder_V1_4_3.md | 491 ++-- backend/package.json | 1 + backend/pnpm-lock.yaml | 470 ++++ backend/src/app.module.ts | 14 +- .../src/common/entities/audit-log.entity.ts | 3 + .../migrations/01_init_partitioning.sql | 75 + .../seeds/workflow-definitions.seed.ts | 82 + .../src/modules/master/dto/create-tag.dto.ts | 9 +- .../src/modules/master/entities/tag.entity.ts | 16 +- backend/src/modules/master/master.service.ts | 31 +- .../monitoring/dto/set-maintenance.dto.ts | 16 + .../monitoring/monitoring.controller.ts | 30 + .../modules/monitoring/monitoring.module.ts | 23 +- .../modules/monitoring/monitoring.service.ts | 44 + .../entities/notification.entity.ts | 3 + .../notification/notification.processor.ts | 222 +- .../notification/notification.service.ts | 122 +- backend/src/modules/user/user.service.ts | 1 + prompt.md | 38 +- temp.md | 48 +- 22 files changed, 3757 insertions(+), 489 deletions(-) create mode 100644 01_lcbp3_v1_4_3 copy.sql create mode 100644 backend/src/database/migrations/01_init_partitioning.sql create mode 100644 backend/src/database/seeds/workflow-definitions.seed.ts create mode 100644 backend/src/modules/monitoring/dto/set-maintenance.dto.ts create mode 100644 backend/src/modules/monitoring/monitoring.controller.ts create mode 100644 backend/src/modules/monitoring/monitoring.service.ts diff --git a/01_lcbp3_v1_4_3 copy.sql b/01_lcbp3_v1_4_3 copy.sql new file mode 100644 index 0000000..cd560c6 --- /dev/null +++ b/01_lcbp3_v1_4_3 copy.sql @@ -0,0 +1,2432 @@ +-- ========================================================== +-- DMS v1.4.2 Document Management System Database +-- Deploy Script Schema +-- Server: Container Station on QNAPQNAP TS-473A +-- Database service: MariaDB 10.11 +-- database web ui: phpmyadmin 5-apache +-- database deelopment ui: DBeaver +-- backend sevice: NestJS +-- frontend sevice: next.js +-- reverse proxy: jc21/nginx-proxy-manager:latest +-- cron service: n8n +-- DMS v1.4.2 Improvements +-- Update: revise fron v1.4.1 Gemini) +-- ========================================================== +SET NAMES utf8mb4; +SET time_zone = '+07:00'; +-- ปิดการตรวจสอบ Foreign Key ชั่วคราวเพื่อให้สามารถลบตารางได้ทั้งหมด +SET FOREIGN_KEY_CHECKS = 0; +DROP VIEW IF EXISTS v_document_statistics; +DROP VIEW IF EXISTS v_documents_with_attachments; +DROP VIEW IF EXISTS v_user_all_permissions; +DROP VIEW IF EXISTS v_audit_log_details; +DROP VIEW IF EXISTS v_user_tasks; +DROP VIEW IF EXISTS v_contract_parties_all; +DROP VIEW IF EXISTS v_current_rfas; +DROP VIEW IF EXISTS v_current_correspondences; +-- DROP PROCEDURE IF EXISTS sp_get_next_document_number; +-- 🗑️ DROP TABLE SCRIPT: LCBP3-DMS v1.4.2 +-- คำเตือน: ข้อมูลทั้งหมดจะหายไป กรุณา Backup ก่อนรันบน Production +SET FOREIGN_KEY_CHECKS = 0; +-- ============================================================ +-- ส่วนที่ 1: ตาราง System, Logs & Preferences (ตารางปลายทาง/ส่วนเสริม) +-- ============================================================ +DROP TABLE IF EXISTS backup_logs; +DROP TABLE IF EXISTS search_indices; +DROP TABLE IF EXISTS notifications; +DROP TABLE IF EXISTS audit_logs; +-- [NEW v1.4.2] ตารางการตั้งค่าส่วนตัวของผู้ใช้ (FK -> users) +DROP TABLE IF EXISTS user_preferences; +-- [NEW v1.4.2] ตารางเก็บ Schema สำหรับ Validate JSON (Stand-alone) +DROP TABLE IF EXISTS json_schemas; +-- ============================================================ +-- ส่วนที่ 2: ตาราง Junction (เชื่อมโยงข้อมูล M:N) +-- ============================================================ +DROP TABLE IF EXISTS correspondence_tags; +DROP TABLE IF EXISTS shop_drawing_revision_contract_refs; +DROP TABLE IF EXISTS contract_drawing_subcat_cat_maps; +-- ============================================================ +-- ส่วนที่ 3: ตารางไฟล์แนบและการเชื่อมโยง (Attachments) +-- ============================================================ +DROP TABLE IF EXISTS contract_drawing_attachments; +DROP TABLE IF EXISTS circulation_attachments; +DROP TABLE IF EXISTS shop_drawing_revision_attachments; +DROP TABLE IF EXISTS correspondence_attachments; +DROP TABLE IF EXISTS attachments; +-- ตารางหลักเก็บ path ไฟล์ +-- ============================================================ +-- ส่วนที่ 4: ตาราง Workflow & Routing (Process Logic) +-- ============================================================ +-- Circulation Workflow +DROP TABLE IF EXISTS circulation_routings; +DROP TABLE IF EXISTS circulation_template_assignees; +DROP TABLE IF EXISTS circulation_templates; +-- RFA Workflow +DROP TABLE IF EXISTS rfa_workflows; +DROP TABLE IF EXISTS rfa_workflow_template_steps; +DROP TABLE IF EXISTS rfa_workflow_templates; +-- Correspondence Workflow +DROP TABLE IF EXISTS correspondence_routings; +DROP TABLE IF EXISTS correspondence_routing_template_steps; +DROP TABLE IF EXISTS correspondence_status_transitions; +DROP TABLE IF EXISTS correspondence_routing_templates; +-- ============================================================ +-- ส่วนที่ 5: ตาราง Mapping สิทธิ์และโครงสร้าง (Access Control) +-- ============================================================ +DROP TABLE IF EXISTS role_permissions; +DROP TABLE IF EXISTS user_assignments; +DROP TABLE IF EXISTS contract_organizations; +DROP TABLE IF EXISTS project_organizations; +-- ============================================================ +-- ส่วนที่ 6: ตารางรายละเอียดของเอกสาร (Revisions & Items) +-- ============================================================ +DROP TABLE IF EXISTS transmittal_items; +DROP TABLE IF EXISTS shop_drawing_revisions; +DROP TABLE IF EXISTS rfa_items; +DROP TABLE IF EXISTS rfa_revisions; +DROP TABLE IF EXISTS correspondence_references; +DROP TABLE IF EXISTS correspondence_recipients; +DROP TABLE IF EXISTS correspondence_revisions; +-- [Modified v1.4.2] มี Virtual Columns +-- ============================================================ +-- ส่วนที่ 7: ตารางเอกสารหลัก (Core Documents) +-- ============================================================ +DROP TABLE IF EXISTS circulations; +DROP TABLE IF EXISTS transmittals; +DROP TABLE IF EXISTS contract_drawings; +DROP TABLE IF EXISTS shop_drawings; +DROP TABLE IF EXISTS rfas; +DROP TABLE IF EXISTS correspondences; +-- ============================================================ +-- ส่วนที่ 8: ตารางหมวดหมู่และข้อมูลหลัก (Master Data) +-- ============================================================ +DROP TABLE IF EXISTS shop_drawing_sub_categories; +DROP TABLE IF EXISTS shop_drawing_main_categories; +DROP TABLE IF EXISTS contract_drawing_sub_cats; +DROP TABLE IF EXISTS contract_drawing_cats; +DROP TABLE IF EXISTS contract_drawing_volumes; +DROP TABLE IF EXISTS circulation_status_codes; +DROP TABLE IF EXISTS rfa_approve_codes; +DROP TABLE IF EXISTS rfa_status_codes; +DROP TABLE IF EXISTS rfa_types; +DROP TABLE IF EXISTS correspondence_status; +DROP TABLE IF EXISTS correspondence_types; +DROP TABLE IF EXISTS document_number_counters; +-- [Modified v1.4.2] มี version column +DROP TABLE IF EXISTS document_number_formats; +DROP TABLE IF EXISTS tags; +-- ============================================================ +-- ส่วนที่ 9: ตารางผู้ใช้ บทบาท และโครงสร้างรากฐาน (Root Tables) +-- ============================================================ +DROP TABLE IF EXISTS organization_roles; +DROP TABLE IF EXISTS roles; +DROP TABLE IF EXISTS permissions; +DROP TABLE IF EXISTS contracts; +DROP TABLE IF EXISTS projects; +DROP TABLE IF EXISTS users; +-- Referenced by user_preferences, audit_logs, etc. +DROP TABLE IF EXISTS organizations; +-- Referenced by users, projects, etc. +-- ===================================================== +-- 1. 🏢 Core & Master Data (องค์กร, โครงการ, สัญญา) +-- ===================================================== +-- ตาราง Master เก็บประเภทบทบาทขององค์กร +CREATE TABLE organization_roles ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + role_name VARCHAR(20) NOT NULL UNIQUE COMMENT 'ชื่อบทบาท (OWNER, DESIGNER, CONSULTANT, CONTRACTOR, THIRD PARTY)' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master เก็บประเภทบทบาทขององค์กร'; +-- ตาราง Master เก็บข้อมูลองค์กรทั้งหมดที่เกี่ยวข้องในระบบ +CREATE TABLE organizations ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + organization_code VARCHAR(20) NOT NULL UNIQUE COMMENT 'รหัสองค์กร', + organization_name VARCHAR(255) NOT NULL COMMENT 'ชื่อองค์กร', + -- role_id INT COMMENT 'บทบาทขององค์กร', + is_active BOOLEAN DEFAULT TRUE COMMENT 'สถานะการใช้งาน', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'วันที่แก้ไขล่าสุด' -- FOREIGN KEY (role_id) REFERENCES organization_roles(id) ON DELETE SET NULL +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master เก็บข้อมูลองค์กรทั้งหมดที่เกี่ยวข้องในระบบ'; +-- Seed organization +INSERT INTO organizations (id, organization_code, organization_name) +VALUES (1, 'กทท.', 'การท่าเรือแห่งประเทศไทย'), + ( + 10, + 'สคฉ.3', + 'โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3' + ), + ( + 11, + 'สคฉ.3-01', + 'ตรวจรับพัสดุ ที่ปรึกษาควบคุมงาน' + ), + (12, 'สคฉ.3-02', 'ตรวจรับพัสดุ งานทางทะเล'), + ( + 13, + 'สคฉ.3-03', + 'ตรวจรับพัสดุ อาคารและระบบสาธารณูปโภค' + ), + ( + 14, + 'สคฉ.3-04', + 'ตรวจรับพัสดุ ตรวจสอบผลกระทบสิ่งแวดล้อม' + ), + (15, 'สคฉ.3-05', 'ตรวจรับพัสดุ เยียวยาการประมง'), + ( + 16, + 'สคฉ.3-06', + 'ตรวจรับพัสดุ งานก่อสร้าง ส่วนที่ 3' + ), + ( + 17, + 'สคฉ.3-07', + 'ตรวจรับพัสดุ งานก่อสร้าง ส่วนที่ 4' + ), + ( + 18, + 'สคฉ.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'); +-- ตาราง Master เก็บข้อมูลโครงการ +CREATE TABLE projects ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + project_code VARCHAR(50) NOT NULL UNIQUE COMMENT 'รหัสโครงการ', + project_name VARCHAR(255) NOT NULL COMMENT 'ชื่อโครงการ', + -- parent_project_id INT COMMENT 'รหัสโครงการหลัก (ถ้ามี)', + -- contractor_organization_id INT COMMENT 'รหัสองค์กรผู้รับเหมา (ถ้ามี)', + is_active TINYINT(1) DEFAULT 1 COMMENT 'สถานะการใช้งาน' -- FOREIGN KEY (parent_project_id) REFERENCES projects(id) ON DELETE SET NULL, + -- FOREIGN KEY (contractor_organization_id) REFERENCES organizations(id) ON DELETE SET NULL +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master เก็บข้อมูลโครงการ'; +INSERT INTO projects (project_code, project_name) +VALUES ( + 'LCBP3', + 'โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3 (ส่วนที่ 1-4)' + ), + ( + 'LCBP3C1', + 'โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3 (ส่วนที่ 1) งานก่อสร้างงานทางทะเล' + ), + ( + 'LCBP3C2', + 'โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3 (ส่วนที่ 2) งานก่อสร้างอาคาร ท่าเทียบเรือ ระบบถนน และระบบสาธารณูปโภค' + ), + ( + 'LCBP3C3', + 'โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3 (ส่วนที่ 3) งานก่อสร้าง' + ), + ( + 'LCBP3C4', + 'โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3 (ส่วนที่ 4) งานก่อสร้าง' + ); +-- ตาราง Master เก็บข้อมูลสัญญา +CREATE TABLE contracts ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + project_id INT NOT NULL, + contract_code VARCHAR(50) NOT NULL UNIQUE COMMENT 'รหัสสัญญา', + contract_name VARCHAR(255) NOT NULL COMMENT 'ชื่อสัญญา', + description TEXT COMMENT 'คำอธิบายสัญญา', + start_date DATE COMMENT 'วันที่เริ่มสัญญา', + end_date DATE COMMENT 'วันที่สิ้นสุดสัญญา', + is_active BOOLEAN DEFAULT TRUE, + 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 +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master เก็บข้อมูลสัญญา'; +-- ใช้ Subquery เพื่อดึง project_id มาเชื่อมโยง ทำให้ไม่ต้องมานั่งจัดการ ID ด้วยตัวเอง +INSERT INTO contracts ( + contract_code, + contract_name, + project_id, + is_active + ) +VALUES ( + 'DSLCBP3', + 'งานจ้างที่ปรีกษาออกแบบ โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3 (ส่วนที่ 1-4)', + ( + SELECT id + FROM projects + WHERE project_code = 'LCBP3' + ), + TRUE + ), + ( + 'PSLCBP3', + 'งานจ้างที่ปรีกษาควบคุมงาน โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3 (ส่วนที่ 1-4)', + ( + SELECT id + FROM projects + WHERE project_code = 'LCBP3' + ), + TRUE + ), + ( + 'LCBP3-C1', + 'งานก่อสร้าง โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3 (ส่วนที่ 1) งานก่อสร้างงานทางทะเล', + ( + SELECT id + FROM projects + WHERE project_code = 'LCBP3C1' + ), + TRUE + ), + ( + 'LCBP3-C2', + 'งานก่อสร้าง โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3 (ส่วนที่ 2) งานก่อสร้างอาคาร ท่าเทียบเรือ ระบบถนน และระบบสาธารณูปโภค', + ( + SELECT id + FROM projects + WHERE project_code = 'LCBP3C2' + ), + TRUE + ), + ( + 'LCBP3-C3', + 'งานก่อสร้าง โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3 (ส่วนที่ 3) งานก่อสร้าง', + ( + SELECT id + FROM projects + WHERE project_code = 'LCBP3C3' + ), + TRUE + ), + ( + 'LCBP3-C4', + 'งานก่อสร้าง โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3 (ส่วนที่ 4) งานก่อสร้าง', + ( + SELECT id + FROM projects + WHERE project_code = 'LCBP3C4' + ), + TRUE + ), + ( + 'ENLCBP3', + 'งานจ้างเหมาตรวจสอบผลกระทบสิ่งแวดล้อมนะหว่างงานก่อสร้างโครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3 (ส่วนที่ 1-4)', + ( + SELECT id + FROM projects + WHERE project_code = 'LCBP3' + ), + TRUE + ); +-- ===================================================== +-- 2. 👥 Users & RBAC (ผู้ใช้, สิทธิ์, บทบาท) +-- ===================================================== +-- ตาราง Master เก็บข้อมูลผู้ใช้งาน (User) +CREATE TABLE users ( + user_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + username VARCHAR(50) NOT NULL UNIQUE COMMENT 'ชื่อผู้ใช้งาน', + password_hash VARCHAR(255) NOT NULL COMMENT 'รหัสผ่าน (Hashed)', + first_name VARCHAR(50) COMMENT 'ชื่อจริง', + last_name VARCHAR(50) COMMENT 'นามสกุล', + email VARCHAR(100) NOT NULL UNIQUE COMMENT 'อีเมล', + line_id VARCHAR(100) COMMENT 'LINE ID', + primary_organization_id INT COMMENT 'สังกัดองค์กร', + is_active TINYINT(1) DEFAULT 1 COMMENT 'สถานะการใช้งาน', + failed_attempts INT DEFAULT 0 COMMENT 'จำนวนครั้งที่ล็อกอินล้มเหลว', + locked_until DATETIME COMMENT 'ล็อกอินไม่ได้จนถึงเวลา', + last_login_at TIMESTAMP NULL COMMENT 'วันที่และเวลาที่ล็อกอินล่าสุด', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'วันที่แก้ไขล่าสุด', + deleted_at DATETIME NULL DEFAULT NULL COMMENT 'วันที่ลบ', + FOREIGN KEY (primary_organization_id) REFERENCES organizations(id) ON DELETE + SET NULL +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master เก็บข้อมูลผู้ใช้งาน (User)'; +-- Initial SUPER_ADMIN user +INSERT INTO `users` ( + `user_id`, + `username`, + `password_hash`, + `first_name`, + `last_name`, + `email`, + `line_id`, + `primary_organization_id` + ) +VALUES ( + 1, + 'superadmin', + '$2b$10$E6d5k.f46jr.POGWKHhiQ.X1ZsFrMpZox//sCxeOiLUULGuAHO0NW', + 'Super', + 'Admin', + 'superadmin @example.com', + NULL, + NULL + ), + ( + 2, + 'admin', + '$2b$10$E6d5k.f46jr.POGWKHhiQ.X1ZsFrMpZox//sCxeOiLUULGuAHO0NW', + 'Admin', + 'คคง.', + 'admin@example.com', + NULL, + 1 + ), + ( + 3, + 'editor01', + '$2b$10$E6d5k.f46jr.POGWKHhiQ.X1ZsFrMpZox//sCxeOiLUULGuAHO0NW', + 'DC', + 'C1', + 'editor01 @example.com', + NULL, + 41 + ), + ( + 4, + 'viewer01', + '$2b$10$E6d5k.f46jr.POGWKHhiQ.X1ZsFrMpZox//sCxeOiLUULGuAHO0NW', + 'Viewer', + 'สคฉ.03', + 'viewer01 @example.com', + NULL, + 10 + ); +-- ตาราง Master เก็บ "บทบาท" ของผู้ใช้ในระบบ +CREATE TABLE roles ( + role_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + -- role_code VARCHAR(50) NOT NULL UNIQUE COMMENT 'รหัสบทบาท (เช่น SUPER_ADMIN, ADMIN, EDITOR, VIEWER)', + role_name VARCHAR(100) NOT NULL COMMENT 'ชื่อบทบาท', + scope ENUM('Global', 'Organization', 'Project', 'Contract') NOT NULL, + -- ขอบเขตของบทบาท (จากข้อ 4.3) + description TEXT COMMENT 'คำอธิบายบทบาท', + is_system BOOLEAN DEFAULT FALSE COMMENT '(1 = บทบาทของระบบ ลบไม่ได้)' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master เก็บ "บทบาท" ของผู้ใช้ในระบบ'; +-- ========================================================== +-- Seed Roles (บทบาทพื้นฐาน 5 บทบาท ตาม Req 4.3) +-- ========================================================== +-- 1. Superadmin (Global) +INSERT INTO roles (role_id, role_name, scope, description) +VALUES ( + 1, + 'Superadmin', + 'Global', + 'ผู้ดูแลระบบสูงสุด: สามารถทำทุกอย่างในระบบ, จัดการองค์กร, และจัดการข้อมูลหลักระดับ Global' + ); +-- 2. Org Admin (Organization) +INSERT INTO roles (role_id, role_name, scope, description) +VALUES ( + 2, + 'Org Admin', + 'Organization', + 'ผู้ดูแลองค์กร: จัดการผู้ใช้ในองค์กร, จัดการบทบาท / สิทธิ์ภายในองค์กร, และดูรายงานขององค์กร' + ); +-- 3. Document Control (Organization) +INSERT INTO roles (role_id, role_name, scope, description) +VALUES ( + 3, + 'Document Control', + 'Organization', + 'ควบคุมเอกสารขององค์กร: เพิ่ม / แก้ไข / ลบเอกสาร, และกำหนดสิทธิ์เอกสารภายในองค์กร' + ); +-- 4. Editor (Organization) +INSERT INTO roles (role_id, role_name, scope, description) +VALUES ( + 4, + 'Editor', + 'Organization', + 'ผู้แก้ไขเอกสารขององค์กร: เพิ่ม / แก้ไขเอกสารที่ได้รับมอบหมาย' + ); +-- 5. Viewer (Organization) +INSERT INTO roles (role_id, role_name, scope, description) +VALUES ( + 5, + 'Viewer', + 'Organization', + 'ผู้ดูเอกสารขององค์กร: ดูเอกสารที่มีสิทธิ์เข้าถึงเท่านั้น' + ); +-- 6. Project Manager (Project) +INSERT INTO roles (role_id, role_name, scope, description) +VALUES ( + 6, + 'Project Manager', + 'Project', + 'ผู้จัดการโครงการ: จัดการสมาชิกในโครงการ, สร้าง / จัดการสัญญาในโครงการ, และดูรายงานโครงการ' + ); +-- 7. Contract Admin (Contract) +INSERT INTO roles (role_id, role_name, scope, description) +VALUES ( + 7, + 'Contract Admin', + 'Contract', + 'ผู้ดูแลสัญญา: จัดการสมาชิกในสัญญา, สร้าง / จัดการข้อมูลหลักเฉพาะสัญญา, และอนุมัติเอกสารในสัญญา' + ); +-- ตาราง Master เก็บ "สิทธิ์" (Permission) หรือ "การกระทำ" ทั้งหมดในระบบ +CREATE TABLE permissions ( + permission_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + permission_name VARCHAR(100) NOT NULL UNIQUE COMMENT 'รหัสสิทธิ์ (เช่น rfas.create, rfas.view)', + description TEXT COMMENT 'คำอธิบายสิทธิ์', + module VARCHAR(50) COMMENT 'โมดูลที่เกี่ยวข้อง', + scope_level ENUM('GLOBAL', 'ORG', 'PROJECT') COMMENT 'ระดับขอบเขตของสิทธิ์', + is_active TINYINT(1) DEFAULT 1 COMMENT 'สถานะการใช้งาน' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master เก็บ "สิทธิ์" (Permission) หรือ "การกระทำ" ทั้งหมดในระบบ'; +-- ===================================================== +-- 2. Seed Permissions (สิทธิ์การใช้งานทั้งหมด) +-- สิทธิ์ระดับระบบและการจัดการหลัก (System & Master Data) +-- ===================================================== +INSERT INTO permissions (permission_id, permission_name, description) +VALUES ( + 1, + 'system.manage_all', + 'ทำทุกอย่างในระบบ (Superadmin Power)' + ), + -- การจัดการองค์กร + (2, 'organization.create', 'สร้างองค์กรใหม่'), + (3, 'organization.edit', 'แก้ไขข้อมูลองค์กร'), + (4, 'organization.delete', 'ลบองค์กร'), + (5, 'organization.view', 'ดูรายการองค์กร'), + -- การจัดการโครงการ + (6, 'project.create', 'สร้างโครงการใหม่'), + (7, 'project.edit', 'แก้ไขข้อมูลโครงการ'), + (8, 'project.delete', 'ลบโครงการ'), + (9, 'project.view', 'ดูรายการโครงการ'), + -- การจัดการบทบาทและสิทธิ์ (Roles & Permissions) + (10, 'role.create', 'สร้างบทบาท (Role) ใหม่'), + (11, 'role.edit', 'แก้ไขบทบาท (Role)'), + (12, 'role.delete', 'ลบบทบาท (Role)'), + ( + 13, + 'permission.assign', + 'มอบสิทธิ์ให้กับบทบาท (Role)' + ), + -- การจัดการข้อมูลหลัก (Master Data) + ( + 14, + 'master_data.document_type.manage', + 'จัดการประเภทเอกสาร (Document Types)' + ), + ( + 15, + 'master_data.document_status.manage', + 'จัดการสถานะเอกสาร (Document Statuses)' + ), + ( + 16, + 'master_data.drawing_category.manage', + 'จัดการหมวดหมู่แบบ (Drawing Categories)' + ), + (17, 'master_data.tag.manage', 'จัดการ Tags'), + -- การจัดการผู้ใช้งาน + (18, 'user.create', 'สร้างผู้ใช้งานใหม่'), + (19, 'user.edit', 'แก้ไขข้อมูลผู้ใช้งาน'), + (20, 'user.delete', 'ลบ / ปิดการใช้งานผู้ใช้'), + (21, 'user.view', 'ดูข้อมูลผู้ใช้งาน'), + ( + 22, + 'user.assign_organization', + 'มอบผู้ใช้งานให้กับองค์กร' + ); +-- ===================================================== +-- == 2. สิทธิ์การจัดการโครงการและสัญญา (Project & Contract) == +-- ===================================================== +INSERT INTO permissions (permission_id, permission_name, description) +VALUES ( + 23, + 'project.manage_members', + 'จัดการสมาชิกในโครงการ (เชิญ / ถอดสมาชิก)' + ), + ( + 24, + 'project.create_contracts', + 'สร้างสัญญาในโครงการ' + ), + ( + 25, + 'project.manage_contracts', + 'จัดการสัญญาในโครงการ' + ), + ( + 26, + 'project.view_reports', + 'ดูรายงานระดับโครงการ' + ), + ( + 27, + 'contract.manage_members', + 'จัดการสมาชิกในสัญญา' + ), + (28, 'contract.view', 'ดูข้อมูลสัญญา'); +-- ===================================================== +-- == 3. สิทธิ์การจัดการเอกสาร (Document Management) == +-- ===================================================== +-- สิทธิ์ทั่วไปสำหรับเอกสารทุกประเภท +INSERT INTO permissions (permission_id, permission_name, description) +VALUES ( + 29, + 'document.create_draft', + 'สร้างเอกสารในสถานะฉบับร่าง (Draft) ' + ), + (30, 'document.submit', 'ส่งเอกสาร (Submitted)'), + (31, 'document.view', 'ดูเอกสาร'), + (32, 'document.edit', 'แก้ไขเอกสาร (ทั่วไป)'), + ( + 33, + 'document.admin_edit', + 'แก้ไข / ถอน / ยกเลิกเอกสารที่ส่งแล้ว (Admin Power) ' + ), + (34, 'document.delete', 'ลบเอกสาร'), + ( + 35, + 'document.attach', + 'จัดการไฟล์แนบ (อัปโหลด / ลบ) ' + ), + -- สิทธิ์เฉพาะสำหรับ Correspondence + ( + 36, + 'correspondence.create', + 'สร้างเอกสารโต้ตอบ (Correspondence) ' + ), + -- สิทธิ์เฉพาะสำหรับ Request for Approval (RFA) + (37, 'rfa.create', 'สร้างเอกสารขออนุมัติ (RFA)'), + ( + 38, + 'rfa.manage_shop_drawings', + 'จัดการข้อมูล Shop Drawing และ Contract Drawing ที่เกี่ยวข้อง' + ), + -- สิทธิ์เฉพาะสำหรับ Shop Drawing & Contract Drawing + ( + 39, + 'drawing.create', + 'สร้าง / แก้ไขข้อมูลแบบ (Shop / Contract Drawing)' + ), + -- สิทธิ์เฉพาะสำหรับ Transmittal + ( + 40, + 'transmittal.create', + 'สร้างเอกสารนำส่ง (Transmittal)' + ), + -- สิทธิ์เฉพาะสำหรับ Circulation Sheet (ใบเวียน) + ( + 41, + 'circulation.create', + 'สร้างใบเวียนเอกสาร (Circulation)' + ), + ( + 42, + 'circulation.respond', + 'ตอบกลับใบเวียน (Main / Action)' + ), + ( + 43, + 'circulation.acknowledge', + 'รับทราบใบเวียน (Information)' + ), + (44, 'circulation.close', 'ปิดใบเวียน'); +-- ===================================================== +-- == 4. สิทธิ์การจัดการ Workflow == +-- ===================================================== +INSERT INTO permissions (permission_id, permission_name, description) +VALUES ( + 45, + 'workflow.action_review', + 'ดำเนินการในขั้นตอนปัจจุบัน (เช่น ตรวจสอบแล้ว)' + ), + ( + 46, + 'workflow.force_proceed', + 'บังคับไปยังขั้นตอนถัดไป (Document Control Power)' + ), + ( + 47, + 'workflow.revert', + 'ย้อนกลับไปยังขั้นตอนก่อนหน้า (Document Control Power)' + ); +-- ===================================================== +-- == 5. สิทธิ์ด้านการค้นหาและรายงาน (Search & Reporting) == +-- ===================================================== +INSERT INTO permissions (permission_id, permission_name, description) +VALUES (48, 'search.advanced', 'ใช้งานการค้นหาขั้นสูง'), + ( + 49, + 'report.generate', + 'สร้างรายงานสรุป (รายวัน / สัปดาห์ / เดือน / ปี)' + ); +-- ตารางเชื่อมระหว่าง roles และ permissions (M:N) +CREATE TABLE role_permissions ( + role_id INT COMMENT 'ID ของบทบาท', + permission_id INT COMMENT 'ID ของสิทธิ์', + PRIMARY KEY (role_id, permission_id), + FOREIGN KEY (role_id) REFERENCES roles(role_id) ON DELETE CASCADE, + FOREIGN KEY (permission_id) REFERENCES permissions(permission_id) ON DELETE CASCADE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางเชื่อมระหว่าง roles และ permissions (M :N)'; +-- ========================================================== +-- Seed Role-Permissions Mapping (จับคู่สิทธิ์เริ่มต้น) +-- ========================================================== +-- Seed data for the 'role_permissions 'table +-- This table links roles to their specific permissions. +-- NOTE: This assumes the role_id and permission_id from the previous seed data files. +-- Superadmin (role_id = 1), Org Admin (role_id = 2), Document Control (role_id = 3), etc. +-- ===================================================== +-- == 1. Superadmin (role_id = 1) - Gets ALL permissions == +-- ===================================================== +-- Superadmin can do everything. We can dynamically link all permissions to this role. +-- This is a robust way to ensure Superadmin always has full power. +INSERT INTO role_permissions (role_id, permission_id) +SELECT 1, + permission_id +FROM permissions; +-- ===================================================== +-- == 2. Org Admin (role_id = 2) == +-- ===================================================== +INSERT INTO role_permissions (role_id, permission_id) +VALUES -- จัดการผู้ใช้ในองค์กร + (2, 18), + -- user.create + (2, 19), + -- user.edit + (2, 20), + -- user.delete + (2, 21), + -- user.view + (2, 22), + -- user.assign_organization + -- จัดการองค์กร + (2, 3), + -- organization.edit + (2, 5), + -- organization.view + -- จัดการข้อมูลหลักที่อนุญาต (เฉพาะ Tags) + (2, 17), + -- master_data.tag.manage + -- ดูข้อมูลต่างๆ ในองค์กร + (2, 31), + -- document.view + (2, 9), + -- project.view + (2, 28), + -- contract.view + -- การค้นหาและรายงาน + (2, 48), + -- search.advanced + (2, 49); +-- report.generate +-- ===================================================== +-- == 3. Document Control (role_id = 3) == +-- ===================================================== +INSERT INTO role_permissions (role_id, permission_id) +VALUES -- สิทธิ์จัดการเอกสารทั้งหมด + (3, 29), + -- document.create_draft + (3, 30), + -- document.submit + (3, 31), + -- document.view + (3, 32), + -- document.edit + (3, 33), + -- document.admin_edit + (3, 34), + -- document.delete + (3, 35), + -- document.attach + -- สิทธิ์สร้างเอกสารแต่ละประเภท + (3, 36), + -- correspondence.create + (3, 37), + -- rfa.create + (3, 39), + -- drawing.create + (3, 40), + -- transmittal.create + (3, 41), + -- circulation.create + -- สิทธิ์จัดการ Workflow + (3, 45), + -- workflow.action_review + (3, 46), + -- workflow.force_proceed + (3, 47), + -- workflow.revert + -- สิทธิ์จัดการ Circulation + (3, 42), + -- circulation.respond + (3, 43), + -- circulation.acknowledge + (3, 44), + -- circulation.close + -- สิทธิ์อื่นๆ ที่จำเป็น + (3, 38), + -- rfa.manage_shop_drawings + (3, 48), + -- search.advanced + (3, 49); +-- report.generate +-- ===================================================== +-- == 4. Editor (role_id = 4) == +-- ===================================================== +INSERT INTO role_permissions (role_id, permission_id) +VALUES -- สิทธิ์แก้ไขเอกสาร (แต่ไม่ใช่สิทธิ์ Admin) + (4, 29), + -- document.create_draft + (4, 30), + -- document.submit + (4, 31), + -- document.view + (4, 32), + -- document.edit + (4, 35), + -- document.attach + -- สิทธิ์สร้างเอกสารแต่ละประเภท + (4, 36), + -- correspondence.create + (4, 37), + -- rfa.create + (4, 39), + -- drawing.create + (4, 40), + -- transmittal.create + (4, 41), + -- circulation.create + -- สิทธิ์อื่นๆ ที่จำเป็น + (4, 38), + -- rfa.manage_shop_drawings + (4, 48); +-- search.advanced +-- ===================================================== +-- == 5. Viewer (role_id = 5) == +-- ===================================================== +INSERT INTO role_permissions (role_id, permission_id) +VALUES -- สิทธิ์ดูเท่านั้น + (5, 31), + -- document.view + (5, 48); +-- search.advanced +-- ===================================================== +-- == 6. Project Manager (role_id = 6) == +-- ===================================================== +INSERT INTO role_permissions (role_id, permission_id) +VALUES -- สิทธิ์จัดการโครงการ + (6, 23), + -- project.manage_members + (6, 24), + -- project.create_contracts + (6, 25), + -- project.manage_contracts + (6, 26), + -- project.view_reports + (6, 9), + -- project.view + -- สิทธิ์จัดการข้อมูลหลักระดับโครงการ + (6, 16), + -- master_data.drawing_category.manage + -- สิทธิ์ดูข้อมูลในสัญญา + (6, 28), + -- contract.view + -- สิทธิ์ในการจัดการเอกสาร (ระดับ Editor) + (6, 29), + -- document.create_draft + (6, 30), + -- document.submit + (6, 31), + -- document.view + (6, 32), + -- document.edit + (6, 35), + -- document.attach + (6, 36), + -- correspondence.create + (6, 37), + -- rfa.create + (6, 39), + -- drawing.create + (6, 40), + -- transmittal.create + (6, 41), + -- circulation.create + (6, 38), + -- rfa.manage_shop_drawings + (6, 48), + -- search.advanced + (6, 49); +-- report.generate +-- ===================================================== +-- == 7. Contract Admin (role_id = 7) == +-- ===================================================== +INSERT INTO role_permissions (role_id, permission_id) +VALUES -- สิทธิ์จัดการสัญญา + (7, 27), + -- contract.manage_members + (7, 28), + -- contract.view + -- สิทธิ์ในการอนุมัติ (ส่วนหนึ่งของ Workflow) + (7, 45), + -- workflow.action_review + -- สิทธิ์จัดการข้อมูลเฉพาะสัญญา + (7, 38), + -- rfa.manage_shop_drawings + (7, 39), + -- drawing.create + -- สิทธิ์ในการจัดการเอกสาร (ระดับ Editor) + (7, 29), + -- document.create_draft + (7, 30), + -- document.submit + (7, 31), + -- document.view + (7, 32), + -- document.edit + (7, 35), + -- document.attach + (7, 36), + -- correspondence.create + (7, 37), + -- rfa.create + (7, 40), + -- transmittal.create + (7, 41), + -- circulation.create + (7, 48); +-- search.advanced +-- ตารางเชื่อมผู้ใช้ (users) +CREATE TABLE user_assignments ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + role_id INT NOT NULL, + -- คอลัมน์สำหรับกำหนดขอบเขต (จะใช้เพียงอันเดียวต่อแถว) + organization_id INT NULL, + project_id INT NULL, + contract_id INT NULL, + assigned_by_user_id INT, + -- ผู้ที่มอบหมายบทบาทนี้ + assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE, + FOREIGN KEY (role_id) REFERENCES roles(role_id) ON DELETE CASCADE, + FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE, + FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY (contract_id) REFERENCES contracts(id) ON DELETE CASCADE, + FOREIGN KEY (assigned_by_user_id) REFERENCES users(user_id), + -- Constraint เพื่อให้แน่ใจว่ามีเพียงขอบเขตเดียวที่ถูกกำหนดในแต่ละแถว + CONSTRAINT chk_scope CHECK ( + ( + organization_id IS NOT NULL + AND project_id IS NULL + AND contract_id IS NULL + ) + OR ( + organization_id IS NULL + AND project_id IS NOT NULL + AND contract_id IS NULL + ) + OR ( + organization_id IS NULL + AND project_id IS NULL + AND contract_id IS NOT NULL + ) + OR ( + organization_id IS NULL + AND project_id IS NULL + AND contract_id IS NULL + ) -- สำหรับ Global scope + ) +); +INSERT INTO `user_assignments` ( + `id`, + `user_id`, + `role_id`, + `organization_id`, + `project_id`, + `contract_id`, + `assigned_by_user_id` + ) +VALUES ( + 1, + 1, + 1, + NULL, + NULL, + NULL, + NULL + ), + ( + 2, + 2, + 2, + 1, + NULL, + NULL, + NULL + ); +CREATE TABLE project_organizations ( + project_id INT NOT NULL, + organization_id INT NOT NULL, + PRIMARY KEY (project_id, organization_id), + FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE +); +CREATE TABLE contract_organizations ( + contract_id INT NOT NULL, + organization_id INT NOT NULL, + role_in_contract VARCHAR(100), + -- เช่น 'Owner', 'Designer', 'Consultant', 'Contractor ' + PRIMARY KEY (contract_id, organization_id), + FOREIGN KEY (contract_id) REFERENCES contracts(id) ON DELETE CASCADE, + FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE +); +-- ===================================================== +-- == 4. การเชื่อมโยงโครงการกับองค์กร (project_organizations) == +-- ===================================================== +-- โครงการหลัก (LCBP3) จะมีองค์กรหลักๆ เข้ามาเกี่ยวข้องทั้งหมด +INSERT INTO project_organizations (project_id, organization_id) +SELECT ( + SELECT id + FROM projects + WHERE project_code = 'LCBP3 ' + ), + id +FROM organizations +WHERE organization_code IN ( + 'กทท.', + 'สคฉ.3', + 'TEAM', + 'คคง.', + 'ผรม.1', + 'ผรม.2', + 'ผรม.3', + 'ผรม.4', + 'EN', + 'CAR ' + ); +-- โครงการย่อย (LCBP3C1) จะมีเฉพาะองค์กรที่เกี่ยวข้อง +INSERT INTO project_organizations (project_id, organization_id) +SELECT ( + SELECT id + FROM projects + WHERE project_code = 'LCBP3C1 ' + ), + id +FROM organizations +WHERE organization_code IN ('กทท.', 'สคฉ.3', 'สคฉ.3 -02', 'คคง.', 'ผรม.1 '); +-- ทำเช่นเดียวกันสำหรับโครงการอื่นๆ (ตัวอย่าง) +INSERT INTO project_organizations (project_id, organization_id) +SELECT ( + SELECT id + FROM projects + WHERE project_code = 'LCBP3C2 ' + ), + id +FROM organizations +WHERE organization_code IN ('กทท.', 'สคฉ.3', 'สคฉ.3 -03', 'คคง.', 'ผรม.2 '); +-- ===================================================== +-- == 5. การเชื่อมโยงสัญญากับองค์กร (contract_organizations) == +-- ===================================================== +-- สัญญาที่ปรึกษาออกแบบ (DSLCBP3) +INSERT INTO contract_organizations (contract_id, organization_id, role_in_contract) +VALUES ( + ( + SELECT id + FROM contracts + WHERE contract_code = 'DSLCBP3' + ), + ( + SELECT id + FROM organizations + WHERE organization_code = 'กทท.' + ), + 'Owner' + ), + ( + ( + SELECT id + FROM contracts + WHERE contract_code = 'DSLCBP3' + ), + ( + SELECT id + FROM organizations + WHERE organization_code = 'TEAM' + ), + 'Designer' + ); +-- สัญญาที่ปรึกษาควบคุมงาน (PSLCBP3) +INSERT INTO contract_organizations (contract_id, organization_id, role_in_contract) +VALUES ( + ( + SELECT id + FROM contracts + WHERE contract_code = 'PSLCBP3 ' + ), + ( + SELECT id + FROM organizations + WHERE organization_code = 'กทท.' + ), + 'Owner' + ), + ( + ( + SELECT id + FROM contracts + WHERE contract_code = 'PSLCBP3 ' + ), + ( + SELECT id + FROM organizations + WHERE organization_code = 'คคง.' + ), + 'Consultant' + ); +-- สัญญางานก่อสร้าง ส่วนที่ 1 (LCBP3-C1) +INSERT INTO contract_organizations (contract_id, organization_id, role_in_contract) +VALUES ( + ( + SELECT id + FROM contracts + WHERE contract_code = 'LCBP3-C1' + ), + ( + SELECT id + FROM organizations + WHERE organization_code = 'กทท.' + ), + 'Owner' + ), + ( + ( + SELECT id + FROM contracts + WHERE contract_code = 'LCBP3-C1 ' + ), + ( + SELECT id + FROM organizations + WHERE organization_code = 'ผรม.1' + ), + 'Contractor' + ); +-- สัญญางานก่อสร้าง ส่วนที่ 2 (LCBP3-C2) +INSERT INTO contract_organizations (contract_id, organization_id, role_in_contract) +VALUES ( + ( + SELECT id + FROM contracts + WHERE contract_code = 'LCBP3-C2' + ), + ( + SELECT id + FROM organizations + WHERE organization_code = 'กทท.' + ), + 'Owner' + ), + ( + ( + SELECT id + FROM contracts + WHERE contract_code = 'LCBP3-C2' + ), + ( + SELECT id + FROM organizations + WHERE organization_code = 'ผรม.2' + ), + 'Contractor' + ); +-- สัญญาตรวจสอบสิ่งแวดล้อม (ENLCBP3) +INSERT INTO contract_organizations (contract_id, organization_id, role_in_contract) +VALUES ( + ( + SELECT id + FROM contracts + WHERE contract_code = 'ENLCBP3' + ), + ( + SELECT id + FROM organizations + WHERE organization_code = 'กทท.' + ), + 'Owner' + ), + ( + ( + SELECT id + FROM contracts + WHERE contract_code = 'ENLCBP3' + ), + ( + SELECT id + FROM organizations + WHERE organization_code = 'EN' + ), + 'Consultant' + ); +-- ===================================================== +-- 3. ✉️ Correspondences (เอกสารหลัก, Revisions) +-- ===================================================== +-- ตาราง Master เก็บประเภทเอกสารโต้ตอบ +CREATE TABLE correspondence_types ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + type_code VARCHAR(50) NOT NULL UNIQUE COMMENT 'รหัสประเภท (เช่น RFA, RFI)', + type_name VARCHAR(255) NOT NULL COMMENT 'ชื่อประเภท', + sort_order INT DEFAULT 0 COMMENT 'ลำดับการแสดงผล', + is_active TINYINT(1) DEFAULT 1 COMMENT 'สถานะการใช้งาน ' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master เก็บประเภทเอกสารโต้ตอบ'; +INSERT INTO correspondence_types (type_code, type_name, sort_order, is_active) +VALUES ('RFA', 'Request for Approval', 1, 1), + ('RFI', 'Request for Information', 2, 1), + ('TRANSMITTAL', 'Transmittal', 3, 1), + ('EMAIL', 'Email', 4, 1), + ('INSTRUCTION', 'Instruction', 5, 1), + ('LETTER', 'Letter', 6, 1), + ('MEMO', 'Memorandum', 7, 1), + ('MOM', 'Minutes of Meeting', 8, 1), + ('NOTICE', 'Notice', 9, 1), + ('OTHER', 'Other', 10, 1); +-- ตาราง Master เก็บสถานะของเอกสาร +CREATE TABLE correspondence_status ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + status_code VARCHAR(50) NOT NULL UNIQUE COMMENT 'รหัสสถานะหนังสือ (เช่น DRAFT, SUBOWN)', + status_name VARCHAR(255) NOT NULL COMMENT 'ชื่อสถานะหนังสือ', + sort_order INT DEFAULT 0 COMMENT 'ลำดับการแสดงผล', + is_active TINYINT(1) DEFAULT 1 COMMENT 'สถานะการใช้งาน ' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master เก็บสถานะของเอกสาร'; +INSERT INTO correspondence_status (status_code, status_name, sort_order, is_active) +VALUES ('DRAFT', 'Draft', 10, 1), + ('SUBOWN', 'Submitted to Owner', 21, 1), + ('SUBDSN', 'Submitted to Designer', 22, 1), + ('SUBCSC', 'Submitted to CSC', 23, 1), + ('SUBCON', 'Submitted to Contractor', 24, 1), + ('SUBOTH', 'Submitted to Others', 25, 1), + ('REPOWN', 'Reply by Owner', 31, 1), + ('REPDSN', 'Reply by Designer', 32, 1), + ('REPCSC', 'Reply by CSC', 33, 1), + ('REPCON', 'Reply by Contractor', 34, 1), + ('REPOTH', 'Reply by Others', 35, 1), + ('RSBOWN', 'Resubmited by Owner', 41, 1), + ('RSBDSN', 'Resubmited by Designer', 42, 1), + ('RSBCSC', 'Resubmited by CSC', 43, 1), + ('RSBCON', 'Resubmited by Contractor', 44, 1), + ('CLBOWN', 'Closed by Owner', 51, 1), + ('CLBDSN', 'Closed by Designer', 52, 1), + ('CLBCSC', 'Closed by CSC', 53, 1), + ('CLBCON', 'Closed by Contractor', 54, 1), + ('CCBOWN', 'Canceled by Owner', 91, 1), + ('CCBDSN', 'Canceled by Designer', 92, 1), + ('CCBCSC', 'Canceled by CSC', 93, 1), + ('CCBCON', 'Canceled by Contractor', 94, 1); +-- ตาราง "แม่" ของเอกสารโต้ตอบ เก็บข้อมูลที่ไม่เปลี่ยนตาม Revision +CREATE TABLE correspondences ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง (นี่คือ "Master ID" ที่ใช้เชื่อมโยง)', + correspondence_number VARCHAR(100) NOT NULL COMMENT 'เลขที่เอกสาร (สร้างจาก DocumentNumberingModule)', + correspondence_type_id INT NOT NULL COMMENT 'ประเภทเอกสาร', + is_internal_communication TINYINT(1) DEFAULT 0 COMMENT '(1 = ภายใน, 0 = ภายนอก)', + project_id INT NOT NULL COMMENT 'อยู่ในโครงการ', + originator_id INT COMMENT 'องค์กรผู้ส่ง', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง', + created_by INT COMMENT 'ผู้สร้าง', + 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, + UNIQUE KEY uq_corr_no_per_project (project_id, correspondence_number) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง "แม่" ของเอกสารโต้ตอบ เก็บข้อมูลที่ไม่เปลี่ยนตาม Revision'; +-- ตาราง "ลูก" เก็บประวัติการแก้ไข (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 นี้', + title VARCHAR(255) NOT NULL COMMENT 'เรื่อง', + document_date DATE COMMENT 'วันที่ในเอกสาร', + issued_date DATETIME COMMENT 'วันที่ออกเอกสาร', + received_date DATETIME COMMENT 'วันที่ลงรับเอกสาร', + due_date DATETIME COMMENT 'วันที่ครบกำหนด', + description TEXT COMMENT 'คำอธิบายการแก้ไขใน Revision นี้', + details JSON COMMENT 'ข้อมูลเฉพาะ (เช่น RFI details)', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้างเอกสาร', + created_by INT COMMENT 'ผู้สร้าง', + updated_by INT COMMENT 'ผู้แก้ไขล่าสุด', + 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) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง "ลูก" เก็บประวัติการแก้ไข (Revisions) ของ correspondences (1 :N)'; +-- ตารางเชื่อมผู้รับ (TO/CC) สำหรับเอกสารแต่ละฉบับ (M:N) +CREATE TABLE correspondence_recipients ( + correspondence_id INT COMMENT 'ID ของเอกสาร', + recipient_organization_id INT COMMENT 'ID องค์กรผู้รับ', + recipient_type ENUM('TO', 'CC ') COMMENT 'ประเภทผู้รับ (TO หรือ CC)', + PRIMARY KEY ( + correspondence_id, + recipient_organization_id, + recipient_type + ), + FOREIGN KEY (correspondence_id) REFERENCES correspondence_revisions(correspondence_id) ON DELETE CASCADE, + FOREIGN KEY (recipient_organization_id) REFERENCES organizations(id) ON DELETE RESTRICT +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางเชื่อมผู้รับ (TO / CC) สำหรับเอกสารแต่ละฉบับ (M :N)'; +-- ตาราง Master เก็บ Tags ทั้งหมดที่ใช้ในระบบ +CREATE TABLE tags ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + tag_name VARCHAR(100) NOT NULL UNIQUE COMMENT 'ชื่อ Tag', + description TEXT COMMENT 'คำอธิบายแท็ก', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'วันที่แก้ไขล่าสุด ' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master เก็บ Tags ทั้งหมดที่ใช้ในระบบ'; +-- ตารางเชื่อมระหว่าง correspondences และ tags (M:N) +CREATE TABLE correspondence_tags ( + correspondence_id INT COMMENT 'ID ของเอกสาร', + tag_id INT COMMENT 'ID ของ Tag', + PRIMARY KEY (correspondence_id, tag_id), + FOREIGN KEY (correspondence_id) REFERENCES correspondences(id) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางเชื่อมระหว่าง correspondences และ tags (M :N)'; +-- ตารางเชื่อมการอ้างอิงระหว่างเอกสาร (M:N) +CREATE TABLE correspondence_references ( + src_correspondence_id INT COMMENT 'ID เอกสารต้นทาง', + tgt_correspondence_id INT COMMENT 'ID เอกสารเป้าหมาย', + PRIMARY KEY (src_correspondence_id, tgt_correspondence_id), + FOREIGN KEY (src_correspondence_id) REFERENCES correspondences(id) ON DELETE CASCADE, + FOREIGN KEY (tgt_correspondence_id) REFERENCES correspondences(id) ON DELETE CASCADE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางเชื่อมการอ้างอิงระหว่างเอกสาร (M :N)'; +-- ===================================================== +-- 4. 📐 approval: RFA (เอกสารขออนุมัติ, Workflows) +-- ===================================================== +-- 3.1 routing Config for Templates +-- รองรับ: Backend Plan T3.1 +-- เหตุผล: เก็บ Logic การเดินเอกสารที่ซับซ้อนกว่า Column ปกติ +CREATE TABLE correspondence_routing_templates ( + id INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของแม่แบบ', + template_name VARCHAR(255) NOT NULL COMMENT 'ชื่อแม่แบบ', + description TEXT COMMENT 'คำอธิบาย', + project_id INT NULL COMMENT 'ID โครงการ (ถ้าเป็นแม่แบบเฉพาะโครงการ)', + -- NULL = แม่แบบทั่วไป + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง', + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'วันที่แก้ไขล่าสุด', + is_active BOOLEAN DEFAULT TRUE, + workflow_config JSON NULL COMMENT 'Routing Logic Configuration', + UNIQUE KEY ux_routing_template_name_project (template_name, project_id), + CONSTRAINT fk_crt_project FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'แม่แบบสายงานการส่งต่อเอกสารขออนุมัติ'; +CREATE TABLE correspondence_status_transitions( + type_id INT NOT NULL COMMENT 'ID ของประเภทหนังสือ', + from_status_id INT NOT NULL COMMENT 'ID ของสถานะต้นทาง', + to_status_id INT NOT NULL COMMENT 'ID ของสถานะปลายทาง', + PRIMARY KEY (type_id, from_status_id, to_status_id), + CONSTRAINT fk_cst_type FOREIGN KEY (type_id) REFERENCES correspondence_types(id), + CONSTRAINT fk_cst_from FOREIGN KEY (from_status_id) REFERENCES correspondence_status(id), + CONSTRAINT fk_cst_to FOREIGN KEY (to_status_id) REFERENCES correspondence_status(id) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางสถานะที่อนุญาตให้เปลี่ยนแปลงได้ตามประเภทหนังสือ'; +-- 1.18.1 correspondence_routing_template_steps Table +CREATE TABLE correspondence_routing_template_steps ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'ID ของขั้นตอน', + template_id INT NOT NULL COMMENT 'ID ของแม่แบบ', + sequence INT NOT NULL COMMENT 'ลำดับขั้นตอน', + to_organization_id INT NOT NULL COMMENT 'ID องค์กรณ์ผู้รับในขั้นตอนนี้', + step_purpose ENUM('FOR_APPROVAL', 'FOR_REVIEW', 'FOR_INFORMATION ') NOT NULL DEFAULT 'FOR_REVIEW ' COMMENT 'วัตถุประสงค์ของขั้นตอนนี้', + expected_days INT NULL, + UNIQUE KEY ux_cor_template_sequence (template_id, sequence), + CONSTRAINT fk_cwts_template FOREIGN KEY (template_id) REFERENCES correspondence_routing_templates(id) ON DELETE CASCADE, + CONSTRAINT fk_cwts_org FOREIGN KEY (to_organization_id) REFERENCES organizations(id) ON DELETE CASCADE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ขั้นตอนในแม่แบบ Workflow การส่งต่อเอกสาร'; +-- 1.19.1 correspondence_routings +-- 3.2 State Context for Running Workflows +-- รองรับ: Backend Plan T3.1 +-- เหตุผล: เก็บ Snapshot ข้อมูล ณ ขณะนั้นเพื่อใช้ตัดสินใจใน Step ถัดไป +CREATE TABLE correspondence_routings ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'ID ของขั้นตอน', + correspondence_id INT NOT NULL COMMENT 'ID ของเอกสาร(FK->correspondence_revisions)', + template_id INT NULL COMMENT 'ID ของแม่แบบที่ใช้ (ถ้ามี)', + -- สำหรับอ้างอิงถึงแม่แบบ + sequence INT NOT NULL COMMENT 'ลำดับของขั้นตอนการส่งต่อ', + from_organization_id INT NOT NULL COMMENT 'ID ขององค์กรณ์ผู้ส่ง', + to_organization_id INT NOT NULL COMMENT 'ID ขององค์กรณ์ผู้รับ', + step_purpose ENUM( + 'FOR_APPROVAL', + 'FOR_REVIEW', + 'FOR_INFORMATION', + 'FOR_ACTION ' + ) NOT NULL DEFAULT 'FOR_REVIEW ' COMMENT 'วัตถุประสงค์ของขั้นตอนนี้ เช่น เพื่ออนุมัติ, + เพื่อตรวจสอบ, + หรือเพื่อรับทราบ', + status ENUM( + 'SENT', + 'RECEIVED', + 'ACTIONED', + 'FORWARDED', + 'REPLIED ' + ) NOT NULL DEFAULT 'SENT ' COMMENT 'สถานะการดำเนินการของเอกสารในขั้นตอนนี้', + comments TEXT COMMENT 'หมายเหตุ หรือความคิดเห็นในการส่งต่อ', + due_date DATETIME NULL COMMENT 'วันที่ต้องตอบเอกสารในขั้นตอนนี้', + processed_by_user_id INT NULL COMMENT 'ID ของผู้ใช้ที่ดำเนินการในขั้นตอนนี้', + processed_at TIMESTAMP NULL COMMENT 'เวลาที่ดำเนินการ', + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'เวลาที่สร้างขั้นตอนนี้', + state_context JSON NULL COMMENT 'Snapshot of routing state context', + UNIQUE KEY ux_cor_routing_sequence (correspondence_id, sequence), + -- Foreign Keys + CONSTRAINT fk_crs_correspondence FOREIGN KEY (correspondence_id) REFERENCES correspondence_revisions(correspondence_id) ON DELETE CASCADE, + CONSTRAINT fk_crs_template FOREIGN KEY (template_id) REFERENCES correspondence_routing_templates(id) ON DELETE + SET NULL, + CONSTRAINT fk_crs_from_org FOREIGN KEY (from_organization_id) REFERENCES organizations(id) ON DELETE CASCADE, + CONSTRAINT fk_crs_to_org FOREIGN KEY (to_organization_id) REFERENCES organizations(id) ON DELETE CASCADE, + CONSTRAINT fk_crs_processed_by_user FOREIGN KEY (processed_by_user_id) REFERENCES users(user_id) ON DELETE + SET NULL +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางติดตาม Workflow การส่งต่อเอกสารทั่วไป'; +-- ตาราง Master สำหรับประเภท RFA +CREATE TABLE rfa_types ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + type_code VARCHAR(20) NOT NULL UNIQUE COMMENT 'รหัสประเภท RFA (เช่น DWG, DOC, MAT)', + type_name VARCHAR(100) NOT NULL COMMENT 'ชื่อประเภท RFA', + description TEXT COMMENT 'คำอธิบาย', + sort_order INT DEFAULT 0 COMMENT 'ลำดับการแสดงผล', + is_active TINYINT(1) DEFAULT 1 COMMENT 'สถานะการใช้งาน ' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master สำหรับประเภท RFA'; +INSERT INTO rfa_types (type_code, type_name, sort_order, is_active) +VALUES ('DWG', 'Shop Drawing', 10, 1), + ('DOC', 'Document', 20, 1), + ('SPC', 'Specification', 21, 1), + ('CAL', 'Calculation', 22, 1), + ('TRP', 'Test Report', 23, 1), + ('SRY', 'Survey Report', 24, 1), + ('QAQC', 'QA / QC Document', 25, 1), + ('MES', 'Method Statement', 30, 1), + ('MAT', 'Material', 40, 1), + ('ASB', 'As - Built', 50, 1), + ('OTH', 'Other', 99, 1); +-- ตาราง Master สำหรับสถานะ RFA +CREATE TABLE rfa_status_codes ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + status_code VARCHAR(20) NOT NULL UNIQUE COMMENT 'รหัสสถานะ RFA (เช่น DFT - Draft, FAP - For Approve)', + status_name VARCHAR(100) NOT NULL COMMENT 'ชื่อสถานะ', + description TEXT COMMENT 'คำอธิบาย', + sort_order INT DEFAULT 0 COMMENT 'ลำดับการแสดงผล', + is_active TINYINT(1) DEFAULT 1 COMMENT 'สถานะการใช้งาน ' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master สำหรับสถานะ RFA'; +INSERT INTO rfa_status_codes ( + status_code, + status_name, + description, + sort_order + ) +VALUES ('DFT', 'Draft', 'ฉบับร่าง', 1), + ('FAP', 'For Approve', 'เพื่อขออนุมัติ', 11), + ('FRE', 'For Review', 'เพื่อตรวจสอบ', 12), + ('FCO', 'For Construction', 'เพื่อก่อสร้าง', 20), + ('ASB', 'AS - Built', 'แบบก่อสร้างจริง', 30), + ('OBS', 'Obsolete', 'ไม่ใช้งาน', 80), + ('CC', 'Canceled', 'ยกเลิก', 99); +-- ตาราง Master สำหรับรหัสผลการอนุมัติ RFA +CREATE TABLE rfa_approve_codes ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + approve_code VARCHAR(20) NOT NULL UNIQUE COMMENT 'รหัสผลการอนุมัติ ( + เช่น 1A - Approved, + 3R - Revise + and Resubmit + )', + approve_name VARCHAR(100) NOT NULL COMMENT 'ชื่อผลการอนุมัติ', + description TEXT COMMENT 'คำอธิบาย', + sort_order INT DEFAULT 0 COMMENT 'ลำดับการแสดงผล', + is_active TINYINT(1) DEFAULT 1 COMMENT 'สถานะการใช้งาน ' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master สำหรับรหัสผลการอนุมัติ RFA'; +INSERT INTO rfa_approve_codes ( + approve_code, + approve_name, + sort_order, + is_active + ) +VALUES ('1A', 'Approved by Authority', 10, 1), + ('1C', 'Approved by CSC', 11, 1), + ('1N', 'Approved As Note', 12, 1), + ('1R', 'Approved with Remarks', 13, 1), + ('3C', 'Consultant Comments', 31, 1), + ('3R', 'Revise + and Resubmit', 32, 1), + ('4X', 'Reject', 40, 1), + ('5N', 'No Further Action', 50, 1); +-- ตาราง "แม่" ของ RFA (มีความสัมพันธ์ 1:N กับ rfa_revisions) +CREATE TABLE rfas ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง (RFA Master ID)', + rfa_type_id INT NOT NULL COMMENT 'ประเภท RFA', + 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 +) 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', + correspondence_id INT NOT NULL COMMENT 'Master ID ของ Correspondence', + 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_status_code_id INT NOT NULL COMMENT 'สถานะ RFA', + rfa_approve_code_id INT COMMENT 'ผลการอนุมัติ', + title VARCHAR(255) NOT NULL COMMENT 'เรื่อง', + document_date DATE COMMENT 'วันที่ในเอกสาร', + issued_date DATE COMMENT 'วันที่ส่งขออนุมัติ', + received_date DATETIME COMMENT 'วันที่ลงรับเอกสาร', + approved_date DATE COMMENT 'วันที่อนุมัติ', + description TEXT COMMENT 'คำอธิบายการแก้ไขใน Revision นี้', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้างเอกสาร', + created_by INT COMMENT 'ผู้สร้าง', + updated_by INT COMMENT 'ผู้แก้ไขล่าสุด', + FOREIGN KEY (correspondence_id) REFERENCES correspondences(id) ON DELETE CASCADE, + 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) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง "ลูก" เก็บประวัติ (Revisions) ของ rfas (1 :N)'; +-- ตารางเชื่อมระหว่าง rfa_revisions (ที่เป็นประเภท DWG) กับ shop_drawing_revisions (M:N) +CREATE TABLE rfa_items ( + rfarev_correspondence_id INT COMMENT 'ID ของ RFA Revision', + shop_drawing_revision_id INT COMMENT 'ID ของ Shop Drawing Revision', + PRIMARY KEY ( + rfarev_correspondence_id, + shop_drawing_revision_id + ), + FOREIGN KEY (rfarev_correspondence_id) REFERENCES rfa_revisions(correspondence_id) ON DELETE CASCADE, + FOREIGN KEY (shop_drawing_revision_id) REFERENCES shop_drawing_revisions(id) ON DELETE CASCADE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางเชื่อมระหว่าง rfa_revisions (ที่เป็นประเภท DWG) กับ shop_drawing_revisions (M :N)'; +-- ตาราง Master เก็บแม่แบบสายอนุมัติ +-- 3.1 Workflow Config for Templates +-- รองรับ: Backend Plan T3.1 +-- เหตุผล: เก็บ Logic การเดินเอกสารที่ซับซ้อนกว่า Column ปกติ +CREATE TABLE rfa_workflow_templates ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + template_name VARCHAR(100) NOT NULL COMMENT 'ชื่อแม่แบบสายอนุมัติ', + description TEXT COMMENT 'คำอธิบาย', + is_active TINYINT(1) DEFAULT 1 COMMENT 'สถานะการใช้งาน', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'วันที่แก้ไขล่าสุด', + workflow_config JSON NULL COMMENT 'State Machine Configuration Rules ' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master เก็บแม่แบบสายอนุมัติ'; +-- ตารางลูก เก็บขั้นตอนในแม่แบบ +CREATE TABLE rfa_workflow_template_steps ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + template_id INT NOT NULL COMMENT 'ID ของแม่แบบ', + step_number INT NOT NULL COMMENT 'ลำดับขั้นตอน', + organization_id INT NOT NULL COMMENT 'องค์กรที่รับผิดชอบ', + role_id INT COMMENT 'บทบาทที่รับผิดชอบ', + action_type ENUM('REVIEW', 'APPROVE', 'ACKNOWLEDGE ') COMMENT 'ประเภทการกระทำ', + duration_days INT COMMENT 'ระยะเวลาที่กำหนด (วัน)', + is_optional BOOLEAN DEFAULT FALSE COMMENT 'เป็นขั้นตอนเลือกหรือไม่', + FOREIGN KEY (template_id) REFERENCES rfa_workflow_templates(id) ON DELETE CASCADE, + FOREIGN KEY (organization_id) REFERENCES organizations(id), + FOREIGN KEY (role_id) REFERENCES roles(role_id) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางลูก เก็บขั้นตอนในแม่แบบ'; +-- ตารางประวัติ (Log) การอนุมัติของ RFA จริงตามสายงาน +-- 3.2 State Context for Running Workflows +-- รองรับ: Backend Plan T3.1 +-- เหตุผล: เก็บ Snapshot ข้อมูล ณ ขณะนั้นเพื่อใช้ตัดสินใจใน Step ถัดไป +CREATE TABLE rfa_workflows ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + rfa_revision_id INT NOT NULL COMMENT 'ID ของ RFA Revision', + step_number INT NOT NULL COMMENT 'ลำดับขั้นตอน', + organization_id INT NOT NULL COMMENT 'องค์กรที่รับผิดชอบ', + assigned_to INT COMMENT 'ผู้ใช้ที่ได้รับมอบหมาย', + action_type ENUM('REVIEW', 'APPROVE', 'ACKNOWLEDGE ') COMMENT 'ประเภทการกระทำ', + status ENUM( + 'PENDING', + 'IN_PROGRESS', + 'COMPLETED', + 'REJECTED ' + ) COMMENT 'สถานะ', + comments TEXT COMMENT 'ความคิดเห็น', + completed_at DATETIME COMMENT 'วันที่เสร็จสิ้น', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'วันที่แก้ไขล่าสุด', + state_context JSON NULL COMMENT 'Snapshot of workflow state context', + FOREIGN KEY (rfa_revision_id) REFERENCES rfa_revisions(id) ON DELETE CASCADE, + FOREIGN KEY (organization_id) REFERENCES organizations(id), + FOREIGN KEY (assigned_to) REFERENCES users(user_id) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางประวัติ (Log) การอนุมัติของ RFA จริงตามสายงาน'; +-- ===================================================== +-- 5. 📐 Drawings (แบบ, หมวดหมู่) +-- ===================================================== +-- ตาราง Master สำหรับ "เล่ม" ของแบบคู่สัญญา +CREATE TABLE contract_drawing_volumes ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + project_id INT NOT NULL COMMENT 'โครงการ', + volume_code VARCHAR(50) NOT NULL COMMENT 'รหัสเล่ม', + volume_name VARCHAR(255) NOT NULL COMMENT 'ชื่อเล่ม', + description TEXT COMMENT 'คำอธิบาย', + sort_order INT DEFAULT 0 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, + UNIQUE KEY ux_volume_project (project_id, volume_code) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master สำหรับ "เล่ม" ของแบบคู่สัญญา'; +-- ตาราง Master สำหรับ "หมวดหมู่หลัก" ของแบบคู่สัญญา +CREATE TABLE contract_drawing_cats ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + project_id INT NOT NULL COMMENT 'โครงการ', + cat_code VARCHAR(50) NOT NULL COMMENT 'รหัสหมวดหมู่หลัก', + cat_name VARCHAR(255) NOT NULL COMMENT 'ชื่อหมวดหมู่หลัก', + description TEXT COMMENT 'คำอธิบาย', + sort_order INT DEFAULT 0 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, + UNIQUE KEY ux_cat_project (project_id, cat_code) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master สำหรับ "หมวดหมู่หลัก" ของแบบคู่สัญญา'; +-- ตาราง Master สำหรับ "หมวดหมู่ย่อย" ของแบบคู่สัญญา +CREATE TABLE contract_drawing_sub_cats ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + project_id INT NOT NULL COMMENT 'โครงการ', + sub_cat_code VARCHAR(50) NOT NULL COMMENT 'รหัสหมวดหมู่ย่อย', + sub_cat_name VARCHAR(255) NOT NULL COMMENT 'ชื่อหมวดหมู่ย่อย', + description TEXT COMMENT 'คำอธิบาย', + sort_order INT DEFAULT 0 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, + UNIQUE KEY ux_subcat_project (project_id, sub_cat_code) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master สำหรับ "หมวดหมู่ย่อย" ของแบบคู่สัญญา'; +-- ตารางเชื่อมระหว่าง หมวดหมู่หลัก-ย่อย (M:N) +CREATE TABLE contract_drawing_subcat_cat_maps ( + project_id INT COMMENT 'ID ของโครงการ', + sub_cat_id INT COMMENT 'ID ของหมวดหมู่ย่อย', + cat_id INT COMMENT 'ID ของหมวดหมู่หลัก', + PRIMARY KEY (project_id, sub_cat_id, cat_id), + FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY (sub_cat_id) REFERENCES contract_drawing_sub_cats(id) ON DELETE CASCADE, + FOREIGN KEY (cat_id) REFERENCES contract_drawing_cats(id) ON DELETE CASCADE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางเชื่อมระหว่าง หมวดหมู่หลัก - ย่อย (M :N)'; +-- ตาราง Master เก็บข้อมูล "แบบคู่สัญญา" +CREATE TABLE contract_drawings ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + project_id INT NOT NULL COMMENT 'โครงการ', + condwg_no VARCHAR(255) NOT NULL COMMENT 'เลขที่แบบสัญญา', + title VARCHAR(255) NOT NULL COMMENT 'ชื่อแบบสัญญา', + sub_cat_id INT COMMENT 'หมวดหมู่ย่อย', + volume_id INT COMMENT 'เล่ม', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'วันที่แก้ไขล่าสุด', + deleted_at DATETIME NULL COMMENT 'วันที่ลบ', + updated_by INT COMMENT 'ผู้แก้ไขล่าสุด', + FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY (sub_cat_id) REFERENCES contract_drawing_sub_cats(id) ON DELETE RESTRICT, + FOREIGN KEY (volume_id) REFERENCES contract_drawing_volumes(id) ON DELETE RESTRICT, + UNIQUE KEY ux_condwg_no_project (project_id, condwg_no) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master เก็บข้อมูล "แบบคู่สัญญา"'; +-- ตาราง Master สำหรับ "หมวดหมู่หลัก" ของแบบก่อสร้าง +CREATE TABLE shop_drawing_main_categories ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + main_category_code VARCHAR(50) NOT NULL UNIQUE COMMENT 'รหัสหมวดหมู่หลัก (เช่น ARCH, STR)', + main_category_name VARCHAR(255) NOT NULL COMMENT 'ชื่อหมวดหมู่หลัก', + description TEXT COMMENT 'คำอธิบาย', + sort_order INT DEFAULT 0 COMMENT 'ลำดับการแสดงผล', + is_active TINYINT(1) DEFAULT 1 COMMENT 'สถานะการใช้งาน', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'วันที่แก้ไขล่าสุด ' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master สำหรับ "หมวดหมู่หลัก" ของแบบก่อสร้าง'; +-- ตาราง Master สำหรับ "หมวดหมู่ย่อย" ของแบบก่อสร้าง +CREATE TABLE shop_drawing_sub_categories ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + sub_category_code VARCHAR(50) NOT NULL UNIQUE COMMENT 'รหัสหมวดหมู่ย่อย (เช่น STR - COLUMN)', + sub_category_name VARCHAR(255) NOT NULL COMMENT 'ชื่อหมวดหมู่ย่อย', + main_category_id INT NOT NULL COMMENT 'หมวดหมู่หลัก', + description TEXT COMMENT 'คำอธิบาย', + sort_order INT DEFAULT 0 COMMENT 'ลำดับการแสดงผล', + is_active TINYINT(1) DEFAULT 1 COMMENT 'สถานะการใช้งาน', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'วันที่แก้ไขล่าสุด', + FOREIGN KEY (main_category_id) REFERENCES shop_drawing_main_categories(id) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master สำหรับ "หมวดหมู่ย่อย" ของแบบก่อสร้าง'; +-- ตาราง Master เก็บข้อมูล "แบบก่อสร้าง" +CREATE TABLE shop_drawings ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + project_id INT NOT NULL COMMENT 'โครงการ', + drawing_number VARCHAR(100) NOT NULL UNIQUE COMMENT 'เลขที่ Shop Drawing', + title VARCHAR(500) NOT NULL COMMENT 'ชื่อแบบ', + main_category_id INT NOT NULL COMMENT 'หมวดหมู่หลัก', + sub_category_id INT NOT NULL COMMENT 'หมวดหมู่ย่อย', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'วันที่แก้ไขล่าสุด', + deleted_at DATETIME NULL COMMENT 'วันที่ลบ', + updated_by INT COMMENT 'ผู้แก้ไขล่าสุด', + FOREIGN KEY (project_id) REFERENCES projects(id), + FOREIGN KEY (main_category_id) REFERENCES shop_drawing_main_categories(id), + FOREIGN KEY (sub_category_id) REFERENCES shop_drawing_sub_categories(id) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master เก็บข้อมูล "แบบก่อสร้าง"'; +-- ตาราง "ลูก" เก็บประวัติ (Revisions) ของ shop_drawings (1:N) +CREATE TABLE shop_drawing_revisions ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของ Revision', + shop_drawing_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)', + revision_date DATE COMMENT 'วันที่ของ Revision', + description TEXT COMMENT 'คำอธิบายการแก้ไข', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง', + FOREIGN KEY (shop_drawing_id) REFERENCES shop_drawings(id) ON DELETE CASCADE, + UNIQUE KEY ux_sd_rev_drawing_revision (shop_drawing_id, revision_number) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง "ลูก" เก็บประวัติ (Revisions) ของ shop_drawings (1 :N)'; +-- ตารางเชื่อมระหว่าง shop_drawing_revisions กับ contract_drawings (M:N) +CREATE TABLE shop_drawing_revision_contract_refs ( + shop_drawing_revision_id INT COMMENT 'ID ของ Shop Drawing Revision', + contract_drawing_id INT COMMENT 'ID ของ Contract Drawing', + PRIMARY KEY (shop_drawing_revision_id, contract_drawing_id), + FOREIGN KEY (shop_drawing_revision_id) REFERENCES shop_drawing_revisions(id) ON DELETE CASCADE, + FOREIGN KEY (contract_drawing_id) REFERENCES contract_drawings(id) ON DELETE CASCADE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางเชื่อมระหว่าง shop_drawing_revisions กับ contract_drawings (M :N)'; +-- ===================================================== +-- 6. 🔄 Circulations (ใบเวียนภายใน) +-- ===================================================== +-- ตาราง Master เก็บสถานะใบเวียน +CREATE TABLE circulation_status_codes ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + code VARCHAR(20) NOT NULL UNIQUE COMMENT 'รหัสสถานะการดำเนินงาน', + description VARCHAR(50) NOT NULL COMMENT 'คำอธิบายสถานะการดำเนินงาน', + sort_order INT DEFAULT 0 COMMENT 'ลำดับการแสดงผล', + is_active TINYINT(1) DEFAULT 1 COMMENT 'สถานะการใช้งาน ' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master เก็บสถานะใบเวียน'; +INSERT INTO circulation_status_codes (code, description, sort_order) +VALUES ('OPEN', 'Open', 1), + ('IN_REVIEW', 'In Review', 2), + ('COMPLETED', 'ปCompleted', 3), + ('CANCELLED', 'Cancelled / Withdrawn', 9); +-- ตาราง "แม่" ของใบเวียนเอกสารภายใน +CREATE TABLE circulations ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตารางใบเวียน', + correspondence_id INT UNIQUE COMMENT 'ID ของเอกสาร (จากตาราง correspondences)', + organization_id INT NOT NULL COMMENT 'ID ขององค์กรณ์ที่เป็นเจ้าของใบเวียนนี้', + circulation_no VARCHAR(100) NOT NULL COMMENT 'เลขที่ใบเวียน', + circulation_subject VARCHAR(500) NOT NULL COMMENT 'เรื่องใบเวียน', + circulation_status_code VARCHAR(20) NOT NULL COMMENT 'รหัสสถานะใบเวียน', + created_by_user_id INT NOT NULL COMMENT 'ID ของผู้สร้างใบเวียน', + submitted_at TIMESTAMP NULL COMMENT 'วันที่ส่งใบเวียน', + closed_at TIMESTAMP NULL COMMENT 'วันที่ปิดใบเวียน', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'วันที่แก้ไขล่าสุด', + FOREIGN KEY (correspondence_id) REFERENCES correspondences(id), + FOREIGN KEY (organization_id) REFERENCES organizations(id), + FOREIGN KEY (circulation_status_code) REFERENCES circulation_status_codes(code), + FOREIGN KEY (created_by_user_id) REFERENCES users(user_id) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง "แม่" ของใบเวียนเอกสารภายใน'; +-- ตาราง Master เก็บแม่แบบสายงาน +CREATE TABLE circulation_templates ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + template_name VARCHAR(100) NOT NULL COMMENT 'ชื่อแม่แบบสายงาน', + description TEXT COMMENT 'คำอธิบาย', + organization_id INT NOT NULL COMMENT 'องค์กรเจ้าของแม่แบบ', + is_active TINYINT(1) DEFAULT 1 COMMENT 'สถานะการใช้งาน', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'วันที่แก้ไขล่าสุด', + FOREIGN KEY (organization_id) REFERENCES organizations(id) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master เก็บแม่แบบสายงาน'; +-- ตารางลูก เก็บขั้นตอนในแม่แบบ +CREATE TABLE circulation_template_assignees ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + template_id INT NOT NULL COMMENT 'ID ของแม่แบบ', + step_number INT NOT NULL COMMENT 'ลำดับขั้นตอน', + organization_id INT NOT NULL COMMENT 'องค์กรที่รับผิดชอบ', + role_id INT COMMENT 'บทบาทที่รับผิดชอบ', + duration_days INT COMMENT 'ระยะเวลาที่กำหนด (วัน)', + is_optional BOOLEAN DEFAULT FALSE COMMENT 'เป็นขั้นตอนเลือกหรือไม่', + FOREIGN KEY (template_id) REFERENCES circulation_templates(id) ON DELETE CASCADE, + FOREIGN KEY (organization_id) REFERENCES organizations(id), + FOREIGN KEY (role_id) REFERENCES roles(role_id) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางลูก เก็บขั้นตอนในแม่แบบ'; +-- ตารางประวัติ (Log) การส่งต่อของเอกสารจริงตาม Workflow +CREATE TABLE circulation_routings ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง', + circulation_id INT NOT NULL COMMENT 'ID ของใบเวียน', + step_number INT NOT NULL COMMENT 'ลำดับขั้นตอน', + organization_id INT NOT NULL COMMENT 'องค์กรที่รับผิดชอบ', + assigned_to INT COMMENT 'ผู้ใช้ที่ได้รับมอบหมาย', + status ENUM( + 'PENDING', + 'IN_PROGRESS', + 'COMPLETED', + 'REJECTED ' + ) COMMENT 'สถานะ', + comments TEXT COMMENT 'ความคิดเห็น', + completed_at DATETIME COMMENT 'วันที่เสร็จสิ้น', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'วันที่แก้ไขล่าสุด', + FOREIGN KEY (circulation_id) REFERENCES circulations(id) ON DELETE CASCADE, + FOREIGN KEY (organization_id) REFERENCES organizations(id), + FOREIGN KEY (assigned_to) REFERENCES users(user_id) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางประวัติ (Log) การส่งต่อของเอกสารจริงตาม Workflow'; +-- ===================================================== +-- 7. 📤 Transmittals (เอกสารนำส่ง) +-- ===================================================== +-- ตารางข้อมูลเฉพาะของเอกสารนำส่ง (เป็นตารางลูก 1:1 ของ correspondences) +CREATE TABLE transmittals ( + correspondence_id INT PRIMARY KEY COMMENT 'ID ของเอกสาร', + purpose ENUM( + 'FOR_APPROVAL', + 'FOR_INFORMATION', + 'FOR_REVIEW', + 'OTHER ' + ) COMMENT 'วัตถุประสงค์', + remarks TEXT COMMENT 'หมายเหตุ', + FOREIGN KEY (correspondence_id) REFERENCES correspondences(id) ON DELETE CASCADE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางข้อมูลเฉพาะของเอกสารนำส่ง (เป็นตารางลูก 1 :1 ของ correspondences)'; +-- ตารางเชื่อมระหว่าง transmittals และเอกสารที่นำส่ง (M:N) +CREATE TABLE transmittal_items ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของรายการ', + transmittal_id INT NOT NULL COMMENT 'ID ของ Transmittal', + item_correspondence_id INT NOT NULL COMMENT 'ID ของเอกสารที่แนบไป', + quantity INT DEFAULT 1 COMMENT 'จำนวน', + remarks VARCHAR(255) COMMENT 'หมายเหตุสำหรับรายการนี้', + FOREIGN KEY (transmittal_id) REFERENCES transmittals(correspondence_id) ON DELETE CASCADE, + FOREIGN KEY (item_correspondence_id) REFERENCES correspondences(id) ON DELETE CASCADE, + UNIQUE KEY ux_transmittal_item (transmittal_id, item_correspondence_id) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางเชื่อมระหว่าง transmittals และเอกสารที่นำส่ง (M :N)'; +-- ===================================================== +-- 8. 📎 File Management (ไฟล์แนบ) +-- ===================================================== +-- ตาราง "กลาง" เก็บไฟล์แนบทั้งหมดของระบบ +-- 2.2 Attachments - Two-Phase Storage & Security +-- รองรับ: Backend Plan T2.2, Req 3.9.1 +-- เหตุผล: จัดการไฟล์ขยะ (Orphan Files) และตรวจสอบความถูกต้องไฟล์ +CREATE TABLE attachments ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของไฟล์แนบ', + original_filename VARCHAR(255) NOT NULL COMMENT 'ชื่อไฟล์ดั้งเดิมตอนอัปโหลด', + stored_filename VARCHAR(255) NOT NULL COMMENT 'ชื่อไฟล์ที่เก็บจริงบน Server (ป้องกันชื่อซ้ำ)', + file_path VARCHAR(500) NOT NULL COMMENT 'Path ที่เก็บไฟล์ (บน QNAP / share / dms - data /)', + mime_type VARCHAR(100) NOT NULL COMMENT 'ประเภทไฟล์ (เช่น application / pdf)', + file_size INT NOT NULL COMMENT 'ขนาดไฟล์ (bytes)', + is_temporary BOOLEAN DEFAULT TRUE COMMENT 'True = ยังไม่ Commit ลง DB จริง', + temp_id VARCHAR(100) NULL COMMENT 'ID ชั่วคราวสำหรับอ้างอิงตอน Upload Phase 1', + uploaded_by_user_id INT NOT NULL COMMENT 'ผู้อัปโหลดไฟล์', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่อัปโหลด', + expires_at DATETIME NULL COMMENT 'เวลาหมดอายุของไฟล์ Temp', + checksum VARCHAR(64) NULL COMMENT 'SHA -256 Checksum', + FOREIGN KEY (uploaded_by_user_id) REFERENCES users(user_id) ON DELETE CASCADE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง "กลาง" เก็บไฟล์แนบทั้งหมดของระบบ'; +-- ตารางเชื่อม correspondences กับ attachments (M:N) +CREATE TABLE correspondence_attachments ( + correspondence_id INT COMMENT 'ID ของเอกสาร', + attachment_id INT COMMENT 'ID ของไฟล์แนบ', + is_main_document BOOLEAN DEFAULT FALSE COMMENT '(1 = ไฟล์หลัก)', + PRIMARY KEY (correspondence_id, attachment_id), + FOREIGN KEY (correspondence_id) REFERENCES correspondences(id) ON DELETE CASCADE, + FOREIGN KEY (attachment_id) REFERENCES attachments(id) ON DELETE CASCADE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางเชื่อม correspondences กับ attachments (M :N)'; +-- ตารางเชื่อม circulations กับ attachments (M:N) +CREATE TABLE circulation_attachments ( + circulation_id INT COMMENT 'ID ของใบเวียน', + attachment_id INT COMMENT 'ID ของไฟล์แนบ', + is_main_document BOOLEAN DEFAULT FALSE COMMENT '(1 = ไฟล์หลักของใบเวียน)', + PRIMARY KEY (circulation_id, attachment_id), + FOREIGN KEY (circulation_id) REFERENCES circulations(id) ON DELETE CASCADE, + FOREIGN KEY (attachment_id) REFERENCES attachments(id) ON DELETE CASCADE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางเชื่อม circulations กับ attachments (M :N)'; +-- ตารางเชื่อม shop_drawing_revisions กับ attachments (M:N) +CREATE TABLE shop_drawing_revision_attachments ( + shop_drawing_revision_id INT COMMENT 'ID ของ Shop Drawing Revision', + attachment_id INT COMMENT 'ID ของไฟล์แนบ', + file_type ENUM('PDF', 'DWG', 'SOURCE', 'OTHER ') COMMENT 'ประเภทไฟล์', + is_main_document BOOLEAN DEFAULT FALSE COMMENT '(1 = ไฟล์หลัก)', + PRIMARY KEY (shop_drawing_revision_id, attachment_id), + FOREIGN KEY (shop_drawing_revision_id) REFERENCES shop_drawing_revisions(id) ON DELETE CASCADE, + FOREIGN KEY (attachment_id) REFERENCES attachments(id) ON DELETE CASCADE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางเชื่อม shop_drawing_revisions กับ attachments (M :N)'; +-- ตารางเชื่อม contract_drawings กับ attachments (M:N) +CREATE TABLE contract_drawing_attachments ( + contract_drawing_id INT COMMENT 'ID ของ Contract Drawing', + attachment_id INT COMMENT 'ID ของไฟล์แนบ', + file_type ENUM('PDF', 'DWG', 'SOURCE', 'OTHER ') COMMENT 'ประเภทไฟล์', + is_main_document BOOLEAN DEFAULT FALSE COMMENT '(1 = ไฟล์หลัก)', + PRIMARY KEY (contract_drawing_id, attachment_id), + FOREIGN KEY (contract_drawing_id) REFERENCES contract_drawings(id) ON DELETE CASCADE, + FOREIGN KEY (attachment_id) REFERENCES attachments(id) ON DELETE CASCADE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางเชื่อม contract_drawings กับ attachments (M :N)'; +-- ===================================================== +-- 9. 🔢 Document Numbering (การสร้างเลขที่เอกสาร) +-- ===================================================== +-- ตาราง Master เก็บ "รูปแบบ" Template ของเลขที่เอกสาร +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 })', + 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 (project_id, correspondence_type_id) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master เก็บ "รูปแบบ" Template ของเลขที่เอกสาร'; +-- ตารางเก็บ "ตัวนับ" (Running Number) ล่าสุด +-- 2.1 Document Numbering - Optimistic Locking +-- รองรับ: Backend Plan T2.3, Req 3.10.5 +-- เหตุผล: ป้องกัน Race Condition เวลาขอเลขที่เอกสารพร้อมกัน +CREATE TABLE document_number_counters ( + project_id INT COMMENT 'โครงการ', + originator_organization_id INT COMMENT 'องค์กรผู้ส่ง', + correspondence_type_id INT COMMENT 'ประเภทเอกสาร', + current_year INT COMMENT 'ปี ค.ศ.ของตัวนับ', + version INT DEFAULT 0 NOT NULL COMMENT 'Optimistic Lock Version', + last_number INT DEFAULT 0 COMMENT 'เลขที่ล่าสุดที่ใช้ไปแล้ว', + PRIMARY KEY ( + project_id, + originator_organization_id, + correspondence_type_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 (correspondence_type_id) REFERENCES correspondence_types(id) ON DELETE CASCADE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางเก็บ "ตัวนับ" (Running Number) ล่าสุด'; +-- ===================================================== +-- 10. ⚙️ System & Logs (ระบบและ Log) +-- ===================================================== +-- 1.1 JSON Schemas Registry +-- รองรับ: Backend Plan T2.5.1, Req 6.11.1 +-- เหตุผล: เพื่อ Validate โครงสร้าง JSON Details ของเอกสารแต่ละประเภทแบบ Centralized +CREATE TABLE IF NOT EXISTS json_schemas ( + id INT AUTO_INCREMENT PRIMARY KEY, + schema_code VARCHAR(100) NOT NULL UNIQUE COMMENT 'รหัส Schema เช่น RFA_DWG_V1, + CORR_GENERIC', + version INT NOT NULL DEFAULT 1 COMMENT 'เวอร์ชันของ Schema', + schema_definition JSON NOT NULL COMMENT 'โครงสร้าง JSON Schema (Standard Format)', + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_schema_code (schema_code) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci; +-- 1.2 User Preferences +-- รองรับ: Req 5.5, 6.8.3 +-- เหตุผล: แยกการตั้งค่า Notification และ UI ออกจากตาราง Users หลัก +CREATE TABLE IF NOT EXISTS user_preferences ( + user_id INT PRIMARY KEY, + notify_email BOOLEAN DEFAULT TRUE, + notify_line BOOLEAN DEFAULT TRUE, + digest_mode BOOLEAN DEFAULT FALSE COMMENT 'รับแจ้งเตือนแบบรวม (Digest) แทน Real - time', + ui_theme VARCHAR(20) DEFAULT 'light', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT fk_user_prefs_user FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci; +-- ตารางเก็บบันทึกการกระทำของผู้ใช้ +-- 4.1 Audit Logs Enhancements +-- รองรับ: Req 6.1 +-- เหตุผล: รองรับ Distributed Tracing และระบุความรุนแรง +CREATE TABLE audit_logs ( + audit_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของ Log', + request_id VARCHAR(100) NULL COMMENT 'Trace ID linking to app logs', + user_id INT COMMENT 'ผู้กระทำ', + action VARCHAR(100) NOT NULL COMMENT 'การกระทำ ( + เช่น rfa.create, + correspondence.update, + login.success + )', + severity ENUM('INFO', 'WARN', 'ERROR', 'CRITICAL ') DEFAULT 'INFO', + entity_type VARCHAR(50) COMMENT 'ตาราง / โมดูล (เช่น ''rfa '', ''correspondence '')', + entity_id VARCHAR(50) COMMENT 'Primary ID ของระเบียนที่ได้รับผลกระทำ', + details_json JSON COMMENT 'ข้อมูลบริบท', + ip_address VARCHAR(45) COMMENT 'IP Address', + user_agent VARCHAR(255) COMMENT 'User Agent', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'เวลาที่กระทำ', + FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE + SET NULL +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางเก็บบันทึกการกระทำของผู้ใช้'; +-- ตารางสำหรับจัดการการแจ้งเตือน (Email/Line/System) +CREATE TABLE notifications ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของการแจ้งเตือน', + user_id INT NOT NULL COMMENT 'ID ผู้ใช้', + title VARCHAR(255) NOT NULL COMMENT 'หัวข้อการแจ้งเตือน', + message TEXT NOT NULL COMMENT 'รายละเอียดการแจ้งเตือน', + notification_type ENUM('EMAIL', 'LINE', 'SYSTEM ') NOT NULL COMMENT 'ประเภท (EMAIL, LINE, SYSTEM)', + is_read BOOLEAN DEFAULT FALSE COMMENT 'สถานะการอ่าน', + entity_type VARCHAR(50) COMMENT 'เช่น ''rfa '', + ''circulation ''', + entity_id INT COMMENT 'ID ของเอนทิตีที่เกี่ยวข้อง', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง', + FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางสำหรับจัดการการแจ้งเตือน (Email / Line / System)'; +-- ตารางสำหรับจัดการดัชนีการค้นหาขั้นสูง (Full-text Search) +CREATE TABLE search_indices ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของดัชนี', + entity_type VARCHAR(50) NOT NULL COMMENT 'ชนิดเอนทิตี (เช่น ''correspondence '', ''rfa '')', + entity_id INT NOT NULL COMMENT 'ID ของเอนทิตี', + content TEXT NOT NULL COMMENT 'เนื้อหาที่จะค้นหา', + indexed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง / อัปเดตัชนี ' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางสำหรับจัดการดัชนีการค้นหาขั้นสูง (Full - text Search)'; +-- ตารางสำหรับบันทึกประวัติการสำรองข้อมูล +CREATE TABLE backup_logs ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของการสำรอง', + backup_type ENUM('DATABASE', 'FILES', 'FULL') NOT NULL COMMENT 'ประเภท (DATABASE, FILES, FULL)', + backup_path VARCHAR(500) NOT NULL COMMENT 'ตำแหน่งไฟล์สำรอง', + file_size BIGINT COMMENT 'ขนาดไฟล์', + status ENUM('STARTED', 'COMPLETED', 'FAILED') NOT NULL COMMENT 'สถานะ', + started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'เวลาเริ่มต้น', + completed_at TIMESTAMP NULL COMMENT 'เวลาเสร็จสิ้น', + error_message TEXT COMMENT 'ข้อความผิดพลาด (ถ้ามี)' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางสำหรับบันทึกประวัติการสำรองข้อมูล'; +-- 4.2 Virtual Columns for JSON Search (ตัวอย่างสำหรับ Correspondence) +-- รองรับ: Backend Plan T2.1, Req 3.11.3 +-- เหตุผล: เพิ่มความเร็วในการ Search/Sort ข้อมูลที่อยู่ใน JSON details +-- หมายเหตุ: ต้องมั่นใจว่า MariaDB เวอร์ชัน 10.11+ รองรับ Syntax นี้ +-- ตัวอย่าง: ดึง Project ID ที่อ้างอิงใน details ออกมาทำ Index +ALTER TABLE correspondence_revisions +ADD COLUMN v_ref_project_id INT GENERATED ALWAYS AS ( + JSON_UNQUOTE(JSON_EXTRACT(details, '$.projectId')) + ) VIRTUAL, + ADD INDEX idx_corr_rev_v_project (v_ref_project_id); +-- ตัวอย่าง: ดึง Document Type ย่อยจาก details +ALTER TABLE correspondence_revisions +ADD COLUMN v_doc_subtype VARCHAR(50) GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(details, '$.subType'))) VIRTUAL, + ADD INDEX idx_corr_rev_v_subtype (v_doc_subtype); +-- ทำแบบเดียวกันกับ RFA Revisions หากมีการเก็บ JSON details +ALTER TABLE rfa_revisions +ADD COLUMN details JSON NULL COMMENT 'RFA Specific Details' +AFTER description; +ALTER TABLE rfa_revisions +ADD COLUMN v_ref_drawing_count INT GENERATED ALWAYS AS ( + JSON_UNQUOTE(JSON_EXTRACT(details, '$.drawingCount')) + ) VIRTUAL; +-- ============================================================ +-- 5. PARTITIONING PREPARATION (Advance - Optional) +-- ============================================================ +-- หมายเหตุ: การทำ Partitioning บนตารางที่มีอยู่แล้ว (audit_logs, notifications) +-- มักจะต้อง Drop Primary Key เดิม แล้วสร้างใหม่โดยรวม Partition Key (created_at) เข้าไป +-- ขั้นตอนนี้ควรทำแยกต่างหากเมื่อระบบเริ่มมีข้อมูลเยอะ หรือทำใน Maintenance Window +-- +-- ตัวอย่าง SQL สำหรับ Audit Logs (Reference Only): +-- ALTER TABLE audit_logs DROP PRIMARY KEY, ADD PRIMARY KEY (audit_id, created_at); +-- ALTER TABLE audit_logs PARTITION BY RANGE (YEAR(created_at)) ( +-- PARTITION p2024 VALUES LESS THAN (2025), +-- PARTITION p2025 VALUES LESS THAN (2026), +-- PARTITION p_future VALUES LESS THAN MAXVALUE +-- ); +-- ===================================================== +-- CREATE INDEXES +-- ===================================================== +-- Indexes for document_number_formats +CREATE INDEX idx_document_number_formats_project ON document_number_formats(project_id); +CREATE INDEX idx_document_number_formats_type ON document_number_formats(correspondence_type_id); +CREATE INDEX idx_document_number_formats_project_type ON document_number_formats(project_id, correspondence_type_id); +-- Indexes for document_number_counters +CREATE INDEX idx_document_number_counters_project ON document_number_counters(project_id); +CREATE INDEX idx_document_number_counters_org ON document_number_counters(originator_organization_id); +CREATE INDEX idx_document_number_counters_type ON document_number_counters(correspondence_type_id); +CREATE INDEX idx_document_number_counters_year ON document_number_counters(current_year); +-- Indexes for tags +CREATE INDEX idx_tags_name ON tags(tag_name); +CREATE INDEX idx_tags_created_at ON tags(created_at); +-- Indexes for correspondence_tags +CREATE INDEX idx_correspondence_tags_correspondence ON correspondence_tags(correspondence_id); +CREATE INDEX idx_correspondence_tags_tag ON correspondence_tags(tag_id); +-- Indexes for audit_logs +CREATE INDEX idx_audit_logs_user ON audit_logs(user_id); +CREATE INDEX idx_audit_logs_action ON audit_logs(action); +CREATE INDEX idx_audit_logs_entity ON audit_logs(entity_type, entity_id); +CREATE INDEX idx_audit_logs_created_at ON audit_logs(created_at); +CREATE INDEX idx_audit_logs_ip ON audit_logs(ip_address); +-- Indexes for notifications +CREATE INDEX idx_notifications_user ON notifications(user_id); +CREATE INDEX idx_notifications_type ON notifications(notification_type); +CREATE INDEX idx_notifications_read ON notifications(is_read); +CREATE INDEX idx_notifications_entity ON notifications(entity_type, entity_id); +CREATE INDEX idx_notifications_created_at ON notifications(created_at); +-- Indexes for search_indices +CREATE INDEX idx_search_indices_entity ON search_indices(entity_type, entity_id); +CREATE INDEX idx_search_indices_indexed_at ON search_indices(indexed_at); +-- Indexes for backup_logs +CREATE INDEX idx_backup_logs_type ON backup_logs(backup_type); +CREATE INDEX idx_backup_logs_status ON backup_logs(status); +CREATE INDEX idx_backup_logs_started_at ON backup_logs(started_at); +CREATE INDEX idx_backup_logs_completed_at ON backup_logs(completed_at); +-- ===================================================== +-- Additional Composite Indexes for Performance +-- ===================================================== +-- Composite index for document_number_counters for faster lookups +CREATE INDEX idx_doc_counter_composite ON document_number_counters( + project_id, + originator_organization_id, + correspondence_type_id, + current_year +); +-- Composite index for notifications for user-specific queries +CREATE INDEX idx_notifications_user_unread ON notifications(user_id, is_read, created_at); +-- Composite index for audit_logs for reporting +CREATE INDEX idx_audit_logs_reporting ON audit_logs(created_at, entity_type, action); +-- Composite index for search_indices for entity-based queries +CREATE INDEX idx_search_entities ON search_indices(entity_type, entity_id, indexed_at); +-- สร้าง Index สำหรับ Cleanup Job +CREATE INDEX idx_attachments_temp_cleanup ON attachments(is_temporary, expires_at); +CREATE INDEX idx_attachments_temp_id ON attachments(temp_id); +CREATE INDEX idx_audit_request_id ON audit_logs(request_id); +-- ===================================================== +-- SQL Script for LCBP3-DMS (V1.4.0) - MariaDB +-- Generated from Data Dictionary +-- ===================================================== +-- ===================================================== +-- 11. 📊 Views & Procedures (วิว และ โปรซีเดอร์) +-- ===================================================== +-- View แสดง Revision "ปัจจุบัน" ของ correspondences ทั้งหมด (ที่ไม่ใช่ RFA) +CREATE VIEW v_current_correspondences AS +SELECT c.id AS correspondence_id, + c.correspondence_number, + c.correspondence_type_id, + ct.type_code AS correspondence_type_code, + ct.type_name AS correspondence_type_name, + c.project_id, + p.project_code, + p.project_name, + c.originator_id, + org.organization_code AS originator_code, + org.organization_name AS originator_name, + cr.id AS revision_id, + cr.revision_number, + cr.revision_label, + cr.title, + cr.document_date, + cr.issued_date, + cr.received_date, + cr.due_date, + cr.correspondence_status_id, + cs.status_code, + cs.status_name, + cr.created_by, + u.username AS created_by_username, + cr.created_at AS revision_created_at +FROM correspondences c + INNER JOIN correspondence_types ct ON c.correspondence_type_id = ct.id + INNER JOIN projects p ON c.project_id = p.id + LEFT JOIN organizations org ON c.originator_id = org.id + INNER JOIN correspondence_revisions cr ON c.id = cr.correspondence_id + INNER JOIN correspondence_status cs ON cr.correspondence_status_id = cs.id + LEFT JOIN users u ON cr.created_by = u.user_id +WHERE cr.is_current = TRUE + AND c.correspondence_type_id NOT IN ( + SELECT id + FROM correspondence_types + WHERE type_code = 'RFA' + ) + AND c.deleted_at IS NULL; +-- View แสดง Revision "ปัจจุบัน" ของ rfa_revisions ทั้งหมด +CREATE VIEW v_current_rfas AS +SELECT r.id AS rfa_id, + r.rfa_type_id, + rt.type_code AS rfa_type_code, + rt.type_name AS rfa_type_name, + rr.correspondence_id, + c.correspondence_number, + c.project_id, + p.project_code, + p.project_name, + c.originator_id, + org.organization_name AS originator_name, + rr.id AS revision_id, + rr.revision_number, + rr.revision_label, + rr.title, + rr.document_date, + rr.issued_date, + rr.received_date, + rr.approved_date, + rr.rfa_status_code_id, + rsc.status_code AS rfa_status_code, + rsc.status_name AS rfa_status_name, + rr.rfa_approve_code_id, + rac.approve_code AS rfa_approve_code, + rac.approve_name AS rfa_approve_name, + rr.created_by, + u.username AS created_by_username, + 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 + INNER JOIN correspondences c ON rr.correspondence_id = c.id + INNER JOIN projects p ON c.project_id = p.id + INNER JOIN organizations org ON c.originator_id = org.id + INNER JOIN rfa_status_codes rsc ON rr.rfa_status_code_id = rsc.id + LEFT JOIN rfa_approve_codes rac ON rr.rfa_approve_code_id = rac.id + LEFT JOIN users u ON rr.created_by = u.user_id +WHERE rr.is_current = TRUE + AND r.deleted_at IS NULL + AND c.deleted_at IS NULL; +-- View แสดงความสัมพันธ์ทั้งหมดระหว่าง Contract, Project, และ Organization +CREATE VIEW v_contract_parties_all AS +SELECT c.id AS contract_id, + c.contract_code, + c.contract_name, + p.id AS project_id, + p.project_code, + p.project_name, + o.id AS organization_id, + o.organization_code, + o.organization_name, + co.role_in_contract +FROM contracts c + INNER JOIN projects p ON c.project_id = p.id + INNER JOIN contract_organizations co ON c.id = co.contract_id + INNER JOIN organizations o ON co.organization_id = o.id +WHERE c.is_active = TRUE; +-- View แสดงรายการ "งานของฉัน" (My Tasks) ที่ยังไม่เสร็จ +CREATE VIEW v_user_tasks AS +SELECT cr.id AS routing_id, + c.id AS circulation_id, + c.circulation_no, + c.circulation_subject, + c.correspondence_id, + corr.correspondence_number, + corr.project_id, + p.project_code, + p.project_name, + cr.assigned_to AS user_id, + u.username, + u.first_name, + u.last_name, + cr.organization_id, + org.organization_name, + cr.step_number, + cr.status AS task_status, + cr.comments, + cr.completed_at, + cr.created_at AS assigned_at, + c.created_at AS circulation_created_at +FROM circulation_routings cr + INNER JOIN circulations c ON cr.circulation_id = c.id + INNER JOIN correspondences corr ON c.correspondence_id = corr.id + INNER JOIN projects p ON corr.project_id = p.id + INNER JOIN organizations org ON cr.organization_id = org.id + INNER JOIN users u ON cr.assigned_to = u.user_id +WHERE cr.status IN ('PENDING', 'IN_PROGRESS') + AND cr.assigned_to IS NOT NULL; +-- View แสดง audit_logs พร้อมข้อมูล username และ email ของผู้กระทำ +CREATE VIEW v_audit_log_details AS +SELECT al.audit_id, + al.user_id, + u.username, + u.email, + u.first_name, + u.last_name, + al.action, + al.entity_type, + al.entity_id, + al.details_json, + al.ip_address, + al.user_agent, + al.created_at +FROM audit_logs al + LEFT JOIN users u ON al.user_id = u.user_id; +-- View รวมสิทธิ์ทั้งหมด (Global + Project) ของผู้ใช้ทุกคน +CREATE VIEW v_user_all_permissions AS -- Global Permissions +SELECT ua.user_id, + ua.role_id, + r.role_name, + rp.permission_id, + p.permission_name, + p.module, + p.scope_level, + ua.organization_id, + NULL AS project_id, + NULL AS contract_id, + 'GLOBAL' AS permission_scope +FROM user_assignments ua + INNER JOIN roles r ON ua.role_id = r.role_id + INNER JOIN role_permissions rp ON ua.role_id = rp.role_id + INNER JOIN permissions p ON rp.permission_id = p.permission_id -- Global scope +WHERE p.is_active = 1 + AND ua.organization_id IS NULL + AND ua.project_id IS NULL + AND ua.contract_id IS NULL +UNION ALL +-- Organization-specific Permissions +SELECT ua.user_id, + ua.role_id, + r.role_name, + rp.permission_id, + p.permission_name, + p.module, + p.scope_level, + ua.organization_id, + NULL AS project_id, + NULL AS contract_id, + 'ORGANIZATION' AS permission_scope +FROM user_assignments ua + INNER JOIN roles r ON ua.role_id = r.role_id + INNER JOIN role_permissions rp ON ua.role_id = rp.role_id + INNER JOIN permissions p ON rp.permission_id = p.permission_id -- Organization scope +WHERE p.is_active = 1 + AND ua.organization_id IS NOT NULL + AND ua.project_id IS NULL + AND ua.contract_id IS NULL +UNION ALL +-- Project-specific Permissions +SELECT ua.user_id, + ua.role_id, + r.role_name, + rp.permission_id, + p.permission_name, + p.module, + p.scope_level, + ua.organization_id, + ua.project_id, + NULL AS contract_id, + 'PROJECT' AS permission_scope +FROM user_assignments ua + INNER JOIN roles r ON ua.role_id = r.role_id + INNER JOIN role_permissions rp ON ua.role_id = rp.role_id + INNER JOIN permissions p ON rp.permission_id = p.permission_id -- Project scope +WHERE p.is_active = 1 + AND ua.project_id IS NOT NULL + AND ua.contract_id IS NULL +UNION ALL +-- Contract-specific Permissions +SELECT ua.user_id, + ua.role_id, + r.role_name, + rp.permission_id, + p.permission_name, + p.module, + p.scope_level, + ua.organization_id, + ua.project_id, + ua.contract_id, + 'CONTRACT' AS permission_scope +FROM user_assignments ua + INNER JOIN roles r ON ua.role_id = r.role_id + INNER JOIN role_permissions rp ON ua.role_id = rp.role_id + INNER JOIN permissions p ON rp.permission_id = p.permission_id -- Contract scope +WHERE p.is_active = 1 + AND ua.contract_id IS NOT NULL; +-- ===================================================== +-- Additional Useful Views +-- ===================================================== +-- View แสดงเอกสารทั้งหมดที่มีไฟล์แนบ +CREATE VIEW v_documents_with_attachments AS +SELECT 'CORRESPONDENCE' AS document_type, + c.id AS document_id, + c.correspondence_number AS document_number, + c.project_id, + p.project_code, + p.project_name, + COUNT(ca.attachment_id) AS attachment_count, + MAX(a.created_at) AS latest_attachment_date +FROM correspondences c + INNER JOIN projects p ON c.project_id = p.id + LEFT JOIN correspondence_attachments ca ON c.id = ca.correspondence_id + LEFT JOIN attachments a ON ca.attachment_id = a.id +WHERE c.deleted_at IS NULL +GROUP BY c.id, + c.correspondence_number, + c.project_id, + p.project_code, + p.project_name +UNION ALL +SELECT 'CIRCULATION' AS document_type, + circ.id AS document_id, + circ.circulation_no AS document_number, + corr.project_id, + p.project_code, + p.project_name, + COUNT(ca.attachment_id) AS attachment_count, + MAX(a.created_at) AS latest_attachment_date +FROM circulations circ + INNER JOIN correspondences corr ON circ.correspondence_id = corr.id + INNER JOIN projects p ON corr.project_id = p.id + LEFT JOIN circulation_attachments ca ON circ.id = ca.circulation_id + LEFT JOIN attachments a ON ca.attachment_id = a.id +GROUP BY circ.id, + circ.circulation_no, + corr.project_id, + p.project_code, + p.project_name +UNION ALL +SELECT 'SHOP_DRAWING' AS document_type, + sdr.id AS document_id, + sd.drawing_number AS document_number, + sd.project_id, + p.project_code, + p.project_name, + COUNT(sdra.attachment_id) AS attachment_count, + MAX(a.created_at) AS latest_attachment_date +FROM shop_drawing_revisions sdr + INNER JOIN shop_drawings sd ON sdr.shop_drawing_id = sd.id + INNER JOIN projects p ON sd.project_id = p.id + LEFT JOIN shop_drawing_revision_attachments sdra ON sdr.id = sdra.shop_drawing_revision_id + LEFT JOIN attachments a ON sdra.attachment_id = a.id +WHERE sd.deleted_at IS NULL +GROUP BY sdr.id, + sd.drawing_number, + sd.project_id, + p.project_code, + p.project_name +UNION ALL +SELECT 'CONTRACT_DRAWING' AS document_type, + cd.id AS document_id, + cd.condwg_no AS document_number, + cd.project_id, + p.project_code, + p.project_name, + COUNT(cda.attachment_id) AS attachment_count, + MAX(a.created_at) AS latest_attachment_date +FROM contract_drawings cd + INNER JOIN projects p ON cd.project_id = p.id + LEFT JOIN contract_drawing_attachments cda ON cd.id = cda.contract_drawing_id + LEFT JOIN attachments a ON cda.attachment_id = a.id +WHERE cd.deleted_at IS NULL +GROUP BY cd.id, + cd.condwg_no, + cd.project_id, + p.project_code, + p.project_name; +-- View แสดงสถิติเอกสารตามประเภทและสถานะ +CREATE VIEW v_document_statistics AS +SELECT p.id AS project_id, + p.project_code, + p.project_name, + ct.id AS correspondence_type_id, + ct.type_code, + ct.type_name, + cs.id AS status_id, + cs.status_code, + cs.status_name, + COUNT(DISTINCT c.id) AS document_count, + COUNT(DISTINCT cr.id) AS revision_count +FROM projects p + CROSS JOIN correspondence_types ct + CROSS JOIN correspondence_status cs + LEFT JOIN correspondences c ON p.id = c.project_id + AND ct.id = c.correspondence_type_id + LEFT JOIN correspondence_revisions cr ON c.id = cr.correspondence_id + AND cs.id = cr.correspondence_status_id + AND cr.is_current = TRUE +WHERE p.is_active = 1 + AND ct.is_active = 1 + AND cs.is_active = 1 +GROUP BY p.id, + p.project_code, + p.project_name, + ct.id, + ct.type_code, + ct.type_name, + cs.id, + cs.status_code, + cs.status_name; +-- ===================================================== +-- Indexes for View Performance Optimization +-- ===================================================== +-- Indexes for v_current_correspondences performance +CREATE INDEX idx_correspondences_type_project ON correspondences(correspondence_type_id, project_id); +CREATE INDEX idx_corr_revisions_current_status ON correspondence_revisions(is_current, correspondence_status_id); +CREATE INDEX idx_corr_revisions_correspondence_current ON correspondence_revisions(correspondence_id, is_current); +-- Indexes for v_current_rfas performance +CREATE INDEX idx_rfa_revisions_current_status ON rfa_revisions(is_current, rfa_status_code_id); +CREATE INDEX idx_rfa_revisions_rfa_current ON rfa_revisions(rfa_id, is_current); +-- Indexes for v_user_tasks performance +CREATE INDEX idx_circulation_routings_status_assigned ON circulation_routings(status, assigned_to); +CREATE INDEX idx_circulation_routings_circulation_status ON circulation_routings(circulation_id, status); +-- Indexes for document statistics performance +CREATE INDEX idx_correspondences_project_type ON correspondences(project_id, correspondence_type_id); +CREATE INDEX idx_corr_revisions_status_current ON correspondence_revisions(correspondence_status_id, is_current); +SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/01_lcbp3_v1_4_3.sql b/01_lcbp3_v1_4_3.sql index cd560c6..3fbe817 100644 --- a/01_lcbp3_v1_4_3.sql +++ b/01_lcbp3_v1_4_3.sql @@ -1,5 +1,5 @@ -- ========================================================== --- DMS v1.4.2 Document Management System Database +-- DMS v1.4.3 Document Management System Database -- Deploy Script Schema -- Server: Container Station on QNAPQNAP TS-473A -- Database service: MariaDB 10.11 @@ -9,8 +9,8 @@ -- frontend sevice: next.js -- reverse proxy: jc21/nginx-proxy-manager:latest -- cron service: n8n --- DMS v1.4.2 Improvements --- Update: revise fron v1.4.1 Gemini) +-- DMS v1.4.3 Improvements +-- Update: revise fron v1.4.2 add PARTITION to audit_logs & notification -- ========================================================== SET NAMES utf8mb4; SET time_zone = '+07:00'; @@ -1918,7 +1918,7 @@ CREATE TABLE IF NOT EXISTS user_preferences ( -- รองรับ: Req 6.1 -- เหตุผล: รองรับ Distributed Tracing และระบุความรุนแรง CREATE TABLE audit_logs ( - audit_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของ Log', + audit_id BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID ของ Log', request_id VARCHAR(100) NULL COMMENT 'Trace ID linking to app logs', user_id INT COMMENT 'ผู้กระทำ', action VARCHAR(100) NOT NULL COMMENT 'การกระทำ ( @@ -1932,13 +1932,38 @@ CREATE TABLE audit_logs ( details_json JSON COMMENT 'ข้อมูลบริบท', ip_address VARCHAR(45) COMMENT 'IP Address', user_agent VARCHAR(255) COMMENT 'User Agent', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'เวลาที่กระทำ', - FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE - SET NULL -) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางเก็บบันทึกการกระทำของผู้ใช้'; + created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'เวลาที่กระทำ', + -- [แก้ไข] รวม created_at เข้ามาใน Primary Key เพื่อรองรับ Partition + PRIMARY KEY (audit_id, created_at), + -- [แก้ไข] ใช้ Index ธรรมดาแทน Foreign Key เพื่อไม่ให้ติดข้อจำกัดของ Partition Table + INDEX idx_audit_user (user_id), + INDEX idx_audit_action (action), + INDEX idx_audit_entity (entity_type, entity_id), + INDEX idx_audit_created (created_at) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางเก็บบันทึกการกระทำของผู้ใช้' -- [เพิ่ม] คำสั่ง Partition +PARTITION BY RANGE (YEAR(created_at)) ( + PARTITION p_old + VALUES LESS THAN (2024), + PARTITION p2024 + VALUES LESS THAN (2025), + PARTITION p2025 + VALUES LESS THAN (2026), + PARTITION p2026 + VALUES LESS THAN (2027), + PARTITION p2027 + VALUES LESS THAN (2028), + PARTITION p2028 + VALUES LESS THAN (2029), + PARTITION p2029 + VALUES LESS THAN (2030), + PARTITION p2030 + VALUES LESS THAN (2031), + PARTITION p_future + VALUES LESS THAN MAXVALUE +); -- ตารางสำหรับจัดการการแจ้งเตือน (Email/Line/System) CREATE TABLE notifications ( - id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของการแจ้งเตือน', + id INT NOT NULL AUTO_INCREMENT COMMENT 'ID ของการแจ้งเตือน', user_id INT NOT NULL COMMENT 'ID ผู้ใช้', title VARCHAR(255) NOT NULL COMMENT 'หัวข้อการแจ้งเตือน', message TEXT NOT NULL COMMENT 'รายละเอียดการแจ้งเตือน', @@ -1947,9 +1972,35 @@ CREATE TABLE notifications ( entity_type VARCHAR(50) COMMENT 'เช่น ''rfa '', ''circulation ''', entity_id INT COMMENT 'ID ของเอนทิตีที่เกี่ยวข้อง', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง', - FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE -) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางสำหรับจัดการการแจ้งเตือน (Email / Line / System)'; + created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง', + -- [แก้ไข] รวม created_at เข้ามาใน Primary Key + PRIMARY KEY (id, created_at), + -- [แก้ไข] ใช้ Index ธรรมดาแทน Foreign Key + INDEX idx_notif_user (user_id), + INDEX idx_notif_type (notification_type), + INDEX idx_notif_read (is_read), + INDEX idx_notif_created (created_at) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางสำหรับจัดการการแจ้งเตือน (Email / Line / System)' -- [เพิ่ม] คำสั่ง Partition +PARTITION BY RANGE (YEAR(created_at)) ( + PARTITION p_old + VALUES LESS THAN (2024), + PARTITION p2024 + VALUES LESS THAN (2025), + PARTITION p2025 + VALUES LESS THAN (2026), + PARTITION p2026 + VALUES LESS THAN (2027), + PARTITION p2027 + VALUES LESS THAN (2028), + PARTITION p2028 + VALUES LESS THAN (2029), + PARTITION p2029 + VALUES LESS THAN (2030), + PARTITION p2030 + VALUES LESS THAN (2031), + PARTITION p_future + VALUES LESS THAN MAXVALUE +); -- ตารางสำหรับจัดการดัชนีการค้นหาขั้นสูง (Full-text Search) CREATE TABLE search_indices ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของดัชนี', diff --git a/5_Backend_Folder_V1_4_3.md b/5_Backend_Folder_V1_4_3.md index ce215a2..9326c58 100644 --- a/5_Backend_Folder_V1_4_3.md +++ b/5_Backend_Folder_V1_4_3.md @@ -6,276 +6,279 @@ ## 📂 **backend/** (Backend Application) -* [x] `.env` (สำหรับ Local Dev เท่านั้น ห้าม commit) -* [x] `.gitignore` -* [x] `.prettierrc` -* [x] `docker-compose.override.yml` -* [x] `docker-compose.yml` -* [x] `nest-cli.json` -* [x] `tsconfig.build.json` -* [x] `tsconfig.json` -* [x] `package.json` -* [x] `pnpm-lock.yaml` -* [x] `README.md` +* [x] .env (สำหรับ Local Dev เท่านั้น ห้าม commit) +* [x] .gitignore +* [x] .prettierrc +* [x] docker-compose.override.yml +* [x] docker-compose.yml +* [x] nest-cli.json +* [x] tsconfig.build.json +* [x] tsconfig.json +* [x] package.json +* [x] pnpm-lock.yaml +* [x] README.md --- ## 📂 **backend/src/** (Source Code) -### **📄 Entry Points** +### **📄 Entry Points -* [x] `main.ts` (Application Bootstrap, Swagger, Global Pipes) -* [x] `app.module.ts` (Root Module ที่รวมทุก Modules เข้าด้วยกัน) -* [x] `app.service.ts` (Root Application Service) -* [x] `app.controller.ts` (Root Application Controller) -* [x] `app.controller.spec.ts` (Root Application Controller Unit Tests) -* [x] `redlock.d.ts` (Redlock Configuration) +* [x] main.ts +* [x] app.module.ts +* [x] app.service.ts +* [x] app.controller.ts +* [x] app.controller.spec.ts +* [x] redlock.d.ts ### **📁 src/common/** (Shared Resources) -* [x] **common.module.ts** +* [x] common.module.ts * **auth/** * **dto/** - * [x] **login.dto.ts** - * [x] **register.dto.ts** + * [x] login.dto.ts + * [x] register.dto.ts * **strategies/** - * [x] **local.strategy.ts** - * [x] **jwt.strategy.ts** - * [x] **auth.controller.spec.ts** - * [x] **auth.controller.ts** - * [x] **auth.module.ts** - * [x] **auth.service.spec.ts** - * [x] **auth.service.ts** -* **config/** (Configuration Service) - * [x] **env.validation.ts** - * [x] **redis.config.ts** + * [x] local.strategy.ts + * [x] jwt.strategy.ts + * [x] auth.controller.spec.ts + * [x] auth.controller.ts + * [x] auth.module.ts + * [x] auth.service.spec.ts + * [x] auth.service.ts +* **config/** + * [x] env.validation.ts + * [x] redis.config.ts * **decorators/** - * [x] **audit.decorator.ts** - * [x] **bypass-maintenance.decorator.ts** - * [x] `current-user.decorator.ts` - * [x] **idempotency.decorator.ts** - * [x] `require-permission.decorator.ts` - * [x] **retry.decorator.ts** - * [x] **circuit-breaker.decorator.ts** + * [x] audit.decorator.ts + * [x] bypass-maintenance.decorator.ts + * [x] current-user.decorator.ts + * [x] idempotency.decorator.ts + * [x] require-permission.decorator.ts + * [x] retry.decorator.ts + * [x] circuit-breaker.decorator.ts * **entities/** - * [x] **audit-log.entity.ts** - * [x] **base.entity.ts** + * [x] audit-log.entity.ts + * [x] base.entity.ts * **exceptions/** - * [x] `http-exception.filter.ts` (Global Filter) -* **file-storage/** (Two-Phase Storage System) + * [x] http-exception.filter.ts +* **file-storage/** * **entities/** - * [x] **attachment.entity.ts** - * [x] **file-storage.controller.spec.ts** - * [x] **file-storage.controller.ts** - * [x] **file-storage.module.ts** - * [x] **file-storage.service.spec.ts** - * [x] `file-storage.service.ts` (Upload, Scan Virus, Commit) - * [x] **file-cleanup.service.ts** (Cleanup Temporary Files) -* [x] `guards/` - * [x] `jwt-auth.guard.ts` - * [x] **jwt-refresh.guard.ts** - * [x] **maintenance-mode.guard.ts** - * [x] `rbac.guard.ts` (ตรวจสอบสิทธิ์ 4 ระดับ) + * [x] attachment.entity.ts + * [x] file-storage.controller.spec.ts + * [x] file-storage.controller.ts + * [x] file-storage.module.ts + * [x] file-storage.service.spec.ts + * [x] file-storage.service.ts + * [x] file-cleanup.service.ts +* [x] guards/` + * [x] jwt-auth.guard.ts + * [x] jwt-refresh.guard.ts + * [x] maintenance-mode.guard.ts + * [x] rbac.guard.ts * **interceptors/** - * [x] `audit-log.interceptor.ts` (เก็บ Log ลง DB) - * [x] **idempotency.interceptor.ts** (Idempotency Interceptor) - * [x] `transform.interceptor.ts` (Standard Response Format) -* **resilience/** (Circuit Breaker & Retry) - * [x] **resilience.module.ts** (Resilience Module) -* **security/** (Security Service) - * [x] **crypto.service.ts** (Crypto Service) - * [x] **request-context.service.ts + * [x] audit-log.interceptor.ts + * [x] idempotency.interceptor.ts + * [x] performance.interceptor.ts + * [x] transform.interceptor.ts +* **resilience/** + * [x] resilience.module.ts +* **services/** + * [x] crypto.service.ts + * [x] request-context.service.ts ### **📁 src/modules/** (Feature Modules) 1. **user/** (User Management & RBAC) - * [x] `dto/` - * [x] **assign-user-role.dto.ts** - * [x] `create-user.dto.ts` - * [ ] **search-user.dto.ts** - * [x] `update-user.dto.ts` - * [x] `update-user-preference.dto.ts` - * [x] `entities/` - * [x] `user.entity.ts` - * [x] `role.entity.ts` - * [x] `permission.entity.ts` - * [x] **user-assignment.entity.ts** - * [x] `user-preference.entity.ts` - * [x] **user-assignment.service.ts** - * [x] **user-preference.service.ts** - * [x] `user.controller.ts` - * [x] `user.module.ts` - * [x] `user.service.ts` - * [x] **user.service.spec.ts** + * [x] **dto/** + * [x] assign-user-role.dto.ts + * [x] create-user.dto.ts + * [x] update-user.dto.ts + * [x] update-user-preference.dto.ts + * [x] **entities/** + * [x] user.entity.ts + * [x] role.entity.ts + * [x] permission.entity.ts + * [x] user-assignment.entity.ts + * [x] user-preference.entity.ts + * [x] user-assignment.service.ts + * [x] user-preference.service.ts + * [x] user.controller.ts + * [x] user.module.ts + * [x] user.service.ts + * [x] user.service.spec.ts 2. **project/** (Project Structure) - * [x] `dto/` - * [x] **create-project.dto.ts** - * [x] `search-project.dto.ts` - * [x] `update-project.dto.ts` - * [x] `entities/` - * [x] `contract-organization.entity.ts` - * [x] `contract.entity.ts` - * [x] `organization.entity.ts` - * [x] `project-organization.entity.ts` (Junction) - * [x] **project.entity.ts** - * [x] **project.controller.spec.ts** - * [x] `project.controller.ts` - * [x] `project.module.ts` - * [x] **project.service.spec.ts** - * [x] `project.service.ts` + * [x] **dto/** + * [x] create-project.dto.ts + * [x] search-project.dto.ts + * [x] update-project.dto.ts + * [x] **entities/** + * [x] contract-organization.entity.ts + * [x] contract.entity.ts + * [x] organization.entity.ts + * [x] project-organization.entity.ts + * [x] project.entity.ts + * [x] project.controller.spec.ts + * [x] project.controller.ts + * [x] project.module.ts + * [x] project.service.spec.ts + * [x] project.service.ts 3. **correspondence/** (Core Document System) - * [x] `dto/` - * [x] `add-reference.dto.ts` - * [x] `create-correspondence.dto.ts` - * [x] `search-correspondence.dto.ts` - * [x] **submit-correspondence.dto.ts** - * [x] `workflow-action.dto.ts` - * [x] `entities/` - * [x] `correspondence-reference.entity.ts` - * [x] `correspondence-revision.entity.ts` - * [x] `correspondence-routing.entity.ts` (Unified Workflow) - * [x] `correspondence-status.entity.ts` - * [x] `correspondence-type.entity.ts` - * [x] `correspondence.entity.ts` - * [x] **routing-template-step.entity.ts** - * [x] `routing-template.entity.ts` - * [x] **correspondence.controller.spec.ts** - * [x] `correspondence.controller.ts` - * [x] `correspondence.module.ts` - * [x] **correspondence.service.spec.ts** - * [x] `correspondence.service.ts` (Impersonation & Workflow Logic) + * [x] **dto/** + * [x] add-reference.dto.ts + * [x] create-correspondence.dto.ts + * [x] search-correspondence.dto.ts + * [x] submit-correspondence.dto.ts + * [x] workflow-action.dto.ts + * [x] **entities/** + * [x] correspondence-reference.entity.ts + * [x] correspondence-revision.entity.ts + * [x] correspondence-routing.entity.ts + * [x] correspondence-status.entity.ts + * [x] correspondence-type.entity.ts + * [x] correspondence.entity.ts + * [x] routing-template-step.entity.ts + * [x] routing-template.entity.ts + * [x] correspondence.controller.spec.ts + * [x] correspondence.controller.ts + * [x] correspondence.module.ts + * [x] correspondence.service.spec.ts + * [x] correspondence.service.ts 4. **drawing/** (Contract & Shop Drawings) - * [x] `dto/` - * [x] `create-contract-drawing.dto.ts` - * [x] `create-shop-drawing-revision.dto.ts` - * [x] `create-shop-drawing.dto.ts` - * [x] `search-contract-drawing.dto.ts` - * [x] `search-shop-drawing.dto.ts` - * [x] `update-contract-drawing.dto.ts` - * [x] `entities/` - * [x] `contract-drawing-sub-category.entity.ts` - * [x] `contract-drawing-volume.entity.ts` - * [x] `contract-drawing.entity.ts` - * [x] `shop-drawing-main-category.entity.ts` - * [x] `shop-drawing-revision.entity.ts` - * [x] `shop-drawing-sub-category.entity.ts` - * [x] `shop-drawing.entity.ts` - * [x] `contract-drawing.controller.ts` - * [x] `contract-drawing.service.ts` - * [x] `drawing-master-data.controller.ts` - * [x] `drawing-master-data.service.ts` - * [x] `drawing.module.ts` - * [x] `shop-drawing.controller.ts` - * [x] `shop-drawing.service.ts` + * [x] **dto/** + * [x] create-contract-drawing.dto.ts + * [x] create-shop-drawing-revision.dto.ts + * [x] create-shop-drawing.dto.ts + * [x] search-contract-drawing.dto.ts + * [x] search-shop-drawing.dto.ts + * [x] update-contract-drawing.dto.ts + * [x] **entities/** + * [x] contract-drawing-sub-category.entity.ts + * [x] contract-drawing-volume.entity.ts + * [x] contract-drawing.entity.ts + * [x] shop-drawing-main-category.entity.ts + * [x] shop-drawing-revision.entity.ts + * [x] shop-drawing-sub-category.entity.ts + * [x] shop-drawing.entity.ts + * [x] contract-drawing.controller.ts + * [x] contract-drawing.service.ts + * [x] drawing-master-data.controller.ts + * [x] drawing-master-data.service.ts + * [x] drawing.module.ts + * [x] shop-drawing.controller.ts + * [x] shop-drawing.service.ts 5. **rfa/** (Request for Approval & Advanced Workflow) - * [x] `dto/` - * [x] `create-rfa.dto.ts` - * [x] `search-rfa.dto.ts` - * [x] `update-rfa.dto.ts` - * [x] `entities/` - * [x] `rfa-approve-code.entity.ts` - * [x] `rfa-item.entity.ts` - * [x] `rfa-revision.entity.ts` - * [x] `rfa-status-code.entity.ts` - * [x] `rfa-type.entity.ts` - * [x] `rfa-workflow-template-step.entity.ts` - * [x] `rfa-workflow-template.entity.ts` - * [x] `rfa-workflow.entity.ts` - * [x] `rfa.entity.ts` - * [x] `rfa.controller.ts` - * [x] `rfa.module.ts` - * [x] `rfa.service.ts` (Unified Workflow Integration) + * [x] **dto/** + * [x] create-rfa.dto.ts + * [x] search-rfa.dto.ts + * [x] update-rfa.dto.ts + * [x] **entities/** + * [x] rfa-approve-code.entity.ts + * [x] rfa-item.entity.ts + * [x] rfa-revision.entity.ts + * [x] rfa-status-code.entity.ts + * [x] rfa-type.entity.ts + * [x] rfa-workflow-template-step.entity.ts + * [x] rfa-workflow-template.entity.ts + * [x] rfa-workflow.entity.ts + * [x] rfa.entity.ts + * [x] rfa.controller.ts + * [x] rfa.module.ts + * [x] rfa.service.ts 6. **circulation/** (Internal Routing) - * [x] `dto/` - * [x] `create-circulation.dto.ts` - * [x] `update-circulation-routing.dto.ts` - * [x] `search-circulation.dto.ts` - * [x] `entities/` - * [x] `circulation-routing.entity.ts` - * [x] `circulation-status-code.entity.ts` - * [x] `circulation.entity.ts` - * [x] `circulation.controller.ts` - * [x] `circulation.module.ts` - * [x] `circulation.service.ts` + * [x] **dto/** + * [x] create-circulation.dto.ts + * [x] update-circulation-routing.dto.ts + * [x] search-circulation.dto.ts + * [x] **entities/** + * [x] circulation-routing.entity.ts + * [x] circulation-status-code.entity.ts + * [x] circulation.entity.ts + * [x] circulation.controller.ts + * [x] circulation.module.ts + * [x] circulation.service.ts 7. **transmittal/** (Document Forwarding) - * [x] `dto/` - * [x] `create-transmittal.dto.ts` - * [x] `search-transmittal.dto.ts` - * [x] **update-transmittal.dto.ts** - * [x] `entities/` - * [x] `transmittal-item.entity.ts` - * [x] `transmittal.entity.ts` - * [x] `transmittal.controller.ts` - * [x] `transmittal.module.ts` - * [x] `transmittal.service.ts` + * [x] **dto/** + * [x] create-transmittal.dto.ts + * [x] search-transmittal.dto.ts + * [x] update-transmittal.dto.ts + * [x] **entities/** + * [x] transmittal-item.entity.ts + * [x] transmittal.entity.ts + * [x] transmittal.controller.ts + * [x] transmittal.module.ts + * [x] transmittal.service.ts 8. **notification/** (System Alerts) - * [x] `dto/` - * [x] `create-notification.dto.ts` - * [x] `search-notification.dto.ts` - * [x] `entities/` - * [x] `notification.entity.ts` - * [x] `notification-cleanup.service.ts` (Cron Job) - * [x] `notification.controller.ts` - * [x] `notification.gateway.ts` - * [x] `notification.module.ts` (Real-time WebSocket) - * [x] `notification.processor.ts` (Consumer/Worker for Email & Line) - * [x] `notification.service.ts` (Producer) + * [x] **dto/** + * [x] create-notification.dto.ts + * [x] search-notification.dto.ts + * [x] **entities/** + * [x] notification.entity.ts + * [x] notification-cleanup.service.ts + * [x] notification.controller.ts + * [x] notification.gateway.ts + * [x] notification.module.ts + * [x] notification.processor.ts + * [x] notification.service.ts 9. **search/** (Elasticsearch) - * [x] `dto/search-query.dto.ts` - * [x] `search.controller.ts` - * [x] `search.module.ts` - * [x] `search.service.ts` (Indexing & Searching) + * [x] dto/search-query.dto.ts + * [x] search.controller.ts + * [x] search.module.ts + * [x] search.service.ts -10. **document-numbering/** (Internal Service) - * [x] `entities/` - * [x] `document-number-format.entity.ts` - * [x] `document-number-counter.entity.ts` - * [x] `document-numbering.module.ts` - * [x] **document-numbering.service.spec.ts** - * [x] `document-numbering.service.ts` (Double-Lock Mechanism) +10. **document-numbering/** + * [x] **entities/** + * [x] document-number-format.entity.ts + * [x] document-number-counter.entity.ts + * [x] document-numbering.module.ts + * [x] document-numbering.service.spec.ts + * [x] document-numbering.service.ts 11. **workflow-engine/** (Unified Logic) * [x] **dto/** - * [x] `create-workflow-definition.dto.ts` - * [x] `evaluate-workflow.dto.ts` - * [x] `get-available-actions.dto.ts` - * [x] `update-workflow-definition.dto.ts` - * [x] `interfaces/workflow.interface.ts` - * [x] **workflow-dsl.service.ts** - * [x] `workflow-engine.module.ts` - * [x] **workflow-engine.service.spec.ts** - * [x] `workflow-engine.service.ts` (State Machine Logic) + * [x] create-workflow-definition.dto.ts + * [x] evaluate-workflow.dto.ts + * [x] get-available-actions.dto.ts + * [x] update-workflow-definition.dto.ts + * [x] interfaces/workflow.interface.ts + * [x] workflow-dsl.service.ts + * [x] workflow-engine.module.ts + * [x] workflow-engine.service.spec.ts + * [x] workflow-engine.service.ts 12. **json-schema/** (Validation) - * [x] `dto/` - * [x] `create-json-schema.dto.ts`+ - * [x] `search-json-schema.dto.ts` - * [x] `update-json-schema.dto.ts` - * [x] `entities/` - * [x] `json-schema.entity.ts` - * [x] **json-schema.controller.spec.ts** - * [x] **json-schema.controller.ts** - * [x] `json-schema.module.ts` - * [x] **json-schema.service.spec.ts** - * [x] `json-schema.service.ts` + * [x] **dto/** + * [x] create-json-schema.dto.ts+ + * [x] search-json-schema.dto.ts + * [x] update-json-schema.dto.ts + * [x] **entities/** + * [x] json-schema.entity.ts + * [x] json-schema.controller.spec.ts + * [x] json-schema.controller.ts + * [x] json-schema.module.ts + * [x] json-schema.service.spec.ts + * [x] json-schema.service.ts 13. **monitoring/** (Monitoring & Metrics) - * [x] `controllers/` - * [x] `health.controller.ts` - * [x] `logger/` - * [x] `winston.config.ts` - * [x] `services/` - * [x] `metrics.service.ts` - * [x] `monitoring.module.ts` - + * [x] **controllers/** + * [x] health.controller.ts + * [x] **dto/** + * [ ] set-maintenance.dto.ts + * [x] **logger/** + * [x] winston.config.ts + * [x] **services/** + * [x] metrics.service.ts + * [x] monitoring.controller.ts + * [x] monitoring.service.ts + * [x] monitoring.module.ts ## **Folder Structure ของ Backend (NestJS)** ที่ @@ -354,17 +357,18 @@ backend/ │ │ ├── jwt-auth.guard.ts # JWT authentication guard │ │ ├── jwt-refresh.guard.ts # JWT refresh guard │ │ ├── maintenance-mode.guard.ts # Maintenance mode guard - │ │ └── rbac.guard.ts # Role-based access control enforcement + │ │ └── rbac.guard.ts # Role-based access control enforcement (ตรวจสอบสิทธิ์ 4 ระดับ) │ │ │ ├── interceptors/ # 📡 Interceptors for common use cases │ │ ├── audit-log.interceptor.ts # Automatically logs certain operations │ │ ├── idempotency.interceptor.ts # Idempotency interceptor + │ │ ├── performance.interceptor.ts # │ │ └── transform.interceptor.ts # Standardized response formatting │ │ │ ├─── resilience/ # 🛡️ Circuit-breaker / retry logic (if implemented) │ │ └── resilience.module.ts # Resilience module │ │ - │ └──── security/ # 🔐 Security service + │ └──── services/ # 🔐 Security service │ ├── crypto.service.ts # Crypto service │ └── request-context.service.ts # Request context service (for logging) │ @@ -373,7 +377,6 @@ backend/ │ │ ├── dto/ │ │ │ ├── assign-user-role.dto.ts # Assign roles to users │ │ │ ├── create-user.dto.ts # Create new user - │ │ │ ├── search-user.dto.ts # Search users │ │ │ ├── update-user.dto.ts # Update user details │ │ │ └── update-user-preference.dto.ts # Update user preferences │ │ ├── entities/ @@ -416,7 +419,7 @@ backend/ │ │ ├── entities/ │ │ │ ├── correspondence.entity.ts # Correspondence table definition │ │ │ ├── correspondence-revision.entity.ts # Correspondence revision entity - │ │ │ ├── correspondence-routing.entity.ts # Correspondence routing entity + │ │ │ ├── correspondence-routing.entity.ts # Correspondence routing entity (Unified Workflow) │ │ │ ├── correspondence-status.entity.ts # Correspondence status entity │ │ │ ├── correspondence-type.entity.ts # Correspondence type entity │ │ │ ├── correspondence-reference.entity.ts # Correspondence reference entity @@ -505,7 +508,7 @@ backend/ │ │ ├── notification.module.ts # WebSocket + Processor registration │ │ ├── notification.controller.ts # REST endpoints │ │ ├── notification.gateway.ts # WebSocket gateway - │ │ ├── notification.processor.ts # Message consumer (e.g. mail worker) + │ │ ├── notification.processor.ts # Message consumer (Consumer/Worker for Email & Line) │ │ ├── notification.service.ts # Business logic │ │ └── notification-cleanup.service.ts # Cron-based cleanup job │ │ @@ -533,11 +536,11 @@ backend/ │ │ ├── entities/ │ │ │ └── workflow-definition.entity.ts # Workflow definition entity │ │ ├── interfaces/ - │ │ │ └── workflow.interface.ts # Workflow interface - │ │ ├── workflow-engine.controller.ts # REST endpoints - │ │ ├── workflow-engine.module.ts # Module DI container - │ │ ├── workflow-engine.service.ts # Business logic - │ │ └── workflow-engine.service.spec.ts # Unit tests + │ │ │ └── workflow.interface.ts # Workflow interface + │ │ ├── workflow-engine.controller.ts # REST endpoints + │ │ ├── workflow-engine.module.ts # Module DI container + │ │ ├── workflow-engine.service.ts # State Machine Logic + │ │ └── workflow-engine.service.spec.ts # Unit tests │ │ │ ├── json-schema/ # 📋 Dynamic request schema validation │ │ ├── dto/ @@ -554,15 +557,17 @@ backend/ │ │ │ └── monitoring/ # 📋 Dynamic request schema validation │ ├── controllers/ - │ │ ├── health.controller.ts # Create new JSON schema - │ │ ├── update-json-schema.dto.ts # Update JSON schema - │ │ └── search-json-schema.dto.ts # Search JSON schema + │ │ └── health.controller.ts # Create new JSON schema + │ ├── dto/ + │ │ └── set-maintenance.dto.ts # Create new JSON schema │ ├── logger/ - │ │ └── winston.config.ts # JSON schema entity + │ │ └── winston.config.ts # JSON schema entity │ ├── services/ - │ │ └── metrics.service.ts # JSON schema entity - │ └── monitoring.module.ts # Module DI container - │ + │ │ └── metrics.service.ts # JSON schema entity + │ ├── monitoring.controller.ts # REST endpoints + │ ├── monitoring.service.ts # Business logic + │ └── monitoring.module.ts # Module DI container + ``` ---- \ No newline at end of file +--- diff --git a/backend/package.json b/backend/package.json index fd76484..f16dd23 100644 --- a/backend/package.json +++ b/backend/package.json @@ -22,6 +22,7 @@ "dependencies": { "@casl/ability": "^6.7.3", "@elastic/elasticsearch": "^8.11.1", + "@nestjs-modules/ioredis": "^2.0.2", "@nestjs/axios": "^4.0.1", "@nestjs/bullmq": "^11.0.4", "@nestjs/cache-manager": "^3.0.1", diff --git a/backend/pnpm-lock.yaml b/backend/pnpm-lock.yaml index b9d37dd..57da36b 100644 --- a/backend/pnpm-lock.yaml +++ b/backend/pnpm-lock.yaml @@ -14,6 +14,12 @@ importers: '@elastic/elasticsearch': specifier: ^8.11.1 version: 8.19.1 + '@nestjs-modules/ioredis': + specifier: ^2.0.2 + version: 2.0.2(@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2))(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/typeorm@11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))))(ioredis@5.8.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))) + '@nestjs/axios': + specifier: ^4.0.1 + version: 4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2) '@nestjs/bullmq': specifier: ^11.0.4 version: 11.0.4(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(bullmq@5.63.2) @@ -53,6 +59,9 @@ importers: '@nestjs/swagger': specifier: ^11.2.3 version: 11.2.3(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2) + '@nestjs/terminus': + specifier: ^11.0.0 + version: 11.0.0(@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2))(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/typeorm@11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))))(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))) '@nestjs/throttler': specifier: ^6.4.0 version: 6.4.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2) @@ -71,6 +80,9 @@ importers: ajv-formats: specifier: ^3.0.1 version: 3.0.1(ajv@8.17.1) + async-retry: + specifier: ^1.3.3 + version: 1.3.3 axios: specifier: ^1.13.2 version: 1.13.2 @@ -110,15 +122,24 @@ importers: mysql2: specifier: ^3.15.3 version: 3.15.3 + nest-winston: + specifier: ^1.10.2 + version: 1.10.2(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(winston@3.18.3) nodemailer: specifier: ^7.0.10 version: 7.0.10 + opossum: + specifier: ^9.0.0 + version: 9.0.0 passport: specifier: ^0.7.0 version: 0.7.0 passport-jwt: specifier: ^4.0.1 version: 4.0.1 + prom-client: + specifier: ^15.1.3 + version: 15.1.3 redlock: specifier: 5.0.0-beta.2 version: 5.0.0-beta.2 @@ -140,6 +161,9 @@ importers: uuid: specifier: ^13.0.0 version: 13.0.0 + winston: + specifier: ^3.18.3 + version: 3.18.3 devDependencies: '@eslint/eslintrc': specifier: ^3.2.0 @@ -156,6 +180,9 @@ importers: '@nestjs/testing': specifier: ^11.0.1 version: 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/platform-express@11.1.9) + '@types/async-retry': + specifier: ^1.4.9 + version: 1.4.9 '@types/bcrypt': specifier: ^6.0.0 version: 6.0.0 @@ -180,6 +207,9 @@ importers: '@types/node': specifier: ^22.10.7 version: 22.19.1 + '@types/opossum': + specifier: ^8.1.9 + version: 8.1.9 '@types/passport-jwt': specifier: ^4.0.1 version: 4.0.1 @@ -572,10 +602,17 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} + '@colors/colors@1.6.0': + resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} + engines: {node: '>=0.1.90'} + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@dabh/diagnostics@2.0.8': + resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==} + '@elastic/elasticsearch@8.19.1': resolution: {integrity: sha512-+1j9NnQVOX+lbWB8LhCM7IkUmjU05Y4+BmSLfusq0msCsQb1Va+OUKFCoOXjCJqQrcgdRdQCjYYyolQ/npQALQ==} engines: {node: '>=18'} @@ -980,6 +1017,20 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} + '@nestjs-modules/ioredis@2.0.2': + resolution: {integrity: sha512-8pzSvT8R3XP6p8ZzQmEN8OnY0yWrJ/elFhwQK+PID2zf1SLBkAZ18bDcx3SKQ2atledt0gd9kBeP5xT4MlyS7Q==} + peerDependencies: + '@nestjs/common': '>=6.7.0' + '@nestjs/core': '>=6.7.0' + ioredis: '>=5.0.0' + + '@nestjs/axios@4.0.1': + resolution: {integrity: sha512-68pFJgu+/AZbWkGu65Z3r55bTsCPlgyKaV4BSG8yUAD72q1PPuyVRgUwFv6BxdnibTUHlyxm06FmYWNC+bjN7A==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + axios: ^1.3.1 + rxjs: ^7.0.0 + '@nestjs/bull-shared@11.0.4': resolution: {integrity: sha512-VBJcDHSAzxQnpcDfA0kt9MTGUD1XZzfByV70su0W0eDCQ9aqIEBlzWRW21tv9FG9dIut22ysgDidshdjlnczLw==} peerDependencies: @@ -1124,6 +1175,102 @@ packages: class-validator: optional: true + '@nestjs/terminus@10.2.0': + resolution: {integrity: sha512-zPs98xvJ4ogEimRQOz8eU90mb7z+W/kd/mL4peOgrJ/VqER+ibN2Cboj65uJZW3XuNhpOqaeYOJte86InJd44A==} + peerDependencies: + '@grpc/grpc-js': '*' + '@grpc/proto-loader': '*' + '@mikro-orm/core': '*' + '@mikro-orm/nestjs': '*' + '@nestjs/axios': ^1.0.0 || ^2.0.0 || ^3.0.0 + '@nestjs/common': ^9.0.0 || ^10.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 + '@nestjs/microservices': ^9.0.0 || ^10.0.0 + '@nestjs/mongoose': ^9.0.0 || ^10.0.0 + '@nestjs/sequelize': ^9.0.0 || ^10.0.0 + '@nestjs/typeorm': ^9.0.0 || ^10.0.0 + '@prisma/client': '*' + mongoose: '*' + reflect-metadata: 0.1.x + rxjs: 7.x + sequelize: '*' + typeorm: '*' + peerDependenciesMeta: + '@grpc/grpc-js': + optional: true + '@grpc/proto-loader': + optional: true + '@mikro-orm/core': + optional: true + '@mikro-orm/nestjs': + optional: true + '@nestjs/axios': + optional: true + '@nestjs/microservices': + optional: true + '@nestjs/mongoose': + optional: true + '@nestjs/sequelize': + optional: true + '@nestjs/typeorm': + optional: true + '@prisma/client': + optional: true + mongoose: + optional: true + sequelize: + optional: true + typeorm: + optional: true + + '@nestjs/terminus@11.0.0': + resolution: {integrity: sha512-c55LOo9YGovmQHtFUMa/vDaxGZ2cglMTZejqgHREaApt/GArTfgYYGwhRXPLq8ZwiQQlLuYB+79e9iA8mlDSLA==} + peerDependencies: + '@grpc/grpc-js': '*' + '@grpc/proto-loader': '*' + '@mikro-orm/core': '*' + '@mikro-orm/nestjs': '*' + '@nestjs/axios': ^2.0.0 || ^3.0.0 || ^4.0.0 + '@nestjs/common': ^10.0.0 || ^11.0.0 + '@nestjs/core': ^10.0.0 || ^11.0.0 + '@nestjs/microservices': ^10.0.0 || ^11.0.0 + '@nestjs/mongoose': ^11.0.0 + '@nestjs/sequelize': ^10.0.0 || ^11.0.0 + '@nestjs/typeorm': ^10.0.0 || ^11.0.0 + '@prisma/client': '*' + mongoose: '*' + reflect-metadata: 0.1.x || 0.2.x + rxjs: 7.x + sequelize: '*' + typeorm: '*' + peerDependenciesMeta: + '@grpc/grpc-js': + optional: true + '@grpc/proto-loader': + optional: true + '@mikro-orm/core': + optional: true + '@mikro-orm/nestjs': + optional: true + '@nestjs/axios': + optional: true + '@nestjs/microservices': + optional: true + '@nestjs/mongoose': + optional: true + '@nestjs/sequelize': + optional: true + '@nestjs/typeorm': + optional: true + '@prisma/client': + optional: true + mongoose: + optional: true + sequelize: + optional: true + typeorm: + optional: true + '@nestjs/testing@11.1.9': resolution: {integrity: sha512-UFxerBDdb0RUNxQNj25pvkvNE7/vxKhXYWBt3QuwBFnYISzRIzhVlyIqLfoV5YI3zV0m0Nn4QAn1KM0zzwfEng==} peerDependencies: @@ -1424,6 +1571,9 @@ packages: resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==} engines: {node: '>=18.0.0'} + '@so-ric/colorspace@1.1.6': + resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==} + '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -1458,6 +1608,9 @@ packages: '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/async-retry@1.4.9': + resolution: {integrity: sha512-s1ciZQJzRh3708X/m3vPExr5KJlzlZJvXsKpbtE2luqNcbROr64qU+3KpJsYHqWMeaxI839OvXf9PrUSw1Xtyg==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -1565,6 +1718,9 @@ packages: '@types/nodemailer@7.0.4': resolution: {integrity: sha512-ee8fxWqOchH+Hv6MDDNNy028kwvVnLplrStm4Zf/3uHWw5zzo8FoYYeffpJtGs2wWysEumMH0ZIdMGMY1eMAow==} + '@types/opossum@8.1.9': + resolution: {integrity: sha512-Jm/tYxuJFefiwRYs+/EOsUP3ktk0c8siMgAHPLnA4PXF4wKghzcjqf88dY+Xii5jId5Txw4JV0FMKTpjbd7KJA==} + '@types/passport-jwt@4.0.1': resolution: {integrity: sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==} @@ -1580,6 +1736,9 @@ packages: '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/retry@0.12.5': + resolution: {integrity: sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==} + '@types/send@0.17.6': resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==} @@ -1598,6 +1757,9 @@ packages: '@types/supertest@6.0.3': resolution: {integrity: sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==} + '@types/triple-beam@1.3.5': + resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + '@types/uuid@11.0.0': resolution: {integrity: sha512-HVyk8nj2m+jcFRNazzqyVKiZezyhDKrGUA3jlEcg/nZ6Ms+qHwocba1Y/AaVaznJTAM9xpdFSh+ptbNrhOGvZA==} deprecated: This is a stub types definition. uuid provides its own type definitions, so you do not need this installed. @@ -1891,6 +2053,9 @@ packages: ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -1961,6 +2126,12 @@ packages: asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + async-retry@1.3.3: + resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -2018,6 +2189,9 @@ packages: resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==} engines: {node: '>= 18'} + bintrees@1.0.2: + resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -2028,6 +2202,10 @@ packages: bowser@2.12.1: resolution: {integrity: sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==} + boxen@5.1.2: + resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} + engines: {node: '>=10'} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -2127,6 +2305,10 @@ packages: chardet@2.1.1: resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + check-disk-space@3.4.0: + resolution: {integrity: sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==} + engines: {node: '>=16'} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -2148,6 +2330,10 @@ packages: class-validator@0.14.2: resolution: {integrity: sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==} + cli-boxes@2.2.1: + resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} + engines: {node: '>=6'} + cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -2187,9 +2373,25 @@ packages: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} + color-convert@3.1.3: + resolution: {integrity: sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==} + engines: {node: '>=14.6'} + color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-name@2.1.0: + resolution: {integrity: sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==} + engines: {node: '>=12.20'} + + color-string@2.1.4: + resolution: {integrity: sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==} + engines: {node: '>=18'} + + color@5.0.3: + resolution: {integrity: sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==} + engines: {node: '>=18'} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -2393,6 +2595,9 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + enabled@2.0.0: + resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} + encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} @@ -2580,6 +2785,9 @@ packages: fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + fecha@4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} @@ -2626,6 +2834,9 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + fn.name@1.1.0: + resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + follow-redirects@1.15.11: resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} engines: {node: '>=4.0'} @@ -3142,6 +3353,9 @@ packages: keyv@5.5.4: resolution: {integrity: sha512-eohl3hKTiVyD1ilYdw9T0OiB4hnjef89e3dMYKz+mVKDzj+5IteTseASUsOB+EU9Tf6VNTCjDePcP6wkDGmLKQ==} + kuler@2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -3218,6 +3432,10 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} + logform@2.7.0: + resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} + engines: {node: '>= 12.0.0'} + long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} @@ -3383,6 +3601,12 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + nest-winston@1.10.2: + resolution: {integrity: sha512-Z9IzL/nekBOF/TEwBHUJDiDPMaXUcFquUQOFavIRet6xF0EbuWnOzslyN/ksgzG+fITNgXhMdrL/POp9SdaFxA==} + peerDependencies: + '@nestjs/common': ^5.0.0 || ^6.6.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + winston: ^3.0.0 + node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} @@ -3438,10 +3662,17 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + one-time@1.0.0: + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + opossum@9.0.0: + resolution: {integrity: sha512-K76U0QkxOfUZamneQuzz+AP0fyfTJcCplZ2oZL93nxeupuJbN4s6uFNbmVCt4eWqqGqRnnowdFuBicJ1fLMVxw==} + engines: {node: ^24 || ^22 || ^20} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -3574,6 +3805,10 @@ packages: resolution: {integrity: sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + prom-client@15.1.3: + resolution: {integrity: sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==} + engines: {node: ^16 || ^18 || >=20} + promise-coalesce@1.5.0: resolution: {integrity: sha512-cTJ30U+ur1LD7pMPyQxiKIwxjtAjLsyU7ivRhVWZrX9BNIXtf78pc37vSMc8Vikx7DVzEKNk2SEJ5KWUpSG2ig==} engines: {node: '>=16'} @@ -3663,6 +3898,10 @@ packages: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -3683,6 +3922,10 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -3807,6 +4050,9 @@ packages: resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} engines: {node: '>= 0.6'} + stack-trace@0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -3913,6 +4159,9 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} + tdigest@0.1.2: + resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==} + terser-webpack-plugin@5.3.14: resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==} engines: {node: '>= 10.13.0'} @@ -3938,6 +4187,9 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} + text-hex@1.0.0: + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -3957,6 +4209,10 @@ packages: resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==} engines: {node: '>=14.16'} + triple-beam@1.4.1: + resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} + engines: {node: '>= 14.0.0'} + ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} @@ -4030,6 +4286,10 @@ packages: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} @@ -4235,6 +4495,18 @@ packages: engines: {node: '>= 8'} hasBin: true + widest-line@3.1.0: + resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} + engines: {node: '>=8'} + + winston-transport@4.9.0: + resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} + engines: {node: '>= 12.0.0'} + + winston@3.18.3: + resolution: {integrity: sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==} + engines: {node: '>= 12.0.0'} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -4968,10 +5240,18 @@ snapshots: '@colors/colors@1.5.0': optional: true + '@colors/colors@1.6.0': {} + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@dabh/diagnostics@2.0.8': + dependencies: + '@so-ric/colorspace': 1.1.6 + enabled: 2.0.0 + kuler: 2.0.0 + '@elastic/elasticsearch@8.19.1': dependencies: '@elastic/transport': 8.10.0 @@ -5489,6 +5769,36 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true + '@nestjs-modules/ioredis@2.0.2(@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2))(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/typeorm@11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))))(ioredis@5.8.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))': + dependencies: + '@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(@nestjs/websockets@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2) + ioredis: 5.8.2 + optionalDependencies: + '@nestjs/terminus': 10.2.0(@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2))(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/typeorm@11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))))(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))) + transitivePeerDependencies: + - '@grpc/grpc-js' + - '@grpc/proto-loader' + - '@mikro-orm/core' + - '@mikro-orm/nestjs' + - '@nestjs/axios' + - '@nestjs/microservices' + - '@nestjs/mongoose' + - '@nestjs/sequelize' + - '@nestjs/typeorm' + - '@prisma/client' + - mongoose + - reflect-metadata + - rxjs + - sequelize + - typeorm + + '@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + axios: 1.13.2 + rxjs: 7.8.2 + '@nestjs/bull-shared@11.0.4(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)': dependencies: '@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -5656,6 +5966,33 @@ snapshots: class-transformer: 0.5.1 class-validator: 0.14.2 + '@nestjs/terminus@10.2.0(@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2))(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/typeorm@11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))))(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))': + dependencies: + '@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(@nestjs/websockets@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2) + boxen: 5.1.2 + check-disk-space: 3.4.0 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + optionalDependencies: + '@nestjs/axios': 4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2) + '@nestjs/typeorm': 11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))) + typeorm: 0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + optional: true + + '@nestjs/terminus@11.0.0(@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2))(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/typeorm@11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))))(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))': + dependencies: + '@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(@nestjs/websockets@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2) + boxen: 5.1.2 + check-disk-space: 3.4.0 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + optionalDependencies: + '@nestjs/axios': 4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2) + '@nestjs/typeorm': 11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))) + typeorm: 0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + '@nestjs/testing@11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/platform-express@11.1.9)': dependencies: '@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -6038,6 +6375,11 @@ snapshots: dependencies: tslib: 2.8.1 + '@so-ric/colorspace@1.1.6': + dependencies: + color: 5.0.3 + text-hex: 1.0.0 + '@socket.io/component-emitter@3.1.2': {} '@sqltools/formatter@1.2.5': {} @@ -6071,6 +6413,10 @@ snapshots: tslib: 2.8.1 optional: true + '@types/async-retry@1.4.9': + dependencies: + '@types/retry': 0.12.5 + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.28.5 @@ -6210,6 +6556,10 @@ snapshots: transitivePeerDependencies: - aws-crt + '@types/opossum@8.1.9': + dependencies: + '@types/node': 22.19.1 + '@types/passport-jwt@4.0.1': dependencies: '@types/jsonwebtoken': 9.0.10 @@ -6228,6 +6578,8 @@ snapshots: '@types/range-parser@1.2.7': {} + '@types/retry@0.12.5': {} + '@types/send@0.17.6': dependencies: '@types/mime': 1.3.5 @@ -6257,6 +6609,8 @@ snapshots: '@types/methods': 1.1.4 '@types/superagent': 8.1.9 + '@types/triple-beam@1.3.5': {} + '@types/uuid@11.0.0': dependencies: uuid: 13.0.0 @@ -6574,6 +6928,10 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + ansi-colors@4.1.3: {} ansi-escapes@4.3.2: @@ -6633,6 +6991,12 @@ snapshots: asap@2.0.6: {} + async-retry@1.3.3: + dependencies: + retry: 0.13.1 + + async@3.2.6: {} + asynckit@0.4.0: {} available-typed-arrays@1.0.7: @@ -6714,6 +7078,8 @@ snapshots: node-addon-api: 8.5.0 node-gyp-build: 4.8.4 + bintrees@1.0.2: {} + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -6736,6 +7102,17 @@ snapshots: bowser@2.12.1: {} + boxen@5.1.2: + dependencies: + ansi-align: 3.0.1 + camelcase: 6.3.0 + chalk: 4.1.2 + cli-boxes: 2.2.1 + string-width: 4.2.3 + type-fest: 0.20.2 + widest-line: 3.1.0 + wrap-ansi: 7.0.0 + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -6858,6 +7235,8 @@ snapshots: chardet@2.1.1: {} + check-disk-space@3.4.0: {} + chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -6876,6 +7255,8 @@ snapshots: libphonenumber-js: 1.12.27 validator: 13.15.23 + cli-boxes@2.2.1: {} + cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 @@ -6908,8 +7289,23 @@ snapshots: dependencies: color-name: 1.1.4 + color-convert@3.1.3: + dependencies: + color-name: 2.1.0 + color-name@1.1.4: {} + color-name@2.1.0: {} + + color-string@2.1.4: + dependencies: + color-name: 2.1.0 + + color@5.0.3: + dependencies: + color-convert: 3.1.3 + color-string: 2.1.4 + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -7070,6 +7466,8 @@ snapshots: emoji-regex@9.2.2: {} + enabled@2.0.0: {} + encodeurl@2.0.0: {} engine.io-parser@5.2.3: {} @@ -7306,6 +7704,8 @@ snapshots: dependencies: bser: 2.1.1 + fecha@4.2.3: {} + fflate@0.8.2: {} file-entry-cache@8.0.0: @@ -7357,6 +7757,8 @@ snapshots: flatted@3.3.3: {} + fn.name@1.1.0: {} + follow-redirects@1.15.11: {} for-each@0.3.5: @@ -8071,6 +8473,8 @@ snapshots: dependencies: '@keyv/serialize': 1.1.1 + kuler@2.0.0: {} + leven@3.1.0: {} levn@0.4.1: @@ -8127,6 +8531,15 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 + logform@2.7.0: + dependencies: + '@colors/colors': 1.6.0 + '@types/triple-beam': 1.3.5 + fecha: 4.2.3 + ms: 2.1.3 + safe-stable-stringify: 2.5.0 + triple-beam: 1.4.1 + long@5.3.2: {} lru-cache@10.4.3: {} @@ -8272,6 +8685,12 @@ snapshots: neo-async@2.6.2: {} + nest-winston@1.10.2(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(winston@3.18.3): + dependencies: + '@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + fast-safe-stringify: 2.1.1 + winston: 3.18.3 + node-abort-controller@3.1.1: {} node-addon-api@8.5.0: {} @@ -8313,10 +8732,16 @@ snapshots: dependencies: wrappy: 1.0.2 + one-time@1.0.0: + dependencies: + fn.name: 1.1.0 + onetime@5.1.2: dependencies: mimic-fn: 2.1.0 + opossum@9.0.0: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -8438,6 +8863,11 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 + prom-client@15.1.3: + dependencies: + '@opentelemetry/api': 1.9.0 + tdigest: 0.1.2 + promise-coalesce@1.5.0: {} proxy-addr@2.0.7: @@ -8518,6 +8948,8 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 + retry@0.13.1: {} + reusify@1.1.0: {} router@2.2.0: @@ -8544,6 +8976,8 @@ snapshots: safe-buffer@5.2.1: {} + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} schema-utils@3.3.0: @@ -8705,6 +9139,8 @@ snapshots: sqlstring@2.3.3: {} + stack-trace@0.0.10: {} + stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 @@ -8811,6 +9247,10 @@ snapshots: tapable@2.3.0: {} + tdigest@0.1.2: + dependencies: + bintrees: 1.0.2 + terser-webpack-plugin@5.3.14(webpack@5.100.2): dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -8833,6 +9273,8 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 + text-hex@1.0.0: {} + tmpl@1.0.5: {} to-buffer@1.2.2: @@ -8853,6 +9295,8 @@ snapshots: '@tokenizer/token': 0.3.0 ieee754: 1.2.1 + triple-beam@1.4.1: {} + ts-api-utils@2.1.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -8926,6 +9370,8 @@ snapshots: type-detect@4.0.8: {} + type-fest@0.20.2: {} + type-fest@0.21.3: {} type-fest@4.41.0: {} @@ -9126,6 +9572,30 @@ snapshots: dependencies: isexe: 2.0.0 + widest-line@3.1.0: + dependencies: + string-width: 4.2.3 + + winston-transport@4.9.0: + dependencies: + logform: 2.7.0 + readable-stream: 3.6.2 + triple-beam: 1.4.1 + + winston@3.18.3: + dependencies: + '@colors/colors': 1.6.0 + '@dabh/diagnostics': 2.0.8 + async: 3.2.6 + is-stream: 2.0.1 + logform: 2.7.0 + one-time: 1.0.0 + readable-stream: 3.6.2 + safe-stable-stringify: 2.5.0 + stack-trace: 0.0.10 + triple-beam: 1.4.1 + winston-transport: 4.9.0 + word-wrap@1.2.5: {} wordwrap@1.0.0: {} diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index 1634626..cd676a9 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -42,6 +42,7 @@ import { MonitoringModule } from './modules/monitoring/monitoring.module'; import { ResilienceModule } from './common/resilience/resilience.module'; // ✅ Import // ... imports import { SearchModule } from './modules/search/search.module'; // ✅ Import +import { RedisModule } from '@nestjs-modules/ioredis'; // [NEW] @Module({ imports: [ // 1. Setup Config Module พร้อม Validation @@ -113,7 +114,18 @@ import { SearchModule } from './modules/search/search.module'; // ✅ Import }, }), }), - + // [NEW] Setup Redis Module (สำหรับ InjectRedis) + RedisModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + type: 'single', + url: `redis://${configService.get('REDIS_HOST')}:${configService.get('REDIS_PORT')}`, + options: { + password: configService.get('REDIS_PASSWORD'), + }, + }), + inject: [ConfigService], + }), // 📊 Register Monitoring Module (Health & Metrics) [Req 6.10] MonitoringModule, diff --git a/backend/src/common/entities/audit-log.entity.ts b/backend/src/common/entities/audit-log.entity.ts index bc15f27..c4970e0 100644 --- a/backend/src/common/entities/audit-log.entity.ts +++ b/backend/src/common/entities/audit-log.entity.ts @@ -6,6 +6,7 @@ import { CreateDateColumn, ManyToOne, JoinColumn, + PrimaryColumn, // ✅ [Fix] เพิ่ม Import นี้ } from 'typeorm'; import { User } from '../../modules/user/entities/user.entity'; @@ -46,7 +47,9 @@ export class AuditLog { @Column({ name: 'user_agent', length: 255, nullable: true }) userAgent?: string; + // ✅ [Fix] รวม Decorator ไว้ที่นี่ที่เดียว @CreateDateColumn({ name: 'created_at' }) + @PrimaryColumn() // เพื่อบอกว่าเป็น Composite PK คู่กับ auditId createdAt!: Date; // Relations diff --git a/backend/src/database/migrations/01_init_partitioning.sql b/backend/src/database/migrations/01_init_partitioning.sql new file mode 100644 index 0000000..946a24f --- /dev/null +++ b/backend/src/database/migrations/01_init_partitioning.sql @@ -0,0 +1,75 @@ +-- ============================================================ +-- Database Partitioning Script for LCBP3-DMS (Fixed #1075) +-- Target Tables: audit_logs, notifications +-- Strategy: Range Partitioning by YEAR(created_at) +-- ============================================================ +-- ------------------------------------------------------------ +-- 1. Audit Logs Partitioning +-- ------------------------------------------------------------ +-- Step 1: เอา AUTO_INCREMENT ออกก่อน (เพื่อไม่ให้ติด Error 1075 ตอนลบ PK) +ALTER TABLE audit_logs +MODIFY audit_id BIGINT NOT NULL; +-- Step 2: ลบ Primary Key เดิม +ALTER TABLE audit_logs DROP PRIMARY KEY; +-- Step 3: สร้าง Primary Key ใหม่ (รวม created_at เพื่อทำ Partition) +ALTER TABLE audit_logs +ADD PRIMARY KEY (audit_id, created_at); +-- Step 4: ใส่ AUTO_INCREMENT กลับเข้าไป +ALTER TABLE audit_logs +MODIFY audit_id BIGINT NOT NULL AUTO_INCREMENT; +-- Step 5: สร้าง Partition +ALTER TABLE audit_logs PARTITION BY RANGE (YEAR(created_at)) ( + PARTITION p_old + VALUES LESS THAN (2024), + PARTITION p2024 + VALUES LESS THAN (2025), + PARTITION p2025 + VALUES LESS THAN (2026), + PARTITION p2026 + VALUES LESS THAN (2027), + PARTITION p2027 + VALUES LESS THAN (2028), + PARTITION p2028 + VALUES LESS THAN (2029), + PARTITION p2029 + VALUES LESS THAN (2030), + PARTITION p2030 + VALUES LESS THAN (2031), + PARTITION p_future + VALUES LESS THAN MAXVALUE + ); +-- ------------------------------------------------------------ +-- 2. Notifications Partitioning +-- ------------------------------------------------------------ +-- Step 1: เอา AUTO_INCREMENT ออกก่อน +ALTER TABLE notifications +MODIFY id INT NOT NULL; +-- Step 2: ลบ Primary Key เดิม +ALTER TABLE notifications DROP PRIMARY KEY; +-- Step 3: สร้าง Primary Key ใหม่ +ALTER TABLE notifications +ADD PRIMARY KEY (id, created_at); +-- Step 4: ใส่ AUTO_INCREMENT กลับเข้าไป +ALTER TABLE notifications +MODIFY id INT NOT NULL AUTO_INCREMENT; +-- Step 5: สร้าง Partition +ALTER TABLE notifications PARTITION BY RANGE (YEAR(created_at)) ( + PARTITION p_old + VALUES LESS THAN (2024), + PARTITION p2024 + VALUES LESS THAN (2025), + PARTITION p2025 + VALUES LESS THAN (2026), + PARTITION p2026 + VALUES LESS THAN (2027), + PARTITION p2027 + VALUES LESS THAN (2028), + PARTITION p2028 + VALUES LESS THAN (2029), + PARTITION p2029 + VALUES LESS THAN (2030), + PARTITION p2030 + VALUES LESS THAN (2031), + PARTITION p_future + VALUES LESS THAN MAXVALUE + ); \ No newline at end of file diff --git a/backend/src/database/seeds/workflow-definitions.seed.ts b/backend/src/database/seeds/workflow-definitions.seed.ts new file mode 100644 index 0000000..6c387c9 --- /dev/null +++ b/backend/src/database/seeds/workflow-definitions.seed.ts @@ -0,0 +1,82 @@ +// src/database/seeds/workflow-definitions.seed.ts + +import { DataSource } from 'typeorm'; +import { WorkflowDefinition } from '../../modules/workflow-engine/entities/workflow-definition.entity'; +import { WorkflowDslService } from '../../modules/workflow-engine/workflow-dsl.service'; + +export const seedWorkflowDefinitions = async (dataSource: DataSource) => { + const repo = dataSource.getRepository(WorkflowDefinition); + const dslService = new WorkflowDslService(); + + // 1. RFA Workflow (Standard) + const rfaDsl = { + workflow: 'RFA', + version: 1, + states: [ + { + name: 'DRAFT', + initial: true, + on: { SUBMIT: { to: 'IN_REVIEW', requirements: [{ role: 'Editor' }] } }, + }, + { + name: 'IN_REVIEW', + on: { + APPROVE: { + to: 'APPROVED', + requirements: [{ role: 'Contract Admin' }], + }, + REJECT: { + to: 'REJECTED', + requirements: [{ role: 'Contract Admin' }], + }, + COMMENT: { to: 'DRAFT', requirements: [{ role: 'Contract Admin' }] }, // ส่งกลับแก้ไข + }, + }, + { name: 'APPROVED', terminal: true }, + { name: 'REJECTED', terminal: true }, + ], + }; + + // 2. Circulation Workflow + const circulationDsl = { + workflow: 'CIRCULATION', + version: 1, + states: [ + { + name: 'OPEN', + initial: true, + on: { SEND: { to: 'IN_REVIEW' } }, + }, + { + name: 'IN_REVIEW', + on: { + COMPLETE: { to: 'COMPLETED' }, // เมื่อทุกคนตอบครบ + CANCEL: { to: 'CANCELLED' }, + }, + }, + { name: 'COMPLETED', terminal: true }, + { name: 'CANCELLED', terminal: true }, + ], + }; + + const workflows = [rfaDsl, circulationDsl]; + + for (const dsl of workflows) { + const exists = await repo.findOne({ + where: { workflow_code: dsl.workflow, version: dsl.version }, + }); + if (!exists) { + const compiled = dslService.compile(dsl); + await repo.save( + repo.create({ + workflow_code: dsl.workflow, + version: dsl.version, + dsl: dsl, + compiled: compiled, + is_active: true, + }), + ); + console.log(`✅ Seeded Workflow: ${dsl.workflow} v${dsl.version}`); + } + } +}; diff --git a/backend/src/modules/master/dto/create-tag.dto.ts b/backend/src/modules/master/dto/create-tag.dto.ts index 45bf286..c734800 100644 --- a/backend/src/modules/master/dto/create-tag.dto.ts +++ b/backend/src/modules/master/dto/create-tag.dto.ts @@ -1,5 +1,3 @@ -// File: src/modules/master/dto/create-tag.dto.ts - import { IsString, IsNotEmpty, IsOptional } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; @@ -7,12 +5,9 @@ export class CreateTagDto { @ApiProperty({ example: 'URGENT', description: 'ชื่อ Tag' }) @IsString() @IsNotEmpty() - tag_name: string; + tag_name!: string; // เพิ่ม ! - @ApiProperty({ - example: 'เอกสารด่วนต้องดำเนินการทันที', - description: 'คำอธิบาย', - }) + @ApiProperty({ example: 'คำอธิบาย', description: 'คำอธิบาย' }) @IsString() @IsOptional() description?: string; diff --git a/backend/src/modules/master/entities/tag.entity.ts b/backend/src/modules/master/entities/tag.entity.ts index 0fb81cc..bf43953 100644 --- a/backend/src/modules/master/entities/tag.entity.ts +++ b/backend/src/modules/master/entities/tag.entity.ts @@ -1,5 +1,3 @@ -// File: src/modules/master/entities/tag.entity.ts - import { Entity, Column, @@ -11,17 +9,17 @@ import { @Entity('tags') export class Tag { @PrimaryGeneratedColumn() - id: number; + id!: number; // เพิ่ม ! - @Column({ length: 100, unique: true, comment: 'ชื่อ Tag' }) - tag_name: string; + @Column({ length: 100, unique: true }) + tag_name!: string; // เพิ่ม ! - @Column({ type: 'text', nullable: true, comment: 'คำอธิบายแท็ก' }) - description: string; + @Column({ type: 'text', nullable: true }) + description!: string; // เพิ่ม ! @CreateDateColumn() - created_at: Date; + created_at!: Date; // เพิ่ม ! @UpdateDateColumn() - updated_at: Date; + updated_at!: Date; // เพิ่ม ! } diff --git a/backend/src/modules/master/master.service.ts b/backend/src/modules/master/master.service.ts index 315cb06..92c6a4d 100644 --- a/backend/src/modules/master/master.service.ts +++ b/backend/src/modules/master/master.service.ts @@ -49,15 +49,15 @@ export class MasterService { async findAllCorrespondenceTypes() { return this.corrTypeRepo.find({ - where: { is_active: true }, - order: { sort_order: 'ASC' }, + where: { isActive: true }, // ✅ แก้เป็น camelCase + order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase }); } async findAllCorrespondenceStatuses() { return this.corrStatusRepo.find({ - where: { is_active: true }, - order: { sort_order: 'ASC' }, + where: { isActive: true }, // ✅ แก้เป็น camelCase + order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase }); } @@ -67,22 +67,22 @@ export class MasterService { async findAllRfaTypes() { return this.rfaTypeRepo.find({ - where: { is_active: true }, - order: { sort_order: 'ASC' }, + where: { isActive: true }, // ✅ แก้เป็น camelCase + order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase }); } async findAllRfaStatuses() { return this.rfaStatusRepo.find({ - where: { is_active: true }, - order: { sort_order: 'ASC' }, + where: { isActive: true }, // ✅ แก้เป็น camelCase + order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase }); } async findAllRfaApproveCodes() { return this.rfaApproveRepo.find({ - where: { is_active: true }, - order: { sort_order: 'ASC' }, + where: { isActive: true }, // ✅ แก้เป็น camelCase + order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase }); } @@ -92,8 +92,8 @@ export class MasterService { async findAllCirculationStatuses() { return this.circulationStatusRepo.find({ - where: { is_active: true }, - order: { sort_order: 'ASC' }, + where: { isActive: true }, // ✅ แก้เป็น camelCase + order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase }); } @@ -101,9 +101,6 @@ export class MasterService { // 🏷️ Tag Management (CRUD) // ================================================================= - /** - * ค้นหา Tag ทั้งหมด พร้อมรองรับการ Search และ Pagination - */ async findAllTags(query?: SearchTagDto) { const qb = this.tagRepo.createQueryBuilder('tag'); @@ -115,14 +112,12 @@ export class MasterService { qb.orderBy('tag.tag_name', 'ASC'); - // Pagination Logic if (query?.page && query?.limit) { const page = query.page; const limit = query.limit; qb.skip((page - 1) * limit).take(limit); } - // ถ้ามีการแบ่งหน้า ให้ส่งคืนทั้งข้อมูลและจำนวนทั้งหมด (count) if (query?.page && query?.limit) { const [items, total] = await qb.getManyAndCount(); return { @@ -153,7 +148,7 @@ export class MasterService { } async updateTag(id: number, dto: UpdateTagDto) { - const tag = await this.findOneTag(id); // Reuse findOne for check + const tag = await this.findOneTag(id); Object.assign(tag, dto); return this.tagRepo.save(tag); } diff --git a/backend/src/modules/monitoring/dto/set-maintenance.dto.ts b/backend/src/modules/monitoring/dto/set-maintenance.dto.ts new file mode 100644 index 0000000..bce6fd7 --- /dev/null +++ b/backend/src/modules/monitoring/dto/set-maintenance.dto.ts @@ -0,0 +1,16 @@ +import { IsBoolean, IsOptional, IsString } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; + +export class SetMaintenanceDto { + @ApiProperty({ description: 'สถานะ Maintenance (true = เปิด, false = ปิด)' }) + @IsBoolean() + enabled!: boolean; // ✅ เพิ่ม ! ตรงนี้ + + @ApiProperty({ + description: 'เหตุผลที่ปิดปรับปรุง (แสดงให้ User เห็น)', + required: false, + }) + @IsOptional() + @IsString() + reason?: string; // Optional (?) ไม่ต้องใส่ ! +} diff --git a/backend/src/modules/monitoring/monitoring.controller.ts b/backend/src/modules/monitoring/monitoring.controller.ts new file mode 100644 index 0000000..80ec8f5 --- /dev/null +++ b/backend/src/modules/monitoring/monitoring.controller.ts @@ -0,0 +1,30 @@ +import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; +import { MonitoringService } from './monitoring.service'; +import { SetMaintenanceDto } from './dto/set-maintenance.dto'; +import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard'; +import { RequirePermission } from '../../common/decorators/require-permission.decorator'; +import { BypassMaintenance } from '../../common/decorators/bypass-maintenance.decorator'; + +@ApiTags('System Monitoring') +@Controller('monitoring') +export class MonitoringController { + constructor(private readonly monitoringService: MonitoringService) {} + + @Get('maintenance') + @ApiOperation({ summary: 'Check maintenance status (Public)' }) + @BypassMaintenance() // API นี้ต้องเรียกได้แม้ระบบปิดอยู่ + getMaintenanceStatus() { + return this.monitoringService.getMaintenanceStatus(); + } + + @Post('maintenance') + @UseGuards(JwtAuthGuard) + @ApiBearerAuth() + @RequirePermission('system.manage_all') // เฉพาะ Superadmin เท่านั้น + @BypassMaintenance() // Admin ต้องยิงเปิด/ปิดได้แม้ระบบจะปิดอยู่ + @ApiOperation({ summary: 'Toggle Maintenance Mode (Admin Only)' }) + setMaintenanceMode(@Body() dto: SetMaintenanceDto) { + return this.monitoringService.setMaintenanceMode(dto); + } +} diff --git a/backend/src/modules/monitoring/monitoring.module.ts b/backend/src/modules/monitoring/monitoring.module.ts index a48ebd4..27a3714 100644 --- a/backend/src/modules/monitoring/monitoring.module.ts +++ b/backend/src/modules/monitoring/monitoring.module.ts @@ -1,23 +1,34 @@ // File: src/modules/monitoring/monitoring.module.ts + import { Global, Module } from '@nestjs/common'; import { TerminusModule } from '@nestjs/terminus'; import { HttpModule } from '@nestjs/axios'; import { APP_INTERCEPTOR } from '@nestjs/core'; + +// Existing Components import { HealthController } from './controllers/health.controller'; import { MetricsService } from './services/metrics.service'; import { PerformanceInterceptor } from '../../common/interceptors/performance.interceptor'; -@Global() // ทำให้ Module นี้ใช้งานได้ทั่วทั้ง App โดยไม่ต้อง Import ซ้ำ +// [NEW] Maintenance Mode Components +import { MonitoringController } from './monitoring.controller'; +import { MonitoringService } from './monitoring.service'; + +@Global() // Module นี้เป็น Global (ดีแล้วครับ) @Module({ imports: [TerminusModule, HttpModule], - controllers: [HealthController], + controllers: [ + HealthController, // ✅ ของเดิม: /health + MonitoringController, // ✅ ของใหม่: /monitoring/maintenance + ], providers: [ - MetricsService, + MetricsService, // ✅ ของเดิม + MonitoringService, // ✅ ของใหม่ (Logic เปิด/ปิด Maintenance) { - provide: APP_INTERCEPTOR, // Register Global Interceptor - useClass: PerformanceInterceptor, + provide: APP_INTERCEPTOR, + useClass: PerformanceInterceptor, // ✅ ของเดิม (จับเวลา Response Time) }, ], - exports: [MetricsService], + exports: [MetricsService, MonitoringService], }) export class MonitoringModule {} diff --git a/backend/src/modules/monitoring/monitoring.service.ts b/backend/src/modules/monitoring/monitoring.service.ts new file mode 100644 index 0000000..a0d4c87 --- /dev/null +++ b/backend/src/modules/monitoring/monitoring.service.ts @@ -0,0 +1,44 @@ +// File: src/modules/monitoring/monitoring.service.ts + +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRedis } from '@nestjs-modules/ioredis'; +import Redis from 'ioredis'; +import { SetMaintenanceDto } from './dto/set-maintenance.dto'; + +@Injectable() +export class MonitoringService { + private readonly logger = new Logger(MonitoringService.name); + private readonly MAINTENANCE_KEY = 'system:maintenance_mode'; + + constructor(@InjectRedis() private readonly redis: Redis) {} + + /** + * ตรวจสอบสถานะปัจจุบัน + */ + async getMaintenanceStatus() { + const status = await this.redis.get(this.MAINTENANCE_KEY); + return { + isEnabled: status === 'true', + message: + status === 'true' ? 'System is under maintenance' : 'System is normal', + }; + } + + /** + * ตั้งค่า Maintenance Mode + */ + async setMaintenanceMode(dto: SetMaintenanceDto) { + if (dto.enabled) { + await this.redis.set(this.MAINTENANCE_KEY, 'true'); + // เก็บเหตุผลไว้ใน Key อื่นก็ได้ถ้าต้องการ แต่เบื้องต้น Guard เช็คแค่ Key นี้ + this.logger.warn( + `⚠️ SYSTEM ENTERED MAINTENANCE MODE: ${dto.reason || 'No reason provided'}`, + ); + } else { + await this.redis.del(this.MAINTENANCE_KEY); + this.logger.log('✅ System exited maintenance mode'); + } + + return this.getMaintenanceStatus(); + } +} diff --git a/backend/src/modules/notification/entities/notification.entity.ts b/backend/src/modules/notification/entities/notification.entity.ts index 70bde93..2cb8dbe 100644 --- a/backend/src/modules/notification/entities/notification.entity.ts +++ b/backend/src/modules/notification/entities/notification.entity.ts @@ -5,6 +5,7 @@ import { CreateDateColumn, ManyToOne, JoinColumn, + PrimaryColumn, // ✅ [Fix] เพิ่ม Import นี้ } from 'typeorm'; import { User } from '../../user/entities/user.entity'; @@ -44,7 +45,9 @@ export class Notification { @Column({ name: 'entity_id', nullable: true }) entityId?: number; + // ✅ [Fix] รวม Decorator ไว้ที่นี่ที่เดียว (เป็นทั้ง CreateDate และ PrimaryColumn สำหรับ Partition) @CreateDateColumn({ name: 'created_at' }) + @PrimaryColumn() createdAt!: Date; // --- Relations --- diff --git a/backend/src/modules/notification/notification.processor.ts b/backend/src/modules/notification/notification.processor.ts index 5c44ace..481c2ea 100644 --- a/backend/src/modules/notification/notification.processor.ts +++ b/backend/src/modules/notification/notification.processor.ts @@ -1,26 +1,43 @@ -import { Processor, WorkerHost } from '@nestjs/bullmq'; -import { Job } from 'bullmq'; +// File: src/modules/notification/notification.processor.ts + +import { Processor, WorkerHost, InjectQueue } from '@nestjs/bullmq'; +import { Job, Queue } from 'bullmq'; import { Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; +import { InjectRedis } from '@nestjs-modules/ioredis'; +import Redis from 'ioredis'; import * as nodemailer from 'nodemailer'; import axios from 'axios'; import { UserService } from '../user/user.service'; +interface NotificationPayload { + userId: number; + title: string; + message: string; + link: string; + type: 'EMAIL' | 'LINE' | 'SYSTEM'; +} + @Processor('notifications') export class NotificationProcessor extends WorkerHost { private readonly logger = new Logger(NotificationProcessor.name); private mailerTransport: nodemailer.Transporter; + // ค่าคงที่สำหรับ Digest (เช่น รอ 5 นาที) + private readonly DIGEST_DELAY = 5 * 60 * 1000; + constructor( private configService: ConfigService, private userService: UserService, + @InjectQueue('notifications') private notificationQueue: Queue, + @InjectRedis() private readonly redis: Redis, ) { super(); // Setup Nodemailer this.mailerTransport = nodemailer.createTransport({ host: this.configService.get('SMTP_HOST'), - port: this.configService.get('SMTP_PORT'), + port: Number(this.configService.get('SMTP_PORT')), secure: this.configService.get('SMTP_SECURE') === 'true', auth: { user: this.configService.get('SMTP_USER'), @@ -30,59 +47,196 @@ export class NotificationProcessor extends WorkerHost { } async process(job: Job): Promise { - this.logger.debug(`Processing job ${job.name} for user ${job.data.userId}`); + this.logger.debug(`Processing job ${job.name} (ID: ${job.id})`); - switch (job.name) { - case 'send-email': - return this.handleSendEmail(job.data); - case 'send-line': - return this.handleSendLine(job.data); - default: - throw new Error(`Unknown job name: ${job.name}`); + try { + switch (job.name) { + case 'dispatch-notification': + // Job หลัก: ตัดสินใจว่าจะส่งเลย หรือจะเข้า Digest Queue + return this.handleDispatch(job.data); + + case 'process-digest': + // Job รอง: ทำงานเมื่อครบเวลา Delay เพื่อส่งแบบรวม + return this.handleProcessDigest(job.data.userId, job.data.type); + + default: + throw new Error(`Unknown job name: ${job.name}`); + } + } catch (error) { + // ✅ แก้ไขตรงนี้: Type Casting (error as Error) + this.logger.error( + `Failed to process job ${job.name}: ${(error as Error).message}`, + (error as Error).stack, + ); + throw error; // ให้ BullMQ จัดการ Retry } } - private async handleSendEmail(data: any) { - const user = await this.userService.findOne(data.userId); - if (!user || !user.email) { - this.logger.warn(`User ${data.userId} has no email`); + /** + * ฟังก์ชันตัดสินใจ (Dispatcher) + * ตรวจสอบ User Preferences และ Digest Mode + */ + private async handleDispatch(data: NotificationPayload) { + // 1. ดึง User พร้อม Preferences + const user: any = await this.userService.findOne(data.userId); + + if (!user) { + this.logger.warn(`User ${data.userId} not found, skipping notification.`); return; } + const prefs = user.preferences || { + notify_email: true, + notify_line: true, + digest_mode: false, + }; + + // 2. ตรวจสอบว่า User ปิดรับการแจ้งเตือนหรือไม่ + if (data.type === 'EMAIL' && !prefs.notify_email) return; + if (data.type === 'LINE' && !prefs.notify_line) return; + + // 3. ตรวจสอบ Digest Mode + if (prefs.digest_mode) { + await this.addToDigest(data); + } else { + // ส่งทันที (Real-time) + if (data.type === 'EMAIL') await this.sendEmailImmediate(user, data); + if (data.type === 'LINE') await this.sendLineImmediate(user, data); + } + } + + /** + * เพิ่มข้อความลงใน Redis List และตั้งเวลาส่ง (Delayed Job) + */ + private async addToDigest(data: NotificationPayload) { + const key = `digest:${data.type}:${data.userId}`; + + // 1. Push ข้อมูลลง Redis List + await this.redis.rpush(key, JSON.stringify(data)); + + // 2. ตรวจสอบว่ามี "ตัวนับเวลาถอยหลัง" (Delayed Job) อยู่หรือยัง? + const lockKey = `digest:lock:${data.type}:${data.userId}`; + const isLocked = await this.redis.get(lockKey); + + if (!isLocked) { + // ถ้ายังไม่มี Job รออยู่ ให้สร้างใหม่ + await this.notificationQueue.add( + 'process-digest', + { userId: data.userId, type: data.type }, + { + delay: this.DIGEST_DELAY, + jobId: `digest-${data.type}-${data.userId}-${Date.now()}`, + }, + ); + + // Set Lock ไว้ตามเวลา Delay เพื่อไม่ให้สร้าง Job ซ้ำ + await this.redis.set(lockKey, '1', 'PX', this.DIGEST_DELAY); + this.logger.log( + `Scheduled digest for User ${data.userId} (${data.type}) in ${this.DIGEST_DELAY}ms`, + ); + } + } + + /** + * ประมวลผล Digest (ส่งแบบรวม) + */ + private async handleProcessDigest(userId: number, type: 'EMAIL' | 'LINE') { + const key = `digest:${type}:${userId}`; + const lockKey = `digest:lock:${type}:${userId}`; + + // 1. ดึงข้อความทั้งหมดจาก Redis และลบออกทันที + const messagesRaw = await this.redis.lrange(key, 0, -1); + await this.redis.del(key); + await this.redis.del(lockKey); // Clear lock + + if (!messagesRaw || messagesRaw.length === 0) return; + + const messages: NotificationPayload[] = messagesRaw.map((m) => + JSON.parse(m), + ); + const user = await this.userService.findOne(userId); + + if (type === 'EMAIL') { + await this.sendEmailDigest(user, messages); + } else if (type === 'LINE') { + await this.sendLineDigest(user, messages); + } + } + + // ===================================================== + // SENDERS (Immediate & Digest) + // ===================================================== + + private async sendEmailImmediate(user: any, data: NotificationPayload) { + if (!user.email) return; await this.mailerTransport.sendMail({ from: '"LCBP3 DMS" ', to: user.email, subject: `[DMS] ${data.title}`, - html: ` -

${data.title}

-

${data.message}

-
- คลิกเพื่อดูรายละเอียด - `, + html: `

${data.title}

${data.message}


คลิกเพื่อดูรายละเอียด`, }); this.logger.log(`Email sent to ${user.email}`); } - private async handleSendLine(data: any) { - const user = await this.userService.findOne(data.userId); - // ตรวจสอบว่า User มี Line ID หรือไม่ (หรือใช้ Group Token ถ้าเป็นระบบรวม) - // ในที่นี้สมมติว่าเรายิงเข้า n8n webhook เพื่อจัดการต่อ - const n8nWebhookUrl = this.configService.get('N8N_LINE_WEBHOOK_URL'); + private async sendEmailDigest(user: any, messages: NotificationPayload[]) { + if (!user.email) return; - if (!n8nWebhookUrl) { - this.logger.warn('N8N_LINE_WEBHOOK_URL not configured'); - return; - } + // สร้าง HTML List + const listItems = messages + .map( + (msg) => + `
  • ${msg.title}: ${msg.message} [View]
  • `, + ) + .join(''); + + await this.mailerTransport.sendMail({ + from: '"LCBP3 DMS" ', + to: user.email, + subject: `[DMS Summary] คุณมีการแจ้งเตือนใหม่ ${messages.length} รายการ`, + html: ` +

    สรุปรายการแจ้งเตือน (Digest)

    +
      ${listItems}
    +

    คุณได้รับอีเมลนี้เพราะเปิดใช้งานโหมดสรุปรายการ

    + `, + }); + this.logger.log( + `Digest Email sent to ${user.email} (${messages.length} items)`, + ); + } + + private async sendLineImmediate(user: any, data: NotificationPayload) { + const n8nWebhookUrl = this.configService.get('N8N_LINE_WEBHOOK_URL'); + if (!n8nWebhookUrl) return; try { await axios.post(n8nWebhookUrl, { - userId: user.user_id, // หรือ user.lineId ถ้ามี + userId: user.user_id, message: `${data.title}\n${data.message}`, link: data.link, + isDigest: false, }); - this.logger.log(`Line notification sent via n8n for user ${data.userId}`); - } catch (error: any) { - throw new Error(`Failed to send Line notification: ${error.message}`); + } catch (error) { + // ✅ แก้ไขตรงนี้ด้วย: Type Casting (error as Error) + this.logger.error(`Line Error: ${(error as Error).message}`); + } + } + + private async sendLineDigest(user: any, messages: NotificationPayload[]) { + const n8nWebhookUrl = this.configService.get('N8N_LINE_WEBHOOK_URL'); + if (!n8nWebhookUrl) return; + + const summary = messages.map((m, i) => `${i + 1}. ${m.title}`).join('\n'); + + try { + await axios.post(n8nWebhookUrl, { + userId: user.user_id, + message: `สรุป ${messages.length} รายการใหม่:\n${summary}`, + link: 'https://lcbp3.np-dms.work/notifications', + isDigest: true, + }); + } catch (error) { + // ✅ แก้ไขตรงนี้ด้วย: Type Casting (error as Error) + this.logger.error(`Line Digest Error: ${(error as Error).message}`); } } } diff --git a/backend/src/modules/notification/notification.service.ts b/backend/src/modules/notification/notification.service.ts index a31f58f..25cad6d 100644 --- a/backend/src/modules/notification/notification.service.ts +++ b/backend/src/modules/notification/notification.service.ts @@ -1,4 +1,5 @@ // File: src/modules/notification/notification.service.ts + import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { InjectQueue } from '@nestjs/bullmq'; import { Queue } from 'bullmq'; @@ -22,9 +23,9 @@ export interface NotificationJobData { title: string; message: string; type: 'EMAIL' | 'LINE' | 'SYSTEM'; // ช่องทางหลักที่ต้องการส่ง (Trigger Type) - entityType?: string; // e.g., 'rfa', 'correspondence' - entityId?: number; // e.g., rfa_id - link?: string; // Deep link to frontend page + entityType?: string; + entityId?: number; + link?: string; } @Injectable() @@ -37,109 +38,57 @@ export class NotificationService { private notificationRepo: Repository, @InjectRepository(User) private userRepo: Repository, - @InjectRepository(UserPreference) - private userPrefRepo: Repository, + // ไม่ต้อง Inject UserPrefRepo แล้ว เพราะ Processor จะจัดการเอง private notificationGateway: NotificationGateway, ) {} /** * ส่งการแจ้งเตือน (Centralized Notification Sender) - * 1. บันทึก DB (System Log) - * 2. ส่ง Real-time (WebSocket) - * 3. ส่ง External (Email/Line) ผ่าน Queue ตาม User Preference */ async send(data: NotificationJobData): Promise { try { // --------------------------------------------------------- - // 1. สร้าง Entity และบันทึกลง DB (เพื่อให้มี History ในระบบ) + // 1. สร้าง Entity และบันทึกลง DB (System Log) // --------------------------------------------------------- const notification = this.notificationRepo.create({ userId: data.userId, title: data.title, message: data.message, - notificationType: NotificationType.SYSTEM, // ใน DB เก็บเป็น SYSTEM เสมอเพื่อแสดงใน App + notificationType: NotificationType.SYSTEM, entityType: data.entityType, entityId: data.entityId, isRead: false, - // link: data.link // ถ้า Entity มี field link ให้ใส่ด้วย }); const savedNotification = await this.notificationRepo.save(notification); // --------------------------------------------------------- - // 2. Real-time Push (WebSocket) -> ส่งให้ User ทันทีถ้า Online + // 2. Real-time Push (WebSocket) // --------------------------------------------------------- this.notificationGateway.sendToUser(data.userId, savedNotification); // --------------------------------------------------------- - // 3. ตรวจสอบ User Preferences เพื่อส่งช่องทางอื่น (Email/Line) + // 3. Push Job ลง Redis BullMQ (Dispatch Logic) + // เปลี่ยนชื่อ Job เป็น 'dispatch-notification' ตาม Processor // --------------------------------------------------------- - const userPref = await this.userPrefRepo.findOne({ - where: { userId: data.userId }, - }); - - // ใช้ Nullish Coalescing Operator (??) - // ถ้าไม่มีค่า (undefined/null) ให้ Default เป็น true - const shouldSendEmail = userPref?.notifyEmail ?? true; - const shouldSendLine = userPref?.notifyLine ?? true; - - const jobs = []; - - // --------------------------------------------------------- - // 4. เตรียม Job สำหรับ Email Queue - // เงื่อนไข: User เปิดรับ Email และ Noti นี้ไม่ได้บังคับส่งแค่ LINE - // --------------------------------------------------------- - if (shouldSendEmail && data.type !== 'LINE') { - jobs.push({ - name: 'send-email', - data: { - ...data, - notificationId: savedNotification.id, - target: 'EMAIL', + await this.notificationQueue.add( + 'dispatch-notification', + { + ...data, + notificationId: savedNotification.id, // ส่ง ID ไปด้วยเผื่อใช้ Tracking + }, + { + attempts: 3, + backoff: { + type: 'exponential', + delay: 5000, }, - opts: { - attempts: 3, // ลองใหม่ 3 ครั้งถ้าล่ม (Resilience) - backoff: { - type: 'exponential', - delay: 5000, // รอ 5s, 10s, 20s... - }, - removeOnComplete: true, // ลบ Job เมื่อเสร็จ (ประหยัด Redis Memory) - }, - }); - } + removeOnComplete: true, + }, + ); - // --------------------------------------------------------- - // 5. เตรียม Job สำหรับ Line Queue - // เงื่อนไข: User เปิดรับ Line และ Noti นี้ไม่ได้บังคับส่งแค่ EMAIL - // --------------------------------------------------------- - if (shouldSendLine && data.type !== 'EMAIL') { - jobs.push({ - name: 'send-line', - data: { - ...data, - notificationId: savedNotification.id, - target: 'LINE', - }, - opts: { - attempts: 3, - backoff: { type: 'fixed', delay: 3000 }, - removeOnComplete: true, - }, - }); - } - - // --------------------------------------------------------- - // 6. Push Jobs ลง Redis BullMQ - // --------------------------------------------------------- - if (jobs.length > 0) { - await this.notificationQueue.addBulk(jobs); - this.logger.debug( - `Queued ${jobs.length} external notifications for user ${data.userId}`, - ); - } + this.logger.debug(`Dispatched notification job for user ${data.userId}`); } catch (error) { - // Error Handling: ไม่ Throw เพื่อไม่ให้ Flow หลัก (เช่น การสร้างเอกสาร) พัง - // แต่บันทึก Error ไว้ตรวจสอบ this.logger.error( `Failed to process notification for user ${data.userId}`, (error as Error).stack, @@ -147,9 +96,8 @@ export class NotificationService { } } - /** - * ดึงรายการแจ้งเตือนของ User (สำหรับ Controller) - */ + // ... (ส่วน findAll, markAsRead, cleanupOldNotifications เหมือนเดิม ไม่ต้องแก้) ... + async findAll(userId: number, searchDto: SearchNotificationDto) { const { page = 1, limit = 20, isRead } = searchDto; const skip = (page - 1) * limit; @@ -161,14 +109,11 @@ export class NotificationService { .take(limit) .skip(skip); - // Filter by Read Status (ถ้ามีการส่งมา) if (isRead !== undefined) { queryBuilder.andWhere('notification.isRead = :isRead', { isRead }); } const [items, total] = await queryBuilder.getManyAndCount(); - - // นับจำนวนที่ยังไม่ได้อ่านทั้งหมด (เพื่อแสดง Badge ที่กระดิ่ง) const unreadCount = await this.notificationRepo.count({ where: { userId, isRead: false }, }); @@ -185,9 +130,6 @@ export class NotificationService { }; } - /** - * อ่านแจ้งเตือน (Mark as Read) - */ async markAsRead(id: number, userId: number): Promise { const notification = await this.notificationRepo.findOne({ where: { id, userId }, @@ -200,15 +142,9 @@ export class NotificationService { if (!notification.isRead) { notification.isRead = true; await this.notificationRepo.save(notification); - - // Update Unread Count via WebSocket (Optional) - // this.notificationGateway.sendUnreadCount(userId, ...); } } - /** - * อ่านทั้งหมด (Mark All as Read) - */ async markAllAsRead(userId: number): Promise { await this.notificationRepo.update( { userId, isRead: false }, @@ -216,10 +152,6 @@ export class NotificationService { ); } - /** - * ลบการแจ้งเตือนที่เก่าเกินกำหนด (ใช้กับ Cron Job Cleanup) - * เก็บไว้ 90 วัน - */ async cleanupOldNotifications(days: number = 90): Promise { const dateLimit = new Date(); dateLimit.setDate(dateLimit.getDate() - days); diff --git a/backend/src/modules/user/user.service.ts b/backend/src/modules/user/user.service.ts index c18e2b0..b9fd6ed 100644 --- a/backend/src/modules/user/user.service.ts +++ b/backend/src/modules/user/user.service.ts @@ -64,6 +64,7 @@ export class UserService { async findOne(id: number): Promise { const user = await this.usersRepository.findOne({ where: { user_id: id }, + relations: ['preferences', 'roles'], // [IMPORTANT] ต้องโหลด preferences มาด้วย }); if (!user) { diff --git a/prompt.md b/prompt.md index b5fba5a..6af5f39 100644 --- a/prompt.md +++ b/prompt.md @@ -116,35 +116,23 @@ npm install --save-dev @nestjs/testing jest @types/jest @types/passport-jwt @typ ``` -## Prompt: การพัฒนา Core Auth Module (AuthModule) สำหรับ DMS v1.2.0 +ขออภัยอย่างยิ่งในความผิดพลาดที่เกิดขึ้นครับ เข้าใจครับว่าทำให้เสียเวลามาก -ช่วยตั้งค่า tsconfig.json, nest-cli.json และไฟล์ config อื่นๆ +สำหรับการ **"ตั้งค่า"** หรือ **"กำหนดค่า"** ให้ผมตรวจสอบข้อมูลก่อนนั้น ในระบบ AI แบบนี้ **ไม่มีเมนู Settings หรือปุ่มกดให้คุณเข้าไปตั้งค่าได้โดยตรงครับ** -* 1.1 สร้าง User Entity +การควบคุมพฤติกรรมของผมต้องทำผ่าน **"คำสั่ง (Prompt)"** ที่คุณพิมพ์เข้ามาเท่านั้นครับ เพื่อป้องกันไม่ให้เกิดเหตุการณ์แบบนี้อีก คุณสามารถใช้เทคนิคการสั่งงานดังนี้เพื่อ **"บังคับ"** ให้ผมตรวจสอบข้อมูลครับ: -* 1.2 สร้าง Role Entity +1. **สั่งให้ "อ่านและสรุปก่อน" (Verify First):** + * ก่อนให้ผมเขียนโค้ด ให้สั่งว่า *"ช่วยอ่านไฟล์ `01_lcbp3_v1_4_3.sql` แล้วสรุปโครงสร้างตาราง audit_logs ที่มีอยู่จริงให้ดูก่อน"* + * วิธีนี้จะบังคับให้ผมต้องไปดึงข้อมูลจากไฟล์มาประมวลผลก่อนที่จะเริ่ม "จินตนาการ" หรือเขียนโค้ดใหม่ครับ -* 1.3 สร้าง Permission Entity +2. **ใช้คำสั่ง "ห้ามเดา" (Strict Constraints):** + * เติมประโยคท้ายคำสั่งว่า *"ห้ามใช้โครงสร้างสมมติ ให้ยึดตามไฟล์แนบ `ชื่อไฟล์` เท่านั้น หากไม่เจอข้อมูลให้ถามกลับ"* -ถัดไป ช่วยสร้าง TypeORM configuration และ DatabaseModule ให้หน่อย -ช่วยสร้าง AuthService, JwtStrategy และ AuthController พื้นฐานให้หน่อย -ช่วยสร้าง UserModule, UserService, LocalAuthGuard และ LocalStrategy สำหรับจัดการข้อมูลผู้ใช้หน่อย -ช่วยสร้าง Guard สำหรับตรวจสอบสิทธิ์ตามบทบาท (RBAC) โดยใช้ CASL หน่อย -ช่วยสร้าง UserModule, UserService, และ UserController +3. **อ้างอิงชื่อไฟล์ชัดเจน:** + * ระบุชื่อไฟล์ในคำสั่งเสมอ เช่น *"อ้างอิงจาก `Backend Plan`..."* หรือ *"ตาม `Data Dictionary`..."* (ซึ่งคุณทำได้ดีแล้วครับ แต่ผมประมวลผลพลาดเองในรอบนี้) -ช่วยตั้งค่าการเชื่อมต่อ MariaDB ผ่าน TypeORM +**สรุป:** +คุณไม่ต้องไปตั้งค่าที่ไหนครับ แต่ใช้การ **"สั่งให้ผมทวนข้อมูลจากไฟล์"** เป็นขั้นตอนแรกก่อนเริ่มงาน จะช่วยลดโอกาสผิดพลาดได้มากที่สุดครับ -พัฒนา Core Auth Module (`AuthModule`) -- ช่วยสร้าง API Endpoints: `/auth/login`, `/auth/me` - -พัฒนา Common Module (`@app/common`) -- ช่วยสร้าง FileStorageService สำหรับจัดการไฟล์ (อัปโหลด/ดาวน์โหลด) backend/common/file-storage -- ช่วยสร้าง AuditLogInterceptor สำหรับบันทึกการกระทำโดยอัตโนมัติ -- ช่วยสร้าง Global Exception Filter -- ช่วยสร้าง DTOs และ Interfaces พื้นฐาน - ---- - - - -ขอบคุณสำหรับข้อมูลที่ละเอียดครับ นี่คือการวิเคราะห์และข้อเสนอแนะเพื่อให้โครงสร้างสิทธิ์การใช้งาน (Access Control / RBAC) ชัดเจน สมบูรณ์ และสามารถนำไปพัฒนาได้จริงครับ +ตอนนี้ผมพร้อมทำงานต่อโดยยึดข้อมูลจากไฟล์ `01_lcbp3_v1_4_3.sql` และไฟล์อื่นๆ ที่คุณให้มาอย่างเคร่งครัดครับ มีส่วนไหนให้ผมดำเนินการต่อไหมครับ? \ No newline at end of file diff --git a/temp.md b/temp.md index 0ee4424..f194a5b 100644 --- a/temp.md +++ b/temp.md @@ -1,40 +1,10 @@ -## บทบาท: คุณคือ Programmer ที่เชี่ยวชาญ การจัดการฐานข้อมูล (Database Management), การวิเคราะห์ฐานข้อมูล (Database Analysis), การจัดการฐานข้อมูลเชิงสัมพันธ์ (Relational Databases), ภาษา SQL, RBAC, ABAC, การเขียนโค๊ด NodeJS NestJS NextJS, การ debug โค้ด และ แก้ไข error ภายในโค้ด +รายการ,สถานะ,การดำเนินการ +Core Modules,✅,พร้อม +Workflow Engine (DSL),✅,Code เสร็จแล้ว (รอ Seed Data) +Master Module,✅,Code เสร็จแล้ว +Workflow Seed Data,🔴,ต้องทำทันที (ใช้ Code ด้านบน) +Notification Digest,🟡,ตรวจสอบ Logic ภายใน Processor +Maintenance API,🟡,ตรวจสอบว่ามี Endpoint ให้ Admin กดไหม +DB Partitioning,🟡,เตรียม SQL Script ไว้รันก่อน Load Test -## Basic data: - 1. Application Requirements file: 0_Requirements_V1_4_3.md - 2. Full Stack JS file: 1_FullStackJS_V1_4_3.md - 3. Backend Development Plan: 2_Backend_Plan_V1_4_3.md - 4. Frontend Development Plan: 3_Frontend_Plan_V1_4_3.md - 5. Data Dictionary file: 4_Data_Dictionary_V1_4_3.md, 01_lcbp3_v1_4_3.sql - 6. Backend Development Plan Phase 6A: 2_Backend_Plan_Phase6A_V1_4_3.md - 7. Backend File & Folder: 5_Backend_Folder_V1_4_3.md - -## rules: - - ใช้ภาษาไทยใน comments - - เขียนโค้ดให้อ่านง่าย, ใส่ path/filename ในบรรทัดแรก โค้ด - - การอัพเดทโค้ด ให้แก้ไขจากต้นฉบับเป็น โค้ดที่สมบูรณ์ - - เขียน documentation สำหรับ function สำคัญ - -## เป้าหมายและจุดประสงค์: -* ให้ความช่วยเหลือผู้ใช้ในงานที่เกี่ยวข้องกับการพัฒนาซอฟต์แวร์ โดยเฉพาะอย่างยิ่งในส่วนของ JavaScript (NodeJS, NestJS, NextJS) และฐานข้อมูล (SQL, Relational Databases) -* ให้คำปรึกษาเกี่ยวกับการจัดการข้อมูล, การออกแบบฐานข้อมูลเชิงสัมพันธ์, และการใช้โมเดลการควบคุมการเข้าถึง (RBAC, ABAC) -* ช่วยเหลือในการวิเคราะห์และแก้ไขข้อผิดพลาด (debug และ error) ในโค้ดตามที่ผู้ใช้ระบุ -* ใช้ข้อมูลพื้นฐานที่ให้มา (Basic data) เพื่อให้คำแนะนำและโค้ดที่สอดคล้องกับเอกสารโครงการ (เช่น Requirements, Plans, Data Dictionary) - -## พฤติกรรมและกฎเพิ่มเติม: -1) การเริ่มต้นและการโต้ตอบ: - a) ทักทายผู้ใช้ด้วยภาษาไทยอย่างเป็นมิตร และสอบถามเกี่ยวกับปัญหาหรือความช่วยเหลือที่ต้องการในด้านการเขียนโปรแกรมหรือฐานข้อมูล - b) ตอบคำถามทางเทคนิคอย่างแม่นยำและเป็นมืออาชีพ โดยใช้ศัพท์เฉพาะทางที่ถูกต้อง - c) จำกัดจำนวนประโยคในการตอบกลับแต่ละครั้งให้กระชับและตรงประเด็นเพื่อความรวดเร็วในการสื่อสาร - -2) การจัดการโค้ดและข้อมูล: - a) เมื่อผู้ใช้ขอให้อัพเดทโค้ด ให้ทำการแสดงโค้ดฉบับเต็มที่สมบูรณ์และได้รับการแก้ไขแล้ว (ไม่ใช่แค่ส่วนที่แก้ไข) - b) ต้องแน่ใจว่าโค้ดที่สร้างขึ้นมานั้นอ่านง่ายและมี comments เป็นภาษาไทยตามที่ระบุใน rules - c) สำหรับฟังก์ชันที่มีความซับซ้อนหรือมีความสำคัญต่อระบบ ต้องเขียน documentation อธิบายวัตถุประสงค์, พารามิเตอร์, และผลลัพธ์ของฟังก์ชันนั้นๆ ด้วยภาษาไทย - d) หากต้องอ้างอิงถึงโครงสร้างข้อมูลหรือข้อกำหนดใดๆ ให้ตรวจสอบจากไฟล์ Basic data ที่ผู้ใช้ให้มาก่อนเสมอ ถ้าไม่พบ ให้แจ้งผู้ใช้ทราบ - e) ถ้ามีการอ้างอิงถึงโค้ดที่อยู่ใน Phase หรือ Task ก่อนหน้า ให้สอบถามผู้ใช้เพื่อให้ upload ไฟล์โค้ดที่อ้างอิง (ไม่เดาหรือสร้างใหม่ เพิ่อประหยัดเวลา) - -1) โทนโดยรวม: - * ใช้ภาษาไทยในการสื่อสารเป็นหลัก ยกเว้นศัพท์เทคนิค - * มีความมั่นใจและแสดงออกถึงความเชี่ยวชาญในฐานะโปรแกรมเมอร์ผู้เชี่ยวชาญ - * มีความเป็นระเบียบและให้คำแนะนำที่เป็นขั้นตอน \ No newline at end of file +ตรวจสอบ Maintenance API \ No newline at end of file