724 lines
48 KiB
HTML
724 lines
48 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="th" class="scroll-smooth">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>DMS v1.3.0 - รายงานสรุปสถาปัตยกรรมระบบ</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
|
|
<link href="https://fonts.googleapis.com/css2?family=Sarabun:wght@300;400;500;700&display=swap" rel="stylesheet">
|
|
|
|
<!-- Chosen Palette: Calm Harmony (Stone & Teal) -->
|
|
<!-- Application Structure Plan: A single-page application with a fixed top navigation (scroll-to-section). This structure is user-friendly, allowing exploration of the complex DMS specs thematically (Overview, Architecture, Modules, Features, Data Explorer) rather than forcing a linear read. The interactive "Data Explorer" is the key interaction, allowing users to dynamically query the database schema. This structure was chosen to synthesize all 4 source reports (Requirements, FullStack, Data Dictionary, SQL) into one cohesive and easily digestible dashboard for all stakeholders (devs, managers). -->
|
|
<!-- Visualization & Content Choices: 1. Architecture Diagram: (Report Info: Docker Stack from Req 2.1) -> (Goal: Organize) -> (Viz: HTML/Tailwind blocks) -> (Interaction: None) -> (Justification: No SVG/Mermaid rule. This is the clearest way to show the stack). 2. RBAC Chart: (Report Info: Roles/Permissions from Req 4.x, DD 2.x) -> (Goal: Compare) -> (Viz: Chart.js Horizontal Bar) -> (Interaction: Tooltip) -> (Justification: Clearly shows permission hierarchy). 3. Data Explorer: (Report Info: Data Dictionary/SQL) -> (Goal: Organize/Explore) -> (Viz: Dynamic HTML Table) -> (Interaction: Dropdown Select) -> (Justification: Makes the dense 44+ table schema interactive and searchable). -->
|
|
<!-- CONFIRMATION: NO SVG graphics used. NO Mermaid JS used. -->
|
|
|
|
<style>
|
|
body {
|
|
font-family: 'Sarabun', sans-serif;
|
|
background-color: #f5f5f4; /* stone-100 */
|
|
}
|
|
.chart-container {
|
|
position: relative;
|
|
width: 100%;
|
|
max-width: 600px; /* max-w-2xl */
|
|
margin-left: auto;
|
|
margin-right: auto;
|
|
height: 350px; /* h-~80 */
|
|
max-height: 40vh;
|
|
}
|
|
.chart-container-sm {
|
|
position: relative;
|
|
width: 100%;
|
|
max-width: 320px; /* max-w-xs */
|
|
margin-left: auto;
|
|
margin-right: auto;
|
|
height: 280px; /* h-72 */
|
|
max-height: 35vh;
|
|
}
|
|
@media (max-width: 640px) {
|
|
.chart-container {
|
|
height: 300px;
|
|
max-height: 50vh;
|
|
}
|
|
}
|
|
nav a {
|
|
transition: color 0.2s;
|
|
}
|
|
nav a:hover {
|
|
color: #0d9488; /* teal-600 */
|
|
}
|
|
.section-title {
|
|
font-size: 1.875rem; /* text-3xl */
|
|
font-weight: 700;
|
|
color: #1c1917; /* stone-900 */
|
|
margin-bottom: 1rem;
|
|
border-bottom: 2px solid #0d9488; /* teal-600 */
|
|
padding-bottom: 0.5rem;
|
|
}
|
|
.card {
|
|
background-color: #ffffff;
|
|
border-radius: 0.75rem; /* rounded-xl */
|
|
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); /* shadow-lg */
|
|
padding: 1.5rem; /* p-6 */
|
|
transition: all 0.3s;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.card-content {
|
|
flex-grow: 1;
|
|
}
|
|
.card:hover {
|
|
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); /* shadow-xl */
|
|
transform: translateY(-2px);
|
|
}
|
|
.stat-card {
|
|
background-color: #f5f5f4; /* stone-100 */
|
|
border: 1px solid #d6d3d1; /* stone-300 */
|
|
border-radius: 0.5rem; /* rounded-lg */
|
|
padding: 1rem;
|
|
text-align: center;
|
|
}
|
|
.stat-value {
|
|
font-size: 2.25rem; /* text-4xl */
|
|
font-weight: 700;
|
|
color: #0d9488; /* teal-600 */
|
|
}
|
|
.stat-label {
|
|
font-size: 0.875rem; /* text-sm */
|
|
color: #57534e; /* stone-600 */
|
|
margin-top: 0.25rem;
|
|
}
|
|
.schema-relation {
|
|
font-size: 0.75rem; /* text-xs */
|
|
font-family: monospace;
|
|
color: #0d9488; /* teal-600 */
|
|
}
|
|
.schema-type {
|
|
font-size: 0.875rem; /* text-sm */
|
|
font-family: monospace;
|
|
color: #a16207; /* yellow-700 */
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="text-stone-700">
|
|
|
|
<!-- Header & Navigation -->
|
|
<header class="bg-white/80 backdrop-blur-md shadow-sm sticky top-0 z-50">
|
|
<nav class="container mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div class="flex justify-between items-center h-16">
|
|
<div class="flex-shrink-0">
|
|
<h1 class="text-xl font-bold text-stone-800">DMS v1.3.0 <span class="text-teal-600">Report</span></h1>
|
|
</div>
|
|
<div class="hidden md:flex md:space-x-6 lg:space-x-8">
|
|
<a href="#overview" class="nav-link font-medium text-stone-600">ภาพรวม</a>
|
|
<a href="#architecture" class="nav-link font-medium text-stone-600">สถาปัตยกรรม</a>
|
|
<a href="#modules" class="nav-link font-medium text-stone-600">โมดูลเอกสาร</a>
|
|
<a href="#features" class="nav-link font-medium text-stone-600">ฟีเจอร์หลัก</a>
|
|
<a href="#data-explorer" class="nav-link font-medium text-stone-600">โครงสร้างข้อมูล</a>
|
|
</div>
|
|
<div class="md:hidden">
|
|
<select id="mobile-nav" class="block w-full rounded-md border-stone-300 shadow-sm focus:border-teal-500 focus:ring-teal-500">
|
|
<option value="#overview">ภาพรวม</option>
|
|
<option value="#architecture">สถาปัตยกรรม</option>
|
|
<option value="#modules">โมดูลเอกสาร</option>
|
|
<option value="#features">ฟีเจอร์หลัก</option>
|
|
<option value="#data-explorer">โครงสร้างข้อมูล</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
</header>
|
|
|
|
<!-- Main Content -->
|
|
<main class="container mx-auto p-4 sm:p-6 lg:p-8 pt-24">
|
|
|
|
<!-- 1. Overview Section -->
|
|
<section id="overview" class="mb-16 -mt-16 pt-20">
|
|
<h2 class="section-title">ภาพรวมระบบ (Overview)</h2>
|
|
<p class="mb-8 text-lg text-stone-600">
|
|
นี่คือรายงานสรุปเชิงโต้ตอบสำหรับ **ระบบจัดการเอกสาร (DMS) v1.3.0** แอปพลิเคชันนี้ถูกออกแบบมาเพื่อวิเคราะห์ข้อกำหนดของระบบ, สถาปัตยกรรม, และโครงสร้างข้อมูล เพื่อให้ทีมพัฒนาและผู้มีส่วนได้ส่วนเสียสามารถทำความเข้าใจภาพรวมของโปรเจกต์ที่ซับซ้อนนี้ได้อย่างรวดเร็ว
|
|
</p>
|
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
<div class="stat-card">
|
|
<div class="stat-value">6+</div>
|
|
<div class="stat-label">โมดูลเอกสารหลัก</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value">44+</div>
|
|
<div class="stat-label">ตารางข้อมูล (Tables)</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value">5+</div>
|
|
<div class="stat-label">วิวข้อมูล (Views)</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value">7</div>
|
|
<div class="stat-label">Services (Docker)</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- 2. Architecture Section -->
|
|
<section id="architecture" class="mb-16 -mt-16 pt-20">
|
|
<h2 class="section-title">สถาปัตยกรรมและเทคโนโลยี (System Architecture)</h2>
|
|
<p class="mb-8 text-lg text-stone-600">
|
|
ระบบ DMS v1.3.0 ถูกออกแบบบนสถาปัตยกรรม Headless/API-First ที่ทันสมัย โดยทำงานทั้งหมดบน QNAP Server ผ่าน Container Station (Docker) เพื่อให้ง่ายต่อการจัดการและบำรุงรักษา
|
|
</p>
|
|
|
|
<div class="card">
|
|
<h3 class="text-xl font-bold text-stone-800 mb-6 text-center">แผนผังการเชื่อมต่อ (Docker Container Stack)</h3>
|
|
<div class="w-full bg-stone-50 p-6 rounded-lg border border-stone-200">
|
|
<!-- Nginx Reverse Proxy -->
|
|
<div class="relative p-4 border-2 border-teal-600 rounded-lg bg-teal-50/50">
|
|
<div class="absolute -top-3 left-4 bg-teal-600 text-white px-3 py-0.5 rounded-full text-sm font-medium">Nginx Proxy Manager</div>
|
|
<p class="text-center text-sm font-medium text-teal-800 pt-2">np-dms.work (HTTPS)</p>
|
|
|
|
<!-- Internal Network 'lcbp3' -->
|
|
<div class="mt-6 p-4 border-2 border-dashed border-stone-400 rounded-lg">
|
|
<div class="absolute -top-3 right-4 bg-stone-500 text-white px-3 py-0.5 rounded-full text-sm font-medium">Network: 'lcbp3'</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mt-4">
|
|
<!-- Frontend -->
|
|
<div class="flex flex-col items-center">
|
|
<div class="w-full p-4 bg-white border border-blue-500 rounded-lg shadow-md text-center">
|
|
<h4 class="font-bold text-blue-700">Next.js (Frontend)</h4>
|
|
<p class="text-xs text-stone-600">ให้บริการ UI (React)</p>
|
|
</div>
|
|
</div>
|
|
<!-- Backend -->
|
|
<div class="flex flex-col items-center">
|
|
<div class="w-full p-4 bg-white border border-red-500 rounded-lg shadow-md text-center">
|
|
<h4 class="font-bold text-red-700">NestJS (Backend)</h4>
|
|
<p class="text-xs text-stone-600">จัดการ API & Business Logic</p>
|
|
</div>
|
|
</div>
|
|
<!-- Other Services -->
|
|
<div class="flex flex-col items-center">
|
|
<div class="w-full p-4 bg-white border border-purple-500 rounded-lg shadow-md text-center">
|
|
<h4 class="font-bold text-purple-700">N8N (Automation)</h4>
|
|
<p class="text-xs text-stone-600">จัดการ Workflow & Line Notify</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Arrow (Backend -> Other Services) -->
|
|
<div class="relative h-10 my-2">
|
|
<div class="absolute top-1/2 left-1/3 md:left-1/2 w-2/3 md:w-1/2 -translate-x-1/2 -translate-y-1/2 border-t-2 border-stone-500"></div>
|
|
<div class="absolute top-1/2 left-1/3 md:left-1/2 -translate-x-1/2 -translate-y-1/2 text-stone-500">▼</div>
|
|
</div>
|
|
|
|
<!-- Data Layer -->
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
<div class="w-full p-4 bg-white border border-orange-500 rounded-lg shadow-md text-center md:col-start-2">
|
|
<h4 class="font-bold text-orange-700">MariaDB (Database)</h4>
|
|
<p class="text-xs text-stone-600">เก็บข้อมูลทั้งหมด (SQL)</p>
|
|
</div>
|
|
<div class="w-full p-4 bg-white border border-green-500 rounded-lg shadow-md text-center">
|
|
<h4 class="font-bold text-green-700">Elasticsearch (Search)</h4>
|
|
<p class="text-xs text-stone-600">ให้บริการค้นหาขั้นสูง</p>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- 3. Document Modules Section -->
|
|
<section id="modules" class="mb-16 -mt-16 pt-20">
|
|
<h2 class="section-title">โมดูลเอกสารหลัก (Document Modules)</h2>
|
|
<p class="mb-8 text-lg text-stone-600">
|
|
ระบบ DMS แบ่งการจัดการเอกสารออกเป็นโมดูลย่อยที่ชัดเจน แต่ละโมดูลมี Workflow และตารางข้อมูลเฉพาะของตนเอง แต่ทั้งหมดเชื่อมโยงกับโครงสร้างเอกสารกลาง (Correspondence)
|
|
</p>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
|
|
<div class="card">
|
|
<div class="card-content">
|
|
<h3 class="text-xl font-bold text-teal-700 mb-2">1. Correspondence (เอกสารโต้ตอบ)</h3>
|
|
<p class="text-sm text-stone-600 mb-4">โมดูลหลักสำหรับเอกสารเข้า-ออกทั่วไป (จดหมาย, เมโม) เป็นตาราง "แม่" สำหรับเอกสารประเภทอื่นเกือบทั้งหมด</p>
|
|
<span class="text-xs font-semibold text-stone-500">ตารางข้อมูลหลัก:</span>
|
|
<ul class="text-sm list-disc list-inside ml-2 text-stone-700">
|
|
<li>`correspondences` (Master)</li>
|
|
<li>`correspondence_revisions` (Child)</li>
|
|
<li>`correspondence_recipients`</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-content">
|
|
<h3 class="text-xl font-bold text-teal-700 mb-2">2. RFA (ขออนุมัติ)</h3>
|
|
<p class="text-sm text-stone-600 mb-4">โมดูลสำหรับเอกสารขออนุมัติ (Request for Approval) เช่น ขออนุมัติแบบ, วัสดุ, หรือเอกสาร ที่มี Workflow ชัดเจน (Draft, Submit, Review, Approved)</p>
|
|
<span class="text-xs font-semibold text-stone-500">ตารางข้อมูลหลัก:</span>
|
|
<ul class="text-sm list-disc list-inside ml-2 text-stone-700">
|
|
<li>`rfas` (Master)</li>
|
|
<li>`rfa_revisions` (Child)</li>
|
|
<li>`rfa_status_codes`</li>
|
|
<li>`rfa_items` (เชื่อม Shop Drawing)</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-content">
|
|
<h3 class="text-xl font-bold text-teal-700 mb-2">3. Drawing (แบบแปลน)</h3>
|
|
<p class="text-sm text-stone-600 mb-4">จัดการแบบแปลน 2 ประเภทหลัก: แบบตามสัญญา (Contract Drawing) และแบบสำหรับก่อสร้าง (Shop Drawing) ซึ่ง Shop Drawing จะถูกอ้างอิงใน RFA</p>
|
|
<span class="text-xs font-semibold text-stone-500">ตารางข้อมูลหลัก:</span>
|
|
<ul class="text-sm list-disc list-inside ml-2 text-stone-700">
|
|
<li>`contract_drawings`</li>
|
|
<li>`shop_drawings` (Master)</li>
|
|
<li>`shop_drawing_revisions` (Child)</li>
|
|
<li>`shop_drawing_revision_contract_refs`</li>
|
|
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-content">
|
|
<h3 class="text-xl font-bold text-teal-700 mb-2">4. Transmittal (เอกสารนำส่ง)</h3>
|
|
<p class="text-sm text-stone-600 mb-4">เอกสารนำส่ง (คล้ายใบปะหน้า) ที่ใช้สำหรับรวบรวมเอกสาร RFA หลายๆ ฉบับ เพื่อส่งให้ผู้รับในคราวเดียว</p>
|
|
<span class="text-xs font-semibold text-stone-500">ตารางข้อมูลหลัก:</span>
|
|
<ul class="text-sm list-disc list-inside ml-2 text-stone-700">
|
|
<li>`transmittals` (1:1 with Correspondence)</li>
|
|
<li>`transmittal_items` (เชื่อม RFA)</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-content">
|
|
<h3 class="text-xl font-bold text-teal-700 mb-2">5. Circulation (ใบเวียนภายใน)</h3>
|
|
<p class="text-sm text-stone-600 mb-4">ระบบใบเวียนภายในองค์กร (Internal) ใช้สำหรับส่งเอกสาร (ที่อ้างอิงจาก Correspondence) เพื่อให้ทีมตรวจสอบ, รับทราบ, หรือดำเนินการ</p>
|
|
<span class="text-xs font-semibold text-stone-500">ตารางข้อมูลหลัก:</span>
|
|
<ul class="text-sm list-disc list-inside ml-2 text-stone-700">
|
|
<li>`circulations` (Master)</li>
|
|
<li>`circulation_assignees` (Tasks)</li>
|
|
<li>`circulation_actions` (Logs)</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-content">
|
|
<h3 class="text-xl font-bold text-teal-700 mb-2">6. สัดส่วนตารางข้อมูล</h3>
|
|
<p class="text-sm text-stone-600 mb-4">แผนภูมิแสดงสัดส่วนตารางข้อมูลที่เกี่ยวข้องในแต่ละโมดูลหลัก (ไม่รวมตาราง Core และ RBAC)</p>
|
|
<div class="chart-container-sm">
|
|
<canvas id="moduleChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</section>
|
|
|
|
<!-- 4. Core Features Section -->
|
|
<section id="features" class="mb-16 -mt-16 pt-20">
|
|
<h2 class="section-title">ฟีเจอร์หลัก (Core Features)</h2>
|
|
<p class="mb-8 text-lg text-stone-600">
|
|
นอกเหนือจากโมดูลเอกสาร ระบบยังมีฟีเจอร์สนับสนุนที่สำคัญ ซึ่งทำงานข้ามโมดูลต่างๆ เพื่อให้ระบบทำงานได้อย่างสมบูรณ์
|
|
</p>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
|
|
<div class="card">
|
|
<h3 class="text-xl font-bold text-stone-800 mb-4">การจัดการสิทธิ์ (RBAC)</h3>
|
|
<p class="text-sm text-stone-600 mb-4">
|
|
ระบบใช้ Role-Based Access Control (RBAC) ที่ละเอียดมาก ผู้ใช้ (Users) จะได้รับบทบาท (Roles) ซึ่งผูกกับสิทธิ์ (Permissions)
|
|
แผนภูมินี้แสดงตัวอย่างจำนวนสิทธิ์ (Permissions) ที่แต่ละบทบาทพื้นฐานอาจมี เพื่อให้เห็นภาพความซับซ้อนในการเข้าถึง
|
|
</p>
|
|
<div class="chart-container">
|
|
<canvas id="rbacChart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3 class="text-xl font-bold text-stone-800 mb-4">การสร้างเลขที่เอกสาร (Numbering)</h3>
|
|
<p class="text-sm text-stone-600 mb-4">
|
|
หนึ่งในส่วนที่สำคัญที่สุด คือการสร้างเลขที่เอกสาร (Running Number) เพื่อป้องกันเลขที่ซ้ำ (Race Condition) ระบบใช้ Stored Procedure (`sp_get_next_document_number`) ใน MariaDB ซึ่งใช้คำสั่ง `SELECT ... FOR UPDATE` เพื่อ "ล็อก" แถวของตัวนับก่อนที่จะอัปเดตค่า
|
|
</p>
|
|
<pre class="bg-stone-900 text-stone-100 text-sm rounded-lg p-4 overflow-x-auto">
|
|
<span class="text-blue-400">SELECT</span> last_number
|
|
<span class="text-blue-400">INTO</span> v_last_number
|
|
<span class="text-blue-400">FROM</span> document_number_counters
|
|
<span class="text-blue-400">WHERE</span> project_id = p_project_id
|
|
<span class="text-blue-400">AND</span> ...
|
|
<span class="text-red-400">FOR UPDATE</span>;
|
|
|
|
<span class="text-blue-400">IF</span> v_last_number IS NULL <span class="text-blue-400">THEN</span>
|
|
<span class="text-blue-400">INSERT INTO</span> ... (last_number) <span class="text-blue-400">VALUES</span> (1);
|
|
<span class="text-blue-400">SET</span> p_next_number = 1;
|
|
<span class="text-blue-400">ELSE</span>
|
|
<span class="text-blue-400">SET</span> p_next_number = v_last_number + 1;
|
|
<span class="text-blue-400">UPDATE</span> document_number_counters
|
|
<span class="text-blue-400">SET</span> last_number = p_next_number
|
|
<span class="text-blue-400">WHERE</span> ...;
|
|
<span class="text-blue-400">END IF</span>;</pre>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3 class="text-xl font-bold text-stone-800 mb-4">การค้นหา (Advanced Search)</h3>
|
|
<p class="text-sm text-stone-600 mb-4">
|
|
(Req 6.2) ระบบใช้ **Elasticsearch** เป็น Service แยกต่างหากสำหรับให้บริการค้นหาขั้นสูง
|
|
NestJS (Backend) จะทำการ Index ข้อมูลจาก Views (`v_current_correspondences`, `v_current_rfas` ฯลฯ) ไปยัง Elasticsearch
|
|
ทำให้ผู้ใช้สามารถค้นหาแบบ Full-text ข้ามโมดูลทั้งหมด (Correspondence, RFA, Drawing) ได้อย่างรวดเร็ว
|
|
</p>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3 class="text-xl font-bold text-stone-800 mb-4">การแจ้งเตือน (Notifications)</h3>
|
|
<p class="text-sm text-stone-600 mb-4">
|
|
(Req 6.7) ระบบใช้ **N8N (Automation)** เป็นศูนย์กลางการแจ้งเตือน
|
|
เมื่อมีเหตุการณ์สำคัญ (เช่น มีการสร้างใบเวียนใหม่) NestJS (Backend) จะยิง Webhook ไปที่ N8N
|
|
จากนั้น N8N จะทำหน้าที่ส่งการแจ้งเตือนไปยัง **Line Notify** หรือ Email ตาม Workflow ที่ตั้งค่าไว้
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- 5. Data Explorer Section -->
|
|
<section id="data-explorer" class="mb-16 -mt-16 pt-20">
|
|
<h2 class="section-title">สำรวจโครงสร้างข้อมูล (Data Explorer)</h2>
|
|
<p class="mb-8 text-lg text-stone-600">
|
|
ระบบ DMS มีตารางข้อมูลมากกว่า 44 ตารางเพื่อรองรับฟีเจอร์ที่ซับซ้อน เลือกตารางจากรายการด้านล่างเพื่อดูโครงสร้าง, ประเภทข้อมูล, และความสัมพันธ์ (Foreign Keys)
|
|
</p>
|
|
|
|
<div class="card">
|
|
<div class="mb-4">
|
|
<label for="table-select" class="block text-sm font-medium text-stone-700 mb-1">เลือกตารางเพื่อดูรายละเอียด:</label>
|
|
<select id="table-select" class="block w-full md:w-1/2 lg:w-1/3 rounded-md border-stone-300 shadow-sm focus:border-teal-500 focus:ring-teal-500">
|
|
<option value="users">users (ผู้ใช้)</option>
|
|
<option value="roles">roles (บทบาท)</option>
|
|
<option value="permissions">permissions (สิทธิ์)</option>
|
|
<option value="user_project_roles">user_project_roles (สิทธิ์ในโปรเจกต์)</option>
|
|
<option value="correspondences">correspondences (เอกสาร - Master)</option>
|
|
<option value="correspondence_revisions">correspondence_revisions (เอกสาร - Child)</option>
|
|
<option value="rfas">rfas (RFA - Master)</option>
|
|
<option value="rfa_revisions">rfa_revisions (RFA - Child)</option>
|
|
<option value="shop_drawings">shop_drawings (Shop Drawing - Master)</option>
|
|
<option value="shop_drawing_revisions">shop_drawing_revisions (Shop Drawing - Child)</option>
|
|
<option value="circulations">circulations (ใบเวียน - Master)</option>
|
|
<option value="circulation_assignees">circulation_assignees (ใบเวียน - Tasks)</option>
|
|
<option value="attachments">attachments (ไฟล์แนบ - Master)</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<h4 id="schema-title" class="text-2xl font-bold text-teal-700 mb-1"></h4>
|
|
<p id="schema-description" class="text-sm text-stone-600 mb-6"></p>
|
|
|
|
<div class="overflow-x-auto border border-stone-200 rounded-lg">
|
|
<table class="min-w-full divide-y divide-stone-200">
|
|
<thead class="bg-stone-50">
|
|
<tr>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-stone-600 uppercase tracking-wider">Column (Field)</th>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-stone-600 uppercase tracking-wider">Type</th>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-stone-600 uppercase tracking-wider">Description / Relation</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="schema-body" class="bg-white divide-y divide-stone-200">
|
|
<!-- JS will populate this -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
</main>
|
|
|
|
<footer class="text-center py-8 mt-12 border-t border-stone-200">
|
|
<p class="text-sm text-stone-500">Interactive Report generated for DMS v1.3.0</p>
|
|
</footer>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
// --- Chart.js Rendering ---
|
|
|
|
// 1. Module Chart (Donut)
|
|
const moduleCtx = document.getElementById('moduleChart');
|
|
if (moduleCtx) {
|
|
new Chart(moduleCtx, {
|
|
type: 'donut',
|
|
data: {
|
|
labels: ['Correspondence', 'RFA', 'Drawing', 'Circulation', 'Transmittal'],
|
|
datasets: [{
|
|
label: 'จำนวนตารางที่เกี่ยวข้อง',
|
|
data: [5, 5, 8, 5, 2],
|
|
backgroundColor: [
|
|
'rgb(20, 184, 166)', // teal-500
|
|
'rgb(15, 118, 110)', // teal-700
|
|
'rgb(107, 114, 128)', // stone-500
|
|
'rgb(55, 65, 81)', // stone-700
|
|
'rgb(245, 158, 11)' // amber-500
|
|
],
|
|
hoverOffset: 4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'bottom',
|
|
labels: {
|
|
font: { family: 'Sarabun' }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 2. RBAC Chart (Horizontal Bar)
|
|
const rbacCtx = document.getElementById('rbacChart');
|
|
if (rbacCtx) {
|
|
new Chart(rbacCtx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: ['Viewer (ผู้ดู)', 'Editor (ผู้แก้ไข)', 'Org Admin (แอดมินองค์กร)', 'Superadmin (ผู้ดูแลระบบ)'],
|
|
datasets: [{
|
|
label: 'จำนวนสิทธิ์ (ตัวอย่าง)',
|
|
data: [5, 12, 20, 30],
|
|
backgroundColor: 'rgba(13, 148, 136, 0.6)', // teal-600
|
|
borderColor: 'rgb(15, 118, 110)', // teal-700
|
|
borderWidth: 1
|
|
}]
|
|
},
|
|
options: {
|
|
indexAxis: 'y',
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
x: {
|
|
beginAtZero: true,
|
|
grid: { display: false }
|
|
},
|
|
y: {
|
|
grid: { display: false }
|
|
}
|
|
},
|
|
plugins: {
|
|
legend: { display: false },
|
|
tooltip: {
|
|
callbacks: {
|
|
label: (context) => ` ${context.dataset.label}: ${context.raw} สิทธิ์`
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// --- Data Explorer Logic ---
|
|
|
|
const databaseSchema = {
|
|
'users': {
|
|
description: 'ตารางเก็บข้อมูลผู้ใช้ในระบบ (Req 4.x)',
|
|
columns: [
|
|
{ name: 'user_id', type: 'INT (PK)', desc: 'ID หลักของผู้ใช้' },
|
|
{ name: 'username', type: 'VARCHAR(100)', desc: 'ชื่อเข้าระบบ (ต้อง Unique)' },
|
|
{ name: 'password_hash', type: 'VARCHAR(255)', desc: 'รหัสผ่านที่ถูก Hashed (Bcrypt)' },
|
|
{ name: 'email', type: 'VARCHAR(100)', desc: 'อีเมล' },
|
|
{ name: 'organization_id', type: 'INT', desc: 'องค์กรที่ผู้ใช้สังกัด', relation: 'organizations(id)' },
|
|
{ name: 'is_active', type: 'BOOLEAN', desc: 'สถานะการใช้งาน' },
|
|
{ name: 'is_superadmin', type: 'BOOLEAN', desc: 'สถานะผู้ดูแลสูงสุด' }
|
|
]
|
|
},
|
|
'roles': {
|
|
description: 'ตาราง Master เก็บ "บทบาท" ทั้งหมด (Req 4.3)',
|
|
columns: [
|
|
{ name: 'role_id', type: 'INT (PK)', desc: 'ID ของบทบาท' },
|
|
{ name: 'role_code', type: 'VARCHAR(50)', desc: 'รหัสบทบาท (เช่น EDITOR, VIEWER)' },
|
|
{ name: 'role_name', type: 'VARCHAR(100)', desc: 'ชื่อบทบาท (เช่น "ผู้แก้ไข")' },
|
|
{ name: 'is_system', type: 'BOOLEAN', desc: 'เป็นบทบาทของระบบ (ห้ามแอดมินลบ)' }
|
|
]
|
|
},
|
|
'permissions': {
|
|
description: 'ตาราง Master เก็บ "สิทธิ์" การกระทำทั้งหมดในระบบ (Req 4.3)',
|
|
columns: [
|
|
{ name: 'permission_id', type: 'INT (PK)', desc: 'ID ของสิทธิ์' },
|
|
{ name: 'permission_code', type: 'VARCHAR(100)', desc: 'รหัสสิทธิ์ (เช่น corr.create, rfa.view)' },
|
|
{ name: 'permission_group', type: 'VARCHAR(50)', desc: 'กลุ่มของสิทธิ์ (เช่น CORR, RFA, ADMIN)' }
|
|
]
|
|
},
|
|
'user_project_roles': {
|
|
description: 'ตารางเชื่อม M:N (Junction) ระหว่าง User, Role, และ Project',
|
|
columns: [
|
|
{ name: 'id', type: 'INT (PK)', desc: 'ID ของการผูกสิทธิ์' },
|
|
{ name: 'user_id', type: 'INT', desc: 'ผู้ใช้', relation: 'users(user_id)' },
|
|
{ name: 'role_id', type: 'INT', desc: 'บทบาท', relation: 'roles(role_id)' },
|
|
{ name: 'project_id', type: 'INT', desc: 'โปรเจกต์', relation: 'projects(id)' }
|
|
]
|
|
},
|
|
'correspondences': {
|
|
description: 'ตาราง "แม่" (Master) สำหรับเอกสารทั้งหมด (Req 3.2)',
|
|
columns: [
|
|
{ name: 'id', type: 'INT (PK)', desc: 'ID หลักของเอกสาร' },
|
|
{ name: 'correspondence_number', type: 'VARCHAR(100)', desc: 'เลขที่เอกสาร (จาก Stored Procedure)' },
|
|
{ name: 'project_id', type: 'INT', desc: 'โปรเจกต์', relation: 'projects(id)' },
|
|
{ name: 'correspondence_type_id', type: 'INT', desc: 'ประเภทเอกสาร', relation: 'correspondence_types(id)' },
|
|
{ name: 'originator_id', type: 'INT', desc: 'องค์กรผู้ส่ง', relation: 'organizations(id)' },
|
|
{ name: 'recipient_id', type: 'INT', desc: 'องค์กรผู้รับ (หลัก)', relation: 'organizations(id)' },
|
|
{ name: 'deleted_at', type: 'DATETIME', desc: 'สำหรับ Soft Delete' }
|
|
]
|
|
},
|
|
'correspondence_revisions': {
|
|
description: 'ตาราง "ลูก" (Child) เก็บประวัติการแก้ไขและเนื้อหาของเอกสาร (Req 3.2)',
|
|
columns: [
|
|
{ name: 'id', type: 'INT (PK)', desc: 'ID ของ Revision' },
|
|
{ name: 'correspondence_id', type: 'INT', desc: 'ID เอกสาร (Master)', relation: 'correspondences(id)' },
|
|
{ name: 'revision_number', type: 'INT', desc: 'เลข Revision (เช่น 0, 1, 2)' },
|
|
{ name: 'is_current', type: 'BOOLEAN', desc: 'เป็น Revision ปัจจุบันหรือไม่' },
|
|
{ name: 'title', type: 'VARCHAR(255)', desc: 'ชื่อเรื่อง/หัวข้อ' },
|
|
{ name: 'document_date', type: 'DATE', desc: 'วันที่ในเอกสาร' },
|
|
{ name: 'issued_date', type: 'DATETIME', desc: 'วันที่ส่ง (Submit)' },
|
|
{ name: 'details', type: 'JSON', desc: 'ข้อมูลอื่นๆ (ถ้ามี)' }
|
|
]
|
|
},
|
|
'rfas': {
|
|
description: 'ตาราง "แม่" (Master) สำหรับเอกสาร RFA (Req 3.5)',
|
|
columns: [
|
|
{ name: 'id', type: 'INT (PK)', desc: 'ID หลักของ RFA' },
|
|
{ name: 'rfa_type_id', type: 'INT', desc: 'ประเภท RFA (DWG, MAT, DOC)', relation: 'rfa_types(id)' },
|
|
{ name: 'revision_number', type: 'INT', desc: 'Revision ล่าสุด (เช่น 0, 1)' }
|
|
]
|
|
},
|
|
'rfa_revisions': {
|
|
description: 'ตาราง "ลูก" (Child) เก็บประวัติและสถานะของ RFA (Req 3.5)',
|
|
columns: [
|
|
{ name: 'id', type: 'INT (PK)', desc: 'ID ของ RFA Revision' },
|
|
{ name: 'correspondence_id', type: 'INT', desc: 'ID เอกสาร (Master)', relation: 'correspondences(id)' },
|
|
{ name: 'rfa_id', type: 'INT', desc: 'ID RFA (Master)', relation: 'rfas(id)' },
|
|
{ name: 'rfa_status_code_id', type: 'INT', desc: 'สถานะ (เช่น DFT, FAP)', relation: 'rfa_status_codes(id)' },
|
|
{ name: 'rfa_approve_code_id', type: 'INT', desc: 'ผลการอนุมัติ (เช่น A, B, C)', relation: 'rfa_approve_codes(id)' },
|
|
{ name: 'title', type: 'VARCHAR(255)', desc: 'ชื่อเรื่อง' }
|
|
]
|
|
},
|
|
'shop_drawings': {
|
|
description: 'ตาราง "แม่" (Master) สำหรับ Shop Drawing (Req 3.4)',
|
|
columns: [
|
|
{ name: 'id', type: 'INT (PK)', desc: 'ID หลักของ Shop Drawing' },
|
|
{ name: 'project_id', type: 'INT', desc: 'โปรเจกต์', relation: 'projects(id)' },
|
|
{ name: 'drawing_number', type: 'VARCHAR(100)', desc: 'เลขที่แบบ (Unique)' },
|
|
{ name: 'title', type: 'VARCHAR(500)', desc: 'ชื่อแบบ' }
|
|
]
|
|
},
|
|
'shop_drawing_revisions': {
|
|
description: 'ตาราง "ลูก" (Child) เก็บ Revision ของ Shop Drawing (Req 3.4)',
|
|
columns: [
|
|
{ name: 'id', type: 'INT (PK)', desc: 'ID ของ Revision' },
|
|
{ name: 'shop_drawing_id', type: 'INT', desc: 'ID Master', relation: 'shop_drawings(id)' },
|
|
{ name: 'revision_number', type: 'VARCHAR(10)', desc: 'เลข Revision (เช่น A, B, 0, 1)' },
|
|
{ name: 'revision_date', type: 'DATE', desc: 'วันที่ของ Revision' }
|
|
]
|
|
},
|
|
'circulations': {
|
|
description: 'ตาราง "แม่" (Master) สำหรับใบเวียนภายใน (Req 3.7)',
|
|
columns: [
|
|
{ name: 'id', type: 'INT (PK)', desc: 'ID ของใบเวียน' },
|
|
{ name: 'correspondence_id', type: 'INT', desc: 'เอกสารที่อ้างอิง', relation: 'correspondences(id)' },
|
|
{ name: 'organization_id', type: 'INT', desc: 'องค์กรเจ้าของใบเวียน', relation: 'organizations(id)' },
|
|
{ name: 'circulation_no', type: 'VARCHAR(100)', desc: 'เลขที่ใบเวียน' },
|
|
{ name: 'circulation_subject', type: 'VARCHAR(500)', desc: 'เรื่อง' },
|
|
{ name: 'circulation_status_code', type: 'VARCHAR(20)', desc: 'สถานะ (OPEN, COMPLETED)', relation: 'circulation_status_codes(code)' }
|
|
]
|
|
},
|
|
'circulation_assignees': {
|
|
description: 'ตาราง "Task" สำหรับผู้ที่ต้องดำเนินการในใบเวียน (Req 3.7.4)',
|
|
columns: [
|
|
{ name: 'id', type: 'INT (PK)', desc: 'ID ของ Task' },
|
|
{ name: 'circulation_id', type: 'INT', desc: 'ใบเวียน', relation: 'circulations(id)' },
|
|
{ name: 'user_id', type: 'INT', desc: 'ผู้ได้รับมอบหมาย', relation: 'users(user_id)' },
|
|
{ name: 'assignee_type', type: 'ENUM', desc: 'ประเภท (MAIN, ACTION, INFO)' },
|
|
{ name: 'deadline', type: 'DATE', desc: 'วันที่กำหนดเสร็จ' },
|
|
{ name: 'is_completed', type: 'BOOLEAN', desc: 'สถานะว่าเสร็จสิ้นหรือยัง' }
|
|
]
|
|
},
|
|
'attachments': {
|
|
description: 'ตาราง "แม่" (Master) สำหรับไฟล์แนบทั้งหมด (Req 3.9)',
|
|
columns: [
|
|
{ name: 'id', type: 'INT (PK)', desc: 'ID ของไฟล์' },
|
|
{ name: 'original_filename', type: 'VARCHAR(255)', desc: 'ชื่อไฟล์เดิม' },
|
|
{ name: 'stored_filename', type: 'VARCHAR(255)', desc: 'ชื่อไฟล์ที่เก็บ (UUID)' },
|
|
{ name: 'file_path', type: 'VARCHAR(500)', desc: 'Path ที่เก็บไฟล์บน Server' },
|
|
{ name: 'mime_type', type: 'VARCHAR(100)', desc: 'ประเภทไฟล์' },
|
|
{ name: 'file_size', type: 'INT', desc: 'ขนาดไฟล์ (bytes)' },
|
|
{ name: 'uploaded_by_user_id', type: 'INT', desc: 'ผู้อัปโหลด', relation: 'users(user_id)' }
|
|
]
|
|
}
|
|
};
|
|
|
|
const tableSelect = document.getElementById('table-select');
|
|
const schemaTitle = document.getElementById('schema-title');
|
|
const schemaDescription = document.getElementById('schema-description');
|
|
const schemaBody = document.getElementById('schema-body');
|
|
|
|
function updateSchemaView(tableName) {
|
|
const tableData = databaseSchema[tableName];
|
|
if (!tableData) return;
|
|
|
|
schemaTitle.textContent = tableName;
|
|
schemaDescription.textContent = tableData.description;
|
|
|
|
let html = '';
|
|
tableData.columns.forEach(col => {
|
|
const relationHtml = col.relation
|
|
? `<span class="schema-relation block">FK → ${col.relation}</span>`
|
|
: '';
|
|
|
|
html += `
|
|
<tr class="hover:bg-stone-50">
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<strong class="text-sm font-medium text-stone-900">${col.name}</strong>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<span class="schema-type">${col.type}</span>
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
<span class="text-sm text-stone-700">${col.desc}</span>
|
|
${relationHtml}
|
|
</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
schemaBody.innerHTML = html;
|
|
}
|
|
|
|
tableSelect.addEventListener('change', (e) => updateSchemaView(e.target.value));
|
|
|
|
// Initial load
|
|
updateSchemaView(tableSelect.value);
|
|
|
|
|
|
// --- Navigation Logic ---
|
|
|
|
// 1. Mobile Navigation
|
|
const mobileNav = document.getElementById('mobile-nav');
|
|
mobileNav.addEventListener('change', (e) => {
|
|
const targetId = e.target.value;
|
|
const targetElement = document.querySelector(targetId);
|
|
if (targetElement) {
|
|
targetElement.scrollIntoView({ behavior: 'smooth' });
|
|
}
|
|
});
|
|
|
|
// 2. Desktop Smooth Scroll
|
|
const desktopLinks = document.querySelectorAll('a.nav-link');
|
|
desktopLinks.forEach(anchor => {
|
|
anchor.addEventListener('click', function (e) {
|
|
e.preventDefault();
|
|
const targetId = this.getAttribute('href');
|
|
const targetElement = document.querySelector(targetId);
|
|
if (targetElement) {
|
|
targetElement.scrollIntoView({ behavior: 'smooth' });
|
|
}
|
|
});
|
|
});
|
|
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |