From 17d9f172d4a7e5b697a9af147cc021667f9a2575 Mon Sep 17 00:00:00 2001 From: admin Date: Sat, 22 Nov 2025 20:30:51 +0700 Subject: [PATCH] docs: Relocate project documentation files to a new subdirectory and add a versioned archive. --- Documnets/Git_command.md | 13 + Documnets/Project/1.4.2/01_lcbp3_v1_4_2.sql | 2432 -------------- .../Project/1.4.2/0_Requirements_V1_4_2.md | 763 ----- .../Project/1.4.2/1_FullStackJS_V1_4_2..md | 970 ------ .../Project/1.4.2/2_Backend_Plan_V1_4_2.md | 551 ---- .../Project/1.4.2/3_Frontend_Plan_V1_4_2.md | 994 ------ .../Project/1.4.2/4_Data_Dictionary_V1_4_2.md | 2840 ----------------- .../Project/T0-0 Setting Project.md | 0 .../Project/T1-0 Setting Project.md | 0 .../Project/T2-0 Setting Project.md | 0 .../Project/T2-Postman.md | 0 .../Project/T3-0 Setting Project.md | 0 .../Project/T3-Postman.md | 0 Documnets/Project/V1_4_2.zip | Bin 0 -> 102785 bytes 14 files changed, 13 insertions(+), 8550 deletions(-) delete mode 100644 Documnets/Project/1.4.2/01_lcbp3_v1_4_2.sql delete mode 100644 Documnets/Project/1.4.2/0_Requirements_V1_4_2.md delete mode 100644 Documnets/Project/1.4.2/1_FullStackJS_V1_4_2..md delete mode 100644 Documnets/Project/1.4.2/2_Backend_Plan_V1_4_2.md delete mode 100644 Documnets/Project/1.4.2/3_Frontend_Plan_V1_4_2.md delete mode 100644 Documnets/Project/1.4.2/4_Data_Dictionary_V1_4_2.md rename T0-0 Setting Project.md => Documnets/Project/T0-0 Setting Project.md (100%) rename T1-0 Setting Project.md => Documnets/Project/T1-0 Setting Project.md (100%) rename T2-0 Setting Project.md => Documnets/Project/T2-0 Setting Project.md (100%) rename T2-Postman.md => Documnets/Project/T2-Postman.md (100%) rename T3-0 Setting Project.md => Documnets/Project/T3-0 Setting Project.md (100%) rename T3-Postman.md => Documnets/Project/T3-Postman.md (100%) create mode 100644 Documnets/Project/V1_4_2.zip diff --git a/Documnets/Git_command.md b/Documnets/Git_command.md index 4b27f25..8c59057 100644 --- a/Documnets/Git_command.md +++ b/Documnets/Git_command.md @@ -98,6 +98,19 @@ git push git pull ``` +ðŸŸĶ Pull (āļ”āļķāļ‡āļ‡āļēāļ™āļĨāđˆāļēāļŠāļļāļ”) āđāļšāļš rebase + +```bash +git pull --rebase +``` + +ðŸŸĶ āļ”āļđ log + +```bash +git log +``` + + --- ## ðŸ§Đ SECTION 3 – āļ—āļģāļ‡āļēāļ™āļāļąāļš Branch diff --git a/Documnets/Project/1.4.2/01_lcbp3_v1_4_2.sql b/Documnets/Project/1.4.2/01_lcbp3_v1_4_2.sql deleted file mode 100644 index 56c3d20..0000000 --- a/Documnets/Project/1.4.2/01_lcbp3_v1_4_2.sql +++ /dev/null @@ -1,2432 +0,0 @@ --- ========================================================== --- DMS v1.4.3 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.3 Improvements --- Update: revise fron v1.4.2 --- ========================================================== -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.3 --- āļ„āļģāđ€āļ•āļ·āļ­āļ™: āļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļąāđ‰āļ‡āļŦāļĄāļ”āļˆāļ°āļŦāļēāļĒāđ„āļ› āļāļĢāļļāļ“āļē 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/Documnets/Project/1.4.2/0_Requirements_V1_4_2.md b/Documnets/Project/1.4.2/0_Requirements_V1_4_2.md deleted file mode 100644 index 19dcad5..0000000 --- a/Documnets/Project/1.4.2/0_Requirements_V1_4_2.md +++ /dev/null @@ -1,763 +0,0 @@ -# 📝 **Documents Management System Version 1.4.2: Application Requirements Specification** - -**āļŠāļ–āļēāļ™āļ°:** FINAL -**āļ§āļąāļ™āļ—āļĩāđˆ:** 2025-11-19 -**āļ­āđ‰āļēāļ‡āļ­āļīāļ‡āļžāļ·āđ‰āļ™āļāļēāļ™:** v1.4.2 -**Classification:** Internal Technical Documentation - -## 📌 **1. āļ§āļąāļ•āļ–āļļāļ›āļĢāļ°āļŠāļ‡āļ„āđŒ** - -āļŠāļĢāđ‰āļēāļ‡āđ€āļ§āđ‡āļšāđāļ­āļ›āļžāļĨāļīāđ€āļ„āļŠāļąāļ™āļŠāļģāļŦāļĢāļąāļšāļĢāļ°āļšāļšāļšāļĢāļīāļŦāļēāļĢāļˆāļąāļ”āļāļēāļĢāđ€āļ­āļāļŠāļēāļĢāđ‚āļ„āļĢāļ‡āļāļēāļĢ (Document Management System - DMS) āđāļšāļšāļ„āļĢāļšāļ§āļ‡āļˆāļĢ āļ—āļĩāđˆāđ€āļ™āđ‰āļ™āļ„āļ§āļēāļĄāļ›āļĨāļ­āļ”āļ āļąāļĒāļŠāļđāļ‡āļŠāļļāļ” āļ„āļ§āļēāļĄāļ–āļđāļāļ•āđ‰āļ­āļ‡āļ‚āļ­āļ‡āļ‚āđ‰āļ­āļĄāļđāļĨ (Data Integrity) āđāļĨāļ°āļĢāļ­āļ‡āļĢāļąāļšāļāļēāļĢāļ‚āļĒāļēāļĒāļ•āļąāļ§āđƒāļ™āļ­āļ™āļēāļ„āļ• (Scalability) āđ‚āļ”āļĒāđāļāđ‰āđ„āļ‚āļ›āļąāļāļŦāļē Race Condition āđāļĨāļ°āđ€āļžāļīāđˆāļĄāļ„āļ§āļēāļĄāđ€āļŠāļ–āļĩāļĒāļĢāđƒāļ™āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāđ„āļŸāļĨāđŒāđāļĨāļ° Workflow - -- āļĄāļĩāļŸāļąāļ‡āļāđŒāļŠāļąāļ™āļŦāļĨāļąāļāđƒāļ™āļāļēāļĢāļ­āļąāļ›āđ‚āļŦāļĨāļ” āļˆāļąāļ”āđ€āļāđ‡āļš āļ„āđ‰āļ™āļŦāļē āđāļŠāļĢāđŒ āđāļĨāļ°āļ„āļ§āļšāļ„āļļāļĄāļŠāļīāļ—āļ˜āļīāđŒāļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āđ€āļ­āļāļŠāļēāļĢ -- āļŠāđˆāļ§āļĒāļĨāļ”āļāļēāļĢāđƒāļŠāđ‰āđ€āļ­āļāļŠāļēāļĢāļāļĢāļ°āļ”āļēāļĐ āđ€āļžāļīāđˆāļĄāļ„āļ§āļēāļĄāļ›āļĨāļ­āļ”āļ āļąāļĒāđƒāļ™āļāļēāļĢāļˆāļąāļ”āđ€āļāđ‡āļšāļ‚āđ‰āļ­āļĄāļđāļĨ -- āđ€āļžāļīāđˆāļĄāļ„āļ§āļēāļĄāļŠāļ°āļ”āļ§āļāđƒāļ™āļāļēāļĢāļ—āļģāļ‡āļēāļ™āļĢāđˆāļ§āļĄāļāļąāļ™āļĢāļ°āļŦāļ§āđˆāļēāļ‡āļ­āļ‡āļāļĢāļ“āđŒ -- **āđ€āļŠāļĢāļīāļĄ:** āļ›āļĢāļąāļšāļ›āļĢāļļāļ‡āļ„āļ§āļēāļĄāļ›āļĨāļ­āļ”āļ āļąāļĒāļ‚āļ­āļ‡āļĢāļ°āļšāļšāļ”āđ‰āļ§āļĒāļĄāļēāļ•āļĢāļāļēāļĢāļ›āđ‰āļ­āļ‡āļāļąāļ™āļ—āļĩāđˆāļ—āļąāļ™āļŠāļĄāļąāļĒ -- **āđ€āļŠāļĢāļīāļĄ:** āđ€āļžāļīāđˆāļĄāļ„āļ§āļēāļĄāļ—āļ™āļ—āļēāļ™āļ‚āļ­āļ‡āļĢāļ°āļšāļšāļ”āđ‰āļ§āļĒāļāļĨāđ„āļ resilience patterns -- **āđ€āļŠāļĢāļīāļĄ:** āļŠāļĢāđ‰āļēāļ‡āļĢāļ°āļšāļš monitoring āđāļĨāļ° observability āļ—āļĩāđˆāļ„āļĢāļ­āļšāļ„āļĨāļļāļĄ - -## 🛠ïļ **2. āļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄāđāļĨāļ°āđ€āļ—āļ„āđ‚āļ™āđ‚āļĨāļĒāļĩ (System Architecture & Technology Stack)** - -āđƒāļŠāđ‰āļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄāđāļšāļš Headless/API-First āļ—āļĩāđˆāļ—āļąāļ™āļŠāļĄāļąāļĒ āļ—āļģāļ‡āļēāļ™āļ—āļąāđ‰āļ‡āļŦāļĄāļ”āļšāļ™ QNAP Server āļœāđˆāļēāļ™ Container Station āđ€āļžāļ·āđˆāļ­āļ„āļ§āļēāļĄāļŠāļ°āļ”āļ§āļāđƒāļ™āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāđāļĨāļ°āļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļē - -**Domain:** `np-dms.work`, `www.np-dms.work` -**IP:** 159.192.126.103 -**Docker Network:** āļ—āļļāļ Service āļˆāļ°āđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­āļœāđˆāļēāļ™āđ€āļ„āļĢāļ·āļ­āļ‚āđˆāļēāļĒāļāļĨāļēāļ‡āļŠāļ·āđˆāļ­ `lcbp3` āđ€āļžāļ·āđˆāļ­āđƒāļŦāđ‰āļŠāļēāļĄāļēāļĢāļ–āļŠāļ·āđˆāļ­āļŠāļēāļĢāļāļąāļ™āđ„āļ”āđ‰ - -### **2.1 Infrastructure & Environment:** - -- **Server:** QNAP (Model: TS-473A, RAM: 32GB, CPU: AMD Ryzen V1500B) -- **Containerization:** Container Station (Docker & Docker Compose) āđƒāļŠāđ‰ UI āļ‚āļ­āļ‡ Container Station āđ€āļ›āđ‡āļ™āļŦāļĨāļąāļ āđƒāļ™āļāļēāļĢ configuration āđāļĨāļ°āļāļēāļĢāļĢāļąāļ™ docker command -- **Development Environment:** VS Code/Cursor on Windows 11 -- **Data Storage:** `/share/dms-data` āļšāļ™ QNAP -- **āļ‚āđ‰āļ­āļˆāļģāļāļąāļ”:** āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āđƒāļŠāđ‰ .env āđƒāļ™āļāļēāļĢāļāļģāļŦāļ™āļ”āļ•āļąāļ§āđāļ›āļĢāļ āļēāļĒāļ™āļ­āļāđ„āļ”āđ‰ āļ•āđ‰āļ­āļ‡āļāļģāļŦāļ™āļ”āđƒāļ™ docker-compose.yml āđ€āļ—āđˆāļēāļ™āļąāđ‰āļ™ - -### **2.2 āļāļēāļĢāļˆāļąāļ”āļāļēāļĢ Configuration (āļ›āļĢāļąāļšāļ›āļĢāļļāļ‡):** - -- āđƒāļŠāđ‰ `docker-compose.yml` āļŠāļģāļŦāļĢāļąāļš environment variables āļ•āļēāļĄāļ‚āđ‰āļ­āļˆāļģāļāļąāļ”āļ‚āļ­āļ‡ QNAP -- **Secrets Management (āđƒāļŦāļĄāđˆ):** - - āļŦāđ‰āļēāļĄāļĢāļ°āļšāļļ Sensitive Secrets (Password, Keys) āđƒāļ™ `docker-compose.yml` āļŦāļĨāļąāļ - - āļ•āđ‰āļ­āļ‡āđƒāļŠāđ‰āđ„āļŸāļĨāđŒ `docker-compose.override.yml` (āļ—āļĩāđˆāļ–āļđāļ gitignore) āļŠāļģāļŦāļĢāļąāļš Inject Environment Variables āļ—āļĩāđˆāđ€āļ›āđ‡āļ™āļ„āļ§āļēāļĄāļĨāļąāļšāđƒāļ™āđāļ•āđˆāļĨāļ° Environment (Dev/Prod) - - āđ„āļŸāļĨāđŒ `docker-compose.yml` āļŦāļĨāļąāļāđƒāļŦāđ‰āđƒāļŠāđˆāļ„āđˆāļē Dummy āļŦāļĢāļ·āļ­āļ§āđˆāļēāļ‡āđ„āļ§āđ‰ -- **āđāļ•āđˆāļ•āđ‰āļ­āļ‡āļĄāļĩ mechanism āļŠāļģāļŦāļĢāļąāļšāļˆāļąāļ”āļāļēāļĢ sensitive secrets āļ­āļĒāđˆāļēāļ‡āļ›āļĨāļ­āļ”āļ āļąāļĒ** āđ‚āļ”āļĒāđƒāļŠāđ‰: - - Docker secrets (āļ–āđ‰āļēāļĢāļ­āļ‡āļĢāļąāļš) - - External secret management (Hashicorp Vault) āļŦāļĢāļ·āļ­ - - Encrypted environment variables -- Development environment āļĒāļąāļ‡āđƒāļŠāđ‰ .env āđ„āļ”āđ‰ āđāļ•āđˆāļ•āđ‰āļ­āļ‡āđ„āļĄāđˆ commit āđ€āļ‚āđ‰āļē version control -- āļ•āđ‰āļ­āļ‡āļĄāļĩ configuration validation during application startup -- āļ•āđ‰āļ­āļ‡āđāļĒāļ configuration āļ•āļēāļĄ environment (development, staging, production) - -### **2.3 Core Services:** - -- **Code Hosting:** Gitea (Self-hosted on QNAP) - - Application name: git - - Service name: gitea - - Domain: `git.np-dms.work` - - āļŦāļ™āđ‰āļēāļ—āļĩāđˆ: āđ€āļ›āđ‡āļ™āļĻāļđāļ™āļĒāđŒāļāļĨāļēāļ‡āđƒāļ™āļāļēāļĢāđ€āļāđ‡āļšāđāļĨāļ°āļˆāļąāļ”āļāļēāļĢāđ€āļ§āļ­āļĢāđŒāļŠāļąāļ™āļ‚āļ­āļ‡āđ‚āļ„āđ‰āļ” (Source Code) āļŠāļģāļŦāļĢāļąāļšāļ—āļļāļāļŠāđˆāļ§āļ™ - -- **Backend / Data Platform:** NestJS - - Application name: lcbp3-backend - - Service name: backend - - Domain: `backend.np-dms.work` - - Framework: NestJS (Node.js, TypeScript, ESM) - - āļŦāļ™āđ‰āļēāļ—āļĩāđˆ: āļˆāļąāļ”āļāļēāļĢāđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨ (Data Models), āļŠāļĢāđ‰āļēāļ‡ API, āļˆāļąāļ”āļāļēāļĢāļŠāļīāļ—āļ˜āļīāđŒāļœāļđāđ‰āđƒāļŠāđ‰ (Roles & Permissions), āđāļĨāļ°āļŠāļĢāđ‰āļēāļ‡ Workflow āļ—āļąāđ‰āļ‡āļŦāļĄāļ”āļ‚āļ­āļ‡āļĢāļ°āļšāļš - -- **Database:** MariaDB 10.11 - - Application name: lcbp3-db - - Service name: mariadb - - Domain: `db.np-dms.work` - - āļŦāļ™āđ‰āļēāļ—āļĩāđˆ: āļāļēāļ™āļ‚āđ‰āļ­āļĄāļđāļĨāļŦāļĨāļąāļāļŠāļģāļŦāļĢāļąāļšāđ€āļāđ‡āļšāļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļąāđ‰āļ‡āļŦāļĄāļ” - - Tooling: DBeaver (Community Edition), phpmyadmin āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ­āļ­āļāđāļšāļšāđāļĨāļ°āļˆāļąāļ”āļāļēāļĢāļāļēāļ™āļ‚āđ‰āļ­āļĄāļđāļĨ - -- **Database Management:** phpMyAdmin - - Application name: lcbp3-db - - Service: phpmyadmin:5-apache - - Service name: pma - - Domain: `pma.np-dms.work` - - āļŦāļ™āđ‰āļēāļ—āļĩāđˆ: āļˆāļąāļ”āļāļēāļĢāļāļēāļ™āļ‚āđ‰āļ­āļĄāļđāļĨ mariadb āļœāđˆāļēāļ™ Web UI - -- **Frontend:** Next.js - - Application name: lcbp3-frontend - - Service name: frontend - - Domain: `lcbp3.np-dms.work` - - Framework: Next.js (App Router, React, TypeScript, ESM) - - Styling: Tailwind CSS + PostCSS - - Component Library: shadcn/ui - - āļŦāļ™āđ‰āļēāļ—āļĩāđˆ: āļŠāļĢāđ‰āļēāļ‡āļŦāļ™āđ‰āļēāļ•āļēāđ€āļ§āđ‡āļšāđāļ­āļ›āļžāļĨāļīāđ€āļ„āļŠāļąāļ™āļŠāļģāļŦāļĢāļąāļšāđƒāļŦāđ‰āļœāļđāđ‰āđƒāļŠāđ‰āļ‡āļēāļ™āđ€āļ‚āđ‰āļēāļĄāļēāļ”āļđ Dashboard, āļˆāļąāļ”āļāļēāļĢāđ€āļ­āļāļŠāļēāļĢ, āđāļĨāļ°āļ•āļīāļ”āļ•āļēāļĄāļ‡āļēāļ™ āđ‚āļ”āļĒāļˆāļ°āļŠāļ·āđˆāļ­āļŠāļēāļĢāļāļąāļš Backend āļœāđˆāļēāļ™ API - -- **Workflow Automation:** n8n - - Application name: lcbp3-n8n - - Service: n8nio/n8n:latest - - Service name: n8n - - Domain: `n8n.np-dms.work` - - āļŦāļ™āđ‰āļēāļ—āļĩāđˆ: āļˆāļąāļ”āļāļēāļĢ workflow āļĢāļ°āļŦāļ§āđˆāļēāļ‡ Backend āđāļĨāļ° Line - -- **Reverse Proxy:** Nginx Proxy Manager - - Application name: lcbp3-npm - - Service: Nginx Proxy Manager (nginx-proxy-manage: latest) - - Service name: npm - - Domain: `npm.np-dms.work` - - āļŦāļ™āđ‰āļēāļ—āļĩāđˆ: āđ€āļ›āđ‡āļ™āļ”āđˆāļēāļ™āļŦāļ™āđ‰āļēāđƒāļ™āļāļēāļĢāļĢāļąāļš-āļŠāđˆāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨ āļˆāļąāļ”āļāļēāļĢāđ‚āļ”āđ€āļĄāļ™āļ—āļąāđ‰āļ‡āļŦāļĄāļ”, āļ—āļģāļŦāļ™āđ‰āļēāļ—āļĩāđˆāđ€āļ›āđ‡āļ™ Proxy āļŠāļĩāđ‰āđ„āļ›āļĒāļąāļ‡ Service āļ—āļĩāđˆāļ–āļđāļāļ•āđ‰āļ­āļ‡, āđāļĨāļ°āļˆāļąāļ”āļāļēāļĢ SSL Certificate (HTTPS) āđƒāļŦāđ‰āļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī - -- **Search Engine:** Elasticsearch -- **Cache:** Redis - -### **2.4 Business Logic & Consistency (āļ›āļĢāļąāļšāļ›āļĢāļļāļ‡):** - -- **2.4.1 āļ•āļĢāļĢāļāļ°āļ—āļēāļ‡āļ˜āļļāļĢāļāļīāļˆāļ—āļĩāđˆāļ‹āļąāļšāļ‹āđ‰āļ­āļ™āļ—āļąāđ‰āļ‡āļŦāļĄāļ”** (āđ€āļŠāđˆāļ™ āļāļēāļĢāđ€āļ›āļĨāļĩāđˆāļĒāļ™āļŠāļ–āļēāļ™āļ° Workflow [cite: 3.5.4, 3.6.5], āļāļēāļĢāļšāļąāļ‡āļ„āļąāļšāđƒāļŠāđ‰āļŠāļīāļ—āļ˜āļīāđŒ [cite: 4.4], āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļš Deadline [cite: 3.2.5]) **āļˆāļ°āļ–āļđāļāļˆāļąāļ”āļāļēāļĢāđƒāļ™āļāļąāđˆāļ‡ Backend (NestJS)** [cite: 2.3] āđ€āļžāļ·āđˆāļ­āđƒāļŦāđ‰āļŠāļēāļĄāļēāļĢāļ–āļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļēāđāļĨāļ°āļ—āļ”āļŠāļ­āļšāđ„āļ”āđ‰āļ‡āđˆāļēāļĒ (Testability) - -- **2.4.2 Unified Workflow Engine (āđƒāļŦāļĄāđˆ):** āļĢāļ§āļĄ Logic āļāļēāļĢāđ€āļ”āļīāļ™āđ€āļ­āļāļŠāļēāļĢāļ‚āļ­āļ‡ `CorrespondenceRouting` āđāļĨāļ° `RfaWorkflow` āđƒāļŦāđ‰āđƒāļŠāđ‰ Core Engine āđ€āļ”āļĩāļĒāļ§āļāļąāļ™āđ€āļžāļ·āđˆāļ­āļĨāļ”āļ„āļ§āļēāļĄāļ‹āđ‰āļģāļ‹āđ‰āļ­āļ™āđāļĨāļ°āļ‡āđˆāļēāļĒāļ•āđˆāļ­āļāļēāļĢāļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļē - -- **2.4.3 Idempotency Keys (āđƒāļŦāļĄāđˆ):** API āļ—āļĩāđˆāļŠāļģāļ„āļąāļ (āđ€āļŠāđˆāļ™ Submit Document, Approve) āļ•āđ‰āļ­āļ‡āļšāļąāļ‡āļ„āļąāļšāļŠāđˆāļ‡ Header `Idempotency-Key` āđ€āļžāļ·āđˆāļ­āļ›āđ‰āļ­āļ‡āļāļąāļ™āļāļēāļĢāļ—āļģāļĢāļēāļĒāļāļēāļĢāļ‹āđ‰āļģāļˆāļēāļāļāļēāļĢāļāļ”āļ›āļļāđˆāļĄāļĢāļąāļ§āđ† āļŦāļĢāļ·āļ­ Network Retry - -- **2.4.4 Optimistic Locking (āđƒāļŦāļĄāđˆ):** āđƒāļŠāđ‰ Version Column āđƒāļ™ Database āļ„āļ§āļšāļ„āļđāđˆāļāļąāļš Redis Lock āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļŠāļĢāđ‰āļēāļ‡āđ€āļĨāļ‚āļ—āļĩāđˆāđ€āļ­āļāļŠāļēāļĢ āđ€āļžāļ·āđˆāļ­āđ€āļ›āđ‡āļ™ Safety Net āļŠāļąāđ‰āļ™āļŠāļļāļ”āļ—āđ‰āļēāļĒ - -- **2.4.5** **āļˆāļ°āđ„āļĄāđˆāļĄāļĩāļāļēāļĢāđƒāļŠāđ‰ SQL Triggers** āđ€āļžāļ·āđˆāļ­āļ›āđ‰āļ­āļ‡āļāļąāļ™āļ•āļĢāļĢāļāļ°āļ‹āđˆāļ­āļ™āđ€āļĢāđ‰āļ™ (Hidden Logic) āđāļĨāļ°āļ„āļ§āļēāļĄāļ‹āļąāļšāļ‹āđ‰āļ­āļ™āđƒāļ™āļāļēāļĢāļ”āļĩāļšāļąāļ - -### **2.5 Data Migration āđāļĨāļ° Schema Versioning:** - -- āļ•āđ‰āļ­āļ‡āļĄāļĩ database migration scripts āļŠāļģāļŦāļĢāļąāļšāļ—āļļāļ schema change āđ‚āļ”āļĒāđƒāļŠāđ‰ TypeORM migrations -- āļ•āđ‰āļ­āļ‡āļĢāļ­āļ‡āļĢāļąāļš rollback āļ‚āļ­āļ‡ migration āđ„āļ”āđ‰ -- āļ•āđ‰āļ­āļ‡āļĄāļĩ data seeding strategy āļŠāļģāļŦāļĢāļąāļš environment āļ•āđˆāļēāļ‡āđ† (development, staging, production) -- āļ•āđ‰āļ­āļ‡āļĄāļĩ version compatibility between schema versions -- Migration scripts āļ•āđ‰āļ­āļ‡āļœāđˆāļēāļ™āļāļēāļĢāļ—āļ”āļŠāļ­āļšāđƒāļ™ staging environment āļāđˆāļ­āļ™ production -- āļ•āđ‰āļ­āļ‡āļĄāļĩ database backup āļāđˆāļ­āļ™āļ—āļģ migration āđƒāļ™ production - -### **2.6 āļāļĨāļĒāļļāļ—āļ˜āđŒāļ„āļ§āļēāļĄāļ—āļ™āļ—āļēāļ™āđāļĨāļ°āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ” (Resilience & Error Handling Strategy)** - -- 2.6.1 Circuit Breaker Pattern: āđƒāļŠāđ‰āļŠāļģāļŦāļĢāļąāļš external service calls (Email, LINE, Elasticsearch) -- 2.6.2 Retry Mechanism: āļ”āđ‰āļ§āļĒ exponential backoff āļŠāļģāļŦāļĢāļąāļš transient failures -- 2.6.3 Fallback Strategies: Graceful degradation āđ€āļĄāļ·āđˆāļ­āļšāļĢāļīāļāļēāļĢāļ āļēāļĒāļ™āļ­āļāļĨāđ‰āļĄāđ€āļŦāļĨāļ§ -- 2.6.4 Error Handling: Error messages āļ•āđ‰āļ­āļ‡āđ„āļĄāđˆāđ€āļ›āļīāļ”āđ€āļœāļĒāļ‚āđ‰āļ­āļĄāļđāļĨ sensitive -- 2.6.5 Monitoring: Centralized error monitoring āđāļĨāļ° alerting system - -## **ðŸ“Ķ 3. āļ‚āđ‰āļ­āļāļģāļŦāļ™āļ”āļ”āđ‰āļēāļ™āļŸāļąāļ‡āļāđŒāļŠāļąāļ™āļāļēāļĢāļ—āļģāļ‡āļēāļ™ (Functional Requirements)** - -### **3.1. āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡āđ‚āļ„āļĢāļ‡āļāļēāļĢāđāļĨāļ°āļ­āļ‡āļ„āđŒāļāļĢ** - -- 3.1.1. āđ‚āļ„āļĢāļ‡āļāļēāļĢ (Projects): āļĢāļ°āļšāļšāļ•āđ‰āļ­āļ‡āļŠāļēāļĄāļēāļĢāļ–āļˆāļąāļ”āļāļēāļĢāđ€āļ­āļāļŠāļēāļĢāļ āļēāļĒāđƒāļ™āļŦāļĨāļēāļĒāđ‚āļ„āļĢāļ‡āļāļēāļĢāđ„āļ”āđ‰ (āļ›āļąāļˆāļˆāļļāļšāļąāļ™āļĄāļĩ 4 āđ‚āļ„āļĢāļ‡āļāļēāļĢ āđāļĨāļ°āļˆāļ°āđ€āļžāļīāđˆāļĄāļ‚āļķāđ‰āļ™āđƒāļ™āļ­āļ™āļēāļ„āļ•) -- 3.1.2. āļŠāļąāļāļāļē (Contracts): āļĢāļ°āļšāļšāļ•āđ‰āļ­āļ‡āļŠāļēāļĄāļēāļĢāļ–āļˆāļąāļ”āļāļēāļĢāđ€āļ­āļāļŠāļēāļĢāļ āļēāļĒāđƒāļ™āđāļ•āđˆāļĨāļ°āļŠāļąāļāļāļēāđ„āļ”āđ‰ āđƒāļ™āđāļ•āđˆāļĨāļ°āđ‚āļ„āļĢāļ‡āļāļēāļĢ āļĄāļĩāđ„āļ”āđ‰āļŦāļĨāļēāļĒāļŠāļąāļāļāļē āļŦāļĢāļ·āļ­āļ­āļĒāđˆāļēāļ‡āļ™āđ‰āļ­āļĒ 1 āļŠāļąāļāļāļē -- 3.1.3. āļ­āļ‡āļ„āđŒāļāļĢ (Organizations): - - āļĄāļĩāļŦāļĨāļēāļĒāļ­āļ‡āļ„āđŒāļāļĢāđƒāļ™āđ‚āļ„āļĢāļ‡āļāļēāļĢ āļ­āļ‡āļ„āđŒāļāļĢāļ“āđŒāļ—āļĩāđˆāđ€āļ›āđ‡āļ™ Owner, Designer āđāļĨāļ° Consultant āļŠāļēāļĄāļēāļĢāļ–āļ­āļĒāļđāđˆāđƒāļ™āļŦāļĨāļēāļĒāđ‚āļ„āļĢāļ‡āļāļēāļĢāđāļĨāļ°āļŦāļĨāļēāļĒāļŠāļąāļāļāļēāđ„āļ”āđ‰ - - Contractor āļˆāļ°āļ–āļ·āļ­ 1 āļŠāļąāļāļāļē āđāļĨāļ°āļ­āļĒāļđāđˆāđƒāļ™ 1 āđ‚āļ„āļĢāļ‡āļāļēāļĢāđ€āļ—āđˆāļēāļ™āļąāđ‰āļ™ - -### **3.2. āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāđ€āļ­āļāļŠāļēāļĢāđ‚āļ•āđ‰āļ•āļ­āļš (Correspondence Management)** - -- 3.2.1. āļ§āļąāļ•āļ–āļļāļ›āļĢāļ°āļŠāļ‡āļ„āđŒ: āđ€āļ­āļāļŠāļēāļĢāđ‚āļ•āđ‰āļ•āļ­āļš (correspondences) āļĢāļ°āļŦāļ§āđˆāļēāļ‡āļ­āļ‡āļāļĢāļ“āļ·-āļ­āļ‡āļāļĢāļ“āđŒ āļ āļēāļĒāđƒāļ™ āđ‚āļ„āļĢāļ‡āļāļēāļĢ (Projects) āđāļĨāļ°āļĢāļ°āļŦāļ§āđˆāļēāļ‡ āļ­āļ‡āļ„āđŒāļāļĢ-āļ­āļ‡āļ„āđŒāļāļĢ āļ āļēāļĒāļ™āļ­āļ āđ‚āļ„āļĢāļ‡āļāļēāļĢ (Projects), āļĢāļ­āļ‡āļĢāļąāļš To (āļœāļđāđ‰āļĢāļąāļšāļŦāļĨāļąāļ) āđāļĨāļ° CC (āļœāļđāđ‰āļĢāļąāļšāļŠāļģāđ€āļ™āļē) āļŦāļĨāļēāļĒāļ­āļ‡āļ„āđŒāļāļĢ -- 3.2.2. āļ›āļĢāļ°āđ€āļ āļ—āđ€āļ­āļāļŠāļēāļĢ: āļĢāļ°āļšāļšāļ•āđ‰āļ­āļ‡āļĢāļ­āļ‡āļĢāļąāļšāđ€āļ­āļāļŠāļēāļĢāļĢāļđāļ›āđāļšāļš āđ„āļŸāļĨāđŒ PDF āļŦāļĨāļēāļĒāļ›āļĢāļ°āđ€āļ āļ— (Types) āđ€āļŠāđˆāļ™ āļˆāļ”āļŦāļĄāļēāļĒ (Letter), āļ­āļĩāđ€āļĄāļĨāđŒ (Email), Request for Information (RFI), āđāļĨāļ°āļŠāļēāļĄāļēāļĢāļ–āđ€āļžāļīāđˆāļĄāļ›āļĢāļ°āđ€āļ āļ—āđƒāļŦāļĄāđˆāđ„āļ”āđ‰āđƒāļ™āļ āļēāļĒāļŦāļĨāļąāļ‡ -- 3.2.3. āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡āđ€āļ­āļāļŠāļēāļĢ (Correspondence): - - āļœāļđāđ‰āđƒāļŠāđ‰āļ—āļĩāđˆāļĄāļĩāļŠāļīāļ—āļ˜āļīāđŒ (āđ€āļŠāđˆāļ™ Document Control) āļŠāļēāļĄāļēāļĢāļ–āļŠāļĢāđ‰āļēāļ‡āđ€āļ­āļāļŠāļēāļĢāļĢāļ­āđ„āļ§āđ‰āđƒāļ™āļŠāļ–āļēāļ™āļ° āļ‰āļšāļąāļšāļĢāđˆāļēāļ‡" (Draft) āđ„āļ”āđ‰ āļ‹āļķāđˆāļ‡āļœāļđāđ‰āđƒāļŠāđ‰āļ‡āļēāļ™āļ•āđˆāļēāļ‡āļ­āļ‡āļ„āđŒāļāļĢāļˆāļ°āļĄāļ­āļ‡āđ„āļĄāđˆāđ€āļŦāđ‡āļ™ - - āđ€āļĄāļ·āđˆāļ­āļāļ” "Submitted" āđāļĨāđ‰āļ§ āļāļēāļĢāđāļāđ‰āđ„āļ‚, āļ–āļ­āļ™āđ€āļ­āļāļŠāļēāļĢāļāļĨāļąāļšāđ„āļ›āļŠāļ–āļēāļ™āļ° Draft, āļŦāļĢāļ·āļ­āļĒāļāđ€āļĨāļīāļ (Cancel) āļˆāļ°āļ•āđ‰āļ­āļ‡āļ—āļģāđ‚āļ”āļĒāļœāļđāđ‰āđƒāļŠāđ‰āļĢāļ°āļ”āļąāļš Admin āļ‚āļķāđ‰āļ™āđ„āļ› āļžāļĢāđ‰āļ­āļĄāļĢāļ°āļšāļļāđ€āļŦāļ•āļļāļœāļĨ -- 3.2.4. āļāļēāļĢāļ­āđ‰āļēāļ‡āļ­āļīāļ‡āđāļĨāļ°āļˆāļąāļ”āļāļĨāļļāđˆāļĄ: - - āđ€āļ­āļāļŠāļēāļĢāļŠāļēāļĄāļēāļĢāļ–āļ­āđ‰āļēāļ‡āļ–āļķāļ‡ (Reference) āđ€āļ­āļāļŠāļēāļĢāļ‰āļšāļąāļšāļāđˆāļ­āļ™āļŦāļ™āđ‰āļēāđ„āļ”āđ‰āļŦāļĨāļēāļĒāļ‰āļšāļąāļš - - āļŠāļēāļĄāļēāļĢāļ–āļāļģāļŦāļ™āļ” Tag āđ„āļ”āđ‰āļŦāļĨāļēāļĒ Tag āđ€āļžāļ·āđˆāļ­āļˆāļąāļ”āļāļĨāļļāđˆāļĄāđāļĨāļ°āđƒāļŠāđ‰āđƒāļ™āļāļēāļĢāļ„āđ‰āļ™āļŦāļēāļ‚āļąāđ‰āļ™āļŠāļđāļ‡ -- 3.2.5. Correspondence Routing & Workflow - - 3.2.5.1 Routing Templates (āđāļĄāđˆāđāļšāļšāļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­) - - āļœāļđāđ‰āļ”āļđāđāļĨāļĢāļ°āļšāļšāļ•āđ‰āļ­āļ‡āļŠāļēāļĄāļēāļĢāļ–āļŠāļĢāđ‰āļēāļ‡āđāļĄāđˆāđāļšāļšāļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­āđ„āļ”āđ‰ - - āđāļĄāđˆāđāļšāļšāļŠāļēāļĄāļēāļĢāļ–āđ€āļ›āđ‡āļ™āđāļšāļšāļ—āļąāđˆāļ§āđ„āļ› (āđƒāļŠāđ‰āđ„āļ”āđ‰āļ—āļļāļāđ‚āļ„āļĢāļ‡āļāļēāļĢ) āļŦāļĢāļ·āļ­āđ€āļ‰āļžāļēāļ°āđ‚āļ„āļĢāļ‡āļāļēāļĢ - - āđāļ•āđˆāļĨāļ°āđāļĄāđˆāđāļšāļšāļ›āļĢāļ°āļāļ­āļšāļ”āđ‰āļ§āļĒāļĨāļģāļ”āļąāļšāļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­ - - āļāļēāļĢāļŠāđˆāļ‡āļˆāļēāļ Originator -> Organization 1 -> Organization 2 -> Organization 3 āđāļĨāđ‰āļ§āļŠāđˆāļ‡āļœāļĨāļāļĨāļąāļšāļ•āļēāļĄāļĨāļģāļ”āļąāļšāđ€āļ”āļīāļĄ (āđ‚āļ”āļĒāļ–āđ‰āļē āļ­āļ‡āļāļĢāļ“āđŒāđƒāļ”āđƒāļ™ Wouting āđƒāļŦāđ‰āļŠāđˆāļ‡āļāļĨāļąāļš āļāđ‡āļŠāļēāļĄāļēāļĢāļ–āļŠāđˆāļ‡āļœāļĨāļāļĨāļąāļšāļ•āļēāļĄāļĨāļģāļ”āļąāļšāđ€āļ”āļīāļĄāđ‚āļ”āļĒāđ„āļĄāđˆāļ•āđ‰āļ­āļ‡āļĢāļ­āđƒāļŦāđ‰āļ–āļķāļ‡ āļ­āļ‡āļāļĢāļ“āļ·āđƒāļ™āļĨāļģāļ”āļąāļšāļ–āļąāļ”āđ„āļ›) - - 3.2.5.2 Routing Steps (āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­) āđāļ•āđˆāļĨāļ°āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āđƒāļ™āđāļĄāđˆāđāļšāļšāļ•āđ‰āļ­āļ‡āļāļģāļŦāļ™āļ”: - - **āļĨāļģāļ”āļąāļšāļ‚āļąāđ‰āļ™āļ•āļ­āļ™** (Sequence) - - **āļ­āļ‡āļ„āđŒāļāļĢāļœāļđāđ‰āļĢāļąāļš** (To Organization) - - **āļ§āļąāļ•āļ–āļļāļ›āļĢāļ°āļŠāļ‡āļ„āđŒ** (Purpose): āđ€āļžāļ·āđˆāļ­āļ­āļ™āļļāļĄāļąāļ•āļī (FOR_APPROVAL), āđ€āļžāļ·āđˆāļ­āļ•āļĢāļ§āļˆāļŠāļ­āļš (FOR_REVIEW), āđ€āļžāļ·āđˆāļ­āļ—āļĢāļēāļš (FOR_INFORMATION), āđ€āļžāļ·āđˆāļ­āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢ (FOR_ACTION) - - **āļĢāļ°āļĒāļ°āđ€āļ§āļĨāļēāļ—āļĩāđˆāļ„āļēāļ”āļŦāļ§āļąāļ‡** (Expected Duration) - - 3.2.5.3 Actual Routing Execution (āļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­āļˆāļĢāļīāļ‡) āđ€āļĄāļ·āđˆāļ­āļŠāļĢāđ‰āļēāļ‡āđ€āļ­āļāļŠāļēāļĢāđāļĨāļ°āđ€āļĨāļ·āļ­āļāđƒāļŠāđ‰āđāļĄāđˆāđāļšāļš āļĢāļ°āļšāļšāļ•āđ‰āļ­āļ‡: - - āļŠāļĢāđ‰āļēāļ‡āļĨāļģāļ”āļąāļšāļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­āļ•āļēāļĄāđāļĄāđˆāđāļšāļš - - āļ•āļīāļ”āļ•āļēāļĄāļŠāļ–āļēāļ™āļ°āļ‚āļ­āļ‡āđāļ•āđˆāļĨāļ°āļ‚āļąāđ‰āļ™āļ•āļ­āļ™: āļŠāđˆāļ‡āđāļĨāđ‰āļ§ (SENT), āļāļģāļĨāļąāļ‡āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢ (IN_PROGRESS), āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāđāļĨāđ‰āļ§ (ACTIONED), āļŠāđˆāļ‡āļ•āđˆāļ­āđāļĨāđ‰āļ§ (FORWARDED), āļ•āļ­āļšāļāļĨāļąāļšāđāļĨāđ‰āļ§ (REPLIED) - - āļĢāļ°āļšāļļāļ§āļąāļ™āļ„āļĢāļšāļāļģāļŦāļ™āļ” (Due Date) āļŠāļģāļŦāļĢāļąāļšāđāļ•āđˆāļĨāļ°āļ‚āļąāđ‰āļ™āļ•āļ­āļ™ - - āļšāļąāļ™āļ—āļķāļāļœāļđāđ‰āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāđāļĨāļ°āđ€āļ§āļĨāļēāļ—āļĩāđˆāļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢ - - 3.2.5.4 Routing Flexibility (āļ„āļ§āļēāļĄāļĒāļ·āļ”āļŦāļĒāļļāđˆāļ™) - - āļŠāļēāļĄāļēāļĢāļ–āļ‚āđ‰āļēāļĄāļ‚āļąāđ‰āļ™āļ•āļ­āļ™āđ„āļ”āđ‰āđƒāļ™āļāļĢāļ“āļĩāļžāļīāđ€āļĻāļĐ (āđ‚āļ”āļĒāļœāļđāđ‰āļĄāļĩāļŠāļīāļ—āļ˜āļīāđŒ) - - āļŠāļēāļĄāļēāļĢāļ–āļŠāđˆāļ‡āļāļĨāļąāļšāļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļāđˆāļ­āļ™āļŦāļ™āđ‰āļēāđ„āļ”āđ‰ - - āļŠāļēāļĄāļēāļĢāļ–āđ€āļžāļīāđˆāļĄāļ„āļ§āļēāļĄāļ„āļīāļ”āđ€āļŦāđ‡āļ™āđƒāļ™āđāļ•āđˆāļĨāļ°āļ‚āļąāđ‰āļ™āļ•āļ­āļ™ - - āđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļīāđ€āļĄāļ·āđˆāļ­āļ–āļķāļ‡āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āđƒāļŦāļĄāđˆāļŦāļĢāļ·āļ­āđƒāļāļĨāđ‰āļ„āļĢāļšāļāļģāļŦāļ™āļ” -- 3.2.6. āļāļēāļĢāļˆāļąāļ”āļāļēāļĢ: āļĄāļĩāļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļ­āļĒāđˆāļēāļ‡āļ™āđ‰āļ­āļĒāļ”āļąāļ‡āļ™āļĩāđ‰ - - āļŠāļēāļĄāļēāļĢāļ–āļāļģāļŦāļ™āļ”āļ§āļąāļ™āđāļĨāđ‰āļ§āđ€āļŠāļĢāđ‡āļˆ (Deadline) āļŠāļģāļŦāļĢāļąāļšāļœāļđāđ‰āļĢāļąāļšāļœāļīāļ”āļŠāļ­āļšāļ‚āļ­āļ‡ āļ­āļ‡āļāļĢāļ“āđŒ āļ—āļĩāđˆāđ€āļ›āđ‡āļ™āļœāļđāđ‰āļĢāļąāļšāđ„āļ”āđ‰ - - āļĄāļĩāļĢāļ°āļšāļšāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™ āđƒāļŦāđ‰āļœāļđāđ‰āļĢāļąāļšāļœāļīāļ”āļŠāļ­āļšāļ‚āļ­āļ‡āļ­āļ‡āļāļĢāļ“āđŒāļ—āļĩāđˆāđ€āļ›āđ‡āļ™ āļœāļđāđ‰āļĢāļąāļš/āļœāļđāđ‰āļŠāđˆāļ‡ āļ—āļĢāļēāļš āđ€āļĄāļ·āđˆāļ­āļĄāļĩāđ€āļ­āļāļŠāļēāļĢāđƒāļŦāļĄāđˆ āļŦāļĢāļ·āļ­āļĄāļĩāļāļēāļĢāđ€āļ›āļĨāļĩāđˆāļĒāļ™āļŠāļ–āļēāļ™āļ° - -### **3.3. āļāļēāļĢāļˆāļąāļ”āļāļēāđāļšāļšāļ„āļđāđˆāļŠāļąāļāļāļē (Contract Drawing)** - -- 3.3.1. āļ§āļąāļ•āļ–āļļāļ›āļĢāļ°āļŠāļ‡āļ„āđŒ: āđāļšāļšāļ„āļđāđˆāļŠāļąāļāļāļē (Contract Drawing) āđƒāļŠāđ‰āđ€āļžāļ·āđˆāļ­āļ­āđ‰āļēāļ‡āļ­āļīāļ‡āđāļĨāļ°āđƒāļŠāđ‰āđƒāļ™āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļš -- 3.3.2. āļ›āļĢāļ°āđ€āļ āļ—āđ€āļ­āļāļŠāļēāļĢ: āđ„āļŸāļĨāđŒ PDF -- 3.3.3. āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡āđ€āļ­āļāļŠāļēāļĢ: āļœāļđāđ‰āđƒāļŠāđ‰āļ—āļĩāđˆāļĄāļĩāļŠāļīāļ—āļ˜āļīāđŒ āļŠāļēāļĄāļēāļĢāļ–āļŠāļĢāđ‰āļēāļ‡āđāļĨāļ°āđāļāđ‰āđ„āļ‚āđ„āļ”āđ‰ -- 3.3.4. āļāļēāļĢāļ­āđ‰āļēāļ‡āļ­āļīāļ‡āđāļĨāļ°āļˆāļąāļ”āļāļĨāļļāđˆāļĄ: āđƒāļŠāđ‰āļŠāļģāļŦāļĢāļąāļšāļ­āđ‰āļēāļ‡āļ­āļīāļ‡ āđƒāļ™ Shop Drawings, āļĄāļĩāļāļēāļĢāļˆāļąāļ”āļŦāļĄāļ§āļ”āļŦāļĄāļđāđˆāļ‚āļ­āļ‡ Contract Drawing - -### **3.4. āļāļēāļĢāļˆāļąāļ”āļāļēāđāļšāļšāļāđˆāļ­āļŠāļĢāđ‰āļēāļ‡ (Shop Drawing)** - -- 3.4.1. āļ§āļąāļ•āļ–āļļāļ›āļĢāļ°āļŠāļ‡āļ„āđŒ: āđāļšāļšāļāđˆāļ­āļŠāļĢāđ‰āļēāļ‡ (Shop Drawing) āđƒāļŠāđ‰āđ€āđƒāļ™āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļš āđ‚āļ”āļĒāļˆāļąāļ”āļŠāđˆāļ‡āļ”āđ‰āļ§āļĒ Request for Approval (RFA) -- 3.4.2. āļ›āļĢāļ°āđ€āļ āļ—āđ€āļ­āļāļŠāļēāļĢ: āđ„āļŸāļĨāđŒ PDF -- 3.4.3. āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡āđ€āļ­āļāļŠāļēāļĢ: āļœāļđāđ‰āđƒāļŠāđ‰āļ—āļĩāđˆāļĄāļĩāļŠāļīāļ—āļ˜āļīāđŒ āļŠāļēāļĄāļēāļĢāļ–āļŠāļĢāđ‰āļēāļ‡āđāļĨāļ°āđāļāđ‰āđ„āļ‚āđ„āļ”āđ‰ -- 3.4.4. āļāļēāļĢāļ­āđ‰āļēāļ‡āļ­āļīāļ‡āđāļĨāļ°āļˆāļąāļ”āļāļĨāļļāđˆāļĄ: āļŠāđ‰āļŠāļģāļŦāļĢāļąāļšāļ­āđ‰āļēāļ‡āļ­āļīāļ‡ āđƒāļ™ Shop Drawings, āļĄāļĩāļāļēāļĢāļˆāļąāļ”āļŦāļĄāļ§āļ”āļŦāļĄāļđāđˆāļ‚āļ­āļ‡ Shop Drawings - -### **3.5. āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāđ€āļ­āļāļŠāļēāļĢāļ‚āļ­āļ­āļ™āļļāļĄāļąāļ•āļī (Request for Approval & Workflow)** - -- 3.5.1. āļ§āļąāļ•āļ–āļļāļ›āļĢāļ°āļŠāļ‡āļ„āđŒ: āđ€āļ­āļāļŠāļēāļĢāļ‚āļ­āļ­āļ™āļļāļĄāļąāļ•āļī (Request for Approval) āđƒāļŠāđ‰āđƒāļ™āļāļēāļĢāļŠāđˆāļ‡āđ€āļ­āļāļŠāļēāļĢāđ€āļžāļīāļ­āļ‚āļ­āļ­āļ™āļļāļĄāļąāļ•āļī -- 3.5.2. āļ›āļĢāļ°āđ€āļ āļ—āđ€āļ­āļāļŠāļēāļĢ: Request for Approval (RFA) āđ€āļ›āđ‡āļ™āļŠāļ™āļīāļ”āļŦāļ™āļķāđˆāļ‡āļ‚āļ­āļ‡ Correspondence āļ—āļĩāđˆāļĄāļĩāļĨāļąāļāļĐāļ“āļ°āđ€āļ‰āļžāļēāļ°āļ—āļĩāđˆāļ•āđ‰āļ­āļ‡āđ„āļ”āđ‰āļĢāļąāļšāļāļēāļĢāļ­āļ™āļļāļĄāļąāļ•āļī āļĄāļĩāļ›āļĢāļ°āđ€āļ āļ—āļ”āļąāļ‡āļ™āļĩāđ‰: - - Request for Drawing Approval (RFA_DWG) - - Request for Document Approval (RFA_DOC) - - Request for Method statement Approval (RFA_MES) - - Request for Material Approval (RFA_MAT) -- 3.5.2. āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡āđ€āļ­āļāļŠāļēāļĢ: āļœāļđāđ‰āđƒāļŠāđ‰āļ—āļĩāđˆāļĄāļĩāļŠāļīāļ—āļ˜āļīāđŒ āļŠāļēāļĄāļēāļĢāļ–āļŠāļĢāđ‰āļēāļ‡āđāļĨāļ°āđāļāđ‰āđ„āļ‚āđ„āļ”āđ‰ -- 3.5.4. āļāļēāļĢāļ­āđ‰āļēāļ‡āļ­āļīāļ‡āđāļĨāļ°āļˆāļąāļ”āļāļĨāļļāđˆāļĄ: āļāļēāļĢāļˆāļąāļ”āļāļēāļĢ Drawing (RFA_DWG): - - āđ€āļ­āļāļŠāļēāļĢ RFA_DWG āļˆāļ°āļ›āļĢāļ°āļāļ­āļšāđ„āļ›āļ”āđ‰āļ§āļĒ Shop Drawing (shop_drawings) āļŦāļĨāļēāļĒāđāļœāđˆāļ™ āļ‹āļķāđˆāļ‡āđāļ•āđˆāļĨāļ°āđāļœāđˆāļ™āļĄāļĩ Revision āļ‚āļ­āļ‡āļ•āļąāļ§āđ€āļ­āļ‡ - - Shop Drawing āđāļ•āđˆāļĨāļ° Revision āļŠāļēāļĄāļēāļĢāļ–āļ­āđ‰āļēāļ‡āļ­āļīāļ‡āļ–āļķāļ‡ Contract Drawing (Ccontract_drawings) āļŦāļĨāļēāļĒāđāļœāđˆāļ™ āļŦāļĢāļ·āļ­āđ„āļĄāđˆāļ­āđ‰āļēāļ‡āļ–āļķāļ‡āļāđ‡āđ„āļ”āđ‰ - - āļĢāļ°āļšāļšāļ•āđ‰āļ­āļ‡āļĄāļĩāļŠāđˆāļ§āļ™āļŠāļģāļŦāļĢāļąāļšāļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļĄāļđāļĨ Master Data āļ‚āļ­āļ‡āļ—āļąāđ‰āļ‡ Shop Drawing āđāļĨāļ° Contract Drawing āđāļĒāļāļˆāļēāļāļāļąāļ™ -- 3.5.5. Workflow āļāļēāļĢāļ­āļ™āļļāļĄāļąāļ•āļī: āļ•āđ‰āļ­āļ‡āļĢāļ­āļ‡āļĢāļąāļšāļāļĢāļ°āļšāļ§āļ™āļāļēāļĢāļ­āļ™āļļāļĄāļąāļ•āļīāļ—āļĩāđˆāļ‹āļąāļšāļ‹āđ‰āļ­āļ™āđāļĨāļ°āđ€āļ›āđ‡āļ™āļĨāļģāļ”āļąāļš āđ€āļŠāđˆāļ™ - - āļŠāđˆāļ‡āļˆāļēāļ Originator -> Organization 1 -> Organization 2 -> Organization 3 āđāļĨāđ‰āļ§āļŠāđˆāļ‡āļœāļĨāļāļĨāļąāļšāļ•āļēāļĄāļĨāļģāļ”āļąāļšāđ€āļ”āļīāļĄ (āđ‚āļ”āļĒāļ–āđ‰āļē āļ­āļ‡āļāļĢāļ“āđŒāđƒāļ”āđƒāļ™ Workflow āđƒāļŦāđ‰āļŠāđˆāļ‡āļāļĨāļąāļš āļāđ‡āļŠāļēāļĄāļēāļĢāļ–āļŠāđˆāļ‡āļœāļĨāļāļĨāļąāļšāļ•āļēāļĄāļĨāļģāļ”āļąāļšāđ€āļ”āļīāļĄāđ‚āļ”āļĒāđ„āļĄāđˆāļ•āđ‰āļ­āļ‡āļĢāļ­āđƒāļŦāđ‰āļ–āļķāļ‡ āļ­āļ‡āļāļĢāļ“āļ·āđƒāļ™āļĨāļģāļ”āļąāļšāļ–āļąāļ”āđ„āļ›) -- 3.5.6. āļāļēāļĢāļˆāļąāļ”āļāļēāļĢ: āļĄāļĩāļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļ­āļĒāđˆāļēāļ‡āļ™āđ‰āļ­āļĒāļ”āļąāļ‡āļ™āļĩāđ‰ - - āļŠāļēāļĄāļēāļĢāļ–āļāļģāļŦāļ™āļ”āļ§āļąāļ™āđāļĨāđ‰āļ§āđ€āļŠāļĢāđ‡āļˆ (Deadline) āļŠāļģāļŦāļĢāļąāļšāļœāļđāđ‰āļĢāļąāļšāļœāļīāļ”āļŠāļ­āļšāļ‚āļ­āļ‡ āļ­āļ‡āļāļĢāļ“āđŒ āļ—āļĩāđˆāļ­āļĒāļđāđˆāđƒāļ™ Workflow āđ„āļ”āđ‰ - - āļĄāļĩāļĢāļ°āļšāļšāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™ āđƒāļŦāđ‰āļœāļđāđ‰āļĢāļąāļšāļœāļīāļ”āļŠāļ­āļšāļ‚āļ­āļ‡ āļ­āļ‡āļāļĢāļ“āđŒ āļ—āļĩāđˆāļ­āļĒāļđāđˆāđƒāļ™ Workflow āļ—āļĢāļēāļš āđ€āļĄāļ·āđˆāļ­āļĄāļĩ RFA āđƒāļŦāļĄāđˆ āļŦāļĢāļ·āļ­āļĄāļĩāļāļēāļĢāđ€āļ›āļĨāļĩāđˆāļĒāļ™āļŠāļ–āļēāļ™āļ° - -### **3.6.āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāđ€āļ­āļāļŠāļēāļĢāļ™āļģāļŠāđˆāļ‡ (Transmittals)** - -- 3.6.1. āļ§āļąāļ•āļ–āļļāļ›āļĢāļ°āļŠāļ‡āļ„āđŒ: āđ€āļ­āļāļŠāļēāļĢāļ™āļģāļŠāđˆāļ‡ āđƒāļŠāđ‰āļŠāļģāļŦāļĢāļąāļš āļ™āļģāļŠāđˆāļ‡ Request for Approval (RFAS) āļŦāļĨāļēāļĒāļ‰āļšāļąāļš āđ„āļ›āļĒāļąāļ‡āļ­āļ‡āļ„āđŒāļāļĢāļ­āļ·āđˆāļ™ -- 3.6.2. āļ›āļĢāļ°āđ€āļ āļ—āđ€āļ­āļāļŠāļēāļĢ: āđ„āļŸāļĨāđŒ PDF -- 3.6.3. āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡āđ€āļ­āļāļŠāļēāļĢ: āļœāļđāđ‰āđƒāļŠāđ‰āļ—āļĩāđˆāļĄāļĩāļŠāļīāļ—āļ˜āļīāđŒ āļŠāļēāļĄāļēāļĢāļ–āļŠāļĢāđ‰āļēāļ‡āđāļĨāļ°āđāļāđ‰āđ„āļ‚āđ„āļ”āđ‰ -- 3.6.4. āļāļēāļĢāļ­āđ‰āļēāļ‡āļ­āļīāļ‡āđāļĨāļ°āļˆāļąāļ”āļāļĨāļļāđˆāļĄ: āđ€āļ­āļāļŠāļēāļĢāļ™āļģāļŠāđˆāļ‡ āđ€āļ›āđ‡āļ™āļŠāđˆāļ§āļ™āļŦāļ™āļķāđˆāļ‡āđƒāļ™ Correspondence - -### **3.7. āđƒāļšāđ€āļ§āļĩāļĒāļ™āđ€āļ­āļāļŠāļēāļĢ (Circulation Sheet)** - -- 3.7.1. āļ§āļąāļ•āļ–āļļāļ›āļĢāļ°āļŠāļ‡āļ„āđŒ: āļāļēāļĢāļŠāļ·āđˆāļ­āļŠāļēāļĢ āđ€āļ­āļāļŠāļēāļĢ (Correspondence) āļ—āļļāļāļ‰āļšāļąāļš āļˆāļ°āļĄāļĩāđƒāļšāđ€āļ§āļĩāļĒāļ™āđ€āļ­āļāļŠāļēāļĢāđ€āļžāļ·āđˆāļ­āļ„āļ§āļšāļ„āļļāļĄāđāļĨāļ°āļĄāļ­āļšāļŦāļĄāļēāļĒāļ‡āļēāļ™āļ āļēāļĒāđƒāļ™āļ­āļ‡āļ„āđŒāļāļĢ (āļŠāļēāļĄāļēāļĢāļ–āļ”āļđāđāļĨāļ°āđāļāđ‰āđ„āļ‚āđ„āļ”āđ‰āđ€āļ‰āļžāļēāļ°āļ„āļ™āđƒāļ™āļ­āļ‡āļ„āđŒāļāļĢ) -- 3.7.2. āļ›āļĢāļ°āđ€āļ āļ—āđ€āļ­āļāļŠāļēāļĢ: āđ„āļŸāļĨāđŒ PDF -- 3.7.3. āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡āđ€āļ­āļāļŠāļēāļĢ: āļœāļđāđ‰āđƒāļŠāđ‰āļ—āļĩāđˆāļĄāļĩāļŠāļīāļ—āļ˜āļīāđŒāđƒāļ™āļ­āļ‡āļ„āđŒāļāļĢāļ™āļąāđ‰āļ™ āļŠāļēāļĄāļēāļĢāļ–āļŠāļĢāđ‰āļēāļ‡āđāļĨāļ°āđāļāđ‰āđ„āļ‚āđ„āļ”āđ‰ -- 3.7.4. āļāļēāļĢāļ­āđ‰āļēāļ‡āļ­āļīāļ‡āđāļĨāļ°āļˆāļąāļ”āļāļĨāļļāđˆāļĄ: āļāļēāļĢāļĢāļ°āļšāļļāļœāļđāđ‰āļĢāļąāļšāļœāļīāļ”āļŠāļ­āļš: - - āļœāļđāđ‰āļĢāļąāļšāļœāļīāļ”āļŠāļ­āļšāļŦāļĨāļąāļ (Main): āļĄāļĩāđ„āļ”āđ‰āļŦāļĨāļēāļĒāļ„āļ™ - - āļœāļđāđ‰āļĢāđˆāļ§āļĄāļ›āļāļīāļšāļąāļ•āļīāļ‡āļēāļ™ (Action): āļĄāļĩāđ„āļ”āđ‰āļŦāļĨāļēāļĒāļ„āļ™ - - āļœāļđāđ‰āļ—āļĩāđˆāļ•āđ‰āļ­āļ‡āļĢāļąāļšāļ—āļĢāļēāļš (Information): āļĄāļĩāđ„āļ”āđ‰āļŦāļĨāļēāļĒāļ„āļ™ -- 3.7.5. āļāļēāļĢāļ•āļīāļ”āļ•āļēāļĄāļ‡āļēāļ™: - - āļŠāļēāļĄāļēāļĢāļ–āļāļģāļŦāļ™āļ”āļ§āļąāļ™āđāļĨāđ‰āļ§āđ€āļŠāļĢāđ‡āļˆ (Deadline) āļŠāļģāļŦāļĢāļąāļšāļœāļđāđ‰āļĢāļąāļšāļœāļīāļ”āļŠāļ­āļšāļ›āļĢāļ°āđ€āļ āļ— Main āđāļĨāļ° Action āđ„āļ”āđ‰ - - āļĄāļĩāļĢāļ°āļšāļšāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āđ€āļĄāļ·āđˆāļ­āļĄāļĩ Circulation āđƒāļŦāļĄāđˆ āđāļĨāļ°āđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļĨāđˆāļ§āļ‡āļŦāļ™āđ‰āļēāļāđˆāļ­āļ™āļ–āļķāļ‡āļ§āļąāļ™āđāļĨāđ‰āļ§āđ€āļŠāļĢāđ‡āļˆ - - āļŠāļēāļĄāļēāļĢāļ–āļ›āļīāļ” Circulation āđ„āļ”āđ‰āđ€āļĄāļ·āđˆāļ­āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāļ•āļ­āļšāļāļĨāļąāļšāđ„āļ›āļĒāļąāļ‡āļ­āļ‡āļ„āđŒāļāļĢāļœāļđāđ‰āļŠāđˆāļ‡ (Originator) āđāļĨāđ‰āļ§ āļŦāļĢāļ·āļ­ āļĢāļąāļšāļ—āļĢāļēāļšāđāļĨāđ‰āļ§ (For Information) - -### **3.8. āļ›āļĢāļ°āļ§āļąāļ•āļīāļāļēāļĢāđāļāđ‰āđ„āļ‚ (Revisions):** āļĢāļ°āļšāļšāļˆāļ°āđ€āļāđ‡āļšāļ›āļĢāļ°āļ§āļąāļ•āļīāļāļēāļĢāļŠāļĢāđ‰āļēāļ‡āđāļĨāļ°āđāļāđ‰āđ„āļ‚ āđ€āļ­āļāļŠāļēāļĢāļ—āļąāđ‰āļ‡āļŦāļĄāļ” - -### **3.9. āļāļēāļĢāļˆāļąāļ”āđ€āļāđ‡āļšāđ„āļŸāļĨāđŒ (File Handling - āļ›āļĢāļąāļšāļ›āļĢāļļāļ‡āđƒāļŦāļāđˆ)** - -- **3.9.1 Two-Phase Storage Strategy:** - 1. **Phase 1 (Upload):** āđ„āļŸāļĨāđŒāļ–āļđāļāļ­āļąāļ›āđ‚āļŦāļĨāļ”āđ€āļ‚āđ‰āļēāđ‚āļŸāļĨāđ€āļ”āļ­āļĢāđŒ `temp/` āđāļĨāļ°āđ„āļ”āđ‰āļĢāļąāļš `temp_id` - 2. **Phase 2 (Commit):** āđ€āļĄāļ·āđˆāļ­ User āļāļ” Submit āļŸāļ­āļĢāđŒāļĄāļŠāļģāđ€āļĢāđ‡āļˆ āļĢāļ°āļšāļšāļˆāļ°āļĒāđ‰āļēāļĒāđ„āļŸāļĨāđŒāļˆāļēāļ `temp/` āđ„āļ›āļĒāļąāļ‡ `permanent/{YYYY}/{MM}/` āđāļĨāļ°āļšāļąāļ™āļ—āļķāļāļĨāļ‡ Database āļ āļēāļĒāđƒāļ™ Transaction āđ€āļ”āļĩāļĒāļ§āļāļąāļ™ - 3. **Cleanup:** āļĄāļĩ Cron Job āļĨāļšāđ„āļŸāļĨāđŒāđƒāļ™ `temp/` āļ—āļĩāđˆāļ„āđ‰āļēāļ‡āđ€āļāļīāļ™ 24 āļŠāļĄ. (Orphan Files) - -- **3.9.2 Security:** - - Virus Scan (ClamAV) āļāđˆāļ­āļ™āļĒāđ‰āļēāļĒāđ€āļ‚āđ‰āļē Permanent - - Whitelist File Types: PDF, DWG, DOCX, XLSX, ZIP - - Max Size: 50MB - - Access Control: āļ•āļĢāļ§āļˆāļŠāļ­āļšāļŠāļīāļ—āļ˜āļīāđŒāļœāđˆāļēāļ™ Junction Table āļāđˆāļ­āļ™āđƒāļŦāđ‰ Download Link - -- **3.9.3 āļ„āļ§āļēāļĄāļ›āļĨāļ­āļ”āļ āļąāļĒāļ‚āļ­āļ‡āļāļēāļĢāļˆāļąāļ”āđ€āļāđ‡āļšāđ„āļŸāļĨāđŒ:** - - āļ•āđ‰āļ­āļ‡āļĄāļĩāļāļēāļĢ scan virus āļŠāļģāļŦāļĢāļąāļšāđ„āļŸāļĨāđŒāļ—āļĩāđˆāļ­āļąāļ›āđ‚āļŦāļĨāļ”āļ—āļąāđ‰āļ‡āļŦāļĄāļ” āđ‚āļ”āļĒāđƒāļŠāđ‰ ClamAV āļŦāļĢāļ·āļ­āļšāļĢāļīāļāļēāļĢ third-party - - āļˆāļģāļāļąāļ”āļ›āļĢāļ°āđ€āļ āļ—āđ„āļŸāļĨāđŒāļ—āļĩāđˆāļ­āļ™āļļāļāļēāļ•: PDF, DWG, DOCX, XLSX, ZIP (āļ•āđ‰āļ­āļ‡āļĢāļ°āļšāļļāļĢāļēāļĒāļāļēāļĢāļ—āļĩāđˆāļŠāļąāļ”āđ€āļˆāļ™) - - āļ‚āļ™āļēāļ”āđ„āļŸāļĨāđŒāļŠāļđāļ‡āļŠāļļāļ”: 50MB āļ•āđˆāļ­āđ„āļŸāļĨāđŒ - - āđ„āļŸāļĨāđŒāļ•āđ‰āļ­āļ‡āļ–āļđāļāđ€āļāđ‡āļšāļ™āļ­āļ web root āđāļĨāļ°āđ€āļ‚āđ‰āļēāļ–āļķāļ‡āđ„āļ”āđ‰āļœāđˆāļēāļ™ authenticated endpoint āđ€āļ—āđˆāļēāļ™āļąāđ‰āļ™ - - āļ•āđ‰āļ­āļ‡āļĄāļĩ file integrity check (checksum) āđ€āļžāļ·āđˆāļ­āļ›āđ‰āļ­āļ‡āļāļąāļ™āļāļēāļĢāđāļāđ‰āđ„āļ‚āđ„āļŸāļĨāđŒ - - Download links āļ•āđ‰āļ­āļ‡āļĄāļĩ expiration time (default: 24 āļŠāļąāđˆāļ§āđ‚āļĄāļ‡) - - āļ•āđ‰āļ­āļ‡āļšāļąāļ™āļ—āļķāļ audit log āļ—āļļāļāļ„āļĢāļąāđ‰āļ‡āļ—āļĩāđˆāļĄāļĩāļāļēāļĢāļ”āļēāļ§āļ™āđŒāđ‚āļŦāļĨāļ”āđ„āļŸāļĨāđŒāļŠāļģāļ„āļąāļ - -### **3.10. āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāđ€āļĨāļ‚āļ—āļĩāđˆāđ€āļ­āļāļŠāļēāļĢ (Document Numbering - āļ›āļĢāļąāļšāļ›āļĢāļļāļ‡)** - -- 3.10.1. āļĢāļ°āļšāļšāļ•āđ‰āļ­āļ‡āļŠāļēāļĄāļēāļĢāļ–āļŠāļĢāđ‰āļēāļ‡āđ€āļĨāļ‚āļ—āļĩāđˆāđ€āļ­āļāļŠāļēāļĢ (āđ€āļŠāđˆāļ™ correspondence_number) āđ„āļ”āđ‰āđ‚āļ”āļĒāļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī -- 3.10.2. āļāļēāļĢāļ™āļąāļšāđ€āļĨāļ‚ Running Number (SEQ) āļˆāļ°āļ•āđ‰āļ­āļ‡āļ™āļąāļšāđāļĒāļāļ•āļēāļĄ Key āļ”āļąāļ‡āļ™āļĩāđ‰: **āđ‚āļ„āļĢāļ‡āļāļēāļĢ (Project)**, **āļ­āļ‡āļ„āđŒāļāļĢāļœāļđāđ‰āļŠāđˆāļ‡ (Originator Organization)**, **āļ›āļĢāļ°āđ€āļ āļ—āđ€āļ­āļāļŠāļēāļĢ (Document Type)** āđāļĨāļ° **āļ›āļĩāļ›āļąāļˆāļˆāļļāļšāļąāļ™ (Year)** -- 3.10.3. āļœāļđāđ‰āļ”āļđāđāļĨāļĢāļ°āļšāļš (Admin) āļ•āđ‰āļ­āļ‡āļŠāļēāļĄāļēāļĢāļ–āļāļģāļŦāļ™āļ” "āļĢāļđāļ›āđāļšāļš" (Format Template) āļ‚āļ­āļ‡āđ€āļĨāļ‚āļ—āļĩāđˆāđ€āļ­āļāļŠāļēāļĢāđ„āļ”āđ‰ (āđ€āļŠāđˆāļ™ {ORG_CODE}-{TYPE_CODE}-{YEAR_SHORT}-{SEQ:4}) āđ‚āļ”āļĒāļāļģāļŦāļ™āļ”āđāļĒāļāļ•āļēāļĄāđ‚āļ„āļĢāļ‡āļāļēāļĢāđāļĨāļ°āļ›āļĢāļ°āđ€āļ āļ—āđ€āļ­āļāļŠāļēāļĢ -- 3.10.4. **āļāļĨāđ„āļ:** āđƒāļŠāđ‰ **Redis Distributed Lock** āđ€āļ›āđ‡āļ™āļ”āđˆāļēāļ™āđāļĢāļ -- 3.10.5. **Safety Net:** āđ€āļžāļīāđˆāļĄ **Optimistic Locking** (āļ•āļĢāļ§āļˆāļŠāļ­āļš Version/Last Number āđƒāļ™ DB āļ‚āļ“āļ° Update) āđ€āļžāļ·āđˆāļ­āļ›āđ‰āļ­āļ‡āļāļąāļ™āļāļĢāļ“āļĩ Redis āļĨāđˆāļĄ āļŦāļĢāļ·āļ­ Race Condition āļŦāļĨāļļāļ”āļĢāļ­āļ” -- 3.10.6. āļ•āđ‰āļ­āļ‡āļĄāļĩ retry mechanism āđāļĨāļ° fallback strategy āđ€āļĄāļ·āđˆāļ­āļāļēāļĢ generate āđ€āļĨāļ‚āļ—āļĩāđˆāđ€āļ­āļāļŠāļēāļĢāļĨāđ‰āļĄāđ€āļŦāļĨāļ§ - -### **3.11 āļāļēāļĢāļˆāļąāļ”āļāļēāļĢ JSON Details (JSON & Performance - āļ›āļĢāļąāļšāļ›āļĢāļļāļ‡)** - -- **3.11.1 āļ§āļąāļ•āļ–āļļāļ›āļĢāļ°āļŠāļ‡āļ„āđŒ** - - āļˆāļąāļ”āđ€āļāđ‡āļšāļ‚āđ‰āļ­āļĄāļđāļĨāđāļšāļšāđ„āļ”āļ™āļēāļĄāļīāļāļ—āļĩāđˆāđ€āļ‰āļžāļēāļ°āđ€āļˆāļēāļ°āļˆāļ‡āļāļąāļšāđāļ•āđˆāļĨāļ°āļ›āļĢāļ°āđ€āļ āļ—āļ‚āļ­āļ‡āđ€āļ­āļāļŠāļēāļĢ - - āļĢāļ­āļ‡āļĢāļąāļšāļāļēāļĢāļ‚āļĒāļēāļĒāļ•āļąāļ§āļ‚āļ­āļ‡āļĢāļ°āļšāļšāđ‚āļ”āļĒāđ„āļĄāđˆāļ•āđ‰āļ­āļ‡āđ€āļ›āļĨāļĩāđˆāļĒāļ™āđāļ›āļĨāļ‡ database schema - - āļˆāļąāļ”āļāļēāļĢ metadata āđāļĨāļ°āļ‚āđ‰āļ­āļĄāļđāļĨāļ›āļĢāļ°āļāļ­āļšāļŠāļģāļŦāļĢāļąāļš correspondence, routing, āđāļĨāļ° workflows - -- **3.11.2 āđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡ JSON Schema** - āļĢāļ°āļšāļšāļ•āđ‰āļ­āļ‡āļĄāļĩ predefined JSON schemas āļŠāļģāļŦāļĢāļąāļšāļ›āļĢāļ°āđ€āļ āļ—āđ€āļ­āļāļŠāļēāļĢāļ•āđˆāļēāļ‡āđ†: - - **3.11.2.1 Correspondence Types** - - **GENERIC**: āļ‚āđ‰āļ­āļĄāļđāļĨāļžāļ·āđ‰āļ™āļāļēāļ™āļŠāļģāļŦāļĢāļąāļšāđ€āļ­āļāļŠāļēāļĢāļ—āļąāđˆāļ§āđ„āļ› - - **RFI**: āļĢāļēāļĒāļĨāļ°āđ€āļ­āļĩāļĒāļ”āļ„āļģāļ–āļēāļĄāđāļĨāļ°āļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļēāļ‡āđ€āļ—āļ„āļ™āļīāļ„ - - **RFA**: āļ‚āđ‰āļ­āļĄāļđāļĨāļāļēāļĢāļ‚āļ­āļ­āļ™āļļāļĄāļąāļ•āļīāđāļšāļšāđāļĨāļ°āļ§āļąāļŠāļ”āļļ - - **TRANSMITTAL**: āļĢāļēāļĒāļāļēāļĢāđ€āļ­āļāļŠāļēāļĢāļ—āļĩāđˆāļŠāđˆāļ‡āļ•āđˆāļ­ - - **LETTER**: āļ‚āđ‰āļ­āļĄāļđāļĨāļˆāļ”āļŦāļĄāļēāļĒāļ—āļēāļ‡āļāļēāļĢ - - **EMAIL**: āļ‚āđ‰āļ­āļĄāļđāļĨāļ­āļĩāđ€āļĄāļĨ - - **3.11.2.2 Routing Types** - - **ROUTING_TEMPLATE**: āļāļŽāđāļĨāļ°āđ€āļ‡āļ·āđˆāļ­āļ™āđ„āļ‚āļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­ - - **ROUTING_INSTANCE**: āļŠāļ–āļēāļ™āļ°āđāļĨāļ°āļ›āļĢāļ°āļ§āļąāļ•āļīāļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­ - - **ROUTING_ACTION**: āļāļēāļĢāļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāđƒāļ™āđāļ•āđˆāļĨāļ°āļ‚āļąāđ‰āļ™āļ•āļ­āļ™ - - **3.11.2.3 Audit Types** - - **AUDIT_LOG**: āļ‚āđ‰āļ­āļĄāļđāļĨāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļš - - **SECURITY_SCAN**: āļœāļĨāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļ„āļ§āļēāļĄāļ›āļĨāļ­āļ”āļ āļąāļĒ - -- **3.11.3 Virtual Columns (āđƒāļŦāļĄāđˆ):** āļŠāļģāļŦāļĢāļąāļš Field āđƒāļ™ JSON āļ—āļĩāđˆāļ•āđ‰āļ­āļ‡āđƒāļŠāđ‰āđƒāļ™āļāļēāļĢāļ„āđ‰āļ™āļŦāļē (Search) āļŦāļĢāļ·āļ­āļˆāļąāļ”āđ€āļĢāļĩāļĒāļ‡ (Sort) āļšāđˆāļ­āļĒāđ† **āļ•āđ‰āļ­āļ‡āļŠāļĢāđ‰āļēāļ‡ Generated Column (Virtual Column)** āđƒāļ™ Database āđāļĨāļ°āļ—āļģ Index āđ„āļ§āđ‰ āđ€āļžāļ·āđˆāļ­āļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāļŠāļđāļ‡āļŠāļļāļ” - -- **3.11.4 Validation Rules** - - āļ•āđ‰āļ­āļ‡āļĄāļĩ JSON schema validation āļŠāļģāļŦāļĢāļąāļšāđāļ•āđˆāļĨāļ°āļ›āļĢāļ°āđ€āļ āļ— - - āļ•āđ‰āļ­āļ‡āļĢāļ­āļ‡āļĢāļąāļš versioning āļ‚āļ­āļ‡ schema - - āļ•āđ‰āļ­āļ‡āļĄāļĩ default values āļŠāļģāļŦāļĢāļąāļš field āļ—āļĩāđˆāđ„āļĄāđˆāļšāļąāļ‡āļ„āļąāļš - - āļ•āđ‰āļ­āļ‡āļ•āļĢāļ§āļˆāļŠāļ­āļš data types āđāļĨāļ° format āđƒāļŦāđ‰āļ–āļđāļāļ•āđ‰āļ­āļ‡ - -- **3.11.5 Performance Requirements** - - JSON field āļ•āđ‰āļ­āļ‡āļĄāļĩāļ‚āļ™āļēāļ”āđ„āļĄāđˆāđ€āļāļīāļ™ 50KB - - āļ•āđ‰āļ­āļ‡āļĢāļ­āļ‡āļĢāļąāļš indexing āļŠāļģāļŦāļĢāļąāļš field āļ—āļĩāđˆāđƒāļŠāđ‰āļ„āđ‰āļ™āļŦāļēāļšāđˆāļ­āļĒ - - āļ•āđ‰āļ­āļ‡āļĄāļĩ compression āļŠāļģāļŦāļĢāļąāļš JSON āļ‚āļ™āļēāļ”āđƒāļŦāļāđˆ - -- **3.11.6 Security Requirements** - - āļ•āđ‰āļ­āļ‡ sanitize JSON input āđ€āļžāļ·āđˆāļ­āļ›āđ‰āļ­āļ‡āļāļąāļ™ injection attacks - - āļ•āđ‰āļ­āļ‡ validate JSON structure āļāđˆāļ­āļ™āļšāļąāļ™āļ—āļķāļ - - āļ•āđ‰āļ­āļ‡ encrypt sensitive data āđƒāļ™ JSON fields - -## **🔐 4. āļ‚āđ‰āļ­āļāļģāļŦāļ™āļ”āļ”āđ‰āļēāļ™āļŠāļīāļ—āļ˜āļīāđŒāđāļĨāļ°āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡ (Access Control Requirements)** - -### **4.1. āļ āļēāļžāļĢāļ§āļĄ:** āļœāļđāđ‰āđƒāļŠāđ‰āđāļĨāļ°āļ­āļ‡āļ„āđŒāļāļĢāļŠāļēāļĄāļēāļĢāļ–āļ”āļđāđāļĨāļ°āđāļāđ‰āđ„āļ‚āđ€āļ­āļāļŠāļēāļĢāđ„āļ”āđ‰āļ•āļēāļĄāļŠāļīāļ—āļ˜āļīāđŒāļ—āļĩāđˆāđ„āļ”āđ‰āļĢāļąāļš āđ‚āļ”āļĒāļĢāļ°āļšāļšāļŠāļīāļ—āļ˜āļīāđŒāļˆāļ°āđ€āļ›āđ‡āļ™āđāļšāļš Role-Based Access Control (RBAC) - -### **4.2. āļĨāļģāļ”āļąāļšāļŠāļąāđ‰āļ™āļ‚āļ­āļ‡āļŠāļīāļ—āļ˜āļīāđŒ (Permission Hierarchy)** - -- Global: āļŠāļīāļ—āļ˜āļīāđŒāļŠāļđāļ‡āļŠāļļāļ”āļ‚āļ­āļ‡āļĢāļ°āļšāļš -- Organization: āļŠāļīāļ—āļ˜āļīāđŒāļ āļēāļĒāđƒāļ™āļ­āļ‡āļ„āđŒāļāļĢ āđ€āļ›āđ‡āļ™āļŠāļīāļ—āļ˜āļīāđŒāļžāļ·āđ‰āļ™āļāļēāļ™āļ‚āļ­āļ‡āļœāļđāđ‰āđƒāļŠāđ‰ -- Project: āļŠāļīāļ—āļ˜āļīāđŒāđ€āļ‰āļžāļēāļ°āđƒāļ™āđ‚āļ„āļĢāļ‡āļāļēāļĢ āļˆāļ°āļ–āļđāļāļžāļīāļˆāļēāļĢāļ“āļēāđ€āļĄāļ·āđˆāļ­āļœāļđāđ‰āđƒāļŠāđ‰āļ­āļĒāļđāđˆāđƒāļ™āđ‚āļ„āļĢāļ‡āļāļēāļĢāļ™āļąāđ‰āļ™ -- Contract: āļŠāļīāļ—āļ˜āļīāđŒāđ€āļ‰āļžāļēāļ°āđƒāļ™āļŠāļąāļāļāļē āļˆāļ°āļ–āļđāļāļžāļīāļˆāļēāļĢāļ“āļēāđ€āļĄāļ·āđˆāļ­āļœāļđāđ‰āđƒāļŠāđ‰āļ­āļĒāļđāđˆāđƒāļ™āļŠāļąāļāļāļēāļ™āļąāđ‰āļ™ (āļŠāļąāļāļāļēāđ€āļ›āđ‡āļ™āļŠāđˆāļ§āļ™āļŦāļ™āļķāđˆāļ‡āļ‚āļ­āļ‡āđ‚āļ„āļĢāļ‡āļāļēāļĢ) - -āļāļŽāļāļēāļĢāļšāļąāļ‡āļ„āļąāļšāđƒāļŠāđ‰: āđ€āļĄāļ·āđˆāļ­āļ•āļĢāļ§āļˆāļŠāļ­āļšāļŠāļīāļ—āļ˜āļīāđŒ āļĢāļ°āļšāļšāļˆāļ°āļžāļīāļˆāļēāļĢāļ“āļēāļŠāļīāļ—āļ˜āļīāđŒāļˆāļēāļāļ—āļļāļāļĢāļ°āļ”āļąāļšāļ—āļĩāđˆāļœāļđāđ‰āđƒāļŠāđ‰āļĄāļĩ āđāļĨāļ°āđƒāļŠāđ‰ āļŠāļīāļ—āļ˜āļīāđŒāļ—āļĩāđˆāļĄāļēāļāļ—āļĩāđˆāļŠāļļāļ” (Most Permissive) āđ€āļ›āđ‡āļ™āļ•āļąāļ§āļ•āļąāļ”āļŠāļīāļ™ - -āļ•āļąāļ§āļ­āļĒāđˆāļēāļ‡: āļœāļđāđ‰āđƒāļŠāđ‰ A āđ€āļ›āđ‡āļ™ Viewer āđƒāļ™āļ­āļ‡āļ„āđŒāļāļĢ āđāļ•āđˆāļ–āļđāļāļĄāļ­āļšāļŦāļĄāļēāļĒāđ€āļ›āđ‡āļ™ Editor āđƒāļ™āđ‚āļ„āļĢāļ‡āļāļēāļĢ X āđ€āļĄāļ·āđˆāļ­āļ­āļĒāļđāđˆāđƒāļ™āđ‚āļ„āļĢāļ‡āļāļēāļĢ X āļœāļđāđ‰āđƒāļŠāđ‰ A āļˆāļ°āļĄāļĩāļŠāļīāļ—āļ˜āļīāđŒāđāļāđ‰āđ„āļ‚āđ„āļ”āđ‰ - -### **4.3. āļāļēāļĢāļāļģāļŦāļ™āļ”āļšāļ—āļšāļēāļ— (Roles) āđāļĨāļ°āļ‚āļ­āļšāđ€āļ‚āļ• (Scope)** - -| āļšāļ—āļšāļēāļ— (Role) | āļ‚āļ­āļšāđ€āļ‚āļ• (Scope) | āļ„āļģāļ­āļ˜āļīāļšāļēāļĒ | āļŠāļīāļ—āļ˜āļīāđŒāļŦāļĨāļąāļ (Key Permissions) | -| :------------------- | :------------- | :---------------------- | :------------------------------------------------------------------------------------- | -| **Superadmin** | Global | āļœāļđāđ‰āļ”āļđāđāļĨāļĢāļ°āļšāļšāļŠāļđāļ‡āļŠāļļāļ” | āļ—āļģāļ—āļļāļāļ­āļĒāđˆāļēāļ‡āđƒāļ™āļĢāļ°āļšāļš, āļˆāļąāļ”āļāļēāļĢāļ­āļ‡āļ„āđŒāļāļĢ, āļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļĄāļđāļĨāļŦāļĨāļąāļāļĢāļ°āļ”āļąāļš Global | -| **Org Admin** | Organization | āļœāļđāđ‰āļ”āļđāđāļĨāļ­āļ‡āļ„āđŒāļāļĢ | āļˆāļąāļ”āļāļēāļĢāļœāļđāđ‰āđƒāļŠāđ‰āđƒāļ™āļ­āļ‡āļ„āđŒāļāļĢ, āļˆāļąāļ”āļāļēāļĢāļšāļ—āļšāļēāļ—/āļŠāļīāļ—āļ˜āļīāđŒāļ āļēāļĒāđƒāļ™āļ­āļ‡āļ„āđŒāļāļĢ, āļ”āļđāļĢāļēāļĒāļ‡āļēāļ™āļ‚āļ­āļ‡āļ­āļ‡āļ„āđŒāļāļĢ | -| **Document Control** | Organization | āļ„āļ§āļšāļ„āļļāļĄāđ€āļ­āļāļŠāļēāļĢāļ‚āļ­āļ‡āļ­āļ‡āļ„āđŒāļāļĢ | āđ€āļžāļīāđˆāļĄ/āđāļāđ‰āđ„āļ‚/āļĨāļšāđ€āļ­āļāļŠāļēāļĢ, āļāļģāļŦāļ™āļ”āļŠāļīāļ—āļ˜āļīāđŒāđ€āļ­āļāļŠāļēāļĢāļ āļēāļĒāđƒāļ™āļ­āļ‡āļ„āđŒāļāļĢ | -| **Editor** | Organization | āļœāļđāđ‰āđāļāđ‰āđ„āļ‚āđ€āļ­āļāļŠāļēāļĢāļ‚āļ­āļ‡āļ­āļ‡āļ„āđŒāļāļĢ | āđ€āļžāļīāđˆāļĄ/āđāļāđ‰āđ„āļ‚āđ€āļ­āļāļŠāļēāļĢāļ—āļĩāđˆāđ„āļ”āđ‰āļĢāļąāļšāļĄāļ­āļšāļŦāļĄāļēāļĒ | -| **Viewer** | Organization | āļœāļđāđ‰āļ”āļđāđ€āļ­āļāļŠāļēāļĢāļ‚āļ­āļ‡āļ­āļ‡āļ„āđŒāļāļĢ | āļ”āļđāđ€āļ­āļāļŠāļēāļĢāļ—āļĩāđˆāļĄāļĩāļŠāļīāļ—āļ˜āļīāđŒāđ€āļ‚āđ‰āļēāļ–āļķāļ‡ | -| **Project Manager** | Project | āļœāļđāđ‰āļˆāļąāļ”āļāļēāļĢāđ‚āļ„āļĢāļ‡āļāļēāļĢ | āļˆāļąāļ”āļāļēāļĢāļŠāļĄāļēāļŠāļīāļāđƒāļ™āđ‚āļ„āļĢāļ‡āļāļēāļĢ (āđ€āļžāļīāđˆāļĄ/āļĨāļš/āļĄāļ­āļšāļšāļ—āļšāļēāļ—), āļŠāļĢāđ‰āļēāļ‡/āļˆāļąāļ”āļāļēāļĢāļŠāļąāļāļāļēāđƒāļ™āđ‚āļ„āļĢāļ‡āļāļēāļĢ, āļ”āļđāļĢāļēāļĒāļ‡āļēāļ™āđ‚āļ„āļĢāļ‡āļāļēāļĢ | -| **Contract Admin** | Contract | āļœāļđāđ‰āļ”āļđāđāļĨāļŠāļąāļāļāļē | āļˆāļąāļ”āļāļēāļĢāļŠāļĄāļēāļŠāļīāļāđƒāļ™āļŠāļąāļāļāļē, āļŠāļĢāđ‰āļēāļ‡/āļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļĄāļđāļĨāļŦāļĨāļąāļāđ€āļ‰āļžāļēāļ°āļŠāļąāļāļāļē (āļ–āđ‰āļēāļĄāļĩ), āļ­āļ™āļļāļĄāļąāļ•āļīāđ€āļ­āļāļŠāļēāļĢāđƒāļ™āļŠāļąāļāļāļē | - -### **4.4. Token Management (āļ›āļĢāļąāļšāļ›āļĢāļļāļ‡)** - -- **Payload Optimization:** āđƒāļ™ JWT Access Token āđƒāļŦāđ‰āđ€āļāđ‡āļšāđ€āļ‰āļžāļēāļ° `userId` āđāļĨāļ° `scope` āļ›āļąāļˆāļˆāļļāļšāļąāļ™āđ€āļ—āđˆāļēāļ™āļąāđ‰āļ™ -- **Permission Caching:** āļŠāļīāļ—āļ˜āļīāđŒāļĨāļ°āđ€āļ­āļĩāļĒāļ” (Permissions List) āđƒāļŦāđ‰āđ€āļāđ‡āļšāđƒāļ™ **Redis** āđāļĨāļ°āļ”āļķāļ‡āļĄāļēāļ•āļĢāļ§āļˆāļŠāļ­āļšāđ€āļĄāļ·āđˆāļ­ Request āđ€āļ‚āđ‰āļēāļĄāļē āđ€āļžāļ·āđˆāļ­āļĨāļ”āļ‚āļ™āļēāļ” Token āđāļĨāļ°āđ€āļžāļīāđˆāļĄāļ„āļ§āļēāļĄāđ€āļĢāđ‡āļ§ - -### **4.5. āļāļĢāļ°āļšāļ§āļ™āļāļēāļĢāđ€āļĢāļīāđˆāļĄāļ•āđ‰āļ™āđƒāļŠāđ‰āļ‡āļēāļ™ (Onboarding Workflow) āļ—āļĩāđˆāļŠāļĄāļšāļđāļĢāļ“āđŒ** - -- **4.5.1. āļŠāļĢāđ‰āļēāļ‡āļ­āļ‡āļ„āđŒāļāļĢ (Organization)** - - **Superadmin** āļŠāļĢāđ‰āļēāļ‡āļ­āļ‡āļ„āđŒāļāļĢāđƒāļŦāļĄāđˆ (āđ€āļŠāđˆāļ™ āļšāļĢāļīāļĐāļąāļ— A) - - **Superadmin** āđāļ•āđˆāļ‡āļ•āļąāđ‰āļ‡āļœāļđāđ‰āđƒāļŠāđ‰āļ­āļĒāđˆāļēāļ‡āļ™āđ‰āļ­āļĒ 1 āļ„āļ™āđƒāļŦāđ‰āđ€āļ›āđ‡āļ™ **Org Admin** āļŦāļĢāļ·āļ­ **Document Control** āļ‚āļ­āļ‡āļšāļĢāļīāļĐāļąāļ— A -- **4.5.2. āđ€āļžāļīāđˆāļĄāļœāļđāđ‰āđƒāļŠāđ‰āđƒāļ™āļ­āļ‡āļ„āđŒāļāļĢ** - - **Org Admin** āļ‚āļ­āļ‡āļšāļĢāļīāļĐāļąāļ— A āđ€āļžāļīāđˆāļĄāļœāļđāđ‰āđƒāļŠāđ‰āļ­āļ·āđˆāļ™āđ† (Editor, Viewer) āđ€āļ‚āđ‰āļēāļĄāļēāđƒāļ™āļ­āļ‡āļ„āđŒāļāļĢāļ‚āļ­āļ‡āļ•āļ™ -- **4.5.3. āļĄāļ­āļšāļŦāļĄāļēāļĒāļœāļđāđ‰āđƒāļŠāđ‰āđƒāļŦāđ‰āļāļąāļšāđ‚āļ„āļĢāļ‡āļāļēāļĢ (Project)** - - **Project Manager** āļ‚āļ­āļ‡āđ‚āļ„āļĢāļ‡āļāļēāļĢ X (āļ‹āļķāđˆāļ‡āļ­āļēāļˆāļĄāļēāļˆāļēāļāļšāļĢāļīāļĐāļąāļ— A āļŦāļĢāļ·āļ­āļšāļĢāļīāļĐāļąāļ—āļ­āļ·āđˆāļ™) āļ—āļģāļāļēāļĢ "āđ€āļŠāļīāļ" āļŦāļĢāļ·āļ­ "āļĄāļ­āļšāļŦāļĄāļēāļĒ" āļœāļđāđ‰āđƒāļŠāđ‰āļˆāļēāļāļ­āļ‡āļ„āđŒāļāļĢāļ•āđˆāļēāļ‡āđ† āļ—āļĩāđˆāđ€āļāļĩāđˆāļĒāļ§āļ‚āđ‰āļ­āļ‡āđ€āļ‚āđ‰āļēāļĄāļēāđƒāļ™āđ‚āļ„āļĢāļ‡āļāļēāļĢ X - - āđƒāļ™āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ™āļĩāđ‰ **Project Manager** āļˆāļ°āļāļģāļŦāļ™āļ” **āļšāļ—āļšāļēāļ—āļĢāļ°āļ”āļąāļšāđ‚āļ„āļĢāļ‡āļāļēāļĢ** (āđ€āļŠāđˆāļ™ Project Member, āļŦāļĢāļ·āļ­āļ­āļēāļˆāđ„āļĄāđˆāļĄāļĩāļšāļ—āļšāļēāļ—āļžāļīāđ€āļĻāļĐ āđƒāļŦāđ‰āđƒāļŠāđ‰āļŠāļīāļ—āļ˜āļīāđŒāļˆāļēāļāļĢāļ°āļ”āļąāļšāļ­āļ‡āļ„āđŒāļāļĢāđ„āļ›āļāđˆāļ­āļ™) -- **4.5.4. āđ€āļĄāļ­āļšāļŦāļĄāļēāļĒāļœāļđāđ‰āđƒāļŠāđ‰āđƒāļŦāđ‰āļāļąāļšāļŠāļąāļāļāļē (Contract)** - - **Contract Admin** āļ‚āļ­āļ‡āļŠāļąāļāļāļē Y (āļ‹āļķāđˆāļ‡āđ€āļ›āđ‡āļ™āļŠāđˆāļ§āļ™āļŦāļ™āļķāđˆāļ‡āļ‚āļ­āļ‡āđ‚āļ„āļĢāļ‡āļāļēāļĢ X) āļ—āļģāļāļēāļĢāđ€āļĨāļ·āļ­āļāļœāļđāđ‰āđƒāļŠāđ‰āļ—āļĩāđˆāļ­āļĒāļđāđˆāđƒāļ™āđ‚āļ„āļĢāļ‡āļāļēāļĢ X āđāļĨāđ‰āļ§ āļĄāļ­āļšāļŦāļĄāļēāļĒāđƒāļŦāđ‰āđ€āļ‚āđ‰āļēāļĄāļēāđƒāļ™āļŠāļąāļāļāļē Y - - āđƒāļ™āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ™āļĩāđ‰ **Contract Admin** āļˆāļ°āļāļģāļŦāļ™āļ” **āļšāļ—āļšāļēāļ—āļĢāļ°āļ”āļąāļšāļŠāļąāļāļāļē** (āđ€āļŠāđˆāļ™ Contract Member) āđāļĨāļ°āļŠāļīāļ—āļ˜āļīāđŒāđ€āļ‰āļžāļēāļ°āļ—āļĩāđˆāļˆāļģāđ€āļ›āđ‡āļ™ -- **4.5.5 Security Onboarding:** - - āļ•āđ‰āļ­āļ‡āļšāļąāļ‡āļ„āļąāļšāđ€āļ›āļĨāļĩāđˆāļĒāļ™ password āļ„āļĢāļąāđ‰āļ‡āđāļĢāļāļŠāļģāļŦāļĢāļąāļšāļœāļđāđ‰āđƒāļŠāđ‰āđƒāļŦāļĄāđˆ - - āļ•āđ‰āļ­āļ‡āļĄāļĩ security awareness training āļŠāļģāļŦāļĢāļąāļšāļœāļđāđ‰āđƒāļŠāđ‰āļ—āļĩāđˆāļĄāļĩāļŠāļīāļ—āļ˜āļīāđŒāļŠāļđāļ‡ - - āļ•āđ‰āļ­āļ‡āļĄāļĩ process āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļĢāļĩāđ€āļ‹āđ‡āļ• password āļ—āļĩāđˆāļ›āļĨāļ­āļ”āļ āļąāļĒ - - āļ•āđ‰āļ­āļ‡āļšāļąāļ™āļ—āļķāļ audit log āļ—āļļāļāļ„āļĢāļąāđ‰āļ‡āļ—āļĩāđˆāļĄāļĩāļāļēāļĢāđ€āļ›āļĨāļĩāđˆāļĒāļ™āđāļ›āļĨāļ‡ permissions - -### **4.6. āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļĄāļđāļĨāļŦāļĨāļąāļ (Master Data Management) āļ—āļĩāđˆāđāļšāđˆāļ‡āļ•āļēāļĄāļĢāļ°āļ”āļąāļš** - -| āļ‚āđ‰āļ­āļĄāļđāļĨāļŦāļĨāļąāļ | āļœāļđāđ‰āļĄāļĩāļŠāļīāļ—āļ˜āļīāđŒāļˆāļąāļ”āļāļēāļĢ | āļĢāļ°āļ”āļąāļš | -| :---------------------------------- | :------------------------------ | :--------------------------------- | -| āļ›āļĢāļ°āđ€āļ āļ—āđ€āļ­āļāļŠāļēāļĢ (Correspondence, RFA) | **Superadmin** | Global | -| āļŠāļ–āļēāļ™āļ°āđ€āļ­āļāļŠāļēāļĢ (Draft, Approved, etc.) | **Superadmin** | Global | -| āļŦāļĄāļ§āļ”āļŦāļĄāļđāđˆāđāļšāļš (Shop Drawing) | **Project Manager** | Project (āļŠāļĢāđ‰āļēāļ‡āđƒāļŦāļĄāđˆāđ„āļ”āđ‰āļ āļēāļĒāđƒāļ™āđ‚āļ„āļĢāļ‡āļāļēāļĢ) | -| Tags | **Org Admin / Project Manager** | Organization / Project | -| āļšāļ—āļšāļēāļ—āđāļĨāļ°āļŠāļīāļ—āļ˜āļīāđŒ (Custom Roles) | **Superadmin / Org Admin** | Global / Organization | -| Document Numbering Formats | **Superadmin / Admin** | Global / Organization | - -## **ðŸ‘Ĩ 5. āļ‚āđ‰āļ­āļāļģāļŦāļ™āļ”āļ”āđ‰āļēāļ™āļœāļđāđ‰āđƒāļŠāđ‰āļ‡āļēāļ™ (User Interface & Experience)** - -### **5.1. Layout āļŦāļĨāļąāļ:** āļŦāļ™āđ‰āļēāđ€āļ§āđ‡āļšāđƒāļŠāđ‰āļĢāļđāļ›āđāļšāļš App Shell āļ—āļĩāđˆāļ›āļĢāļ°āļāļ­āļšāļ”āđ‰āļ§āļĒ - -- Navbar (āļŠāđˆāļ§āļ™āļšāļ™): āđāļŠāļ”āļ‡āļŠāļ·āđˆāļ­āļĢāļ°āļšāļš, āđ€āļĄāļ™āļđāļœāļđāđ‰āđƒāļŠāđ‰ (Profile), āđ€āļĄāļ™āļđāļŠāļģāļŦāļĢāļąāļš Document Control/āđ€āļĄāļ™āļđāļŠāļģāļŦāļĢāļąāļš Admin/Superadmin (āļˆāļąāļ”āļāļēāļĢāļœāļđāđ‰āđƒāļŠāđ‰, āļˆāļąāļ”āļāļēāļĢāļŠāļīāļ—āļ˜āļīāđŒ), āđāļĨāļ°āļ›āļļāđˆāļĄ Login/Logout -- Sidebar (āļ”āđ‰āļēāļ™āļ‚āđ‰āļēāļ‡): āđ€āļ›āđ‡āļ™āđ€āļĄāļ™āļđāļŦāļĨāļąāļāļŠāļģāļŦāļĢāļąāļšāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļŠāđˆāļ§āļ™āļ—āļĩāđˆāđ€āļāļĩāđˆāļĒāļ§āļ‚āđ‰āļ­āļ‡āļāļąāļšāđ€āļ­āļāļŠāļēāļĢāļ—āļąāđ‰āļ‡āļŦāļĄāļ” āđ€āļŠāđˆāļ™ Dashboard, Correspondences, RFA, Drawings -- Main Content Area: āļžāļ·āđ‰āļ™āļ—āļĩāđˆāļŠāļģāļŦāļĢāļąāļšāđāļŠāļ”āļ‡āđ€āļ™āļ·āđ‰āļ­āļŦāļēāļŦāļĨāļąāļāļ‚āļ­āļ‡āļŦāļ™āđ‰āļēāļ—āļĩāđˆāđ€āļĨāļ·āļ­āļ - -### **5.2. āļŦāļ™āđ‰āļē Landing Page:** āđ€āļ›āđ‡āļ™āļŦāļ™āđ‰āļēāđāļĢāļāļ—āļĩāđˆāđāļŠāļ”āļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļšāļēāļ‡āļŠāđˆāļ§āļ™āļ‚āļ­āļ‡āđ‚āļ„āļĢāļ‡āļāļēāļĢāļŠāļģāļŦāļĢāļąāļšāļœāļđāđ‰āđƒāļŠāđ‰āļ—āļĩāđˆāļĒāļąāļ‡āđ„āļĄāđˆāđ„āļ”āđ‰āļĨāđ‡āļ­āļāļ­āļīāļ™ - -### **5.3. āļŦāļ™āđ‰āļē Dashboard:** āđ€āļ›āđ‡āļ™āļŦāļ™āđ‰āļēāđāļĢāļāļŦāļĨāļąāļ‡āļˆāļēāļāļĨāđ‡āļ­āļāļ­āļīāļ™ āļ›āļĢāļ°āļāļ­āļšāļ”āđ‰āļ§āļĒ - -- āļāļēāļĢāđŒāļ”āļŠāļĢāļļāļ›āļ āļēāļžāļĢāļ§āļĄ (KPI Cards): āđāļŠāļ”āļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļŠāļĢāļļāļ›āļ—āļĩāđˆāļŠāļģāļ„āļąāļāļ‚āļ­āļ‡āļ­āļ‡āļ„āđŒāļāļĢ āđ€āļŠāđˆāļ™ āļˆāļģāļ™āļ§āļ™āđ€āļ­āļāļŠāļēāļĢ, āļ‡āļēāļ™āļ—āļĩāđˆāđ€āļāļīāļ™āļāļģāļŦāļ™āļ” -- āļ•āļēāļĢāļēāļ‡ "āļ‡āļēāļ™āļ‚āļ­āļ‡āļ‰āļąāļ™" (My Tasks Table): āđāļŠāļ”āļ‡āļĢāļēāļĒāļāļēāļĢāļ‡āļēāļ™āļ—āļąāđ‰āļ‡āļŦāļĄāļ”āļˆāļēāļ Circulation āļ—āļĩāđˆāļœāļđāđ‰āđƒāļŠāđ‰āļ•āđ‰āļ­āļ‡āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢ -- Security Metrics: āđāļŠāļ”āļ‡āļˆāļģāļ™āļ§āļ™ files scanned, security incidents, failed login attempts - -### **5.4. āļāļēāļĢāļ•āļīāļ”āļ•āļēāļĄāļŠāļ–āļēāļ™āļ°:** āļ­āļ‡āļ„āđŒāļāļĢāļŠāļēāļĄāļēāļĢāļ–āļ•āļīāļ”āļ•āļēāļĄāļŠāļ–āļēāļ™āļ°āđ€āļ­āļāļŠāļēāļĢāļ—āļąāđ‰āļ‡āļ‚āļ­āļ‡āļ•āļ™āđ€āļ­āļ‡ (Originator) āđāļĨāļ°āļŠāļ–āļēāļ™āļ°āđ€āļ­āļāļŠāļēāļĢāļ—āļĩāđˆāļŠāđˆāļ‡āļĄāļēāļ–āļķāļ‡āļ•āļ™āđ€āļ­āļ‡ (Recipient) - -### **5.5. āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļĄāļđāļĨāļŠāđˆāļ§āļ™āļ•āļąāļ§ (Profile Page):** āļœāļđāđ‰āđƒāļŠāđ‰āļŠāļēāļĄāļēāļĢāļ–āļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļĄāļđāļĨāļŠāđˆāļ§āļ™āļ•āļąāļ§āđāļĨāļ°āđ€āļ›āļĨāļĩāđˆāļĒāļ™āļĢāļŦāļąāļŠāļœāđˆāļēāļ™āļ‚āļ­āļ‡āļ•āļ™āđ€āļ­āļ‡āđ„āļ”āđ‰ - -### **5.6. āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāđ€āļ­āļāļŠāļēāļĢāļ—āļēāļ‡āđ€āļ—āļ„āļ™āļīāļ„ (RFA & Workflow):** āļœāļđāđ‰āđƒāļŠāđ‰āļŠāļēāļĄāļēāļĢāļ–āļ”āļđ RFA āđƒāļ™āļĢāļđāļ›āđāļšāļš Workflow āļ—āļąāđ‰āļ‡āļŦāļĄāļ”āđ„āļ”āđ‰āđƒāļ™āļŦāļ™āđ‰āļēāđ€āļ”āļĩāļĒāļ§, āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ—āļĩāđˆāļĒāļąāļ‡āđ„āļĄāđˆāļ–āļķāļ‡āļŦāļĢāļ·āļ­āļœāđˆāļēāļ™āđ„āļ›āđāļĨāđ‰āļ§āļˆāļ°āđ€āļ›āđ‡āļ™āļĢāļđāļ›āđāļšāļš diable, āļŠāļēāļĄāļēāļĢāļ–āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāđ„āļ”āđ‰āđ€āļ‰āļžāļēāļ°āđƒāļ™āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ—āļĩāđˆāđ„āļ”āđ‰āļĢāļąāļšāļĄāļ­āļšāļŦāļĄāļēāļĒāļ‡āļēāļ™ (active) āđ€āļŠāđˆāļ™ āļ•āļĢāļ§āļˆāļŠāļ­āļšāđāļĨāđ‰āļ§ āđ€āļžāļ·āđˆāļ­āđ„āļ›āļĒāļąāļ‡āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ•āđˆāļ­āđ„āļ›, āļŠāļīāļ—āļ˜āļīāđŒ Document Control āļ‚āļķāđ‰āļ™āđ„āļ› āļŠāļēāļĄāļĢāļ–āļāļ” āđ„āļ›āļĒāļąāļ‡āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ•āđˆāļ­āđ„āļ› āđ„āļ”āđ‰āļ—āļļāļāļ‚āļąāđ‰āļ™āļ•āļ­āļ™, āļāļēāļĢāļĒāđ‰āļ­āļ™āļāļĨāļąāļš āđ„āļ›āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļāđˆāļ­āļ™āļŦāļ™āđ‰āļē āļŠāļēāļĄāļēāļĢāļ–āļ—āļģāđ„āļ”āđ‰āđ‚āļ”āļĒ āļŠāļīāļ—āļ˜āļīāđŒ Document Control āļ‚āļķāđ‰āļ™āđ„āļ› - -### **5.7. āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāđƒāļšāđ€āļ§āļĩāļĒāļ™āđ€āļ­āļāļŠāļēāļĢ (Circulation):** āļœāļđāđ‰āđƒāļŠāđ‰āļŠāļēāļĄāļēāļĢāļ–āļ”āļđ Circulation āđƒāļ™āļĢāļđāļ›āđāļšāļš Workflow āļ—āļąāđ‰āļ‡āļŦāļĄāļ”āđ„āļ”āđ‰āđƒāļ™āļŦāļ™āđ‰āļēāđ€āļ”āļĩāļĒāļ§,āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ—āļĩāđˆāļĒāļąāļ‡āđ„āļĄāđˆāļ–āļķāļ‡āļŦāļĢāļ·āļ­āļœāđˆāļēāļ™āđ„āļ›āđāļĨāđ‰āļ§āļˆāļ°āđ€āļ›āđ‡āļ™āļĢāļđāļ›āđāļšāļš diable, āļŠāļēāļĄāļēāļĢāļ–āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāđ„āļ”āđ‰āđ€āļ‰āļžāļēāļ°āđƒāļ™āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ—āļĩāđˆāđ„āļ”āđ‰āļĢāļąāļšāļĄāļ­āļšāļŦāļĄāļēāļĒāļ‡āļēāļ™ (active) āđ€āļŠāđˆāļ™ āļ•āļĢāļ§āļˆāļŠāļ­āļšāđāļĨāđ‰āļ§ āđ€āļžāļ·āđˆāļ­āđ„āļ›āļĒāļąāļ‡āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ•āđˆāļ­āđ„āļ›, āļŠāļīāļ—āļ˜āļīāđŒ Document Control āļ‚āļķāđ‰āļ™āđ„āļ› āļŠāļēāļĄāļĢāļ–āļāļ” āđ„āļ›āļĒāļąāļ‡āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ•āđˆāļ­āđ„āļ› āđ„āļ”āđ‰āļ—āļļāļāļ‚āļąāđ‰āļ™āļ•āļ­āļ™, āļāļēāļĢāļĒāđ‰āļ­āļ™āļāļĨāļąāļš āđ„āļ›āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļāđˆāļ­āļ™āļŦāļ™āđ‰āļē āļŠāļēāļĄāļēāļĢāļ–āļ—āļģāđ„āļ”āđ‰āđ‚āļ”āļĒ āļŠāļīāļ—āļ˜āļīāđŒ Document Control āļ‚āļķāđ‰āļ™āđ„āļ› - -### **5.8. āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāđ€āļ­āļāļŠāļēāļĢāļ™āļģāļŠāđˆāļ‡ (Transmittals):** āļœāļđāđ‰āđƒāļŠāđ‰āļŠāļēāļĄāļēāļĢāļ–āļ”āļđ Transmittals āđƒāļ™āļĢāļđāļ›āđāļšāļšāļĢāļēāļĒāļāļēāļĢāļ—āļąāđ‰āļ‡āļŦāļĄāļ”āđ„āļ”āđ‰āđƒāļ™āļŦāļ™āđ‰āļēāđ€āļ”āļĩāļĒāļ§ - -### **5.9. āļ‚āđ‰āļ­āļāļģāļŦāļ™āļ” UI/UX āļāļēāļĢāđāļ™āļšāđ„āļŸāļĨāđŒ (File Attachment UX):** - -- āļĢāļ°āļšāļšāļ•āđ‰āļ­āļ‡āļĢāļ­āļ‡āļĢāļąāļšāļāļēāļĢāļ­āļąāļ›āđ‚āļŦāļĨāļ”āđ„āļŸāļĨāđŒāļŦāļĨāļēāļĒāđ„āļŸāļĨāđŒāļžāļĢāđ‰āļ­āļĄāļāļąāļ™ (Multi-file upload) āđ€āļŠāđˆāļ™ āļāļēāļĢāļĨāļēāļāđāļĨāļ°āļ§āļēāļ‡ (Drag-and-Drop) -- āđƒāļ™āļŦāļ™āđ‰āļēāļ­āļąāļ›āđ‚āļŦāļĨāļ” (āđ€āļŠāđˆāļ™ āļŠāļĢāđ‰āļēāļ‡ RFA āļŦāļĢāļ·āļ­ Correspondence) āļœāļđāđ‰āđƒāļŠāđ‰āļ•āđ‰āļ­āļ‡āļŠāļēāļĄāļēāļĢāļ–āļāļģāļŦāļ™āļ”āđ„āļ”āđ‰āļ§āđˆāļēāđ„āļŸāļĨāđŒāđƒāļ”āđ€āļ›āđ‡āļ™ "āđ€āļ­āļāļŠāļēāļĢāļŦāļĨāļąāļ" (Main Document āđ€āļŠāđˆāļ™ PDF) āđāļĨāļ°āđ„āļŸāļĨāđŒāđƒāļ”āđ€āļ›āđ‡āļ™ "āđ€āļ­āļāļŠāļēāļĢāđāļ™āļšāļ›āļĢāļ°āļāļ­āļš" (Supporting Attachments āđ€āļŠāđˆāļ™ .dwg, .docx, .zip) -- **Security Feedback:** āđāļŠāļ”āļ‡ security warnings āļŠāļģāļŦāļĢāļąāļš file types āļ—āļĩāđˆāđ€āļŠāļĩāđˆāļĒāļ‡āļŦāļĢāļ·āļ­ files āļ—āļĩāđˆ fail virus scan -- **File Type Indicators:** āđāļŠāļ”āļ‡ file type icons āđāļĨāļ° security status - -### **5.10 Form & Interaction (āđƒāļŦāļĄāđˆ)** - -- **Dynamic Form Generator:** āđƒāļŠāđ‰ Component āļāļĨāļēāļ‡āļ—āļĩāđˆāļĢāļąāļš JSON Schema āđāļĨāđ‰āļ§ Render Form āļ­āļ­āļāļĄāļēāļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī āđ€āļžāļ·āđˆāļ­āļĨāļ”āļ„āļ§āļēāļĄāļ‹āđ‰āļģāļ‹āđ‰āļ­āļ™āļ‚āļ­āļ‡āđ‚āļ„āđ‰āļ”āļŦāļ™āđ‰āļēāļšāđ‰āļēāļ™ āđāļĨāļ°āļĢāļ­āļ‡āļĢāļąāļšāđ€āļ­āļāļŠāļēāļĢāļ›āļĢāļ°āđ€āļ āļ—āđƒāļŦāļĄāđˆāđ† āđ„āļ”āđ‰āļ—āļąāļ™āļ—āļĩ -- **Optimistic Updates:** āļāļēāļĢāđ€āļ›āļĨāļĩāđˆāļĒāļ™āļŠāļ–āļēāļ™āļ° (āđ€āļŠāđˆāļ™ āļāļ” Approve, āļāļ” Read) āđƒāļŦāđ‰ UI āđ€āļ›āļĨāļĩāđˆāļĒāļ™āļŠāļ–āļēāļ™āļ°āļ—āļąāļ™āļ—āļĩāđƒāļŦāđ‰āļœāļđāđ‰āđƒāļŠāđ‰āđ€āļŦāđ‡āļ™āļāđˆāļ­āļ™āļĢāļ­ API Response (Rollback āļ–āđ‰āļē Failed) - -### **5.11 Mobile Responsiveness (āđƒāļŦāļĄāđˆ)** - -- **Table Visualization:** āļšāļ™āļŦāļ™āđ‰āļēāļˆāļ­āļĄāļ·āļ­āļ–āļ·āļ­ āļ•āļēāļĢāļēāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļĩāđˆāļĄāļĩāļŦāļĨāļēāļĒ Column (āđ€āļŠāđˆāļ™ Correspondence List) āļ•āđ‰āļ­āļ‡āđ€āļ›āļĨāļĩāđˆāļĒāļ™āļāļēāļĢāđāļŠāļ”āļ‡āļœāļĨāđ€āļ›āđ‡āļ™āđāļšāļš **Card View** āļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī -- **Navigation:** Sidebar āļ•āđ‰āļ­āļ‡āđ€āļ›āđ‡āļ™āđāļšāļš Collapsible Drawer - -### **5.12 Resilience & Offline Support (āđƒāļŦāļĄāđˆ)** - -- **Auto-Save Draft:** āļĢāļ°āļšāļšāļ•āđ‰āļ­āļ‡āļšāļąāļ™āļ—āļķāļāļ‚āđ‰āļ­āļĄāļđāļĨāļŸāļ­āļĢāđŒāļĄāļ—āļĩāđˆāļāļģāļĨāļąāļ‡āļāļĢāļ­āļāļĨāļ‡ **LocalStorage** āļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī āđ€āļžāļ·āđˆāļ­āļ›āđ‰āļ­āļ‡āļāļąāļ™āļ‚āđ‰āļ­āļĄāļđāļĨāļŦāļēāļĒāļāļĢāļ“āļĩāđ€āļ™āđ‡āļ•āļŦāļĨāļļāļ”āļŦāļĢāļ·āļ­āļ›āļīāļ” Browser āđ‚āļ”āļĒāđ„āļĄāđˆāđ„āļ”āđ‰āļ•āļąāđ‰āļ‡āđƒāļˆ -- **Graceful Degradation:** āļŦāļēāļ Service āļĢāļ­āļ‡ (āđ€āļŠāđˆāļ™ Search, Notification) āļĨāđˆāļĄ āļĢāļ°āļšāļšāļŦāļĨāļąāļ (CRUD) āļ•āđ‰āļ­āļ‡āļĒāļąāļ‡āļ—āļģāļ‡āļēāļ™āļ•āđˆāļ­āđ„āļ”āđ‰ - -## **ðŸ›Ąïļ 6. āļ‚āđ‰āļ­āļāļģāļŦāļ™āļ”āļ—āļĩāđˆāđ„āļĄāđˆāđƒāļŠāđˆāļŸāļąāļ‡āļāđŒāļŠāļąāļ™āļāļēāļĢāļ—āļģāļ‡āļēāļ™ (Non-Functional Requirements)** - -### **6.1. āļāļēāļĢāļšāļąāļ™āļ—āļķāļāļāļēāļĢāļāļĢāļ°āļ—āļģ (Audit Log):** āļ—āļļāļāļāļēāļĢāļāļĢāļ°āļ—āļģāļ—āļĩāđˆāļŠāļģāļ„āļąāļāļ‚āļ­āļ‡āļœāļđāđ‰āđƒāļŠāđ‰ (āļŠāļĢāđ‰āļēāļ‡, āđāļāđ‰āđ„āļ‚, āļĨāļš, āļŠāđˆāļ‡) āļˆāļ°āļ–āļđāļāļšāļąāļ™āļ—āļķāļāđ„āļ§āđ‰āđƒāļ™ audit_logs āđ€āļžāļ·āđˆāļ­āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļĒāđ‰āļ­āļ™āļŦāļĨāļąāļ‡ - -- **6.1.1 āļ‚āļ­āļšāđ€āļ‚āļ•āļāļēāļĢāļšāļąāļ™āļ—āļķāļ Audit Log:** - - āļ—āļļāļāļāļēāļĢāļŠāļĢāđ‰āļēāļ‡/āđāļāđ‰āđ„āļ‚/āļĨāļš āļ‚āđ‰āļ­āļĄāļđāļĨāļŠāļģāļ„āļąāļ (correspondences, RFAs, drawings, users, permissions) - - āļ—āļļāļāļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨ sensitive (user data, financial information) - - āļ—āļļāļāļāļēāļĢāđ€āļ›āļĨāļĩāđˆāļĒāļ™āļŠāļ–āļēāļ™āļ° workflow (status transitions) - - āļ—āļļāļāļāļēāļĢāļ”āļēāļ§āļ™āđŒāđ‚āļŦāļĨāļ”āđ„āļŸāļĨāđŒāļŠāļģāļ„āļąāļ (contract documents, financial reports) - - āļ—āļļāļāļāļēāļĢāđ€āļ›āļĨāļĩāđˆāļĒāļ™āđāļ›āļĨāļ‡ permission āđāļĨāļ° role assignment - - āļ—āļļāļāļāļēāļĢāļĨāđ‡āļ­āļāļ­āļīāļ™āļ—āļĩāđˆāļŠāļģāđ€āļĢāđ‡āļˆāđāļĨāļ°āļĨāđ‰āļĄāđ€āļŦāļĨāļ§ - - āļ—āļļāļāļāļēāļĢāļŠāđˆāļ‡āļ„āļģāļ‚āļ­ API āļ—āļĩāđˆāļŠāļģāļ„āļąāļ - -- **6.1.2 āļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļĩāđˆāļ•āđ‰āļ­āļ‡āļšāļąāļ™āļ—āļķāļāđƒāļ™ Audit Log:** - - āļœāļđāđ‰āđƒāļŠāđ‰āļ‡āļēāļ™ (user_id) - - āļāļēāļĢāļāļĢāļ°āļ—āļģ (action) - - āļŠāļ™āļīāļ”āļ‚āļ­āļ‡ entity (entity_type) - - ID āļ‚āļ­āļ‡ entity (entity_id) - - āļ‚āđ‰āļ­āļĄāļđāļĨāļāđˆāļ­āļ™āļāļēāļĢāđ€āļ›āļĨāļĩāđˆāļĒāļ™āđāļ›āļĨāļ‡ (old_values) - āļŠāļģāļŦāļĢāļąāļš update operations - - āļ‚āđ‰āļ­āļĄāļđāļĨāļŦāļĨāļąāļ‡āļāļēāļĢāđ€āļ›āļĨāļĩāđˆāļĒāļ™āđāļ›āļĨāļ‡ (new_values) - āļŠāļģāļŦāļĢāļąāļš update operations - - IP address - - User agent - - Timestamp - - Request ID āļŠāļģāļŦāļĢāļąāļš tracing - -### **6.2. Data Archiving & Partitioning (āđƒāļŦāļĄāđˆ)** - -- āļŠāļģāļŦāļĢāļąāļšāļ•āļēāļĢāļēāļ‡āļ—āļĩāđˆāļĄāļĩāļ‚āļ™āļēāļ”āđƒāļŦāļāđˆāđāļĨāļ°āđ‚āļ•āđ€āļĢāđ‡āļ§ (āđ€āļŠāđˆāļ™ `audit_logs`, `notifications`, `correspondence_revisions`) āļ•āđ‰āļ­āļ‡āļ­āļ­āļāđāļšāļšāđ‚āļ”āļĒāļĢāļ­āļ‡āļĢāļąāļš **Table Partitioning** (āđāļšāđˆāļ‡āļ•āļēāļĄ Range āļ§āļąāļ™āļ—āļĩāđˆ āļŦāļĢāļ·āļ­ List) āđ€āļžāļ·āđˆāļ­āļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāđƒāļ™āļĢāļ°āļĒāļ°āļĒāļēāļ§ - -### **6.3. āļāļēāļĢāļ„āđ‰āļ™āļŦāļē (Search):** āļĢāļ°āļšāļšāļ•āđ‰āļ­āļ‡āļĄāļĩāļŸāļąāļ‡āļāđŒāļŠāļąāļ™āļāļēāļĢāļ„āđ‰āļ™āļŦāļēāļ‚āļąāđ‰āļ™āļŠāļđāļ‡ āļ—āļĩāđˆāļŠāļēāļĄāļēāļĢāļ–āļ„āđ‰āļ™āļŦāļēāđ€āļ­āļāļŠāļēāļĢ **correspondence**, **rfa**, **shop_drawing**, **contract-drawing**, **transmittal** āđāļĨāļ° **āđƒāļšāđ€āļ§āļĩāļĒāļ™ (Circulations)** āļˆāļēāļāļŦāļĨāļēāļĒāđ€āļ‡āļ·āđˆāļ­āļ™āđ„āļ‚āļžāļĢāđ‰āļ­āļĄāļāļąāļ™āđ„āļ”āđ‰ āđ€āļŠāđˆāļ™ āļ„āđ‰āļ™āļŦāļēāļˆāļēāļāļŠāļ·āđˆāļ­āđ€āļĢāļ·āđˆāļ­āļ‡, āļ›āļĢāļ°āđ€āļ āļ—, āļ§āļąāļ™āļ—āļĩāđˆ, āđāļĨāļ° Tag - -### **6.4. āļāļēāļĢāļ—āļģāļĢāļēāļĒāļ‡āļēāļ™ (Reporting):** āļŠāļēāļĄāļēāļĢāļ–āļˆāļąāļ”āļ—āļģāļĢāļēāļĒāļ‡āļēāļ™āļŠāļĢāļļāļ›āđāļĒāļāļ›āļĢāļ°āđ€āļ āļ—āļ‚āļ­āļ‡ Correspondence āļ›āļĢāļ°āļˆāļģāļ§āļąāļ™, āļŠāļąāļ›āļ”āļēāļŦāđŒ, āđ€āļ”āļ·āļ­āļ™, āđāļĨāļ°āļ›āļĩāđ„āļ”āđ‰ - -### **6.5. āļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļž (Performance):** āļĄāļĩāļāļēāļĢāđƒāļŠāđ‰ Caching āļāļąāļšāļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļĩāđˆāđ€āļĢāļĩāļĒāļāđƒāļŠāđ‰āļšāđˆāļ­āļĒ āđāļĨāļ°āđƒāļŠāđ‰ Pagination āđƒāļ™āļ•āļēāļĢāļēāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāđ€āļžāļ·āđˆāļ­āļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļĄāļđāļĨāļˆāļģāļ™āļ§āļ™āļĄāļēāļ - -- **6.5.1 āļ•āļąāļ§āļŠāļĩāđ‰āļ§āļąāļ”āļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļž:** - - **API Response Time:** < 200ms (90th percentile) āļŠāļģāļŦāļĢāļąāļš operation āļ—āļąāđˆāļ§āđ„āļ› - - **Search Query Performance:** < 500ms āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ„āđ‰āļ™āļŦāļēāļ‚āļąāđ‰āļ™āļŠāļđāļ‡ - - **File Upload Performance:** < 30 seconds āļŠāļģāļŦāļĢāļąāļšāđ„āļŸāļĨāđŒāļ‚āļ™āļēāļ” 50MB - - **Concurrent Users:** āļĢāļ­āļ‡āļĢāļąāļšāļœāļđāđ‰āđƒāļŠāđ‰āļžāļĢāđ‰āļ­āļĄāļāļąāļ™āļ­āļĒāđˆāļēāļ‡āļ™āđ‰āļ­āļĒ 100 āļ„āļ™ - - **Database Connection Pool:** āļ‚āļ™āļēāļ”āđ€āļŦāļĄāļēāļ°āļŠāļĄāļāļąāļš workload (default: min 5, max 20 connections) - - **Cache Hit Ratio:** > 80% āļŠāļģāļŦāļĢāļąāļš cached data - - **Application Startup Time:** < 30 seconds - -- **6.5.2 Caching Strategy:** - - **Master Data Cache:** Roles, Permissions, Organizations, Project metadata (TTL: 1 hour) - - **User Session Cache:** User permissions āđāļĨāļ° profile data (TTL: 30 minutes) - - **Search Result Cache:** Frequently searched queries (TTL: 15 minutes) - - **File Metadata Cache:** Attachment metadata (TTL: 1 hour) - - **Document Cache:** Frequently accessed document metadata (TTL: 30 minutes) - - **āļ•āđ‰āļ­āļ‡āļĄāļĩ cache invalidation strategy āļ—āļĩāđˆāļŠāļąāļ”āđ€āļˆāļ™:** - - Invalidate on update/delete operations - - Time-based expiration - - Manual cache clearance āļŠāļģāļŦāļĢāļąāļš admin operations - - āđƒāļŠāđ‰ Redis āđ€āļ›āđ‡āļ™ distributed cache backend - - āļ•āđ‰āļ­āļ‡āļĄāļĩ cache monitoring (hit/miss ratios) - -### **6.6. āļ„āļ§āļēāļĄāļ›āļĨāļ­āļ”āļ āļąāļĒ (Security):** - -- āļĄāļĩāļĢāļ°āļšāļš Rate Limiting āđ€āļžāļ·āđˆāļ­āļ›āđ‰āļ­āļ‡āļāļąāļ™āļāļēāļĢāđ‚āļˆāļĄāļ•āļĩāđāļšāļš Brute-force -- āļāļēāļĢāļˆāļąāļ”āļāļēāļĢ Secret (āđ€āļŠāđˆāļ™ āļĢāļŦāļąāļŠāļœāđˆāļēāļ™ DB, JWT Secret) āļˆāļ°āļ•āđ‰āļ­āļ‡āļ—āļģāļœāđˆāļēāļ™ Environment Variable āļ‚āļ­āļ‡ Docker āđ€āļžāļ·āđˆāļ­āļ„āļ§āļēāļĄāļ›āļĨāļ­āļ”āļ āļąāļĒāļŠāļđāļ‡āļŠāļļāļ” - -- **6.6.1 Rate Limiting Strategy:** - - **Anonymous Endpoints:** 100 requests/hour āļ•āđˆāļ­ IP address - - **Authenticated Endpoints:** - - Viewer: 500 requests/hour - - Editor: 1000 requests/hour - - Document Control: 2000 requests/hour - - Admin/Superadmin: 5000 requests/hour - - **File Upload Endpoints:** 50 requests/hour āļ•āđˆāļ­ user - - **Search Endpoints:** 500 requests/hour āļ•āđˆāļ­ user - - **Authentication Endpoints:** 10 requests/minute āļ•āđˆāļ­ IP address - - **āļ•āđ‰āļ­āļ‡āļĄāļĩ mechanism āļŠāļģāļŦāļĢāļąāļšāļĒāļāđ€āļ§āđ‰āļ™ rate limiting āļŠāļģāļŦāļĢāļąāļš trusted services** - - āļ•āđ‰āļ­āļ‡āļšāļąāļ™āļ—āļķāļ log āđ€āļĄāļ·āđˆāļ­āļĄāļĩāļāļēāļĢ trigger rate limiting - -- **6.6.2 Error Handling āđāļĨāļ° Resilience:** - - āļ•āđ‰āļ­āļ‡āļĄāļĩ circuit breaker pattern āļŠāļģāļŦāļĢāļąāļš external service calls - - āļ•āđ‰āļ­āļ‡āļĄāļĩ retry mechanism āļ”āđ‰āļ§āļĒ exponential backoff - - āļ•āđ‰āļ­āļ‡āļĄāļĩ graceful degradation āđ€āļĄāļ·āđˆāļ­āļšāļĢāļīāļāļēāļĢāļ āļēāļĒāļ™āļ­āļāļĨāđ‰āļĄāđ€āļŦāļĨāļ§ - - Error messages āļ•āđ‰āļ­āļ‡āđ„āļĄāđˆāđ€āļ›āļīāļ”āđ€āļœāļĒāļ‚āđ‰āļ­āļĄāļđāļĨ sensitive - -- **6.6.3 Input Validation:** - - āļ•āđ‰āļ­āļ‡āļĄāļĩ input validation āļ—āļąāđ‰āļ‡āļāļąāđˆāļ‡ client āđāļĨāļ° server (defense in depth) - - āļ•āđ‰āļ­āļ‡āļ›āđ‰āļ­āļ‡āļāļąāļ™ OWASP Top 10 vulnerabilities: - - SQL Injection (āđƒāļŠāđ‰ parameterized queries āļœāđˆāļēāļ™ ORM) - - XSS (input sanitization āđāļĨāļ° output encoding) - - CSRF (CSRF tokens āļŠāļģāļŦāļĢāļąāļš state-changing operations) - - āļ•āđ‰āļ­āļ‡ validate file uploads: - - File type (white-list approach) - - File size - - File content (magic number verification) - - āļ•āđ‰āļ­āļ‡ sanitize user inputs āļāđˆāļ­āļ™āđāļŠāļ”āļ‡āļœāļĨāđƒāļ™ UI - - āļ•āđ‰āļ­āļ‡āđƒāļŠāđ‰ content security policy (CSP) headers - - āļ•āđ‰āļ­āļ‡āļĄāļĩ request size limits āđ€āļžāļ·āđˆāļ­āļ›āđ‰āļ­āļ‡āļāļąāļ™ DoS attacks - -- **6.6.4 Session āđāļĨāļ° Token Management:** - - **JWT token expiration:** 8 hours āļŠāļģāļŦāļĢāļąāļš access token - - **Refresh token expiration:** 7 days - - **Refresh token mechanism:** āļ•āđ‰āļ­āļ‡āļĢāļ­āļ‡āļĢāļąāļš token rotation āđāļĨāļ° revocation - - **Token revocation on logout:** āļ•āđ‰āļ­āļ‡āļšāļąāļ™āļ—āļķāļ revoked tokens āļˆāļ™āļāļ§āđˆāļēāļˆāļ° expire - - **Concurrent session management:** - - āļˆāļģāļāļąāļ”āļˆāļģāļ™āļ§āļ™ session āļžāļĢāđ‰āļ­āļĄāļāļąāļ™āđ„āļ”āđ‰ (default: 5 devices) - - āļ•āđ‰āļ­āļ‡āđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āđ€āļĄāļ·āđˆāļ­āļĄāļĩ login āļˆāļēāļ device/location āđƒāļŦāļĄāđˆ - - **Device fingerprinting:** āļŠāļģāļŦāļĢāļąāļš security āđāļĨāļ° audit purposes - - **Password policy:** - - āļ„āļ§āļēāļĄāļĒāļēāļ§āļ‚āļąāđ‰āļ™āļ•āđˆāļģ: 8 characters - - āļ•āđ‰āļ­āļ‡āļĄāļĩ uppercase, lowercase, number, special character - - āļ•āđ‰āļ­āļ‡āđ€āļ›āļĨāļĩāđˆāļĒāļ™ password āļ—āļļāļ 90 āļ§āļąāļ™ - - āļ•āđ‰āļ­āļ‡āļ›āđ‰āļ­āļ‡āļāļąāļ™āļāļēāļĢāđƒāļŠāđ‰ password āļ—āļĩāđˆāđ€āļ„āļĒāđƒāļŠāđ‰āļĄāļēāđāļĨāđ‰āļ§ 5 āļ„āļĢāļąāđ‰āļ‡āļĨāđˆāļēāļŠāļļāļ” - -### **6.7. āļāļēāļĢāļŠāļģāļĢāļ­āļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāđāļĨāļ°āļāļēāļĢāļāļđāđ‰āļ„āļ·āļ™ (Backup & Recovery):** - -- āļĢāļ°āļšāļšāļˆāļ°āļ•āđ‰āļ­āļ‡āļĄāļĩāļāļĨāđ„āļāļāļēāļĢāļŠāļģāļĢāļ­āļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļīāļŠāļģāļŦāļĢāļąāļšāļāļēāļ™āļ‚āđ‰āļ­āļĄāļđāļĨ MariaDB [cite: 2.4] āđāļĨāļ°āđ„āļŸāļĨāđŒāđ€āļ­āļāļŠāļēāļĢāļ—āļąāđ‰āļ‡āļŦāļĄāļ”āđƒāļ™ /share/dms-data [cite: 2.1] (āđ€āļŠāđˆāļ™ āđƒāļŠāđ‰ HBS 3 āļ‚āļ­āļ‡ QNAP āļŦāļĢāļ·āļ­āļŠāļ„āļĢāļīāļ›āļ•āđŒāļŠāļģāļĢāļ­āļ‡āļ‚āđ‰āļ­āļĄāļđāļĨ) āļ­āļĒāđˆāļēāļ‡āļ™āđ‰āļ­āļĒāļ§āļąāļ™āļĨāļ° 1 āļ„āļĢāļąāđ‰āļ‡ -- āļ•āđ‰āļ­āļ‡āļĄāļĩāđāļœāļ™āļāļēāļĢāļāļđāđ‰āļ„āļ·āļ™āļĢāļ°āļšāļš (Disaster Recovery Plan) āđƒāļ™āļāļĢāļ“āļĩāļ—āļĩāđˆ Server āļŦāļĨāļąāļ (QNAP) āđƒāļŠāđ‰āļ‡āļēāļ™āđ„āļĄāđˆāđ„āļ”āđ‰ - -- **6.7.1 āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļāļēāļĢāļāļđāđ‰āļ„āļ·āļ™:** - - **Database Restoration Procedure:** - - āļŠāļĢāđ‰āļēāļ‡āļˆāļēāļ full backup āļĨāđˆāļēāļŠāļļāļ” - - Apply transaction logs āļ–āļķāļ‡ point-in-time āļ—āļĩāđˆāļ•āđ‰āļ­āļ‡āļāļēāļĢ - - Verify data integrity post-restoration - - **File Storage Restoration Procedure:** - - Restore āļˆāļēāļ QNAP snapshot āļŦāļĢāļ·āļ­ backup - - Verify file integrity āđāļĨāļ° permissions - - **Application Redeployment Procedure:** - - Deploy āļˆāļēāļ version āļĨāđˆāļēāļŠāļļāļ”āļ—āļĩāđˆāļĢāļđāđ‰āļ§āđˆāļēāļ—āļģāļ‡āļēāļ™āđ„āļ”āđ‰ - - Verify application health - - **Data Integrity Verification Post-Recovery:** - - Run data consistency checks - - Verify critical business data - - **Recovery Time Objective (RTO):** < 4 āļŠāļąāđˆāļ§āđ‚āļĄāļ‡ - - **Recovery Point Objective (RPO):** < 1 āļŠāļąāđˆāļ§āđ‚āļĄāļ‡ - -### **6.8. āļāļĨāļĒāļļāļ—āļ˜āđŒāļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™ (Notification Strategy - āļ›āļĢāļąāļšāļ›āļĢāļļāļ‡):** - -- **6.8.1 āļĢāļ°āļšāļšāļˆāļ°āļŠāđˆāļ‡āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™ (āļœāđˆāļēāļ™ Email āļŦāļĢāļ·āļ­ Line [cite: 2.7]) āđ€āļĄāļ·āđˆāļ­āļĄāļĩāļāļēāļĢāļāļĢāļ°āļ—āļģāļ—āļĩāđˆāļŠāļģāļ„āļąāļ** āļ”āļąāļ‡āļ™āļĩāđ‰: - 1. āđ€āļĄāļ·āđˆāļ­āļĄāļĩāđ€āļ­āļāļŠāļēāļĢāđƒāļŦāļĄāđˆ (Correspondence, RFA) āļ–āļđāļāļŠāđˆāļ‡āļĄāļēāļ–āļķāļ‡āļ­āļ‡āļ„āđŒāļāļĢāļ“āđŒāļ‚āļ­āļ‡āđ€āļĢāļē - 2. āđ€āļĄāļ·āđˆāļ­āļĄāļĩāđƒāļšāđ€āļ§āļĩāļĒāļ™ (Circulation) āđƒāļŦāļĄāđˆ āļĄāļ­āļšāļŦāļĄāļēāļĒāļ‡āļēāļ™āļĄāļēāļ—āļĩāđˆāđ€āļĢāļē - 3. (āļ—āļēāļ‡āđ€āļĨāļ·āļ­āļ) āđ€āļĄāļ·āđˆāļ­āđ€āļ­āļāļŠāļēāļĢāļ—āļĩāđˆāđ€āļĢāļēāļŠāđˆāļ‡āđ„āļ› āļ–āļđāļāļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢ (āđ€āļŠāđˆāļ™ āļ­āļ™āļļāļĄāļąāļ•āļī/āļ›āļāļīāđ€āļŠāļ˜) - 4. (āļ—āļēāļ‡āđ€āļĨāļ·āļ­āļ) āđ€āļĄāļ·āđˆāļ­āđƒāļāļĨāđ‰āļ–āļķāļ‡āļ§āļąāļ™āļ„āļĢāļšāļāļģāļŦāļ™āļ” (Deadline) [cite: 3.2.5, 3.6.6, 3.7.5] - -- **6.8.2 Grouping/Digest (āđƒāļŦāļĄāđˆ):** āļāļĢāļ“āļĩāļĄāļĩāļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļ›āļĢāļ°āđ€āļ āļ—āđ€āļ”āļĩāļĒāļ§āļāļąāļ™āļˆāļģāļ™āļ§āļ™āļĄāļēāļāđƒāļ™āļŠāđˆāļ§āļ‡āđ€āļ§āļĨāļēāļŠāļąāđ‰āļ™āđ† (āđ€āļŠāđˆāļ™ Approve āđ€āļ­āļāļŠāļēāļĢ 10 āļ‰āļšāļąāļšāļĢāļ§āļ”) āļĢāļ°āļšāļšāļ•āđ‰āļ­āļ‡ **āļĢāļ§āļĄ (Batch)** āđ€āļ›āđ‡āļ™ 1 Email/Line Notification āđ€āļžāļ·āđˆāļ­āđ„āļĄāđˆāđƒāļŦāđ‰āļĢāļšāļāļ§āļ™āļœāļđāđ‰āđƒāļŠāđ‰ (Spamming) - -- **6.8.3 Notification Delivery Guarantees:** - - **At-least-once delivery:** āļŠāļģāļŦāļĢāļąāļš important notifications - - **Retry mechanism:** āļ”āđ‰āļ§āļĒ exponential backoff (max 3 reties) - - **Dead letter queue:** āļŠāļģāļŦāļĢāļąāļš notifications āļ—āļĩāđˆāļŠāđˆāļ‡āđ„āļĄāđˆāļŠāļģāđ€āļĢāđ‡āļˆāļŦāļĨāļąāļ‡āļˆāļēāļ retries - - **Delivery status tracking:** āļ•āđ‰āļ­āļ‡āļšāļąāļ™āļ—āļķāļāļŠāļ–āļēāļ™āļ°āļāļēāļĢāļŠāđˆāļ‡ notifications - - **Fallback channels:** āļ–āđ‰āļē Email āļĨāđ‰āļĄāđ€āļŦāļĨāļ§ āđƒāļŦāđ‰āļŠāđˆāļ‡āļœāđˆāļēāļ™ SYSTEM notification - - **Notification preferences:** āļœāļđāđ‰āđƒāļŠāđ‰āļ•āđ‰āļ­āļ‡āļŠāļēāļĄāļēāļĢāļ–āļāļģāļŦāļ™āļ” channel preferences āđ„āļ”āđ‰ - -### **6.9. Maintenance Mode (āđƒāļŦāļĄāđˆ)** - -- āļĢāļ°āļšāļšāļ•āđ‰āļ­āļ‡āļĄāļĩāļāļĨāđ„āļ **Maintenance Mode** āļ—āļĩāđˆ Admin āļŠāļēāļĄāļēāļĢāļ–āđ€āļ›āļīāļ”āđƒāļŠāđ‰āļ‡āļēāļ™āđ„āļ”āđ‰ - - āđ€āļĄāļ·āđˆāļ­āđ€āļ›āļīāļ”: āļœāļđāđ‰āđƒāļŠāđ‰āļ—āļąāđˆāļ§āđ„āļ›āļˆāļ°āđ€āļŦāđ‡āļ™āļŦāļ™āđ‰āļē "āļ›āļīāļ”āļ›āļĢāļąāļšāļ›āļĢāļļāļ‡" āđāļĨāļ°āđ„āļĄāđˆāļŠāļēāļĄāļēāļĢāļ–āđ€āļĢāļĩāļĒāļ API āđ„āļ”āđ‰ (āļĒāļāđ€āļ§āđ‰āļ™ Admin) - - āđƒāļŠāđ‰āļŠāļģāļŦāļĢāļąāļšāļŠāđˆāļ§āļ‡ Deploy Version āđƒāļŦāļĄāđˆ āļŦāļĢāļ·āļ­ Database Migration - -### **6.10. Monitoring āđāļĨāļ° Observability** - -- **6.10.1 Application Monitoring:** - - **Health checks:** /health endpoint āļŠāļģāļŦāļĢāļąāļš load balancer - - **Metrics collection:** Response times, error rates, throughput - - **Distributed tracing:** āļŠāļģāļŦāļĢāļąāļš request tracing across services - - **Log aggregation:** Structured logging āļ”āđ‰āļ§āļĒ JSON format - - **Alerting:** āļŠāļģāļŦāļĢāļąāļš critical errors āđāļĨāļ° performance degradation -- **6.10.2 Business Metrics:** - - āļˆāļģāļ™āļ§āļ™ documents created āļ•āđˆāļ­āļ§āļąāļ™ - - Workflow completion rates - - User activity metrics - - System utilization rates - - Search query performance -- **6.10.3 Security Monitoring:** - - Failed login attempts - - Rate limiting triggers - - Virus scan results - - File download activities - - Permission changes - -### **6.11 JSON Processing & Validation** - -- **6.11.1 JSON Schema Management** - - āļ•āđ‰āļ­āļ‡āļĄāļĩ centralized JSON schema registry - - āļ•āđ‰āļ­āļ‡āļĢāļ­āļ‡āļĢāļąāļš schema versioning āđāļĨāļ° migration - - āļ•āđ‰āļ­āļ‡āļĄāļĩ schema validation during runtime -- **6.11.2 Performance Optimization** - - **Caching:** Cache parsed JSON structures - - **Compression:** āđƒāļŠāđ‰ compression āļŠāļģāļŦāļĢāļąāļš JSON āļ‚āļ™āļēāļ”āđƒāļŦāļāđˆ - - **Indexing:** Support JSON path indexing āļŠāļģāļŦāļĢāļąāļš query -- **6.11.3 Error Handling** - - āļ•āđ‰āļ­āļ‡āļĄāļĩ graceful degradation āđ€āļĄāļ·āđˆāļ­ JSON validation āļĨāđ‰āļĄāđ€āļŦāļĨāļ§ - - āļ•āđ‰āļ­āļ‡āļĄāļĩ default fallback values - - āļ•āđ‰āļ­āļ‡āļšāļąāļ™āļ—āļķāļ error logs āļŠāļģāļŦāļĢāļąāļš validation failures - -## **🧊 7. āļ‚āđ‰āļ­āļāļģāļŦāļ™āļ”āļ”āđ‰āļēāļ™āļāļēāļĢāļ—āļ”āļŠāļ­āļš (Testing Requirements)** - -### **7.1. Unit Testing:** - -- āļ•āđ‰āļ­āļ‡āļĄāļĩ unit tests āļŠāļģāļŦāļĢāļąāļš business logic āļ—āļąāđ‰āļ‡āļŦāļĄāļ” -- Code coverage āļ­āļĒāđˆāļēāļ‡āļ™āđ‰āļ­āļĒ 70% āļŠāļģāļŦāļĢāļąāļš backend services -- āļ•āđ‰āļ­āļ‡āļ—āļ”āļŠāļ­āļš RBAC permission logic āļ—āļļāļāļĢāļ°āļ”āļąāļš - -### **7.2. Integration Testing:** - -- āļ—āļ”āļŠāļ­āļšāļāļēāļĢāļ—āļģāļ‡āļēāļ™āļĢāđˆāļ§āļĄāļāļąāļ™āļ‚āļ­āļ‡ modules -- āļ—āļ”āļŠāļ­āļš database migrations āđāļĨāļ° data integrity -- āļ—āļ”āļŠāļ­āļš API endpoints āļ”āđ‰āļ§āļĒ realistic data - -### **7.3. End-to-End Testing:** - -- āļ—āļ”āļŠāļ­āļš complete user workflows -- āļ—āļ”āļŠāļ­āļš document lifecycle āļˆāļēāļ creation āļ–āļķāļ‡ archival -- āļ—āļ”āļŠāļ­āļš cross-module integrations - -### **7.4. Security Testing:** - -- **Penetration Testing:** āļ—āļ”āļŠāļ­āļš OWASP Top 10 vulnerabilities -- **Security Audit:** Review code āļŠāļģāļŦāļĢāļąāļš security flaws -- **Virus Scanning Test:** āļ—āļ”āļŠāļ­āļš file upload security -- **Rate Limiting Test:** āļ—āļ”āļŠāļ­āļš rate limiting functionality - -### **7.5. Performance Testing:** - -- **Load Testing:** āļ—āļ”āļŠāļ­āļšāļ”āđ‰āļ§āļĒ realistic workloads -- **Stress Testing:** āļŦāļē breaking points āļ‚āļ­āļ‡āļĢāļ°āļšāļš -- **Endurance Testing:** āļ—āļ”āļŠāļ­āļšāļāļēāļĢāļ—āļģāļ‡āļēāļ™āļ•āđˆāļ­āđ€āļ™āļ·āđˆāļ­āļ‡āđ€āļ›āđ‡āļ™āđ€āļ§āļĨāļēāļ™āļēāļ™ - -### **7.6. Disaster Recovery Testing:** - -- āļ—āļ”āļŠāļ­āļš backup āđāļĨāļ° restoration procedures -- āļ—āļ”āļŠāļ­āļš failover mechanisms -- āļ—āļ”āļŠāļ­āļš data integrity āļŦāļĨāļąāļ‡āļāļēāļĢ recovery - -### **7.7 Specific Scenario Testing (āđ€āļžāļīāđˆāļĄ)** - -- **Race Condition Test:** āļ—āļ”āļŠāļ­āļšāļĒāļīāļ‡ Request āļ‚āļ­āđ€āļĨāļ‚āļ—āļĩāđˆāđ€āļ­āļāļŠāļēāļĢāļžāļĢāđ‰āļ­āļĄāļāļąāļ™ 100 Request -- **Transaction Test:** āļ—āļ”āļŠāļ­āļšāļ›āļīāļ”āđ€āļ™āđ‡āļ•āļĢāļ°āļŦāļ§āđˆāļēāļ‡ Upload āđ„āļŸāļĨāđŒ (āļ•āļĢāļ§āļˆāļŠāļ­āļšāļ§āđˆāļēāđ„āļĄāđˆāļĄāļĩ Orphan File āļŦāļĢāļ·āļ­ Broken Link) -- **Permission Test:** āļ—āļ”āļŠāļ­āļš CASL Integration āļ—āļąāđ‰āļ‡āļāļąāđˆāļ‡ Backend āđāļĨāļ° Frontend āđƒāļŦāđ‰āļ•āļĢāļ‡āļāļąāļ™ - -## **8. āļ‚āđ‰āļ­āļāļģāļŦāļ™āļ”āļ”āđ‰āļēāļ™āļāļēāļĢāļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļē (Maintenance Requirements)** - -### **8.1. Log Retention:** - -- Audit logs: 7 āļ›āļĩ -- Application logs: 1 āļ›āļĩ -- Performance metrics: 2 āļ›āļĩ - -### **8.2. Monitoring āđāļĨāļ° Alerting:** - -- āļ•āđ‰āļ­āļ‡āļĄāļĩ proactive monitoring āļŠāļģāļŦāļĢāļąāļš critical systems -- āļ•āđ‰āļ­āļ‡āļĄāļĩ alerting āļŠāļģāļŦāļĢāļąāļš security incidents -- āļ•āđ‰āļ­āļ‡āļĄāļĩ performance degradation alerts - -### **8.3. Patch Management:** - -- āļ•āđ‰āļ­āļ‡āļĄāļĩ process āļŠāļģāļŦāļĢāļąāļš security patches -- āļ•āđ‰āļ­āļ‡āļ—āļ”āļŠāļ­āļš patches āđƒāļ™ staging environment -- āļ•āđ‰āļ­āļ‡āļĄāļĩ rollback plan āļŠāļģāļŦāļĢāļąāļš failed updates - -### **8.4. Capacity Planning:** - -- āļ•āđ‰āļ­āļ‡ monitor resource utilization -- āļ•āđ‰āļ­āļ‡āļĄāļĩ scaling strategy āļŠāļģāļŦāļĢāļąāļš growth -- āļ•āđ‰āļ­āļ‡āļĄāļĩ performance baselines āđāļĨāļ° trending - -## **9. āļ‚āđ‰āļ­āļāļģāļŦāļ™āļ”āļ”āđ‰āļēāļ™āļāļēāļĢāļ›āļāļīāļšāļąāļ•āļīāļ•āļēāļĄāļāļŽāļĢāļ°āđ€āļšāļĩāļĒāļš (Compliance Requirements)** - -### **9.1. Data Privacy:** - -- āļ•āđ‰āļ­āļ‡āļ›āļāļīāļšāļąāļ•āļīāļ•āļēāļĄāļāļŽāļŦāļĄāļēāļĒāļ„āļļāđ‰āļĄāļ„āļĢāļ­āļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļŠāđˆāļ§āļ™āļšāļļāļ„āļ„āļĨ -- āļ•āđ‰āļ­āļ‡āļĄāļĩ data retention policies -- āļ•āđ‰āļ­āļ‡āļĄāļĩ data deletion procedures - -### **9.2. Audit Compliance:** - -- āļ•āđ‰āļ­āļ‡āļĢāļ­āļ‡āļĢāļąāļš internal āđāļĨāļ° external audits -- āļ•āđ‰āļ­āļ‡āļĄāļĩ comprehensive audit trails -- āļ•āđ‰āļ­āļ‡āļĄāļĩ reporting capabilities āļŠāļģāļŦāļĢāļąāļš compliance - -### **9.3. Security Standards:** - -- āļ•āđ‰āļ­āļ‡āļ›āļāļīāļšāļąāļ•āļīāļ•āļēāļĄ organizational security policies -- āļ•āđ‰āļ­āļ‡āļĄāļĩ security incident response plan -- āļ•āđ‰āļ­āļ‡āļĄāļĩ regular security assessments - -## **📋 āļŠāļĢāļļāļ›āļāļēāļĢāļ›āļĢāļąāļšāļ›āļĢāļļāļ‡āļˆāļēāļāđ€āļ§āļ­āļĢāđŒāļŠāļąāļ™āļāđˆāļ­āļ™āļŦāļ™āđ‰āļē** - -### **Security Enhancements:** - -1. **File Upload Security** - Virus scanning, file type validation, access controls -2. **Input Validation** - OWASP Top 10 protection, XSS/CSRF prevention -3. **Rate Limiting** - Comprehensive rate limiting strategy -4. **Secrets Management** - Secure handling of sensitive configuration - -### **Architecture Improvements:** - -1. **Document Numbering** - Changed from Stored Procedure to Application-level Locking with Optimistic Locking safety net -2. **Resilience Patterns** - Circuit breaker, retry mechanisms, fallback strategies -3. **Monitoring & Observability** - Health checks, metrics, distributed tracing -4. **Caching Strategy** - Comprehensive caching with proper invalidation -5. **Two-Phase File Storage** - Temp -> Permanent storage with transaction safety -6. **Unified Workflow Engine** - Consolidated routing logic for better maintainability - -### **Performance Targets:** - -1. **API Response Time** - < 200ms (90th percentile) -2. **Search Performance** - < 500ms -3. **File Upload** - < 30 seconds for 50MB files -4. **Cache Hit Ratio** - > 80% - -### **Operational Excellence:** - -1. **Disaster Recovery** - RTO < 4 hours, RPO < 1 hour -2. **Backup Procedures** - Comprehensive backup and restoration -3. **Security Testing** - Penetration testing and security audits -4. **Performance Testing** - Load testing with realistic workloads -5. **Maintenance Mode** - Graceful system maintenance capabilities - -### **User Experience Improvements:** - -1. **Dynamic Form Generator** - Reduced code duplication and better schema support -2. **Mobile Responsiveness** - Card view for tables on mobile devices -3. **Auto-Save Draft** - LocalStorage integration for form resilience -4. **Notification Digest** - Reduced notification spam - -### **Data Management:** - -1. **Virtual Columns** - Improved JSON field search performance -2. **Table Partitioning** - Support for large-scale data growth -3. **Idempotency Keys** - Prevention of duplicate transactions - -āđ€āļ­āļāļŠāļēāļĢāļ™āļĩāđ‰āļŠāļ°āļ—āđ‰āļ­āļ™āļ–āļķāļ‡āļ„āļ§āļēāļĄāļĄāļļāđˆāļ‡āļĄāļąāđˆāļ™āđƒāļ™āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡āļĢāļ°āļšāļšāļ—āļĩāđˆāļĄāļĩāļ„āļ§āļēāļĄāļ›āļĨāļ­āļ”āļ āļąāļĒ, āļĄāļĩāļ„āļ§āļēāļĄāļ—āļ™āļ—āļēāļ™, āđāļĨāļ°āļĄāļĩāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāļŠāļđāļ‡ āļžāļĢāđ‰āļ­āļĄāļĢāļ­āļ‡āļĢāļąāļšāļāļēāļĢāđ€āļ•āļīāļšāđ‚āļ•āđƒāļ™āļ­āļ™āļēāļ„āļ•āđāļĨāļ°āļ„āļ§āļēāļĄāļ•āđ‰āļ­āļ‡āļāļēāļĢāļ—āļēāļ‡āļ˜āļļāļĢāļāļīāļˆāļ—āļĩāđˆāđ€āļ›āļĨāļĩāđˆāļĒāļ™āđāļ›āļĨāļ‡āđ„āļ› - -**āļŦāļĄāļēāļĒāđ€āļŦāļ•āļļ:** Requirements āļ™āļĩāđ‰āļˆāļ°āļ–āļđāļāļ—āļšāļ—āļ§āļ™āđāļĨāļ°āļ›āļĢāļąāļšāļ›āļĢāļļāļ‡āđ€āļ›āđ‡āļ™āļĢāļ°āļĒāļ°āļ•āļēāļĄ feedback āļˆāļēāļāļ—āļĩāļĄāļžāļąāļ’āļ™āļēāđāļĨāļ°āļ„āļ§āļēāļĄāļ•āđ‰āļ­āļ‡āļāļēāļĢāļ—āļēāļ‡āļ˜āļļāļĢāļāļīāļˆāļ—āļĩāđˆāđ€āļ›āļĨāļĩāđˆāļĒāļ™āđāļ›āļĨāļ‡āđ„āļ› - -## **Document Control:** - -- **Document:** Application Requirements Specification v1.4.2 -- **Version:** 1.4 -- **Date:** 2025-11-19 -- **Author:** NAP LCBP3-DMS & Gemini -- **Status:** FINAL -- **Classification:** Internal Technical Documentation -- **Approved By:** Nattanin - ---- - -`End of Requirements Specification v1.4.2` diff --git a/Documnets/Project/1.4.2/1_FullStackJS_V1_4_2..md b/Documnets/Project/1.4.2/1_FullStackJS_V1_4_2..md deleted file mode 100644 index c4cf1b1..0000000 --- a/Documnets/Project/1.4.2/1_FullStackJS_V1_4_2..md +++ /dev/null @@ -1,970 +0,0 @@ -# 📝 **Documents Management System Version 1.4.2: āđāļ™āļ§āļ—āļēāļ‡āļāļēāļĢāļžāļąāļ’āļ™āļē FullStackJS** - -**āļŠāļ–āļēāļ™āļ°:** FINAL GUIDELINE -**āļ§āļąāļ™āļ—āļĩāđˆ:** 2025-11-19 -**āļ­āđ‰āļēāļ‡āļ­āļīāļ‡:** Requirements Specification v1.4.2 -**Classification:** Internal Technical Documentation - -## 🧠 **1. āļ›āļĢāļąāļŠāļāļēāļ—āļąāđˆāļ§āđ„āļ› (General Philosophy)** - -āđāļ™āļ§āļ—āļēāļ‡āļ›āļāļīāļšāļąāļ•āļīāļ—āļĩāđˆāļ”āļĩāļ—āļĩāđˆāļŠāļļāļ”āđāļšāļšāļ„āļĢāļšāļ§āļ‡āļˆāļĢāļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļžāļąāļ’āļ™āļē NestJS Backend, NextJS Frontend āđāļĨāļ° Tailwind-based UI/UX āđƒāļ™āļŠāļ āļēāļžāđāļ§āļ”āļĨāđ‰āļ­āļĄ TypeScript āļĄāļļāđˆāļ‡āđ€āļ™āđ‰āļ™āļ—āļĩāđˆ **"Data Integrity First"** (āļ„āļ§āļēāļĄāļ–āļđāļāļ•āđ‰āļ­āļ‡āļ‚āļ­āļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļ•āđ‰āļ­āļ‡āļĄāļēāļāđˆāļ­āļ™) āļ•āļēāļĄāļ”āđ‰āļ§āļĒ Security āđāļĨāļ° UX - -* **āļ„āļ§āļēāļĄāļŠāļąāļ”āđ€āļˆāļ™ (clarity), āļ„āļ§āļēāļĄāļ‡āđˆāļēāļĒāđƒāļ™āļāļēāļĢāļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļē (maintainability), āļ„āļ§āļēāļĄāļŠāļ­āļ”āļ„āļĨāđ‰āļ­āļ‡āļāļąāļ™ (consistency) āđāļĨāļ° āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āđ„āļ”āđ‰ (accessibility)** āļ•āļĨāļ­āļ”āļ—āļąāđ‰āļ‡āļŠāđāļ•āđ‡āļ -* **Strict Typing:** āđƒāļŠāđ‰ TypeScript āļ­āļĒāđˆāļēāļ‡āđ€āļ„āļĢāđˆāļ‡āļ„āļĢāļąāļ” āļŦāđ‰āļēāļĄ `any` -* **Consistency:** āđƒāļŠāđ‰āļ āļēāļĐāļēāļ­āļąāļ‡āļāļĪāļĐāđƒāļ™ Code / āļ āļēāļĐāļēāđ„āļ—āļĒāđƒāļ™ Comment -* **Resilience:** āļĢāļ°āļšāļšāļ•āđ‰āļ­āļ‡āļ—āļ™āļ—āļēāļ™āļ•āđˆāļ­ Network Failure āđāļĨāļ° Race Condition - -## ⚙ïļ **2. āđāļ™āļ§āļ—āļēāļ‡āļ—āļąāđˆāļ§āđ„āļ›āļŠāļģāļŦāļĢāļąāļš TypeScript** - -### **2.1 āļŦāļĨāļąāļāļāļēāļĢāļžāļ·āđ‰āļ™āļāļēāļ™** - -* āđƒāļŠāđ‰ **āļ āļēāļĐāļēāļ­āļąāļ‡āļāļĪāļĐ** āļŠāļģāļŦāļĢāļąāļšāđ‚āļ„āđ‰āļ” -* āđƒāļŠāđ‰ **āļ āļēāļĐāļēāđ„āļ—āļĒ** āļŠāļģāļŦāļĢāļąāļš comment āđāļĨāļ°āđ€āļ­āļāļŠāļēāļĢāļ—āļąāđ‰āļ‡āļŦāļĄāļ” -* āļāļģāļŦāļ™āļ”āđ„āļ—āļ›āđŒ (type) āļ­āļĒāđˆāļēāļ‡āļŠāļąāļ”āđ€āļˆāļ™āļŠāļģāļŦāļĢāļąāļšāļ•āļąāļ§āđāļ›āļĢ, āļžāļēāļĢāļēāļĄāļīāđ€āļ•āļ­āļĢāđŒ āđāļĨāļ°āļ„āđˆāļēāļ—āļĩāđˆāļŠāđˆāļ‡āļāļĨāļąāļš (return values) āļ—āļąāđ‰āļ‡āļŦāļĄāļ” -* āļŦāļĨāļĩāļāđ€āļĨāļĩāđˆāļĒāļ‡āļāļēāļĢāđƒāļŠāđ‰ any; āđƒāļŦāđ‰āļŠāļĢāđ‰āļēāļ‡āđ„āļ—āļ›āđŒ (types) āļŦāļĢāļ·āļ­āļ­āļīāļ™āđ€āļ—āļ­āļĢāđŒāđ€āļŸāļ‹ (interfaces) āļ—āļĩāđˆāļāļģāļŦāļ™āļ”āđ€āļ­āļ‡ -* āđƒāļŠāđ‰ **JSDoc** āļŠāļģāļŦāļĢāļąāļšāļ„āļĨāļēāļŠ (classes) āđāļĨāļ°āđ€āļĄāļ˜āļ­āļ” (methods) āļ—āļĩāđˆāđ€āļ›āđ‡āļ™ public -* āļŠāđˆāļ‡āļ­āļ­āļ (Export) **āļŠāļąāļāļĨāļąāļāļĐāļ“āđŒāļŦāļĨāļąāļ (main symbol) āđ€āļžāļĩāļĒāļ‡āļŦāļ™āļķāđˆāļ‡āđ€āļ”āļĩāļĒāļ§** āļ•āđˆāļ­āđ„āļŸāļĨāđŒ -* āļŦāļĨāļĩāļāđ€āļĨāļĩāđˆāļĒāļ‡āļšāļĢāļĢāļ—āļąāļ”āļ§āđˆāļēāļ‡āļ āļēāļĒāđƒāļ™āļŸāļąāļ‡āļāđŒāļŠāļąāļ™ -* āļĢāļ°āļšāļļ // File: path/filename āđƒāļ™āļšāļĢāļĢāļ—āļąāļ”āđāļĢāļāļ‚āļ­āļ‡āļ—āļļāļāđ„āļŸāļĨāđŒ -* āļĢāļ°āļšāļļ // āļšāļąāļ™āļ—āļķāļāļāļēāļĢāđāļāđ‰āđ„āļ‚, āļŦāļēāļāļĄāļĩāļāļēāļĢāđāļāđ‰āđ„āļ‚āđ€āļžāļīāđˆāļĄāđƒāļ™āļ­āļ™āļēāļ„āļ• āđƒāļŦāđ‰āđ€āļžāļīāđˆāļĄāļšāļąāļ™āļ—āļķāļ - -### **2.2 Configuration & Secrets Management** - -* **Production/Staging:** āļŦāđ‰āļēāļĄāđƒāļŠāđˆ Secrets (Password, Keys) āđƒāļ™ `docker-compose.yml` āļŦāļĨāļąāļ -* **Development:** āđƒāļŦāđ‰āļŠāļĢāđ‰āļēāļ‡āđ„āļŸāļĨāđŒ `docker-compose.override.yml` (āđ€āļžāļīāđˆāļĄāđƒāļ™ `.gitignore`) āđ€āļžāļ·āđˆāļ­ Inject āļ•āļąāļ§āđāļ›āļĢ Environment āļ—āļĩāđˆāđ€āļ›āđ‡āļ™āļ„āļ§āļēāļĄāļĨāļąāļš -* **Validation:** āđƒāļŠāđ‰ `joi` āļŦāļĢāļ·āļ­ `zod` āđƒāļ™āļāļēāļĢ Validate Environment Variables āļ•āļ­āļ™ Start App āļŦāļēāļāļ‚āļēāļ”āļ•āļąāļ§āđāļ›āļĢāļŠāļģāļ„āļąāļāđƒāļŦāđ‰ Throw Error āļ—āļąāļ™āļ—āļĩ - -### **2.3 Idempotency (āļ„āļ§āļēāļĄāļŠāļēāļĄāļēāļĢāļ–āđƒāļ™āļāļēāļĢāļ—āļģāļ‹āđ‰āļģāđ„āļ”āđ‰)** - -* āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ—āļģāļ‡āļēāļ™āļ—āļĩāđˆāļŠāļģāļ„āļąāļ (Create Document, Approve, Transactional) **āļ•āđ‰āļ­āļ‡** āļ­āļ­āļāđāļšāļšāđƒāļŦāđ‰āđ€āļ›āđ‡āļ™ Idempotent -* Client **āļ•āđ‰āļ­āļ‡** āļŠāđˆāļ‡ Header `Idempotency-Key` (UUID) āļĄāļēāļāļąāļš Request -* Server **āļ•āđ‰āļ­āļ‡** āļ•āļĢāļ§āļˆāļŠāļ­āļšāļ§āđˆāļē Key āļ™āļĩāđ‰āđ€āļ„āļĒāļ–āļđāļāļ›āļĢāļ°āļĄāļ§āļĨāļœāļĨāļŠāļģāđ€āļĢāđ‡āļˆāđ„āļ›āđāļĨāđ‰āļ§āļŦāļĢāļ·āļ­āđ„āļĄāđˆ āļ–āđ‰āļēāđƒāļŠāđˆ āđƒāļŦāđ‰āļ„āļ·āļ™āļ„āđˆāļēāđ€āļ”āļīāļĄāđ‚āļ”āļĒāđ„āļĄāđˆāļ—āļģāļ‹āđ‰āļģ - -### **2.4 āļ‚āđ‰āļ­āļ•āļāļĨāļ‡āđƒāļ™āļāļēāļĢāļ•āļąāđ‰āļ‡āļŠāļ·āđˆāļ­ (Naming Conventions)** - -| Entity (āļŠāļīāđˆāļ‡āļ—āļĩāđˆāļ•āļąāđ‰āļ‡āļŠāļ·āđˆāļ­) | Convention (āļĢāļđāļ›āđāļšāļš) | Example (āļ•āļąāļ§āļ­āļĒāđˆāļēāļ‡) | -| :---- | :---- | :---- | -| Classes | PascalCase | UserService | -| Property | snake_case | user_id | -| Variables & Functions | camelCase | getUserInfo | -| Files & Folders | kebab-case | user-service.ts | -| Environment Variables | UPPERCASE | DATABASE_URL | -| Booleans | Verb + Noun | isActive, canDelete, hasPermission | - -āđƒāļŠāđ‰āļ„āļģāđ€āļ•āđ‡āļĄ â€” āđ„āļĄāđˆāđƒāļŠāđ‰āļ­āļąāļāļĐāļĢāļĒāđˆāļ­ â€” āļĒāļāđ€āļ§āđ‰āļ™āļ„āļģāļĄāļēāļ•āļĢāļāļēāļ™ (āđ€āļŠāđˆāļ™ API, URL, req, res, err, ctx) - -### ðŸ§Đ**2.5 āļŸāļąāļ‡āļāđŒāļŠāļąāļ™ (Functions)** - -* āđ€āļ‚āļĩāļĒāļ™āļŸāļąāļ‡āļāđŒāļŠāļąāļ™āđƒāļŦāđ‰āļŠāļąāđ‰āļ™ āđāļĨāļ°āļ—āļģ **āļŦāļ™āđ‰āļēāļ—āļĩāđˆāđ€āļžāļĩāļĒāļ‡āļ­āļĒāđˆāļēāļ‡āđ€āļ”āļĩāļĒāļ§** (single-purpose) (\< 20 āļšāļĢāļĢāļ—āļąāļ”) -* āđƒāļŠāđ‰ **early returns** āđ€āļžāļ·āđˆāļ­āļĨāļ”āļāļēāļĢāļ‹āđ‰āļ­āļ™ (nesting) āļ‚āļ­āļ‡āđ‚āļ„āđ‰āļ” -* āđƒāļŠāđ‰ **map**, **filter**, **reduce** āđāļ—āļ™āļāļēāļĢāđƒāļŠāđ‰ loops āđ€āļĄāļ·āđˆāļ­āđ€āļŦāļĄāļēāļ°āļŠāļĄ -* āļ„āļ§āļĢāđƒāļŠāđ‰ **arrow functions** āļŠāļģāļŦāļĢāļąāļšāļ•āļĢāļĢāļāļ°āļŠāļąāđ‰āļ™āđ†, āđāļĨāļ°āđƒāļŠāđ‰ **named functions** āđƒāļ™āļāļĢāļ“āļĩāļ­āļ·āđˆāļ™ -* āđƒāļŠāđ‰ **default parameters** āđāļ—āļ™āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļ„āđˆāļē null -* āļˆāļąāļ”āļāļĨāļļāđˆāļĄāļžāļēāļĢāļēāļĄāļīāđ€āļ•āļ­āļĢāđŒāļŦāļĨāļēāļĒāļ•āļąāļ§āđƒāļŦāđ‰āđ€āļ›āđ‡āļ™āļ­āđ‡āļ­āļšāđ€āļˆāļāļ•āđŒāđ€āļ”āļĩāļĒāļ§ (RO-RO pattern) -* āļŠāđˆāļ‡āļ„āđˆāļēāļāļĨāļąāļš (Return) āđ€āļ›āđ‡āļ™āļ­āđ‡āļ­āļšāđ€āļˆāļāļ•āđŒāļ—āļĩāđˆāļĄāļĩāđ„āļ—āļ›āđŒāļāļģāļŦāļ™āļ” (typed objects) āđ„āļĄāđˆāđƒāļŠāđˆāļ„āđˆāļēāļžāļ·āđ‰āļ™āļāļēāļ™ (primitives) -* āļĢāļąāļāļĐāļēāļĢāļ°āļ”āļąāļšāļ‚āļ­āļ‡āļŠāļīāđˆāļ‡āļ—āļĩāđˆāđ€āļ›āđ‡āļ™āļ™āļēāļĄāļ˜āļĢāļĢāļĄ (abstraction level) āđƒāļŦāđ‰āđ€āļ›āđ‡āļ™āļĢāļ°āļ”āļąāļšāđ€āļ”āļĩāļĒāļ§āđƒāļ™āđāļ•āđˆāļĨāļ°āļŸāļąāļ‡āļāđŒāļŠāļąāļ™ - -### ðŸ§ą**2.6 āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļĄāļđāļĨ (Data Handling)** - -* āļŦāđˆāļ­āļŦāļļāđ‰āļĄāļ‚āđ‰āļ­āļĄāļđāļĨ (Encapsulate) āđƒāļ™āđ„āļ—āļ›āđŒāđāļšāļšāļœāļŠāļĄ (composite types) -* āđƒāļŠāđ‰ **immutability** (āļāļēāļĢāđ„āļĄāđˆāđ€āļ›āļĨāļĩāđˆāļĒāļ™āđāļ›āļĨāļ‡āļ„āđˆāļē) āļ”āđ‰āļ§āļĒ readonly āđāļĨāļ° as const -* āļ—āļģāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļ„āļ§āļēāļĄāļ–āļđāļāļ•āđ‰āļ­āļ‡āļ‚āļ­āļ‡āļ‚āđ‰āļ­āļĄāļđāļĨ (Validations) āđƒāļ™āļ„āļĨāļēāļŠāļŦāļĢāļ·āļ­ DTOs āđ„āļĄāđˆāđƒāļŠāđˆāļ āļēāļĒāđƒāļ™āļŸāļąāļ‡āļāđŒāļŠāļąāļ™āļ—āļēāļ‡āļ˜āļļāļĢāļāļīāļˆ -* āļ•āļĢāļ§āļˆāļŠāļ­āļšāļ„āļ§āļēāļĄāļ–āļđāļāļ•āđ‰āļ­āļ‡āļ‚āļ­āļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāđ‚āļ”āļĒāđƒāļŠāđ‰ DTOs āļ—āļĩāđˆāļĄāļĩāđ„āļ—āļ›āđŒāļāļģāļŦāļ™āļ”āđ€āļŠāļĄāļ­ - -### 🧰**2.7 āļ„āļĨāļēāļŠ (Classes)** - -* āļ›āļāļīāļšāļąāļ•āļīāļ•āļēāļĄāļŦāļĨāļąāļāļāļēāļĢ **SOLID** -* āļ„āļ§āļĢāđƒāļŠāđ‰ **composition āļĄāļēāļāļāļ§āđˆāļē inheritance** (Prefer composition over inheritance) -* āļāļģāļŦāļ™āļ” **interfaces** āļŠāļģāļŦāļĢāļąāļšāļŠāļąāļāļāļē (contracts) -* āđƒāļŦāđ‰āļ„āļĨāļēāļŠāļĄāļļāđˆāļ‡āđ€āļ™āđ‰āļ™āļāļēāļĢāļ—āļģāļ‡āļēāļ™āđ€āļ‰āļžāļēāļ°āļ­āļĒāđˆāļēāļ‡āđāļĨāļ°āļĄāļĩāļ‚āļ™āļēāļ”āđ€āļĨāđ‡āļ (\< 200 āļšāļĢāļĢāļ—āļąāļ”, \< 10 āđ€āļĄāļ˜āļ­āļ”, \< 10 properties) - -### ðŸšĻ**2.8 āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ” (Error Handling)** - -* āđƒāļŠāđ‰ Exceptions āļŠāļģāļŦāļĢāļąāļšāļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļ—āļĩāđˆāđ„āļĄāđˆāļ„āļēāļ”āļ„āļīāļ” -* āļ”āļąāļāļˆāļąāļš (Catch) āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āđ€āļžāļ·āđˆāļ­āđāļāđ‰āđ„āļ‚āļŦāļĢāļ·āļ­āđ€āļžāļīāđˆāļĄāļšāļĢāļīāļšāļ— (context) āđ€āļ—āđˆāļēāļ™āļąāđ‰āļ™; āļŦāļēāļāđ„āļĄāđˆāđ€āļŠāđˆāļ™āļ™āļąāđ‰āļ™ āđƒāļŦāđ‰āđƒāļŠāđ‰ global error handlers -* āļĢāļ°āļšāļļāļ‚āđ‰āļ­āļ„āļ§āļēāļĄāļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ” (error messages) āļ—āļĩāđˆāļĄāļĩāļ„āļ§āļēāļĄāļŦāļĄāļēāļĒāđ€āļŠāļĄāļ­ - -### 🧊**2.9 āļāļēāļĢāļ—āļ”āļŠāļ­āļš (āļ—āļąāđˆāļ§āđ„āļ›) (Testing (General))** - -* āđƒāļŠāđ‰āļĢāļđāļ›āđāļšāļš **Arrange–Act–Assert** -* āđƒāļŠāđ‰āļŠāļ·āđˆāļ­āļ•āļąāļ§āđāļ›āļĢāđƒāļ™āļāļēāļĢāļ—āļ”āļŠāļ­āļšāļ—āļĩāđˆāļŠāļ·āđˆāļ­āļ„āļ§āļēāļĄāļŦāļĄāļēāļĒ (inputData, expectedOutput) -* āđ€āļ‚āļĩāļĒāļ™ **unit tests** āļŠāļģāļŦāļĢāļąāļš public methods āļ—āļąāđ‰āļ‡āļŦāļĄāļ” -* āļˆāļģāļĨāļ­āļ‡ (Mock) āļāļēāļĢāļžāļķāđˆāļ‡āļžāļēāļ āļēāļĒāļ™āļ­āļ (external dependencies) -* āđ€āļžāļīāđˆāļĄ **acceptance tests** āļ•āđˆāļ­āđ‚āļĄāļ”āļđāļĨāđ‚āļ”āļĒāđƒāļŠāđ‰āļĢāļđāļ›āđāļšāļš Given–When-Then - -### **Testing Strategy āđ‚āļ”āļĒāļĨāļ°āđ€āļ­āļĩāļĒāļ”** - -* **Test Pyramid Structure** - - /\ - / \ E2E Tests (10%) - /____\ Integration Tests (20%) - / \ Unit Tests (70%) -/________\ - -* **Testing Tools Stack** - -```typescript -// Backend Testing Stack -const backendTesting = { - unit: ['Jest', 'ts-jest', '@nestjs/testing'], - integration: ['Supertest', 'Testcontainers', 'Jest'], - e2e: ['Supertest', 'Jest', 'Database Seeds'], - security: ['Jest', 'Custom Security Test Helpers'], - performance: ['Jest', 'autocannon', 'artillery'] -}; - -// Frontend Testing Stack -const frontendTesting = { - unit: ['Vitest', 'React Testing Library'], - integration: ['React Testing Library', 'MSW'], - e2e: ['Playwright', 'Jest'], - visual: ['Playwright', 'Loki'] -}; -``` - -* **Test Data Management** - -```typescript -// Test Data Factories -interface TestDataFactory { - createUser(overrides?: Partial): User; - createCorrespondence(overrides?: Partial): Correspondence; - createRoutingTemplate(overrides?: Partial): RoutingTemplate; -} - -// Test Scenarios -const testScenarios = { - happyPath: 'Normal workflow execution', - edgeCases: 'Boundary conditions and limits', - errorConditions: 'Error handling and recovery', - security: 'Authentication and authorization', - performance: 'Load and stress conditions' -}; -``` - -## 🏗ïļ **3. āđāļšāđ‡āļāđ€āļ­āļ™āļ”āđŒ (NestJS) - Implementation Details** - -### **3.1 āļŦāļĨāļąāļāļāļēāļĢ** - -* **āļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄāđāļšāļšāđ‚āļĄāļ”āļđāļĨāļēāļĢāđŒ (Modular architecture)**: - * āļŦāļ™āļķāđˆāļ‡āđ‚āļĄāļ”āļđāļĨāļ•āđˆāļ­āļŦāļ™āļķāđˆāļ‡āđ‚āļ”āđ€āļĄāļ™ - * āđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡āđāļšāļš Controller → Service → Repository (Model) -* API-First: āļĄāļļāđˆāļ‡āđ€āļ™āđ‰āļ™āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡ API āļ—āļĩāđˆāļĄāļĩāļ„āļļāļ“āļ āļēāļžāļŠāļđāļ‡ āļĄāļĩāđ€āļ­āļāļŠāļēāļĢāļ›āļĢāļ°āļāļ­āļš (Swagger) āļ—āļĩāđˆāļŠāļąāļ”āđ€āļˆāļ™āļŠāļģāļŦāļĢāļąāļš Frontend Team -* DTOs āļ—āļĩāđˆāļ•āļĢāļ§āļˆāļŠāļ­āļšāļ„āļ§āļēāļĄāļ–āļđāļāļ•āđ‰āļ­āļ‡āļ”āđ‰āļ§āļĒ **class-validator** -* āđƒāļŠāđ‰ **MikroORM** (āļŦāļĢāļ·āļ­ TypeORM/Prisma) āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ„āļ‡āļ­āļĒāļđāđˆāļ‚āļ­āļ‡āļ‚āđ‰āļ­āļĄāļđāļĨ (persistence) āļ‹āļķāđˆāļ‡āļŠāļ­āļ”āļ„āļĨāđ‰āļ­āļ‡āļāļąāļšāļŠāļ„āļĩāļĄāļē MariaDB -* āļŦāđˆāļ­āļŦāļļāđ‰āļĄāđ‚āļ„āđ‰āļ”āļ—āļĩāđˆāđƒāļŠāđ‰āļ‹āđ‰āļģāđ„āļ”āđ‰āđ„āļ§āđ‰āđƒāļ™ **common module** (@app/common): - * Configs, decorators, DTOs, guards, interceptors, notifications, shared services, types, validators - -### **3.2 Database & Data Modeling (MariaDB + TypeORM)** - -#### **3.2.1 Optimistic Locking & Versioning** - -āđ€āļžāļ·āđˆāļ­āļ›āđ‰āļ­āļ‡āļāļąāļ™ Race Condition āđƒāļ™āļāļēāļĢāđāļāđ‰āđ„āļ‚āļ‚āđ‰āļ­āļĄāļđāļĨāļžāļĢāđ‰āļ­āļĄāļāļąāļ™ (āđ‚āļ”āļĒāđ€āļ‰āļžāļēāļ°āļāļēāļĢāļĢāļąāļ™āđ€āļĨāļ‚āļ—āļĩāđˆāđ€āļ­āļāļŠāļēāļĢ) āđƒāļŦāđ‰āđ€āļžāļīāđˆāļĄ Column `@VersionColumn()` āđƒāļ™ Entity āļ—āļĩāđˆāļŠāļģāļ„āļąāļ - -```typescript -@Entity() -export class DocumentCounter { - // ... fields - @Column() - last_number: number; - - @VersionColumn() // āđ€āļžāļīāđˆāļĄ Versioning - version: number; -} -``` - -#### **3.2.2 Virtual Columns for JSON Performance** - -āđ€āļ™āļ·āđˆāļ­āļ‡āļˆāļēāļāđ€āļĢāļēāđƒāļŠāđ‰ MariaDB 10.11 āđāļĨāļ°āļĄāļĩāļāļēāļĢāđ€āļāđ‡āļšāļ‚āđ‰āļ­āļĄāļđāļĨ JSON (Details) āđƒāļŦāđ‰āđƒāļŠāđ‰ **Generated Columns (Virtual)** āļŠāļģāļŦāļĢāļąāļš Field āļ—āļĩāđˆāļ•āđ‰āļ­āļ‡ Search/Sort āļšāđˆāļ­āļĒāđ† āđāļĨāļ°āļ—āļģ Index āļšāļ™ Virtual Column āļ™āļąāđ‰āļ™ - -```sql --- āļ•āļąāļ§āļ­āļĒāđˆāļēāļ‡ SQL Migration -ALTER TABLE correspondence_revisions -ADD COLUMN ref_project_id INT GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(details, '$.projectId'))) VIRTUAL; -CREATE INDEX idx_ref_project_id ON correspondence_revisions(ref_project_id); -``` - -#### **3.2.3 Partitioning Strategy** - -āļŠāļģāļŦāļĢāļąāļšāļ•āļēāļĢāļēāļ‡ `audit_logs` āđāļĨāļ° `notifications` āđƒāļŦāđ‰āđ€āļ•āļĢāļĩāļĒāļĄāļ­āļ­āļāđāļšāļš Entity āđƒāļŦāđ‰āļĢāļ­āļ‡āļĢāļąāļš Partitioning (āđ€āļŠāđˆāļ™ āđāļĒāļāļ•āļēāļĄāļ›āļĩ) āđ‚āļ”āļĒāđƒāļŠāđ‰ Raw SQL Migration āđƒāļ™āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡āļ•āļēāļĢāļēāļ‡ - -### **3.3 File Storage Service (Two-Phase Storage)** - -āļ›āļĢāļąāļšāļ›āļĢāļļāļ‡ Service āļˆāļąāļ”āļāļēāļĢāđ„āļŸāļĨāđŒāđƒāļŦāđ‰āļĢāļ­āļ‡āļĢāļąāļš Transactional Integrity - -1. **Upload (Phase 1):** - * āļĢāļąāļšāđ„āļŸāļĨāđŒ → Scan Virus (ClamAV) → Save āļĨāļ‡āđ‚āļŸāļĨāđ€āļ”āļ­āļĢāđŒ `temp/` - * Return `temp_id` āđāļĨāļ° Metadata āļāļĨāļąāļšāđ„āļ›āđƒāļŦāđ‰ Client -2. **Commit (Phase 2):** - * āđ€āļĄāļ·āđˆāļ­ Business Logic (āđ€āļŠāđˆāļ™ Create Correspondence) āļ—āļģāļ‡āļēāļ™āļŠāļģāđ€āļĢāđ‡āļˆ - * Service āļˆāļ°āļĒāđ‰āļēāļĒāđ„āļŸāļĨāđŒāļˆāļēāļ `temp/` āđ„āļ›āļĒāļąāļ‡ `permanent/{YYYY}/{MM}/` - * Update path āđƒāļ™ Database - * āļ—āļąāđ‰āļ‡āļŦāļĄāļ”āļ™āļĩāđ‰āļ•āđ‰āļ­āļ‡āļ­āļĒāļđāđˆāļ āļēāļĒāđƒāļ•āđ‰ Database Transaction āđ€āļ”āļĩāļĒāļ§āļāļąāļ™ (āļ–āđ‰āļē DB Fail, āđ„āļŸāļĨāđŒāļˆāļ°āļ„āđ‰āļēāļ‡āļ—āļĩāđˆ Temp āđāļĨāļ°āļ–āļđāļāļĨāļšāđ‚āļ”āļĒ Cron Job) - -### **3.4 Document Numbering (Double-Lock Mechanism)** - -āļāļēāļĢāļ­āļ­āļāđ€āļĨāļ‚āļ—āļĩāđˆāđ€āļ­āļāļŠāļēāļĢāļ•āđ‰āļ­āļ‡āđƒāļŠāđ‰āļāļĨāđ„āļāļ„āļ§āļēāļĄāļ›āļĨāļ­āļ”āļ āļąāļĒ 2 āļŠāļąāđ‰āļ™: - -1. **Layer 1 (Redis Lock):** āđƒāļŠāđ‰ `redlock` āđ€āļžāļ·āđˆāļ­ Block āđ„āļĄāđˆāđƒāļŦāđ‰ Process āļ­āļ·āđˆāļ™āđ€āļ‚āđ‰āļēāļĄāļēāļĒāļļāđˆāļ‡āļāļąāļš Counter āļ‚āļ­āļ‡ Project/Type āļ™āļąāđ‰āļ™āđ† āļŠāļąāđˆāļ§āļ„āļĢāļēāļ§ -2. **Layer 2 (Optimistic Lock):** āļ•āļ­āļ™ Update Database āđƒāļŦāđ‰āđ€āļŠāđ‡āļ„ `version` āļ–āđ‰āļē version āđ€āļ›āļĨāļĩāđˆāļĒāļ™ (āđāļŠāļ”āļ‡āļ§āđˆāļē Redis Lock āļŦāļĨāļļāļ”āļŦāļĢāļ·āļ­āļĄāļĩāļ„āļ™āđāļ—āļĢāļ) āđƒāļŦāđ‰ Throw Error āđāļĨāļ° Retry āđƒāļŦāļĄāđˆ - -### **3.5 Unified Workflow Engine** - -āļŦāđ‰āļēāļĄāđāļĒāļ Logic āļĢāļ°āļŦāļ§āđˆāļēāļ‡ `CorrespondenceRouting` āđāļĨāļ° `RfaWorkflow` āļ­āļ­āļāļˆāļēāļāļāļąāļ™āđ€āļ”āđ‡āļ”āļ‚āļēāļ” āđƒāļŦāđ‰āļŠāļĢāđ‰āļēāļ‡ `WorkflowEngineService` āļ—āļĩāđˆāđ€āļ›āđ‡āļ™ Generic: - -* **Input:** `currentState`, `action`, `rules (Guard)` -* **Output:** `nextState`, `assignee` -* āļĢāļ­āļ‡āļĢāļąāļšāļ—āļąāđ‰āļ‡ Linear Flow (Routing) āđāļĨāļ° Complex Flow (RFA) āļœāđˆāļēāļ™ Configuration - -### **3.6 āļŸāļąāļ‡āļāđŒāļŠāļąāļ™āļŦāļĨāļąāļ (Core Functionalities)** - -* Global **filters** āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļˆāļąāļ”āļāļēāļĢ exception -* **Middlewares** āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļˆāļąāļ”āļāļēāļĢ request -* **Guards** āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ­āļ™āļļāļāļēāļ• (permissions) āđāļĨāļ° RBAC -* **Interceptors** āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāđāļ›āļĨāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨ response āđāļĨāļ°āļāļēāļĢāļšāļąāļ™āļ—āļķāļ log - -### **3.7 āļ‚āđ‰āļ­āļˆāļģāļāļąāļ”āđƒāļ™āļāļēāļĢ Deploy (QNAP Container Station)** - -* **āļŦāđ‰āļēāļĄāđƒāļŠāđ‰āđ„āļŸāļĨāđŒ .env** āđƒāļ™āļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļē Environment Variables [cite: 2.1] -* āļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļēāļ—āļąāđ‰āļ‡āļŦāļĄāļ” (āđ€āļŠāđˆāļ™ Database connection string, JWT secret) **āļˆāļ°āļ•āđ‰āļ­āļ‡āļ–āļđāļāļāļģāļŦāļ™āļ”āļœāđˆāļēāļ™ Environment Variable āđƒāļ™ docker-compose.yml āđ‚āļ”āļĒāļ•āļĢāļ‡** [cite: 6.5] āļ‹āļķāđˆāļ‡āļˆāļ°āļˆāļąāļ”āļāļēāļĢāļœāđˆāļēāļ™ UI āļ‚āļ­āļ‡ QNAP Container Station [cite: 2.1] - -### **3.8 āļ‚āđ‰āļ­āļˆāļģāļāļąāļ”āļ”āđ‰āļēāļ™āļ„āļ§āļēāļĄāļ›āļĨāļ­āļ”āļ āļąāļĒ (Security Constraints):** - -* **File Upload Security:** āļ•āđ‰āļ­āļ‡āļĄāļĩ virus scanning (ClamAV), file type validation (white-list), āđāļĨāļ° file size limits (50MB) -* **Input Validation:** āļ•āđ‰āļ­āļ‡āļ›āđ‰āļ­āļ‡āļāļąāļ™ OWASP Top 10 vulnerabilities (SQL Injection, XSS, CSRF) -* **Rate Limiting:** āļ•āđ‰āļ­āļ‡ implement rate limiting āļ•āļēāļĄ strategy āļ—āļĩāđˆāļāļģāļŦāļ™āļ” -* **Secrets Management:** āļ•āđ‰āļ­āļ‡āļĄāļĩ mechanism āļŠāļģāļŦāļĢāļąāļšāļˆāļąāļ”āļāļēāļĢ sensitive secrets āļ­āļĒāđˆāļēāļ‡āļ›āļĨāļ­āļ”āļ āļąāļĒ āđāļĄāđ‰āļˆāļ°āđƒāļŠāđ‰ docker-compose.yml - -### **3.9 āđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡āđ‚āļĄāļ”āļđāļĨāļ•āļēāļĄāđ‚āļ”āđ€āļĄāļ™ (Domain-Driven Module Structure)** - -āđ€āļžāļ·āđˆāļ­āđƒāļŦāđ‰āļŠāļ­āļ”āļ„āļĨāđ‰āļ­āļ‡āļāļąāļšāļŠāļ„āļĩāļĄāļē SQL (LCBP3-DMS) āđ€āļĢāļēāļˆāļ°āđƒāļŠāđ‰āđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡āđ‚āļĄāļ”āļđāļĨāđāļšāļš **Domain-Driven (āđāļšāđˆāļ‡āļ•āļēāļĄāļ‚āļ­āļšāđ€āļ‚āļ•āļ˜āļļāļĢāļāļīāļˆ)** āđāļ—āļ™āļāļēāļĢāđāļšāđˆāļ‡āļ•āļēāļĄāļŸāļąāļ‡āļāđŒāļŠāļąāļ™: - -#### 3.9.1 **CommonModule:** - -* āđ€āļāđ‡āļš Services āļ—āļĩāđˆāđƒāļŠāđ‰āļĢāđˆāļ§āļĄāļāļąāļ™ āđ€āļŠāđˆāļ™ DatabaseModule, FileStorageService (āļˆāļąāļ”āļāļēāļĢāđ„āļŸāļĨāđŒāđƒāļ™ QNAP), AuditLogService, NotificationService -* āļˆāļąāļ”āļāļēāļĢ audit_logs -* NotificationService āļ•āđ‰āļ­āļ‡āļĢāļ­āļ‡āļĢāļąāļš Triggers āļ—āļĩāđˆāļĢāļ°āļšāļļāđƒāļ™ Requirement 6.7 [cite: 6.7] - -#### 3.9.2 **AuthModule:** - -* āļˆāļąāļ”āļāļēāļĢāļ°āļāļēāļĢāļĒāļ·āļ™āļĒāļąāļ™āļ•āļąāļ§āļ•āļ™ (JWT, Guards) -* **(āļŠāļģāļ„āļąāļ)** āļ•āđ‰āļ­āļ‡āļĢāļąāļšāļœāļīāļ”āļŠāļ­āļšāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļŠāļīāļ—āļ˜āļīāđŒ **4 āļĢāļ°āļ”āļąāļš** [cite: 4.2]: āļŠāļīāļ—āļ˜āļīāđŒāļĢāļ°āļ”āļąāļšāļĢāļ°āļšāļš (Global Role), āļŠāļīāļ—āļ˜āļīāđŒāļĢāļ°āļ”āļąāļšāļ­āļ‡āļāļĢāļ“āđŒ (Organization Role), āļŠāļīāļ—āļ˜āļīāđŒāļĢāļ°āļ”āļąāļšāđ‚āļ›āļĢāđ€āļˆāļāļ•āđŒ (Project Role), āđāļĨāļ° āļŠāļīāļ—āļ˜āļīāđŒāļĢāļ°āļ”āļąāļšāļŠāļąāļāļāļē (Contract Role) -* **(āļŠāļģāļ„āļąāļ)** āļ•āđ‰āļ­āļ‡āļĄāļĩ API āļŠāļģāļŦāļĢāļąāļš **Admin Panel** āđ€āļžāļ·āđˆāļ­: - * āļŠāļĢāđ‰āļēāļ‡āđāļĨāļ°āļˆāļąāļ”āļāļēāļĢ Role āđāļĨāļ°āļāļēāļĢāļˆāļąāļšāļ„āļđāđˆ Permission āđāļšāļšāđ„āļ”āļ™āļēāļĄāļīāļ [cite: 4.3] - * āđƒāļŦāđ‰ Superadmin āļŠāļĢāđ‰āļēāļ‡ Organizations āđāļĨāļ°āļāļģāļŦāļ™āļ” Org Admin āđ„āļ”āđ‰ [cite: 4.6] - * āđƒāļŦāđ‰ Superadmin/Admin āļˆāļąāļ”āļāļēāļĢ document_number_formats (āļĢāļđāļ›āđāļšāļšāđ€āļĨāļ‚āļ—āļĩāđˆāđ€āļ­āļāļŠāļēāļĢ), document_number_counters (Running Number) [cite: 3.10] - -#### 3.9.3 **UserModule:** - -* āļˆāļąāļ”āļāļēāļĢ users, roles, permissions, global_default_roles, role_permissions, user_roles, user_project_roles -* **(āļŠāļģāļ„āļąāļ)** āļ•āđ‰āļ­āļ‡āļĄāļĩ API āļŠāļģāļŦāļĢāļąāļš **Admin Panel** āđ€āļžāļ·āđˆāļ­: - * āļŠāļĢāđ‰āļēāļ‡āđāļĨāļ°āļˆāļąāļ”āļāļēāļĢ Role āđāļĨāļ°āļāļēāļĢāļˆāļąāļšāļ„āļđāđˆ Permission āđāļšāļšāđ„āļ”āļ™āļēāļĄāļīāļ [cite: 4.3] - -#### 3.9.4 **ProjectModule:** - -* āļˆāļąāļ”āļāļēāļĢ projects, organizations, contracts, project_parties, contract_parties - -#### 3.9.5 **MasterModule:** - -* āļˆāļąāļ”āļāļēāļĢ master data (correspondence_types, rfa_types, rfa_status_codes, rfa_approve_codes, circulation_status_codes, correspondence_types, correspondence_status, tags) [cite: 4.5] - -#### 3.9.6 **CorrespondenceModule (āđ‚āļĄāļ”āļđāļĨāļĻāļđāļ™āļĒāđŒāļāļĨāļēāļ‡):** - -* āļˆāļąāļ”āļāļēāļĢ correspondences, correspondence_revisions, correspondence_tags -* **(āļŠāļģāļ„āļąāļ)** Service āļ™āļĩāđ‰āļ•āđ‰āļ­āļ‡ Inject DocumentNumberingService āđ€āļžāļ·āđˆāļ­āļ‚āļ­āđ€āļĨāļ‚āļ—āļĩāđˆāđ€āļ­āļāļŠāļēāļĢāđƒāļŦāļĄāđˆāļāđˆāļ­āļ™āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡ -* **(āļŠāļģāļ„āļąāļ)** āļ•āļĢāļĢāļāļ°āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡/āļ­āļąāļ›āđ€āļ”āļ• Revision āļˆāļ°āļ­āļĒāļđāđˆāđƒāļ™ Service āļ™āļĩāđ‰ -* āļˆāļąāļ”āļāļēāļĢ correspondence_attachments (āļ•āļēāļĢāļēāļ‡āđ€āļŠāļ·āđˆāļ­āļĄāđ„āļŸāļĨāđŒāđāļ™āļš) -* āļĢāļąāļšāļœāļīāļ”āļŠāļ­āļš Routing **Correspondence Routing** (correspondence_routings, correspondence_routing_template_steps, correspondence_routing_templates, correspondence_status_transitions) āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­āđ€āļ­āļāļŠāļēāļĢāļ—āļąāđˆāļ§āđ„āļ›āļĢāļ°āļŦāļ§āđˆāļēāļ‡āļ­āļ‡āļ„āđŒāļāļĢ - -#### 3.9.7 **RfaModule:** - -* āļˆāļąāļ”āļāļēāļĢ rfas, rfa_revisions, rfa_items -* āļĢāļąāļšāļœāļīāļ”āļŠāļ­āļšāđ€āļ§āļīāļĢāđŒāļāđ‚āļŸāļĨāļ§āđŒ **"RFA Workflows"** (rfa_workflows, rfa_workflow_templates, rfa_workflow_template_steps, rfa_status_transitions) āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ­āļ™āļļāļĄāļąāļ•āļīāđ€āļ­āļāļŠāļēāļĢāļ—āļēāļ‡āđ€āļ—āļ„āļ™āļīāļ„ - -#### 3.9.8 **DrawingModule:** - -* āļˆāļąāļ”āļāļēāļĢ shop_drawings, shop_drawing_revisions, contract_drawings, contract_drawing_volumes, contract_drawing_cats, contract_drawing_sub_cats, shop_drawing_main_categories, shop_drawing_sub_categories, contract_drawing_subcat_cat_maps, shop_drawing_revision_contract_refs -* āļˆāļąāļ”āļāļēāļĢ shop_drawing_revision_attachments āđāļĨāļ° contract_drawing_attachments(āļ•āļēāļĢāļēāļ‡āđ€āļŠāļ·āđˆāļ­āļĄāđ„āļŸāļĨāđŒāđāļ™āļš) - -#### 3.9.9 **CirculationModule:** - -* āļˆāļąāļ”āļāļēāļĢ circulations, circulation_templates, circulation_assignees -* āļˆāļąāļ”āļāļēāļĢ circulation_attachments (āļ•āļēāļĢāļēāļ‡āđ€āļŠāļ·āđˆāļ­āļĄāđ„āļŸāļĨāđŒāđāļ™āļš) -* āļĢāļąāļšāļœāļīāļ”āļŠāļ­āļšāđ€āļ§āļīāļĢāđŒāļāđ‚āļŸāļĨāļ§āđŒ **"Circulations"** (circulation_status_transitions, circulation_template_assignees, circulation_assignees, circulation_recipients, circulation_actions, circulation_action_documents)āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāđ€āļ§āļĩāļĒāļ™āđ€āļ­āļāļŠāļēāļĢ **āļ āļēāļĒāđƒāļ™āļ­āļ‡āļ„āđŒāļāļĢ** - -#### 3.9.10 **TransmittalModule:** - -* āļˆāļąāļ”āļāļēāļĢ transmittals āđāļĨāļ° transmittal_items - -#### 3.9.11 **SearchModule:** - -* āđƒāļŦāđ‰āļšāļĢāļīāļāļēāļĢāļ„āđ‰āļ™āļŦāļēāļ‚āļąāđ‰āļ™āļŠāļđāļ‡ (Advanced Search) [cite: 6.2] āđ‚āļ”āļĒāđƒāļŠāđ‰ **Elasticsearch** āđ€āļžāļ·āđˆāļ­āļĢāļ­āļ‡āļĢāļąāļšāļāļēāļĢāļ„āđ‰āļ™āļŦāļēāđāļšāļš Full-text āļˆāļēāļāļŠāļ·āđˆāļ­āđ€āļĢāļ·āđˆāļ­āļ‡, āļĢāļēāļĒāļĨāļ°āđ€āļ­āļĩāļĒāļ”, āđ€āļĨāļ‚āļ—āļĩāđˆāđ€āļ­āļāļŠāļēāļĢ, āļ›āļĢāļ°āđ€āļ āļ—, āļ§āļąāļ™āļ—āļĩāđˆ, āđāļĨāļ° Tags -* āļĢāļ°āļšāļšāļˆāļ°āđƒāļŠāđ‰ Elasticsearch Engine āđƒāļ™āļāļēāļĢāļˆāļąāļ”āļ—āļģāļ”āļąāļŠāļ™āļĩāđ€āļžāļ·āđˆāļ­āļāļēāļĢāļ„āđ‰āļ™āļŦāļēāļ‚āđ‰āļ­āļĄāļđāļĨāđ€āļŠāļīāļ‡āļĨāļķāļāļˆāļēāļāđ€āļ™āļ·āđ‰āļ­āļŦāļēāļ‚āļ­āļ‡āđ€āļ­āļāļŠāļēāļĢ āđ‚āļ”āļĒāļ‚āđ‰āļ­āļĄāļđāļĨāļˆāļ°āļ–āļđāļāļŠāđˆāļ‡āđ„āļ›āļ—āļģāļ”āļąāļŠāļ™āļĩāļˆāļēāļ Backend (NestJS) āļ—āļļāļāļ„āļĢāļąāđ‰āļ‡āļ—āļĩāđˆāļĄāļĩāļāļēāļĢāļŠāļĢāđ‰āļēāļ‡āļŦāļĢāļ·āļ­āđāļāđ‰āđ„āļ‚āđ€āļ­āļāļŠāļēāļĢ - -#### 3.9.12 **DocumentNumberingModule:** - -* **āļŠāļ–āļēāļ™āļ°:** āđ€āļ›āđ‡āļ™ Module āļ āļēāļĒāđƒāļ™ (Internal Module) āđ„āļĄāđˆāđ€āļ›āļīāļ” API āļŠāļđāđˆāļ āļēāļĒāļ™āļ­āļ -* **āļŦāļ™āđ‰āļēāļ—āļĩāđˆ:** āđƒāļŦāđ‰āļšāļĢāļīāļāļēāļĢ DocumentNumberingService āļ—āļĩāđˆ Module āļ­āļ·āđˆāļ™ (āđ€āļŠāđˆāļ™ CorrespondenceModule) āļˆāļ° Inject āđ„āļ›āđƒāļŠāđ‰āļ‡āļēāļ™ -* **āļ•āļĢāļĢāļāļ°:** āļĢāļąāļšāļœāļīāļ”āļŠāļ­āļšāļāļēāļĢāļŠāļĢāđ‰āļēāļ‡āđ€āļĨāļ‚āļ—āļĩāđˆāđ€āļ­āļāļŠāļēāļĢāđ‚āļ”āļĒāđƒāļŠāđ‰ **Redis distributed locking** āđāļ—āļ™ stored procedure -* **Features:** - * Application-level locking āđ€āļžāļ·āđˆāļ­āļ›āđ‰āļ­āļ‡āļāļąāļ™ race condition - * Retry mechanism āļ”āđ‰āļ§āļĒ exponential backoff - * Fallback mechanism āđ€āļĄāļ·āđˆāļ­āļāļēāļĢāļ‚āļ­āđ€āļĨāļ‚āļĨāđ‰āļĄāđ€āļŦāļĨāļ§ - * Audit log āļ—āļļāļāļ„āļĢāļąāđ‰āļ‡āļ—āļĩāđˆāļĄāļĩāļāļēāļĢ generate āđ€āļĨāļ‚āļ—āļĩāđˆāđ€āļ­āļāļŠāļēāļĢāđƒāļŦāļĄāđˆ - -#### 3.9.13 **CorrespondenceRoutingModule:** - -* **āļŠāļ–āļēāļ™āļ°:** āđ‚āļĄāļ”āļđāļĨāļŦāļĨāļąāļāļŠāļģāļŦāļĢāļąāļšāļˆāļąāļ”āļāļēāļĢāļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­āđ€āļ­āļāļŠāļēāļĢ -* **āļŦāļ™āđ‰āļēāļ—āļĩāđˆ:** āļˆāļąāļ”āļāļēāļĢāđāļĄāđˆāđāļšāļšāļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­āđāļĨāļ°āļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­āļˆāļĢāļīāļ‡ -* **Entities:** - * CorrespondenceRoutingTemplate - * CorrespondenceRoutingTemplateStep - * CorrespondenceRouting -* **Features:** - * āļŠāļĢāđ‰āļēāļ‡āđāļĨāļ°āļˆāļąāļ”āļāļēāļĢāđāļĄāđˆāđāļšāļšāļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­ - * āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­āđ€āļ­āļāļŠāļēāļĢāļ•āļēāļĄāđāļĄāđˆāđāļšāļš - * āļ•āļīāļ”āļ•āļēāļĄāļŠāļ–āļēāļ™āļ°āļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­ - * āļ„āļģāļ™āļ§āļ“āļ§āļąāļ™āļ„āļĢāļšāļāļģāļŦāļ™āļ”āļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī - * āļŠāđˆāļ‡āļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āđ€āļĄāļ·āđˆāļ­āļĄāļĩāļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­āđƒāļŦāļĄāđˆ - -#### 3.9.14 **WorkflowEngineModule:** - -* **āļŠāļ–āļēāļ™āļ°:** Internal Module āļŠāļģāļŦāļĢāļąāļšāļˆāļąāļ”āļāļēāļĢ workflow logic -* **āļŦāļ™āđ‰āļēāļ—āļĩāđˆ:** āļ›āļĢāļ°āļĄāļ§āļĨāļœāļĨ state transitions āđāļĨāļ° business rules -* **Features:** - * State machine āļŠāļģāļŦāļĢāļąāļšāļŠāļ–āļēāļ™āļ°āđ€āļ­āļāļŠāļēāļĢ - * Validation rules āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāđ€āļ›āļĨāļĩāđˆāļĒāļ™āļŠāļ–āļēāļ™āļ° - * Automatic status updates - * Deadline management āđāļĨāļ° escalation - -#### 3.9.15 **JsonSchemaModule:** - -* **āļŠāļ–āļēāļ™āļ°:** Internal Module āļŠāļģāļŦāļĢāļąāļšāļˆāļąāļ”āļāļēāļĢ JSON schemas -* **āļŦāļ™āđ‰āļēāļ—āļĩāđˆ:** Validate, transform, āđāļĨāļ° manage JSON data structures -* **Features:** - * JSON schema validation āļ”āđ‰āļ§āļĒ AJV - * Schema versioning āđāļĨāļ° migration - * Dynamic schema generation - * Data transformation āđāļĨāļ° sanitization - -#### 3.9.16 **DetailsService:** - -* **āļŠāļ–āļēāļ™āļ°:** Shared Service āļŠāļģāļŦāļĢāļąāļšāļˆāļąāļ”āļāļēāļĢ details fields -* **āļŦāļ™āđ‰āļēāļ—āļĩāđˆ:** Centralized service āļŠāļģāļŦāļĢāļąāļš JSON details operations -* **Methods:** - * validateDetails(type: string, data: any): ValidationResult - * transformDetails(input: any, targetVersion: string): any - * sanitizeDetails(data: any): any - * getDefaultDetails(type: string): any - -### **3.10 āļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄāļĢāļ°āļšāļš (System Architecture)** - -āđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡āđ‚āļĄāļ”āļđāļĨ (Module Structure) - -```bash -📁 src -├── 📄 app.module.ts -├── 📄 main.ts -├── 📁 common # @app/common (āđ‚āļĄāļ”āļđāļĨāļŠāđˆāļ§āļ™āļāļĨāļēāļ‡) -│ ├── 📁 auth # AuthModule (JWT, Guards) -│ ├── 📁 config # Configuration -│ ├── 📁 decorators # Custom Decorators (āđ€āļŠāđˆāļ™ @RequirePermission) -│ ├── 📁 entities # Shared Entities (User, Role, Permission) -│ ├── 📁 exceptions # Global Exception Filters -│ ├── 📁 file-storage # FileStorageService (Virus Scanning, Security) -│ ├── 📁 guards # Custom Guards (RBAC Guard, RateLimitGuard) -│ ├── 📁 interceptors # Interceptors (Audit Log, Transform, Performance) -│ ├── 📁 resilience # Circuit Breaker, Retry Patterns -│ └── 📁 services # Shared Services (NotificationService, CachingService) -├── 📁 modules -│ ├── 📁 user # UserModule (āļˆāļąāļ”āļāļēāļĢ Users, Roles, Permissions) -│ ├── 📁 project # ProjectModule (āļˆāļąāļ”āļāļēāļĢ Projects, Organizations, Contracts) -│ ├── 📁 correspondence # CorrespondenceModule (āļˆāļąāļ”āļāļēāļĢāđ€āļ­āļāļŠāļēāļĢāđ‚āļ•āđ‰āļ•āļ­āļš) -│ ├── 📁 rfa # RfaModule (āļˆāļąāļ”āļāļēāļĢāđ€āļ­āļāļŠāļēāļĢāļ‚āļ­āļ­āļ™āļļāļĄāļąāļ•āļī) -│ ├── 📁 drawing # DrawingModule (āļˆāļąāļ”āļāļēāļĢāđāļšāļšāđāļ›āļĨāļ™) -│ ├── 📁 circulation # CirculationModule (āļˆāļąāļ”āļāļēāļĢāđƒāļšāđ€āļ§āļĩāļĒāļ™) -│ ├── 📁 transmittal # TransmittalModule (āļˆāļąāļ”āļāļēāļĢāđ€āļ­āļāļŠāļēāļĢāļ™āļģāļŠāđˆāļ‡) -│ ├── 📁 search # SearchModule (āļ„āđ‰āļ™āļŦāļēāļ‚āļąāđ‰āļ™āļŠāļđāļ‡āļ”āđ‰āļ§āļĒ Elasticsearch) -│ ├── 📁 monitoring # MonitoringModule (Metrics, Health Checks) -│ └── 📁 document-numbering # DocumentNumberingModule (Internal Module) -└── 📁 database # Database Migration & Seeding Scripts -``` - -### **3.11 āļāļĨāļĒāļļāļ—āļ˜āđŒāļ„āļ§āļēāļĄāļ—āļ™āļ—āļēāļ™āđāļĨāļ°āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ” (Resilience & Error Handling Strategy)** - -* **Circuit Breaker Pattern:** āđƒāļŠāđ‰āļŠāļģāļŦāļĢāļąāļš external service calls (Email, LINE, Elasticsearch) -* **Retry Mechanism:** āļ”āđ‰āļ§āļĒ exponential backoff āļŠāļģāļŦāļĢāļąāļš transient failures -* **Fallback Strategies:** Graceful degradation āđ€āļĄāļ·āđˆāļ­āļšāļĢāļīāļāļēāļĢāļ āļēāļĒāļ™āļ­āļāļĨāđ‰āļĄāđ€āļŦāļĨāļ§ -* **Error Handling:** Error messages āļ•āđ‰āļ­āļ‡āđ„āļĄāđˆāđ€āļ›āļīāļ”āđ€āļœāļĒāļ‚āđ‰āļ­āļĄāļđāļĨ sensitive -* **Monitoring:** Centralized error monitoring āđāļĨāļ° alerting system - -### **3.12 FileStorageService (āļ›āļĢāļąāļšāļ›āļĢāļļāļ‡āđƒāļŦāļĄāđˆ):** - -* **Virus Scanning:** Integrate ClamAV āļŠāļģāļŦāļĢāļąāļš scan āđ„āļŸāļĨāđŒāļ—āļĩāđˆāļ­āļąāļ›āđ‚āļŦāļĨāļ”āļ—āļąāđ‰āļ‡āļŦāļĄāļ” -* **File Type Validation:** āđƒāļŠāđ‰ white-list approach (PDF, DWG, DOCX, XLSX, ZIP) -* **File Size Limits:** 50MB āļ•āđˆāļ­āđ„āļŸāļĨāđŒ -* **Security Measures:** - * āđ€āļāđ‡āļšāđ„āļŸāļĨāđŒāļ™āļ­āļ web root - * Download āļœāđˆāļēāļ™ authenticated endpoint āđ€āļ—āđˆāļēāļ™āļąāđ‰āļ™ - * Download links āļĄāļĩ expiration time (24 āļŠāļąāđˆāļ§āđ‚āļĄāļ‡) - * File integrity checks (checksum) - * Access control checks āļāđˆāļ­āļ™āļ”āļēāļ§āļ™āđŒāđ‚āļŦāļĨāļ” - -### **3.13 āđ€āđ€āļ—āļ„āđ‚āļ™āđ‚āļĨāļĒāļĩāļ—āļĩāđˆāđƒāļŠāđ‰ (Technology Stack)** - -| āļŠāđˆāļ§āļ™ | Library/Tool | āļŦāļĄāļēāļĒāđ€āļŦāļ•āļļ | -|---|---|---| -| **Framework** | `@nestjs/core`, `@nestjs/common` | Core Framework | -| **Language** | `TypeScript` | āđƒāļŠāđ‰ TypeScript āļ—āļąāđ‰āļ‡āļĢāļ°āļšāļš | -| **Database** | `MariaDB 10.11` | āļāļēāļ™āļ‚āđ‰āļ­āļĄāļđāļĨāļŦāļĨāļąāļ | -| **ORM** | `@nestjs/typeorm`, `typeorm` | 🗃ïļāļˆāļąāļ”āļāļēāļĢāļāļēāļĢāđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­āđāļĨāļ° Query āļāļēāļ™āļ‚āđ‰āļ­āļĄāļđāļĨ | -| **Validation** | `class-validator`, `class-transformer` | ðŸ“Ķāļ•āļĢāļ§āļˆāļŠāļ­āļšāđāļĨāļ°āđāļ›āļĨāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāđƒāļ™ DTO | -| **Auth** | `@nestjs/jwt`, `@nestjs/passport`, `passport-jwt` | 🔐āļāļēāļĢāļĒāļ·āļ™āļĒāļąāļ™āļ•āļąāļ§āļ•āļ™āļ”āđ‰āļ§āļĒ JWT | -|**Authorization** | `casl` | 🔐āļˆāļąāļ”āļāļēāļĢāļŠāļīāļ—āļ˜āļīāđŒāđāļšāļš RBAC | -| **File Upload** | `multer` | 📁āļˆāļąāļ”āļāļēāļĢāļāļēāļĢāļ­āļąāļ›āđ‚āļŦāļĨāļ”āđ„āļŸāļĨāđŒ | -| **Search** | `@nestjs/elasticsearch` | 🔍āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ„āđ‰āļ™āļŦāļēāļ‚āļąāđ‰āļ™āļŠāļđāļ‡ | -| **Notification** | `nodemailer` | 📎āļŠāđˆāļ‡āļ­āļĩāđ€āļĄāļĨāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™ | -| **Scheduling** | `@nestjs/schedule` | 📎āļŠāļģāļŦāļĢāļąāļš Cron Jobs (āđ€āļŠāđˆāļ™ āđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™ Deadline) | -| **Logging** | `winston` | 📊āļšāļąāļ™āļ—āļķāļ Log āļ—āļĩāđˆāļĄāļĩāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļž | -| **Testing** | `@nestjs/testing`, `jest`, `supertest` | 🧊āļ—āļ”āļŠāļ­āļš Unit, Integration āđāļĨāļ° E2E | -| **Documentation** | `@nestjs/swagger` | 🌐āļŠāļĢāđ‰āļēāļ‡ API Documentation āļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī | -| **Security** | `helmet`, `rate-limiter-flexible` | ðŸ›Ąïļāđ€āļžāļīāđˆāļĄāļ„āļ§āļēāļĄāļ›āļĨāļ­āļ”āļ āļąāļĒāđƒāļŦāđ‰ API | -| **Resilience** | `@nestjs/circuit-breaker` | 🔄 Circuit breaker pattern | -| **Caching** | `@nestjs/cache-manager`, `cache-manager-redis-store` | ðŸ’ū Distributed caching | -| **Security** | `helmet`, `csurf`, `rate-limiter-flexible` | ðŸ›Ąïļ Security enhancements | -| **Validation** | `class-validator`, `class-transformer` | ✅ Input validation | -| **Monitoring** | `@nestjs/monitoring`, `winston` | 📊 Application monitoring | -| **File Processing** | `clamscan` | ðŸĶ  Virus scanning | -| **Cryptography** | `bcrypt`, `crypto` | 🔐 Password hashing āđāļĨāļ° checksums | -| **JSON Validation** | `ajv`, `ajv-formats` | ðŸŽŊ JSON schema validation | -| **JSON Processing** | `jsonpath`, `json-schema-ref-parser` | 🔧 JSON manipulation | -| **Data Transformation** | `class-transformer` | 🔄 Object transformation | -| **Compression** | `compression` | ðŸ“Ķ JSON compression | - -### **3.14 Security Testing:** - -* **Penetration Testing:** āļ—āļ”āļŠāļ­āļš OWASP Top 10 vulnerabilities -* **Security Audit:** Review code āļŠāļģāļŦāļĢāļąāļš security flaws -* **Virus Scanning Test:** āļ—āļ”āļŠāļ­āļš file upload security -* **Rate Limiting Test:** āļ—āļ”āļŠāļ­āļš rate limiting functionality - -### **3.15 Performance Testing:** - -* **Load Testing:** āļ—āļ”āļŠāļ­āļšāļ”āđ‰āļ§āļĒ realistic workloads -* **Stress Testing:** āļŦāļē breaking points āļ‚āļ­āļ‡āļĢāļ°āļšāļš -* **Endurance Testing:** āļ—āļ”āļŠāļ­āļšāļāļēāļĢāļ—āļģāļ‡āļēāļ™āļ•āđˆāļ­āđ€āļ™āļ·āđˆāļ­āļ‡āđ€āļ›āđ‡āļ™āđ€āļ§āļĨāļēāļ™āļēāļ™ - -### 🗄ïļ**3.16 Backend State Management** - -Backend (NestJS) āļ„āļ§āļĢāđ€āļ›āđ‡āļ™ **Stateless** (āđ„āļĄāđˆāđ€āļāđ‡āļšāļŠāļ–āļēāļ™āļ°) "State" āļ—āļąāđ‰āļ‡āļŦāļĄāļ”āļˆāļ°āļ–āļđāļāļˆāļąāļ”āđ€āļāđ‡āļšāđƒāļ™ MariaDB - -* **Request-Scoped State (āļŠāļ–āļēāļ™āļ°āļ āļēāļĒāđƒāļ™ Request āđ€āļ”āļĩāļĒāļ§):** - * **āļ›āļąāļāļŦāļē:** āļˆāļ°āļŠāđˆāļ‡āļ•āđˆāļ­āļ‚āđ‰āļ­āļĄāļđāļĨ (āđ€āļŠāđˆāļ™ User āļ—āļĩāđˆāļĨāđ‡āļ­āļāļ­āļīāļ™) āļĢāļ°āļŦāļ§āđˆāļēāļ‡ Guard āđāļĨāļ° Service āđƒāļ™ Request āđ€āļ”āļĩāļĒāļ§āļāļąāļ™āđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āđ„āļĢ? - * **āļ§āļīāļ˜āļĩāđāļāđ‰:** āđƒāļŠāđ‰ **Request-Scoped Providers** āļ‚āļ­āļ‡ NestJS (āđ€āļŠāđˆāļ™ AuthContextService) āđ€āļžāļ·āđˆāļ­āđ€āļāđ‡āļšāļ‚āđ‰āļ­āļĄāļđāļĨ User āļ›āļąāļˆāļˆāļļāļšāļąāļ™āļ—āļĩāđˆāđ„āļ”āđ‰āļˆāļēāļ AuthGuard āđāļĨāļ°āđƒāļŦāđ‰ Service āļ­āļ·āđˆāļ™ Inject āđ„āļ›āđƒāļŠāđ‰ -* **Application-Scoped State (āļāļēāļĢ Caching):** - * **āļ›āļąāļāļŦāļē:** āļ‚āđ‰āļ­āļĄāļđāļĨ Master (āđ€āļŠāđˆāļ™ roles, permissions, organizations) āļ–āļđāļāđ€āļĢāļĩāļĒāļāđƒāļŠāđ‰āļšāđˆāļ­āļĒ - * **āļ§āļīāļ˜āļĩāđāļāđ‰:** āđƒāļŠāđ‰ **Caching** (āđ€āļŠāđˆāļ™ @nestjs/cache-manager) āđ€āļžāļ·āđˆāļ­ Caching āļ‚āđ‰āļ­āļĄāļđāļĨāđ€āļŦāļĨāđˆāļēāļ™āļĩāđ‰ āđāļĨāļ°āļĨāļ”āļ āļēāļĢāļ° Database - -### **3.17 Caching Strategy (āļ•āļēāļĄāļ‚āđ‰āļ­ 6.4.2):** - -* **Master Data Cache:** Roles, Permissions, Organizations (TTL: 1 hour) -* **User Session Cache:** User permissions āđāļĨāļ° profile (TTL: 30 minutes) -* **Search Result Cache:** Frequently searched queries (TTL: 15 minutes) -* **File Metadata Cache:** Attachment metadata (TTL: 1 hour) -* **Cache Invalidation:** Clear cache on update/delete operations - -### **3.18 āļāļēāļĢāđ„āļŦāļĨāļ‚āļ­āļ‡āļ‚āđ‰āļ­āļĄāļđāļĨ (Data Flow)** - -#### **3.18.1 Main Flow:** - - 1. Request: āļœāđˆāļēāļ™ Nginx Proxy Manager -> NestJS Controller - 2. **Rate Limiting:** RateLimitGuard āļ•āļĢāļ§āļˆāļŠāļ­āļš request limits - 3. **Input Validation:** Validation Pipe āļ•āļĢāļ§āļˆāļŠāļ­āļšāđāļĨāļ° sanitize inputs - 4. Authentication: JWT Guard āļ•āļĢāļ§āļˆāļŠāļ­āļš Token āđāļĨāļ°āļ”āļķāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨ User - 5. Authorization: RBAC Guard āļ•āļĢāļ§āļˆāļŠāļ­āļšāļŠāļīāļ—āļ˜āļīāđŒ - 6. **Security Checks:** Virus scanning (āļŠāļģāļŦāļĢāļąāļš file upload), XSS protection - 7. Business Logic: Service Layer āļ›āļĢāļ°āļĄāļ§āļĨāļœāļĨāļ•āļĢāļĢāļāļ°āļ—āļēāļ‡āļ˜āļļāļĢāļāļīāļˆ - 8. **Resilience:** Circuit breaker āđāļĨāļ° retry logic āļŠāļģāļŦāļĢāļąāļš external calls - 9. Data Access: Repository Layer āļ•āļīāļ”āļ•āđˆāļ­āļāļąāļšāļāļēāļ™āļ‚āđ‰āļ­āļĄāļđāļĨ - 10. **Caching:** Cache frequently accessed data - 11. **Audit Log:** āļšāļąāļ™āļ—āļķāļāļāļēāļĢāļāļĢāļ°āļ—āļģāļŠāļģāļ„āļąāļ - 12. Response: āļŠāđˆāļ‡āļāļĨāļąāļšāđ„āļ›āļĒāļąāļ‡ Frontend - -#### **3.18.2 Workflow Data Flow:** - - 1. User āļŠāļĢāđ‰āļēāļ‡āđ€āļ­āļāļŠāļēāļĢ â†’ āđ€āļĨāļ·āļ­āļ routing template - 2. System āļŠāļĢāđ‰āļēāļ‡ routing instances āļ•āļēāļĄ template - 3. āļŠāļģāļŦāļĢāļąāļšāđāļ•āđˆāļĨāļ° routing step: - - āļāļģāļŦāļ™āļ” due date (āļˆāļēāļ expected_days) - - āļŠāđˆāļ‡ notification āđ„āļ›āļĒāļąāļ‡āļ­āļ‡āļ„āđŒāļāļĢāļœāļđāđ‰āļĢāļąāļš - - āļ­āļąāļžāđ€āļ”āļ—āļŠāļ–āļēāļ™āļ°āđ€āļ›āđ‡āļ™ SENT - 4. āđ€āļĄāļ·āđˆāļ­āļ­āļ‡āļ„āđŒāļāļĢāļœāļđāđ‰āļĢāļąāļšāļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢ: - - āļ­āļąāļžāđ€āļ”āļ—āļŠāļ–āļēāļ™āļ°āđ€āļ›āđ‡āļ™ ACTIONED/FORWARDED/REPLIED - - āļšāļąāļ™āļ—āļķāļ processed_by āđāļĨāļ° processed_at - - āļŠāđˆāļ‡ notification āđ„āļ›āļĒāļąāļ‡āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ•āđˆāļ­āđ„āļ› (āļ–āđ‰āļēāļĄāļĩ) - 5. āđ€āļĄāļ·āđˆāļ­āļ„āļĢāļšāļ—āļļāļāļ‚āļąāđ‰āļ™āļ•āļ­āļ™ â†’ āļ­āļąāļžāđ€āļ”āļ—āļŠāļ–āļēāļ™āļ°āđ€āļ­āļāļŠāļēāļĢāđ€āļ›āđ‡āļ™ COMPLETED - -#### **3.18.3 JSON Details Processing Flow:** - - 1. **Receive Request** → Get JSON data from client - 2. **Schema Validation** → Validate against predefined schema - 3. **Data Sanitization** → Sanitize and transform data - 4. **Version Check** → Handle schema version compatibility - 5. **Storage** → Store validated JSON in database - 6. **Retrieval** → Retrieve and transform on demand - -### 📊**3.19 Monitoring & Observability (āļ•āļēāļĄāļ‚āđ‰āļ­ 6.8)** - -#### **Application Monitoring:** - -* **Health Checks:** `/health` endpoint āļŠāļģāļŦāļĢāļąāļš load balancer -* **Metrics Collection:** Response times, error rates, throughput -* **Distributed Tracing:** āļŠāļģāļŦāļĢāļąāļš request tracing across services -* **Log Aggregation:** Structured logging āļ”āđ‰āļ§āļĒ JSON format -* **Alerting:** āļŠāļģāļŦāļĢāļąāļš critical errors āđāļĨāļ° performance degradation - -#### **Business Metrics:** - -* āļˆāļģāļ™āļ§āļ™ documents created āļ•āđˆāļ­āļ§āļąāļ™ -* Workflow completion rates -* User activity metrics -* System utilization rates -* Search query performance - -#### **Performance Targets:** - -* API Response Time: < 200ms (90th percentile) -* Search Query Performance: < 500ms -* File Upload Performance: < 30 seconds āļŠāļģāļŦāļĢāļąāļšāđ„āļŸāļĨāđŒ 50MB -* Cache Hit Ratio: > 80% - -## ðŸ–Ĩïļ **4. āļŸāļĢāļ­āļ™āļ•āđŒāđ€āļ­āļ™āļ”āđŒ (Next.js) - Implementation Details** - -**āđ‚āļ›āļĢāđ„āļŸāļĨāđŒāļ™āļąāļāļžāļąāļ’āļ™āļē (Developer Profile:** āļ§āļīāļĻāļ§āļāļĢ TypeScript + React/NextJS āļĢāļ°āļ”āļąāļš Senior -āđ€āļŠāļĩāđˆāļĒāļ§āļŠāļēāļ TailwindCSS, Shadcn/UI, āđāļĨāļ° Radix āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļžāļąāļ’āļ™āļē UI - -### **4.1 State Management & Offline Support** - -#### **4.1.1 Auto-Save Drafts** - -āđƒāļŠāđ‰ `zustand` āļĢāđˆāļ§āļĄāļāļąāļš middleware `persist` (āļĨāļ‡ LocalStorage) āļŠāļģāļŦāļĢāļąāļšāļŸāļ­āļĢāđŒāļĄāļ—āļĩāđˆāļĄāļĩāļ‚āļ™āļēāļ”āđƒāļŦāļāđˆ (RFA, Correspondence) āđ€āļžāļ·āđˆāļ­āļ›āđ‰āļ­āļ‡āļāļąāļ™āļ‚āđ‰āļ­āļĄāļđāļĨāļŦāļēāļĒāđ€āļĄāļ·āđˆāļ­āđ€āļ™āđ‡āļ•āļŦāļĨāļļāļ” - -```typescript -// lib/stores/draft-store.ts -export const useDraftStore = create( - persist( - (set) => ({ - drafts: {}, - saveDraft: (key, data) => set((state) => ({ drafts: { ...state.drafts, [key]: data } })), - clearDraft: (key) => set((state) => { - const newDrafts = { ...state.drafts }; - delete newDrafts[key]; - return { drafts: newDrafts }; - }), - }), - { name: 'form-drafts' } - ) -); -``` - -### **4.2 Dynamic Form Generator** - -āđ€āļžāļ·āđˆāļ­āļĢāļ­āļ‡āļĢāļąāļš JSON Schema āļŦāļĨāļēāļāļŦāļĨāļēāļĒāļĢāļđāļ›āđāļšāļš āđƒāļŦāđ‰āļŠāļĢāđ‰āļēāļ‡ Component āļāļĨāļēāļ‡āļ—āļĩāđˆāļĢāļąāļš Schema āđāļĨāđ‰āļ§ Gen Form āļ­āļ­āļāļĄāļē (āļĨāļ”āļāļēāļĢāđāļāđ‰ Code āļšāđˆāļ­āļĒāđ†) - -* **Libraries:** āđāļ™āļ°āļ™āļģ `react-jsonschema-form` āļŦāļĢāļ·āļ­āļŠāļĢāđ‰āļēāļ‡ Wrapper āļšāļ™ `react-hook-form` āļ—āļĩāđˆ Recursively render field āļ•āļēāļĄ Type -* **Validation:** āđƒāļŠāđ‰ `ajv` āļ—āļĩāđˆāļāļąāđˆāļ‡ Client āđ€āļžāļ·āđˆāļ­ Validate JSON āļāđˆāļ­āļ™ Submit - -### **4.3 Mobile Responsiveness (Card View)** - -āļ•āļēāļĢāļēāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨ (`DataTable`) āļ•āđ‰āļ­āļ‡āļĄāļĩāļ„āļ§āļēāļĄāļ‰āļĨāļēāļ”āđƒāļ™āļāļēāļĢāđāļŠāļ”āļ‡āļœāļĨ: - -* **Desktop:** āđāļŠāļ”āļ‡āđ€āļ›āđ‡āļ™ Table āļ›āļāļ•āļī -* **Mobile:** āđāļ›āļĨāļ‡āđ€āļ›āđ‡āļ™ **Card View** āđ‚āļ”āļĒāļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī (āļ‹āđˆāļ­āļ™ Header, āđāļŠāļ”āļ‡ Label āļ„āļđāđˆ Value āđƒāļ™āđāļ•āđˆāļĨāļ° Card) - -```tsx -// components/ui/responsive-table.tsx -
- {/* Desktop View */}
-
-
- {data.map((item) => ( - - {/* Mobile View: Render cells as list items */} - - ))} -
-``` - -### **4.4 Optimistic Updates** - -āđƒāļŠāđ‰āļ„āļ§āļēāļĄāļŠāļēāļĄāļēāļĢāļ–āļ‚āļ­āļ‡ **TanStack Query** (`onMutate`) āđ€āļžāļ·āđˆāļ­āļ­āļąāļ›āđ€āļ”āļ• UI āļ—āļąāļ™āļ—āļĩ (āđ€āļŠāđˆāļ™ āđ€āļ›āļĨāļĩāđˆāļĒāļ™āļŠāļ–āļēāļ™āļ°āļˆāļēāļ "āļĢāļ­āļ­āđˆāļēāļ™" āđ€āļ›āđ‡āļ™ "āļ­āđˆāļēāļ™āđāļĨāđ‰āļ§") āđāļĨāđ‰āļ§āļ„āđˆāļ­āļĒāļŠāđˆāļ‡ Request āđ„āļ› Server āļ–āđ‰āļē Failed āļ„āđˆāļ­āļĒ Rollback - -### **4.5 āđāļ™āļ§āļ—āļēāļ‡āļāļēāļĢāļžāļąāļ’āļ™āļēāđ‚āļ„āđ‰āļ” (Code Implementation Guidelines)** - -* āđƒāļŠāđ‰ **early returns** āđ€āļžāļ·āđˆāļ­āļ„āļ§āļēāļĄāļŠāļąāļ”āđ€āļˆāļ™ -* āđƒāļŠāđ‰āļ„āļĨāļēāļŠāļ‚āļ­āļ‡ **TailwindCSS** āđƒāļ™āļāļēāļĢāļāļģāļŦāļ™āļ”āļŠāđ„āļ•āļĨāđŒāđ€āļŠāļĄāļ­ -* āļ„āļ§āļĢāđƒāļŠāđ‰ class: syntax āđāļšāļšāļĄāļĩāđ€āļ‡āļ·āđˆāļ­āļ™āđ„āļ‚ (āļŦāļĢāļ·āļ­ utility clsx) āļĄāļēāļāļāļ§āđˆāļēāļāļēāļĢāđƒāļŠāđ‰ ternary operators āđƒāļ™ class strings -* āđƒāļŠāđ‰ **const arrow functions** āļŠāļģāļŦāļĢāļąāļš components āđāļĨāļ° handlers -* Event handlers āđƒāļŦāđ‰āļ‚āļķāđ‰āļ™āļ•āđ‰āļ™āļ”āđ‰āļ§āļĒ handle... (āđ€āļŠāđˆāļ™ handleClick, handleSubmit) -* āļĢāļ§āļĄāđāļ­āļ•āļ—āļĢāļīāļšāļīāļ§āļ•āđŒāļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡ (accessibility) āļ”āđ‰āļ§āļĒ: - tabIndex="0", aria-label, onKeyDown, āļŊāļĨāļŊ -* āļ•āļĢāļ§āļˆāļŠāļ­āļšāđƒāļŦāđ‰āđāļ™āđˆāđƒāļˆāļ§āđˆāļēāđ‚āļ„āđ‰āļ”āļ—āļąāđ‰āļ‡āļŦāļĄāļ” **āļŠāļĄāļšāļđāļĢāļ“āđŒ**, **āļœāđˆāļēāļ™āļāļēāļĢāļ—āļ”āļŠāļ­āļš**, āđāļĨāļ° **āđ„āļĄāđˆāļ‹āđ‰āļģāļ‹āđ‰āļ­āļ™ (DRY)** -* āļ•āđ‰āļ­āļ‡ import āđ‚āļĄāļ”āļđāļĨāļ—āļĩāđˆāļˆāļģāđ€āļ›āđ‡āļ™āļ•āđ‰āļ­āļ‡āđƒāļŠāđ‰āļ­āļĒāđˆāļēāļ‡āļŠāļąāļ”āđ€āļˆāļ™āđ€āļŠāļĄāļ­ - -### **4.6 UI/UX āļ”āđ‰āļ§āļĒ React** - -* āđƒāļŠāđ‰ **semantic HTML** -* āđƒāļŠāđ‰āļ„āļĨāļēāļŠāļ‚āļ­āļ‡ **Tailwind** āļ—āļĩāđˆāļĢāļ­āļ‡āļĢāļąāļš responsive (sm:, md:, lg:) -* āļĢāļąāļāļĐāļēāļĨāļģāļ”āļąāļšāļŠāļąāđ‰āļ™āļ‚āļ­āļ‡āļāļēāļĢāļĄāļ­āļ‡āđ€āļŦāđ‡āļ™ (visual hierarchy) āļ”āđ‰āļ§āļĒāļāļēāļĢāđƒāļŠāđ‰ typography āđāļĨāļ° spacing -* āđƒāļŠāđ‰ **Shadcn** components (Button, Input, Card, āļŊāļĨāļŊ) āđ€āļžāļ·āđˆāļ­ UI āļ—āļĩāđˆāļŠāļ­āļ”āļ„āļĨāđ‰āļ­āļ‡āļāļąāļ™ -* āļ—āļģāđƒāļŦāđ‰ components āļĄāļĩāļ‚āļ™āļēāļ”āđ€āļĨāđ‡āļāđāļĨāļ°āļĄāļļāđˆāļ‡āđ€āļ™āđ‰āļ™āļāļēāļĢāļ—āļģāļ‡āļēāļ™āđ€āļ‰āļžāļēāļ°āļ­āļĒāđˆāļēāļ‡ -* āđƒāļŠāđ‰ utility classes āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļˆāļąāļ”āļŠāđ„āļ•āļĨāđŒāļ­āļĒāđˆāļēāļ‡āļĢāļ§āļ”āđ€āļĢāđ‡āļ§ (spacing, colors, text, āļŊāļĨāļŊ) -* āļ•āļĢāļ§āļˆāļŠāļ­āļšāđƒāļŦāđ‰āđāļ™āđˆāđƒāļˆāļ§āđˆāļēāļŠāļ­āļ”āļ„āļĨāđ‰āļ­āļ‡āļāļąāļš **ARIA** āđāļĨāļ°āđƒāļŠāđ‰ semantic markup - -### **4.7 āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļŸāļ­āļĢāđŒāļĄāđāļĨāļ°āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ” (Form Validation & Errors)** - -* āđƒāļŠāđ‰āđ„āļĨāļšāļĢāļēāļĢāļĩāļāļąāđˆāļ‡ client āđ€āļŠāđˆāļ™ zod āđāļĨāļ° react-hook-form -* āđāļŠāļ”āļ‡āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļ”āđ‰āļ§āļĒ **alert components** āļŦāļĢāļ·āļ­āļ‚āđ‰āļ­āļ„āļ§āļēāļĄ inline -* āļ•āđ‰āļ­āļ‡āļĄāļĩ labels, placeholders, āđāļĨāļ°āļ‚āđ‰āļ­āļ„āļ§āļēāļĄ feedback - -### **🧊4.8 Frontend Testing** - -āđ€āļĢāļēāļˆāļ°āđƒāļŠāđ‰ **React Testing Library (RTL)** āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ—āļ”āļŠāļ­āļš Component āđāļĨāļ° **Playwright** āļŠāļģāļŦāļĢāļąāļš E2E: - -* **Unit Tests (āļāļēāļĢāļ—āļ”āļŠāļ­āļšāļŦāļ™āđˆāļ§āļĒāļĒāđˆāļ­āļĒ):** - * **āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļĄāļ·āļ­:** Vitest + RTL - * **āđ€āļ›āđ‰āļēāļŦāļĄāļēāļĒ:** āļ—āļ”āļŠāļ­āļš Component āļ‚āļ™āļēāļ”āđ€āļĨāđ‡āļ (āđ€āļŠāđˆāļ™ Buttons, Inputs) āļŦāļĢāļ·āļ­ Utility functions -* **Integration Tests (āļāļēāļĢāļ—āļ”āļŠāļ­āļšāļāļēāļĢāļšāļđāļĢāļ“āļēāļāļēāļĢ):** - * **āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļĄāļ·āļ­:** RTL + **Mock Service Worker (MSW)** - * **āđ€āļ›āđ‰āļēāļŦāļĄāļēāļĒ:** āļ—āļ”āļŠāļ­āļšāļ§āđˆāļē Component āļŦāļĢāļ·āļ­ Page āļ—āļģāļ‡āļēāļ™āļāļąāļš API (āļ—āļĩāđˆāļˆāļģāļĨāļ­āļ‡āļ‚āļķāđ‰āļ™) āđ„āļ”āđ‰āļ–āļđāļāļ•āđ‰āļ­āļ‡ - * **āđ€āļ—āļ„āļ™āļīāļ„:** āđƒāļŠāđ‰ MSW āđ€āļžāļ·āđˆāļ­āļˆāļģāļĨāļ­āļ‡ NestJS API āđāļĨāļ°āļ—āļ”āļŠāļ­āļšāļ§āđˆāļē Component āđāļŠāļ”āļ‡āļœāļĨāļ‚āđ‰āļ­āļĄāļđāļĨāļˆāļģāļĨāļ­āļ‡āđ„āļ”āđ‰āļ–āļđāļāļ•āđ‰āļ­āļ‡āļŦāļĢāļ·āļ­āđ„āļĄāđˆ (āđ€āļŠāđˆāļ™ āļ—āļ”āļŠāļ­āļšāļŦāļ™āđ‰āļē Dashboard [cite: 5.3] āļ—āļĩāđˆāļ”āļķāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļˆāļēāļ v_user_tasks) -* **E2E (End-to-End) Tests:** - * **āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļĄāļ·āļ­:** **Playwright** - * **āđ€āļ›āđ‰āļēāļŦāļĄāļēāļĒ:** āļ—āļ”āļŠāļ­āļš User Flow āļ—āļąāđ‰āļ‡āļĢāļ°āļšāļšāđ‚āļ”āļĒāļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī (āđ€āļŠāđˆāļ™ āļĨāđ‡āļ­āļāļ­āļīāļ™ -> āļŠāļĢāđ‰āļēāļ‡ RFA -> āļ•āļĢāļ§āļˆāļŠāļ­āļš Workflow Visualization [cite: 5.6]) - -### **🗄ïļ4.9 Frontend State Management** - -āļŠāļģāļŦāļĢāļąāļš Next.js App Router āđ€āļĢāļēāļˆāļ°āđāļšāđˆāļ‡ State āđ€āļ›āđ‡āļ™ 4 āļĢāļ°āļ”āļąāļš: - -1. **Local UI State (āļŠāļ–āļēāļ™āļ° UI āļŠāļąāđˆāļ§āļ„āļĢāļēāļ§):** - * **āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļĄāļ·āļ­:** useState, useReducer - * **āđƒāļŠāđ‰āđ€āļĄāļ·āđˆāļ­:** āļˆāļąāļ”āļāļēāļĢāļŠāļ–āļēāļ™āļ°āđ€āļĨāđ‡āļāđ† āļ—āļĩāđˆāļˆāļšāđƒāļ™ Component āđ€āļ”āļĩāļĒāļ§ (āđ€āļŠāđˆāļ™ Modal āđ€āļ›āļīāļ”/āļ›āļīāļ”, āļ„āđˆāļēāđƒāļ™ Input) -2. **Server State (āļŠāļ–āļēāļ™āļ°āļ‚āđ‰āļ­āļĄāļđāļĨāļˆāļēāļāđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ):** - * **āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļĄāļ·āļ­:** **React Query (TanStack Query)** āļŦāļĢāļ·āļ­ SWR - * **āđƒāļŠāđ‰āđ€āļĄāļ·āđˆāļ­:** āļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļĩāđˆāļ”āļķāļ‡āļĄāļēāļˆāļēāļ NestJS API (āđ€āļŠāđˆāļ™ āļĢāļēāļĒāļāļēāļĢ correspondences, rfas, drawings) - * **āļ—āļģāđ„āļĄ:** React Query āđ€āļ›āđ‡āļ™ "Cache" āļ—āļĩāđˆāļˆāļąāļ”āļāļēāļĢ Caching, Re-fetching, āđāļĨāļ° Invalidation āđƒāļŦāđ‰āđ‚āļ”āļĒāļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī -3. **Global Client State (āļŠāļ–āļēāļ™āļ°āļŠāđˆāļ§āļ™āļāļĨāļēāļ‡āļāļąāđˆāļ‡ Client):** - * **āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļĄāļ·āļ­:** **Zustand** (āđāļ™āļ°āļ™āļģ) āļŦāļĢāļ·āļ­ Context API - * **āđƒāļŠāđ‰āđ€āļĄāļ·āđˆāļ­:** āļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļĩāđˆāļ•āđ‰āļ­āļ‡āđƒāļŠāđ‰āļĢāđˆāļ§āļĄāļāļąāļ™āļ—āļąāđˆāļ§āļ—āļąāđ‰āļ‡āđāļ­āļ› āđāļĨāļ° *āđ„āļĄāđˆāđƒāļŠāđˆ* āļ‚āđ‰āļ­āļĄāļđāļĨāļˆāļēāļāđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒ (āđ€āļŠāđˆāļ™ āļ‚āđ‰āļ­āļĄāļđāļĨ User āļ—āļĩāđˆāļĨāđ‡āļ­āļāļ­āļīāļ™, āļŠāļīāļ—āļ˜āļīāđŒ Permissions) -4. **Form State (āļŠāļ–āļēāļ™āļ°āļ‚āļ­āļ‡āļŸāļ­āļĢāđŒāļĄ):** - * **āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļĄāļ·āļ­:** **React Hook Form** + **Zod** - * **āđƒāļŠāđ‰āđ€āļĄāļ·āđˆāļ­:** āļˆāļąāļ”āļāļēāļĢāļŸāļ­āļĢāđŒāļĄāļ—āļĩāđˆāļ‹āļąāļšāļ‹āđ‰āļ­āļ™ (āđ€āļŠāđˆāļ™ āļŸāļ­āļĢāđŒāļĄāļŠāļĢāđ‰āļēāļ‡ RFA, āļŸāļ­āļĢāđŒāļĄ Circulation [cite: 3.7]) - -## 🔐 **5. Security & Access Control (Full Stack Integration)** - -### **5.1 CASL Integration (Shared Ability)** - -* **Backend:** āđƒāļŠāđ‰ CASL āļāļģāļŦāļ™āļ” Permission Rule -* **Frontend:** āđƒāļŦāđ‰āļ”āļķāļ‡ Rule (JSON) āļˆāļēāļ Backend āļĄāļē Load āđƒāļŠāđˆ `@casl/react` āđ€āļžāļ·āđˆāļ­āđƒāļŦāđ‰ Logic āļāļēāļĢ Show/Hide āļ›āļļāđˆāļĄ āļ•āļĢāļ‡āļāļąāļ™ 100% - -### **5.2 Maintenance Mode** - -āđ€āļžāļīāđˆāļĄ Middleware (āļ—āļąāđ‰āļ‡ NestJS āđāļĨāļ° Next.js) āđ€āļžāļ·āđˆāļ­āļ•āļĢāļ§āļˆāļŠāļ­āļš Flag āđƒāļ™ Redis: - -* āļ–āđ‰āļē `MAINTENANCE_MODE = true` -* **API:** Return `503 Service Unavailable` (āļĒāļāđ€āļ§āđ‰āļ™ Admin IP) -* **Frontend:** Redirect āđ„āļ›āļŦāļ™āđ‰āļē `/maintenance` - -### **5.3 Idempotency Client** - -āļŠāļĢāđ‰āļēāļ‡ Axios Interceptor āđ€āļžāļ·āđˆāļ­ Generate `Idempotency-Key` āļŠāļģāļŦāļĢāļąāļš POST/PUT/DELETE requests āļ—āļļāļāļ„āļĢāļąāđ‰āļ‡ - -```typescript -// lib/api/client.ts -import { v4 as uuidv4 } from 'uuid'; - -apiClient.interceptors.request.use((config) => { - if (['post', 'put', 'delete'].includes(config.method)) { - config.headers['Idempotency-Key'] = uuidv4(); - } - return config; -}); -``` - -### **5.4 RBAC āđāļĨāļ°āļāļēāļĢāļ„āļ§āļšāļ„āļļāļĄāļŠāļīāļ—āļ˜āļīāđŒ (RBAC & Permission Control)** - -āđƒāļŠāđ‰ Decorators āđ€āļžāļ·āđˆāļ­āļšāļąāļ‡āļ„āļąāļšāđƒāļŠāđ‰āļŠāļīāļ—āļ˜āļīāđŒāļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡ āđ‚āļ”āļĒāļ­āđ‰āļēāļ‡āļ­āļīāļ‡āļŠāļīāļ—āļ˜āļīāđŒāļˆāļēāļāļ•āļēāļĢāļēāļ‡ permissions - -```typescript -@RequirePermission('rfas.respond') // āļ•āđ‰āļ­āļ‡āļ•āļĢāļ‡āļāļąāļš 'permission_code' -@Put(':id') -updateRFA(@Param('id') id: string) { - return this.rfaService.update(id); -} -``` - -#### **5.4.1 Roles (āļšāļ—āļšāļēāļ—)** - -* **Superadmin**: āđ„āļĄāđˆāļĄāļĩāļ‚āđ‰āļ­āļˆāļģāļāļąāļ”āđƒāļ”āđ† [cite: 4.3] -* **Admin**: āļĄāļĩāļŠāļīāļ—āļ˜āļīāđŒāđ€āļ•āđ‡āļĄāļ—āļĩāđˆāđƒāļ™āļ­āļ‡āļ„āđŒāļāļĢ [cite: 4.3] -* **Document Control**: āđ€āļžāļīāđˆāļĄ/āđāļāđ‰āđ„āļ‚/āļĨāļš āđ€āļ­āļāļŠāļēāļĢāđƒāļ™āļ­āļ‡āļ„āđŒāļāļĢ [cite: 4.3] -* **Editor**: āļŠāļēāļĄāļēāļĢāļ– āđ€āļžāļīāđˆāļĄ/āđāļāđ‰āđ„āļ‚ āđ€āļ­āļāļŠāļēāļĢāļ—āļĩāđˆāļāļģāļŦāļ™āļ” [cite: 4.3] -* **Viewer**: āļŠāļēāļĄāļēāļĢāļ–āļ”āļđ āđ€āļ­āļāļŠāļēāļĢ [cite: 4.3] - -#### **5.4.2 āļ•āļąāļ§āļ­āļĒāđˆāļēāļ‡ Permissions (āļˆāļēāļāļ•āļēāļĢāļēāļ‡ permissions)** - -* rfas.view, rfas.create, rfas.respond, rfas.delete -* drawings.view, drawings.upload, drawings.delete -* corr.view, corr.manage -* transmittals.manage -* cirs.manage -* project_parties.manage - -āļāļēāļĢāļˆāļąāļšāļ„āļđāđˆāļĢāļ°āļŦāļ§āđˆāļēāļ‡ roles āđāļĨāļ° permissions **āđ€āļĢāļīāđˆāļĄāļ•āđ‰āļ™** āļˆāļ°āļ–āļđāļ seed āļœāđˆāļēāļ™āļŠāļ„āļĢāļīāļ›āļ•āđŒ (āļ”āļąāļ‡āļ—āļĩāđˆāđ€āļŦāđ‡āļ™āđƒāļ™āđ„āļŸāļĨāđŒ SQL)**āļ­āļĒāđˆāļēāļ‡āđ„āļĢāļāđ‡āļ•āļēāļĄ AuthModule/UserModule āļ•āđ‰āļ­āļ‡āļĄāļĩ API āļŠāļģāļŦāļĢāļąāļš Admin āđ€āļžāļ·āđˆāļ­āļŠāļĢāđ‰āļēāļ‡ Role āđƒāļŦāļĄāđˆāđāļĨāļ°āļāļģāļŦāļ™āļ”āļŠāļīāļ—āļ˜āļīāđŒ (Permissions) āđ€āļžāļīāđˆāļĄāđ€āļ•āļīāļĄāđ„āļ”āđ‰āđƒāļ™āļ āļēāļĒāļŦāļĨāļąāļ‡** [cite: 4.3] - -## 📊 **6. Notification & Background Jobs** - -### **6.1 Digest Notification** - -āļŦāđ‰āļēāļĄāļŠāđˆāļ‡ Email āļ—āļąāļ™āļ—āļĩāļ—āļĩāđˆāđ€āļāļīāļ” Event āđƒāļŦāđ‰: - -1. Push Event āļĨāļ‡ Queue (Redis/BullMQ) -2. āļĄāļĩ Processor āļĢāļ­āđ€āļ§āļĨāļē (āđ€āļŠāđˆāļ™ 5 āļ™āļēāļ—āļĩ) āđ€āļžāļ·āđˆāļ­ Group Events āļ—āļĩāđˆāļ„āļĨāđ‰āļēāļĒāļāļąāļ™ (āđ€āļŠāđˆāļ™ "āļ„āļļāļ“āļĄāļĩāđ€āļ­āļāļŠāļēāļĢāļĢāļ­āļ­āļ™āļļāļĄāļąāļ•āļī 5 āļ‰āļšāļąāļš") -3. āļŠāđˆāļ‡ Email āđ€āļ”āļĩāļĒāļ§ (Digest) āđ€āļžāļ·āđˆāļ­āļĨāļ” Spam - -## 🔗 **7. āđāļ™āļ§āļ—āļēāļ‡āļāļēāļĢāļšāļđāļĢāļ“āļēāļāļēāļĢ Full Stack (Full Stack Integration Guidelines)** - -| Aspect (āđāļ‡āđˆāļĄāļļāļĄ) | Backend (NestJS) | Frontend (NextJS) | UI Layer (Tailwind/Shadcn) | -| :---- | :---- | :---- | :---- | -| API | REST / GraphQL Controllers | API hooks āļœāđˆāļēāļ™ fetch/axios/SWR | Components āļ—āļĩāđˆāļĢāļąāļšāļ‚āđ‰āļ­āļĄāļđāļĨ | -| Validation (āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļš) | class-validator DTOs | zod / react-hook-form | āļŠāļ–āļēāļ™āļ°āļ‚āļ­āļ‡āļŸāļ­āļĢāđŒāļĄ/input āđƒāļ™ Shadcn | -| Auth (āļāļēāļĢāļĒāļ·āļ™āļĒāļąāļ™āļ•āļąāļ§āļ•āļ™) | Guards, JWT | NextAuth / cookies | āļŠāļ–āļēāļ™āļ° UI āļ‚āļ­āļ‡ Auth (loading, signed in) | -| Errors (āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”) | Global filters | Toasts / modals | Alerts / āļ‚āđ‰āļ­āļ„āļ§āļēāļĄ feedback | -| Testing (āļāļēāļĢāļ—āļ”āļŠāļ­āļš) | Jest (unit/e2e) | Vitest / Playwright | Visual regression | -| Styles (āļŠāđ„āļ•āļĨāđŒ) | Scoped modules (āļ–āđ‰āļēāļˆāļģāđ€āļ›āđ‡āļ™) | Tailwind / Shadcn | Tailwind utilities | -| Accessibility (āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡) | Guards + filters | ARIA attributes | Semantic HTML | - -## 🗂ïļ **8. āļ‚āđ‰āļ­āļ•āļāļĨāļ‡āđ€āļ‰āļžāļēāļ°āļŠāļģāļŦāļĢāļąāļš DMS (LCBP3-DMS)** - -āļŠāđˆāļ§āļ™āļ™āļĩāđ‰āļ‚āļĒāļēāļĒāđāļ™āļ§āļ—āļēāļ‡ FullStackJS āļ—āļąāđˆāļ§āđ„āļ›āļŠāļģāļŦāļĢāļąāļšāđ‚āļ›āļĢāđ€āļˆāļāļ•āđŒ **LCBP3-DMS** āđ‚āļ”āļĒāļĄāļļāđˆāļ‡āđ€āļ™āđ‰āļ™āđ„āļ›āļ—āļĩāđˆāđ€āļ§āļīāļĢāđŒāļāđ‚āļŸāļĨāļ§āđŒāļāļēāļĢāļ­āļ™āļļāļĄāļąāļ•āļīāđ€āļ­āļāļŠāļēāļĢ (Correspondence, RFA, Drawing, Contract, Transmittal, Circulation) - -### ðŸ§ū**8.1 āļĄāļēāļ•āļĢāļāļēāļ™ AuditLog (AuditLog Standard)** - -āļšāļąāļ™āļ—āļķāļāļāļēāļĢāļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢ CRUD āđāļĨāļ°āļāļēāļĢāļˆāļąāļšāļ„āļđāđˆāļ—āļąāđ‰āļ‡āļŦāļĄāļ”āļĨāļ‡āđƒāļ™āļ•āļēāļĢāļēāļ‡ audit_logs - -| Field (āļŸāļīāļĨāļ”āđŒ) | Type (āļˆāļēāļ SQL) | Description (āļ„āļģāļ­āļ˜āļīāļšāļēāļĒ) | -| :---- | :---- | :---- | -| audit_id | BIGINT | Primary Key | -| user_id | INT | āļœāļđāđ‰āđƒāļŠāđ‰āļ—āļĩāđˆāļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢ (FK -> users) | -| action | VARCHAR(100) | rfa.create, correspondence.update, login.success | -| entity_type | VARCHAR(50) | āļŠāļ·āđˆāļ­āļ•āļēāļĢāļēāļ‡/āđ‚āļĄāļ”āļđāļĨ āđ€āļŠāđˆāļ™ 'rfa', 'correspondence' | -| entity_id | VARCHAR(50) | Primary ID āļ‚āļ­āļ‡āļĢāļ°āđ€āļšāļĩāļĒāļ™āļ—āļĩāđˆāđ„āļ”āđ‰āļĢāļąāļšāļœāļĨāļāļĢāļ°āļ—āļš | -| details_json | JSON | āļ‚āđ‰āļ­āļĄāļđāļĨāļšāļĢāļīāļšāļ— (āđ€āļŠāđˆāļ™ āļŸāļīāļĨāļ”āđŒāļ—āļĩāđˆāļĄāļĩāļāļēāļĢāđ€āļ›āļĨāļĩāđˆāļĒāļ™āđāļ›āļĨāļ‡) | -| ip_address | VARCHAR(45) | IP address āļ‚āļ­āļ‡āļœāļđāđ‰āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢ | -| user_agent | VARCHAR(255) | User Agent āļ‚āļ­āļ‡āļœāļđāđ‰āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢ | -| created_at | TIMESTAMP | Timestamp (UTC) | - -### 📂**8.2 āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāđ„āļŸāļĨāđŒ (File Handling)** - -#### **8.2.1 āļĄāļēāļ•āļĢāļāļēāļ™āļāļēāļĢāļ­āļąāļ›āđ‚āļŦāļĨāļ”āđ„āļŸāļĨāđŒ (File Upload Standard)** - -* **Security-First Approach:** āļāļēāļĢāļ­āļąāļ›āđ‚āļŦāļĨāļ”āđ„āļŸāļĨāđŒāļ—āļąāđ‰āļ‡āļŦāļĄāļ”āļˆāļ°āļ–āļđāļāļˆāļąāļ”āļāļēāļĢāđ‚āļ”āļĒ FileStorageService āļ—āļĩāđˆāļĄāļĩ security measures āļ„āļĢāļšāļ–āđ‰āļ§āļ™ -* āđ„āļŸāļĨāđŒāļˆāļ°āļ–āļđāļāđ€āļŠāļ·āđˆāļ­āļĄāđ‚āļĒāļ‡āđ„āļ›āļĒāļąāļ‡ Entity āļ—āļĩāđˆāļ–āļđāļāļ•āđ‰āļ­āļ‡āļœāđˆāļēāļ™ **āļ•āļēāļĢāļēāļ‡āđ€āļŠāļ·āđˆāļ­āļĄ (Junction Tables)** āđ€āļ—āđˆāļēāļ™āļąāđ‰āļ™: - * correspondence_attachments (āđ€āļŠāļ·āđˆāļ­āļĄ Correspondence āļāļąāļš Attachments) - * circulation_attachments (āđ€āļŠāļ·āđˆāļ­āļĄ Circulation āļāļąāļš Attachments) - * shop_drawing_revision_attachments (āđ€āļŠāļ·āđˆāļ­āļĄ Shop Drawing Revision āļāļąāļš Attachments) - * contract_drawing_attachments (āđ€āļŠāļ·āđˆāļ­āļĄ Contract Drawing āļāļąāļš Attachments) -* āđ€āļŠāđ‰āļ™āļ—āļēāļ‡āļˆāļąāļ”āđ€āļāđ‡āļšāđ„āļŸāļĨāđŒ (Upload path): āļ­āđ‰āļēāļ‡āļ­āļīāļ‡āļˆāļēāļ Requirement 2.1 āļ„āļ·āļ­ /share/dms-data [cite: 2.1] āđ‚āļ”āļĒ FileStorageService āļˆāļ°āļŠāļĢāđ‰āļēāļ‡āđ‚āļŸāļĨāđ€āļ”āļ­āļĢāđŒāļĒāđˆāļ­āļĒāđāļšāļšāļĢāļ§āļĄāļĻāļđāļ™āļĒāđŒ (āđ€āļŠāđˆāļ™ /share/dms-data/uploads/{YYYY}/{MM}/[stored_filename]) -* āļ›āļĢāļ°āđ€āļ āļ—āđ„āļŸāļĨāđŒāļ—āļĩāđˆāļ­āļ™āļļāļāļēāļ•: pdf, dwg, docx, xlsx, zip (āļœāđˆāļēāļ™ white-list validation) -* āļ‚āļ™āļēāļ”āļŠāļđāļ‡āļŠāļļāļ”: **50 MB** -* āļˆāļąāļ”āđ€āļāđ‡āļšāļ™āļ­āļ webroot -* āđƒāļŦāđ‰āļšāļĢāļīāļāļēāļĢāđ„āļŸāļĨāđŒāļœāđˆāļēāļ™ endpoint āļ—āļĩāđˆāļ›āļĨāļ­āļ”āļ āļąāļĒ /files/:attachment_id/download - -#### **8.2.2 Security Controls āļŠāļģāļŦāļĢāļąāļš File Access:** - -āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āđ„āļŸāļĨāđŒāđ„āļĄāđˆāđƒāļŠāđˆāļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āđ‚āļ”āļĒāļ•āļĢāļ‡ endpoint /files/:attachment_id/download āļˆāļ°āļ•āđ‰āļ­āļ‡: - -1. āļ„āđ‰āļ™āļŦāļēāļĢāļ°āđ€āļšāļĩāļĒāļ™ attachment -2. āļ•āļĢāļ§āļˆāļŠāļ­āļšāļ§āđˆāļē attachment_id āļ™āļĩāđ‰ āđ€āļŠāļ·āđˆāļ­āļĄāđ‚āļĒāļ‡āļāļąāļš Entity āđƒāļ” (āđ€āļŠāđˆāļ™ correspondence, circulation, shop_drawing_revision, contract_drawing) āļœāđˆāļēāļ™āļ•āļēāļĢāļēāļ‡āđ€āļŠāļ·āđˆāļ­āļĄ -3. āļ•āļĢāļ§āļˆāļŠāļ­āļšāļ§āđˆāļēāļœāļđāđ‰āđƒāļŠāđ‰āļĄāļĩāļŠāļīāļ—āļ˜āļīāđŒ (permission) āđƒāļ™āļāļēāļĢāļ”āļđ Entity āļ•āđ‰āļ™āļ—āļēāļ‡āļ™āļąāđ‰āļ™āđ† āļŦāļĢāļ·āļ­āđ„āļĄāđˆ -4. āļ•āļĢāļ§āļˆāļŠāļ­āļš download token expiration (24 āļŠāļąāđˆāļ§āđ‚āļĄāļ‡) -5. āļšāļąāļ™āļ—āļķāļ audit log āļāļēāļĢāļ”āļēāļ§āļ™āđŒāđ‚āļŦāļĨāļ” - -### 🔟**8.3 āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāđ€āļĨāļ‚āļ—āļĩāđˆāđ€āļ­āļāļŠāļēāļĢ (Document Numbering) [cite: 3.10]** - -* **āđ€āļ›āđ‰āļēāļŦāļĄāļēāļĒ:** āļŠāļĢāđ‰āļēāļ‡āđ€āļĨāļ‚āļ—āļĩāđˆāđ€āļ­āļāļŠāļēāļĢ (āđ€āļŠāđˆāļ™ correspondence_number) āđ‚āļ”āļĒāļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī āļ•āļēāļĄāļĢāļđāļ›āđāļšāļšāļ—āļĩāđˆāļāļģāļŦāļ™āļ” -* **āļ•āļĢāļĢāļāļ°āļāļēāļĢāļ™āļąāļš:** āļāļēāļĢāļ™āļąāļš Running number (SEQ) āļˆāļ°āļ™āļąāļšāđāļĒāļāļ•āļēāļĄ Key: **Project + Originator Organization + Document Type + Year** -* **āļ•āļēāļĢāļēāļ‡ SQL:** - * document_number_formats: Admin āđƒāļŠāđ‰āļāļģāļŦāļ™āļ” "āļĢāļđāļ›āđāļšāļš" (Template) āļ‚āļ­āļ‡āđ€āļĨāļ‚āļ—āļĩāđˆ (āđ€āļŠāđˆāļ™ {ORG_CODE}-{TYPE_CODE}-{YEAR_SHORT}-{SEQ:4}) āđ‚āļ”āļĒāļāļģāļŦāļ™āļ”āļ•āļēāļĄ **Project** āđāļĨāļ° **Document Type** [cite: 4.5] - * document_number_counters: āļĢāļ°āļšāļšāđƒāļŠāđ‰āđ€āļāđ‡āļš "āļ•āļąāļ§āļ™āļąāļš" āļĨāđˆāļēāļŠāļļāļ”āļ‚āļ­āļ‡ Key (Project+Org+Type+Year) -* **āļāļēāļĢāļ—āļģāļ‡āļēāļ™ (Backend):** - * DocumentNumberingModule āļˆāļ°āđƒāļŦāđ‰āļšāļĢāļīāļāļēāļĢ DocumentNumberingService - * āđ€āļĄāļ·āđˆāļ­ CorrespondenceModule āļ•āđ‰āļ­āļ‡āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡āđ€āļ­āļāļŠāļēāļĢāđƒāļŦāļĄāđˆ, āļĄāļąāļ™āļˆāļ°āđ€āļĢāļĩāļĒāļ documentNumberingService.generateNextNumber(...) - * Service āļ™āļĩāđ‰āļˆāļ°āđƒāļŠāđ‰ **Redis distributed locking** āđāļ—āļ™ stored procedure āļ‹āļķāđˆāļ‡āļˆāļ°āļˆāļąāļ”āļāļēāļĢ Database Transaction āđāļĨāļ° Row Locking āļ āļēāļĒāđƒāļ™ Application Layer āđ€āļžāļ·āđˆāļ­āļĢāļąāļšāļ›āļĢāļ°āļāļąāļ™āļāļēāļĢāļ›āđ‰āļ­āļ‡āļāļąāļ™ Race Condition - * āļĄāļĩ retry mechanism āđāļĨāļ° fallback strategies - -### 📊**8.4 āļāļēāļĢāļĢāļēāļĒāļ‡āļēāļ™āđāļĨāļ°āļāļēāļĢāļŠāđˆāļ‡āļ­āļ­āļ (Reporting & Exports)** - -#### **8.4.1 āļ§āļīāļ§āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļĢāļēāļĒāļ‡āļēāļ™ (Reporting Views) (āļˆāļēāļ SQL)** - -āļāļēāļĢāļĢāļēāļĒāļ‡āļēāļ™āļ„āļ§āļĢāļŠāļĢāđ‰āļēāļ‡āļ‚āļķāđ‰āļ™āļˆāļēāļ Views āļ—āļĩāđˆāļāļģāļŦāļ™āļ”āđ„āļ§āđ‰āļĨāđˆāļ§āļ‡āļŦāļ™āđ‰āļēāđƒāļ™āļāļēāļ™āļ‚āđ‰āļ­āļĄāļđāļĨāđ€āļ›āđ‡āļ™āļŦāļĨāļąāļ: - -* v_current_correspondences: āļŠāļģāļŦāļĢāļąāļš revision āļ›āļąāļˆāļˆāļļāļšāļąāļ™āļ—āļąāđ‰āļ‡āļŦāļĄāļ”āļ‚āļ­āļ‡āđ€āļ­āļāļŠāļēāļĢāļ—āļĩāđˆāđ„āļĄāđˆāđƒāļŠāđˆ RFA -* v_current_rfas: āļŠāļģāļŦāļĢāļąāļš revision āļ›āļąāļˆāļˆāļļāļšāļąāļ™āļ—āļąāđ‰āļ‡āļŦāļĄāļ”āļ‚āļ­āļ‡ RFA āđāļĨāļ°āļ‚āđ‰āļ­āļĄāļđāļĨ master -* v_contract_parties_all: āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļ„āļ§āļēāļĄāļŠāļąāļĄāļžāļąāļ™āļ˜āđŒāļ‚āļ­āļ‡ project/contract/organization -* v_user_tasks: āļŠāļģāļŦāļĢāļąāļš Dashboard "āļ‡āļēāļ™āļ‚āļ­āļ‡āļ‰āļąāļ™" -* v_audit_log_details: āļŠāļģāļŦāļĢāļąāļš Activity Feed - -Views āđ€āļŦāļĨāđˆāļēāļ™āļĩāđ‰āļ—āļģāļŦāļ™āđ‰āļēāļ—āļĩāđˆāđ€āļ›āđ‡āļ™āđāļŦāļĨāđˆāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļŦāļĨāļąāļāļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļĢāļēāļĒāļ‡āļēāļ™āļāļąāđˆāļ‡āđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒāđāļĨāļ°āļāļēāļĢāļŠāđˆāļ‡āļ­āļ­āļāļ‚āđ‰āļ­āļĄāļđāļĨ - -#### **8.4.2 āļāļŽāļāļēāļĢāļŠāđˆāļ‡āļ­āļ­āļ (Export Rules)** - -* Export formats: CSV, Excel, PDF. -* āļˆāļąāļ”āđ€āļ•āļĢāļĩāļĒāļĄāļĄāļļāļĄāļĄāļ­āļ‡āļŠāļģāļŦāļĢāļąāļšāļžāļīāļĄāļžāđŒ (Print view). -* āļĢāļ§āļĄāļĨāļīāļ‡āļāđŒāđ„āļ›āļĒāļąāļ‡āļ•āđ‰āļ™āļ—āļēāļ‡ (āđ€āļŠāđˆāļ™ /rfas/:id). - -## ðŸ§Ū **9. āļŸāļĢāļ­āļ™āļ•āđŒāđ€āļ­āļ™āļ”āđŒ: āļĢāļđāļ›āđāļšāļš DataTable āđāļĨāļ°āļŸāļ­āļĢāđŒāļĄ (Frontend: DataTable & Form Patterns)** - -### **9.1 DataTable (Server‑Side)** - -* Endpoint: /api/{module}?page=1&pageSize=20&sort=...&filter=... -* āļ•āđ‰āļ­āļ‡āļĢāļ­āļ‡āļĢāļąāļš: āļāļēāļĢāđāļšāđˆāļ‡āļŦāļ™āđ‰āļē (pagination), āļāļēāļĢāđ€āļĢāļĩāļĒāļ‡āļĨāļģāļ”āļąāļš (sorting), āļāļēāļĢāļ„āđ‰āļ™āļŦāļē (search), āļāļēāļĢāļāļĢāļ­āļ‡ (filters) -* āđāļŠāļ”āļ‡ revision āļĨāđˆāļēāļŠāļļāļ”āđāļšāļš inline āđ€āļŠāļĄāļ­ (āļŠāļģāļŦāļĢāļąāļš RFA/Drawing) - -### **9.2 āļĄāļēāļ•āļĢāļāļēāļ™āļŸāļ­āļĢāđŒāļĄ (Form Standards)** - -* āļ•āđ‰āļ­āļ‡āļĄāļĩāļāļēāļĢāđƒāļŠāđ‰āļ‡āļēāļ™ Dropdowns āđāļšāļšāļ‚āļķāđ‰āļ™āļ•āđˆāļ­āļāļąāļ™ (Dependent dropdowns) (āļ•āļēāļĄāļ—āļĩāđˆāļŠāļ„āļĩāļĄāļēāļĢāļ­āļ‡āļĢāļąāļš): - * Project → Contract Drawing Volumes - * Contract Drawing Category → Sub-Category - * RFA (āļ›āļĢāļ°āđ€āļ āļ— Shop Drawing) → Shop Drawing Revisions āļ—āļĩāđˆāđ€āļŠāļ·āđˆāļ­āļĄāđ‚āļĒāļ‡āđ„āļ”āđ‰ -* **File Upload Security:** āļ•āđ‰āļ­āļ‡āļĢāļ­āļ‡āļĢāļąāļš **Multi-file upload (Drag-and-Drop)** [cite: 5.7] āļžāļĢāđ‰āļ­āļĄ virus scanning feedback -* **File Type Indicators:** UI āļ•āđ‰āļ­āļ‡āļ­āļ™āļļāļāļēāļ•āđƒāļŦāđ‰āļœāļđāđ‰āđƒāļŠāđ‰āļāļģāļŦāļ™āļ”āļ§āđˆāļēāđ„āļŸāļĨāđŒāđƒāļ”āđ€āļ›āđ‡āļ™ **"āđ€āļ­āļāļŠāļēāļĢāļŦāļĨāļąāļ"** āļŦāļĢāļ·āļ­ "āđ€āļ­āļāļŠāļēāļĢāđāļ™āļšāļ›āļĢāļ°āļāļ­āļš" [cite: 5.7] āļžāļĢāđ‰āļ­āļĄāđāļŠāļ”āļ‡ file type icons -* **Security Feedback:** āđāļŠāļ”āļ‡ security warnings āļŠāļģāļŦāļĢāļąāļš file types āļ—āļĩāđˆāđ€āļŠāļĩāđˆāļĒāļ‡āļŦāļĢāļ·āļ­ files āļ—āļĩāđˆ fail virus scan -* āļŠāđˆāļ‡ (Submit) āļœāđˆāļēāļ™ API āļžāļĢāđ‰āļ­āļĄ feedback āđāļšāļš toast - -### **9.3 āļ‚āđ‰āļ­āļāļģāļŦāļ™āļ” Component āđ€āļ‰āļžāļēāļ° (Specific UI Requirements)** - -* **Dashboard - My Tasks:** āļ•āđ‰āļ­āļ‡āļžāļąāļ’āļ™āļē Component āļ•āļēāļĢāļēāļ‡ "āļ‡āļēāļ™āļ‚āļ­āļ‡āļ‰āļąāļ™" (My Tasks)āļ‹āļķāđˆāļ‡āļ”āļķāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļ‡āļēāļ™āļ—āļĩāđˆāļœāļđāđ‰āđƒāļŠāđ‰āļĨāđ‡āļ­āļāļ­āļīāļ™āļ­āļĒāļđāđˆāļ•āđ‰āļ­āļ‡āļĢāļąāļšāļœāļīāļ”āļŠāļ­āļš (Main/Action) āļˆāļēāļ v_user_tasks [cite: 5.3] -* **Workflow Visualization:** āļ•āđ‰āļ­āļ‡āļžāļąāļ’āļ™āļē Component āļŠāļģāļŦāļĢāļąāļšāđāļŠāļ”āļ‡āļœāļĨ Workflow (āđ‚āļ”āļĒāđ€āļ‰āļžāļēāļ° RFA)āļ—āļĩāđˆāđāļŠāļ”āļ‡āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ—āļąāđ‰āļ‡āļŦāļĄāļ”āđ€āļ›āđ‡āļ™āļĨāļģāļ”āļąāļš āđ‚āļ”āļĒāļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ›āļąāļˆāļˆāļļāļšāļąāļ™ (active) āđ€āļ—āđˆāļēāļ™āļąāđ‰āļ™āļ—āļĩāđˆāļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāđ„āļ”āđ‰ āđāļĨāļ°āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ­āļ·āđˆāļ™āđ€āļ›āđ‡āļ™ disabled [cite: 5.6] āļ•āđ‰āļ­āļ‡āļĄāļĩāļ•āļĢāļĢāļāļ°āļŠāļģāļŦāļĢāļąāļš Admin āđƒāļ™āļāļēāļĢ override āļŦāļĢāļ·āļ­āļĒāđ‰āļ­āļ™āļāļĨāļąāļšāļ‚āļąāđ‰āļ™āļ•āļ­āļ™āđ„āļ”āđ‰ [cite: 5.6] -* **Admin Panel:** āļ•āđ‰āļ­āļ‡āļĄāļĩāļŦāļ™āđ‰āļē UI āļŠāļģāļŦāļĢāļąāļš Superadmin/Admin āđ€āļžāļ·āđˆāļ­āļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļĄāļđāļĨāļŦāļĨāļąāļ (Master Data [cite: 4.5]), āļāļēāļĢāđ€āļĢāļīāđˆāļĄāļ•āđ‰āļ™āđƒāļŠāđ‰āļ‡āļēāļ™ (Onboarding [cite: 4.6]), āđāļĨāļ° **āļĢāļđāļ›āđāļšāļšāđ€āļĨāļ‚āļ—āļĩāđˆāđ€āļ­āļāļŠāļēāļĢ (Numbering Formats [cite: 3.10])** -* **Security Dashboard:** āđāļŠāļ”āļ‡ security metrics āđāļĨāļ° audit logs āļŠāļģāļŦāļĢāļąāļš administrators - -## 🧭 **10. āđāļ”āļŠāļšāļ­āļĢāđŒāļ”āđāļĨāļ°āļŸāļĩāļ”āļāļīāļˆāļāļĢāļĢāļĄ (Dashboard & Activity Feed)** - -### **10.1 āļāļēāļĢāđŒāļ”āļšāļ™āđāļ”āļŠāļšāļ­āļĢāđŒāļ” (Dashboard Cards)** - -* āđāļŠāļ”āļ‡ Correspondences, RFAs, Circulations, Shop Drawing Revision āļĨāđˆāļēāļŠāļļāļ” -* āļĢāļ§āļĄāļŠāļĢāļļāļ› KPI (āđ€āļŠāđˆāļ™ "RFAs āļ—āļĩāđˆāļĢāļ­āļāļēāļĢāļ­āļ™āļļāļĄāļąāļ•āļī", "Shop Drawing āļ—āļĩāđˆāļĢāļ­āļāļēāļĢāļ­āļ™āļļāļĄāļąāļ•āļī") [cite: 5.3] -* āļĢāļ§āļĄāļĨāļīāļ‡āļāđŒāļ”āđˆāļ§āļ™āđ„āļ›āļĒāļąāļ‡āđ‚āļĄāļ”āļđāļĨāļ•āđˆāļēāļ‡āđ† -* **Security Metrics:** āđāļŠāļ”āļ‡āļˆāļģāļ™āļ§āļ™ files scanned, security incidents, failed login attempts - -### **10.2 āļŸāļĩāļ”āļāļīāļˆāļāļĢāļĢāļĄ (Activity Feed)** - -* āđāļŠāļ”āļ‡āļĢāļēāļĒāļāļēāļĢ v_audit_log_details āļĨāđˆāļēāļŠāļļāļ” (10 āļĢāļēāļĒāļāļēāļĢ) āļ—āļĩāđˆāđ€āļāļĩāđˆāļĒāļ§āļ‚āđ‰āļ­āļ‡āļāļąāļšāļœāļđāđ‰āđƒāļŠāđ‰ -* āļĢāļ§āļĄ security-related activities (failed logins, permission changes) - -```typescript -// āļ•āļąāļ§āļ­āļĒāđˆāļēāļ‡ API response -[ - { user: 'editor01', action: 'Updated RFA (LCBP3-RFA-001)', time: '2025-11-04T09:30Z' }, - { user: 'system', action: 'Virus scan completed - 0 threats found', time: '2025-11-04T09:25Z' } -] -``` - -## ðŸ›Ąïļ **11. āļ‚āđ‰āļ­āļāļģāļŦāļ™āļ”āļ—āļĩāđˆāđ„āļĄāđˆāđƒāļŠāđˆāļŸāļąāļ‡āļāđŒāļŠāļąāļ™āļāļēāļĢāļ—āļģāļ‡āļēāļ™ (Non-Functional Requirements)** - -āļŠāđˆāļ§āļ™āļ™āļĩāđ‰āļŠāļĢāļļāļ›āļ‚āđ‰āļ­āļāļģāļŦāļ™āļ” Non-Functional āļˆāļēāļ requirements.md āđ€āļžāļ·āđˆāļ­āđƒāļŦāđ‰āļ—āļĩāļĄāļžāļąāļ’āļ™āļēāļ—āļēāļ™ - -* **Audit Log [cite: 6.1]:** āļ—āļļāļāļāļēāļĢāļāļĢāļ°āļ—āļģāļ—āļĩāđˆāļŠāļģāļ„āļąāļ (C/U/D) āļ•āđ‰āļ­āļ‡āļ–āļđāļāļšāļąāļ™āļ—āļķāļāđƒāļ™ audit_logs -* **Performance [cite: 6.4]:** āļ•āđ‰āļ­āļ‡āđƒāļŠāđ‰ Caching āļŠāļģāļŦāļĢāļąāļšāļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļĩāđˆāđ€āļĢāļĩāļĒāļāļšāđˆāļ­āļĒ āđāļĨāļ°āđƒāļŠāđ‰ Pagination -* **Security [cite: 6.5]:** āļ•āđ‰āļ­āļ‡āļĄāļĩ Rate Limiting āđāļĨāļ°āļˆāļąāļ”āļāļēāļĢ Secret āļœāđˆāļēāļ™ docker-compose.yml (āđ„āļĄāđˆāđƒāļŠāđˆ .env) -* **File Security [cite: 3.9.6]:** āļ•āđ‰āļ­āļ‡āļĄāļĩ virus scanning, file type validation, access controls -* **Resilience [cite: 6.5.3]:** āļ•āđ‰āļ­āļ‡āļĄāļĩ circuit breaker, retry mechanisms, graceful degradation -* **Backup & Recovery [cite: 6.6]:** āļ•āđ‰āļ­āļ‡āļĄāļĩāđāļœāļ™āļŠāļģāļĢāļ­āļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļąāđ‰āļ‡ Database (MariaDB) āđāļĨāļ° File Storage (/share/dms-data) āļ­āļĒāđˆāļēāļ‡āļ™āđ‰āļ­āļĒāļ§āļąāļ™āļĨāļ° 1 āļ„āļĢāļąāđ‰āļ‡ -* **Notification Strategy [cite: 6.7]:** āļĢāļ°āļšāļšāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™ (Email/Line) āļ•āđ‰āļ­āļ‡āļ–āļđāļ Trigger āđ€āļĄāļ·āđˆāļ­āļĄāļĩāđ€āļ­āļāļŠāļēāļĢāđƒāļŦāļĄāđˆāļŠāđˆāļ‡āļ–āļķāļ‡, āļĄāļĩāļāļēāļĢāļĄāļ­āļšāļŦāļĄāļēāļĒāļ‡āļēāļ™āđƒāļŦāļĄāđˆ (Circulation), āļŦāļĢāļ·āļ­ (āļ—āļēāļ‡āđ€āļĨāļ·āļ­āļ) āđ€āļĄāļ·āđˆāļ­āļ‡āļēāļ™āđ€āļŠāļĢāđ‡āļˆ/āđƒāļāļĨāđ‰āļ–āļķāļ‡āļāļģāļŦāļ™āļ” -* **Monitoring [cite: 6.8]:** āļ•āđ‰āļ­āļ‡āļĄāļĩ health checks, metrics collection, alerting - -## ✅ **12. āļĄāļēāļ•āļĢāļāļēāļ™āļ—āļĩāđˆāļ™āļģāđ„āļ›āđƒāļŠāđ‰āđāļĨāđ‰āļ§ (āļˆāļēāļ SQL v1.4.0) (Implemented Standards (from SQL v1.4.0))** - -āļŠāđˆāļ§āļ™āļ™āļĩāđ‰āļĒāļ·āļ™āļĒāļąāļ™āļ§āđˆāļēāđāļ™āļ§āļ—āļēāļ‡āļ›āļāļīāļšāļąāļ•āļīāļ—āļĩāđˆāļ”āļĩāļ—āļĩāđˆāļŠāļļāļ”āļ•āđˆāļ­āđ„āļ›āļ™āļĩāđ‰āđ€āļ›āđ‡āļ™āļŠāđˆāļ§āļ™āļŦāļ™āļķāđˆāļ‡āļ‚āļ­āļ‡āļāļēāļĢāļ­āļ­āļāđāļšāļšāļāļēāļ™āļ‚āđ‰āļ­āļĄāļđāļĨāļ­āļĒāļđāđˆāđāļĨāđ‰āļ§ āđāļĨāļ°āļ„āļ§āļĢāļ–āļđāļāļ™āļģāđ„āļ›āđƒāļŠāđ‰āļ›āļĢāļ°āđ‚āļĒāļŠāļ™āđŒ āđ„āļĄāđˆāđƒāļŠāđˆāļŠāļĢāđ‰āļēāļ‡āļ‚āļķāđ‰āļ™āđƒāļŦāļĄāđˆ - -* ✅ **Soft Delete:** āļ™āļģāđ„āļ›āđƒāļŠāđ‰āđāļĨāđ‰āļ§āļœāđˆāļēāļ™āļ„āļ­āļĨāļąāļĄāļ™āđŒ deleted_at āđƒāļ™āļ•āļēāļĢāļēāļ‡āļŠāļģāļ„āļąāļ (āđ€āļŠāđˆāļ™ correspondences, rfas, project_parties) āļ•āļĢāļĢāļāļ°āļāļēāļĢāļ”āļķāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļ•āđ‰āļ­āļ‡āļāļĢāļ­āļ‡ deleted_at IS NULL -* ✅ **Database Indexes:** āļŠāļ„āļĩāļĄāļēāđ„āļ”āđ‰āļĄāļĩāļāļēāļĢāļ—āļģ index āđ„āļ§āđ‰āļ­āļĒāđˆāļēāļ‡āļŦāļ™āļąāļāļŦāļ™āđˆāļ§āļ‡āļšāļ™ foreign keys āđāļĨāļ°āļ„āļ­āļĨāļąāļĄāļ™āđŒāļ—āļĩāđˆāđƒāļŠāđ‰āļ„āđ‰āļ™āļŦāļēāļšāđˆāļ­āļĒ (āđ€āļŠāđˆāļ™ idx_rr_rfa, idx_cor_project, idx_cr_is_current) āđ€āļžāļ·āđˆāļ­āļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļž -* ✅ **āđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡ RBAC:** āļĄāļĩāļĢāļ°āļšāļš users, roles, permissions, user_roles, āđāļĨāļ° user_project_roles āļ—āļĩāđˆāļ„āļĢāļ­āļšāļ„āļĨāļļāļĄāļ­āļĒāļđāđˆāđāļĨāđ‰āļ§ -* ✅ **Data Seeding:** āļ‚āđ‰āļ­āļĄāļđāļĨ Master (roles, permissions, organization_roles, initial users, project parties) āļ–āļđāļāļĢāļ§āļĄāļ­āļĒāļđāđˆāđƒāļ™āļŠāļ„āļĢāļīāļ›āļ•āđŒāļŠāļ„āļĩāļĄāļēāđāļĨāđ‰āļ§ -* ✅ **Application-level Locking:** āđƒāļŠāđ‰ Redis distributed lock āđāļ—āļ™ stored procedure -* ✅ **File Security:** Virus scanning, file type validation, access control -* ✅ **Resilience Patterns:** Circuit breaker, retry, fallback mechanisms -* ✅ **Security Measures:** Input validation, rate limiting, security headers -* ✅ **Monitoring:** Health checks, metrics collection, distributed tracing - -## ðŸ§Đ **13. āļāļēāļĢāļ›āļĢāļąāļšāļ›āļĢāļļāļ‡āļ—āļĩāđˆāđāļ™āļ°āļ™āļģ (āļŠāļģāļŦāļĢāļąāļšāļ­āļ™āļēāļ„āļ•) (Recommended Enhancements (Future))** - -* ✅ āļŠāļĢāđ‰āļēāļ‡ Background job (āđ‚āļ”āļĒāđƒāļŠāđ‰ **n8n** āđ€āļžāļ·āđˆāļ­āđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­āļāļąāļš **Line** [cite: 2.7] āđāļĨāļ°/āļŦāļĢāļ·āļ­āđƒāļŠāđ‰āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™ RFA āļ—āļĩāđˆāđƒāļāļĨāđ‰āļ–āļķāļ‡āļāļģāļŦāļ™āļ” due_date [cite: 6.7]) -* ✅ āđ€āļžāļīāđˆāļĄ job āļĨāđ‰āļēāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāđ€āļ›āđ‡āļ™āļĢāļ°āļĒāļ°āļŠāļģāļŦāļĢāļąāļš attachments āļ—āļĩāđˆāđ„āļĄāđˆāļ–āļđāļāđ€āļŠāļ·āđˆāļ­āļĄāđ‚āļĒāļ‡āļāļąāļš Entity āđƒāļ”āđ† āđ€āļĨāļĒ (āđ„āļŸāļĨāđŒāļāļģāļžāļĢāđ‰āļē) -* 🔄 **AI-Powered Document Classification:** āđƒāļŠāđ‰ machine learning āļŠāļģāļŦāļĢāļąāļš automatic document categorization -* 🔄 **Advanced Analytics:** Predictive analytics āļŠāļģāļŦāļĢāļąāļš workflow optimization -* 🔄 **Mobile App:** Native mobile application āļŠāļģāļŦāļĢāļąāļš field workers -* 🔄 **Blockchain Integration:** āļŠāļģāļŦāļĢāļąāļš document integrity verification āļ—āļĩāđˆāļ•āđ‰āļ­āļ‡āļāļēāļĢāļ„āļ§āļēāļĄāļ›āļĨāļ­āļ”āļ āļąāļĒāļŠāļđāļ‡āļŠāļļāļ” - -## ✅ **14. Summary Checklist for Developers** - -āļāđˆāļ­āļ™āļŠāđˆāļ‡ PR (Pull Request) āļ™āļąāļāļžāļąāļ’āļ™āļēāļ•āđ‰āļ­āļ‡āļ•āļĢāļ§āļˆāļŠāļ­āļšāļŦāļąāļ§āļ‚āđ‰āļ­āļ•āđˆāļ­āđ„āļ›āļ™āļĩāđ‰: - -* [ ] **Security:** āđ„āļĄāđˆāļĄāļĩ Secrets āđƒāļ™ Code, āđƒāļŠāđ‰ `docker-compose.override.yml` āđāļĨāđ‰āļ§ -* [ ] **Concurrency:** āđƒāļŠāđ‰ Optimistic Lock āđƒāļ™ Entity āļ—āļĩāđˆāđ€āļŠāļĩāđˆāļĒāļ‡ Race Condition āđāļĨāđ‰āļ§ -* [ ] **Idempotency:** API āļĢāļ­āļ‡āļĢāļąāļš Idempotency Key āđāļĨāđ‰āļ§ -* [ ] **File Upload:** āđƒāļŠāđ‰ Flow Two-Phase (Temp -> Perm) āđāļĨāđ‰āļ§ -* [ ] **Mobile:** āļŦāļ™āđ‰āļēāļˆāļ­āđāļŠāļ”āļ‡āļœāļĨāđāļšāļš Card View āļšāļ™āļĄāļ·āļ­āļ–āļ·āļ­āđ„āļ”āđ‰āļ–āļđāļāļ•āđ‰āļ­āļ‡ -* [ ] **Performance:** āļŠāļĢāđ‰āļēāļ‡ Index āļŠāļģāļŦāļĢāļąāļš JSON Virtual Columns āđāļĨāđ‰āļ§ (āļ–āđ‰āļēāļĄāļĩ) - ---- - -## 📋 **15. Summary of Key Changes from Previous Version** - -### **Security Enhancements:** - -1. **File Upload Security** - Virus scanning, file type validation, access controls -2. **Input Validation** - OWASP Top 10 protection, XSS/CSRF prevention -3. **Rate Limiting** - Comprehensive rate limiting strategy -4. **Secrets Management** - Secure handling of sensitive configuration - -### **Architecture Improvements:** - -1. **Document Numbering** - Changed from Stored Procedure to Application-level Locking -2. **Resilience Patterns** - Circuit breaker, retry mechanisms, fallback strategies -3. **Monitoring & Observability** - Health checks, metrics, distributed tracing -4. **Caching Strategy** - Comprehensive caching with proper invalidation - -### **Performance Targets :** - -1. **API Response Time** - < 200ms (90th percentile) -2. **Search Performance** - < 500ms -3. **File Upload** - < 30 seconds for 50MB files -4. **Cache Hit Ratio** - > 80% - -### **Operational Excellence:** - -1. **Disaster Recovery** - RTO < 4 hours, RPO < 1 hour -2. **Backup Procedures** - Comprehensive backup and restoration -3. **Security Testing** - Penetration testing and security audits -4. **Performance Testing** - Load testing with realistic workloads - -āđ€āļ­āļāļŠāļēāļĢāļ™āļĩāđ‰āļŠāļ°āļ—āđ‰āļ­āļ™āļ–āļķāļ‡āļ„āļ§āļēāļĄāļĄāļļāđˆāļ‡āļĄāļąāđˆāļ™āđƒāļ™āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡āļĢāļ°āļšāļšāļ—āļĩāđˆāļĄāļĩāļ„āļ§āļēāļĄāļ›āļĨāļ­āļ”āļ āļąāļĒ, āļĄāļĩāļ„āļ§āļēāļĄāļ—āļ™āļ—āļēāļ™, āđāļĨāļ°āļĄāļĩāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāļŠāļđāļ‡ āļžāļĢāđ‰āļ­āļĄāļĢāļ­āļ‡āļĢāļąāļšāļāļēāļĢāđ€āļ•āļīāļšāđ‚āļ•āđƒāļ™āļ­āļ™āļēāļ„āļ•āđāļĨāļ°āļ„āļ§āļēāļĄāļ•āđ‰āļ­āļ‡āļāļēāļĢāļ—āļēāļ‡āļ˜āļļāļĢāļāļīāļˆāļ—āļĩāđˆāđ€āļ›āļĨāļĩāđˆāļĒāļ™āđāļ›āļĨāļ‡āđ„āļ› - -**āļŦāļĄāļēāļĒāđ€āļŦāļ•āļļ:** āđāļ™āļ§āļ—āļēāļ‡āļ™āļĩāđ‰āļˆāļ°āļ–āļđāļāļ—āļšāļ—āļ§āļ™āđāļĨāļ°āļ›āļĢāļąāļšāļ›āļĢāļļāļ‡āđ€āļ›āđ‡āļ™āļĢāļ°āļĒāļ°āļ•āļēāļĄ feedback āļˆāļēāļāļ—āļĩāļĄāļžāļąāļ’āļ™āļēāđāļĨāļ°āļ„āļ§āļēāļĄāļ•āđ‰āļ­āļ‡āļāļēāļĢāļ—āļēāļ‡āļ˜āļļāļĢāļāļīāļˆāļ—āļĩāđˆāđ€āļ›āļĨāļĩāđˆāļĒāļ™āđāļ›āļĨāļ‡āđ„āļ› - -## **Document Control:** - -* **Document:** FullStackJS v1.4.2 -* **Version:** 1.4 -* **Date:** 2025-11-19 -* **Author:** NAP LCBP3-DMS & Gemini -* **Status:** FINAL -* **Classification:** Internal Technical Documentation -* **Approved By:** Nattanin - ---- - -`End of FullStackJS Guidelines v1.4.2` diff --git a/Documnets/Project/1.4.2/2_Backend_Plan_V1_4_2.md b/Documnets/Project/1.4.2/2_Backend_Plan_V1_4_2.md deleted file mode 100644 index 547fe00..0000000 --- a/Documnets/Project/1.4.2/2_Backend_Plan_V1_4_2.md +++ /dev/null @@ -1,551 +0,0 @@ -# 📋 **āđāļœāļ™āļāļēāļĢāļžāļąāļ’āļ™āļē Backend (NestJS) - LCBP3-DMS v1.4.2 (āļ‰āļšāļąāļšāļ›āļĢāļąāļšāļ›āļĢāļļāļ‡)** - -**āļŠāļ–āļēāļ™āļ°:** FINAL GUIDELINE -**āļ§āļąāļ™āļ—āļĩāđˆ:** 2025-11-19 -**āļ­āđ‰āļēāļ‡āļ­āļīāļ‡:** Requirements v1.4.2 & FullStackJS Guidelines v1.4.2 -**Classification:** Internal Technical Documentation - ------ - -## ðŸŽŊ **āļ āļēāļžāļĢāļ§āļĄāđ‚āļ„āļĢāļ‡āļāļēāļĢ** - -āļžāļąāļ’āļ™āļē Backend āļŠāļģāļŦāļĢāļąāļšāļĢāļ°āļšāļšāļšāļĢāļīāļŦāļēāļĢāļˆāļąāļ”āļāļēāļĢāđ€āļ­āļāļŠāļēāļĢāđ‚āļ„āļĢāļ‡āļāļēāļĢ (Document Management System) āļ—āļĩāđˆāļĄāļĩāļ„āļ§āļēāļĄāļ›āļĨāļ­āļ”āļ āļąāļĒāļŠāļđāļ‡ āļĢāļ­āļ‡āļĢāļąāļšāļāļēāļĢāļ—āļģāļ‡āļēāļ™āļžāļĢāđ‰āļ­āļĄāļāļąāļ™ (Concurrency) āđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āļ–āļđāļāļ•āđ‰āļ­āļ‡āđāļĄāđˆāļ™āļĒāļģ āļĄāļĩāļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄāļ—āļĩāđˆāļĒāļ·āļ”āļŦāļĒāļļāđˆāļ™āļ•āđˆāļ­āļāļēāļĢāļ‚āļĒāļēāļĒāļ•āļąāļ§ āđāļĨāļ°āļĢāļ­āļ‡āļĢāļąāļšāļāļēāļĢāļˆāļąāļ”āļāļēāļĢāđ€āļ­āļāļŠāļēāļĢāļ—āļĩāđˆāļ‹āļąāļšāļ‹āđ‰āļ­āļ™ āļĄāļĩāļĢāļ°āļšāļš Workflow āļāļēāļĢāļ­āļ™āļļāļĄāļąāļ•āļī āđāļĨāļ°āļāļēāļĢāļ„āļ§āļšāļ„āļļāļĄāļŠāļīāļ—āļ˜āļīāđŒāđāļšāļš RBAC 4 āļĢāļ°āļ”āļąāļš āļžāļĢāđ‰āļ­āļĄāļĄāļēāļ•āļĢāļāļēāļĢāļ„āļ§āļēāļĄāļ›āļĨāļ­āļ”āļ āļąāļĒāļ—āļĩāđˆāļ—āļąāļ™āļŠāļĄāļąāļĒ - ------ - -## 📐 **āļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄāļĢāļ°āļšāļš** - -### **Technology Stack (Updated)** - -- **Framework:** NestJS (TypeScript, ESM) -- **Database:** MariaDB 10.11 (āđƒāļŠāđ‰ Virtual Columns) -- **ORM:** TypeORM (āđƒāļŠāđ‰ Optimistic Locking) -- **Authentication:** JWT + Passport -- **Authorization:** CASL (RBAC 4-level) -- **File Upload:** Multer + Virus Scanning (ClamAV) + Two-Phase Storage -- **Search:** Elasticsearch -- **Notification:** Nodemailer + n8n (Line Integration) + BullMQ Queue -- **Caching/Locking:** Redis (Redlock) āļŠāļģāļŦāļĢāļąāļš Distributed Locking -- **Queue:** BullMQ (Redis) āļŠāļģāļŦāļĢāļąāļš Notification Batching āđāļĨāļ° Async Jobs -- **Resilience:** Circuit Breaker, Retry Patterns -- **Security:** Helmet, CSRF Protection, Rate Limiting, Idempotency -- **Monitoring:** Winston, Health Checks, Metrics -- **Scheduling:** @nestjs/schedule (Cron Jobs) -- **Documentation:** Swagger -- **Validation:** Zod / Class-validator - -### **āđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡āđ‚āļĄāļ”āļđāļĨ (Domain-Driven)** - -```tree -src/ -├── common/ # Shared Module -│ ├── auth/ # JWT, Guards, RBAC -│ ├── config/ # Configuration Management -│ ├── decorators/ # @RequirePermission, @RateLimit -│ ├── entities/ # Base Entities -│ ├── exceptions/ # Global Filters -│ ├── file-storage/ # FileStorageService (Virus Scanning + Two-Phase) -│ ├── guards/ # RBAC Guard, RateLimitGuard -│ ├── interceptors/ # Audit, Transform, Performance, Idempotency -│ ├── resilience/ # Circuit Breaker, Retry Patterns -│ ├── security/ # Input Validation, XSS Protection -│ ├── idempotency/ # [New] Idempotency Logic -│ └── maintenance/ # [New] Maintenance Mode Guard -├── modules/ -│ ├── user/ # Users, Roles, Permissions -│ ├── project/ # Projects, Contracts, Organizations -│ ├── master/ # Master Data Management -│ ├── correspondence/ # Correspondence Management -│ ├── rfa/ # RFA & Workflows -│ ├── drawing/ # Shop/Contract Drawings -│ ├── circulation/ # Internal Circulation -│ ├── transmittal/ # Transmittals -│ ├── search/ # Elasticsearch -│ ├── monitoring/ # Metrics, Health Checks -│ ├── workflow-engine/ # [New] Unified Workflow Logic -│ ├── document-numbering/ # [Update] Double-Locking Logic -│ ├── notification/ # [Update] Queue & Digest -│ └── file-storage/ # [Update] Two-Phase Commit -└── database/ # Migrations & Seeds -``` - ------ - -## 🗓ïļ **āđāļœāļ™āļāļēāļĢāļžāļąāļ’āļ™āļēāđāļšāļš Phase-Based** - -- *(Dependency Diagram āļ–āļđāļāļĨāļ°āđ„āļ§āđ‰āđ€āļžāļ·āđˆāļ­āļ›āļĢāļ°āļŦāļĒāļąāļ”āļžāļ·āđ‰āļ™āļ—āļĩāđˆ āđ€āļ™āļ·āđˆāļ­āļ‡āļˆāļēāļāļĄāļĩāļāļēāļĢāļ­āđ‰āļēāļ‡āļ­āļīāļ‡āļˆāļēāļāđāļœāļ™āđ€āļ”āļīāļĄ)* - -## **Phase 0: Infrastructure & Configuration (āļŠāļąāļ›āļ”āļēāļŦāđŒāļ—āļĩāđˆ 1)** - -**Milestone:** āđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡āļžāļ·āđ‰āļ™āļāļēāļ™āļžāļĢāđ‰āļ­āļĄ āļĢāļ­āļ‡āļĢāļąāļš Secrets āļ—āļĩāđˆāļ›āļĨāļ­āļ”āļ āļąāļĒ āđāļĨāļ° Redis āļžāļĢāđ‰āļ­āļĄāđƒāļŠāđ‰āļ‡āļēāļ™ - -### **Phase 0: Tasks** - -- **[ ] T0.1 Secure Configuration Setup** - - - [ ] āļ›āļĢāļąāļšāļ›āļĢāļļāļ‡ `ConfigModule` āđƒāļŦāđ‰āļĢāļ­āļ‡āļĢāļąāļšāļāļēāļĢāļ­āđˆāļēāļ™āļ„āđˆāļēāļˆāļēāļ Environment Variables - - [ ] āļŠāļĢāđ‰āļēāļ‡ Template `docker-compose.override.yml.example` āļŠāļģāļŦāļĢāļąāļš Dev - - [ ] Validate Config āļ”āđ‰āļ§āļĒ Joi/Zod āļ•āļ­āļ™ Start App (Throw error āļ–āđ‰āļēāļ‚āļēāļ” Secrets) - - [ ] **Security:** Setup network segmentation āđāļĨāļ° firewall rules - - [ ] **Deliverable:** Configuration Management āļžāļĢāđ‰āļ­āļĄāđƒāļŠāđ‰āļ‡āļēāļ™āļ­āļĒāđˆāļēāļ‡āļ›āļĨāļ­āļ”āļ āļąāļĒ - - [ ] **Dependencies:** None (Task āđ€āļĢāļīāđˆāļĄāļ•āđ‰āļ™) - -- **[ ] T0.2 Redis & Queue Infrastructure** - - - [ ] Setup Redis Container - - [ ] Setup BullMQ Module āđƒāļ™ NestJS āļŠāļģāļŦāļĢāļąāļšāļˆāļąāļ”āļāļēāļĢ Background Jobs - - [ ] Setup Redis Client āļŠāļģāļŦāļĢāļąāļš Distributed Lock (Redlock) - - [ ] **Security:** Setup Redis authentication āđāļĨāļ° encryption - - [ ] **Deliverable:** Redis āđāļĨāļ° Queue System āļžāļĢāđ‰āļ­āļĄāđƒāļŠāđ‰āļ‡āļēāļ™ - - [ ] **Dependencies:** T0.1 - -- **[ ] T0.3 Setup Database Connection** - - - [ ] Import SQL Schema v1.4.2 āđ€āļ‚āđ‰āļē MariaDB - - [ ] Run Seed Data (organizations, users, roles, permissions) - - [ ] Configure TypeORM āđƒāļ™ AppModule - - [ ] **Security:** Setup database connection encryption - - [ ] āļ—āļ”āļŠāļ­āļš Connection - - [ ] **Deliverable:** Database āļžāļĢāđ‰āļ­āļĄāđƒāļŠāđ‰āļ‡āļēāļ™, āļĄāļĩ Seed Data - - [ ] **Dependencies:** T0.1 - -- **[ ] T0.4 Setup Git Repository** - - - [ ] āļŠāļĢāđ‰āļēāļ‡ Repository āđƒāļ™ Gitea (git.np-dms.work) - - [ ] Setup .gitignore, README.md, SECURITY.md - - [ ] Commit Initial Project - - [ ] **Deliverable:** Code āļ­āļĒāļđāđˆāđƒāļ™ Version Control - - [ ] **Dependencies:** T0.1, T0.2, T0.3 - ------ - -## **Phase 1: Core Foundation & Security (āļŠāļąāļ›āļ”āļēāļŦāđŒāļ—āļĩāđˆ 2-3)** - -**Milestone:** āļĢāļ°āļšāļš Authentication, Authorization, Idempotency āļžāļ·āđ‰āļ™āļāļēāļ™ āđāļĨāļ° Security Baseline - -### **Phase 1: Tasks** - -- **[ ] T1.1 CommonModule - Base Infrastructure** - - - [ ] āļŠāļĢāđ‰āļēāļ‡ Base Entity (id, created\_at, updated\_at, deleted\_at) - - [ ] āļŠāļĢāđ‰āļēāļ‡ Global Exception Filter (āđ„āļĄāđˆāđ€āļ›āļīāļ”āđ€āļœāļĒ sensitive information) - - [ ] āļŠāļĢāđ‰āļēāļ‡ Response Transform Interceptor - - [ ] āļŠāļĢāđ‰āļēāļ‡ Audit Log Interceptor - - [ ] **[New] Idempotency Interceptor:** āļ•āļĢāļ§āļˆāļŠāļ­āļš Header `Idempotency-Key` āđāļĨāļ° Cache Response āđ€āļ”āļīāļĄāđƒāļ™ Redis - - [ ] **[New] Maintenance Mode Middleware:** āļ•āļĢāļ§āļˆāļŠāļ­āļš Flag āđƒāļ™ **Redis Key** āđ€āļžāļ·āđˆāļ­ Block API āļĢāļ°āļŦāļ§āđˆāļēāļ‡āļ›āļĢāļąāļšāļ›āļĢāļļāļ‡āļĢāļ°āļšāļš **(Admin āđƒāļŠāđ‰ Redis/Admin UI āđƒāļ™āļāļēāļĢ Toggle āļŠāļ–āļēāļ™āļ°)** - - [ ] āļŠāļĢāđ‰āļēāļ‡ RequestContextService - āļŠāļģāļŦāļĢāļąāļšāđ€āļāđ‡āļšāļ‚āđ‰āļ­āļĄāļđāļĨāļĢāļ°āļŦāļ§āđˆāļēāļ‡ Request - - [ ] āļŠāļĢāđ‰āļēāļ‡ ConfigService - Centralized configuration management - - [ ] āļŠāļĢāđ‰āļēāļ‡ CryptoService - āļŠāļģāļŦāļĢāļąāļš encryption/decryption - - [ ] **Security:** Implement input validation pipeline - - [ ] **Deliverable:** Common Services āļžāļĢāđ‰āļ­āļĄāđƒāļŠāđ‰ āļĢāļ§āļĄāļ–āļķāļ‡ Idempotency āđāļĨāļ° Maintenance Mode - - [ ] **Dependencies:** T0.2, T0.3 - -- **[ ] T1.2 AuthModule - JWT Authentication** - - - [ ] āļŠāļĢāđ‰āļēāļ‡ Entity: User - - [ ] āļŠāļĢāđ‰āļēāļ‡ AuthService: - - [ ] login(username, password) → JWT Token - - [ ] validateUser(username, password) → User | null - - [ ] Password Hashing (bcrypt) + salt - - [ ] āļŠāļĢāđ‰āļēāļ‡ JWT Strategy (Passport) - - [ ] āļŠāļĢāđ‰āļēāļ‡ JwtAuthGuard - - [ ] āļŠāļĢāđ‰āļēāļ‡ Controllers: - - [ ] POST /auth/login → { access\_token, refresh\_token } - - [ ] POST /auth/register → Create User (Admin only) - - [ ] POST /auth/refresh → Refresh token - - [ ] POST /auth/logout → Revoke token - - [ ] GET /auth/profile (Protected) - - [ ] **Security:** Implement rate limiting āļŠāļģāļŦāļĢāļąāļš authentication endpoints - - [ ] **Deliverable:** āļĨāđ‡āļ­āļāļ­āļīāļ™/āļĨāđ‡āļ­āļāđ€āļ­āļēāļ•āđŒāļ—āļģāļ‡āļēāļ™āđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āļ›āļĨāļ­āļ”āļ āļąāļĒ - - [ ] **Dependencies:** T1.1, T0.3 - -- **[ ] T1.3 UserModule - User Management** - - - [ ] āļŠāļĢāđ‰āļēāļ‡ Entities: User, Role, Permission, UserRole, UserAssignment, **UserPreference** - - [ ] āļŠāļĢāđ‰āļēāļ‡ UserService CRUD (āļžāļĢāđ‰āļ­āļĄ soft delete) - - [ ] āļŠāļĢāđ‰āļēāļ‡ RoleService CRUD - - [ ] āļŠāļĢāđ‰āļēāļ‡ PermissionService (Read-Only, āļˆāļēāļ Seed) - - [ ] āļŠāļĢāđ‰āļēāļ‡ UserAssignmentService - āļŠāļģāļŦāļĢāļąāļšāļˆāļąāļ”āļāļēāļĢ user assignments āļ•āļēāļĄ scope - - [ ] āļŠāļĢāđ‰āļēāļ‡ **UserPreferenceService** - āļŠāļģāļŦāļĢāļąāļšāļˆāļąāļ”āļāļēāļĢāļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļē Notification āđāļĨāļ° UI - - [ ] āļŠāļĢāđ‰āļēāļ‡ Controllers: - - [ ] GET /users → List Users (Paginated) - - [ ] GET /users/:id → User Detail - - [ ] POST /users → Create User (āļ•āđ‰āļ­āļ‡āļšāļąāļ‡āļ„āļąāļšāđ€āļ›āļĨāļĩāđˆāļĒāļ™ password āļ„āļĢāļąāđ‰āļ‡āđāļĢāļ) - - [ ] PUT /users/:id → Update User - - [ ] DELETE /users/:id → Soft Delete - - [ ] GET /roles → List Roles - - [ ] POST /roles → Create Role (Admin) - - [ ] PUT /roles/:id/permissions → Assign Permissions - - [ ] **Security:** Implement permission checks āļŠāļģāļŦāļĢāļąāļš user management - - [ ] **Deliverable:** āļˆāļąāļ”āļāļēāļĢāļœāļđāđ‰āđƒāļŠāđ‰, Role, āđāļĨāļ° Preferences āđ„āļ”āđ‰ - - [ ] **Dependencies:** T1.1, T1.2 - -- **[ ] T1.4 RBAC Guard - 4-Level Authorization** - - - [ ] āļŠāļĢāđ‰āļēāļ‡ @RequirePermission() Decorator - - [ ] āļŠāļĢāđ‰āļēāļ‡ RbacGuard āļ—āļĩāđˆāļ•āļĢāļ§āļˆāļŠāļ­āļš 4 āļĢāļ°āļ”āļąāļš: - - [ ] Global Permissions - - [ ] Organization Permissions - - [ ] Project Permissions - - [ ] Contract Permissions - - [ ] Permission Hierarchy Logic - - [ ] Integration āļāļąāļš CASL - - [ ] **Security:** Implement audit logging āļŠāļģāļŦāļĢāļąāļš permission checks - - [ ] **Deliverable:** āļĢāļ°āļšāļšāļŠāļīāļ—āļ˜āļīāđŒāļ—āļģāļ‡āļēāļ™āđ„āļ”āđ‰āļ—āļąāđ‰āļ‡ 4 āļĢāļ°āļ”āļąāļš - - [ ] **Dependencies:** T1.1, T1.3 - -- **[ ] T1.5 ProjectModule - Base Structures** - - - [ ] āļŠāļĢāđ‰āļēāļ‡ Entities: - - [ ] Organization - - [ ] Project - - [ ] Contract - - [ ] ProjectOrganization (Junction) - - [ ] ContractOrganization (Junction) - - [ ] āļŠāļĢāđ‰āļēāļ‡ Services & Controllers: - - [ ] GET /organizations → List - - [ ] POST /projects → Create (Superadmin) - - [ ] GET /projects/:id/contracts → List Contracts - - [ ] POST /projects/:id/contracts → Create Contract - - [ ] **Security:** Implement data isolation āļĢāļ°āļŦāļ§āđˆāļēāļ‡ organizations - - [ ] **Deliverable:** āļˆāļąāļ”āļāļēāļĢāđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡āđ‚āļ›āļĢāđ€āļˆāļāļ•āđŒāđ„āļ”āđ‰ - - [ ] **Dependencies:** T1.1, T1.2, T0.3 - ------ - -## **Phase 2: High-Integrity Data & File Management (āļŠāļąāļ›āļ”āļēāļŦāđŒāļ—āļĩāđˆ 4)** - -**Milestone:** Master Data, āļĢāļ°āļšāļšāļˆāļąāļ”āļāļēāļĢāđ„āļŸāļĨāđŒāđāļšāļš Transactional, Document Numbering āļ—āļĩāđˆāđ„āļĄāđˆāļĄāļĩ Race Condition, JSON details system āļžāļĢāđ‰āļ­āļĄāđƒāļŠāđ‰āļ‡āļēāļ™ - -### **Phase 2: Tasks** - -- **[ ] T2.1 Virtual Columns for JSON** - - - [ ] āļ­āļ­āļāđāļšāļš Migration Script āļŠāļģāļŦāļĢāļąāļšāļ•āļēāļĢāļēāļ‡āļ—āļĩāđˆāļĄāļĩ JSON Details - - [ ] āđ€āļžāļīāđˆāļĄ **Generated Columns (Virtual)** āļŠāļģāļŦāļĢāļąāļšāļŸāļīāļĨāļ”āđŒāļ—āļĩāđˆāđƒāļŠāđ‰ Search āļšāđˆāļ­āļĒāđ† (āđ€āļŠāđˆāļ™ `project_id`, `type`) āļžāļĢāđ‰āļ­āļĄ Index - - [ ] **Security:** Implement admin-only access āļŠāļģāļŦāļĢāļąāļš master data - - [ ] **Deliverable:** JSON Data Search Performance āļ”āļĩāļ‚āļķāđ‰āļ™ - - [ ] **Dependencies:** T0.3, T1.1, T1.5 - -- **[ ] T2.2 FileStorageService - Two-Phase Storage** - - - [ ] āļŠāļĢāđ‰āļēāļ‡ Attachment Entity - - [ ] āļŠāļĢāđ‰āļēāļ‡ FileStorageService: - - [ ] **Phase 1 (Upload):** API āļĢāļąāļšāđ„āļŸāļĨāđŒ → Scan Virus → Save āļĨāļ‡ `temp/` → Return `temp_id` - - [ ] **Phase 2 (Commit):** Method `commitFiles(tempIds[])` → āļĒāđ‰āļēāļĒāļˆāļēāļ `temp/` āđ„āļ› `permanent/{YYYY}/{MM}/` → Update DB - - [ ] File type validation (white-list: PDF, DWG, DOCX, XLSX, PPTX, ZIP) - - [ ] File size check (max 50MB) - - [ ] Generate checksum (SHA-256) - - [ ] **Cleanup Job:** āļŠāļĢāđ‰āļēāļ‡ Cron Job āļĨāļšāđ„āļŸāļĨāđŒāđƒāļ™ `temp/` āļ—āļĩāđˆāļ„āđ‰āļēāļ‡āđ€āļāļīāļ™ 24 āļŠāļĄ. **āđ‚āļ”āļĒāļ•āļĢāļ§āļˆāļŠāļ­āļšāļˆāļēāļāļ„āļ­āļĨāļąāļĄāļ™āđŒ `expires_at` āđƒāļ™āļ•āļēāļĢāļēāļ‡ `attachments`** - - [ ] āļŠāļĢāđ‰āļēāļ‡ Controller: - - [ ] POST /files/upload → { temp\_id } (Protected) - - [ ] POST /files/commit → { attachment\_id, url } (Protected) - - [ ] GET /files/:id/download → File Stream (Protected + Expiration) - - [ ] **Security:** Access Control - āļ•āļĢāļ§āļˆāļŠāļ­āļšāļŠāļīāļ—āļ˜āļīāđŒāļœāđˆāļēāļ™ Junction Table - - [ ] **Deliverable:** āļ­āļąāļ›āđ‚āļŦāļĨāļ”/āļ”āļēāļ§āļ™āđŒāđ‚āļŦāļĨāļ”āđ„āļŸāļĨāđŒāđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āļ›āļĨāļ­āļ”āļ āļąāļĒ āđāļšāļš Transactional - - [ ] **Dependencies:** T1.1, T1.4 - -- **[ ] T2.3 DocumentNumberingModule - Double-Lock Mechanism** - - - [ ] āļŠāļĢāđ‰āļēāļ‡ Entities: - - [ ] DocumentNumberFormat - - [ ] DocumentNumberCounter - - [ ] āļŠāļĢāđ‰āļēāļ‡ DocumentNumberingService: - - [ ] generateNextNumber(projectId, orgId, typeId, year) → string - - [ ] āđƒāļŠāđ‰ **Double-Lock Mechanism**: - 1. Acquire **Redis Lock** (Key: `doc_num:{project}:{type}`) - 2. Read DB & Calculate Next Number - 3. Update DB with **Optimistic Lock** Check (āđƒāļŠāđ‰ `@VersionColumn()`) - 4. Release Redis Lock - 5. Retry on Failure āļ”āđ‰āļ§āļĒ exponential backoff - - [ ] Fallback mechanism āđ€āļĄāļ·āđˆāļ­āļāļēāļĢāļ‚āļ­āđ€āļĨāļ‚āļĨāđ‰āļĄāđ€āļŦāļĨāļ§ - - [ ] Format āļ•āļēāļĄ Template: {ORG\_CODE}-{TYPE\_CODE}-{YEAR\_SHORT}-{SEQ:4} - - **āđ„āļĄāđˆāļĄāļĩ Controller** (Internal Service āđ€āļ—āđˆāļēāļ™āļąāđ‰āļ™) - - [ ] **Security:** Implement audit log āļ—āļļāļāļ„āļĢāļąāđ‰āļ‡āļ—āļĩāđˆāļĄāļĩāļāļēāļĢ generate āđ€āļĨāļ‚āļ—āļĩāđˆ - - [ ] **Deliverable:** Service āļŠāļĢāđ‰āļēāļ‡āđ€āļĨāļ‚āļ—āļĩāđˆāđ€āļ­āļāļŠāļēāļĢāđ„āļ”āđ‰āļ–āļđāļāļ•āđ‰āļ­āļ‡āđāļĨāļ°āļ›āļĨāļ­āļ”āļ āļąāļĒ āđ„āļĄāđˆāļĄāļĩ Race Condition - - [ ] **Dependencies:** T1.1, T0.3 - -- **[ ] T2.4 SecurityModule - Enhanced Security** - - - [ ] āļŠāļĢāđ‰āļēāļ‡ Input Validation Service: - - [ ] XSS Prevention - - [ ] SQL Injection Prevention - - [ ] CSRF Protection - - [ ] āļŠāļĢāđ‰āļēāļ‡ RateLimitGuard: - - [ ] Implement rate limiting āļ•āļēāļĄ strategy (anonymous: 100/hr, authenticated: 500-5000/hr) - - [ ] Different limits āļŠāļģāļŦāļĢāļąāļš endpoints āļ•āđˆāļēāļ‡āđ† - - [ ] āļŠāļĢāđ‰āļēāļ‡ Security Headers Middleware - - [ ] **Security:** Implement content security policy (CSP) - - [ ] **Deliverable:** Security layers āļ—āļģāļ‡āļēāļ™āđ„āļ”āđ‰ - - [ ] **Dependencies:** T1.1 - -- **[ ] T2.5 JSON Details & Schema Management** - - - [ ] T2.5.1 JsonSchemaModule - Schema Management: āļŠāļĢāđ‰āļēāļ‡ Service āļŠāļģāļŦāļĢāļąāļš Validate, get, register JSON schemas - - [ ] T2.5.2 DetailsService - Data Processing: āļŠāļĢāđ‰āļēāļ‡ Service āļŠāļģāļŦāļĢāļąāļš sanitize, transform, compress/decompress JSON - - [ ] T2.5.3 JSON Security & Validation: Implement security checks āđāļĨāļ° validation rules - - [ ] **Deliverable:** JSON schema system āļ—āļģāļ‡āļēāļ™āđ„āļ”āđ‰ - - [ ] **Dependencies:** T1.1 - ------ - -## **Phase 3: Unified Workflow Engine (āļŠāļąāļ›āļ”āļēāļŦāđŒāļ—āļĩāđˆ 5-6)** - -**Milestone:** āļĢāļ°āļšāļš Workflow āļāļĨāļēāļ‡āļ—āļĩāđˆāļĢāļ­āļ‡āļĢāļąāļšāļ—āļąāđ‰āļ‡ Routing āļ›āļāļ•āļī āđāļĨāļ° RFA - -### **Phase 3: Tasks** - -- **[ ] T3.1 WorkflowEngineModule (New)** - - - [ ] āļ­āļ­āļāđāļšāļš Generic Schema āļŠāļģāļŦāļĢāļąāļš Workflow State Machine - - [ ] Implement Service: `initializeWorkflow()`, `processAction()`, `getNextStep()` - - [ ] āļĢāļ­āļ‡āļĢāļąāļš Logic āļāļēāļĢ "āļ‚āđ‰āļēāļĄāļ‚āļąāđ‰āļ™āļ•āļ­āļ™" āđāļĨāļ° "āļŠāđˆāļ‡āļāļĨāļąāļš" āļ āļēāļĒāđƒāļ™ Engine āđ€āļ”āļĩāļĒāļ§ - - [ ] **Security:** Implement audit logging āļŠāļģāļŦāļĢāļąāļš workflow actions - - [ ] **Deliverable:** Unified Workflow Engine āļžāļĢāđ‰āļ­āļĄāđƒāļŠāđ‰āļ‡āļēāļ™ - - [ ] **Dependencies:** T1.1 - -- **[ ] T3.2 CorrespondenceModule - Basic CRUD** - - - [ ] āļŠāļĢāđ‰āļēāļ‡ Entities (Correspondence, Revision, Recipient, Tag, Reference, Attachment) - - [ ] āļŠāļĢāđ‰āļēāļ‡ CorrespondenceService (Create with Document Numbering, Update with new Revision, Soft Delete) - - [ ] āļŠāļĢāđ‰āļēāļ‡ Controllers (POST/GET/PUT/DELETE /correspondences) - - [ ] **Security:** Implement permission checks āļŠāļģāļŦāļĢāļąāļš document access - - [ ] **Deliverable:** āļŠāļĢāđ‰āļēāļ‡/āđāļāđ‰āđ„āļ‚/āļ”āļđāđ€āļ­āļāļŠāļēāļĢāđ„āļ”āđ‰ - - [ ] **Dependencies:** T1.1, T1.2, T1.3, T1.4, T1.5, T2.3, T2.2, T2.5 - -- **[ ] T3.3 CorrespondenceModule - Advanced Features** - - - [ ] Implement Status Transitions (DRAFT → SUBMITTED) - - [ ] Implement References (Link Documents) - - [ ] Implement Search (Basic) - - [ ] **Security:** Implement state transition validation - - [ ] **Deliverable:** Workflow āļžāļ·āđ‰āļ™āļāļēāļ™āļ—āļģāļ‡āļēāļ™āđ„āļ”āđ‰ - - [ ] **Dependencies:** T3.2 - -- **[ ] T3.4 Correspondence Integration with Workflow** - - - [ ] āđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­ `CorrespondenceService` āđ€āļ‚āđ‰āļēāļāļąāļš `WorkflowEngineModule` - - [ ] āļĒāđ‰āļēāļĒ Logic āļāļēāļĢ Routing āđ€āļ”āļīāļĄāļĄāļēāđƒāļŠāđ‰ Engine āđƒāļŦāļĄāđˆ - - [ ] āļŠāļĢāđ‰āļēāļ‡ API endpoints āļŠāļģāļŦāļĢāļąāļš Frontend (Templates, Pending Tasks, Bulk Action) - - [ ] **Security:** Implement permission checks āļŠāļģāļŦāļĢāļąāļš workflow operations - - [ ] **Deliverable:** āļĢāļ°āļšāļšāļŠāđˆāļ‡āļ•āđˆāļ­āđ€āļ­āļāļŠāļēāļĢāļ—āļģāļ‡āļēāļ™āđ„āļ”āđ‰āļŠāļĄāļšāļđāļĢāļ“āđŒāļ”āđ‰āļ§āļĒ Unified Engine - - [ ] **Dependencies:** T3.1, T3.2 - ------ - -## **Phase 4: Drawing & Advanced Workflows (āļŠāļąāļ›āļ”āļēāļŦāđŒāļ—āļĩāđˆ 7-8)** - -**Milestone:** āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāđāļšāļšāđāļĨāļ° RFA āđ‚āļ”āļĒāđƒāļŠāđ‰ Unified Engine - -### **Phase 4: Tasks** - -- **[ ] T4.1 DrawingModule - Contract Drawings** - - - [ ] āļŠāļĢāđ‰āļēāļ‡ Entities (ContractDrawing, Volume, Category, SubCategory, Attachment) - - [ ] āļŠāļĢāđ‰āļēāļ‡ ContractDrawingService CRUD - - [ ] āļŠāļĢāđ‰āļēāļ‡ Controllers (GET/POST /drawings/contract) - - [ ] **Security:** Implement access control āļŠāļģāļŦāļĢāļąāļš contract drawings - - [ ] **Deliverable:** āļˆāļąāļ”āļāļēāļĢ Contract Drawings āđ„āļ”āđ‰ - - [ ] **Dependencies:** T1.1, T1.2, T1.4, T1.5, T2.2 - -- **[ ] T4.2 DrawingModule - Shop Drawings** - - - [ ] āļŠāļĢāđ‰āļēāļ‡ Entities (ShopDrawing, Revision, Main/SubCategory, ContractRef, RevisionAttachment) - - [ ] āļŠāļĢāđ‰āļēāļ‡ ShopDrawingService CRUD (āļĢāļ§āļĄāļāļēāļĢāļŠāļĢāđ‰āļēāļ‡ Revision) - - [ ] āļŠāļĢāđ‰āļēāļ‡ Controllers (GET/POST /drawings/shop, /drawings/shop/:id/revisions) - - [ ] Link Shop Drawing Revision → Contract Drawings - - [ ] **Security:** Implement virus scanning āļŠāļģāļŦāļĢāļąāļš drawing files - - [ ] **Deliverable:** āļˆāļąāļ”āļāļēāļĢ Shop Drawings āđāļĨāļ° Revisions āđ„āļ”āđ‰ - - [ ] **Dependencies:** T4.1 - -- **[ ] T5.1 RfaModule with Unified Workflow** - - - [ ] āļŠāļĢāđ‰āļēāļ‡ Entities (Rfa, RfaRevision, RfaItem, RfaWorkflowTemplate/Step) - - [ ] āļŠāļĢāđ‰āļēāļ‡ RfaService (Create RFA, Link Shop Drawings) - - [ ] Implement RFA Workflow āđ‚āļ”āļĒāđƒāļŠāđ‰ Configuration āļ‚āļ­āļ‡ `WorkflowEngineModule` - - [ ] āļŠāļĢāđ‰āļēāļ‡ Controllers (POST/GET /rfas, POST /rfas/:id/workflow/...) - - [ ] **Resilience:** Implement circuit breaker āļŠāļģāļŦāļĢāļąāļš notification services - - [ ] **Deliverable:** RFA Workflow āļ—āļģāļ‡āļēāļ™āđ„āļ”āđ‰āļ”āđ‰āļ§āļĒ Unified Engine - - [ ] **Dependencies:** T3.2, T4.2, T2.5, T6.2 - ------ - -## **Phase 5: Workflow Systems & Resilience (āļŠāļąāļ›āļ”āļēāļŦāđŒāļ—āļĩāđˆ 8-9)** - -**Milestone:** āļĢāļ°āļšāļš Workflow āļ—āļąāđ‰āļ‡āļŦāļĄāļ”āļžāļĢāđ‰āļ­āļĄ Resilience Patterns - -### **Phase 5: Tasks** - -- **[ ] T5.2 CirculationModule - Internal Routing** - - - [ ] āļŠāļĢāđ‰āļēāļ‡ Entities (Circulation, Template, Routing, Attachment) - - [ ] āļŠāļĢāđ‰āļēāļ‡ CirculationService (Create 1:1 with Correspondence, Assign User, Complete/Close Step) - - [ ] āļŠāļĢāđ‰āļēāļ‡ Controllers (POST/GET /circulations, POST /circulations/:id/steps/...) - - [ ] **Resilience:** Implement retry mechanism āļŠāļģāļŦāļĢāļąāļš assignment notifications - - [ ] **Deliverable:** āđƒāļšāđ€āļ§āļĩāļĒāļ™āļ āļēāļĒāđƒāļ™āļ­āļ‡āļ„āđŒāļāļĢāļ—āļģāļ‡āļēāļ™āđ„āļ”āđ‰ - - [ ] **Dependencies:** T3.2, T2.5, T6.2 - -- **[ ] T5.3 TransmittalModule - Document Forwarding** - - - [ ] āļŠāļĢāđ‰āļēāļ‡ Entities (Transmittal, TransmittalItem) - - [ ] āļŠāļĢāđ‰āļēāļ‡ TransmittalService (Create Correspondence + Transmittal, Link Multiple Correspondences) - - [ ] āļŠāļĢāđ‰āļēāļ‡ Controllers (POST/GET /transmittals) - - [ ] **Security:** Implement access control āļŠāļģāļŦāļĢāļąāļš transmittal items - - [ ] **Deliverable:** āļŠāļĢāđ‰āļēāļ‡ Transmittal āđ„āļ”āđ‰ - - [ ] **Dependencies:** T3.2 - ------ - -## **Phase 6: Notification & Resilience (āļŠāļąāļ›āļ”āļēāļŦāđŒāļ—āļĩāđˆ 9)** - -**Milestone:** āļĢāļ°āļšāļšāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āđāļšāļš Digest āđāļĨāļ°āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļĄāļđāļĨāļ‚āļ™āļēāļ”āđƒāļŦāļāđˆ - -### **Phase 6: Tasks** - -- **[ ] T6.1 SearchModule - Elasticsearch Integration** - - - [ ] Setup Elasticsearch Container - - [ ] āļŠāļĢāđ‰āļēāļ‡ SearchService (index/update/delete documents, search) - - [ ] Index āļ—āļļāļ Document Type - - [ ] āļŠāļĢāđ‰āļēāļ‡ Controllers (GET /search) - - [ ] **Resilience:** Implement circuit breaker āļŠāļģāļŦāļĢāļąāļš Elasticsearch - - [ ] **Deliverable:** āļ„āđ‰āļ™āļŦāļēāļ‚āļąāđ‰āļ™āļŠāļđāļ‡āļ—āļģāļ‡āļēāļ™āđ„āļ”āđ‰ - - [ ] **Dependencies:** T3.2, T5.1, T4.2, T5.2, T5.3 - -- **[ ] T6.2 Notification Queue & Digest** - - - [ ] āļŠāļĢāđ‰āļēāļ‡ NotificationService (sendEmail/Line/System) - - [ ] **Producer:** Push Event āļĨāļ‡ BullMQ Queue - - [ ] **Consumer:** āļˆāļąāļ”āļāļĨāļļāđˆāļĄ Notification (Digest Message) āđāļĨāļ°āļŠāđˆāļ‡āļœāđˆāļēāļ™ Email/Line - - [ ] Integrate āļāļąāļš Workflow Events (āđāļˆāđ‰āļ‡ Recipients, Assignees, Deadline) - - [ ] āļŠāļĢāđ‰āļēāļ‡ Controllers (GET /notifications, PUT /notifications/:id/read) - - [ ] **Resilience:** Implement retry mechanism āļ”āđ‰āļ§āļĒ exponential backoff - - [ ] **Deliverable:** āļĢāļ°āļšāļšāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļ—āļģāļ‡āļēāļ™āđ„āļ”āđ‰āđāļšāļš Digest - - [ ] **Dependencies:** T1.1, T6.4 - -- **[ ] T6.3 MonitoringModule - Observability** - - - [ ] āļŠāļĢāđ‰āļēāļ‡ Health Check Controller (GET /health) - - [ ] āļŠāļĢāđ‰āļēāļ‡ Metrics Service (API response times, Error rates) - - [ ] āļŠāļĢāđ‰āļēāļ‡ Performance Interceptor (Track request duration) - - [ ] āļŠāļĢāđ‰āļēāļ‡ Logging Service (Structured logging) - - [ ] **Deliverable:** Monitoring system āļ—āļģāļ‡āļēāļ™āđ„āļ”āđ‰ - - [ ] **Dependencies:** T1.1 - -- **[ ] T6.4 ResilienceModule - Circuit Breaker & Retry** - - - [ ] āļŠāļĢāđ‰āļēāļ‡ Circuit Breaker Service (@CircuitBreaker() decorator) - - [ ] āļŠāļĢāđ‰āļēāļ‡ Retry Service (@Retry() decorator) - - [ ] āļŠāļĢāđ‰āļēāļ‡ Fallback Strategies - - [ ] Implement āļŠāļģāļŦāļĢāļąāļš Email, LINE, Elasticsearch, Virus Scanning - - [ ] **Deliverable:** Resilience patterns āļ—āļģāļ‡āļēāļ™āđ„āļ”āđ‰ - - [ ] **Dependencies:** T1.1 - -- **[ ] T6.5 Data Partitioning Strategy** - - - [ ] āļ­āļ­āļāđāļšāļš Table Partitioning āļŠāļģāļŦāļĢāļąāļš `audit_logs` āđāļĨāļ° `notifications` (āđāļšāđˆāļ‡āļ•āļēāļĄ Range: Year) - - [ ] āđ€āļ‚āļĩāļĒāļ™ Raw SQL Migration āļŠāļģāļŦāļĢāļąāļšāļŠāļĢāđ‰āļēāļ‡ Partition Table - - [ ] **Deliverable:** Database Performance āđāļĨāļ° Scalability āļ”āļĩāļ‚āļķāđ‰āļ™ - - [ ] **Dependencies:** T0.3 - ------ - -## **Phase 7: Testing & Hardening (āļŠāļąāļ›āļ”āļēāļŦāđŒāļ—āļĩāđˆ 10-12)** - -**Milestone:** āļ—āļ”āļŠāļ­āļšāļ„āļ§āļēāļĄāļ—āļ™āļ—āļēāļ™āļ•āđˆāļ­ Race Condition, Security, āđāļĨāļ° Performance - -### **Phase 7: Tasks** - -- **[ ] T7.1 Concurrency Testing** - - - [ ] āđ€āļ‚āļĩāļĒāļ™ Test Scenarios āļĒāļīāļ‡ Request āļ‚āļ­āđ€āļĨāļ‚āļ—āļĩāđˆāđ€āļ­āļāļŠāļēāļĢāļžāļĢāđ‰āļ­āļĄāļāļąāļ™ 100 Request (āļ•āđ‰āļ­āļ‡āđ„āļĄāđˆāļ‹āđ‰āļģāđāļĨāļ°āđ„āļĄāđˆāļ‚āđ‰āļēāļĄ) - - [ ] āļ—āļ”āļŠāļ­āļš Optimistic Lock āļ—āļģāļ‡āļēāļ™āļ–āļđāļāļ•āđ‰āļ­āļ‡āđ€āļĄāļ·āđˆāļ­ Redis āļ–āļđāļāļ›āļīāļ” - - [ ] āļ—āļ”āļŠāļ­āļš File Upload āļžāļĢāđ‰āļ­āļĄāļāļąāļ™āļŦāļĨāļēāļĒāđ„āļŸāļĨāđŒ - - [ ] **Deliverable:** āļĢāļ°āļšāļšāļ—āļ™āļ—āļēāļ™āļ•āđˆāļ­ Concurrency Issues - -- **[ ] T7.2 Transaction Integrity Testing** - - - [ ] āļ—āļ”āļŠāļ­āļš Upload āđ„āļŸāļĨāđŒāđāļĨāđ‰āļ§ Kill Process āļāđˆāļ­āļ™ Commit - - [ ] āļ—āļ”āļŠāļ­āļš Two-Phase File Storage āļ—āļģāļ‡āļēāļ™āļ–āļđāļāļ•āđ‰āļ­āļ‡ - - [ ] āļ—āļ”āļŠāļ­āļš Database Transaction Rollback Scenarios - - [ ] **Deliverable:** Data Integrity āļĢāļąāļšāļ›āļĢāļ°āļāļąāļ™āđ„āļ”āđ‰ - -- **[ ] T7.3 Security & Idempotency Test** - - - [ ] āļ—āļ”āļŠāļ­āļš Replay Attack āđ‚āļ”āļĒāđƒāļŠāđ‰ `Idempotency-Key` āļ‹āđ‰āļģ - - [ ] āļ—āļ”āļŠāļ­āļš Maintenance Mode Block API āđ„āļ”āđ‰āļˆāļĢāļīāļ‡ - - [ ] āļ—āļ”āļŠāļ­āļš RBAC 4-Level āļ—āļģāļ‡āļēāļ™āļ–āļđāļāļ•āđ‰āļ­āļ‡ 100% - - [ ] **Deliverable:** Security āđāļĨāļ° Idempotency āļ—āļģāļ‡āļēāļ™āđ„āļ”āđ‰āļ•āļēāļĄčŪūčŪĄčĶæą‚ - -- **[ ] T7.4 Unit Testing (80% Coverage)** - -- **[ ] T7.5 Integration Testing** - -- **[ ] T7.6 E2E Testing** - -- **[ ] T7.7 Performance Testing** - - - [ ] Load Testing: 100 concurrent users - - [ ] **(āļŠāļģāļ„āļąāļ)** āļāļēāļĢāļˆāļđāļ™āđāļĨāļ°āļ—āļ”āļŠāļ­āļš Load Test āļˆāļ°āļ•āđ‰āļ­āļ‡āļ—āļģāđƒāļ™āļŠāļ āļēāļžāđāļ§āļ”āļĨāđ‰āļ­āļĄāļ—āļĩāđˆāļˆāļģāļĨāļ­āļ‡ Spec āļ‚āļ­āļ‡ QNAP Server (TS-473A, AMD Ryzen V1500B) āđ€āļžāļ·āđˆāļ­āđƒāļŦāđ‰āđ„āļ”āđ‰āļ„āđˆāļē Response Time āđāļĨāļ° Connection Pool āļ—āļĩāđˆāđ€āļ—āļĩāđˆāļĒāļ‡āļ•āļĢāļ‡ - - [ ] Stress Testing - - [ ] Endurance Testing - - [ ] **Deliverable:** Performance targets āļšāļĢāļĢāļĨāļļ - -- **[ ] T7.8 Security Testing** - - - [ ] Penetration Testing (OWASP Top 10) - - [ ] Security Audit (Code review, Dependency scanning) - - [ ] File Upload Security Testing - - [ ] **Deliverable:** Security tests āļœāđˆāļēāļ™ - -- **[ ] T7.9 Performance Optimization** - - - [ ] Implement Caching (Master Data, User Permissions, Search Results) - - [ ] Database Optimization (Review Indexes, Query Optimization, Pagination) - - [ ] **Deliverable:** Response Time \< 200ms (90th percentile) - ------ - -## **Phase 8: Documentation & Deployment (āļŠāļąāļ›āļ”āļēāļŦāđŒāļ—āļĩāđˆ 14)** - -**Milestone:** āđ€āļ­āļāļŠāļēāļĢāđāļĨāļ° Deploy āļŠāļđāđˆ Production āļžāļĢāđ‰āļ­āļĄ Security Hardening - -### **Phase 8: Tasks** - -- **[ ] T8.1 API Documentation (Swagger)** -- **[ ] T8.2 Technical Documentation** -- **[ ] T8.3 Security Hardening** -- **[ ] T8.4 Deployment Preparation (QNAP Setup, Nginx Proxy Manager)** -- **[ ] T8.5 Production Deployment** -- **[ ] T8.6 Handover to Frontend Team** - ------ - -## 📊 **āļŠāļĢāļļāļ› Timeline** - -| Phase | āļĢāļ°āļĒāļ°āđ€āļ§āļĨāļē | āļˆāļģāļ™āļ§āļ™āļ‡āļēāļ™ | Output āļŦāļĨāļąāļ | -| :--- | :--- | :--- | :--- | -| Phase 0 | 1 āļŠāļąāļ›āļ”āļēāļŦāđŒ | 4 | Infrastructure Ready + Security Base | -| Phase 1 | 2 āļŠāļąāļ›āļ”āļēāļŦāđŒ | 5 | Auth & User Management + RBAC + Idempotency | -| Phase 2 | 1 āļŠāļąāļ›āļ”āļēāļŦāđŒ | 5 | High-Integrity Data & File Management | -| Phase 3 | 2 āļŠāļąāļ›āļ”āļēāļŦāđŒ | 4 | Unified Workflow Engine + Correspondence | -| Phase 4 | 2 āļŠāļąāļ›āļ”āļēāļŦāđŒ | 3 | Drawing Management + RFA with Unified Workflow | -| Phase 5 | 2 āļŠāļąāļ›āļ”āļēāļŦāđŒ | 2 | Workflow Systems + Resilience | -| Phase 6 | 1 āļŠāļąāļ›āļ”āļēāļŦāđŒ | 5 | Notification & Resilience + Data Partitioning | -| Phase 7 | 3 āļŠāļąāļ›āļ”āļēāļŦāđŒ | 9 | Testing & Hardening | -| Phase 8 | 1 āļŠāļąāļ›āļ”āļēāļŦāđŒ | 6 | Documentation & Deploy | -| **āļĢāļ§āļĄ** | **15 āļŠāļąāļ›āļ”āļēāļŦāđŒ** | **39 Tasks** | **Production-Ready Backend v1.4.2** | - -## **Document Control:** - -- **Document:** Backend Development Plan v1.4.2 -- **Version:** 1.4 -- **Date:** 2025-11-19 -- **Author:** NAP LCBP3-DMS & Gemini -- **Status:** FINAL -- **Classification:** Internal Technical Documentation -- **Approved By:** Nattanin - ------ - -`End of Backend Development Plan v1.4.2` diff --git a/Documnets/Project/1.4.2/3_Frontend_Plan_V1_4_2.md b/Documnets/Project/1.4.2/3_Frontend_Plan_V1_4_2.md deleted file mode 100644 index 8315231..0000000 --- a/Documnets/Project/1.4.2/3_Frontend_Plan_V1_4_2.md +++ /dev/null @@ -1,994 +0,0 @@ -# 📋 **āđāļœāļ™āļāļēāļĢāļžāļąāļ’āļ™āļē Frontend (Next.js) - LCBP3-DMS v1.4.2** - -**āļŠāļ–āļēāļ™āļ°:** FINAL GUIDELINE -**āļ§āļąāļ™āļ—āļĩāđˆ:** 2025-11-19 -**āļ­āđ‰āļēāļ‡āļ­āļīāļ‡:** Requirements v1.4.2 & FullStackJS Guidelines v1.4.2 -**Classification:** Internal Technical Documentation - -## ðŸŽŊ **āļ āļēāļžāļĢāļ§āļĄāđ‚āļ„āļĢāļ‡āļāļēāļĢ** - -āļžāļąāļ’āļ™āļē Frontend āļŠāļģāļŦāļĢāļąāļšāļĢāļ°āļšāļšāļšāļĢāļīāļŦāļēāļĢāļˆāļąāļ”āļāļēāļĢāđ€āļ­āļāļŠāļēāļĢāđ‚āļ„āļĢāļ‡āļāļēāļĢ (Document Management System) āļ—āļĩāđˆāļĄāļĩāļ„āļ§āļēāļĄāļ—āļąāļ™āļŠāļĄāļąāļĒ āļĢāļ­āļ‡āļĢāļąāļšāļāļēāļĢāļ—āļģāļ‡āļēāļ™āļšāļ™āļ­āļļāļ›āļāļĢāļ“āđŒāļ•āđˆāļēāļ‡āđ† āđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āļŠāļĄāļšāļđāļĢāļ“āđŒ āļĄāļĩāļ›āļĢāļ°āļŠāļšāļāļēāļĢāļ“āđŒāļœāļđāđ‰āđƒāļŠāđ‰āļ—āļĩāđˆāļĢāļēāļšāļĢāļ·āđˆāļ™ āđāļĨāļ°āļĢāļ­āļ‡āļĢāļąāļšāļāļēāļĢāļ—āļģāļ‡āļēāļ™āđāļšāļš Offline āđ€āļšāļ·āđ‰āļ­āļ‡āļ•āđ‰āļ™ - ---- - -## 📐 **āļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄāļĢāļ°āļšāļš** - -### **Technology Stack** - -- **Framework:** Next.js 14+ (App Router, React 18, TypeScript, ESM) -- **Styling:** Tailwind CSS + PostCSS -- **UI Components:** shadcn/ui + Radix UI Primitives -- **State Management:** - - **Server State:** TanStack Query (React Query) - - **Client State:** Zustand - - **Form State:** React Hook Form + Zod -- **API Client:** Axios (āļžāļĢāđ‰āļ­āļĄ Idempotency Interceptor) -- **Authentication:** NextAuth.js (āļĢāļ­āļ‡āļĢāļąāļš JWT) -- **File Upload:** Custom Hook + Drag & Drop -- **Testing:** - - **Unit/Integration:** Vitest + React Testing Library - - **E2E:** Playwright - - **Mocking:** MSW (Mock Service Worker) -- **Development:** - - **Package Manager:** pnpm - - **Linting:** ESLint + Prettier - - **Type Checking:** TypeScript Strict Mode - -### **āđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡āđ‚āļ›āļĢāđ€āļˆāļāļ•āđŒ** - -```tree -app/ -├── (auth)/ -│ ├── login/ -│ └── register/ -├── (dashboard)/ -│ ├── layout.tsx -│ ├── page.tsx -│ └── components/ -├── admin/ -│ ├── users/ -│ ├── roles/ -│ └── numbering-formats/ -├── correspondences/ -│ ├── page.tsx -│ ├── [id]/ -│ └── new/ -├── rfas/ -│ ├── page.tsx -│ ├── [id]/ -│ └── new/ -├── drawings/ -├── circulations/ -├── transmittals/ -├── search/ -└── profile/ - -components/ -├── ui/ # shadcn/ui components -├── forms/ # Dynamic form components -├── tables/ # Responsive data tables -├── workflow/ # Workflow visualization -├── file-upload/ # File upload with security -├── notifications/ # Notification system -└── layout/ # App layout components - -lib/ -├── api/ # API clients & interceptors -├── auth/ # Authentication utilities -├── stores/ # Zustand stores -├── hooks/ # Custom React hooks -├── utils/ # Utility functions -├── constants/ # App constants -└── types/ # TypeScript type definitions - -styles/ -├── globals.css -└── components/ - -__tests__/ -├── unit/ -├── integration/ -└── e2e/ -``` - ---- - -## 🗓ïļ **āđāļœāļ™āļāļēāļĢāļžāļąāļ’āļ™āļēāđāļšāļš Phase-Based** - -### **Dependency Diagram (āļ āļēāļžāļĢāļ§āļĄ)** - -```mermaid -%% Phase 0: Foundation Setup -subgraph Phase0 [Phase 0: Foundation & Configuration] - F0_1[F0.1: Project Setup & Tooling] - F0_2[F0.2: Design System & UI Components] - F0_3[F0.3: API Client & Authentication] - F0_4[F0.4: State Management Setup] -end - -%% Phase 1: Core Layout & Navigation -subgraph Phase1 [Phase 1: Core Application Structure] - F1_1[F1.1: Main Layout & Navigation] - F1_2[F1.2: Authentication Pages] - F1_3[F1.3: Dashboard & Landing] - F1_4[F1.4: Responsive Design System] -end - -%% Phase 2: User Management & Profile -subgraph Phase2 [Phase 2: User Management & Security] - F2_1[F2.1: User Profile & Settings] - F2_2[F2.2: Admin Panel - User Management] - F2_3[F2.3: Admin Panel - Role Management] - F2_4[F2.4: Permission Integration] -end - -%% Phase 3: Project & Organization Management -subgraph Phase3 [Phase 3: Project Structure] - F3_1[F3.1: Project Management UI] - F3_2[F3.2: Organization Management] - F3_3[F3.3: Contract Management] -end - -%% Phase 4: Correspondence Management -subgraph Phase4 [Phase 4: Correspondence System] - F4_1[F4.1: Correspondence List & Search] - F4_2[F4.2: Correspondence Creation Form] - F4_3[F4.3: Correspondence Detail View] - F4_4[F4.4: File Upload Integration] -end - -%% Phase 5: Workflow & Routing System -subgraph Phase5 [Phase 5: Workflow Management] - F5_1[F5.1: Workflow Visualization Component] - F5_2[F5.2: Routing Template Management] - F5_3[F5.3: Workflow Step Actions] - F5_4[F5.4: Real-time Status Updates] -end - -%% Phase 6: Drawing Management -subgraph Phase6 [Phase 6: Drawing System] - F6_1[F6.1: Contract Drawings Management] - F6_2[F6.2: Shop Drawings Management] - F6_3[F6.3: Drawing Revision System] - F6_4[F6.4: Drawing References] -end - -%% Phase 7: RFA & Approval Workflows -subgraph Phase7 [Phase 7: RFA System] - F7_1[F7.1: RFA List & Dashboard] - F7_2[F7.2: RFA Creation with Dynamic Forms] - F7_3[F7.3: RFA Workflow Integration] - F7_4[F7.4: RFA Approval Interface] -end - -%% Phase 8: Circulation & Internal Routing -subgraph Phase8 [Phase 8: Internal Workflows] - F8_1[F8.1: Circulation Management] - F8_2[F8.2: Task Assignment Interface] - F8_3[F8.3: Internal Approval Flows] -end - -%% Phase 9: Advanced Features -subgraph Phase9 [Phase 9: Advanced Features] - F9_1[F9.1: Advanced Search Interface] - F9_2[F9.2: Notification System] - F9_3[F9.3: Reporting & Analytics] - F9_4[F9.4: Mobile Optimization] -end - -%% Phase 10: Testing & Optimization -subgraph Phase10 [Phase 10: Testing & Polish] - F10_1[F10.1: Comprehensive Testing] - F10_2[F10.2: Performance Optimization] - F10_3[F10.3: Security Hardening] - F10_4[F10.4: Documentation] -end - -%% Dependencies -F0_1 --> F0_2 -F0_1 --> F0_3 -F0_1 --> F0_4 - -F0_2 --> F1_1 -F0_3 --> F1_1 -F0_4 --> F1_1 -F1_1 --> F1_2 -F1_1 --> F1_3 -F1_1 --> F1_4 - -F1_1 --> F2_1 -F1_3 --> F2_1 -F0_3 --> F2_1 -F2_1 --> F2_2 -F2_2 --> F2_3 -F2_3 --> F2_4 - -F1_1 --> F3_1 -F2_4 --> F3_1 -F3_1 --> F3_2 -F3_2 --> F3_3 - -F1_1 --> F4_1 -F3_1 --> F4_1 -F4_1 --> F4_2 -F4_2 --> F4_3 -F4_2 --> F4_4 - -F4_1 --> F5_1 -F4_2 --> F5_2 -F4_3 --> F5_3 -F5_1 --> F5_4 - -F3_1 --> F6_1 -F4_4 --> F6_1 -F6_1 --> F6_2 -F6_2 --> F6_3 -F6_3 --> F6_4 - -F4_1 --> F7_1 -F5_1 --> F7_1 -F6_2 --> F7_1 -F7_1 --> F7_2 -F7_2 --> F7_3 -F7_3 --> F7_4 - -F4_1 --> F8_1 -F5_3 --> F8_1 -F8_1 --> F8_2 -F8_2 --> F8_3 - -F4_1 --> F9_1 -F7_1 --> F9_1 -F1_3 --> F9_2 -F5_4 --> F9_2 -F1_3 --> F9_3 -F1_4 --> F9_4 - -F1_1 --> F10_1 -F4_1 --> F10_1 -F7_1 --> F10_1 -F10_1 --> F10_2 -F10_2 --> F10_3 -F10_3 --> F10_4 -``` - -## **Phase 0: Foundation & Configuration (āļŠāļąāļ›āļ”āļēāļŦāđŒāļ—āļĩāđˆ 1)** - -**Milestone:** āđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡āļžāļ·āđ‰āļ™āļāļēāļ™āļžāļĢāđ‰āļ­āļĄ āļĢāļ­āļ‡āļĢāļąāļš Development Workflow āļ—āļĩāđˆāļĄāļĩāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļž - -### **Phase 0: Tasks** - -- **[ ] F0.1 Project Setup & Tooling** - - [ ] Initialize Next.js 14+ project with TypeScript - - [ ] Configure pnpm workspace - - [ ] Setup ESLint, Prettier, and pre-commit hooks - - [ ] Configure Tailwind CSS with PostCSS - - [ ] Setup shadcn/ui components - - [ ] Configure absolute imports and path aliases - - [ ] **Deliverable:** Development environment ready - - [ ] **Dependencies:** None - -- **[ ] F0.2 Design System & UI Components** - - [ ] Setup color palette and design tokens - - [ ] Create responsive design breakpoints - - [ ] Implement core shadcn/ui components: - - [ ] Button, Input, Label, Form - - [ ] Card, Table, Badge - - [ ] Dialog, Dropdown, Select - - [ ] Tabs, Accordion - - [ ] Create custom design system components: - - [ ] DataTable (responsive) - - [ ] FileUpload zone - - [ ] Workflow visualization - - [ ] **Deliverable:** Consistent UI component library - - [ ] **Dependencies:** F0.1 - -- **[ ] F0.3 API Client & Authentication** - - [ ] Setup Axios client with interceptors: - - [ ] Idempotency-Key header injection - - [ ] Authentication token management - - [ ] Error handling and retry logic - - [ ] Configure NextAuth.js for JWT authentication - - [ ] Create auth hooks (useAuth, usePermissions) - - [ ] Setup API route handlers for auth callbacks - - [ ] **Security:** Implement secure token storage - - [ ] **Deliverable:** Secure API communication layer - - [ ] **Dependencies:** F0.1 - -- **[ ] F0.4 State Management Setup** - - [ ] Configure TanStack Query for server state: - - [ ] Query client setup - - [ ] Default configurations - - [ ] Error boundaries - - [ ] Create Zustand stores: - - [ ] Auth store (user, permissions) - - [ ] UI store (theme, sidebar state) - - [ ] Draft store (form auto-save) - - [ ] Setup React Hook Form with Zod integration - - [ ] Create form validation schemas - - [ ] **Deliverable:** Robust state management system - - [ ] **Dependencies:** F0.1, F0.3 - -### **Phase 0: Testing - Foundation** - -#### **F0.T1 Component Test Suite** - -- [ ] **Unit Tests:** Core UI components (Button, Input, Card) -- [ ] **Integration Tests:** Form validation, API client interceptors -- [ ] **Visual Tests:** Component styling and responsive behavior - -#### **F0.T2 Authentication Test Suite** - -- [ ] **Unit Tests:** Auth hooks, token management -- [ ] **Integration Tests:** Login/logout flow, permission checks -- [ ] **Security Tests:** Token security, API authentication - ---- - -## **Phase 1: Core Application Structure (āļŠāļąāļ›āļ”āļēāļŦāđŒāļ—āļĩāđˆ 2)** - -**Milestone:** Layout āļŦāļĨāļąāļāļžāļĢāđ‰āļ­āļĄāđƒāļŠāđ‰āļ‡āļēāļ™ āļāļēāļĢāļ™āļģāļ—āļēāļ‡āđāļĨāļ° Authentication āļ—āļģāļ‡āļēāļ™āđ„āļ”āđ‰ - -### **Phase 1: Tasks** - -- **[ ] F1.1 Main Layout & Navigation** - - [ ] Create App Shell layout: - - [ ] Navbar with user menu and notifications - - [ ] Collapsible sidebar with navigation - - [ ] Main content area with responsive design - - [ ] Implement navigation menu structure: - - [ ] Dashboard, Correspondences, RFAs, Drawings, etc. - - [ ] Dynamic menu based on user permissions - - [ ] Create breadcrumb navigation component - - [ ] Implement mobile-responsive sidebar (drawer) - - [ ] Maintenance Mode Integration: - - [ ] Implement a Global Middleware/Wrapper āļ—āļĩāđˆāļ•āļĢāļ§āļˆāļŠāļ­āļšāļŠāļ–āļēāļ™āļ° Maintenance Mode āļœāđˆāļēāļ™ API/Service āļāđˆāļ­āļ™āļāļēāļĢ Render āļŦāļ™āđ‰āļē āļŦāļēāļāļŠāļ–āļēāļ™āļ°āđ€āļ›āđ‡āļ™ true āđƒāļŦāđ‰ Redirect āļœāļđāđ‰āđƒāļŠāđ‰ (āļĒāļāđ€āļ§āđ‰āļ™ Admin) āđ„āļ›āļĒāļąāļ‡āļŦāļ™āđ‰āļē /maintenance āļ—āļąāļ™āļ—āļĩ āđ€āļžāļ·āđˆāļ­āđƒāļŦāđ‰āļŠāļ­āļ”āļ„āļĨāđ‰āļ­āļ‡āļāļąāļš Logic āļ‚āļ­āļ‡ Backend. - - [ ] **Accessibility:** Ensure keyboard navigation and screen reader support - - [ ] **Deliverable:** Fully functional application layout - - [ ] **Dependencies:** F0.2, F0.3 - -- **[ ] F1.2 Authentication Pages** - - [ ] Create login page with form validation - - [ ] Implement forgot password flow - - [ ] Create registration page (admin-only) - - [ ] Setup protected route middleware - - [ ] Implement route-based permission checks - - [ ] **Security:** Rate limiting on auth attempts, secure password requirements - - [ ] **Deliverable:** Complete authentication flow - - [ ] **Dependencies:** F0.3, F1.1 - -- **[ ] F1.3 Dashboard & Landing** - - [ ] Create public landing page for non-authenticated users - - [ ] Implement main dashboard with: - - [ ] KPI cards (document counts, pending tasks) - - [ ] "My Tasks" table from v_user_tasks - - [ ] Recent activity feed - - [ ] Security metrics display - - [ ] Create dashboard widgets system - - [ ] Implement data fetching with TanStack Query - - [ ] **Performance:** Optimize dashboard data loading - - [ ] **Deliverable:** Functional dashboard with real data - - [ ] **Dependencies:** F0.4, F1.1 - -- **[ ] F1.4 Responsive Design System** - - [ ] Implement mobile-first responsive design - - [ ] Create card view components for mobile tables - - [ ] Setup touch-friendly interactions - - [ ] Optimize images and assets for mobile - - [ ] Test across multiple device sizes - - [ ] **UX:** Ensure seamless mobile experience - - [ ] **Deliverable:** Fully responsive application - - [ ] **Dependencies:** F0.2, F1.1 - -### **Phase 1: Testing - Core Structure** - -#### **F1.T1 Layout Test Suite** - -- [ ] **Unit Tests:** Navigation components, layout responsiveness -- [ ] **Integration Tests:** Route protection, permission-based navigation -- [ ] **E2E Tests:** Complete user navigation flow - -#### **F1.T2 Dashboard Test Suite** - -- [ ] **Unit Tests:** Dashboard components, data formatting -- [ ] **Integration Tests:** Data fetching and display, real-time updates -- [ ] **Performance Tests:** Dashboard loading performance - ---- - -## **Phase 2: User Management & Security (āļŠāļąāļ›āļ”āļēāļŦāđŒāļ—āļĩāđˆ 3)** - -**Milestone:** āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļœāļđāđ‰āđƒāļŠāđ‰āđāļĨāļ°āļŠāļīāļ—āļ˜āļīāđŒāđāļšāļšāļŠāļĄāļšāļđāļĢāļ“āđŒ - -### **Phase 2: Tasks** - -- **[ ] F2.1 User Profile & Settings** - - [ ] Create user profile page: - - [ ] Personal information display/edit - - [ ] Password change functionality - - [ ] Notification preferences - - [ ] Implement profile picture upload - - [ ] Create user settings page - - [ ] **Security:** Secure password change with current password verification - - [ ] **Deliverable:** Complete user self-service management - - [ ] **Dependencies:** F1.1, F0.4 - -- **[ ] F2.2 Admin Panel - User Management** - - [ ] Create user list with search and filters - - [ ] Implement user creation form - - [ ] Create user edit interface - - [ ] Implement bulk user operations - - [ ] Add user activity tracking display - - [ ] **Security:** Admin-only access enforcement - - [ ] **Deliverable:** Comprehensive user management interface - - [ ] **Dependencies:** F1.1, F2.1 - -- **[ ] F2.3 Admin Panel - Role Management** - - [ ] Create role list and management interface - - [ ] Implement role creation and editing - - [ ] Create permission assignment interface - - [ ] Implement role-based access control visualization - - [ ] Add role usage statistics - - [ ] **Security:** Permission hierarchy enforcement - - [ ] **Deliverable:** Complete RBAC management system - - [ ] **Dependencies:** F2.2 - -- **[ ] F2.4 Permission Integration** - - [ ] Implement CASL ability integration - - [ ] Create permission-based UI components: - - [ ] ProtectedButton, ProtectedLink - - [ ] Conditional rendering based on permissions - - [ ] Setup real-time permission updates - - [ ] Implement permission debugging tools - - [ ] **Security:** Frontend-backend permission consistency - - [ ] **Deliverable:** Seamless permission enforcement throughout app - - [ ] **Dependencies:** F0.3, F2.3 - -### **Phase 2: Testing - User Management** - -#### **F2.T1 User Management Test Suite** - -- [ ] **Unit Tests:** User CRUD operations, form validation -- [ ] **Integration Tests:** User-role assignment, permission propagation -- [ ] **Security Tests:** Permission escalation attempts, admin access control - -#### **F2.T2 RBAC Test Suite** - -- [ ] **Unit Tests:** Permission checks, role validation -- [ ] **Integration Tests:** Multi-level permission enforcement, UI element protection -- [ ] **E2E Tests:** Complete role-based workflow testing - ---- - -## **Phase 3: Project Structure (āļŠāļąāļ›āļ”āļēāļŦāđŒāļ—āļĩāđˆ 4)** - -**Milestone:** āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡āđ‚āļ›āļĢāđ€āļˆāļāļ•āđŒāđāļĨāļ°āļ­āļ‡āļ„āđŒāļāļĢ - -### **Phase 3: Tasks** - -- **[ ] F3.1 Project Management UI** - - [ ] Create project list with search and filters - - [ ] Implement project creation and editing - - [ ] Create project detail view - - [ ] Implement project dashboard with statistics - - [ ] Add project member management - - [ ] **UX:** Intuitive project navigation and management - - [ ] **Deliverable:** Complete project management interface - - [ ] **Dependencies:** F1.1, F2.4 - -- **[ ] F3.2 Organization Management** - - [ ] Create organization list and management - - [ ] Implement organization creation and editing - - [ ] Create organization detail view - - [ ] Add organization user management - - [ ] Implement organization hierarchy visualization - - [ ] **Business Logic:** Proper organization-project relationships - - [ ] **Deliverable:** Comprehensive organization management - - [ ] **Dependencies:** F3.1 - -- **[ ] F3.3 Contract Management** - - [ ] Create contract list within projects - - [ ] Implement contract creation and editing - - [ ] Create contract detail view - - [ ] Add contract party management - - [ ] Implement contract document associations - - [ ] **Business Logic:** Contract-project-organization relationships - - [ ] **Deliverable:** Complete contract management system - - [ ] **Dependencies:** F3.1, F3.2 - -### **Phase 3: Testing - Project Structure** - -#### **F3.T1 Project Management Test Suite** - -- [ ] **Unit Tests:** Project CRUD operations, validation -- [ ] **Integration Tests:** Project-organization relationships, member management -- [ ] **Business Logic Tests:** Project hierarchy, access control - ---- - -## **Phase 4: Correspondence System (āļŠāļąāļ›āļ”āļēāļŦāđŒāļ—āļĩāđˆ 5-6)** - -**Milestone:** āļĢāļ°āļšāļšāļˆāļąāļ”āļāļēāļĢāđ€āļ­āļāļŠāļēāļĢāđ‚āļ•āđ‰āļ•āļ­āļšāđāļšāļšāļŠāļĄāļšāļđāļĢāļ“āđŒ - -### **Phase 4: Tasks** - -- **[ ] F4.1 Correspondence List & Search** - - [ ] Create correspondence list with advanced filtering: - - [ ] Filter by type, status, project, organization - - [ ] Search by title, document number, content - - [ ] Date range filtering - - [ ] Implement responsive data table: - - [ ] Desktop: Full table view - - [ ] Mobile: Card view conversion - - [ ] Add bulk operations (export, status change) - - [ ] Implement real-time updates - - [ ] **Performance:** Virtual scrolling for large datasets - - [ ] **Deliverable:** High-performance correspondence listing - - [ ] **Dependencies:** F1.1, F3.1 - -- **[ ] F4.2 Correspondence Creation Form** - - [ ] Create dynamic form generator based on JSON schema - - [ ] Implement form with multiple sections: - - [ ] Basic information (type, title, recipients) - - [ ] Content and details (JSON schema-based) - - [ ] File attachments - - [ ] Routing template selection - - [ ] Add draft auto-save functionality - - [ ] Implement form validation with Zod - - [ ] **UX:** Intuitive form with progress indication - - [ ] **Deliverable:** Flexible correspondence creation interface - - [ ] **Dependencies:** F0.4, F4.1 - -- **[ ] F4.3 Correspondence Detail View** - - [ ] Create comprehensive detail page: - - [ ] Document header with metadata - - [ ] Content display based on type - - [ ] Revision history - - [ ] Related documents - - [ ] Workflow status visualization - - [ ] Implement document actions: - - [ ] Edit, withdraw, cancel (with permissions) - - [ ] Download, print - - [ ] Create related documents - - [ ] Add comments and activity timeline - - [ ] **UX:** Clean, readable document presentation - - [ ] **Deliverable:** Complete document detail experience - - [ ] **Dependencies:** F4.1, F4.2 - -- **[ ] F4.4 File Upload Integration** - - [ ] Create drag-and-drop file upload component - - [ ] Implement file type validation and preview - - [ ] Add virus scan status indication - - [ ] Create file management interface: - - [ ] Mark files as main/supporting documents - - [ ] Reorder and manage attachments - - [ ] File security status display - - [ ] Implement two-phase upload progress - - [ ] **Security:** File type restrictions, size limits, virus scan integration - - [ ] **Deliverable:** Secure and user-friendly file management - - [ ] **Dependencies:** F0.2, F4.2 - -### **Phase 4: Testing - Correspondence System** - -#### **F4.T1 Correspondence Test Suite** - -- [ ] **Unit Tests:** Form validation, file upload components -- [ ] **Integration Tests:** Complete document lifecycle, file attachment flow -- [ ] **E2E Tests:** End-to-end correspondence creation and management - -#### **F4.T2 File Upload Test Suite** - -- [ ] **Unit Tests:** File validation, type checking -- [ ] **Integration Tests:** Two-phase upload process, virus scan integration -- [ ] **Security Tests:** Malicious file upload attempts, security feedback - ---- - -## **Phase 5: Workflow Management (āļŠāļąāļ›āļ”āļēāļŦāđŒāļ—āļĩāđˆ 7)** - -**Milestone:** āļĢāļ°āļšāļš Visualization āđāļĨāļ°āļˆāļąāļ”āļāļēāļĢ Workflow - -### **Phase 5: Tasks** - -- **[ ] F5.1 Workflow Visualization Component** - - [ ] Create horizontal workflow progress visualization - - [ ] Implement step status indicators (pending, active, completed, skipped) - - [ ] Add due date and assignee information - - [ ] Create interactive workflow diagram - - [ ] Implement workflow history timeline - - [ ] **UX:** Clear visual representation of complex workflows - - [ ] **Deliverable:** Intuitive workflow visualization - - [ ] **Dependencies:** F4.3 - -- **[ ] F5.2 Routing Template Management** - - [ ] Create routing template list and editor - - [ ] Implement drag-and-drop step configuration - - [ ] Add step configuration (purpose, duration, assignee rules) - - [ ] Create template preview functionality - - [ ] Implement template versioning - - [ ] **Business Logic:** Proper step sequencing and validation - - [ ] **Deliverable:** Comprehensive routing template management - - [ ] **Dependencies:** F3.1, F4.2 - -- **[ ] F5.3 Workflow Step Actions** - - [ ] Create step action interface: - - [ ] Approve, reject, request changes - - [ ] Add comments and attachments - - [ ] Forward to other users - - [ ] Implement bulk step actions - - [ ] Add action confirmation with reason required - - [ ] Create step delegation functionality - - [ ] **UX:** Streamlined step completion process - - [ ] **Deliverable:** Efficient workflow step management - - [ ] **Dependencies:** F5.1 - -- **[ ] F5.4 Real-time Status Updates** - - [ ] Implement WebSocket connections for real-time updates - - [ ] Create status change notifications - - [ ] Add auto-refresh for workflow states - - [ ] Implement optimistic updates for better UX - - [ ] Create update history and audit trail - - [ ] **Performance:** Efficient real-time data synchronization - - [ ] **Deliverable:** Real-time workflow monitoring - - [ ] **Dependencies:** F5.1, F9.2 - -### **Phase 5: Testing - Workflow Management** - -#### **F5.T1 Workflow Test Suite** - -- [ ] **Unit Tests:** Workflow visualization, step status logic -- [ ] **Integration Tests:** Complete workflow execution, real-time updates -- [ ] **E2E Tests:** Multi-step workflow with different user roles - ---- - -## **Phase 6: Drawing System (āļŠāļąāļ›āļ”āļēāļŦāđŒāļ—āļĩāđˆ 8)** - -**Milestone:** āļĢāļ°āļšāļšāļˆāļąāļ”āļāļēāļĢāđāļšāļšāđāļ›āļĨāļ™āđāļšāļšāļŠāļĄāļšāļđāļĢāļ“āđŒ - -### **Phase 6: Tasks** - -- **[ ] F6.1 Contract Drawings Management** - - [ ] Create contract drawing list with categorization - - [ ] Implement drawing upload and metadata management - - [ ] Create drawing preview and viewer - - [ ] Add drawing version control - - [ ] Implement drawing search and filtering - - [ ] **UX:** Efficient drawing navigation and access - - [ ] **Deliverable:** Comprehensive contract drawing management - - [ ] **Dependencies:** F3.1, F4.4 - -- **[ ] F6.2 Shop Drawings Management** - - [ ] Create shop drawing list with revision tracking - - [ ] Implement shop drawing creation and revision system - - [ ] Create drawing comparison interface - - [ ] Add drawing approval status tracking - - [ ] Implement bulk drawing operations - - [ ] **Business Logic:** Proper revision control and approval workflows - - [ ] **Deliverable:** Complete shop drawing management system - - [ ] **Dependencies:** F6.1 - -- **[ ] F6.3 Drawing Revision System** - - [ ] Create revision history interface - - [ ] Implement revision comparison functionality - - [ ] Add revision notes and change tracking - - [ ] Create revision approval workflow - - [ ] Implement revision rollback capability - - [ ] **UX:** Clear visualization of changes between revisions - - [ ] **Deliverable:** Robust drawing revision control - - [ ] **Dependencies:** F6.2 - -- **[ ] F6.4 Drawing References** - - [ ] Create drawing reference management - - [ ] Implement cross-drawing references - - [ ] Add reference validation and integrity checks - - [ ] Create reference visualization - - [ ] Implement reference impact analysis - - [ ] **Business Logic:** Maintain reference integrity during changes - - [ ] **Deliverable:** Comprehensive drawing reference system - - [ ] **Dependencies:** F6.2, F6.3 - -### **Phase 6: Testing - Drawing System** - -#### **F6.T1 Drawing Management Test Suite** - -- [ ] **Unit Tests:** Drawing CRUD operations, revision logic -- [ ] **Integration Tests:** Drawing approval workflows, reference management -- [ ] **E2E Tests:** Complete drawing lifecycle with revisions - ---- - -## **Phase 7: RFA System (āļŠāļąāļ›āļ”āļēāļŦāđŒāļ—āļĩāđˆ 9-10)** - -**Milestone:** āļĢāļ°āļšāļšāļ‚āļ­āļ­āļ™āļļāļĄāļąāļ•āļīāđāļšāļšāļŠāļĄāļšāļđāļĢāļ“āđŒāļžāļĢāđ‰āļ­āļĄ Dynamic Forms - -### **Phase 7: Tasks** - -- **[ ] F7.1 RFA List & Dashboard** - - [ ] Create RFA dashboard with status overview - - [ ] Implement advanced RFA filtering and search - - [ ] Create RFA calendar view for deadlines - - [ ] Add RFA statistics and reporting - - [ ] Implement RFA bulk operations - - [ ] **UX:** Comprehensive RFA overview and management - - [ ] **Deliverable:** Complete RFA dashboard and listing - - [ ] **Dependencies:** F4.1, F5.1 - -- **[ ] F7.2 RFA Creation with Dynamic Forms** - - [ ] Create RFA type-specific form generator - - [ ] Implement dynamic form fields based on RFA type: - - [ ] RFA_DWG: Shop drawing selection - - [ ] RFA_DOC: Document specifications - - [ ] RFA_MES: Method statement details - - [ ] RFA_MAT: Material specifications - - [ ] Add form validation with JSON schema - - [ ] Implement form data persistence and recovery - - [ ] **UX:** Intuitive form experience for complex RFA types - - [ ] Dynamic Form & Schema Validation: āļŠāļĢāđ‰āļēāļ‡ Component Dynamic Form Generator āļ—āļĩāđˆ: - - [ ] Fetch Schema: āļ”āļķāļ‡āđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡ JSON Schema āļ—āļĩāđˆāļ–āļđāļāļ•āđ‰āļ­āļ‡āļ•āļēāļĄ rfa_type āļˆāļēāļ Backend (āļ•āļēāļĢāļēāļ‡ json_schemas āļ—āļĩāđˆāļŠāļĢāđ‰āļēāļ‡āđƒāļŦāļĄāđˆ) āļāđˆāļ­āļ™āļāļēāļĢ Render Form. - - [ ] Client-side Validation: Implement AJV (Another JSON Schema Validator) āļŦāļĢāļ·āļ­āđ„āļĨāļšāļĢāļēāļĢāļĩāļ—āļĩāđˆāđ€āļ—āļĩāļĒāļšāđ€āļ—āđˆāļē āđ€āļžāļ·āđˆāļ­āļ—āļģ Client-side Validation āļšāļ™āļ‚āđ‰āļ­āļĄāļđāļĨ JSON āļāđˆāļ­āļ™āļŠāđˆāļ‡ Submit. - - [ ] Implement dynamic form fields based on RFA type: RFA_DWG, RFA_DOC, RFA_MES, RFA_MAT. - - [ ] Add form data persistence and recovery. - - [ ] **Deliverable:** Flexible RFA creation system - - [ ] **Dependencies:** F4.2, F6.2 - -- **[ ] F7.3 RFA Workflow Integration** - - [ ] Integrate RFA with unified workflow engine - - [ ] Create RFA-specific workflow steps and actions - - [ ] Implement RFA approval interface - - [ ] Add RFA workflow history and tracking - - [ ] Create RFA workflow templates - - [ ] **Business Logic:** Proper RFA approval sequencing and validation - - [ ] **Deliverable:** Seamless RFA workflow integration - - [ ] **Dependencies:** F5.1, F7.2 - -- **[ ] F7.4 RFA Approval Interface** - - [ ] Create RFA review and approval interface - - [ ] Implement side-by-side document comparison - - [ ] Add approval comments and attachments - - [ ] Create conditional approval workflows - - [ ] Implement approval delegation and escalation - - [ ] **UX:** Efficient approval process for technical reviews - - [ ] **Deliverable:** Comprehensive RFA approval system - - [ ] **Dependencies:** F7.1, F7.3 - -### **Phase 7: Testing - RFA System** - -#### **F7.T1 RFA Test Suite** - -- [ ] **Unit Tests:** RFA form generation, validation logic -- [ ] **Integration Tests:** Complete RFA lifecycle, workflow integration -- [ ] **E2E Tests:** Multi-type RFA creation and approval workflows - ---- - -## **Phase 8: Internal Workflows (āļŠāļąāļ›āļ”āļēāļŦāđŒāļ—āļĩāđˆ 11)** - -**Milestone:** āļĢāļ°āļšāļšāđƒāļšāđ€āļ§āļĩāļĒāļ™āđāļĨāļ°āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļ‡āļēāļ™āļ āļēāļĒāđƒāļ™ - -### **Phase 8: Tasks** - -- **[ ] F8.1 Circulation Management** - - [ ] Create circulation list and management interface - - [ ] Implement circulation creation from correspondence - - [ ] Create circulation template management - - [ ] Add circulation status tracking - - [ ] Implement circulation search and filtering - - [ ] **Business Logic:** Proper circulation-correspondence relationships - - [ ] **Deliverable:** Comprehensive circulation management - - [ ] **Dependencies:** F4.1, F5.2 - -- **[ ] F8.2 Task Assignment Interface** - - [ ] Create task assignment interface with user selection - - [ ] Implement task priority and deadline setting - - [ ] Add task dependency management - - [ ] Create task progress tracking - - [ ] Implement task reassignment and delegation - - [ ] **UX:** Intuitive task management and assignment - - [ ] **Deliverable:** Efficient task assignment system - - [ ] **Dependencies:** F8.1 - -- **[ ] F8.3 Internal Approval Flows** - - [ ] Create internal approval workflow interface - - [ ] Implement multi-level approval processes - - [ ] Add approval chain visualization - - [ ] Create approval delegation system - - [ ] Implement approval deadline management - - [ ] **Business Logic:** Proper approval hierarchy and escalation - - [ ] **Deliverable:** Robust internal approval system - - [ ] **Dependencies:** F8.1, F8.2 - -### **Phase 8: Testing - Internal Workflows** - -#### **F8.T1 Circulation Test Suite** - -- [ ] **Unit Tests:** Circulation creation, task assignment logic -- [ ] **Integration Tests:** Complete circulation workflow, internal approvals -- [ ] **E2E Tests:** End-to-end circulation with task completion - ---- - -## **Phase 9: Advanced Features (āļŠāļąāļ›āļ”āļēāļŦāđŒāļ—āļĩāđˆ 12)** - -**Milestone:** āļŸāļĩāđ€āļˆāļ­āļĢāđŒāļ‚āļąāđ‰āļ™āļŠāļđāļ‡āđāļĨāļ°āļāļēāļĢāļ›āļĢāļąāļšāļ›āļĢāļļāļ‡āļ›āļĢāļ°āļŠāļšāļāļēāļĢāļ“āđŒāļœāļđāđ‰āđƒāļŠāđ‰ - -### **Phase 9: Tasks** - -- **[ ] F9.1 Advanced Search Interface** - - [ ] Create unified search interface across all document types - - [ ] Implement faceted search with multiple filters - - [ ] Add search result highlighting and relevance scoring - - [ ] Create saved search and search templates - - [ ] Implement search result export functionality - - [ ] **Performance:** Efficient search with large datasets - - [ ] **Deliverable:** Powerful cross-document search system - - [ ] **Dependencies:** F4.1, F7.1 - -- **[ ] F9.2 Notification System** - - [ ] Create notification center with real-time updates - - [ ] Implement notification preferences management - - [ ] Add notification grouping and digest views - - [ ] Create actionable notifications with quick actions - - [ ] Implement notification read/unread status - - [ ] **UX:** Non-intrusive but effective notification delivery - - [ ] **Deliverable:** Comprehensive notification management - - [ ] **Dependencies:** F1.3, F5.4 - -- **[ ] F9.3 Reporting & Analytics** - - [ ] Create reporting dashboard with customizable widgets - - [ ] Implement data visualization components (charts, graphs) - - [ ] Add report scheduling and export - - [ ] Create ad-hoc reporting interface - - [ ] Implement performance metrics tracking - - [ ] **Business Logic:** Accurate data aggregation and reporting - - [ ] **Deliverable:** Powerful reporting and analytics system - - [ ] **Dependencies:** F1.3, F7.1 - -- **[ ] F9.4 Mobile Optimization** - - [ ] Implement touch-optimized interactions - - [ ] Create mobile-specific navigation patterns - - [ ] Add offline capability for critical functions - - [ ] Optimize images and assets for mobile networks - - [ ] Implement mobile-specific performance optimizations - - [ ] **UX:** Seamless mobile experience comparable to desktop - - [ ] **Deliverable:** Fully optimized mobile application - - [ ] **Dependencies:** F1.4 - -### **Phase 9: Testing - Advanced Features** - -#### **F9.T1 Advanced Features Test Suite** - -- [ ] **Unit Tests:** Search algorithms, notification logic -- [ ] **Integration Tests:** Cross-module search, real-time notifications -- [ ] **Performance Tests:** Search performance, mobile responsiveness - ---- - -## **Phase 10: Testing & Polish (āļŠāļąāļ›āļ”āļēāļŦāđŒāļ—āļĩāđˆ 13-14)** - -**Milestone:** āđāļ­āļ›āļžāļĨāļīāđ€āļ„āļŠāļąāļ™āļ—āļĩāđˆāļœāđˆāļēāļ™āļāļēāļĢāļ—āļ”āļŠāļ­āļšāđāļĨāļ°āļ›āļĢāļąāļšāļ›āļĢāļļāļ‡āļ­āļĒāđˆāļēāļ‡āļŠāļĄāļšāļđāļĢāļ“āđŒ - -### **Phase 10: Tasks** - -- **[ ] F10.1 Comprehensive Testing** - - [ ] Idempotency Testing: āđ€āļžāļīāđˆāļĄāļāļēāļĢāļ—āļ”āļŠāļ­āļšāđ€āļ‰āļžāļēāļ°āļŠāļģāļŦāļĢāļąāļš Axios Interceptor āđ€āļžāļ·āđˆāļ­āļˆāļģāļĨāļ­āļ‡āļāļēāļĢāļŠāđˆāļ‡ Request POST/PUT/DELETE āļ—āļĩāđˆāļĄāļĩ Idempotency-Key āļ‹āđ‰āļģāđ„āļ›āļĒāļąāļ‡ Mock API (MSW) āđ€āļžāļ·āđˆāļ­āļĒāļ·āļ™āļĒāļąāļ™āļ§āđˆāļē Client-side āđ„āļĄāđˆāļŠāđˆāļ‡ Key āļ‹āđ‰āļģāđƒāļ™āļāļēāļĢāļ—āļģāļ‡āļēāļ™āļ›āļāļ•āļī āđāļĨāļ°āđ„āļĄāđˆāđ€āļāļīāļ” Side Effect āļˆāļēāļāļāļēāļĢ Replay Attack. - - [ ] Write unit tests for all components and utilities - - [ ] Create integration tests for critical user flows - - [ ] Implement E2E tests for complete workflows - - [ ] Perform cross-browser compatibility testing - - [ ] Conduct accessibility testing (WCAG 2.1 AA) - - [ ] **Quality:** 80%+ test coverage, all critical paths tested - - [ ] **Deliverable:** Fully tested application - - [ ] **Dependencies:** All previous phases - -- **[ ] F10.2 Performance Optimization** - - [ ] Implement code splitting and lazy loading - - [ ] Optimize bundle size and asset delivery - - [ ] Add performance monitoring and metrics - - [ ] Implement caching strategies for static assets - - [ ] Optimize API call patterns and reduce over-fetching - - [ ] **Performance:** Core Web Vitals targets met - - [ ] **Deliverable:** High-performance application - - [ ] **Dependencies:** F10.1 - -- **[ ] F10.3 Security Hardening** - - [ ] Conduct security audit and penetration testing - - [ ] Implement Content Security Policy (CSP) - - [ ] Add security headers and protections - - [ ] Conduct dependency vulnerability scanning - - [ ] Implement secure coding practices review - - [ ] **Security:** No critical security vulnerabilities - - [ ] **Deliverable:** Security-hardened application - - [ ] **Dependencies:** F10.1 - -- **[ ] F10.4 Documentation** - - [ ] Create user documentation and guides - - [ ] Write technical documentation for developers - - [ ] Create API integration documentation - - [ ] Add inline code documentation - - [ ] Create deployment and maintenance guides - - [ ] **Quality:** Comprehensive and up-to-date documentation - - [ ] **Deliverable:** Complete documentation suite - - [ ] **Dependencies:** F10.1 - -### **Phase 10: Testing - Final Validation** - -#### **F10.T1 Final Test Suite** - -- [ ] **Performance Tests:** Load testing, stress testing -- [ ] **Security Tests:** Final security audit, vulnerability assessment -- [ ] **User Acceptance Tests:** Real user testing, feedback incorporation -- [ ] **Compatibility Tests:** Cross-browser, cross-device testing - ---- - -## 📊 **āļŠāļĢāļļāļ› Timeline** - -| Phase | āļĢāļ°āļĒāļ°āđ€āļ§āļĨāļē | āļˆāļģāļ™āļ§āļ™āļ‡āļēāļ™ | Output āļŦāļĨāļąāļ | -| ------- | ------------ | -------- | ------------------------------------ | -| Phase 0 | 1 āļŠāļąāļ›āļ”āļēāļŦāđŒ | 4 | Foundation & Tooling Ready | -| Phase 1 | 1 āļŠāļąāļ›āļ”āļēāļŦāđŒ | 4 | Core Application Structure | -| Phase 2 | 1 āļŠāļąāļ›āļ”āļēāļŦāđŒ | 4 | User Management & Security | -| Phase 3 | 1 āļŠāļąāļ›āļ”āļēāļŦāđŒ | 3 | Project Structure Management | -| Phase 4 | 2 āļŠāļąāļ›āļ”āļēāļŦāđŒ | 4 | Correspondence System | -| Phase 5 | 1 āļŠāļąāļ›āļ”āļēāļŦāđŒ | 4 | Workflow Management | -| Phase 6 | 1 āļŠāļąāļ›āļ”āļēāļŦāđŒ | 4 | Drawing System | -| Phase 7 | 2 āļŠāļąāļ›āļ”āļēāļŦāđŒ | 4 | RFA System (Dynamic Forms) | -| Phase 8 | 1 āļŠāļąāļ›āļ”āļēāļŦāđŒ | 3 | Internal Workflows | -| Phase 9 | 1 āļŠāļąāļ›āļ”āļēāļŦāđŒ | 4 | Advanced Features | -| Phase 10| 2 āļŠāļąāļ›āļ”āļēāļŦāđŒ | 4 | Testing & Polish (Idempotency Test) | -| **āļĢāļ§āļĄ** | **14 āļŠāļąāļ›āļ”āļēāļŦāđŒ** | **39 Tasks** | **Production-Ready Frontend v1.4.2** | - ---- - -## ðŸŽŊ **Critical Success Factors** - -1. **User Experience First:** āļ—āļļāļāļŸāļĩāđ€āļˆāļ­āļĢāđŒāļ•āđ‰āļ­āļ‡āļ­āļ­āļāđāļšāļšāđ€āļžāļ·āđˆāļ­āļ›āļĢāļ°āļŠāļšāļāļēāļĢāļ“āđŒāļœāļđāđ‰āđƒāļŠāđ‰āļ—āļĩāđˆāļ”āļĩ -2. **Responsive Design:** āļĢāļ­āļ‡āļĢāļąāļšāļāļēāļĢāđƒāļŠāđ‰āļ‡āļēāļ™āļšāļ™āļ­āļļāļ›āļāļĢāļ“āđŒāļ—āļļāļāļĢāļđāļ›āđāļšāļš -3. **Performance:** Core Web Vitals āļ•āđ‰āļ­āļ‡āļ­āļĒāļđāđˆāđƒāļ™āđ€āļāļ“āļ‘āđŒāļ—āļĩāđˆāļ”āļĩ -4. **Accessibility:** āļ•āđ‰āļ­āļ‡āđ€āļ›āđ‡āļ™āđ„āļ›āļ•āļēāļĄāļĄāļēāļ•āļĢāļāļēāļ™ WCAG 2.1 AA -5. **Security:** āļ›āđ‰āļ­āļ‡āļāļąāļ™ XSS, CSRF āđāļĨāļ°āļ„āļ§āļēāļĄāđ€āļŠāļĩāđˆāļĒāļ‡āļ”āđ‰āļēāļ™āļ„āļ§āļēāļĄāļ›āļĨāļ­āļ”āļ āļąāļĒāļ­āļ·āđˆāļ™āđ† -6. **Offline Support:** āļĢāļ­āļ‡āļĢāļąāļšāļāļēāļĢāļ—āļģāļ‡āļēāļ™āđāļšāļš Offline āđ€āļšāļ·āđ‰āļ­āļ‡āļ•āđ‰āļ™ -7. **Real-time Updates:** āļāļēāļĢāļ­āļąāļ›āđ€āļ”āļ•āļŠāļ–āļēāļ™āļ°āđāļšāļš Real-time -8. **Testing Coverage:** āļ„āļĢāļ­āļšāļ„āļĨāļļāļĄāļāļēāļĢāļ—āļ”āļŠāļ­āļšāļ—āļļāļ Critical Path -9. **Documentation:** āđ€āļ­āļāļŠāļēāļĢāļ„āļĢāļšāļ–āđ‰āļ§āļ™āļŠāļģāļŦāļĢāļąāļšāļœāļđāđ‰āđƒāļŠāđ‰āđāļĨāļ°āļ™āļąāļāļžāļąāļ’āļ™āļē - ---- - -## 📋 **Quality Assurance Checklist** - -### **āļāđˆāļ­āļ™ Production Deployment** - -- [ ] **Performance:** Core Web Vitals āļœāđˆāļēāļ™āđ€āļāļ“āļ‘āđŒ -- [ ] **Accessibility:** WCAG 2.1 AA compliant -- [ ] **Security:** Security audit āļœāđˆāļēāļ™ -- [ ] **Testing:** Test coverage â‰Ĩ 80% -- [ ] **Browser Compatibility:** āļ—āļģāļ‡āļēāļ™āđ„āļ”āđ‰āļšāļ™āđ€āļšāļĢāļēāļ§āđŒāđ€āļ‹āļ­āļĢāđŒāļŦāļĨāļąāļ -- [ ] **Mobile Responsive:** āđƒāļŠāđ‰āļ‡āļēāļ™āđ„āļ”āđ‰āļ”āļĩāļšāļ™āļĄāļ·āļ­āļ–āļ·āļ­ -- [ ] **Documentation:** āđ€āļ­āļāļŠāļēāļĢāļ„āļĢāļšāļ–āđ‰āļ§āļ™ -- [ ] **User Acceptance:** āđ„āļ”āđ‰āļĢāļąāļšāļāļēāļĢāļĒāļ­āļĄāļĢāļąāļšāļˆāļēāļāļœāļđāđ‰āđƒāļŠāđ‰ - ---- - -## 🚀 **āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ–āļąāļ”āđ„āļ›** - -1. **Approve āđāļœāļ™āļ™āļĩāđ‰** → āļ›āļĢāļąāļšāđāļ•āđˆāļ‡āļ•āļēāļĄ Feedback -2. **Coordinate āļāļąāļš Backend Team** → Sync API Specifications -3. **āđ€āļĢāļīāđˆāļĄāļžāļąāļ’āļ™āļē Phase 0** → Setup Foundation -4. **Regular Sync** → āļ›āļĢāļ°āļŠāļēāļ™āļ‡āļēāļ™āļāļąāļš Backend āļ—āļļāļāļŠāļąāļ›āļ”āļēāļŦāđŒ -5. **User Testing** → āļ—āļ”āļŠāļ­āļšāļāļąāļšāļœāļđāđ‰āđƒāļŠāđ‰āļˆāļĢāļīāļ‡āļĢāļ°āļŦāļ§āđˆāļēāļ‡āļžāļąāļ’āļ™āļē -6. **Deploy to Production** → Week 15 (āļžāļĢāđ‰āļ­āļĄ Backend) - -## **Document Control:** - -- **Document:** Frontend Development Plan v1.4.2 -- **Version:** 1.4 -- **Date:** 2025-11-19 -- **Author:** NAP LCBP3-DMS & Gemini -- **Status:** FINAL -- **Classification:** Internal Technical Documentation -- **Approved By:** Nattanin - ---- - -`End of Frontend Development Plan v1.4.2 (āļ‰āļšāļąāļšāļ›āļĢāļąāļšāļ›āļĢāļļāļ‡)` diff --git a/Documnets/Project/1.4.2/4_Data_Dictionary_V1_4_2.md b/Documnets/Project/1.4.2/4_Data_Dictionary_V1_4_2.md deleted file mode 100644 index 2a919ad..0000000 --- a/Documnets/Project/1.4.2/4_Data_Dictionary_V1_4_2.md +++ /dev/null @@ -1,2840 +0,0 @@ -# **āļ•āļēāļĢāļēāļ‡āļāļēāļ™āļ‚āđ‰āļ­āļĄāļđāļĨ (Data Dictionary) - LCBP3-DMS (V1.4.2)** - -āđ€āļ­āļāļŠāļēāļĢāļ™āļĩāđ‰āļŠāļĢāļļāļ›āđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡āļ•āļēāļĢāļēāļ‡, Foreign Keys (FK), āđāļĨāļ° Constraints āļ—āļĩāđˆāļŠāļģāļ„āļąāļāļ—āļąāđ‰āļ‡āļŦāļĄāļ”āđƒāļ™āļāļēāļ™āļ‚āđ‰āļ­āļĄāļđāļĨ LCBP3-DMS (v1.4.0) āđ€āļžāļ·āđˆāļ­āđƒāļŠāđ‰āđ€āļ›āđ‡āļ™āđ€āļ­āļāļŠāļēāļĢāļ­āđ‰āļēāļ‡āļ­āļīāļ‡āļŠāļģāļŦāļĢāļąāļšāļ—āļĩāļĄāļžāļąāļ’āļ™āļē Backend (NestJS) āđāļĨāļ° Frontend (Next.js) āđ‚āļ”āļĒāļ­āļīāļ‡āļˆāļēāļ Requirements āđāļĨāļ° SQL Script āļĨāđˆāļēāļŠāļļāļ” [GEMINI] - -**āļŠāļ–āļēāļ™āļ°:** FINAL GUIDELINE -**āļ§āļąāļ™āļ—āļĩāđˆ:** 2025-11-19 -**āļ­āđ‰āļēāļ‡āļ­āļīāļ‡:** Requirements v1.4.2 & FullStackJS Guidelines v1.4.2 -**Classification:** Internal Technical Documentation - ---- - -## **1. ðŸĒ Core & Master Data Tables (āļ­āļ‡āļ„āđŒāļāļĢ, āđ‚āļ„āļĢāļ‡āļāļēāļĢ, āļŠāļąāļāļāļē)** - -### 1.1 organization_roles - -**Purpose**: Master table for organization role types in the system - -| Column Name | Data Type | Constraints | Description | -| ----------- | ----------- | --------------------------- | ---------------------------------------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique identifier for organization role | -| role_name | VARCHAR(20) | NOT NULL, UNIQUE | Role name (OWNER, DESIGNER, CONSULTANT, CONTRACTOR, THIRD PARTY) | - -**Indexes**: - -- PRIMARY KEY (id) -- UNIQUE (role_name) - -**Business Rules**: - -- Predefined system roles for organization types -- Cannot be deleted if referenced by organizations - ---- - -### 1.2 organizations - -**Purpose**: Master table storing all organizations involved in the system - -| Column Name | Data Type | Constraints | Description | -| ----------------- | ------------ | ----------------------------------- | ---------------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique identifier for organization | -| organization_code | VARCHAR(20) | NOT NULL, UNIQUE | Organization code (e.g., 'āļāļ—āļ—.', 'TEAM') | -| organization_name | VARCHAR(255) | NOT NULL | Full organization name | -| is_active | BOOLEAN | DEFAULT TRUE | Active status (1=active, 0=inactive) | -| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp | -| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | - -**Indexes**: - -- PRIMARY KEY (id) -- UNIQUE (organization_code) -- INDEX (is_active) - -**Relationships**: - -- Referenced by: users, project_organizations, contract_organizations, correspondences, circulations - -**Seed Data**: Pre-populated with 15 organizations including: - -- Port Authority of Thailand (āļāļ—āļ—.) -- Project supervision consultants (āļŠāļ„ÂĐ.3-xx) -- Design consultants (TEAM) -- Construction supervisors (āļ„āļ„āļ‡.) -- Contractors (āļœāļĢāļĄ.1-4) -- Third parties (EN, CAR) - ---- - -### 1.3 projects - -**Purpose**: Master table for all projects in the system - -| Column Name | Data Type | Constraints | Description | -| ------------ | ------------ | --------------------------- | ----------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique identifier for project | -| project_code | VARCHAR(50) | NOT NULL, UNIQUE | Project code (e.g., 'LCBP3') | -| project_name | VARCHAR(255) | NOT NULL | Full project name | -| is_active | TINYINT(1) | DEFAULT 1 | Active status | - -**Indexes**: - -- PRIMARY KEY (id) -- UNIQUE (project_code) -- INDEX (is_active) - -**Relationships**: - -- Referenced by: contracts, correspondences, document_number_formats, drawings - -**Seed Data**: 5 projects for Laem Chabang Port Phase 3 (LCBP3) including main project and 4 sub-contracts - ---- - -### 1.4 contracts - -**Purpose**: Master table for contracts within projects - -| Column Name | Data Type | Constraints | Description | -| ------------- | ------------ | ----------------------------------- | ------------------------------ | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique identifier for contract | -| project_id | INT | NOT NULL, FK | Reference to projects table | -| contract_code | VARCHAR(50) | NOT NULL, UNIQUE | Contract code | -| contract_name | VARCHAR(255) | NOT NULL | Full contract name | -| description | TEXT | NULL | Contract description | -| start_date | DATE | NULL | Contract start date | -| end_date | DATE | NULL | Contract end date | -| is_active | BOOLEAN | DEFAULT TRUE | Active status | -| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp | -| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | - -**Indexes**: - -- PRIMARY KEY (id) -- UNIQUE (contract_code) -- FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE -- INDEX (project_id, is_active) - -**Relationships**: - -- Parent: projects -- Referenced by: contract_organizations, user_assignments - -**Seed Data**: 7 contracts including design, supervision, construction, and environmental monitoring - ---- - -## **2. ðŸ‘Ĩ Users & RBAC Tables (āļœāļđāđ‰āđƒāļŠāđ‰, āļŠāļīāļ—āļ˜āļīāđŒ, āļšāļ—āļšāļēāļ—)** - -### 2.1 users - -**Purpose**: Master table storing all system users - -| Column Name | Data Type | Constraints | Description | -| ----------------------- | ------------ | ----------------------------------- | -------------------------------- | -| user_id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique identifier for user | -| username | VARCHAR(50) | NOT NULL, UNIQUE | Login username | -| password_hash | VARCHAR(255) | NOT NULL | Hashed password (bcrypt) | -| first_name | VARCHAR(50) | NULL | User's first name | -| last_name | VARCHAR(50) | NULL | User's last name | -| email | VARCHAR(100) | NOT NULL, UNIQUE | Email address | -| line_id | VARCHAR(100) | NULL | LINE messenger ID | -| primary_organization_id | INT | NULL, FK | Primary organization affiliation | -| is_active | TINYINT(1) | DEFAULT 1 | Active status | -| failed_attempts | INT | DEFAULT 0 | Failed login attempts counter | -| locked_until | DATETIME | NULL | Account lock expiration time | -| last_login_at | TIMESTAMP | NULL | Last successful login timestamp | -| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp | -| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | -| deleted_at | DATETIME | NULL | Deleted at | - -**Indexes**: - -- PRIMARY KEY (user_id) -- UNIQUE (username) -- UNIQUE (email) -- FOREIGN KEY (primary_organization_id) REFERENCES organizations(id) ON DELETE SET NULL -- INDEX (is_active) -- INDEX (email) - -**Relationships**: - -- Parent: organizations (primary_organization_id) -- Referenced by: user_assignments, audit_logs, notifications, circulation_routings - -**Security Features**: - -- Password stored as bcrypt hash -- Account locking after failed attempts -- Last login tracking - -**Seed Data**: 3 initial users (superadmin, editor01, viewer01) - ---- - -### 2.2 roles - -**Purpose**: Master table defining system roles with scope levels - -| Column Name | Data Type | Constraints | Description | -| ----------- | ------------ | --------------------------- | ---------------------------------------------------- | -| role_id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique identifier for role | -| role_name | VARCHAR(100) | NOT NULL | Role name (e.g., 'Superadmin', 'Document Control') | -| scope | ENUM | NOT NULL | Scope level: Global, Organization, Project, Contract | -| description | TEXT | NULL | Role description | -| is_system | BOOLEAN | DEFAULT FALSE | System role flag (cannot be deleted) | - -**Indexes**: - -- PRIMARY KEY (role_id) -- INDEX (scope) - -**Relationships**: - -- Referenced by: role_permissions, user_assignments - -**Seed Data**: 7 predefined roles - -1. Superadmin (Global) - Full system access -2. Org Admin (Organization) - Organization management -3. Document Control (Organization) - Document lifecycle management -4. Editor (Organization) - Document creation and editing -5. Viewer (Organization) - Read-only access -6. Project Manager (Project) - Project-level management -7. Contract Admin (Contract) - Contract-specific administration - ---- - -### 2.3 permissions - -**Purpose**: Master table defining all system permissions - -| Column Name | Data Type | Constraints | Description | -| --------------- | ------------ | --------------------------- | ------------------------------------------------------ | -| permission_id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique identifier for permission | -| permission_name | VARCHAR(100) | NOT NULL, UNIQUE | Permission code (e.g., 'rfas.create', 'document.view') | -| description | TEXT | NULL | Permission description | -| module | VARCHAR(50) | NULL | Related module name | -| scope_level | ENUM | NULL | Scope: GLOBAL, ORG, PROJECT | -| is_active | TINYINT(1) | DEFAULT 1 | Active status | - -**Indexes**: - -- PRIMARY KEY (permission_id) -- UNIQUE (permission_name) -- INDEX (module) -- INDEX (scope_level) -- INDEX (is_active) - -**Relationships**: - -- Referenced by: role_permissions - -**Permission Categories**: - -1. **System Management** (1): system.manage_all -2. **Organization Management** (2-5): create, edit, delete, view -3. **Project Management** (6-9, 23-26): create, edit, delete, view, manage members/contracts/reports -4. **Role & Permission Management** (10-13): create, edit, delete roles, assign permissions -5. **Master Data Management** (14-17): document types, statuses, categories, tags -6. **User Management** (18-22): create, edit, delete, view, assign organization -7. **Contract Management** (27-28): manage members, view -8. **Document Management** (29-44): CRUD operations, workflows, circulation -9. **Search & Reporting** (48-49): advanced search, generate reports - -**Total Permissions**: 49 - ---- - -### 2.4 role_permissions - -**Purpose**: Junction table mapping roles to permissions (M:N) - -| Column Name | Data Type | Constraints | Description | -| ------------- | --------- | --------------- | ------------------------------ | -| role_id | INT | PRIMARY KEY, FK | Reference to roles table | -| permission_id | INT | PRIMARY KEY, FK | Reference to permissions table | - -**Indexes**: - -- 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 -- INDEX (permission_id) - -**Relationships**: - -- Parent: roles, permissions - -**Seed Data**: Complete permission mappings for all 7 roles - -- Superadmin: All 49 permissions -- Org Admin: 15 permissions (user/org/tag management, view access) -- Document Control: 26 permissions (full document lifecycle) -- Editor: 12 permissions (document CRUD without admin powers) -- Viewer: 2 permissions (view and search only) -- Project Manager: 23 permissions (project management + document CRUD) -- Contract Admin: 15 permissions (contract management + document CRUD) - ---- - -### 2.5 user_assignments - -**Purpose**: Junction table assigning users to roles with scope context - -| Column Name | Data Type | Constraints | Description | -| ------------------- | --------- | --------------------------- | ---------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique identifier | -| user_id | INT | NOT NULL, FK | Reference to users table | -| role_id | INT | NOT NULL, FK | Reference to roles table | -| organization_id | INT | NULL, FK | Organization scope (if applicable) | -| project_id | INT | NULL, FK | Project scope (if applicable) | -| contract_id | INT | NULL, FK | Contract scope (if applicable) | -| assigned_by_user_id | INT | NULL, FK | User who made the assignment | -| assigned_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Assignment timestamp | - -**Indexes**: - -- PRIMARY KEY (id) -- 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) -- INDEX (user_id, role_id) -- INDEX (organization_id) -- INDEX (project_id) -- INDEX (contract_id) - -**Constraints**: - -- CHECK: Only one scope field (organization_id, project_id, contract_id) can be NOT NULL, or all NULL for Global scope - -**Relationships**: - -- Parent: users, roles, organizations, projects, contracts - -**Business Rules**: - -- User can have multiple role assignments with different scopes -- Scope inheritance: Contract → Project → Organization → Global -- Global scope: all scope fields are NULL - ---- - -### 2.6 project_organizations - -**Purpose**: Junction table linking projects to participating organizations (M:N) - -| Column Name | Data Type | Constraints | Description | -| --------------- | --------- | --------------- | -------------------------------- | -| project_id | INT | PRIMARY KEY, FK | Reference to projects table | -| organization_id | INT | PRIMARY KEY, FK | Reference to organizations table | - -**Indexes**: - -- 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 -- INDEX (organization_id) - -**Relationships**: - -- Parent: projects, organizations - -**Seed Data**: Pre-populated with project-organization relationships - ---- - -### 2.7 contract_organizations - -**Purpose**: Junction table linking contracts to participating organizations with roles (M:N) - -| Column Name | Data Type | Constraints | Description | -| ---------------- | ------------ | --------------- | ------------------------------------------------------------------------- | -| contract_id | INT | PRIMARY KEY, FK | Reference to contracts table | -| organization_id | INT | PRIMARY KEY, FK | Reference to organizations table | -| role_in_contract | VARCHAR(100) | NULL | Organization's role in contract (Owner, Designer, Consultant, Contractor) | - -**Indexes**: - -- 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 -- INDEX (organization_id) -- INDEX (role_in_contract) - -**Relationships**: - -- Parent: contracts, organizations - -**Seed Data**: Pre-populated with contract-organization-role relationships - ---- - -## **3. ✉ïļ Correspondences Tables (āđ€āļ­āļāļŠāļēāļĢāļŦāļĨāļąāļ, Revisions, Workflows)** - -### 3.1 correspondence_types - -**Purpose**: Master table for correspondence document types - -| Column Name | Data Type | Constraints | Description | -| ----------- | ------------ | --------------------------- | --------------------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique identifier | -| type_code | VARCHAR(50) | NOT NULL, UNIQUE | Type code (e.g., 'RFA', 'RFI', 'TRANSMITTAL') | -| type_name | VARCHAR(255) | NOT NULL | Full type name | -| sort_order | INT | DEFAULT 0 | Display order | -| is_active | TINYINT(1) | DEFAULT 1 | Active status | - -**Indexes**: - -- PRIMARY KEY (id) -- UNIQUE (type_code) -- INDEX (is_active) -- INDEX (sort_order) - -**Relationships**: - -- Referenced by: correspondences, document_number_formats, document_number_counters - -**Seed Data**: 10 correspondence types including RFA, RFI, TRANSMITTAL, EMAIL, INSTRUCTION, LETTER, MEMO, MOM, NOTICE, OTHER - ---- - -### 3.2 correspondence_status - -**Purpose**: Master table for correspondence status codes - -| Column Name | Data Type | Constraints | Description | -| ----------- | ------------ | --------------------------- | ------------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique identifier | -| status_code | VARCHAR(50) | NOT NULL, UNIQUE | Status code (e.g., 'DRAFT', 'SUBOWN') | -| status_name | VARCHAR(255) | NOT NULL | Full status name | -| sort_order | INT | DEFAULT 0 | Display order | -| is_active | TINYINT(1) | DEFAULT 1 | Active status | - -**Indexes**: - -- PRIMARY KEY (id) -- UNIQUE (status_code) -- INDEX (is_active) -- INDEX (sort_order) - -**Relationships**: - -- Referenced by: correspondence_revisions - -**Seed Data**: 23 status codes covering draft, submission, reply, resubmission, closure, and cancellation states by different parties (Owner, Designer, CSC, Contractor) - ---- - -### 3.3 correspondences - -**Purpose**: Master table for correspondence documents (non-revisioned data) - -| Column Name | Data Type | Constraints | Description | -| ------------------------- | ------------ | --------------------------- | ------------------------------------------ | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Master correspondence ID | -| correspondence_number | VARCHAR(100) | NOT NULL | Document number (from numbering system) | -| correspondence_type_id | INT | NOT NULL, FK | Reference to correspondence_types | -| is_internal_communication | TINYINT(1) | DEFAULT 0 | Internal (1) or external (0) communication | -| project_id | INT | NOT NULL, FK | Reference to projects table | -| originator_id | INT | NULL, FK | Originating organization | -| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp | -| created_by | INT | NULL, FK | User who created the record | -| deleted_at | DATETIME | NULL | Soft delete timestamp | - -**Indexes**: - -- PRIMARY KEY (id) -- 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 (project_id, correspondence_number) -- INDEX (correspondence_type_id) -- INDEX (originator_id) -- INDEX (deleted_at) - -**Relationships**: - -- Parent: correspondence_types, projects, organizations, users -- Children: correspondence_revisions, correspondence_recipients, correspondence_tags, correspondence_references, correspondence_attachments, circulations, transmittals - -**Business Rules**: - -- One correspondence can have multiple revisions -- Correspondence number must be unique within a project -- Soft delete preserves history - ---- - -### 3.4 correspondence_revisions - -**Purpose**: Child table storing revision history of correspondences (1:N) - -| Column Name | Data Type | Constraints | Description | -| ------------------------ | ------------ | --------------------------------- | -------------------------------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique revision ID | -| correspondence_id | INT | NOT NULL, FK | Master correspondence ID | -| revision_number | INT | NOT NULL | Revision sequence (0, 1, 2...) | -| revision_label | VARCHAR(10) | NULL | Display revision (A, B, 1.1...) | -| is_current | BOOLEAN | DEFAULT FALSE | Current revision flag | -| correspondence_status_id | INT | NOT NULL, FK | Current status of this revision | -| title | VARCHAR(255) | NOT NULL | Document title | -| document_date | DATE | NULL | Document date | -| issued_date | DATETIME | NULL | Issue date | -| received_date | DATETIME | NULL | Received date | -| due_date | DATETIME | NULL | Due date for response | -| description | TEXT | NULL | Revision description | -| details | JSON | NULL | Type-specific details (e.g., RFI questions) | -| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | Revision creation timestamp | -| created_by | INT | NULL, FK | User who created revision | -| updated_by | INT | NULL, FK | User who last updated | -| v_ref_project_id | INT | GENERATED ALWAYS AS (...) VIRTUAL | Virtual Column āļ”āļķāļ‡ Project ID āļˆāļēāļ JSON details āđ€āļžāļ·āđˆāļ­āļ—āļģ Index | -| v_ref_type | VARCHAR(50) | GENERATED ALWAYS AS (...) VIRTUAL | Virtual Column āļ”āļķāļ‡ Type āļˆāļēāļ JSON details | - -**Indexes**: - -- PRIMARY KEY (id) -- 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 (correspondence_id, revision_number) -- UNIQUE KEY (correspondence_id, is_current) - Only one current revision per correspondence -- INDEX (correspondence_status_id) -- INDEX (is_current) -- INDEX (document_date) -- INDEX (issued_date) -- INDEX (v_ref_project_id) -- INDEX (v_ref_type) - -**Relationships**: - -- Parent: correspondences, correspondence_status, users - -**Business Rules**: - -- Only one revision can be marked as current (is_current=TRUE) per correspondence -- Revision numbers are sequential starting from 0 -- JSON details field allows type-specific data storage - ---- - -### 3.5 correspondence_recipients - -**Purpose**: Junction table for correspondence recipients (TO/CC) (M:N) - -| Column Name | Data Type | Constraints | Description | -| ------------------------- | ---------------- | --------------- | ---------------------------- | -| correspondence_id | INT | PRIMARY KEY, FK | Reference to correspondences | -| recipient_organization_id | INT | PRIMARY KEY, FK | Recipient organization | -| recipient_type | ENUM('TO', 'CC') | PRIMARY KEY | Recipient type | - -**Indexes**: - -- 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 -- INDEX (recipient_organization_id) -- INDEX (recipient_type) - -**Relationships**: - -- Parent: correspondences, organizations - -**Business Rules**: - -- One organization can be both TO and CC recipient -- Cascade delete when correspondence is deleted - ---- - -### 3.6 tags - -**Purpose**: Master table for document tagging system - -| Column Name | Data Type | Constraints | Description | -| ----------- | ------------ | ----------------------------------- | ------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique tag ID | -| tag_name | VARCHAR(100) | NOT NULL, UNIQUE | Tag name | -| description | TEXT | NULL | Tag description | -| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp | -| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | - -**Indexes**: - -- PRIMARY KEY (id) -- UNIQUE (tag_name) -- INDEX (tag_name) - For autocomplete - -**Relationships**: - -- Referenced by: correspondence_tags - ---- - -### 3.7 correspondence_tags - -**Purpose**: Junction table linking correspondences to tags (M:N) - -| Column Name | Data Type | Constraints | Description | -| ----------------- | --------- | --------------- | ---------------------------- | -| correspondence_id | INT | PRIMARY KEY, FK | Reference to correspondences | -| tag_id | INT | PRIMARY KEY, FK | Reference to tags | - -**Indexes**: - -- 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 -- INDEX (tag_id) - -**Relationships**: - -- Parent: correspondences, tags - ---- - -### 3.8 correspondence_references - -**Purpose**: Junction table for cross-referencing correspondences (M:N) - -| Column Name | Data Type | Constraints | Description | -| --------------------- | --------- | --------------- | ------------------------------------- | -| src_correspondence_id | INT | PRIMARY KEY, FK | Source correspondence ID | -| tgt_correspondence_id | INT | PRIMARY KEY, FK | Target (referenced) correspondence ID | - -**Indexes**: - -- 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 -- INDEX (tgt_correspondence_id) - -**Relationships**: - -- Parent: correspondences (both sides) - -**Business Rules**: - -- Allows tracing document relationships -- Bi-directional references should be created as separate records - -### 3.9 āļ•āļēāļĢāļēāļ‡ correspondence_routing_templates - -**Purpose**: āđ€āļāđ‡āļšāļ‚āđ‰āļ­āļĄāļđāļĨāđāļĄāđˆāđāļšāļš (Template) āļ‚āļ­āļ‡āļŠāļēāļĒāļ‡āļēāļ™āļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­āđ€āļ­āļāļŠāļēāļĢāđ€āļžāļ·āđˆāļ­āļ‚āļ­āļ­āļ™āļļāļĄāļąāļ•āļī āļ—āļģāđƒāļŦāđ‰āđ„āļĄāđˆāļ•āđ‰āļ­āļ‡āļāļģāļŦāļ™āļ”āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ‹āđ‰āļģāļ—āļļāļāļ„āļĢāļąāđ‰āļ‡ āļŠāļēāļĄāļēāļĢāļ–āļŠāļĢāđ‰āļēāļ‡āđ€āļ›āđ‡āļ™āđāļĄāđˆāđāļšāļšāļ—āļąāđˆāļ§āđ„āļ› āļŦāļĢāļ·āļ­āđ€āļ‰āļžāļēāļ°āļŠāļģāļŦāļĢāļąāļšāđ‚āļ„āļĢāļ‡āļāļēāļĢāđƒāļ”āđ‚āļ„āļĢāļ‡āļāļēāļĢāļŦāļ™āļķāđˆāļ‡āđ„āļ”āđ‰ - -| Column Name | Data Type | Constraints | Description | -| --------------- | ------------ | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | ID āļŦāļĨāļąāļ (Primary Key) āļ‚āļ­āļ‡āđāļĄāđˆāđāļšāļš āļĢāļąāļ™āļ„āđˆāļēāļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī | -| template_name | VARCHAR(255) | NOT NULL | āļŠāļ·āđˆāļ­āļ‚āļ­āļ‡āđāļĄāđˆāđāļšāļš āđ€āļŠāđˆāļ™ "āđ€āļŠāļ™āļ­āđ‚āļ„āļĢāļ‡āļāļēāļĢ", "āļ‚āļ­āļ­āļ™āļļāļĄāļąāļ•āļīāļˆāļąāļ”āļ‹āļ·āđ‰āļ­" | -| description | TEXT | NULL | āļ„āļģāļ­āļ˜āļīāļšāļēāļĒāļĢāļēāļĒāļĨāļ°āđ€āļ­āļĩāļĒāļ”āđ€āļāļĩāđˆāļĒāļ§āļāļąāļšāđāļĄāđˆāđāļšāļšāļ™āļĩāđ‰ | -| project_id | INT | NULL | ID āļ‚āļ­āļ‡āđ‚āļ„āļĢāļ‡āļāļēāļĢāļ—āļĩāđˆāđāļĄāđˆāđāļšāļšāļ™āļĩāđ‰āļŠāļąāļ‡āļāļąāļ”āļ­āļĒāļđāđˆ (āļ–āđ‰āļēāļĄāļĩ) **āļ„āđˆāļē NULL āļŦāļĄāļēāļĒāļ–āļķāļ‡** āđ€āļ›āđ‡āļ™ "āđāļĄāđˆāđāļšāļšāļ—āļąāđˆāļ§āđ„āļ›" āļ—āļĩāđˆāļŠāļēāļĄāļēāļĢāļ–āđƒāļŠāđ‰āļāļąāļšāļ—āļļāļāđ‚āļ„āļĢāļ‡āļāļēāļĢāđ„āļ”āđ‰ | -| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | āļ§āļąāļ™āļ—āļĩāđˆāđāļĨāļ°āđ€āļ§āļĨāļēāļ—āļĩāđˆāļŠāļĢāđ‰āļēāļ‡āđāļĄāđˆāđāļšāļšāļ™āļĩāđ‰ | -| updated_at | TIMESTAMP | NOT NULL,`DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | āļ§āļąāļ™āļ—āļĩāđˆāđāļĨāļ°āđ€āļ§āļĨāļēāļ—āļĩāđˆāđāļāđ‰āđ„āļ‚āļ‚āđ‰āļ­āļĄāļđāļĨāđƒāļ™āđāļĄāđˆāđāļšāļšāļ™āļĩāđ‰āļĨāđˆāļēāļŠāļļāļ” | -| is_active | BOOLEAN | DEFAULT TRUE | āļŠāļ–āļēāļ™āļ°āđƒāļŠāđ‰āļ‡āļēāļ™ | -| workflow_config | JSON | NULL | āđ€āļāđ‡āļš State Machine Configuration āļŦāļĢāļ·āļ­ Rules āđ€āļžāļīāđˆāļĄāđ€āļ•āļīāļĄāļ—āļĩāđˆāļ‹āļąāļšāļ‹āđ‰āļ­āļ™āļāļ§āđˆāļē Column āļ›āļāļ•āļī | - -**Indexes**: - -- ux_routing_template_name_project (template_name, project_id): āļ”āļąāļŠāļ™āļĩāđāļšāļš UNIQUE āļ›āđ‰āļ­āļ‡āļāļąāļ™āļāļēāļĢāļĄāļĩāļŠāļ·āđˆāļ­āđāļĄāđˆāđāļšāļšāļ‹āđ‰āļģāļāļąāļ™āđƒāļ™āđāļ•āđˆāļĨāļ°āđ‚āļ„āļĢāļ‡āļāļēāļĢ āļŦāļĢāļ·āļ­āđƒāļ™āļŠāđˆāļ§āļ™āļ‚āļ­āļ‡āđāļĄāđˆāđāļšāļšāļ—āļąāđˆāļ§āđ„āļ› (āļ—āļĩāđˆ project_id āđ€āļ›āđ‡āļ™ NULL) - -**āļ„āļ§āļēāļĄāļŠāļąāļĄāļžāļąāļ™āļ˜āđŒ Foreign Key:** - -- fk_crt_project: āļ­āđ‰āļēāļ‡āļ­āļīāļ‡āđ„āļ›āļĒāļąāļ‡ projects(id) -- ON DELETE CASCAD`: āļ–āđ‰āļēāđ‚āļ„āļĢāļ‡āļāļēāļĢ (projects) āļ–āļđāļāļĨāļš āđāļĄāđˆāđāļšāļšāļ—āļĩāđˆāđ€āļāļĩāđˆāļĒāļ§āļ‚āđ‰āļ­āļ‡āļāļąāļšāđ‚āļ„āļĢāļ‡āļāļēāļĢāļ™āļąāđ‰āļ™āđ† āļˆāļ°āļ–āļđāļāļĨāļšāđ„āļ›āļ”āđ‰āļ§āļĒāļ—āļąāđ‰āļ‡āļŦāļĄāļ” - ---- - -### 3.10 āļ•āļēāļĢāļēāļ‡ correspondence_routing_template_steps - -**Purpose**: āđ€āļāđ‡āļšāļĢāļēāļĒāļĨāļ°āđ€āļ­āļĩāļĒāļ”āļ‚āļ­āļ‡āđāļ•āđˆāļĨāļ°āļ‚āļąāđ‰āļ™āļ•āļ­āļ™ (Steps) āļ āļēāļĒāđƒāļ™āđāļĄāđˆāđāļšāļšāļŠāļēāļĒāļ‡āļēāļ™ (correspondence_routing_templates) āļāļģāļŦāļ™āļ”āļ§āđˆāļēāļˆāļ°āļŠāđˆāļ‡āđ„āļ›āļ—āļĩāđˆāļ­āļ‡āļ„āđŒāļāļĢāđ„āļŦāļ™ āļĨāļģāļ”āļąāļšāđ€āļ›āđ‡āļ™āđ€āļ—āđˆāļēāđ„āļĢ āđāļĨāļ°āđ€āļžāļ·āđˆāļ­āļ§āļąāļ•āļ–āļļāļ›āļĢāļ°āļŠāļ‡āļ„āđŒāļ­āļ°āđ„āļĢ - -| Column Name | Data Type | Constraints | Description | -| :----------------- | --------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | ID āļŦāļĨāļąāļāļ‚āļ­āļ‡āļ‚āļąāđ‰āļ™āļ•āļ­āļ™ | -| template_id | INT | NOT NULL | ID āļ‚āļ­āļ‡āđāļĄāđˆāđāļšāļšāļ—āļĩāđˆāļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ™āļĩāđ‰āļŠāļąāļ‡āļāļąāļ”āļ­āļĒāļđāđˆ | -| sequence | INT | NOT NULL | āļĨāļģāļ”āļąāļšāļ‚āļ­āļ‡āļ‚āļąāđ‰āļ™āļ•āļ­āļ™ (1, 2, 3, ...) | -| to_organization_id | INT | NOT NULL | ID āļ‚āļ­āļ‡āļ­āļ‡āļ„āđŒāļāļĢāļ—āļĩāđˆāđ€āļ›āđ‡āļ™āļœāļđāđ‰āļĢāļąāļšāđƒāļ™āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ™āļĩāđ‰ | -| step_purpose | ENUM | NOT NULL,DEFAULT FOR_REVIEW | āļ§āļąāļ•āļ–āļļāļ›āļĢāļ°āļŠāļ‡āļ„āđŒāļ‚āļ­āļ‡āļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­āđƒāļ™āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ™āļĩāđ‰ **āļ„āđˆāļēāļ—āļĩāđˆāđ€āļ›āđ‡āļ™āđ„āļ›āđ„āļ”āđ‰:** [FOR_APPROVAL: āđ€āļžāļ·āđˆāļ­āļ­āļ™āļļāļĄāļąāļ•āļī, FOR_REVIEW: āđ€āļžāļ·āđˆāļ­āļ•āļĢāļ§āļˆāļŠāļ­āļš/āļžāļīāļˆāļēāļĢāļ“āļē, FOR_INFORMATION: āđ€āļžāļ·āđˆāļ­āļ—āļĢāļēāļš] | -| expected_days | INT | NULL | āļ§āļąāļ™āļ—āļĩāđˆāļ„āļēāļ”āļŦāļ§āļąāļ‡ | - -**Indexes**: - -- ux_cor_template_sequence (template_id, sequence): āļ”āļąāļŠāļ™āļĩāđāļšāļš UNIQUE āļ›āđ‰āļ­āļ‡āļāļąāļ™āļāļēāļĢāļĄāļĩāļĨāļģāļ”āļąāļšāļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ‹āđ‰āļģāļāļąāļ™āļ āļēāļĒāđƒāļ™āđāļĄāđˆāđāļšāļšāđ€āļ”āļĩāļĒāļ§āļāļąāļ™ - -**āļ„āļ§āļēāļĄāļŠāļąāļĄāļžāļąāļ™āļ˜āđŒ Foreign Key:** - -- fk_cwts_template: āļ­āđ‰āļēāļ‡āļ­āļīāļ‡āđ„āļ›āļĒāļąāļ‡ correspondence_routing_templates(id) -- ON DELETE CASCADE: āļ–āđ‰āļēāđāļĄāđˆāđāļšāļšāļ–āļđāļāļĨāļš āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ—āļąāđ‰āļ‡āļŦāļĄāļ”āļ āļēāļĒāđƒāļ™āđāļĄāđˆāđāļšāļšāļ™āļąāđ‰āļ™āļˆāļ°āļ–āļđāļāļĨāļšāđ„āļ›āļ”āđ‰āļ§āļĒ -- fk_cwts_org: āļ­āđ‰āļēāļ‡āļ­āļīāļ‡āđ„āļ›āļĒāļąāļ‡ organizations(id) -- ON DELETE CASCADE: āļ–āđ‰āļēāļ­āļ‡āļ„āđŒāļāļĢāļ–āļđāļāļĨāļš āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ—āļĩāđˆāļŠāļĩāđ‰āđ„āļ›āļĒāļąāļ‡āļ­āļ‡āļ„āđŒāļāļĢāļ™āļąāđ‰āļ™āļˆāļ°āļ–āļđāļāļĨāļš - ---- - -### 3.11 āļ•āļēāļĢāļēāļ‡ correspondence_routings - -**Purpose**: āđ€āļ›āđ‡āļ™āļ•āļēāļĢāļēāļ‡āļ—āļĩāđˆāđ€āļāđ‡āļšāļ‚āđ‰āļ­āļĄāļđāļĨāļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­āđ€āļ­āļāļŠāļēāļĢāļˆāļĢāļīāļ‡ (Instance/Run-time) āļ•āļīāļ”āļ•āļēāļĄāļ›āļĢāļ°āļ§āļąāļ•āļīāļāļēāļĢāđ€āļ„āļĨāļ·āđˆāļ­āļ™āļĒāđ‰āļēāļĒāļ‚āļ­āļ‡āđāļ•āđˆāļĨāļ°āđ€āļ­āļāļŠāļēāļĢ āļ§āđˆāļēāļœāđˆāļēāļ™āđƒāļ„āļĢāļĄāļēāļšāđ‰āļēāļ‡ āļ­āļĒāļđāđˆāļ—āļĩāđˆāđƒāļ„āļĢ āđāļĨāļ°āļŠāļ–āļēāļ™āļ°āļ›āļąāļˆāļˆāļļāļšāļąāļ™āļ„āļ·āļ­āļ­āļ°āđ„āļĢ - -| Column Name | Data Type | Constraints | Description | -| -------------------- | --------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | ID āļŦāļĨāļąāļāļ‚āļ­āļ‡āļĢāļēāļĒāļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­ | -| correspondence_id | INT | NOT NUL | ID āļ‚āļ­āļ‡āđ€āļ­āļāļŠāļēāļĢ (FK āđ„āļ›āļĒāļąāļ‡ correspondence_revisions) | -| template_id | INT | NULL | ID āļ‚āļ­āļ‡āđāļĄāđˆāđāļšāļšāļ—āļĩāđˆāđƒāļŠāđ‰āļŠāļĢāđ‰āļēāļ‡āļŠāļēāļĒāļ‡āļēāļ™āļ™āļĩāđ‰ (āđ€āļāđ‡āļšāđ„āļ§āđ‰āđ€āļ›āđ‡āļ™āļ‚āđ‰āļ­āļĄāļđāļĨāļ­āđ‰āļēāļ‡āļ­āļīāļ‡) | -| sequence | INT | NOT NULL | āļĨāļģāļ”āļąāļšāļ‚āļ­āļ‡āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­āļˆāļĢāļīāļ‡ | -| from_organization_id | INT | NOT NULL | ID āļ‚āļ­āļ‡āļ­āļ‡āļ„āđŒāļāļĢāļœāļđāđ‰āļŠāđˆāļ‡ | -| to_organization_id | INT | NOT NULL | ID āļ‚āļ­āļ‡āļ­āļ‡āļ„āđŒāļāļĢāļœāļđāđ‰āļĢāļąāļš | -| step_purpose | ENUM | NOT NULL, DEFAULT FOR_REVIEW | āļ§āļąāļ•āļ–āļļāļ›āļĢāļ°āļŠāļ‡āļ„āđŒāļ‚āļ­āļ‡āļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­āđƒāļ™āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ™āļĩāđ‰āļˆāļĢāļīāļ‡ **āļ„āđˆāļēāļ—āļĩāđˆāđ€āļ›āđ‡āļ™āđ„āļ›āđ„āļ”āđ‰:** [FOR_APPROVAL: āđ€āļžāļ·āđˆāļ­āļ­āļ™āļļāļĄāļąāļ•āļī, FOR_REVIEW: āđ€āļžāļ·āđˆāļ­āļ•āļĢāļ§āļˆāļŠāļ­āļš/āļžāļīāļˆāļēāļĢāļ“āļē, FOR_INFORMATION: āđ€āļžāļ·āđˆāļ­āļ—āļĢāļēāļš, FOR_ACTION: āđ€āļžāļ·āđˆāļ­āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢ] | -| status | ENUM | NOT NULL, DEFAULT SENT | [ACTIONED: āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāđāļĨāđ‰āļ§, FORWARDED: āļŠāđˆāļ‡āļ•āđˆāļ­āđāļĨāđ‰āļ§, REPLIE: āļ•āļ­āļšāļāļĨāļąāļšāđāļĨāđ‰āļ§] | -| comments | TEXT | NULL | āļŦāļĄāļēāļĒāđ€āļŦāļ•āļļāļŦāļĢāļ·āļ­āļ„āļ§āļēāļĄāļ„āļīāļ”āđ€āļŦāđ‡āļ™āđƒāļ™āļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­ | -| due_date | DATETIME | NULL | āļ§āļąāļ™āļ—āļĩāđˆāļ„āļĢāļšāļāļģāļŦāļ™āļ”āļ—āļĩāđˆāļ•āđ‰āļ­āļ‡āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāđƒāļ™āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ™āļĩāđ‰ | -| processed_by_user_id | INT | NULL | ID āļ‚āļ­āļ‡āļœāļđāđ‰āđƒāļŠāđ‰āļ—āļĩāđˆāļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāđƒāļ™āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļ™āļĩāđ‰āļˆāļĢāļīāļ‡āđ† | -| processed_at | TIMESTAMP | NULL | āđ€āļ§āļĨāļēāļ—āļĩāđˆāļœāļđāđ‰āđƒāļŠāđ‰āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāđ€āļŠāļĢāđ‡āļˆāļŠāļīāđ‰āļ™ | -| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | āđ€āļ§āļĨāļēāļ—āļĩāđˆāļŠāļĢāđ‰āļēāļ‡āļĢāļēāļĒāļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­āļ™āļĩāđ‰ | -| state_context | JSON | NULL | āđ€āļāđ‡āļšāļ‚āđ‰āļ­āļĄāļđāļĨ Context āļ‚āļ­āļ‡ Workflow āļ“ āļ‚āļ“āļ°āļ™āļąāđ‰āļ™ (Snapshot) | - -**Indexes**: - -- ux_cor_routing_sequence (correspondence_id, sequence): āļ”āļąāļŠāļ™āļĩāđāļšāļš UNIQUE āļ—āļģāđƒāļŦāđ‰āļĄāļąāđˆāļ™āđƒāļˆāđ„āļ”āđ‰āļ§āđˆāļēāđāļ•āđˆāļĨāļ°āđ€āļ­āļāļŠāļēāļĢāļˆāļ°āļĄāļĩāļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­āļ•āļēāļĄāļĨāļģāļ”āļąāļšāđ„āļ”āđ‰āđ€āļžāļĩāļĒāļ‡āļŠāļļāļ”āđ€āļ”āļĩāļĒāļ§ - -**āļ„āļ§āļēāļĄāļŠāļąāļĄāļžāļąāļ™āļ˜āđŒ Foreign Key:** - -- fk_crs_correspondence: āļ­āđ‰āļēāļ‡āļ­āļīāļ‡āđ„āļ›āļĒāļąāļ‡ correspondence_revisions(correspondence_id) -- ON DELETE CASCADE`: āļ–āđ‰āļēāđ€āļ­āļāļŠāļēāļĢ (revision) āļ–āļđāļāļĨāļš āļ›āļĢāļ°āļ§āļąāļ•āļīāļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­āļ—āļąāđ‰āļ‡āļŦāļĄāļ”āļˆāļ°āļ–āļđāļāļĨāļšāđ„āļ›āļ”āđ‰āļ§āļĒ -- fk_crs_template: āļ­āđ‰āļēāļ‡āļ­āļīāļ‡āđ„āļ›āļĒāļąāļ‡ correspondence_routing_templates(id) -- ON DELETE SET NULL: āļ–āđ‰āļēāđāļĄāđˆāđāļšāļšāļ–āļđāļāļĨāļš āļ„āđˆāļē template_id āđƒāļ™āļ•āļēāļĢāļēāļ‡āļ™āļĩāđ‰āļˆāļ°āļ–āļđāļāđ€āļ›āļĨāļĩāđˆāļĒāļ™āđ€āļ›āđ‡āļ™ NULL āđ€āļžāļ·āđˆāļ­āļĢāļąāļāļĐāļēāļ›āļĢāļ°āļ§āļąāļ•āļīāļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­āđ„āļ§āđ‰ -- fk_crs_from_org: āļ­āđ‰āļēāļ‡āļ­āļīāļ‡āđ„āļ›āļĒāļąāļ‡ organizations(id) -- ON DELETE CASCADE: āļ–āđ‰āļēāļ­āļ‡āļ„āđŒāļāļĢāļœāļđāđ‰āļŠāđˆāļ‡āļ–āļđāļāļĨāļš āļĢāļēāļĒāļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­āļ™āļĩāđ‰āļˆāļ°āļ–āļđāļāļĨāļš -- fk_crs_to_org: āļ­āđ‰āļēāļ‡āļ­āļīāļ‡āđ„āļ›āļĒāļąāļ‡ organizations(id) -- ON DELETE CASCADE: āļ–āđ‰āļēāļ­āļ‡āļ„āđŒāļāļĢāļœāļđāđ‰āļĢāļąāļšāļ–āļđāļāļĨāļš āļĢāļēāļĒāļāļēāļĢāļŠāđˆāļ‡āļ•āđˆāļ­āļ™āļĩāđ‰āļˆāļ°āļ–āļđāļāļĨāļš -- fk_crs_processed_by_user: āļ­āđ‰āļēāļ‡āļ­āļīāļ‡āđ„āļ›āļĒāļąāļ‡ users(user_id) -- ON DELETE SET NULL: āļ–āđ‰āļēāļœāļđāđ‰āđƒāļŠāđ‰āļ–āļđāļāļĨāļš āļ„āđˆāļē processed_by_user_id āļˆāļ°āļ–āļđāļāđ€āļ›āļĨāļĩāđˆāļĒāļ™āđ€āļ›āđ‡āļ™ NULL āđ€āļžāļ·āđˆāļ­āļĢāļąāļāļĐāļēāļ›āļĢāļ°āļ§āļąāļ•āļīāļāļēāļĢāļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāđ„āļ§āđ‰ - ---- - -### 3.12 āļ•āļēāļĢāļēāļ‡ correspondence_status_transitions - -**Purpose**: āļ•āļēāļĢāļēāļ‡āļ™āļĩāđ‰āđƒāļŠāđ‰āļāļģāļŦāļ™āļ”āļāļŽ (State Machine) āļ§āđˆāļēāļŠāļ–āļēāļ™āļ°āđƒāļ”āļŠāļēāļĄāļēāļĢāļ–āđ€āļ›āļĨāļĩāđˆāļĒāļ™āđ„āļ›āđ€āļ›āđ‡āļ™āļŠāļ–āļēāļ™āļ°āđƒāļ”āđ„āļ”āđ‰āļšāđ‰āļēāļ‡ āđ‚āļ”āļĒāļ‚āļķāđ‰āļ™āļ­āļĒāļđāđˆāļāļąāļšāļ›āļĢāļ°āđ€āļ āļ—āļ‚āļ­āļ‡āļŦāļ™āļąāļ‡āļŠāļ·āļ­ āđ€āļžāļ·āđˆāļ­āļ„āļ§āļšāļ„āļļāļĄāļāļēāļĢāđ„āļŦāļĨāļ‚āļ­āļ‡āļŠāļ–āļēāļ™āļ°āđƒāļŦāđ‰āļ–āļđāļāļ•āđ‰āļ­āļ‡āļ•āļēāļĄāļ‚āđ‰āļ­āļšāļąāļ‡āļ„āļąāļš - -| Column Name | Data Type | Constraints | Description | -| -------------- | --------- | ----------- | ----------------------------------------------- | -| type_id | INT | PRIMARY KEY | ID āļ‚āļ­āļ‡āļ›āļĢāļ°āđ€āļ āļ—āļŦāļ™āļąāļ‡āļŠāļ·āļ­ (āđ€āļŠāđˆāļ™ āļŦāļ™āļąāļ‡āļŠāļ·āļ­āļ āļēāļĒāđƒāļ™, āļŦāļ™āļąāļ‡āļŠāļ·āļ­āļ āļēāļĒāļ™āļ­āļ) | -| from_status_id | INT | PRIMARY KEY | ID āļ‚āļ­āļ‡āļŠāļ–āļēāļ™āļ°āļ•āđ‰āļ™āļ—āļēāļ‡ (āđ€āļŠāđˆāļ™ āļĢāđˆāļēāļ‡) | -| to_status_id | INT | PRIMARY KEY | ID āļ‚āļ­āļ‡āļŠāļ–āļēāļ™āļ°āļ›āļĨāļēāļĒāļ—āļēāļ‡ (āđ€āļŠāđˆāļ™ āļĢāļ­āļ­āļ™āļļāļĄāļąāļ•āļī) | - -**āļ„āļĩāļĒāđŒāļŦāļĨāļąāļ (Primary Key):** - -- (type_id, from_status_id, to_status_id)`: āļ„āļĩāļĒāđŒāļŦāļĨāļąāļāđāļšāļšāļ›āļĢāļ°āļāļ­āļš āļ—āļģāđƒāļŦāđ‰āđāļ™āđˆāđƒāļˆāļ§āđˆāļēāđāļ•āđˆāļĨāļ°āļ›āļĢāļ°āđ€āļ āļ—āļŦāļ™āļąāļ‡āļŠāļ·āļ­ āļāļēāļĢāđ€āļ›āļĨāļĩāđˆāļĒāļ™āļˆāļēāļāļŠāļ–āļēāļ™āļ°āļŦāļ™āļķāđˆāļ‡āđ„āļ›āļ­āļĩāļāļŠāļ–āļēāļ™āļ°āļŦāļ™āļķāđˆāļ‡āļˆāļ°āļ‹āđ‰āļģāļāļąāļ™āđ„āļĄāđˆāđ„āļ”āđ‰ - -**āļ„āļ§āļēāļĄāļŠāļąāļĄāļžāļąāļ™āļ˜āđŒ Foreign Key:** - -- fk_cst_type : āļ­āđ‰āļēāļ‡āļ­āļīāļ‡āđ„āļ›āļĒāļąāļ‡ correspondence_types(id) -- fk_cst_from : āļ­āđ‰āļēāļ‡āļ­āļīāļ‡āđ„āļ›āļĒāļąāļ‡ correspondence_status(id) -- fk_cst_to : āļ­āđ‰āļēāļ‡āļ­āļīāļ‡āđ„āļ›āļĒāļąāļ‡ correspondence_status(id) -- āđ„āļĄāđˆāļĄāļĩāļāļēāļĢāļāļģāļŦāļ™āļ” ON DELETE āļˆāļķāļ‡āđ€āļ›āđ‡āļ™āļ„āđˆāļēāđ€āļĢāļīāđˆāļĄāļ•āđ‰āļ™ RESTRICT āļŦāļĄāļēāļĒāļ„āļ§āļēāļĄāļ§āđˆāļē āļˆāļ°āļĨāļšāļ›āļĢāļ°āđ€āļ āļ—āļŦāļ™āļąāļ‡āļŠāļ·āļ­āļŦāļĢāļ·āļ­āļŠāļ–āļēāļ™āļ°āđ„āļĄāđˆāđ„āļ”āđ‰ āļ–āđ‰āļēāļĒāļąāļ‡āļĄāļĩāļāļēāļĢāđƒāļŠāđ‰āļ‡āļēāļ™āļ­āļĒāļđāđˆāđƒāļ™āļ•āļēāļĢāļēāļ‡āļ™āļĩāđ‰ - ---- - -## **4. 📐 approval: RFA Tables (āđ€āļ­āļāļŠāļēāļĢāļ‚āļ­āļ­āļ™āļļāļĄāļąāļ•āļī, Workflows)** - -### 4.1 rfa_types - -**Purpose**: Master table for RFA (Request for Approval) types - -| Column Name | Data Type | Constraints | Description | -| ----------- | ------------ | --------------------------- | ------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique identifier | -| type_code | VARCHAR(20) | NOT NULL, UNIQUE | Type code (DWG, DOC, MAT, etc.) | -| type_name | VARCHAR(100) | NOT NULL | Full type name | -| description | TEXT | NULL | Type description | -| sort_order | INT | DEFAULT 0 | Display order | -| is_active | TINYINT(1) | DEFAULT 1 | Active status | - -**Indexes**: - -- PRIMARY KEY (id) -- UNIQUE (type_code) -- INDEX (is_active) -- INDEX (sort_order) - -**Relationships**: - -- Referenced by: rfas - -**Seed Data**: 11 RFA types including Shop Drawing (DWG), Document (DOC), Specification (SPC), Calculation (CAL), Test Report (TRP), Survey Report (SRY), QA/QC Document, Method Statement (MES), Material (MAT), As-Built (ASB), Other (OTH) - ---- - -### 4.2 rfa_status_codes - -**Purpose**: Master table for RFA status codes - -| Column Name | Data Type | Constraints | Description | -| ----------- | ------------ | --------------------------- | --------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique identifier | -| status_code | VARCHAR(20) | NOT NULL, UNIQUE | Status code (DFT, FAP, FRE, etc.) | -| status_name | VARCHAR(100) | NOT NULL | Full status name | -| description | TEXT | NULL | Status description | -| sort_order | INT | DEFAULT 0 | Display order | -| is_active | TINYINT(1) | DEFAULT 1 | Active status | - -**Indexes**: - -- PRIMARY KEY (id) -- UNIQUE (status_code) -- INDEX (is_active) -- INDEX (sort_order) - -**Relationships**: - -- Referenced by: rfa_revisions - -**Seed Data**: 7 status codes - -- DFT: Draft -- FAP: For Approve -- FRE: For Review -- FCO: For Construction -- ASB: AS-Built -- OBS: Obsolete -- CC: Canceled - ---- - -### 4.3 rfa_approve_codes - -**Purpose**: Master table for RFA approval result codes - -| Column Name | Data Type | Constraints | Description | -| ------------ | ------------ | --------------------------- | -------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique identifier | -| approve_code | VARCHAR(20) | NOT NULL, UNIQUE | Approval code (1A, 1C, 3R, etc.) | -| approve_name | VARCHAR(100) | NOT NULL | Full approval name | -| description | TEXT | NULL | Code description | -| sort_order | INT | DEFAULT 0 | Display order | -| is_active | TINYINT(1) | DEFAULT 1 | Active status | - -**Indexes**: - -- PRIMARY KEY (id) -- UNIQUE (approve_code) -- INDEX (is_active) -- INDEX (sort_order) - -**Relationships**: - -- Referenced by: rfa_revisions - -**Seed Data**: 8 approval codes - -- 1A: Approved by Authority -- 1C: Approved by CSC -- 1N: Approved As Note -- 1R: Approved with Remarks -- 3C: Consultant Comments -- 3R: Revise and Resubmit -- 4X: Reject -- 5N: No Further Action - ---- - -### 4.4 rfas - -**Purpose**: Master table for RFA documents (non-revisioned data) - -| Column Name | Data Type | Constraints | Description | -| ----------- | --------- | --------------------------- | --------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Master RFA ID | -| rfa_type_id | INT | NOT NULL, FK | Reference to rfa_types | -| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp | -| created_by | INT | NULL, FK | User who created the record | -| deleted_at | DATETIME | NULL | Soft delete timestamp | - -**Indexes**: - -- PRIMARY KEY (id) -- FOREIGN KEY (rfa_type_id) REFERENCES rfa_types(id) -- FOREIGN KEY (created_by) REFERENCES users(user_id) ON DELETE SET NULL -- INDEX (rfa_type_id) -- INDEX (deleted_at) - -**Relationships**: - -- Parent: rfa_types, users -- Children: rfa_revisions - -**Business Rules**: - -- One RFA can have multiple revisions -- Soft delete preserves history - ---- - -### 4.5 rfa_revisions - -**Purpose**: Child table storing revision history of RFAs (1:N) - -| Column Name | Data Type | Constraints | Description | -| ------------------- | ------------ | --------------------------- | ---------------------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique revision ID | -| correspondence_id | INT | NOT NULL, FK | Link to correspondence (RFA as correspondence) | -| rfa_id | INT | NOT NULL, FK | Master RFA ID | -| revision_number | INT | NOT NULL | Revision sequence (0, 1, 2...) | -| revision_label | VARCHAR(10) | NULL | Display revision (A, B, 1.1...) | -| is_current | BOOLEAN | DEFAULT FALSE | Current revision flag | -| rfa_status_code_id | INT | NOT NULL, FK | Current RFA status | -| rfa_approve_code_id | INT | NULL, FK | Approval result code | -| title | VARCHAR(255) | NOT NULL | RFA title | -| document_date | DATE | NULL | Document date | -| issued_date | DATE | NULL | Issue date for approval | -| received_date | DATETIME | NULL | Received date | -| approved_date | DATE | NULL | Approval date | -| description | TEXT | NULL | Revision description | -| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | Revision creation timestamp | -| created_by | INT | NULL, FK | User who created revision | -| updated_by | INT | NULL, FK | User who last updated | - -**Indexes**: - -- PRIMARY KEY (id) -- 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 (rfa_id, revision_number) -- UNIQUE KEY (rfa_id, is_current) -- INDEX (rfa_status_code_id) -- INDEX (rfa_approve_code_id) -- INDEX (is_current) - -**Relationships**: - -- Parent: correspondences, rfas, rfa_status_codes, rfa_approve_codes, users -- Children: rfa_items, rfa_workflows - -**Business Rules**: - -- RFA is a specialized type of correspondence -- Only one revision can be current per RFA -- Links to shop drawings through rfa_items - ---- - -### 4.6 rfa_items - -**Purpose**: Junction table linking RFA revisions to shop drawing revisions (M:N) - -| Column Name | Data Type | Constraints | Description | -| ------------------------ | --------- | --------------- | ------------------------------ | -| rfarev_correspondence_id | INT | PRIMARY KEY, FK | RFA revision correspondence ID | -| shop_drawing_revision_id | INT | PRIMARY KEY, FK | Shop drawing revision ID | - -**Indexes**: - -- 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 -- INDEX (shop_drawing_revision_id) - -**Relationships**: - -- Parent: rfa_revisions, shop_drawing_revisions - -**Business Rules**: - -- Used primarily for RFA type = 'DWG' (Shop Drawing) -- One RFA can contain multiple shop drawings -- One shop drawing can be referenced by multiple RFAs - ---- - -### 4.7 rfa_workflow_templates - -**Purpose**: Master table for RFA approval workflow templates - -| Column Name | Data Type | Constraints | Description | -| --------------- | ------------ | ----------------------------------- | -------------------------------------------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique template ID | -| template_name | VARCHAR(100) | NOT NULL | Template name | -| description | TEXT | NULL | Template description | -| is_active | TINYINT(1) | DEFAULT 1 | Active status | -| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp | -| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | -| workflow_config | JSON | NULL | āđ€āļāđ‡āļš State Machine Configuration āļŦāļĢāļ·āļ­ Rules āđ€āļžāļīāđˆāļĄāđ€āļ•āļīāļĄāļ—āļĩāđˆāļ‹āļąāļšāļ‹āđ‰āļ­āļ™āļāļ§āđˆāļē Column āļ›āļāļ•āļī | - -**Indexes**: - -- PRIMARY KEY (id) -- INDEX (is_active) -- INDEX (template_name) - -**Relationships**: - -- Children: rfa_workflow_template_steps - -**Business Rules**: - -- Defines reusable approval workflows for RFAs -- Can be assigned to specific RFA types or organizations - ---- - -### 4.8 rfa_workflow_template_steps - -**Purpose**: Child table defining steps in workflow templates - -| Column Name | Data Type | Constraints | Description | -| --------------- | --------- | --------------------------- | ----------------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique step ID | -| template_id | INT | NOT NULL, FK | Reference to workflow template | -| step_number | INT | NOT NULL | Step sequence order | -| organization_id | INT | NOT NULL, FK | Organization responsible for step | -| role_id | INT | NULL, FK | Required role for this step | -| action_type | ENUM | NULL | Action type: REVIEW, APPROVE, ACKNOWLEDGE | -| duration_days | INT | NULL | Expected duration in days | -| is_optional | BOOLEAN | DEFAULT FALSE | Optional step flag | - -**Indexes**: - -- PRIMARY KEY (id) -- 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) -- INDEX (template_id, step_number) -- INDEX (organization_id) - -**Relationships**: - -- Parent: rfa_workflow_templates, organizations, roles - -**Business Rules**: - -- Steps are executed in step_number order -- Optional steps can be skipped -- Duration used for deadline calculation - ---- - -### 4.9 rfa_workflows - -**Purpose**: Transaction log table tracking actual RFA approval workflow execution - -| Column Name | Data Type | Constraints | Description | -| --------------- | --------- | ----------------------------------- | ------------------------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique workflow log ID | -| rfa_revision_id | INT | NOT NULL, FK | Reference to RFA revision | -| step_number | INT | NOT NULL | Current step number | -| organization_id | INT | NOT NULL, FK | Organization responsible | -| assigned_to | INT | NULL, FK | Assigned user ID | -| action_type | ENUM | NULL | Action type: REVIEW, APPROVE, ACKNOWLEDGE | -| status | ENUM | NULL | Status: PENDING, IN_PROGRESS, COMPLETED, REJECTED | -| comments | TEXT | NULL | Comments/remarks | -| completed_at | DATETIME | NULL | Completion timestamp | -| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp | -| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | -| state_context | JSON* | NULL | āđ€āļāđ‡āļšāļ‚āđ‰āļ­āļĄāļđāļĨ Context āļ‚āļ­āļ‡ Workflow āļ“ āļ‚āļ“āļ°āļ™āļąāđ‰āļ™ (Snapshot) | - -**Indexes**: - -- PRIMARY KEY (id) -- 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) -- INDEX (rfa_revision_id, step_number) -- INDEX (assigned_to, status) -- INDEX (status) - -**Relationships**: - -- Parent: rfa_revisions, organizations, users - -**Business Rules**: - -- Records actual workflow execution history -- Tracks who did what and when -- Multiple records per RFA revision (one per step) -- Status changes tracked via updated_at - ---- - -## **5. 📐 Drawings Tables (āđāļšāļš, āļŦāļĄāļ§āļ”āļŦāļĄāļđāđˆ)** - -### 5.1 contract_drawing_volumes - -**Purpose**: Master table for contract drawing volume classification - -| Column Name | Data Type | Constraints | Description | -| ----------- | ------------ | ----------------------------------- | ------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique volume ID | -| project_id | INT | NOT NULL, FK | Reference to projects | -| volume_code | VARCHAR(50) | NOT NULL | Volume code | -| volume_name | VARCHAR(255) | NOT NULL | Volume name | -| description | TEXT | NULL | Volume description | -| sort_order | INT | DEFAULT 0 | Display order | -| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp | -| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | - -**Indexes**: - -- PRIMARY KEY (id) -- FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE -- UNIQUE KEY (project_id, volume_code) -- INDEX (sort_order) - -**Relationships**: - -- Parent: projects -- Referenced by: contract_drawings - -**Business Rules**: - -- Volume codes must be unique within a project -- Used for organizing large sets of contract drawings - ---- - -### 5.2 contract_drawing_cats - -**Purpose**: Master table for contract drawing main categories - -| Column Name | Data Type | Constraints | Description | -| ----------- | ------------ | ----------------------------------- | ------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique category ID | -| project_id | INT | NOT NULL, FK | Reference to projects | -| cat_code | VARCHAR(50) | NOT NULL | Category code | -| cat_name | VARCHAR(255) | NOT NULL | Category name | -| description | TEXT | NULL | Category description | -| sort_order | INT | DEFAULT 0 | Display order | -| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp | -| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | - -**Indexes**: - -- PRIMARY KEY (id) -- FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE -- UNIQUE KEY (project_id, cat_code) -- INDEX (sort_order) - -**Relationships**: - -- Parent: projects -- Referenced by: contract_drawing_subcat_cat_maps - -**Business Rules**: - -- Category codes must be unique within a project -- Hierarchical relationship with sub-categories via mapping table - ---- - -### 5.3 contract_drawing_sub_cats - -**Purpose**: Master table for contract drawing sub-categories - -| Column Name | Data Type | Constraints | Description | -| ------------ | ------------ | ----------------------------------- | ------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique sub-category ID | -| project_id | INT | NOT NULL, FK | Reference to projects | -| sub_cat_code | VARCHAR(50) | NOT NULL | Sub-category code | -| sub_cat_name | VARCHAR(255) | NOT NULL | Sub-category name | -| description | TEXT | NULL | Sub-category description | -| sort_order | INT | DEFAULT 0 | Display order | -| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp | -| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | - -**Indexes**: - -- PRIMARY KEY (id) -- FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE -- UNIQUE KEY (project_id, sub_cat_code) -- INDEX (sort_order) - -**Relationships**: - -- Parent: projects -- Referenced by: contract_drawings, contract_drawing_subcat_cat_maps - -**Business Rules**: - -- Sub-category codes must be unique within a project -- Can be mapped to multiple main categories via mapping table - ---- - -### 5.4 contract_drawing_subcat_cat_maps - -**Purpose**: Junction table mapping sub-categories to main categories (M:N) - -| Column Name | Data Type | Constraints | Description | -| ----------- | --------- | --------------- | -------------------------- | -| project_id | INT | PRIMARY KEY, FK | Reference to projects | -| sub_cat_id | INT | PRIMARY KEY, FK | Reference to sub-category | -| cat_id | INT | PRIMARY KEY, FK | Reference to main category | - -**Indexes**: - -- 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 -- INDEX (sub_cat_id) -- INDEX (cat_id) - -**Relationships**: - -- Parent: projects, contract_drawing_sub_cats, contract_drawing_cats - -**Business Rules**: - -- Allows flexible categorization -- One sub-category can belong to multiple main categories -- All three fields required for uniqueness - ---- - -### 5.5 contract_drawings - -**Purpose**: Master table for contract drawings (from contract specifications) - -| Column Name | Data Type | Constraints | Description | -| ----------- | ------------ | ----------------------------------- | ------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique drawing ID | -| project_id | INT | NOT NULL, FK | Reference to projects | -| condwg_no | VARCHAR(255) | NOT NULL | Contract drawing number | -| title | VARCHAR(255) | NOT NULL | Drawing title | -| sub_cat_id | INT | NULL, FK | Reference to sub-category | -| volume_id | INT | NULL, FK | Reference to volume | -| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp | -| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | -| deleted_at | DATETIME | NULL | Soft delete timestamp | -| updated_by | INT | NULL, FK | User who last updated | - -**Indexes**: - -- PRIMARY KEY (id) -- 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 -- FOREIGN KEY (updated_by) REFERENCES users(user_id) -- UNIQUE KEY (project_id, condwg_no) -- INDEX (sub_cat_id) -- INDEX (volume_id) -- INDEX (deleted_at) - -**Relationships**: - -- Parent: projects, contract_drawing_sub_cats, contract_drawing_volumes, users -- Referenced by: shop_drawing_revision_contract_refs, contract_drawing_attachments - -**Business Rules**: - -- Drawing numbers must be unique within a project -- Represents baseline/contract drawings -- Referenced by shop drawings for compliance tracking -- Soft delete preserves history - ---- - -### 5.6 shop_drawing_main_categories - -**Purpose**: Master table for shop drawing main categories (discipline-level) - -| Column Name | Data Type | Constraints | Description | -| ------------------ | ------------ | ----------------------------------- | ------------------------------------ | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique category ID | -| main_category_code | VARCHAR(50) | NOT NULL, UNIQUE | Category code (ARCH, STR, MEP, etc.) | -| main_category_name | VARCHAR(255) | NOT NULL | Category name | -| description | TEXT | NULL | Category description | -| sort_order | INT | DEFAULT 0 | Display order | -| is_active | TINYINT(1) | DEFAULT 1 | Active status | -| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp | -| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | - -**Indexes**: - -- PRIMARY KEY (id) -- UNIQUE (main_category_code) -- INDEX (is_active) -- INDEX (sort_order) - -**Relationships**: - -- Referenced by: shop_drawing_sub_categories, shop_drawings - -**Business Rules**: - -- Global categories (not project-specific) -- Typically represents engineering disciplines - ---- - -### 5.7 shop_drawing_sub_categories - -**Purpose**: Master table for shop drawing sub-categories (component-level) - -| Column Name | Data Type | Constraints | Description | -| ----------------- | ------------ | ----------------------------------- | ----------------------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique sub-category ID | -| sub_category_code | VARCHAR(50) | NOT NULL, UNIQUE | Sub-category code (STR-COLUMN, ARCH-DOOR, etc.) | -| sub_category_name | VARCHAR(255) | NOT NULL | Sub-category name | -| main_category_id | INT | NOT NULL, FK | Reference to main category | -| description | TEXT | NULL | Sub-category description | -| sort_order | INT | DEFAULT 0 | Display order | -| is_active | TINYINT(1) | DEFAULT 1 | Active status | -| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp | -| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | - -**Indexes**: - -- PRIMARY KEY (id) -- UNIQUE (sub_category_code) -- FOREIGN KEY (main_category_id) REFERENCES shop_drawing_main_categories(id) -- INDEX (main_category_id) -- INDEX (is_active) -- INDEX (sort_order) - -**Relationships**: - -- Parent: shop_drawing_main_categories -- Referenced by: shop_drawings - -**Business Rules**: - -- Global sub-categories (not project-specific) -- Hierarchical under main categories -- Represents specific drawing types or components - ---- - -### 5.8 shop_drawings - -**Purpose**: Master table for shop drawings (contractor-submitted) - -| Column Name | Data Type | Constraints | Description | -| ---------------- | ------------ | ----------------------------------- | -------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique drawing ID | -| project_id | INT | NOT NULL, FK | Reference to projects | -| drawing_number | VARCHAR(100) | NOT NULL, UNIQUE | Shop drawing number | -| title | VARCHAR(500) | NOT NULL | Drawing title | -| main_category_id | INT | NOT NULL, FK | Reference to main category | -| sub_category_id | INT | NOT NULL, FK | Reference to sub-category | -| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp | -| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | -| deleted_at | DATETIME | NULL | Soft delete timestamp | -| updated_by | INT | NULL, FK | User who last updated | - -**Indexes**: - -- PRIMARY KEY (id) -- UNIQUE (drawing_number) -- 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) -- FOREIGN KEY (updated_by) REFERENCES users(user_id) -- INDEX (project_id) -- INDEX (main_category_id) -- INDEX (sub_category_id) -- INDEX (deleted_at) - -**Relationships**: - -- Parent: projects, shop_drawing_main_categories, shop_drawing_sub_categories, users -- Children: shop_drawing_revisions - -**Business Rules**: - -- Drawing numbers are globally unique across all projects -- Represents contractor shop drawings -- Can have multiple revisions -- Soft delete preserves history - ---- - -### 5.9 shop_drawing_revisions - -**Purpose**: Child table storing revision history of shop drawings (1:N) - -| Column Name | Data Type | Constraints | Description | -| --------------- | ----------- | --------------------------- | ------------------------------ | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique revision ID | -| shop_drawing_id | INT | NOT NULL, FK | Master shop drawing ID | -| revision_number | INT | NOT NULL | Revision sequence (0, 1, 2...) | -| revision_label | VARCHAR(10) | NULL | Display revision (A, B, C...) | -| revision_date | DATE | NULL | Revision date | -| description | TEXT | NULL | Revision description/changes | -| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Revision creation timestamp | - -**Indexes**: - -- PRIMARY KEY (id) -- FOREIGN KEY (shop_drawing_id) REFERENCES shop_drawings(id) ON DELETE CASCADE -- UNIQUE KEY (shop_drawing_id, revision_number) -- INDEX (revision_date) - -**Relationships**: - -- Parent: shop_drawings -- Referenced by: rfa_items, shop_drawing_revision_contract_refs, shop_drawing_revision_attachments - -**Business Rules**: - -- Revision numbers are sequential starting from 0 -- Each revision can reference multiple contract drawings -- Each revision can have multiple file attachments -- Linked to RFAs for approval tracking - ---- - -### 5.10 shop_drawing_revision_contract_refs - -**Purpose**: Junction table linking shop drawing revisions to referenced contract drawings (M:N) - -| Column Name | Data Type | Constraints | Description | -| ------------------------ | --------- | --------------- | ---------------------------------- | -| shop_drawing_revision_id | INT | PRIMARY KEY, FK | Reference to shop drawing revision | -| contract_drawing_id | INT | PRIMARY KEY, FK | Reference to contract drawing | - -**Indexes**: - -- 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 -- INDEX (contract_drawing_id) - -**Relationships**: - -- Parent: shop_drawing_revisions, contract_drawings - -**Business Rules**: - -- Tracks which contract drawings each shop drawing revision is based on -- Ensures compliance with contract specifications -- One shop drawing revision can reference multiple contract drawings - ---- - -## **6. 🔄 Circulations Tables (āđƒāļšāđ€āļ§āļĩāļĒāļ™āļ āļēāļĒāđƒāļ™)** - -### 6.1 circulation_status_codes - -**Purpose**: Master table for circulation workflow status codes - -| Column Name | Data Type | Constraints | Description | -| ----------- | ----------- | --------------------------- | --------------------------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique status ID | -| code | VARCHAR(20) | NOT NULL, UNIQUE | Status code (OPEN, IN_REVIEW, COMPLETED, CANCELLED) | -| description | VARCHAR(50) | NOT NULL | Status description | -| sort_order | INT | DEFAULT 0 | Display order | -| is_active | TINYINT(1) | DEFAULT 1 | Active status | - -**Indexes**: - -- PRIMARY KEY (id) -- UNIQUE (code) -- INDEX (is_active) -- INDEX (sort_order) - -**Relationships**: - -- Referenced by: circulations - -**Seed Data**: 4 status codes - -- OPEN: Initial status when created -- IN_REVIEW: Under review by recipients -- COMPLETED: All recipients have responded -- CANCELLED: Withdrawn/cancelled - ---- - -### 6.2 circulations - -**Purpose**: Master table for internal circulation sheets (document routing) - -| Column Name | Data Type | Constraints | Description | -| ----------------------- | ------------ | ----------------------------------- | ----------------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique circulation ID | -| correspondence_id | INT | UNIQUE, FK | Link to correspondence (1:1 relationship) | -| organization_id | INT | NOT NULL, FK | Organization that owns this circulation | -| circulation_no | VARCHAR(100) | NOT NULL | Circulation sheet number | -| circulation_subject | VARCHAR(500) | NOT NULL | Subject/title | -| circulation_status_code | VARCHAR(20) | NOT NULL, FK | Current status code | -| created_by_user_id | INT | NOT NULL, FK | User who created circulation | -| submitted_at | TIMESTAMP | NULL | Submission timestamp | -| closed_at | TIMESTAMP | NULL | Closure timestamp | -| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp | -| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | - -**Indexes**: - -- PRIMARY KEY (id) -- UNIQUE (correspondence_id) -- 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) -- INDEX (organization_id) -- INDEX (circulation_status_code) -- INDEX (created_by_user_id) - -**Relationships**: - -- Parent: correspondences, organizations, circulation_status_codes, users -- Children: circulation_routings, circulation_attachments - -**Business Rules**: - -- Internal document routing within organization -- One-to-one relationship with correspondences -- Tracks document review/approval workflow -- Status progression: OPEN → IN_REVIEW → COMPLETED/CANCELLED - ---- - -### 6.3 circulation_templates - -**Purpose**: Master table for circulation workflow templates - -| Column Name | Data Type | Constraints | Description | -| --------------- | ------------ | ----------------------------------- | --------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique template ID | -| template_name | VARCHAR(100) | NOT NULL | Template name | -| description | TEXT | NULL | Template description | -| organization_id | INT | NOT NULL, FK | Template owner organization | -| is_active | TINYINT(1) | DEFAULT 1 | Active status | -| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp | -| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | - -**Indexes**: - -- PRIMARY KEY (id) -- FOREIGN KEY (organization_id) REFERENCES organizations(id) -- INDEX (organization_id) -- INDEX (is_active) - -**Relationships**: - -- Parent: organizations -- Children: circulation_template_assignees - -**Business Rules**: - -- Organization-specific templates -- Defines reusable routing workflows - ---- - -### 6.4 circulation_template_assignees - -**Purpose**: Child table defining steps in circulation templates - -| Column Name | Data Type | Constraints | Description | -| --------------- | --------- | --------------------------- | --------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique step ID | -| template_id | INT | NOT NULL, FK | Reference to template | -| step_number | INT | NOT NULL | Step sequence order | -| organization_id | INT | NOT NULL, FK | Organization responsible for step | -| role_id | INT | NULL, FK | Required role for this step | -| duration_days | INT | NULL | Expected duration in days | -| is_optional | BOOLEAN | DEFAULT FALSE | Optional step flag | - -**Indexes**: - -- PRIMARY KEY (id) -- 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) -- INDEX (template_id, step_number) -- INDEX (organization_id) - -**Relationships**: - -- Parent: circulation_templates, organizations, roles - -**Business Rules**: - -- Steps executed in step_number order -- Optional steps can be skipped -- Duration used for deadline calculation - ---- - -### 6.5 circulation_routings - -**Purpose**: Transaction log table tracking actual circulation routing execution - -| Column Name | Data Type | Constraints | Description | -| --------------- | --------- | ----------------------------------- | ------------------------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique routing log ID | -| circulation_id | INT | NOT NULL, FK | Reference to circulation | -| step_number | INT | NOT NULL | Current step number | -| organization_id | INT | NOT NULL, FK | Organization responsible | -| assigned_to | INT | NULL, FK | Assigned user ID | -| status | ENUM | NULL | Status: PENDING, IN_PROGRESS, COMPLETED, REJECTED | -| comments | TEXT | NULL | Comments/remarks | -| completed_at | DATETIME | NULL | Completion timestamp | -| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp | -| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | - -**Indexes**: - -- PRIMARY KEY (id) -- 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) -- INDEX (circulation_id, step_number) -- INDEX (assigned_to, status) -- INDEX (status) - -**Relationships**: - -- Parent: circulations, organizations, users - -**Business Rules**: - -- Records actual routing history -- Multiple records per circulation (one per step) -- Tracks who reviewed/approved and when -- Used in v_user_tasks view for pending items - ---- - -## **7. ðŸ“Ī Transmittals Tables (āđ€āļ­āļāļŠāļēāļĢāļ™āļģāļŠāđˆāļ‡)** - -### 7.1 transmittals - -**Purpose**: Child table for transmittal-specific data (1:1 with correspondences) - -| Column Name | Data Type | Constraints | Description | -| ----------------- | --------- | --------------- | --------------------------------------------------------- | -| correspondence_id | INT | PRIMARY KEY, FK | Reference to correspondences (1:1) | -| purpose | ENUM | NULL | Purpose: FOR_APPROVAL, FOR_INFORMATION, FOR_REVIEW, OTHER | -| remarks | TEXT | NULL | Additional remarks | - -**Indexes**: - -- PRIMARY KEY (correspondence_id) -- FOREIGN KEY (correspondence_id) REFERENCES correspondences(id) ON DELETE CASCADE -- INDEX (purpose) - -**Relationships**: - -- Parent: correspondences -- Children: transmittal_items - -**Business Rules**: - -- One-to-one relationship with correspondences -- Transmittal is a correspondence type for forwarding documents -- Contains metadata about the transmission - ---- - -### 7.2 transmittal_items - -**Purpose**: Junction table listing documents included in transmittal (M:N) - -| Column Name | Data Type | Constraints | Description | -| ---------------------- | ------------ | --------------------------- | --------------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique item ID | -| transmittal_id | INT | NOT NULL, FK | Reference to transmittal | -| item_correspondence_id | INT | NOT NULL, FK | Reference to document being transmitted | -| quantity | INT | DEFAULT 1 | Number of copies | -| remarks | VARCHAR(255) | NULL | Item-specific remarks | - -**Indexes**: - -- PRIMARY KEY (id) -- FOREIGN KEY (transmittal_id) REFERENCES transmittals(correspondence_id) ON DELETE CASCADE -- FOREIGN KEY (item_correspondence_id) REFERENCES correspondences(id) ON DELETE CASCADE -- UNIQUE KEY (transmittal_id, item_correspondence_id) -- INDEX (item_correspondence_id) - -**Relationships**: - -- Parent: transmittals, correspondences - -**Business Rules**: - -- One transmittal can contain multiple documents -- Tracks quantity of physical copies (if applicable) -- Links to any type of correspondence document - ---- - -## **8. 📎 File Management Tables (āđ„āļŸāļĨāđŒāđāļ™āļš)** - -### 8.1 attachments - -**Purpose**: Central repository for all file attachments in the system - -| Column Name | Data Type | Constraints | Description | -| ------------------- | ------------ | --------------------------- | -------------------------------------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique attachment ID | -| original_filename | VARCHAR(255) | NOT NULL | Original filename from upload | -| stored_filename | VARCHAR(255) | NOT NULL | System-generated unique filename | -| file_path | VARCHAR(500) | NOT NULL | Full file path on server (/share/dms-data/) | -| mime_type | VARCHAR(100) | NOT NULL | MIME type (application/pdf, image/jpeg, etc.) | -| file_size | INT | NOT NULL | File size in bytes | -| uploaded_by_user_id | INT | NOT NULL, FK | User who uploaded file | -| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Upload timestamp | -| is_temporary | BOOLEAN | DEFAULT TRUE | āļĢāļ°āļšāļļāļ§āđˆāļēāđ€āļ›āđ‡āļ™āđ„āļŸāļĨāđŒāļŠāļąāđˆāļ§āļ„āļĢāļēāļ§ (āļĒāļąāļ‡āđ„āļĄāđˆāđ„āļ”āđ‰ Commit) | -| temp_id* | VARCHAR(100) | NULL | ID āļŠāļąāđˆāļ§āļ„āļĢāļēāļ§āļŠāļģāļŦāļĢāļąāļšāļ­āđ‰āļēāļ‡āļ­āļīāļ‡āļ•āļ­āļ™ Upload Phase 1 (āļ­āļēāļˆāđƒāļŠāđ‰āļĢāđˆāļ§āļĄāļāļąāļš id āļŦāļĢāļ·āļ­āđāļĒāļāļāđ‡āđ„āļ”āđ‰) | -| expires_at | DATETIME | NULL | āđ€āļ§āļĨāļēāļŦāļĄāļ”āļ­āļēāļĒāļļāļ‚āļ­āļ‡āđ„āļŸāļĨāđŒ Temp (āđ€āļžāļ·āđˆāļ­āđƒāļŦāđ‰ Cron Job āļĨāļšāļ­āļ­āļ) | -| checksum | VARCHAR(64) | NULL | SHA-256 Checksum āļŠāļģāļŦāļĢāļąāļš Verify File Integrity [Req 3.9.3] | - -**Indexes**: - -- PRIMARY KEY (id) -- FOREIGN KEY (uploaded_by_user_id) REFERENCES users(user_id) ON DELETE CASCADE -- INDEX (stored_filename) -- INDEX (mime_type) -- INDEX (uploaded_by_user_id) -- INDEX (created_at) - -**Relationships**: - -- Parent: users -- Referenced by: correspondence_attachments, circulation_attachments, shop_drawing_revision_attachments, contract_drawing_attachments - -**Business Rules**: - -- Central storage prevents file duplication -- Stored filename prevents naming conflicts -- File path points to QNAP NAS storage -- Original filename preserved for download -- One file record can be linked to multiple documents - ---- - -### 8.2 correspondence_attachments - -**Purpose**: Junction table linking correspondences to file attachments (M:N) - -| Column Name | Data Type | Constraints | Description | -| ----------------- | --------- | --------------- | ---------------------------- | -| correspondence_id | INT | PRIMARY KEY, FK | Reference to correspondences | -| attachment_id | INT | PRIMARY KEY, FK | Reference to attachments | -| is_main_document | BOOLEAN | DEFAULT FALSE | Main/primary document flag | - -**Indexes**: - -- 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 -- INDEX (attachment_id) -- INDEX (is_main_document) - -**Relationships**: - -- Parent: correspondences, attachments - -**Business Rules**: - -- One correspondence can have multiple attachments -- One attachment can be linked to multiple correspondences -- is_main_document identifies primary file (typically PDF) - ---- - -### 8.3 circulation_attachments - -**Purpose**: Junction table linking circulations to file attachments (M:N) - -| Column Name | Data Type | Constraints | Description | -| ---------------- | --------- | --------------- | -------------------------- | -| circulation_id | INT | PRIMARY KEY, FK | Reference to circulations | -| attachment_id | INT | PRIMARY KEY, FK | Reference to attachments | -| is_main_document | BOOLEAN | DEFAULT FALSE | Main/primary document flag | - -**Indexes**: - -- 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 -- INDEX (attachment_id) -- INDEX (is_main_document) - -**Relationships**: - -- Parent: circulations, attachments - ---- - -### 8.4 shop_drawing_revision_attachments - -**Purpose**: Junction table linking shop drawing revisions to file attachments (M:N) - -| Column Name | Data Type | Constraints | Description | -| ------------------------ | --------- | --------------- | ---------------------------------- | -| shop_drawing_revision_id | INT | PRIMARY KEY, FK | Reference to shop drawing revision | -| attachment_id | INT | PRIMARY KEY, FK | Reference to attachments | -| file_type | ENUM | NULL | File type: PDF, DWG, SOURCE, OTHER | -| is_main_document | BOOLEAN | DEFAULT FALSE | Main/primary document flag | - -**Indexes**: - -- 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 -- INDEX (attachment_id) -- INDEX (file_type) -- INDEX (is_main_document) - -**Relationships**: - -- Parent: shop_drawing_revisions, attachments - -**Business Rules**: - -- file_type categorizes drawing file formats -- Typically includes PDF for viewing and DWG for editing -- SOURCE may include native CAD files - ---- - -### 8.5 contract_drawing_attachments - -**Purpose**: Junction table linking contract drawings to file attachments (M:N) - -| Column Name | Data Type | Constraints | Description | -| ------------------- | --------- | --------------- | ---------------------------------- | -| contract_drawing_id | INT | PRIMARY KEY, FK | Reference to contract drawing | -| attachment_id | INT | PRIMARY KEY, FK | Reference to attachments | -| file_type | ENUM | NULL | File type: PDF, DWG, SOURCE, OTHER | -| is_main_document | BOOLEAN | DEFAULT FALSE | Main/primary document flag | - -**Indexes**: - -- 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 -- INDEX (attachment_id) -- INDEX (file_type) -- INDEX (is_main_document) - -**Relationships**: - -- Parent: contract_drawings, attachments - ---- - -## **9. ðŸ”Ē Document Numbering Tables (āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡āđ€āļĨāļ‚āļ—āļĩāđˆāđ€āļ­āļāļŠāļēāļĢ)** - -### 9.1 document_number_formats - -**Purpose**: Master table defining document numbering templates per project and type - -| Column Name | Data Type | Constraints | Description | -| ---------------------- | ------------ | ----------------------------------- | -------------------------------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique format ID | -| project_id | INT | NOT NULL, FK | Reference to projects | -| correspondence_type_id | INT | NOT NULL, FK | Reference to correspondence types | -| format_template | VARCHAR(255) | NOT NULL | Template string (e.g., '{ORG_CODE}-{TYPE_CODE}-{SEQ:4}') | -| description | TEXT | NULL | Format description | -| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Record creation timestamp | -| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | - -**Indexes**: - -- PRIMARY KEY (id) -- FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE -- FOREIGN KEY (correspondence_type_id) REFERENCES correspondence_types(id) ON DELETE CASCADE -- UNIQUE KEY (project_id, correspondence_type_id) -- INDEX (project_id) -- INDEX (correspondence_type_id) - -**Relationships**: - -- Parent: projects, correspondence_types - -**Business Rules**: - -- One format template per project per correspondence type combination -- Template placeholders: {ORG_CODE}, {TYPE_CODE}, {YEAR}, {SEQ:n} where n is zero-padding length -- Used by document numbering module to generate unique document numbers - ---- - -### 9.2 document_number_counters - -**Purpose**: Transaction table maintaining running sequence numbers for document numbering - -| Column Name | Data Type | Constraints | Description | -| -------------------------- | --------- | --------------- | ----------------------------------------------- | -| project_id | INT | PRIMARY KEY, FK | Reference to projects | -| originator_organization_id | INT | PRIMARY KEY, FK | Originating organization | -| correspondence_type_id | INT | PRIMARY KEY, FK | Reference to correspondence types | -| current_year | INT | PRIMARY KEY | Year (Buddhist calendar) | -| version | INT | DEFAULT 0 | āđƒāļŠāđ‰āļŠāļģāļŦāļĢāļąāļš Optimistic Locking (āļ•āļĢāļ§āļˆāļŠāļ­āļšāļ„āđˆāļēāļāđˆāļ­āļ™ Update) | -| last_number | INT | DEFAULT 0 | Last assigned sequence number | - -**Indexes**: - -- 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 -- INDEX (project_id) -- INDEX (originator_organization_id) -- INDEX (correspondence_type_id) -- INDEX (current_year) - -**Relationships**: - -- Parent: projects, organizations, correspondence_types - -**Business Rules**: - -- Composite primary key ensures unique counters per project/organization/type/year -- Counter resets each year -- Thread-safe increments handled by DocumentNumberingService in NestJS using Redis Lock and Optimistic Locking on version column -- last_number tracks highest assigned number -- Used with document_number_formats to generate complete document numbers - ---- - -## **10. ⚙ïļ System & Logs Tables (āļĢāļ°āļšāļšāđāļĨāļ° Log)** - -### 10.1 audit_logs - -**Purpose**: Comprehensive audit trail for all significant system actions - -| Column Name | Data Type | Constraints | Description | -| ---------------- | ----------------------------------------- | --------------------------------- | -------------------------------------------------------- | -| audit_id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | Unique audit log ID | -| user_id | INT | NULL, FK | User who performed action | -| action | VARCHAR(100) | NOT NULL | Action code (e.g., 'rfa.create', 'login.success') | -| entity_type | VARCHAR(50) | NULL | Entity/module affected (e.g., 'rfa', 'correspondence') | -| entity_id | VARCHAR(50) | NULL | Primary ID of affected record | -| details_json | JSON | NULL | Additional context/details in JSON format | -| ip_address | VARCHAR(45) | NULL | Client IP address (supports IPv6) | -| user_agent | VARCHAR(255) | NULL | Browser user agent string | -| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Action timestamp | -| v_ref_project_id | INT | GENERATED ALWAYS AS (...) VIRTUAL | Virtual Column āļ”āļķāļ‡ Project ID āļˆāļēāļ JSON details āđ€āļžāļ·āđˆāļ­āļ—āļģ Index | -| v_ref_type | VARCHAR(50) | GENERATED ALWAYS AS (...) VIRTUAL | Virtual Column āļ”āļķāļ‡ Type āļˆāļēāļ JSON details | -| request_id | VARCHAR(100) | NULL | Request ID/Trace ID āđ€āļžāļ·āđˆāļ­āđ€āļŠāļ·āđˆāļ­āļĄāđ‚āļĒāļ‡āļāļąāļš App Logs | -| severity | ENUM('INFO', 'WARN', 'ERROR', 'CRITICAL') | NULL | āļĢāļ°āļ”āļąāļšāļ„āļ§āļēāļĄāļĢāļļāļ™āđāļĢāļ‡āļ‚āļ­āļ‡āđ€āļŦāļ•āļļāļāļēāļĢāļ“āđŒ | - -**Indexes**: - -- PRIMARY KEY (audit_id) -- FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE SET NULL -- INDEX (user_id) -- INDEX (action) -- INDEX (entity_type, entity_id) -- INDEX (created_at) -- INDEX (ip_address) -- INDEX (v_ref_project_id) -- INDEX (v_ref_type) - -**Relationships**: - -- Parent: users - -**Business Rules**: - -- Immutable records (no updates/deletes) -- Captures all CRUD operations on sensitive data -- Includes authentication events (login, logout, failed attempts) -- JSON details field for flexible data storage -- Retention policy: typically 7 years for compliance -- Used for security audits, compliance reporting, and troubleshooting - -**Common Actions**: - -- Authentication: login.success, login.failed, logout -- Documents: correspondence.create, correspondence.update, rfa.submit -- Users: user.create, user.deactivate, role.assign -- Workflow: rfa.approve, rfa.reject, circulation.complete - ---- - -### 10.2 notifications - -**Purpose**: User notification queue for email, LINE, and in-system alerts - -| Column Name | Data Type | Constraints | Description | -| ----------------- | ------------ | --------------------------- | ------------------------------------------------ | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique notification ID | -| user_id | INT | NOT NULL, FK | Recipient user ID | -| title | VARCHAR(255) | NOT NULL | Notification title/subject | -| message | TEXT | NOT NULL | Notification message body | -| notification_type | ENUM | NOT NULL | Type: EMAIL, LINE, SYSTEM | -| is_read | BOOLEAN | DEFAULT FALSE | Read status flag | -| entity_type | VARCHAR(50) | NULL | Related entity type (e.g., 'rfa', 'circulation') | -| entity_id | INT | NULL | Related entity ID | -| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Notification creation timestamp | - -**Indexes**: - -- PRIMARY KEY (id) -- FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE -- INDEX (user_id) -- INDEX (notification_type) -- INDEX (is_read) -- INDEX (entity_type, entity_id) -- INDEX (created_at) -- COMPOSITE INDEX (user_id, is_read, created_at) - For user notification listing - -**Relationships**: - -- Parent: users - -**Business Rules**: - -- EMAIL: Sent via SMTP to user's email address -- LINE: Sent via LINE Notify API to user's LINE ID -- SYSTEM: In-app notifications displayed in user interface -- Same notification can trigger multiple types (EMAIL + SYSTEM) -- entity_type/entity_id allow deep-linking to related records -- is_read flag only applicable for SYSTEM notifications -- Auto-cleanup: delete read notifications older than 30 days - -**Common Notification Triggers**: - -- Task assignment (circulation routing, RFA workflow) -- Document status changes (submitted, approved, rejected) -- Approaching deadlines -- System announcements -- Mention in comments - ---- - -### 10.3 search_indices - -**Purpose**: Full-text search index for document content - -| Column Name | Data Type | Constraints | Description | -| ----------- | ----------- | --------------------------- | ------------------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique index ID | -| entity_type | VARCHAR(50) | NOT NULL | Entity type (e.g., 'correspondence', 'rfa') | -| entity_id | INT | NOT NULL | Entity primary ID | -| content | TEXT | NOT NULL | Searchable text content | -| indexed_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Last indexing timestamp | - -**Indexes**: - -- PRIMARY KEY (id) -- INDEX (entity_type, entity_id) -- INDEX (indexed_at) - -**Business Rules**: - -- Automatically populated/updated when documents change -- Content includes: title, description, document number, metadata -- May include OCR text from PDF attachments (future enhancement) -- Used by advanced search functionality -- Periodic re-indexing to catch missed updates -- Supports Boolean operators, phrase searching - -**Search Features**: - -- Natural language queries -- Wildcard support (\*, ?) -- Boolean operators (AND, OR, NOT) -- Phrase matching with quotes -- Result ranking by relevance - ---- - -### 10.4 backup_logs - -**Purpose**: Log table tracking database and file backup operations - -| Column Name | Data Type | Constraints | Description | -| ------------- | ------------ | --------------------------- | ---------------------------------- | -| id | INT | PRIMARY KEY, AUTO_INCREMENT | Unique backup log ID | -| backup_type | ENUM | NOT NULL | Type: DATABASE, FILES, FULL | -| backup_path | VARCHAR(500) | NOT NULL | Path to backup file/directory | -| file_size | BIGINT | NULL | Backup file size in bytes | -| status | ENUM | NOT NULL | Status: STARTED, COMPLETED, FAILED | -| started_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Backup start timestamp | -| completed_at | TIMESTAMP | NULL | Backup completion timestamp | -| error_message | TEXT | NULL | Error details if failed | - -**Indexes**: - -- PRIMARY KEY (id) -- INDEX (backup_type) -- INDEX (status) -- INDEX (started_at) -- INDEX (completed_at) - -**Business Rules**: - -- DATABASE: MariaDB dump of database schema and data -- FILES: Backup of attachment files from QNAP storage -- FULL: Complete system backup (database + files) -- Triggered by n8n cron jobs -- Backup retention policy defined in backup strategy -- Failed backups trigger alert notifications -- completed_at - started_at = backup duration - -**Monitoring**: - -- Alert if no successful backup in 24 hours -- Track backup size trends over time -- Verify backup integrity with test restores - ---- - -### 10.5 json_schemas - -**Purpose**: āļ­āļ‡āļĢāļąāļš **Centralized JSON Schema Registry** āđ€āļžāļ·āđˆāļ­ Validate āļ‚āđ‰āļ­āļĄāļđāļĨ JSON Details āļ‚āļ­āļ‡āđ€āļ­āļāļŠāļēāļĢāđāļ•āđˆāļĨāļ°āļ›āļĢāļ°āđ€āļ āļ— āļ•āļēāļĄ Requirements 6.11.1 āđāļĨāļ° Backend Plan T2.5.1 - -| Column Name | Data Type | Constraints | Description | -| :-------------------- | :------------- | :--------------- | :------------------------------------------------- | -| **id** | `INT` | PK, AI | Unique Identifier | -| **schema_code** | `VARCHAR(100)` | UNIQUE, NOT NULL | āļĢāļŦāļąāļŠ Schema (āđ€āļŠāđˆāļ™ `RFA_DWG_V1`, `CORR_RFI_V1`) | -| **version** | `INT` | NOT NULL | āđ€āļ§āļ­āļĢāđŒāļŠāļąāļ™āļ‚āļ­āļ‡ Schema | -| **schema_definition** | `JSON` | NOT NULL | āđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡ JSON Schema (Standard JSON Schema format) | -| **is_active** | `BOOLEAN` | DEFAULT TRUE | āļŠāļ–āļēāļ™āļ°āļāļēāļĢāđƒāļŠāđ‰āļ‡āļēāļ™ | -| **created_at** | `TIMESTAMP` | | āļ§āļąāļ™āļ—āļĩāđˆāļŠāļĢāđ‰āļēāļ‡ | - -### 10.6 user_preferences - -**Purpose**: āđāļĒāļāļ‚āđ‰āļ­āļĄāļđāļĨāļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļēāļŠāđˆāļ§āļ™āļ•āļąāļ§ (āđ€āļŠāđˆāļ™ Notification Settings) āļ­āļ­āļāļˆāļēāļāļ•āļēāļĢāļēāļ‡ Users āđ€āļžāļ·āđˆāļ­āļ„āļ§āļēāļĄāļĒāļ·āļ”āļŦāļĒāļļāđˆāļ™ āļ•āļēāļĄ Requirements 5.5 āđāļĨāļ° 6.8.3 - -| Column Name | Data Type | Constraints | Description | -| :--------------- | :------------ | :-------------- | :------------------------------------- | -| **user_id** | `INT` | PK, FK | āļ­āđ‰āļēāļ‡āļ­āļīāļ‡āļ•āļēāļĢāļēāļ‡ users | -| **notify_email** | `BOOLEAN` | DEFAULT TRUE | āļĢāļąāļšāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļ—āļēāļ‡āļ­āļĩāđ€āļĄāļĨ | -| **notify_line** | `BOOLEAN` | DEFAULT TRUE | āļĢāļąāļšāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āļ—āļēāļ‡ LINE | -| **digest_mode** | `BOOLEAN` | DEFAULT TRUE | āļĢāļąāļšāđāļˆāđ‰āļ‡āđ€āļ•āļ·āļ­āļ™āđāļšāļšāļĢāļ§āļĄ (Digest) āđāļ—āļ™ Real-time | -| **ui_theme** | `VARCHAR(20)` | DEFAULT 'light' | āļ˜āļĩāļĄāļŦāļ™āđ‰āļēāļˆāļ­ (Light/Dark) | -| **updated_at** | `TIMESTAMP` | | āļ§āļąāļ™āļ—āļĩāđˆāđāļāđ‰āđ„āļ‚āļĨāđˆāļēāļŠāļļāļ” | - -## **11. 📊 Views & Procedures (āļ§āļīāļ§ āđāļĨāļ° āđ‚āļ›āļĢāļ‹āļĩāđ€āļ”āļ­āļĢāđŒ)** - -### 11.1 v_current_correspondences - -**Purpose**: View showing current revision of all non-RFA correspondences - -**Columns**: - -- correspondence_id, correspondence_number, correspondence_type_id/code/name -- project_id, project_code, project_iname -- organization_id, organization_code, organization_name -- revision_id, revision_number, revision_label -- title, document_date, issued_date, received_date, due_date -- correspondence_status_id, correspondence_status_code, correspondence_status_name -- created_by, created_by_username, revision_created_at - -**Filters**: - -- is_current = TRUE (only latest revision) -- correspondence_type NOT IN ('RFA') (excludes RFAs) -- deleted_at IS NULL (excludes soft-deleted records) - -**Business Rules**: - -- Provides flattened view of current correspondence state -- Joins correspondence_revisions with is_current flag -- Used by dashboard, document listing screens -- Excludes RFAs (they have separate view) - ---- - -### 11.2 v_current_rfas - -**Purpose**: View showing current revision of all RFA documents - -**Columns**: - -- rfa_id, rfa_type_id, rfa_type_code, rfa_type_name -- correspondence_id, correspondence_number -- project_id, project_code, project_iname -- organization_id, organization_name -- revision_id, revision_number, revision_label -- title, document_date, issued_date, received_date, approved_date -- rfa_status_code_id, rfa_status_code, rfa_status_name -- rfa_approve_code_id, rfa_approve_code, rfa_approve_code_name -- created_by, created_by_username, revision_created_at - -**Filters**: - -- is_current = TRUE -- deleted_at IS NULL (both rfas and correspondences) - -**Business Rules**: - -- Specialized view for RFA documents -- Includes RFA-specific fields (approval codes, approved date) -- Joins across rfas → rfa_revisions → correspondences -- Used by RFA management screens - ---- - -### 11.3 v_contract_parties_all - -**Purpose**: View showing all organization relationships across contracts and projects - -**Columns**: - -- contract_id, contract_code, contract_name -- project_id, project_code, project_iname -- organization_id, organization_code, organization_name -- role_in_contract - -**Business Rules**: - -- Joins contracts → projects → contract_organizations → organizations -- Shows only active contracts (is_active = TRUE) -- Used for permission checks and document routing -- Supports multi-organization projects/contracts - ---- - -### 11.4 v_user_tasks - -**Purpose**: View showing pending tasks assigned to users (action items) - -**Columns**: - -- routing_id, circulation_id, circulation_no, circulation_subject -- correspondence_id, correspondence_number -- project_id, project_code, project_name -- user_id, username, first_name, last_name -- organization_id, organization_name -- step_number, task_status, comments -- completed_at, assigned_at, circulation_created_at - -**Filters**: - -- status IN ('PENDING', 'IN_PROGRESS') -- assigned_to IS NOT NULL - -**Business Rules**: - -- Shows circulation routings requiring user action -- Used for "My Tasks" / "Inbox" functionality -- Excludes completed/cancelled tasks -- Ordered by creation date (oldest first) - ---- - -### 11.5 v_audit_log_details - -**Purpose**: View enriching audit logs with user information - -**Columns**: - -- audit_id, user_id, username, email, first_name, last_name -- action, entity_type, entity_id -- details_json, ip_address, user_agent -- created_at - -**Business Rules**: - -- Joins audit_logs with users table -- Used for audit trail reports -- Includes user details even if user later deleted (LEFT JOIN) - ---- - -### 11.6 v_user_all_permissions - -**Purpose**: View showing all effective permissions for users across all scopes - -**Columns**: - -- user_id, role_id, role_name -- permission_id, permission_name -- module, scope_level -- organization_id, project_id, contract_id -- permission_scope (GLOBAL, ORGANIZATION, PROJECT, CONTRACT) - -**Business Rules**: - -- UNION of permissions from Global, Organization, Project, and Contract scopes -- Used for authorization checks -- Considers role-permission mappings at all levels -- Only shows active permissions (is_active = 1) -- One row per user-permission-scope combination - -**Usage Example**: - -```sql -SELECT permission_name -FROM v_user_all_permissions -WHERE user_id = ? - AND project_id = ? - AND permission_name = 'document.edit'; -``` - ---- - -### 11.7 v_documents_with_attachments - -**Purpose**: View showing all documents and their attachment counts - -**Columns**: - -- document_type (CORRESPONDENCE, CIRCULATION, SHOP_DRAWING, CONTRACT_DRAWING) -- document_id, document_number -- project_id, project_code, project_name -- attachment_count, latest_attachment_date - -**Business Rules**: - -- UNION of all document types with attachments -- Used for document listing with file indicators -- Helps identify documents missing attachments -- Aggregates count per document - ---- - -### 11.8 v_document_statistics - -**Purpose**: View providing aggregated document statistics by project, type, and status - -**Columns**: - -- project_id, project_code, project_name -- correspondence_type_id, correspondence_type_code, correspondence_type_name -- status_id, status_code, status_name -- document_count, revision_count - -**Business Rules**: - -- CROSS JOIN creates all possible combinations -- LEFT JOIN shows zeros for combinations with no documents -- Only includes active projects, types, and statuses -- Used for dashboard charts and reports -- Groups by project → type → status - ---- - -## Database Indexes Summary - -### Performance Optimization Indexes - -**Primary Indexes** (automatic with PRIMARY KEY): - -- All tables have PRIMARY KEY with AUTO_INCREMENT - -**Foreign Key Indexes** (automatic with FOREIGN KEY): - -- All FK relationships automatically indexed - -**Additional Performance Indexes**: - -1. **Correspondence Tables**: - - - `idx_correspondences_type_project` on (correspondence_type_id, project_id) - - `idx_corr_revisions_current_status` on (is_current, correspondence_status_id) - - `idx_corr_revisions_correspondence_current` on (correspondence_id, is_current) - - `idx_correspondences_project_type` on (project_id, correspondence_type_id) - -2. **RFA Tables**: - - - `idx_rfa_revisions_current_status` on (is_current, rfa_status_code_id) - - `idx_rfa_revisions_rfa_current` on (rfa_id, is_current) - -3. **Circulation Tables**: - - - `idx_circulation_routings_status_assigned` on (status, assigned_to) - - `idx_circulation_routings_circulation_status` on (circulation_id, status) - -4. **Document Numbering**: - - - `idx_doc_counter_composite` on (project_id, originator_organization_id, correspondence_type_id, current_year) - -5. **Audit & Notifications**: - - - `idx_audit_logs_reporting` on (created_at, entity_type, action) - - `idx_notifications_user_unread` on (user_id, is_read, created_at) - ---- - -## Data Integrity Constraints - -### Foreign Key Constraints - -**Cascade Delete**: - -- Parent-child relationships where child should be deleted with parent -- Examples: correspondence_revisions, shop_drawing_revisions, project/contract relationships - -**Restrict Delete**: - -- Prevents deletion if references exist -- Examples: correspondence_types, rfa_status_codes, organizations (when referenced) - -**Set NULL**: - -- Preserves record but removes reference -- Examples: originator_id, created_by, updated_by - -### Unique Constraints - -1. **Globally Unique**: - - - usernames, emails - - shop_drawing.drawing_number - -2. **Unique Within Scope**: - - - (project_id, correspondence_number) - - (project_id, condwg_no) - - (correspondence_id, revision_number) - - (rfa_id, revision_number) - -3. **Composite Unique**: - - (correspondence_id, is_current) - ensures only one current revision - - (project_id, correspondence_type_id) - in document_number_formats - -### Check Constraints - -1. **user_assignments.chk_scope**: - - Ensures only one scope field (organization_id, project_id, contract_id) is NOT NULL - - OR all are NULL for Global scope - -### Business Rule Constraints - -1. **Soft Delete Pattern**: - - - deleted_at timestamp instead of hard delete - - Preserves audit trail and relationships - - Applied to: correspondences, rfas, shop_drawings, contract_drawings - -2. **Current Revision Pattern**: - - - is_current flag with UNIQUE constraint - - Ensures only one current revision per document - -3. **Sequential Numbering**: - - revision_number starts at 0, increments by 1 - - Enforced by application logic + stored procedure - ---- - -## Security & Permissions Model - -### Access Control Hierarchy - -```tree -Global Scope (Superadmin) - └── Organization Scope (Org Admin, Document Control) - └── Project Scope (Project Manager) - └── Contract Scope (Contract Admin) -``` - -### Permission Inheritance - -- Users can have multiple role assignments at different scopes -- More specific scopes inherit access from broader scopes -- Permission checks evaluate all applicable scopes -- View v_user_all_permissions aggregates effective permissions - -### Role-Based Access Control (RBAC) - -**7 Predefined Roles**: - -1. Superadmin (Global) - Full system access -2. Org Admin (Organization) - Organization management -3. Document Control (Organization) - Document lifecycle + admin powers -4. Editor (Organization) - Document CRUD -5. Viewer (Organization) - Read-only -6. Project Manager (Project) - Project + document management -7. Contract Admin (Contract) - Contract-specific management - -### Permission Categories (49 total) - -1. **System Management** (1): Full system control -2. **Organization Management** (4): CRUD on organizations -3. **Project Management** (8): CRUD + member/contract management -4. **Role & Permission Management** (4): RBAC administration -5. **Master Data Management** (4): Document types, categories, tags -6. **User Management** (5): User CRUD + organization assignment -7. **Contract Management** (2): Contract administration -8. **Document Management** (16): Full document lifecycle -9. **Workflow Management** (3): Approval workflow control -10. **Search & Reporting** (2): Advanced search, report generation - ---- - -## Data Migration & Seeding - -### Pre-populated Master Data - -1. **organization_roles**: Not used in current implementation -2. **organizations**: 15 organizations (Owner, Consultants, Contractors, Third parties) -3. **projects**: 5 projects (LCBP3 + 4 sub-contracts) -4. **contracts**: 7 contracts -5. **users**: 3 initial users (superadmin, editor01, viewer01) -6. **roles**: 7 predefined roles -7. **permissions**: 49 system permissions -8. **role_permissions**: Complete permission mappings -9. **project_organizations**: Project-organization relationships -10. **contract_organizations**: Contract-organization-role relationships -11. **correspondence_types**: 10 types -12. **correspondence_status**: 23 status codes -13. **rfa_types**: 11 types -14. **rfa_status_codes**: 7 statuses -15. **rfa_approve_codes**: 8 approval codes -16. **circulation_status_codes**: 4 statuses - -### Initial Passwords - -All seed users have password: `password123` - -- Hashed as: `$2y$10$0kjBMxWq7E4G7P.dc8r5i.cjiPBiup553AsFpDfxUt31gKg9h/udq` -- **Must be changed on first login in production** - ---- - -## Backup & Recovery Strategy - -### Database Backup - -**Strategy**: - -- Daily full database backups -- Hourly incremental backups (transaction logs) -- Retention: 30 days online, 7 years archived - -**Backup Method**: - -- MariaDB mysqldump for logical backups -- MariaDB Backup (Mariabackup) for physical backups -- Automated via n8n cron workflows - -### File Backup - -**Strategy**: - -- Daily backup of /share/dms-data/ directory -- QNAP snapshot every 4 hours -- Offsite replication to secondary NAS - -**File Organization**: - -```tree -/share/dms-data/ - ├── attachments/ - │ ├── 2025/ - │ │ ├── 01/ - │ │ │ └── {uuid}-{filename} - │ │ └── 02/ - │ └── 2024/ - └── temp/ -``` - -### Recovery Procedures - -1. **Point-in-Time Recovery**: Using transaction logs -2. **Full Restore**: From latest full backup -3. **Selective Restore**: Individual tables or records -4. **File Recovery**: From QNAP snapshots or backup - ---- - -## Performance Optimization - -### Query Optimization - -1. **Use Indexed Columns**: WHERE, JOIN, ORDER BY clauses -2. **Avoid SELECT**: Specify needed columns -3. **Use Views**: For complex, frequently-used queries -4. **Limit Result Sets**: Use LIMIT and pagination -5. **Analyze Slow Queries**: Enable slow query log - -### Index Maintenance - -```sql --- Check index usage -SELECT * FROM information_schema.STATISTICS -WHERE TABLE_SCHEMA = 'lcbp3'; - --- Optimize tables -OPTIMIZE TABLE correspondences; - --- Analyze tables -ANALYZE TABLE correspondences; -``` - -### Connection Pooling - -- Backend uses connection pool (NestJS TypeORM) -- Min pool size: 5 -- Max pool size: 20 -- Idle timeout: 10 minutes - ---- - -## Data Validation Rules - -### Required Fields Validation - -**At Application Level**: - -- Email format validation -- Date range validation (start_date < end_date) -- File size limits (5MB per attachment) -- File type restrictions (PDF, DWG, DOC, XLS, images) - -**At Database Level**: - -- NOT NULL constraints -- UNIQUE constraints -- Foreign key constraints -- Check constraints (user_assignments scope) - -### Business Logic Validation - -1. **Document Workflow**: - - - Cannot edit submitted documents (unless Document Control) - - Cannot skip workflow steps (unless forced) - - Must provide approval comments - -2. **User Management**: - - - Cannot delete users with active assignments - - Cannot deactivate own account - - Must have valid organization for non-Global roles - -3. **File Management**: - - Original filename preserved for audit - - Unique stored filename prevents conflicts - - File path must exist and be accessible - ---- - -## Change Log & Versioning - -### Database Version: v1.4.0 - -**Changes from v1.3.0**: - -- Added comprehensive RBAC system (roles, permissions, user_assignments) -- Refactored organization-project-contract relationships -- Added junction tables for M:N relationships -- Implemented soft delete pattern -- Added full-text search support -- Enhanced audit logging with JSON details -- Added circulation workflow templates -- Improved document numbering with stored procedure -- Added comprehensive views for common queries -- Optimized indexes for performance - -**Migration Path**: - -- v1.3.0 → v1.4.0: Run migration script (not provided in this excerpt) -- Backup database before migration -- Test migration on staging environment first - ---- - -## Technical Specifications - -### Database Configuration - -**MariaDB Server**: - -- Version: 10.11 -- Character Set: utf8mb4 -- Collation: utf8mb4_general_ci -- Time Zone: +07:00 (Bangkok/Asia) -- SQL Mode: STRICT_TRANS_TABLES, NO_ENGINE_SUBSTITUTION - -**Connection Settings**: - -- Host: Container on QNAP TS-473A -- Port: 3306 (default) -- Max Connections: 100 -- Max Packet Size: 64MB - -### Table Engine - -**InnoDB Features Used**: - -- ACID compliance -- Foreign key constraints -- Row-level locking -- Crash recovery -- Transaction support - -### Storage Requirements - -**Estimated Initial Size**: - -- Database: ~50 MB (with seed data) -- Indexes: ~20 MB -- Total: ~70 MB - -**Growth Estimates**: - -- 1,000 documents/month: +100 MB/month (database) -- 10 attachments/document @ 2MB avg: +20 GB/month (files) -- Plan for 1TB+ storage within first year - ---- - -## Application Integration - -### Backend (NestJS) - -**TypeORM Entities**: - -- One entity class per table -- Decorators for columns, relationships -- DTOs for data transfer -- Repositories for data access - -**Key Modules**: - -- AuthModule: Authentication, authorization -- DocumentsModule: Correspondence, RFA management -- DrawingsModule: Shop drawing, contract drawing -- WorkflowModule: Circulation, approval workflows -- FilesModule: Attachment management -- UsersModule: User, role, permission management - -### Frontend (Next.js) - -**Key Features**: - -- Document listing/search -- Document creation wizards -- Workflow approval interface -- File upload/download -- User management console -- Dashboard analytics - -### Integration Points - -1. **Document Numbering**: - - - Call DocumentNumberingService.generateNextNumber() (NestJS) which handles Redis locking and retry logic - - Format with template from document_number_formats - - Store in correspondences.correspondence_number - -2. **File Upload**: - - - Upload to QNAP /share/dms-data/ - - Create attachment record - - Link via junction table - -3. **Workflow Execution**: - - - Check rfa_workflow_templates - - Create rfa_workflows records - - Update status as steps complete - - Send notifications - -4. **Permission Checks**: - - Query v_user_all_permissions - - Cache results per session - - Re-check on sensitive operations - ---- - -## Monitoring & Maintenance - -### Health Checks - -```sql --- Database size -SELECT - table_schema, - SUM(data_length + index_length) / 1024 / 1024 AS size_mb -FROM information_schema.TABLES -WHERE table_schema = 'dms_db' -GROUP BY table_schema; - --- Table sizes -SELECT - table_name, - (data_length + index_length) / 1024 / 1024 AS size_mb, - table_rows -FROM information_schema.TABLES -WHERE table_schema = 'dms_db' -ORDER BY (data_length + index_length) DESC; - --- Active connections -SHOW PROCESSLIST; - --- Lock wait statistics -SELECT * FROM information_schema.INNODB_LOCK_WAITS; -``` - -### Scheduled Maintenance - -**Daily**: - -- Full database backup -- Check backup log for failures -- Monitor disk space - -**Weekly**: - -- OPTIMIZE tables -- ANALYZE tables -- Review slow query log -- Check for deadlocks - -**Monthly**: - -- Review audit logs -- Clean up old notifications (30+ days) -- Archive old audit logs (7+ years) -- Verify backup integrity (test restore) - -**Quarterly**: - -- Review and optimize indexes -- Update database statistics -- Capacity planning review - ---- - -## Glossary - -**Terms**: - -- **Correspondence**: Any formal document exchanged between parties -- **RFA**: Request for Approval - formal submittal for review/approval -- **Circulation**: Internal document routing workflow -- **Transmittal**: Cover sheet for forwarding multiple documents -- **Shop Drawing**: Detailed construction drawing from contractor -- **Contract Drawing**: Baseline drawing from contract specifications -- **Revision**: Version of a document -- **Originator**: Organization that creates/sends a document -- **Recipient**: Organization that receives a document (TO or CC) -- **Scope**: Level of permission application (Global, Organization, Project, Contract) - -**Acronyms**: - -- **DMS**: Document Management System -- **LCBP3**: Laem Chabang Port Phase 3 -- **RBAC**: Role-Based Access Control -- **RFA**: Request for Approval -- **RFI**: Request for Information -- **CSC**: Construction Supervision Consultant -- **TO**: Primary recipient (action required) -- **CC**: Carbon copy (for information) -- **QNAP**: Network Attached Storage device manufacturer - ---- - -## **Document Control:** - -- **Document:** Data Dictionary v1.4.2 -- **Version:** 1.4 -- **Date:** 2025-11-19 -- **Author:** NAP LCBP3-DMS & Gemini -- **Status:** FINAL -- **Classification:** Internal Technical Documentation -- **Approved By:** Nattanin - ---- - -`End of Data Dictionary v1.4.2 (āļ‰āļšāļąāļšāļ›āļĢāļąāļšāļ›āļĢāļļāļ‡)` diff --git a/T0-0 Setting Project.md b/Documnets/Project/T0-0 Setting Project.md similarity index 100% rename from T0-0 Setting Project.md rename to Documnets/Project/T0-0 Setting Project.md diff --git a/T1-0 Setting Project.md b/Documnets/Project/T1-0 Setting Project.md similarity index 100% rename from T1-0 Setting Project.md rename to Documnets/Project/T1-0 Setting Project.md diff --git a/T2-0 Setting Project.md b/Documnets/Project/T2-0 Setting Project.md similarity index 100% rename from T2-0 Setting Project.md rename to Documnets/Project/T2-0 Setting Project.md diff --git a/T2-Postman.md b/Documnets/Project/T2-Postman.md similarity index 100% rename from T2-Postman.md rename to Documnets/Project/T2-Postman.md diff --git a/T3-0 Setting Project.md b/Documnets/Project/T3-0 Setting Project.md similarity index 100% rename from T3-0 Setting Project.md rename to Documnets/Project/T3-0 Setting Project.md diff --git a/T3-Postman.md b/Documnets/Project/T3-Postman.md similarity index 100% rename from T3-Postman.md rename to Documnets/Project/T3-Postman.md diff --git a/Documnets/Project/V1_4_2.zip b/Documnets/Project/V1_4_2.zip new file mode 100644 index 0000000000000000000000000000000000000000..95d5ce2e0e14ac208605835a6e07d1d6cd4a4bab GIT binary patch literal 102785 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%0DM(pV*PF*M4TW5t7ubF}83jH>v(6 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 literal 0 HcmV?d00001