251128:1700 Backend to T3.1.1

This commit is contained in:
admin
2025-11-28 17:12:05 +07:00
parent b22d00877e
commit f7a43600a3
50 changed files with 4891 additions and 2849 deletions

View File

@@ -7,9 +7,6 @@
// Code Quality & Error Handling
"usernamehw.errorlens",
"yoavbls.pretty-typescript-errors",
"wix.vscode-import-cost",
// Comments & Documentation
"aaron-bond.better-comments",
"gruntfuggly.todo-tree",
@@ -23,21 +20,12 @@
// API Testing
"rangav.vscode-thunder-client",
"humao.rest-client",
// Auto Tags
"formulahendry.auto-close-tag",
"formulahendry.auto-rename-tag",
// Docker & DevOps
"ms-azuretools.vscode-docker",
"ms-kubernetes-tools.vscode-kubernetes-tools",
// Database
"mtxr.sqltools",
"mongodb.mongodb-vscode",
// YAML & Config
"redhat.vscode-yaml",
"mikestead.dotenv",
"editorconfig.editorconfig",
@@ -61,7 +49,7 @@
"wallabyjs.console-ninja",
// Icons & Theme
"pkief.material-icon-theme",
"pkief.material-icon-theme"
// AI Assistance (Optional - เลือก 1 อัน)
// "github.copilot",

View File

@@ -0,0 +1,709 @@
# 🎯 **Admin Panel Comprehensive Analysis สำหรับ LCBP3-DMS v1.4.4**
จากการวิเคราะห์ Requirements และ Technical Design ทั้งหมด ขอนำเสนอ Admin Panel ที่สมบูรณ์สำหรับระบบ LCBP3-DMS
## 📊 **ภาพรวม Admin Panel Structure**
### **Role-Based Access Levels**
- **Superadmin:** Full system access
- **Org Admin:** Organization-level management
- **Project Manager:** Project-level administration
- **Contract Admin:** Contract-specific management
---
## 🛠️ **1. System Administration**
### **1.1 Global System Configuration**
```typescript
interface SystemConfig {
maintenance_mode: boolean;
session_timeout: number;
password_policy: {
min_length: number;
require_uppercase: boolean;
require_numbers: boolean;
require_special_chars: boolean;
expiry_days: number;
};
file_upload: {
max_size_mb: number;
allowed_types: string[];
virus_scan_enabled: boolean;
};
notification: {
email_enabled: boolean;
line_enabled: boolean;
digest_frequency: number; // minutes
};
}
```
**Features:**
- [ ] Toggle Maintenance Mode
- [ ] Configure Security Policies
- [ ] Manage File Upload Settings
- [ ] Configure Notification Channels
- [ ] System Health Monitoring Dashboard
### **1.2 Audit & Monitoring Center**
```typescript
interface AuditDashboard {
security_metrics: {
failed_logins: number;
file_scans: number;
virus_detections: number;
permission_changes: number;
};
user_activity: AuditLog[];
system_performance: PerformanceMetrics;
recent_errors: SystemError[];
}
```
**Features:**
- [ ] Real-time Activity Monitoring
- [ ] Security Incident Reporting
- [ ] Performance Metrics Dashboard
- [ ] Error Log Viewer
- [ ] Export Audit Reports
---
## 👥 **2. User & Organization Management**
### **2.1 User Management**
```typescript
interface UserManagement {
user_list: User[];
bulk_operations: {
import_users: FileUpload;
export_users: CSVExport;
bulk_assign_roles: RoleAssignment[];
};
user_lifecycle: {
onboarding_workflow: WorkflowConfig;
offboarding_checklist: ChecklistItem[];
};
}
```
**Features:**
- [ ] User CRUD Operations
- [ ] Bulk User Import/Export
- [ ] User Activity Tracking
- [ ] Password Reset Management
- [ ] User Session Management
### **2.2 Organization Hierarchy**
```typescript
interface OrganizationManagement {
organization_tree: Organization[];
department_structure: Department[];
contact_persons: Contact[];
organization_settings: {
document_retention_policy: RetentionPolicy;
notification_preferences: NotificationConfig;
};
}
```
**Features:**
- [ ] Organization CRUD
- [ ] Department Structure Management
- [ ] Contact Person Assignment
- [ ] Organization-specific Policies
### **2.3 Advanced RBAC Management**
```typescript
interface RBACManagement {
role_definitions: Role[];
permission_matrix: Permission[];
assignment_rules: {
automatic_assignments: AutoAssignmentRule[];
conditional_access: ConditionalRule[];
};
permission_audit: {
permission_usage: UsageStats[];
conflict_detection: Conflict[];
};
}
```
**Features:**
- [ ] Custom Role Creation
- [ ] Granular Permission Management
- [ ] Automatic Role Assignment Rules
- [ ] Permission Conflict Detection
- [ ] Role Usage Analytics
---
## 📁 **3. Project & Contract Administration**
### **3.1 Project Portfolio Management**
```typescript
interface ProjectManagement {
project_dashboard: ProjectOverview[];
project_phases: Phase[];
milestone_tracking: Milestone[];
resource_allocation: Resource[];
project_analytics: ProjectMetrics;
}
```
**Features:**
- [ ] Project Lifecycle Management
- [ ] Phase and Milestone Tracking
- [ ] Resource Allocation
- [ ] Project Performance Analytics
- [ ] Project Documentation Repository
### **3.2 Contract Administration**
```typescript
interface ContractManagement {
contract_register: Contract[];
party_management: ContractParty[];
amendment_tracking: Amendment[];
compliance_monitoring: ComplianceCheck[];
financial_tracking: FinancialMetrics;
}
```
**Features:**
- [ ] Contract CRUD Operations
- [ ] Contract Party Management
- [ ] Amendment History
- [ ] Compliance Monitoring
- [ ] Financial Tracking
### **3.3 Project-Organization Mapping**
```typescript
interface ProjectOrgMapping {
project_assignments: ProjectAssignment[];
access_control: AccessRule[];
collaboration_settings: CollaborationConfig;
}
```
**Features:**
- [ ] Organization Assignment to Projects
- [ ] Cross-Organization Collaboration Settings
- [ ] Access Control Configuration
---
## 📋 **4. Master Data Management**
### **4.1 Document Type Ecosystem**
```typescript
interface DocumentTypeManagement {
correspondence_types: DocumentType[];
rfa_types: RFAType[];
circulation_types: CirculationType[];
drawing_categories: DrawingCategory[];
type_hierarchy: TypeHierarchy;
}
```
**Features:**
- [ ] Document Type CRUD
- [ ] Type-Specific Workflow Configuration
- [ ] Category Hierarchy Management
- [ ] Template Association
### **4.2 Discipline & Classification System**
```typescript
interface DisciplineManagement {
disciplines: Discipline[];
sub_types: SubType[];
classification_rules: ClassificationRule[];
mapping_configurations: MappingConfig[];
}
```
**Features:**
- [ ] Discipline CRUD (ตาม Requirement 6B)
- [ ] Sub-Type Management with Number Mapping
- [ ] Automatic Classification Rules
- [ ] Cross-Reference Mapping
### **4.3 Status & Code Management**
```typescript
interface StatusManagement {
status_codes: StatusCode[];
transition_rules: TransitionRule[];
status_workflows: StatusWorkflow[];
automated_status_changes: AutoStatusChange[];
}
```
**Features:**
- [ ] Status Code Configuration
- [ ] State Transition Rules
- [ ] Automated Status Updates
- [ ] Status Change Analytics
---
## 🔢 **5. Document Numbering System Administration**
### **5.1 Numbering Format Configuration**
```typescript
interface NumberingFormatManagement {
format_templates: NumberingTemplate[];
token_library: TokenDefinition[];
format_preview: FormatPreview;
validation_rules: ValidationRule[];
}
```
**Features:**
- [ ] Template Editor with Live Preview
- [ ] Custom Token Definition
- [ ] Format Validation
- [ ] Bulk Template Application
### **5.2 Counter Management**
```typescript
interface CounterManagement {
counter_groups: CounterGroup[];
reset_schedules: ResetSchedule[];
counter_audit: CounterHistory[];
conflict_resolution: ConflictResolutionRule[];
}
```
**Features:**
- [ ] Counter Group Configuration
- [ ] Scheduled Reset Management
- [ ] Counter Audit Trail
- [ ] Conflict Resolution Rules
### **5.3 Numbering Rule Engine**
```typescript
interface NumberingRuleEngine {
conditional_rules: ConditionalRule[];
context_resolvers: ContextResolver[];
fallback_strategies: FallbackStrategy[];
performance_monitoring: PerformanceMetrics;
}
```
**Features:**
- [ ] Conditional Numbering Rules
- [ ] Context Variable Management
- [ ] Fallback Strategy Configuration
- [ ] Performance Optimization
---
## ⚙️ **6. Workflow & Routing Administration**
### **6.1 Workflow DSL Management**
```typescript
interface WorkflowDSLManagement {
workflow_library: WorkflowDefinition[];
dsl_editor: DSLEditor;
version_control: VersionHistory[];
deployment_pipeline: DeploymentConfig[];
}
```
**Features:**
- [ ] Visual Workflow Designer
- [ ] DSL Code Editor with Syntax Highlighting
- [ ] Version Control and Rollback
- [ ] Testing and Deployment Pipeline
### **6.2 Routing Template Administration**
```typescript
interface RoutingTemplateManagement {
template_library: RoutingTemplate[];
step_configurations: StepConfig[];
approval_chains: ApprovalChain[];
escalation_rules: EscalationRule[];
}
```
**Features:**
- [ ] Template CRUD Operations
- [ ] Drag-and-Drop Step Configuration
- [ ] Approval Chain Management
- [ ] Escalation Rule Setup
### **6.3 Workflow Analytics**
```typescript
interface WorkflowAnalytics {
performance_metrics: WorkflowMetrics[];
bottleneck_analysis: Bottleneck[];
compliance_reporting: ComplianceReport[];
optimization_recommendations: Recommendation[];
}
```
**Features:**
- [ ] Workflow Performance Dashboard
- [ ] Bottleneck Identification
- [ ] Compliance Reporting
- [ ] Optimization Suggestions
---
## 📊 **7. Reporting & Analytics Center**
### **7.1 Custom Report Builder**
```typescript
interface ReportBuilder {
data_sources: DataSource[];
visualization_types: VisualizationType[];
report_templates: ReportTemplate[];
scheduling: ScheduleConfig[];
}
```
**Features:**
- [ ] Drag-and-Drop Report Designer
- [ ] Multiple Visualization Options
- [ ] Template Library
- [ ] Automated Report Scheduling
### **7.2 Business Intelligence**
```typescript
interface BusinessIntelligence {
kpi_dashboard: KPIMetric[];
trend_analysis: TrendData[];
predictive_analytics: PredictiveModel[];
data_export: ExportConfig[];
}
```
**Features:**
- [ ] Real-time KPI Dashboard
- [ ] Trend Analysis Tools
- [ ] Predictive Analytics
- [ ] Data Export and Integration
### **7.3 Compliance Reporting**
```typescript
interface ComplianceReporting {
regulatory_reports: RegulatoryReport[];
audit_trails: AuditTrail[];
compliance_dashboard: ComplianceMetric[];
certification_tracking: Certification[];
}
```
**Features:**
- [ ] Pre-built Regulatory Reports
- [ ] Comprehensive Audit Trails
- [ ] Compliance Dashboard
- [ ] Certification Management
---
## 🔐 **8. Security & Compliance Center**
### **8.1 Security Policy Management**
```typescript
interface SecurityPolicyManagement {
access_policies: AccessPolicy[];
data_classification: DataClassification[];
encryption_settings: EncryptionConfig[];
security_incidents: SecurityIncident[];
}
```
**Features:**
- [ ] Access Policy Configuration
- [ ] Data Classification Scheme
- [ ] Encryption Management
- [ ] Security Incident Tracking
### **8.2 Compliance Framework**
```typescript
interface ComplianceFramework {
compliance_rules: ComplianceRule[];
control_testing: ControlTest[];
evidence_management: Evidence[];
compliance_calendar: ComplianceEvent[];
}
```
**Features:**
- [ ] Compliance Rule Engine
- [ ] Control Testing Framework
- [ ] Evidence Collection
- [ ] Compliance Calendar
### **8.3 Risk Management**
```typescript
interface RiskManagement {
risk_register: Risk[];
risk_assessments: RiskAssessment[];
mitigation_plans: MitigationPlan[];
risk_dashboard: RiskMetrics;
}
```
**Features:**
- [ ] Risk Identification and Registration
- [ ] Risk Assessment Tools
- [ ] Mitigation Planning
- [ ] Risk Monitoring Dashboard
---
## 📧 **9. Notification & Communication Management**
### **9.1 Notification Template System**
```typescript
interface NotificationTemplateManagement {
email_templates: EmailTemplate[];
line_templates: LineTemplate[];
system_notifications: SystemTemplate[];
template_variables: TemplateVariable[];
}
```
**Features:**
- [ ] Multi-channel Template Management
- [ ] Variable Substitution System
- [ ] Template Testing and Preview
- [ ] Bulk Template Operations
### **9.2 Subscription Management**
```typescript
interface SubscriptionManagement {
user_preferences: UserPreference[];
group_subscriptions: GroupSubscription[];
escalation_policies: EscalationPolicy[];
delivery_reports: DeliveryReport[];
}
```
**Features:**
- [ ] User Preference Management
- [ ] Group Subscription Configuration
- [ ] Escalation Policy Setup
- [ ] Delivery Monitoring
### **9.3 Digest Configuration**
```typescript
interface DigestConfiguration {
digest_rules: DigestRule[];
grouping_criteria: GroupingCriteria[];
timing_configurations: TimingConfig[];
content_prioritization: PriorityRule[];
}
```
**Features:**
- [ ] Digest Rule Engine
- [ ] Content Grouping Configuration
- [ ] Timing and Frequency Settings
- [ ] Content Prioritization Rules
---
## 🗃️ **10. Data Management & Maintenance**
### **10.1 Data Lifecycle Management**
```typescript
interface DataLifecycleManagement {
retention_policies: RetentionPolicy[];
archival_rules: ArchivalRule[];
purge_schedules: PurgeSchedule[];
data_governance: GovernancePolicy[];
}
```
**Features:**
- [ ] Retention Policy Configuration
- [ ] Automated Archival Rules
- [ ] Scheduled Data Purge
- [ ] Data Governance Framework
### **10.2 Backup & Recovery**
```typescript
interface BackupRecoveryManagement {
backup_configurations: BackupConfig[];
recovery_procedures: RecoveryProcedure[];
disaster_recovery: DisasterRecoveryPlan[];
backup_monitoring: BackupMonitor[];
}
```
**Features:**
- [ ] Backup Schedule Management
- [ ] Recovery Procedure Documentation
- [ ] Disaster Recovery Planning
- [ ] Backup Status Monitoring
### **10.3 System Maintenance**
```typescript
interface SystemMaintenance {
maintenance_windows: MaintenanceWindow[];
update_management: UpdateConfig[];
performance_tuning: TuningParameter[];
cleanup_jobs: CleanupJob[];
}
```
**Features:**
- [ ] Maintenance Window Scheduling
- [ ] Update Management
- [ ] Performance Tuning Parameters
- [ ] Automated Cleanup Jobs
---
## 🎯 **11. Dashboard & Overview**
### **11.1 Executive Dashboard**
```typescript
interface ExecutiveDashboard {
system_health: HealthMetric[];
business_metrics: BusinessMetric[];
user_engagement: EngagementMetric[];
security_posture: SecurityMetric[];
}
```
**Features:**
- [ ] Real-time System Health Monitoring
- [ ] Business Performance Metrics
- [ ] User Engagement Analytics
- [ ] Security Posture Assessment
### **11.2 Operational Dashboard**
```typescript
interface OperationalDashboard {
workflow_monitoring: WorkflowStatus[];
document_metrics: DocumentMetric[];
user_productivity: ProductivityMetric[];
system_utilization: UtilizationMetric[];
}
```
**Features:**
- [ ] Workflow Status Monitoring
- [ ] Document Processing Metrics
- [ ] User Productivity Analytics
- [ ] System Resource Utilization
---
## 🔄 **12. Integration & API Management**
### **12.1 API Gateway Administration**
```typescript
interface APIManagement {
api_endpoints: APIEndpoint[];
rate_limiting: RateLimitConfig[];
authentication_settings: AuthConfig[];
api_analytics: APIAnalytics[];
}
```
**Features:**
- [ ] API Endpoint Management
- [ ] Rate Limiting Configuration
- [ ] Authentication Settings
- [ ] API Usage Analytics
### **12.2 External Integration**
```typescript
interface ExternalIntegration {
webhook_configurations: WebhookConfig[];
third_party_connectors: Connector[];
data_sync_rules: SyncRule[];
integration_monitoring: IntegrationMonitor[];
}
```
**Features:**
- [ ] Webhook Management
- [ ] Third-party Connector Configuration
- [ ] Data Synchronization Rules
- [ ] Integration Health Monitoring
---
## 🎯 **Critical Success Factors**
1. **Unified Administration Experience** - Single pane of glass สำหรับทุกการจัดการ
2. **Role-Based Access Control** - แต่ละระดับเห็นและจัดการได้เฉพาะส่วนของตัวเอง
3. **Real-time Monitoring** - ระบบ monitoring แบบ real-time ทุกส่วน
4. **Audit Trail** - ทุกการเปลี่ยนแปลงใน Admin Panel ถูกบันทึกไว้
5. **Performance** - Admin operations ต้องรวดเร็วแม้มีข้อมูลจำนวนมาก
6. **User Experience** - Interface ใช้งานง่าย แม้สำหรับฟีเจอร์ที่ซับซ้อน
Admin Panel นี้จะทำให้ผู้ดูแลระบบสามารถจัดการ LCBP3-DMS ได้อย่างสมบูรณ์แบบ ตั้งแต่การตั้งค่าระดับพื้นฐานไปจนถึงการจัดการ workflow ที่ซับซ้อนและการวิเคราะห์ข้อมูลเชิงลึก

File diff suppressed because it is too large Load Diff

View File

@@ -36,229 +36,177 @@
### **โครงสร้างโปรเจกต์**
```tree
app/
├── (auth)/
│ ├── login/
│ └── register/
├── (dashboard)/
```
📁frontend
├── .env.local
├── .eslintrc.json
├── .gitignore
├── components.json
├── middleware.ts
├── next-env.d.ts
├── next.config.mjs
├── package.json
├── pnpm-lock.yaml
├── postcss.config.mjs
├── README.md
├── tailwind.config.ts
├── tsconfig.json
├── 📁app
│ ├── 📁(auth)
│ │ └── 📁login
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── 📁(dashboard)
│ │ └── 📁admin
│ │ ├──📁users
│ │ │ └── page.tsx
│ │ ├──📁correspondences
│ │ │ └── 📁new
│ │ │ │ └── page.tsx
│ │ │ └── page.tsx
│ │ ├──📁dashboard
│ │ │ └── page.tsx
│ │ ├──📁profile
│ │ │ └── page.tsx
│ │ ├──📁projects
│ │ │ ├──📁new
│ │ │ │ └── page.tsx
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── 📁api
│ │ └── 📁auth
│ │ └── 📁[...nextauth]
│ │ └── route.ts
│ ├── 📁demo
│ │ └── page.tsx
│ ├── 📁fonts
│ │ ├── GeistMonoVF.woff
│ │ └── GeistVF.woff
│ ├── favicon.ico
│ ├── globals copy.css
│ ├── globals.css
│ ├── layout copy.tsx
│ ├── 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/
── page.tsx
── 📁components
│ ├── 📁custom
│ ├── file-upload-zone.tsx
│ ├── responsive-data-table.tsx
│ └── workflow-visualizer.tsx
│ ├── 📁dashboard
│ └── recent-activity.tsx
│ ├── 📁forms
│ └── file-upload.tsx
│ ├── 📁layout
│ ├── dashboard-shell.tsx
│ ├── navbar.tsx
│ ├── sidebar.tsx
│ │ └── user-nav.tsx
│ ├── 📁tables
│ └── 📁ui
│ ├── avatar.tsx
│ ├── badge.tsx
│ ├── button.tsx
│ ├── calendar.tsx
│ ├── card.tsx
│ ├── checkbox.tsx
│ ├── dropdown-menu.tsx
│ ├── input.tsx
│ ├── label.tsx
│ ├── popover.tsx
│ ├── progress.tsx
│ ├── scroll-area.tsx
│ ├── select.tsx
│ ├── switch.tsx
│ ├── table.tsx
│ ├── tabs.tsx
│ └── textarea.tsx
├── 📁config
│ └── menu.ts
── 📁lib
│ ├── 📁api
│ │ └── client.ts
│ ├── 📁auth
│ ├── 📁hooks
│ ├── 📁services
│ │ ├── circulation.service.ts
│ │ ├── contract-drawing.service.ts
│ │ ├── correspondence.service.ts
│ │ ├── index.ts
│ │ ├── json-schema.service.ts
│ │ ├── master-data.service.ts
│ │ ├── monitoring.service.ts
│ │ ├── notification.service.ts
│ │ ├── project.service.ts
│ │ ├── rfa.service.ts
│ │ ├── search.service.ts
│ │ ├── shop-drawing.service.ts
│ │ ├── transmittal.service.ts
│ │ ├── user.service.ts
│ │ └── workflow-engine.service.ts
│ ├── 📁stores
│ │ ├── draft-store.ts
│ │ └── ui-store.ts
│ ├── auth.ts
│ └── utils.ts
├── 📁providers
│ ├── query-provider.tsx
│ └── session-provider.tsx
├── 📁public
├── 📁styles
└── 📁types
└── 📁dto
└── next-auth.d.ts
├── 📁circulation
│ ├── create-circulation.dto.ts
│ ├── search-circulation.dto.ts
│ └── update-circulation-routing.dto.ts
├── 📁correspondence
│ ├── add-reference.dto.ts
│ ├── create-correspondence.dto.ts
│ ├── search-correspondence.dto.ts
│ ├── submit-correspondence.dto.ts
│ └── workflow-action.dto.ts
├── 📁drawing
│ ├── contract-drawing.dto.ts
│ └── shop-drawing.dto.ts
├── 📁json-schema
│ └── json-schema.dto.ts
├── 📁master
│ ├── discipline.dto.ts
│ ├── number-format.dto.ts
│ ├── sub-type.dto.ts
│ └── tag.dto.ts
├── 📁monitoring
│ └── set-maintenance.dto.ts
├── 📁notification
│ └── notification.dto.ts
├── 📁project
│ └── project.dto.ts
├── 📁rfa
│ └── rfa.dto.ts
├── 📁search
│ └── search-query.dto.ts
├── 📁transmittal
│ └── transmittal.dto.ts
├── 📁user
│ └── user.dto.ts
└── 📁workflow-engine
└── workflow-engine.dto.ts
```
---
## 🗓️ **แผนการพัฒนาแบบ 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)**
### **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
@@ -269,6 +217,7 @@ F10_3 --> F10_4
- [ ] **Dependencies:** None
- **[ ] F0.2 Design System & UI Components**
- [ ] Setup color palette and design tokens
- [ ] Create responsive design breakpoints
- [ ] Implement core shadcn/ui components:
@@ -284,6 +233,7 @@ F10_3 --> F10_4
- [ ] **Dependencies:** F0.1
- **[ ] F0.3 API Client & Authentication**
- [ ] Setup Axios client with interceptors:
- [ ] Idempotency-Key header injection
- [ ] Authentication token management
@@ -325,13 +275,14 @@ F10_3 --> F10_4
---
## **Phase 1: Core Application Structure (สัปดาห์ที่ 2)**
### **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
@@ -348,6 +299,7 @@ F10_3 --> F10_4
- [ ] **Dependencies:** F0.2, F0.3
- **[ ] F1.2 Authentication Pages**
- [ ] Create login page with form validation
- [ ] Implement forgot password flow
- [ ] Create registration page (admin-only)
@@ -358,6 +310,7 @@ F10_3 --> F10_4
- [ ] **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)
@@ -382,13 +335,13 @@ F10_3 --> F10_4
### **Phase 1: Testing - Core Structure**
#### **F1.T1 Layout Test Suite**
- **[ ] 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**
- **[ ] F1.T2 Dashboard Test Suite**
- [ ] **Unit Tests:** Dashboard components, data formatting
- [ ] **Integration Tests:** Data fetching and display, real-time updates
@@ -396,13 +349,14 @@ F10_3 --> F10_4
---
## **Phase 2: User Management & Security (สัปดาห์ที่ 3)**
### **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
@@ -414,6 +368,7 @@ F10_3 --> F10_4
- [ ] **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
@@ -424,6 +379,7 @@ F10_3 --> F10_4
- [ ] **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
@@ -446,22 +402,22 @@ F10_3 --> F10_4
### **Phase 2: User Management & Admin Panel (สัปดาห์ที่ 3)**
* **[ ] F2.5 Admin Panel - Master Data Management (Req 6B)** (New)
* [ ] Create "Disciplines Management" page (CRUD)
* [ ] Create "Sub-Types Management" page (CRUD + Mapping Number)
* [ ] Create "Numbering Format" configuration page (Template Editor)
* [ ] **Deliverable:** UI for Admin to configure system master data
* [ ] **Dependencies:** F2.1
- **[ ] F2.5 Admin Panel - Master Data Management (Req 6B)** (New)
- [ ] Create "Disciplines Management" page (CRUD)
- [ ] Create "Sub-Types Management" page (CRUD + Mapping Number)
- [ ] Create "Numbering Format" configuration page (Template Editor)
- [ ] **Deliverable:** UI for Admin to configure system master data
- [ ] **Dependencies:** F2.1
### **Phase 2: Testing - User Management**
#### **F2.T1 User Management Test Suite**
- **[ ] 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**
- **[ ] F2.T2 RBAC Test Suite**
- [ ] **Unit Tests:** Permission checks, role validation
- [ ] **Integration Tests:** Multi-level permission enforcement, UI element protection
@@ -469,13 +425,14 @@ F10_3 --> F10_4
---
## **Phase 3: Project Structure (สัปดาห์ที่ 4)**
### **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
@@ -486,6 +443,7 @@ F10_3 --> F10_4
- [ ] **Dependencies:** F1.1, F2.4
- **[ ] F3.2 Organization Management**
- [ ] Create organization list and management
- [ ] Implement organization creation and editing
- [ ] Create organization detail view
@@ -507,21 +465,21 @@ F10_3 --> F10_4
### **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
- **[ ] 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)**
### **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
@@ -536,6 +494,7 @@ F10_3 --> F10_4
- [ ] **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)
@@ -553,6 +512,7 @@ F10_3 --> F10_4
- [ ] **Dependencies:** F0.4, F4.1
- **[ ] F4.3 Correspondence Detail View**
- [ ] Create comprehensive detail page:
- [ ] Document header with metadata
- [ ] Content display based on type
@@ -583,27 +543,27 @@ F10_3 --> F10_4
### **Phase 4: Testing - Correspondence System**
#### **F4.T1 Correspondence Test Suite**
- **[ ] 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
- [ ] **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
- **[ ] 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)**
### **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
@@ -614,6 +574,7 @@ F10_3 --> F10_4
- [ ] **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)
@@ -624,6 +585,7 @@ F10_3 --> F10_4
- [ ] **Dependencies:** F3.1, F4.2
- **[ ] F5.3 Workflow Step Actions**
- [ ] Create step action interface:
- [ ] Approve, reject, request changes
- [ ] Add comments and attachments
@@ -647,21 +609,21 @@ F10_3 --> F10_4
### **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
- **[ ] 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)**
### **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
@@ -672,6 +634,7 @@ F10_3 --> F10_4
- [ ] **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
@@ -682,6 +645,7 @@ F10_3 --> F10_4
- [ ] **Dependencies:** F6.1
- **[ ] F6.3 Drawing Revision System**
- [ ] Create revision history interface
- [ ] Implement revision comparison functionality
- [ ] Add revision notes and change tracking
@@ -703,21 +667,21 @@ F10_3 --> F10_4
### **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
- **[ ] 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)**
### **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
@@ -728,6 +692,7 @@ F10_3 --> F10_4
- [ ] **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
@@ -747,6 +712,7 @@ F10_3 --> F10_4
- [ ] **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
@@ -768,21 +734,21 @@ F10_3 --> F10_4
### **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
- **[ ] 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)**
### **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
@@ -793,6 +759,7 @@ F10_3 --> F10_4
- [ ] **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
@@ -814,21 +781,21 @@ F10_3 --> F10_4
### **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
- **[ ] 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)**
### **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
@@ -839,6 +806,7 @@ F10_3 --> F10_4
- [ ] **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
@@ -849,6 +817,7 @@ F10_3 --> F10_4
- [ ] **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
@@ -870,21 +839,21 @@ F10_3 --> F10_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
- **[ ] 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)**
### **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
@@ -896,6 +865,7 @@ F10_3 --> F10_4
- [ ] **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
@@ -906,6 +876,7 @@ F10_3 --> F10_4
- [ ] **Dependencies:** F10.1
- **[ ] F10.3 Security Hardening**
- [ ] Conduct security audit and penetration testing
- [ ] Implement Content Security Policy (CSP)
- [ ] Add security headers and protections
@@ -927,19 +898,18 @@ F10_3 --> F10_4
### **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
- **[ ] 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 | ระยะเวลา | จำนวนงาน | Output หลัก |
| -------- | -------------- | ------------ | ------------------------------------ |
| Phase 0 | 1 สัปดาห์ | 4 | Foundation & Tooling Ready |
| Phase 1 | 1 สัปดาห์ | 4 | Core Application Structure |
| Phase 2 | 1 สัปดาห์ | 4 | User Management & Security |

View File

@@ -485,26 +485,29 @@
**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 |
| 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 |
| v_ref_type | VARCHAR(50) | GENERATED ALWAYS AS (...) VIRTUAL | Virtual Column ดึง Type จาก JSON details |
| details | JSON | NULL | Type-specific details (e.g., RFI questions) |
| v_doc_subtype | VARCHAR(50) | GENERATED ALWAYS AS (...) VIRTUAL | Virtual Column ดึง Type จาก JSON details |
| schema_version | INT | DEFAULT 1 | Version of the schema used with this details |
**Indexes**:
@@ -521,8 +524,9 @@
- INDEX (issued_date)
- INDEX (v_ref_project_id)
- INDEX (v_ref_type)
- INDEX (v_doc_subtype)
**Relationships**:
- **Relationships**:
- Parent: correspondences, correspondence_status, users
@@ -638,16 +642,16 @@
**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 | คำอธิบายรายละเอียดเกี่ยวกับแม่แบบนี้ |
| 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 ปกติ |
| 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**:
@@ -664,14 +668,14 @@
**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 ขององค์กรที่เป็นผู้รับในขั้นตอนนี้ |
| 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 | วันที่คาดหวัง |
| expected_days | INT | NULL | วันที่คาดหวัง |
**Indexes**:
@@ -690,22 +694,22 @@
**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) |
| 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 | Snapshot ของตัวแปรต่างๆ ใน Workflow ณ จุดนี้ (เช่น ผู้อนุมัติ, เงื่อนไขที่ผ่าน) |
**Indexes**:
@@ -730,11 +734,11 @@
**Purpose**: ตารางนี้ใช้กำหนดกฎ (State Machine) ว่าสถานะใดสามารถเปลี่ยนไปเป็นสถานะใดได้บ้าง โดยขึ้นอยู่กับประเภทของหนังสือ เพื่อควบคุมการไหลของสถานะให้ถูกต้องตามข้อบังคับ
| Column Name | Data Type | Constraints | Description |
| -------------- | --------- | ----------- | ----------------------------------------------- |
| 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 ของสถานะปลายทาง (เช่น รออนุมัติ) |
| from_status_id | INT | PRIMARY KEY | ID ของสถานะต้นทาง (เช่น ร่าง) |
| to_status_id | INT | PRIMARY KEY | ID ของสถานะปลายทาง (เช่น รออนุมัติ) |
**คีย์หลัก (Primary Key):**
@@ -890,25 +894,28 @@
**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 |
| 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 |
| details | JSON | NULL | Type-specific details (e.g., RFI questions) |
| v_ref_drawing_count | INT | GENERATED ALWAYS AS (...) VIRTUAL | Virtual Column ดึง Drawing Count จาก JSON details เพื่อทำ Index |
| schema_version | INT | DEFAULT 1 | Version of the schema used with this details |
**Indexes**:
@@ -924,6 +931,7 @@
- INDEX (rfa_status_code_id)
- INDEX (rfa_approve_code_id)
- INDEX (is_current)
- INDEX (v_ref_drawing_count): ตัวอย่างการ Index ข้อมูลตัวเลขใน JSON
**Relationships**:
@@ -970,14 +978,14 @@
**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 |
| 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**:
@@ -1037,20 +1045,20 @@
**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) |
| 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**:
@@ -1124,6 +1132,7 @@
- Parent: contracts, correspondence_types
- Referenced by: (Used in logic for Document Numbering)
---
## **5. 📐 Drawings Tables (แบบ, หมวดหมู่)**
@@ -1737,20 +1746,20 @@
**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] |
| 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**:
@@ -1929,15 +1938,15 @@
**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 |
| **discipline_id** | **INT** | **PRIMARY KEY** | **Discipline ID (0 if not applicable)** |
| current_year | INT | PRIMARY KEY | Year (Buddhist calendar) |
| 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 |
| **discipline_id** | **INT** | **PRIMARY KEY** | **Discipline ID (0 if not applicable)** |
| current_year | INT | PRIMARY KEY | Year (Buddhist calendar) |
| version | INT | DEFAULT 0 | ใช้สำหรับ Optimistic Locking (ตรวจสอบค่าก่อน Update) |
| last_number | INT | DEFAULT 0 | Last assigned sequence number |
| last_number | INT | DEFAULT 0 | Last assigned sequence number |
**Indexes**:
@@ -1970,20 +1979,20 @@
**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 |
| 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 |
| 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**:
@@ -2153,27 +2162,36 @@
**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` | | วันที่สร้าง |
| 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 |
| **table_name** | `VARCHAR(100)` | NOT NULL | ชื่อตารางที่ Schema นี้ผูกอยู่ (เช่น rfa_revisions) |
| **schema_definition** | `JSON` | NOT NULL | โครงสร้าง JSON Schema (Standard JSON Schema format) |
| **ui_schema** | `JSON` | NULL | โครงสร้าง UI Schema สำหรับ Frontend Form Generator |
| **virtual_columns** | `JSON` | NULL | Config สำหรับสร้าง Generated Columns ใน DB เพื่อ Performance |
| **migration_script** | `JSON` | NULL | Rules สำหรับแปลงข้อมูลจากเวอร์ชันก่อนหน้า (Migration) |
| **is_active** | `BOOLEAN` | DEFAULT TRUE | สถานะการใช้งาน |
| **created_at** | `TIMESTAMP` | | วันที่สร้าง |
| **updated_at** | `TIMESTAMP` | | วันที่อัปเดต |
**Indexes**:
- UNIQUE KEY (schema_code, version): ป้องกัน Version ซ้ำใน Schema เดียวกัน
### 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 | รับแจ้งเตือนทางอีเมล |
| 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` | | วันที่แก้ไขล่าสุด |
| **ui_theme** | `VARCHAR(20)` | DEFAULT 'light' | ธีมหน้าจอ (Light/Dark) |
| **updated_at** | `TIMESTAMP` | | วันที่แก้ไขล่าสุด |
## **11. 📊 Views & Procedures (วิว และ โปรซีเดอร์)**

View File

@@ -1,573 +0,0 @@
# โครงสร้างโฟลเดอร์และไฟล์ทั้งหมดสำหรับ **Backend (NestJS)** ตามแผนงาน **LCBP3-DMS v1.4.3** ตั้งแต่ Phase 0 ถึง Phase 6 (T0-T6.2) ที่ได้ดำเนินการไปแล้ว
โครงสร้างนี้ออกแบบตามหลัก **Domain-Driven Design** และ **Modular Architecture** ที่ระบุไว้ในแผนพัฒนา
---
## 📂 **backend/** (Backend Application)
* [x] .env (สำหรับ Local Dev เท่านั้น ห้าม commit)
* [x] .gitignore
* [x] .prettierrc
* [x] docker-compose.override.yml
* [x] docker-compose.yml
* [x] nest-cli.json
* [x] tsconfig.build.json
* [x] tsconfig.json
* [x] package.json
* [x] pnpm-lock.yaml
* [x] README.md
---
## 📂 **backend/src/** (Source Code)
### **📄 Entry Points
* [x] main.ts
* [x] app.module.ts
* [x] app.service.ts
* [x] app.controller.ts
* [x] app.controller.spec.ts
* [x] redlock.d.ts
### **📁 src/common/** (Shared Resources)
* [x] common.module.ts
* **auth/**
* **dto/**
* [x] login.dto.ts
* [x] register.dto.ts
* **strategies/**
* [x] local.strategy.ts
* [x] jwt.strategy.ts
* [x] auth.controller.spec.ts
* [x] auth.controller.ts
* [x] auth.module.ts
* [x] auth.service.spec.ts
* [x] auth.service.ts
* **config/**
* [x] env.validation.ts
* [x] redis.config.ts
* **decorators/**
* [x] audit.decorator.ts
* [x] bypass-maintenance.decorator.ts
* [x] current-user.decorator.ts
* [x] idempotency.decorator.ts
* [x] require-permission.decorator.ts
* [x] retry.decorator.ts
* [x] circuit-breaker.decorator.ts
* **entities/**
* [x] audit-log.entity.ts
* [x] base.entity.ts
* **exceptions/**
* [x] http-exception.filter.ts
* **file-storage/**
* **entities/**
* [x] attachment.entity.ts
* [x] file-storage.controller.spec.ts
* [x] file-storage.controller.ts
* [x] file-storage.module.ts
* [x] file-storage.service.spec.ts
* [x] file-storage.service.ts
* [x] file-cleanup.service.ts
* [x] guards/`
* [x] jwt-auth.guard.ts
* [x] jwt-refresh.guard.ts
* [x] maintenance-mode.guard.ts
* [x] rbac.guard.ts
* **interceptors/**
* [x] audit-log.interceptor.ts
* [x] idempotency.interceptor.ts
* [x] performance.interceptor.ts
* [x] transform.interceptor.ts
* **resilience/**
* [x] resilience.module.ts
* **services/**
* [x] crypto.service.ts
* [x] request-context.service.ts
### **📁 src/modules/** (Feature Modules)
1. **user/** (User Management & RBAC)
* [x] **dto/**
* [x] assign-user-role.dto.ts
* [x] create-user.dto.ts
* [x] update-user.dto.ts
* [x] update-user-preference.dto.ts
* [x] **entities/**
* [x] user.entity.ts
* [x] role.entity.ts
* [x] permission.entity.ts
* [x] user-assignment.entity.ts
* [x] user-preference.entity.ts
* [x] user-assignment.service.ts
* [x] user-preference.service.ts
* [x] user.controller.ts
* [x] user.module.ts
* [x] user.service.ts
* [x] user.service.spec.ts
2. **project/** (Project Structure)
* [x] **dto/**
* [x] create-project.dto.ts
* [x] search-project.dto.ts
* [x] update-project.dto.ts
* [x] **entities/**
* [x] contract-organization.entity.ts
* [x] contract.entity.ts
* [x] organization.entity.ts
* [x] project-organization.entity.ts
* [x] project.entity.ts
* [x] project.controller.spec.ts
* [x] project.controller.ts
* [x] project.module.ts
* [x] project.service.spec.ts
* [x] project.service.ts
3. **correspondence/** (Core Document System)
* [x] **dto/**
* [x] add-reference.dto.ts
* [x] create-correspondence.dto.ts
* [x] search-correspondence.dto.ts
* [x] submit-correspondence.dto.ts
* [x] workflow-action.dto.ts
* [x] **entities/**
* [x] correspondence-reference.entity.ts
* [x] correspondence-revision.entity.ts
* [x] correspondence-routing.entity.ts
* [x] correspondence-status.entity.ts
* [x] correspondence-type.entity.ts
* [x] correspondence.entity.ts
* [x] routing-template-step.entity.ts
* [x] routing-template.entity.ts
* [x] correspondence.controller.spec.ts
* [x] correspondence.controller.ts
* [x] correspondence.module.ts
* [x] correspondence.service.spec.ts
* [x] correspondence.service.ts
4. **drawing/** (Contract & Shop Drawings)
* [x] **dto/**
* [x] create-contract-drawing.dto.ts
* [x] create-shop-drawing-revision.dto.ts
* [x] create-shop-drawing.dto.ts
* [x] search-contract-drawing.dto.ts
* [x] search-shop-drawing.dto.ts
* [x] update-contract-drawing.dto.ts
* [x] **entities/**
* [x] contract-drawing-sub-category.entity.ts
* [x] contract-drawing-volume.entity.ts
* [x] contract-drawing.entity.ts
* [x] shop-drawing-main-category.entity.ts
* [x] shop-drawing-revision.entity.ts
* [x] shop-drawing-sub-category.entity.ts
* [x] shop-drawing.entity.ts
* [x] contract-drawing.controller.ts
* [x] contract-drawing.service.ts
* [x] drawing-master-data.controller.ts
* [x] drawing-master-data.service.ts
* [x] drawing.module.ts
* [x] shop-drawing.controller.ts
* [x] shop-drawing.service.ts
5. **rfa/** (Request for Approval & Advanced Workflow)
* [x] **dto/**
* [x] create-rfa.dto.ts
* [x] search-rfa.dto.ts
* [x] update-rfa.dto.ts
* [x] **entities/**
* [x] rfa-approve-code.entity.ts
* [x] rfa-item.entity.ts
* [x] rfa-revision.entity.ts
* [x] rfa-status-code.entity.ts
* [x] rfa-type.entity.ts
* [x] rfa-workflow-template-step.entity.ts
* [x] rfa-workflow-template.entity.ts
* [x] rfa-workflow.entity.ts
* [x] rfa.entity.ts
* [x] rfa.controller.ts
* [x] rfa.module.ts
* [x] rfa.service.ts
6. **circulation/** (Internal Routing)
* [x] **dto/**
* [x] create-circulation.dto.ts
* [x] update-circulation-routing.dto.ts
* [x] search-circulation.dto.ts
* [x] **entities/**
* [x] circulation-routing.entity.ts
* [x] circulation-status-code.entity.ts
* [x] circulation.entity.ts
* [x] circulation.controller.ts
* [x] circulation.module.ts
* [x] circulation.service.ts
7. **transmittal/** (Document Forwarding)
* [x] **dto/**
* [x] create-transmittal.dto.ts
* [x] search-transmittal.dto.ts
* [x] update-transmittal.dto.ts
* [x] **entities/**
* [x] transmittal-item.entity.ts
* [x] transmittal.entity.ts
* [x] transmittal.controller.ts
* [x] transmittal.module.ts
* [x] transmittal.service.ts
8. **notification/** (System Alerts)
* [x] **dto/**
* [x] create-notification.dto.ts
* [x] search-notification.dto.ts
* [x] **entities/**
* [x] notification.entity.ts
* [x] notification-cleanup.service.ts
* [x] notification.controller.ts
* [x] notification.gateway.ts
* [x] notification.module.ts
* [x] notification.processor.ts
* [x] notification.service.ts
9. **search/** (Elasticsearch)
* [x] dto/search-query.dto.ts
* [x] search.controller.ts
* [x] search.module.ts
* [x] search.service.ts
10. **document-numbering/**
* [x] **entities/**
* [x] document-number-format.entity.ts
* [x] document-number-counter.entity.ts
* [x] document-numbering.module.ts
* [x] document-numbering.service.spec.ts
* [x] document-numbering.service.ts
11. **workflow-engine/** (Unified Logic)
* [x] **dto/**
* [x] create-workflow-definition.dto.ts
* [x] evaluate-workflow.dto.ts
* [x] get-available-actions.dto.ts
* [x] update-workflow-definition.dto.ts
* [x] interfaces/workflow.interface.ts
* [x] workflow-dsl.service.ts
* [x] workflow-engine.module.ts
* [x] workflow-engine.service.spec.ts
* [x] workflow-engine.service.ts
12. **json-schema/** (Validation)
* [x] **dto/**
* [x] create-json-schema.dto.ts+
* [x] search-json-schema.dto.ts
* [x] update-json-schema.dto.ts
* [x] **entities/**
* [x] json-schema.entity.ts
* [x] json-schema.controller.spec.ts
* [x] json-schema.controller.ts
* [x] json-schema.module.ts
* [x] json-schema.service.spec.ts
* [x] json-schema.service.ts
13. **monitoring/** (Monitoring & Metrics)
* [x] **controllers/**
* [x] health.controller.ts
* [x] **dto/**
* [ ] set-maintenance.dto.ts
* [x] **logger/**
* [x] winston.config.ts
* [x] **services/**
* [x] metrics.service.ts
* [x] monitoring.controller.ts
* [x] monitoring.service.ts
* [x] monitoring.module.ts
## **Folder Structure ของ Backend (NestJS)** ที่
---
### 📁 Backend Folder Structure (LCBP3-DMS v1.4.4)
```
backend/
├── .env # Environment variables for local development only (not committed)
├── .gitignore # Git ignore rules
├── .prettierrc # Prettier configuration
├── docker-compose.yml # Main deployment container configuration
├── docker-compose.override.yml # Dev-time secret/environment injection
├── package.json # Node dependencies and NPM scripts
├── pnpm-lock.yaml # Dependency lock file for pnpm
├── tsconfig.json # TypeScript compiler configuration
├── tsconfig.build.json # TypeScript compiler configuration for production
├── nest-cli.json # NestJS project configuration
├── README.md # Project documentation
└── src/
├── main.ts # Application bootstrap and initialization
├── app.module.ts # Root application module
├── app.service.ts # Root application service
├── app.controller.ts # Root application controller
├── app.controller.spec.ts # Root application unit tests
├── redlock.d.ts # Redlock configuration
├── common/ # 🛠️ Shared framework resources used across modules
│ ├── common.module.ts # Registers shared providers
│ │
│ ├── auth/ # 🛡️ Authentication module
│ │ ├── dto/
│ │ │ ├── login.dto.ts # Login request payload
│ │ │ └── register.dto.ts # Registration payload
│ │ ├── strategies/
│ │ │ ├── local.strategy.ts # Local strategy for authentication
│ │ │ └── jwt.strategy.ts # JWT strategy for authentication
│ │ ├── auth.module.ts # Auth DI module
│ │ ├── auth.controller.ts # Auth REST endpoints
│ │ ├── auth.controller.spec.ts # Unit tests for controller
│ │ ├── auth.service.ts # Authentication logic
│ │ └── auth.service.spec.ts # Unit test for service
│ │
│ ├── config/ # 📄 Configuration
│ │ ├── env.validation.ts # Zod/Joi validation for environment variables
│ │ └── redis.config.ts # Redis configuration
│ │
│ ├── decorators/ # 📡 Decorators for common use cases
│ │ ├── audit.decorator.ts # Enables audit logging for a method
│ │ ├── bypass-maintenance.decorator.ts # Declares bypass maintenance requirement
│ │ ├── current-user.decorator.ts # Extracts logged-in user from request
│ │ ├── idempotency.decorator.ts # Declares idempotency requirement
│ │ ├── require-permission.decorator.ts # Declares RBAC permission requirement
│ │ ├── retry.decorator.ts # Declares retry requirement
│ │ └── circuit-breaker.decorator.ts # Declares circuit breaker requirement
│ │
│ ├── entities/ # 📚 Database entities
│ │ ├── audit-log.entity.ts # Audit log database entity
│ │ └── base.entity.ts # Base abstraction containing core columns
│ │
│ ├── exceptions/ # 🛡️ Global exception trap/formatter
│ │ └── http-exception.filter.ts # Global exception trap/formatter
│ │
│ ├── file-storage/ # 📂 Two-Phase document storage (upload → scan → commit)
│ │ ├── entities/
│ │ │ └── attachment.entity.ts # Represents stored file metadata
│ │ ├── file-storage.controller.ts # Upload/download endpoints
│ │ ├── file-storage.controller.spec.ts # Unit tests
│ │ ├── file-storage.module.ts # Module DI bindings
│ │ ├── file-storage.service.ts # File handling logic
│ │ ├── file-storage.service.spec.ts # Unit tests
│ │ └── file-cleanup.service.ts # Cleanup temporary files
│ │
│ ├── guards/ # 🛡️ JWT authentication guard
│ │ ├── jwt-auth.guard.ts # JWT authentication guard
│ │ ├── jwt-refresh.guard.ts # JWT refresh guard
│ │ ├── maintenance-mode.guard.ts # Maintenance mode guard
│ │ └── rbac.guard.ts # Role-based access control enforcement (ตรวจสอบสิทธิ์ 4 ระดับ)
│ │
│ ├── interceptors/ # 📡 Interceptors for common use cases
│ │ ├── audit-log.interceptor.ts # Automatically logs certain operations
│ │ ├── idempotency.interceptor.ts # Idempotency interceptor
│ │ ├── performance.interceptor.ts #
│ │ └── transform.interceptor.ts # Standardized response formatting
│ │
│ ├─── resilience/ # 🛡️ Circuit-breaker / retry logic (if implemented)
│ │ └── resilience.module.ts # Resilience module
│ │
│ └──── services/ # 🔐 Security service
│ ├── crypto.service.ts # Crypto service
│ └── request-context.service.ts # Request context service (for logging)
├── modules/ # 📦 Module-specific resources
│ ├── user/ # 👤 User + RBAC module
│ │ ├── dto/
│ │ │ ├── assign-user-role.dto.ts # Assign roles to users
│ │ │ ├── create-user.dto.ts # Create new user
│ │ │ ├── update-user.dto.ts # Update user details
│ │ │ └── update-user-preference.dto.ts # Update user preferences
│ │ ├── entities/
│ │ │ ├── user.entity.ts # User table definition
│ │ │ ├── role.entity.ts # Role definition
│ │ │ ├── permission.entity.ts # Permission entity
│ │ │ ├── user-assignment.entity.ts # User assignment entity
│ │ │ └── user-preference.entity.ts # User preference settings
│ │ ├── user-assignment.service.ts # User assignment service
│ │ ├── user-preference.service.ts # User preference service
│ │ ├── user.controller.ts # REST endpoints
│ │ ├── user.module.ts # Module DI container
│ │ ├── user.service.ts # Business logic
│ │ └── user.service.spec.ts # Unit tests
│ │
│ ├── project/ # 🏢 Project/Organization/Contract structure
│ │ ├── dto/
│ │ │ ├── create-project.dto.ts # Create new project
│ │ │ ├── search-project.dto.ts # Search projects
│ │ │ └── update-project.dto.ts # Update project
│ │ ├── entities/
│ │ │ ├── project.entity.ts # Project table definition
│ │ │ ├── contract.entity.ts # Contract table definition
│ │ │ ├── organization.entity.ts # Organization table definition
│ │ │ ├── project-organization.entity.ts # Project organization entity
│ │ │ └── contract-organization.entity.ts # Contract organization entity
│ │ ├── project.controller.ts # REST endpoints
│ │ ├── project.controller.spec.ts # Unit tests
│ │ ├── project.module.ts # Module DI container
│ │ ├── project.service.ts # Business logic
│ │ └── project.service.spec.ts # Unit tests
│ │
│ ├── correspondence/ # ✉️ Formal letters with routing workflow
│ │ ├── dto/
│ │ │ ├── add-reference.dto.ts # Add reference to correspondence
│ │ │ ├── create-correspondence.dto.ts # Create new correspondence
│ │ │ ├── search-correspondence.dto.ts # Search correspondences
│ │ │ ├── submit-correspondence.dto.ts # Submit correspondence
│ │ │ └── workflow-action.dto.ts # Workflow action
│ │ ├── entities/
│ │ │ ├── correspondence.entity.ts # Correspondence table definition
│ │ │ ├── correspondence-revision.entity.ts # Correspondence revision entity
│ │ │ ├── correspondence-routing.entity.ts # Correspondence routing entity (Unified Workflow)
│ │ │ ├── correspondence-status.entity.ts # Correspondence status entity
│ │ │ ├── correspondence-type.entity.ts # Correspondence type entity
│ │ │ ├── correspondence-reference.entity.ts # Correspondence reference entity
│ │ │ ├── routing-template.entity.ts # Routing template entity
│ │ │ └── routing-template-step.entity.ts # Routing template step entity
│ │ ├── correspondence.controller.ts # REST endpoints
│ │ ├── correspondence.controller.spec.ts # Unit tests
│ │ ├── correspondence.module.ts # Module DI container
│ │ ├── correspondence.service.ts # Business logic
│ │ └── correspondence.service.spec.ts # Unit tests
│ │
│ ├── drawing/ # 📐Contract & Shop drawing tracking
│ │ ├── dto/
│ │ │ ├── create-contract-drawing.dto.ts # Create new contract drawing
│ │ │ ├── create-shop-drawing.dto.ts # Create new shop drawing
│ │ │ ├── create-shop-drawing-revision.dto.ts # Create new shop drawing revision
│ │ │ ├── search-contract-drawing.dto.ts # Search contract drawings
│ │ │ ├── search-shop-drawing.dto.ts # Search shop drawings
│ │ │ └── update-contract-drawing.dto.ts # Update contract drawing
│ │ ├── entities/
│ │ │ ├── contract-drawing.entity.ts # Contract drawing entity
│ │ │ ├── contract-drawing-volume.entity.ts # Contract drawing volume entity
│ │ │ ├── contract-drawing-sub-category.entity.ts # Contract drawing sub category entity
│ │ │ ├── shop-drawing.entity.ts # Shop drawing entity
│ │ │ ├── shop-drawing-revision.entity.ts # Shop drawing revision entity
│ │ │ ├── shop-drawing-main-category.entity.ts # Shop drawing main category entity
│ │ │ └── shop-drawing-sub-category.entity.ts # Shop drawing sub category entity
│ │ ├── drawing.module.ts # Module DI container
│ │ ├── contract-drawing.controller.ts # REST endpoints
│ │ ├── contract-drawing.service.ts # Business logic
│ │ ├── drawing-master-data.controller.ts # REST endpoints
│ │ ├── drawing-master-data.service.ts # Business logic
│ │ ├── shop-drawing.controller.ts # REST endpoints
│ │ └── shop-drawing.service.ts # Business logic
│ │
│ ├── rfa/ # ✅ Request for Approval (multi-step workflow)
│ │ ├── dto/
│ │ │ ├── create-rfa.dto.ts # Create new RFA
│ │ │ ├── search-rfa.dto.ts # Search RFAs
│ │ │ └── update-rfa.dto.ts # Update RFA
│ │ ├── entities/
│ │ │ ├── rfa.entity.ts # RFA entity
│ │ │ ├── rfa-revision.entity.ts # RFA revision entity
│ │ │ ├── rfa-item.entity.ts # RFA item entity
│ │ │ ├── rfa-type.entity.ts # RFA type entity
│ │ │ ├── rfa-status-code.entity.ts # RFA status code entity
│ │ │ ├── rfa-approve-code.entity.ts # RFA approve code entity
│ │ │ ├── rfa-workflow.entity.ts # RFA workflow entity
│ │ │ ├── rfa-workflow-template.entity.ts # RFA workflow template entity
│ │ │ └── rfa-workflow-template-step.entity.ts # RFA workflow template step entity
│ │ ├── rfa.controller.ts # REST endpoints
│ │ ├── rfa.module.ts # Module DI container
│ │ └── rfa.service.ts # Business logic
│ │
│ ├── circulation/ # 🔄 Internal routing workflow
│ │ ├── dto/
│ │ │ ├── create-circulation.dto.ts # Create new circulation
│ │ │ ├── update-circulation-routing.dto.ts # Update circulation routing
│ │ │ └── search-circulation.dto.ts # Search circulation
│ │ ├── entities/
│ │ │ ├── circulation.entity.ts # Circulation entity
│ │ │ ├── circulation-routing.entity.ts # Circulation routing entity
│ │ │ └── circulation-status-code.entity.ts # Circulation status code entity
│ │ ├── circulation.controller.ts # REST endpoints
│ │ ├── circulation.module.ts # Module DI container
│ │ └── circulation.service.ts # Business logic
│ │
│ ├── transmittal/ # 📤 Document forwarding
│ │ ├── dto/
│ │ │ ├── create-transmittal.dto.ts # Create new transmittal
│ │ │ ├── search-transmittal.dto.ts # Search transmittal
│ │ │ └── update-transmittal.dto.ts # Update transmittal
│ │ ├── entities/
│ │ │ ├── transmittal.entity.ts # Transmittal entity
│ │ │ └── transmittal-item.entity.ts # Transmittal item entity
│ │ ├── transmittal.controller.ts # REST endpoints
│ │ ├── transmittal.module.ts # Module DI container
│ │ └── transmittal.service.ts # Business logic
│ │
│ ├── notification/ # 🔔 Real-Time notification system
│ │ ├── dto/
│ │ │ ├── create-notification.dto.ts # Create new notification
│ │ │ └── search-notification.dto.ts # Search notification
│ │ ├── entities/
│ │ │ └── notification.entity.ts # Notification entity
│ │ ├── notification.module.ts # WebSocket + Processor registration
│ │ ├── notification.controller.ts # REST endpoints
│ │ ├── notification.gateway.ts # WebSocket gateway
│ │ ├── notification.processor.ts # Message consumer (Consumer/Worker for Email & Line)
│ │ ├── notification.service.ts # Business logic
│ │ └── notification-cleanup.service.ts # Cron-based cleanup job
│ │
│ ├── search/ # 🔍 Elasticsearch integration
│ │ ├── dto/
│ │ │ └── search-query.dto.ts # Search query
│ │ ├── search.module.ts # Module DI container
│ │ ├── search.controller.ts # REST endpoints
│ │ └── search.service.ts # Indexing/search logic
│ │
│ ├── document-numbering/ # 🔢 Auto-increment controlled ID generation
│ │ ├── entities/
│ │ │ ├── document-number-format.entity.ts # Document number format entity
│ │ │ └── document-number-counter.entity.ts # Document number counter entity
│ │ ├── document-numbering.module.ts # Module DI container
│ │ ├── document-numbering.service.ts # Business logic
│ │ └── document-numbering.service.spec.ts # Unit tests
│ │
│ ├── workflow-engine/ # ⚙️ Unified state-machine workflow engine
│ │ ├── dto/
│ │ │ ├── create-workflow-definition.dto.ts # Create new workflow definition
│ │ │ ├── evaluate-workflow.dto.ts # Evaluate workflow
│ │ │ ├── get-available-actions.dto.ts # Get available actions
│ │ │ └── update-workflow-definition.dto.ts # Update workflow definition
│ │ ├── entities/
│ │ │ └── workflow-definition.entity.ts # Workflow definition entity
│ │ ├── interfaces/
│ │ │ └── workflow.interface.ts # Workflow interface
│ │ ├── workflow-engine.controller.ts # REST endpoints
│ │ ├── workflow-engine.module.ts # Module DI container
│ │ ├── workflow-engine.service.ts # State Machine Logic
│ │ └── workflow-engine.service.spec.ts # Unit tests
│ │
│ ├── json-schema/ # 📋 Dynamic request schema validation
│ │ ├── dto/
│ │ │ ├── create-json-schema.dto.ts # Create new JSON schema
│ │ │ ├── update-json-schema.dto.ts # Update JSON schema
│ │ │ └── search-json-schema.dto.ts # Search JSON schema
│ │ ├── entities/
│ │ │ └── json-schema.entity.ts # JSON schema entity
│ │ ├── json-schema.module.ts # Module DI container
│ │ ├── json-schema.controller.ts # REST endpoints
│ │ ├── json-schema.controller.spec.ts # Unit tests
│ │ ├── json-schema.service.ts # Business logic
│ │ └── json-schema.service.spec.ts # Unit tests
│ │
│ └── monitoring/ # 📋 Dynamic request schema validation
│ ├── controllers/
│ │ └── health.controller.ts # Create new JSON schema
│ ├── dto/
│ │ └── set-maintenance.dto.ts # Create new JSON schema
│ ├── logger/
│ │ └── winston.config.ts # JSON schema entity
│ ├── services/
│ │ └── metrics.service.ts # JSON schema entity
│ ├── monitoring.controller.ts # REST endpoints
│ ├── monitoring.service.ts # Business logic
│ └── monitoring.module.ts # Module DI container
```
---

View File

@@ -1001,21 +1001,25 @@ CREATE TABLE document_number_counters (
-- 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 (
CREATE TABLE json_schemas (
id INT AUTO_INCREMENT PRIMARY KEY,
schema_code VARCHAR(100) NOT NULL UNIQUE COMMENT 'รหัส Schema เช่น RFA_DWG_V1,
CORR_GENERIC',
schema_code VARCHAR(100) NOT NULL COMMENT 'รหัส Schema (เช่น RFA_DWG)',
version INT NOT NULL DEFAULT 1 COMMENT 'เวอร์ชันของ Schema',
schema_definition JSON NOT NULL COMMENT 'โครงสร้าง JSON Schema (Standard Format)',
is_active BOOLEAN DEFAULT TRUE,
table_name VARCHAR(100) NOT NULL COMMENT 'ชื่อตารางเป้าหมาย (เช่น rfa_revisions)',
schema_definition JSON NOT NULL COMMENT 'โครงสร้าง Data Schema (AJV Standard)',
ui_schema JSON NULL COMMENT 'โครงสร้าง UI Schema สำหรับ Frontend',
virtual_columns JSON NULL COMMENT 'Config สำหรับสร้าง Virtual Columns',
migration_script JSON NULL COMMENT 'Script สำหรับแปลงข้อมูลจากเวอร์ชันก่อนหน้า',
is_active BOOLEAN DEFAULT TRUE COMMENT 'สถานะการใช้งาน',
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;
-- ป้องกัน Schema Code ซ้ำกันใน Version เดียวกัน
UNIQUE KEY uk_schema_version (schema_code, version)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'ตารางเก็บ JSON Schema และ Configuration';
-- 1.2 User Preferences
-- รองรับ: Req 5.5, 6.8.3
-- เหตุผล: แยกการตั้งค่า Notification และ UI ออกจากตาราง Users หลัก
CREATE TABLE IF NOT EXISTS user_preferences (
CREATE TABLE user_preferences (
user_id INT PRIMARY KEY,
notify_email BOOLEAN DEFAULT TRUE,
notify_line BOOLEAN DEFAULT TRUE,
@@ -1145,6 +1149,11 @@ ADD COLUMN v_ref_project_id INT GENERATED ALWAYS AS (
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);
-- 2. ปรับปรุงตาราง correspondence_revisions
-- เพิ่ม Virtual Columns และ Schema Version
ALTER TABLE correspondence_revisions
ADD COLUMN schema_version INT DEFAULT 1 COMMENT 'เวอร์ชันของ Schema ที่ใช้กับ details'
AFTER details;
-- ทำแบบเดียวกันกับ RFA Revisions หากมีการเก็บ JSON details
ALTER TABLE rfa_revisions
ADD COLUMN details JSON NULL COMMENT 'RFA Specific Details'
@@ -1153,6 +1162,7 @@ ALTER TABLE rfa_revisions
ADD COLUMN v_ref_drawing_count INT GENERATED ALWAYS AS (
JSON_UNQUOTE(JSON_EXTRACT(details, '$.drawingCount'))
) VIRTUAL;
CREATE INDEX idx_rfa_rev_v_drawing_count ON rfa_revisions(v_ref_drawing_count);
-- ============================================================
-- 5. PARTITIONING PREPARATION (Advance - Optional)
-- ============================================================
@@ -1592,4 +1602,4 @@ CREATE INDEX idx_circulation_routings_circulation_status ON circulation_routings
-- 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;
SET FOREIGN_KEY_CHECKS = 1;

View File

@@ -1,301 +0,0 @@
```
└── 📁backend
├── 📁scripts # Script files
│ ├── debug-db.ts # Debug database script
│ └── verify-workflow.ts # Verify workflow script
├── 📁src # Source files
│ ├── 📁common # Common files
│ │ ├── 📁auth # Authentication files
│ │ │ ├── 📁dto # Data transfer objects
│ │ │ │ ├── login.dto.ts # Login data transfer object
│ │ │ │ └── register.dto.ts # Register data transfer object
│ │ │ ├── 📁strategies # Authentication strategies
│ │ │ │ ├── jwt-refresh.strategy.ts # JWT refresh strategy
│ │ │ │ └── jwt.strategy.ts # JWT strategy
│ │ │ ├── auth.controller.spec.ts # Authentication controller spec
│ │ │ ├── auth.controller.ts # Authentication controller
│ │ │ ├── auth.module.ts # Authentication module
│ │ │ ├── auth.service.spec.ts # Authentication service spec
│ │ │ └── auth.service.ts # Authentication service
│ │ ├── 📁config # Configuration files
│ │ │ ├── env.validation.ts # Environment validation
│ │ │ └── redis.config.ts # Redis configuration
│ │ ├── 📁decorators # Decorators files
│ │ │ ├── audit.decorator.ts # Audit decorator
│ │ │ ├── bypass-maintenance.decorator.ts # Bypass maintenance decorator
│ │ │ ├── circuit-breaker.decorator.ts # Circuit breaker decorator
│ │ │ ├── current-user.decorator.ts # Current user decorator
│ │ │ ├── idempotency.decorator.ts # Idempotency decorator
│ │ │ ├── require-permission.decorator.ts # Require permission decorator
│ │ │ └── retry.decorator.ts # Retry decorator
│ │ ├── 📁entities # Entities files
│ │ │ ├── audit-log.entity.ts # Audit log entity
│ │ │ └── base.entity.ts # Base entity
│ │ ├── 📁exceptions # Exceptions files
│ │ │ └── http-exception.filter.ts # HTTP exception filter
│ │ ├── 📁file-storage # File storage files
│ │ │ ├── 📁entities # Entities files
│ │ │ │ └── attachment.entity.ts # Attachment entity
│ │ │ ├── file-cleanup.service.ts # File cleanup service
│ │ │ ├── file-storage.controller.spec.ts # File storage controller spec
│ │ │ ├── file-storage.controller.ts # File storage controller
│ │ │ ├── file-storage.module.ts # File storage module
│ │ │ ├── file-storage.service.spec.ts # File storage service spec
│ │ │ └── file-storage.service.ts # File storage service
│ │ ├── 📁guards # Guards files
│ │ │ ├── jwt-auth.guard.ts # JWT authentication guard
│ │ │ ├── jwt-refresh.guard.ts # JWT refresh guard
│ │ │ ├── maintenance-mode.guard.ts # Maintenance mode guard
│ │ │ └── rbac.guard.ts # Role-based access control guard
│ │ ├── 📁idempotency # Idempotency files
│ │ │ ├── idempotency.interceptor.ts # Idempotency interceptor
│ │ │ └── performance.interceptor.ts # Performance interceptor
│ │ ├── 📁interceptors # Interceptors files
│ │ │ ├── audit-log.interceptor.ts # Audit log interceptor
│ │ │ ├── idempotency.interceptor.ts # Idempotency interceptor
│ │ │ ├── performance.interceptor.ts # Performance interceptor
│ │ │ └── transform.interceptor.ts # Transform interceptor
│ │ ├── 📁maintenance # Maintenance files
│ │ ├── 📁resilience # Resilience files
│ │ │ └── resilience.module.ts # Resilience module
│ │ ├── 📁security # Security files
│ │ ├── 📁services # Services files
│ │ │ ├── crypto.service.ts # Crypto service
│ │ │ └── request-context.service.ts # Request context service
│ │ └── common.module.ts # Common module
│ ├── 📁database # Database files
│ │ ├── 📁migrations # Migrations files
│ │ ├── 📁seeds # Seeds files
│ │ │ └── workflow-definitions.seed.ts # Workflow definitions seed
│ ├── 📁modules # Modules files
│ │ ├── 📁circulation # Circulation files
│ │ │ ├── 📁dto # DTO files
│ │ │ │ ├── create-circulation.dto.ts # Create circulation DTO
│ │ │ │ ├── search-circulation.dto.ts # Search circulation DTO
│ │ │ │ └── update-circulation-routing.dto.ts # Update circulation routing DTO
│ │ │ ├── 📁entities # Entities files
│ │ │ │ ├── circulation-routing.entity.ts # Circulation routing entity
│ │ │ │ ├── circulation-status-code.entity.ts # Circulation status code entity
│ │ │ │ └── circulation.entity.ts # Circulation entity
│ │ │ ├── circulation.controller.ts # Circulation controller
│ │ │ ├── circulation.module.ts # Circulation module
│ │ │ └── circulation.service.ts # Circulation service
│ │ ├── 📁correspondence # Correspondence files
│ │ │ ├── 📁dto
│ │ │ │ ├── add-reference.dto.ts
│ │ │ │ ├── create-correspondence.dto.ts
│ │ │ │ ├── search-correspondence.dto.ts
│ │ │ │ ├── submit-correspondence.dto.ts
│ │ │ │ └── workflow-action.dto.ts
│ │ │ ├── 📁entities
│ │ │ │ ├── correspondence-reference.entity.ts
│ │ │ │ ├── correspondence-revision.entity.ts
│ │ │ │ ├── correspondence-routing.entity.ts
│ │ │ │ ├── correspondence-status.entity.ts
│ │ │ │ ├── correspondence-sub-type.entity.ts
│ │ │ │ ├── correspondence-type.entity.ts
│ │ │ │ ├── correspondence.entity.ts
│ │ │ │ ├── routing-template-step.entity.ts
│ │ │ │ └── routing-template.entity.ts
│ │ │ ├── correspondence.controller.spec.ts
│ │ │ ├── correspondence.controller.ts
│ │ │ ├── correspondence.module.ts
│ │ │ ├── correspondence.service.spec.ts
│ │ │ └── correspondence.service.ts
│ │ ├── 📁document-numbering
│ │ │ ├── 📁entities
│ │ │ │ ├── document-number-counter.entity.ts
│ │ │ │ └── document-number-format.entity.ts
│ │ │ ├── 📁interfaces
│ │ │ │ └── document-numbering.interface.ts
│ │ │ ├── document-numbering.module.ts
│ │ │ ├── document-numbering.service.spec.ts
│ │ │ └── document-numbering.service.ts
│ │ ├── 📁drawing
│ │ │ ├── 📁dto
│ │ │ │ ├── create-contract-drawing.dto.ts
│ │ │ │ ├── create-shop-drawing-revision.dto.ts
│ │ │ │ ├── create-shop-drawing.dto.ts
│ │ │ │ ├── search-contract-drawing.dto.ts
│ │ │ │ ├── search-shop-drawing.dto.ts
│ │ │ │ └── update-contract-drawing.dto.ts
│ │ │ ├── 📁entities
│ │ │ │ ├── contract-drawing-sub-category.entity.ts
│ │ │ │ ├── contract-drawing-volume.entity.ts
│ │ │ │ ├── contract-drawing.entity.ts
│ │ │ │ ├── shop-drawing-main-category.entity.ts
│ │ │ │ ├── shop-drawing-revision.entity.ts
│ │ │ │ ├── shop-drawing-sub-category.entity.ts
│ │ │ │ └── shop-drawing.entity.ts
│ │ │ ├── contract-drawing.controller.ts
│ │ │ ├── contract-drawing.service.ts
│ │ │ ├── drawing-master-data.controller.ts
│ │ │ ├── drawing-master-data.service.ts
│ │ │ ├── drawing.module.ts
│ │ │ ├── shop-drawing.controller.ts
│ │ │ └── shop-drawing.service.ts
│ │ ├── 📁json-schema
│ │ │ ├── 📁dto
│ │ │ │ ├── create-json-schema.dto.ts
│ │ │ │ ├── search-json-schema.dto.ts
│ │ │ │ └── update-json-schema.dto.ts
│ │ │ ├── 📁entities
│ │ │ │ └── json-schema.entity.ts
│ │ │ ├── json-schema.controller.spec.ts
│ │ │ ├── json-schema.controller.ts
│ │ │ ├── json-schema.module.ts
│ │ │ ├── json-schema.service.spec.ts
│ │ │ └── json-schema.service.ts
│ │ ├── 📁master
│ │ │ ├── 📁dto
│ │ │ │ ├── create-discipline.dto.ts
│ │ │ │ ├── create-sub-type.dto.ts
│ │ │ │ ├── create-tag.dto.ts
│ │ │ │ ├── save-number-format.dto.ts
│ │ │ │ ├── search-tag.dto.ts
│ │ │ │ └── update-tag.dto.ts
│ │ │ ├── 📁entities
│ │ │ │ ├── discipline.entity.ts
│ │ │ │ └── tag.entity.ts
│ │ │ ├── master.controller.ts
│ │ │ ├── master.module.ts
│ │ │ └── master.service.ts
│ │ ├── 📁monitoring
│ │ │ ├── 📁controllers
│ │ │ │ └── health.controller.ts
│ │ │ ├── 📁dto
│ │ │ │ └── set-maintenance.dto.ts
│ │ │ ├── 📁logger
│ │ │ │ └── winston.config.ts
│ │ │ ├── 📁services
│ │ │ │ └── metrics.service.ts
│ │ │ ├── monitoring.controller.ts
│ │ │ ├── monitoring.module.ts
│ │ │ └── monitoring.service.ts
│ │ ├── 📁notification
│ │ │ ├── 📁dto
│ │ │ │ ├── create-notification.dto.ts
│ │ │ │ └── search-notification.dto.ts
│ │ │ ├── 📁entities
│ │ │ │ └── notification.entity.ts
│ │ │ ├── notification-cleanup.service.ts
│ │ │ ├── notification.controller.ts
│ │ │ ├── notification.gateway.ts
│ │ │ ├── notification.module.ts
│ │ │ ├── notification.processor.ts
│ │ │ └── notification.service.ts
│ │ ├── 📁project
│ │ │ ├── 📁dto
│ │ │ │ ├── create-project.dto.ts
│ │ │ │ ├── search-project.dto.ts
│ │ │ │ └── update-project.dto.ts
│ │ │ ├── 📁entities
│ │ │ │ ├── contract-organization.entity.ts
│ │ │ │ ├── contract.entity.ts
│ │ │ │ ├── organization.entity.ts
│ │ │ │ ├── project-organization.entity.ts
│ │ │ │ └── project.entity.ts
│ │ │ ├── project.controller.spec.ts
│ │ │ ├── project.controller.ts
│ │ │ ├── project.module.ts
│ │ │ ├── project.service.spec.ts
│ │ │ └── project.service.ts
│ │ ├── 📁rfa
│ │ │ ├── 📁dto
│ │ │ │ ├── create-rfa.dto.ts
│ │ │ │ ├── search-rfa.dto.ts
│ │ │ │ └── update-rfa.dto.ts
│ │ │ ├── 📁entities
│ │ │ │ ├── rfa-approve-code.entity.ts
│ │ │ │ ├── rfa-item.entity.ts
│ │ │ │ ├── rfa-revision.entity.ts
│ │ │ │ ├── rfa-status-code.entity.ts
│ │ │ │ ├── rfa-type.entity.ts
│ │ │ │ ├── rfa-workflow-template-step.entity.ts
│ │ │ │ ├── rfa-workflow-template.entity.ts
│ │ │ │ ├── rfa-workflow.entity.ts
│ │ │ │ └── rfa.entity.ts
│ │ │ ├── rfa.controller.ts
│ │ │ ├── rfa.module.ts
│ │ │ └── rfa.service.ts
│ │ ├── 📁search
│ │ │ ├── 📁dto
│ │ │ │ └── search-query.dto.ts
│ │ │ ├── search.controller.ts
│ │ │ ├── search.module.ts
│ │ │ └── search.service.ts
│ │ ├── 📁transmittal
│ │ │ ├── 📁dto
│ │ │ │ ├── create-transmittal.dto.ts
│ │ │ │ ├── search-transmittal.dto.ts
│ │ │ │ └── update-transmittal.dto.ts
│ │ │ ├── 📁entities
│ │ │ │ ├── transmittal-item.entity.ts
│ │ │ │ └── transmittal.entity.ts
│ │ │ ├── transmittal.controller.ts
│ │ │ ├── transmittal.module.ts
│ │ │ └── transmittal.service.ts
│ │ ├── 📁user
│ │ │ ├── 📁dto
│ │ │ │ ├── assign-role.dto.ts
│ │ │ │ ├── create-user.dto.ts
│ │ │ │ ├── update-preference.dto.ts
│ │ │ │ └── update-user.dto.ts
│ │ │ ├── 📁entities
│ │ │ │ ├── permission.entity.ts
│ │ │ │ ├── role.entity.ts
│ │ │ │ ├── user-assignment.entity.ts
│ │ │ │ ├── user-preference.entity.ts
│ │ │ │ └── user.entity.ts
│ │ │ ├── user-assignment.service.ts
│ │ │ ├── user-preference.service.ts
│ │ │ ├── user.controller.ts
│ │ │ ├── user.module.ts
│ │ │ ├── user.service.spec.ts
│ │ │ └── user.service.ts
│ │ └── 📁workflow-engine
│ │ ├── 📁dto
│ │ │ ├── create-workflow-definition.dto.ts
│ │ │ ├── evaluate-workflow.dto.ts
│ │ │ ├── get-available-actions.dto.ts
│ │ │ └── update-workflow-definition.dto.ts
│ │ ├── 📁entities
│ │ │ └── workflow-definition.entity.ts
│ │ ├── 📁interfaces
│ │ │ └── workflow.interface.ts
│ │ ├── workflow-dsl.service.ts
│ │ ├── workflow-engine.controller.ts
│ │ ├── workflow-engine.module.ts
│ │ ├── workflow-engine.service.spec.ts
│ │ └── workflow-engine.service.ts
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ ├── main.ts
│ └── redlock.d.ts
├── 📁test
│ ├── app.e2e-spec.ts
│ ├── jest-e2e.json
│ ├── phase3-workflow.e2e-spec.ts
│ └── simple.e2e-spec.ts
├─ 📁uploads
│ └── 📁temp
│ ├── 5a6d4c26-84b2-4c8a-b177-9fa267651a93.pdf
│ └── d60d9807-a22d-4ca0-b99a-5d5d8b81b3e8.pdf
├── .editorconfig
├── .env
├── .gitignore
├── .prettierrc
├── docker-compose.override.yml.example
├── docker-compose.yml
├── eslint.config.mjs
├── Infrastructure Setup.yml
├── nest-cli.json
├── package-lock.json
├── package.json
├── pnpm-lock.yaml
├── README.md
├── tsconfig.build.json
└── tsconfig.json
```

View File

@@ -1,159 +0,0 @@
```
└── 📁frontend
└── 📁app
└── 📁(auth)
└── 📁login
└── page.tsx
└── layout.tsx
└── 📁(dashboard)
└── 📁admin
└── 📁users
└── page.tsx
└── 📁correspondences
└── 📁new
└── page.tsx
└── page.tsx
└── 📁dashboard
└── page.tsx
└── 📁profile
└── page.tsx
└── 📁projects
└── 📁new
└── page.tsx
└── page.tsx
└── layout.tsx
└── 📁admin
└── 📁api
└── 📁auth
└── 📁[...nextauth]
└── route.ts
└── 📁demo
└── page.tsx
└── 📁fonts
├── GeistMonoVF.woff
└── GeistVF.woff
├── favicon.ico
├── globals copy.css
├── globals.css
├── layout copy.tsx
├── layout.tsx
├── page.tsx
└── 📁components
└── 📁custom
├── file-upload-zone.tsx
├── responsive-data-table.tsx
└── workflow-visualizer.tsx
└── 📁dashboard
└── recent-activity.tsx
└── 📁forms
└── file-upload.tsx
└── 📁layout
├── dashboard-shell.tsx
├── navbar.tsx
├── sidebar.tsx
└── user-nav.tsx
└── 📁tables
└── 📁ui
├── avatar.tsx
├── badge.tsx
├── button.tsx
├── calendar.tsx
├── card.tsx
├── checkbox.tsx
├── dropdown-menu.tsx
├── input.tsx
├── label.tsx
├── popover.tsx
├── progress.tsx
├── scroll-area.tsx
├── select.tsx
├── switch.tsx
├── table.tsx
├── tabs.tsx
└── textarea.tsx
└── 📁config
└── menu.ts
└── 📁lib
└── 📁api
└── client.ts
└── 📁auth
└── 📁hooks
└── 📁services
├── circulation.service.ts
├── contract-drawing.service.ts
├── correspondence.service.ts
├── index.ts
├── json-schema.service.ts
├── master-data.service.ts
├── monitoring.service.ts
├── notification.service.ts
├── project.service.ts
├── rfa.service.ts
├── search.service.ts
├── shop-drawing.service.ts
├── transmittal.service.ts
├── user.service.ts
└── workflow-engine.service.ts
└── 📁stores
├── draft-store.ts
└── ui-store.ts
├── auth.ts
└── utils.ts
└── 📁providers
├── query-provider.tsx
└── session-provider.tsx
└── 📁public
└── 📁styles
└── 📁types
└── 📁dto
└── 📁circulation
├── create-circulation.dto.ts
├── search-circulation.dto.ts
└── update-circulation-routing.dto.ts
└── 📁correspondence
├── add-reference.dto.ts
├── create-correspondence.dto.ts
├── search-correspondence.dto.ts
├── submit-correspondence.dto.ts
└── workflow-action.dto.ts
└── 📁drawing
├── contract-drawing.dto.ts
└── shop-drawing.dto.ts
└── 📁json-schema
└── json-schema.dto.ts
└── 📁master
├── discipline.dto.ts
├── number-format.dto.ts
├── sub-type.dto.ts
└── tag.dto.ts
└── 📁monitoring
└── set-maintenance.dto.ts
└── 📁notification
└── notification.dto.ts
└── 📁project
└── project.dto.ts
└── 📁rfa
└── rfa.dto.ts
└── 📁search
└── search-query.dto.ts
└── 📁transmittal
└── transmittal.dto.ts
└── 📁user
└── user.dto.ts
└── 📁workflow-engine
└── workflow-engine.dto.ts
└── next-auth.d.ts
├── .env.local
├── .eslintrc.json
├── .gitignore
├── components.json
├── middleware.ts
├── next-env.d.ts
├── next.config.mjs
├── package.json
├── pnpm-lock.yaml
├── postcss.config.mjs
├── README.md
├── tailwind.config.ts
└── tsconfig.json
```

View File

@@ -1,12 +1,13 @@
// File: src/common/services/crypto.service.ts
// บันทึกการแก้ไข: Encryption/Decryption Utility (T1.1)
import { Injectable } from '@nestjs/common';
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as crypto from 'crypto';
@Injectable()
export class CryptoService {
private readonly logger = new Logger(CryptoService.name);
private readonly algorithm = 'aes-256-cbc';
private readonly key: Buffer;
private readonly ivLength = 16;
@@ -19,22 +20,42 @@ export class CryptoService {
this.key = crypto.scryptSync(secret, 'salt', 32);
}
encrypt(text: string): string {
const iv = crypto.randomBytes(this.ivLength);
const cipher = crypto.createCipheriv(this.algorithm, this.key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return `${iv.toString('hex')}:${encrypted}`;
encrypt(text: string | number | boolean): string {
if (text === null || text === undefined) return text as any;
try {
const stringValue = String(text);
const iv = crypto.randomBytes(this.ivLength);
const cipher = crypto.createCipheriv(this.algorithm, this.key, iv);
let encrypted = cipher.update(stringValue, 'utf8', 'hex');
encrypted += cipher.final('hex');
return `${iv.toString('hex')}:${encrypted}`;
} catch (error: any) {
// Fix TS18046: Cast error to any or Error to access .message
this.logger.error(`Encryption failed: ${error.message}`);
throw error;
}
}
decrypt(text: string): string {
const [ivHex, encryptedHex] = text.split(':');
if (!ivHex || !encryptedHex) return text;
if (!text || typeof text !== 'string' || !text.includes(':')) return text;
const iv = Buffer.from(ivHex, 'hex');
const decipher = crypto.createDecipheriv(this.algorithm, this.key, iv);
let decrypted = decipher.update(encryptedHex, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
try {
const [ivHex, encryptedHex] = text.split(':');
if (!ivHex || !encryptedHex) return text;
const iv = Buffer.from(ivHex, 'hex');
const decipher = crypto.createDecipheriv(this.algorithm, this.key, iv);
let decrypted = decipher.update(encryptedHex, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
} catch (error: any) {
// Fix TS18046: Cast error to any or Error to access .message
this.logger.warn(
`Decryption failed for value. Returning original text. Error: ${error.message}`,
);
// กรณี Decrypt ไม่ได้ ให้คืนค่าเดิมเพื่อป้องกัน App Crash
return text;
}
}
}

View File

@@ -0,0 +1,57 @@
// File: src/modules/correspondence/dto/create-routing-template.dto.ts
import {
IsString,
IsNotEmpty,
IsOptional,
IsInt,
IsArray,
ValidateNested,
IsEnum,
IsBoolean,
} from 'class-validator';
import { Type } from 'class-transformer';
export class CreateRoutingTemplateStepDto {
@IsInt()
@IsNotEmpty()
sequence!: number;
@IsInt()
@IsNotEmpty()
toOrganizationId!: number;
@IsInt()
@IsOptional()
roleId?: number;
@IsString()
@IsOptional()
stepPurpose?: string = 'FOR_REVIEW';
@IsInt()
@IsOptional()
expectedDays?: number;
}
export class CreateRoutingTemplateDto {
@IsString()
@IsNotEmpty()
templateName!: string;
@IsString()
@IsOptional()
description?: string;
@IsInt()
@IsOptional()
projectId?: number;
@IsBoolean()
@IsOptional()
isActive?: boolean;
@IsArray()
@ValidateNested({ each: true })
@Type(() => CreateRoutingTemplateStepDto)
steps!: CreateRoutingTemplateStepDto[];
}

View File

@@ -1,3 +1,4 @@
// File: src/modules/correspondence/entities/correspondence-revision.entity.ts
import {
Entity,
Column,
@@ -5,12 +6,16 @@ import {
ManyToOne,
JoinColumn,
CreateDateColumn,
Index,
} from 'typeorm';
import { Correspondence } from './correspondence.entity.js';
import { CorrespondenceStatus } from './correspondence-status.entity.js';
import { User } from '../../user/entities/user.entity.js';
@Entity('correspondence_revisions')
// ✅ เพิ่ม Index สำหรับ Virtual Columns เพื่อให้ Search เร็วขึ้น
@Index('idx_corr_rev_v_project', ['vRefProjectId'])
@Index('idx_corr_rev_v_type', ['vRefType'])
export class CorrespondenceRevision {
@PrimaryGeneratedColumn()
id!: number;
@@ -39,6 +44,27 @@ export class CorrespondenceRevision {
@Column({ type: 'json', nullable: true })
details?: any; // เก็บข้อมูลแบบ Dynamic ตาม Type
// ✅ [New] Virtual Column: ดึง Project ID จาก JSON details
@Column({
name: 'v_ref_project_id',
type: 'int',
generatedType: 'VIRTUAL',
asExpression: "JSON_UNQUOTE(JSON_EXTRACT(details, '$.projectId'))",
nullable: true,
})
vRefProjectId?: number;
// ✅ [New] Virtual Column: ดึง Document SubType จาก JSON details
@Column({
name: 'v_doc_subtype',
type: 'varchar',
length: 50,
generatedType: 'VIRTUAL',
asExpression: "JSON_UNQUOTE(JSON_EXTRACT(details, '$.subType'))",
nullable: true,
})
vRefType?: string;
// Dates
@Column({ name: 'document_date', type: 'date', nullable: true })
documentDate?: Date;

View File

@@ -1,3 +1,4 @@
// File: src/modules/correspondence/entities/correspondence-routing.entity.ts
import {
Entity,
Column,
@@ -9,7 +10,7 @@ import {
import { CorrespondenceRevision } from './correspondence-revision.entity.js';
import { Organization } from '../../project/entities/organization.entity.js';
import { User } from '../../user/entities/user.entity.js';
import { RoutingTemplate } from './routing-template.entity.js'; // <--- ✅ เพิ่ม Import นี้ครับ
import { RoutingTemplate } from './routing-template.entity.js';
@Entity('correspondence_routings')
export class CorrespondenceRouting {
@@ -49,6 +50,10 @@ export class CorrespondenceRouting {
@Column({ name: 'processed_at', type: 'datetime', nullable: true })
processedAt?: Date;
// ✅ [New] เพิ่ม State Context เพื่อเก็บ Snapshot ข้อมูล Workflow ณ จุดนั้น
@Column({ name: 'state_context', type: 'json', nullable: true })
stateContext?: any;
@CreateDateColumn({ name: 'created_at' })
createdAt!: Date;
@@ -57,7 +62,7 @@ export class CorrespondenceRouting {
@JoinColumn({ name: 'correspondence_id' })
correspondenceRevision?: CorrespondenceRevision;
@ManyToOne(() => RoutingTemplate) // ตอนนี้ TypeScript จะรู้จัก RoutingTemplate แล้ว
@ManyToOne(() => RoutingTemplate)
@JoinColumn({ name: 'template_id' })
template?: RoutingTemplate;

View File

@@ -1,12 +1,14 @@
// File: src/modules/correspondence/entities/routing-template-step.entity.ts
import {
Entity,
Column,
PrimaryGeneratedColumn,
Column,
ManyToOne,
JoinColumn,
} from 'typeorm';
import { RoutingTemplate } from './routing-template.entity.js';
import { Organization } from '../../project/entities/organization.entity.js';
import { Role } from '../../user/entities/role.entity.js';
@Entity('correspondence_routing_template_steps')
export class RoutingTemplateStep {
@@ -22,17 +24,27 @@ export class RoutingTemplateStep {
@Column({ name: 'to_organization_id' })
toOrganizationId!: number;
@Column({ name: 'role_id', nullable: true })
roleId?: number;
@Column({ name: 'step_purpose', default: 'FOR_REVIEW' })
stepPurpose!: string; // FOR_APPROVAL, FOR_REVIEW
stepPurpose!: string;
@Column({ name: 'expected_days', nullable: true })
expectedDays?: number;
@ManyToOne(() => RoutingTemplate, (t) => t.steps, { onDelete: 'CASCADE' })
// Relations
@ManyToOne(() => RoutingTemplate, (template) => template.steps, {
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'template_id' })
template?: RoutingTemplate;
@ManyToOne(() => Organization)
@JoinColumn({ name: 'to_organization_id' })
toOrganization?: Organization;
@ManyToOne(() => Role)
@JoinColumn({ name: 'role_id' })
role?: Role;
}

View File

@@ -1,3 +1,4 @@
// File: src/modules/json-schema/dto/create-json-schema.dto.ts
import {
IsString,
IsNotEmpty,
@@ -5,22 +6,65 @@ import {
IsOptional,
IsBoolean,
IsObject,
IsArray,
ValidateNested,
} from 'class-validator';
import { Type } from 'class-transformer';
export class VirtualColumnConfigDto {
@IsString()
@IsNotEmpty()
json_path!: string;
@IsString()
@IsNotEmpty()
column_name!: string;
@IsString()
@IsNotEmpty()
data_type!: 'INT' | 'VARCHAR' | 'BOOLEAN' | 'DATE' | 'DECIMAL' | 'DATETIME';
@IsString()
@IsOptional()
index_type?: 'INDEX' | 'UNIQUE' | 'FULLTEXT';
@IsBoolean()
@IsOptional()
is_required?: boolean;
}
export class CreateJsonSchemaDto {
@IsString()
@IsNotEmpty()
schemaCode!: string; // รหัส Schema (ต้องไม่ซ้ำ เช่น 'RFA_DWG_V1')
schemaCode!: string;
@IsString() // ✅ เพิ่ม Validation
@IsNotEmpty()
tableName!: string;
@IsInt()
@IsOptional()
version?: number; // เวอร์ชัน (Default: 1)
version?: number;
@IsObject()
@IsNotEmpty()
schemaDefinition!: Record<string, any>; // โครงสร้าง JSON Schema (Standard Format)
schemaDefinition!: Record<string, any>;
@IsObject()
@IsOptional()
uiSchema?: Record<string, any>;
@IsArray()
@IsOptional()
@ValidateNested({ each: true })
@Type(() => VirtualColumnConfigDto)
virtualColumns?: VirtualColumnConfigDto[];
@IsObject()
@IsOptional()
migrationScript?: Record<string, any>;
@IsBoolean()
@IsOptional()
isActive?: boolean; // สถานะการใช้งาน
isActive?: boolean;
}

View File

@@ -0,0 +1,19 @@
// File: src/modules/json-schema/dto/migrate-data.dto.ts
import { IsString, IsNotEmpty, IsInt, IsOptional } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class MigrateDataDto {
@ApiProperty({ description: 'The schema code to migrate to (e.g., RFA_DWG)' })
@IsString()
@IsNotEmpty()
targetSchemaCode!: string;
@ApiProperty({
description: 'Target version. If omitted, migrates to latest.',
required: false,
})
@IsInt()
@IsOptional()
targetVersion?: number;
}

View File

@@ -1,24 +1,47 @@
// File: src/modules/json-schema/entities/json-schema.entity.ts
import {
Entity,
Column,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
Index,
} from 'typeorm';
export interface VirtualColumnConfig {
json_path: string;
column_name: string;
data_type: 'INT' | 'VARCHAR' | 'BOOLEAN' | 'DATE' | 'DECIMAL' | 'DATETIME';
index_type?: 'INDEX' | 'UNIQUE' | 'FULLTEXT';
is_required: boolean;
}
@Entity('json_schemas')
@Index(['schemaCode', 'version'], { unique: true })
export class JsonSchema {
@PrimaryGeneratedColumn()
id!: number;
@Column({ name: 'schema_code', unique: true, length: 100 })
schemaCode!: string; // เช่น 'RFA_DWG_V1'
@Column({ name: 'schema_code', length: 100 })
schemaCode!: string;
@Column({ default: 1 })
version!: number;
@Column({ name: 'table_name', length: 100, nullable: false }) // ✅ เพิ่ม: ระบุตารางเป้าหมาย
tableName!: string;
@Column({ name: 'schema_definition', type: 'json' })
schemaDefinition!: any; // เก็บ JSON Schema มาตรฐาน (Draft 7/2019-09)
schemaDefinition!: any;
@Column({ name: 'ui_schema', type: 'json', nullable: true })
uiSchema?: any;
@Column({ name: 'virtual_columns', type: 'json', nullable: true })
virtualColumns?: VirtualColumnConfig[];
@Column({ name: 'migration_script', type: 'json', nullable: true })
migrationScript?: any;
@Column({ name: 'is_active', default: true })
isActive!: boolean;

View File

@@ -0,0 +1,84 @@
// File: src/modules/json-schema/interfaces/ui-schema.interface.ts
export type WidgetType =
| 'text'
| 'textarea'
| 'number'
| 'select'
| 'radio'
| 'checkbox'
| 'date'
| 'datetime'
| 'file-upload'
| 'document-ref'; // Custom widget สำหรับอ้างอิงเอกสารอื่น
export type Operator =
| 'equals'
| 'notEquals'
| 'contains'
| 'greaterThan'
| 'lessThan'
| 'in';
export interface FieldCondition {
field: string;
operator: Operator;
value: any;
}
export interface FieldDependency {
condition: FieldCondition;
actions: {
visibility?: boolean; // true = show, false = hide
required?: boolean;
disabled?: boolean;
filterOptions?: Record<string, any>; // เช่น กรอง Dropdown ตามค่าที่เลือก
};
}
export interface UiSchemaField {
type: 'string' | 'number' | 'boolean' | 'array' | 'object';
widget?: WidgetType;
title: string;
description?: string;
placeholder?: string;
enum?: any[]; // กรณีเป็น static options
enumNames?: string[]; // label สำหรับ options
dataSource?: string; // กรณีดึง options จาก API (เช่น 'master-data/disciplines')
defaultValue?: any;
readOnly?: boolean;
hidden?: boolean;
// Validation & Rules
required?: boolean;
dependencies?: FieldDependency[];
// For Nested Structures
properties?: { [key: string]: UiSchemaField };
items?: UiSchemaField; // For arrays
// UI Grid Layout (Tailwind classes equivalent)
colSpan?: number; // 1-12
}
export interface LayoutGroup {
id: string;
title: string;
description?: string;
type: 'group' | 'section';
fields: string[]; // Field keys ที่จะอยู่ในกลุ่มนี้
}
export interface LayoutConfig {
type: 'stack' | 'grid' | 'tabs' | 'steps' | 'wizard';
groups: LayoutGroup[];
options?: Record<string, any>; // Config เพิ่มเติมเฉพาะ Layout type
}
export interface UiSchema {
layout: LayoutConfig;
fields: {
[key: string]: UiSchemaField;
};
}

View File

@@ -0,0 +1,34 @@
// File: src/modules/json-schema/interfaces/validation-result.interface.ts
export interface ValidationOptions {
/**
* ลบ field ที่ไม่ได้ระบุใน Schema ออกอัตโนมัติหรือไม่
* Default: true
*/
removeAdditional?: boolean;
/**
* แปลงชนิดข้อมูลอัตโนมัติถ้าเป็นไปได้ (เช่น "123" -> 123)
* Default: true
*/
coerceTypes?: boolean;
/**
* ใช้ค่า Default จาก Schema ถ้าข้อมูลไม่ถูกส่งมา
* Default: true
*/
useDefaults?: boolean;
}
export interface ValidationErrorDetail {
field: string;
message: string;
value?: any;
}
export interface ValidationResult {
isValid: boolean;
errors: ValidationErrorDetail[];
sanitizedData: any;
}

View File

@@ -1,36 +1,172 @@
import { Controller, Post, Body, Param, UseGuards } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
// File: src/modules/json-schema/json-schema.controller.ts
import {
Body,
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Param,
ParseIntPipe,
Patch,
Post,
Query,
UseGuards,
} from '@nestjs/common';
import {
ApiBearerAuth,
ApiOperation,
ApiParam,
ApiResponse,
ApiTags,
} from '@nestjs/swagger';
import { JsonSchemaService } from './json-schema.service';
// ✅ FIX: Import DTO
import { SchemaMigrationService } from './services/schema-migration.service';
import { CreateJsonSchemaDto } from './dto/create-json-schema.dto';
// ✅ FIX: แก้ไข Path ของ Guards
import { MigrateDataDto } from './dto/migrate-data.dto';
import { SearchJsonSchemaDto } from './dto/search-json-schema.dto';
import { UpdateJsonSchemaDto } from './dto/update-json-schema.dto';
import { CurrentUser } from '../../common/decorators/current-user.decorator';
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { RbacGuard } from '../../common/guards/rbac.guard';
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
import { User } from '../user/entities/user.entity';
@ApiTags('JSON Schemas') // ✅ Add Swagger Tag
@ApiTags('JSON Schemas Management')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard, RbacGuard)
@Controller('json-schemas')
export class JsonSchemaController {
constructor(private readonly schemaService: JsonSchemaService) {}
constructor(
private readonly jsonSchemaService: JsonSchemaService,
private readonly migrationService: SchemaMigrationService,
) {}
// ----------------------------------------------------------------------
// Schema Management (CRUD)
// ----------------------------------------------------------------------
@Post()
@ApiOperation({ summary: 'Create or Update JSON Schema' })
@ApiOperation({
summary: 'Create a new schema or new version of existing schema',
})
@ApiResponse({
status: 201,
description: 'The schema has been successfully created.',
})
@RequirePermission('system.manage_all') // Admin Only
create(@Body() createDto: CreateJsonSchemaDto) {
return this.schemaService.createOrUpdate(
createDto.schemaCode,
createDto.schemaDefinition,
);
return this.jsonSchemaService.create(createDto);
}
@Post(':code/validate')
@ApiOperation({ summary: 'Test validation against a schema' })
@Get()
@ApiOperation({ summary: 'List all schemas with pagination and filtering' })
@RequirePermission('document.view') // Viewer+ can see schemas
findAll(@Query() searchDto: SearchJsonSchemaDto) {
return this.jsonSchemaService.findAll(searchDto);
}
@Get(':id')
@ApiOperation({ summary: 'Get a specific schema version by ID' })
@RequirePermission('document.view')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.jsonSchemaService.findOne(id);
}
@Get('latest/:code')
@ApiOperation({
summary: 'Get the latest active version of a schema by code',
})
@ApiParam({ name: 'code', description: 'Schema Code (e.g., RFA_DWG)' })
@RequirePermission('document.view')
findLatest(@Param('code') code: string) {
return this.jsonSchemaService.findLatestByCode(code);
}
@Patch(':id')
@ApiOperation({
summary: 'Update a specific schema (Not recommended for active schemas)',
})
@RequirePermission('system.manage_all')
update(
@Param('id', ParseIntPipe) id: number,
@Body() updateDto: UpdateJsonSchemaDto,
) {
return this.jsonSchemaService.update(id, updateDto);
}
@Delete(':id')
@ApiOperation({ summary: 'Delete a schema version (Hard Delete)' })
@RequirePermission('system.manage_all')
remove(@Param('id', ParseIntPipe) id: number) {
return this.jsonSchemaService.remove(id);
}
// ----------------------------------------------------------------------
// Validation & Security
// ----------------------------------------------------------------------
@Post('validate/:code')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Validate data against the latest schema version' })
@ApiResponse({
status: 200,
description: 'Validation result including errors and sanitized data',
})
@RequirePermission('document.view')
async validate(@Param('code') code: string, @Body() data: any) {
const isValid = await this.schemaService.validate(code, data);
return { valid: isValid, message: 'Validation passed' };
// Note: Validation API นี้ใช้สำหรับ Test หรือ Pre-check เท่านั้น
// การ Save จริงจะเรียกผ่าน Service ภายใน
return this.jsonSchemaService.validateData(code, data);
}
@Post('read/:code')
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'Process read data (Decrypt & Filter) based on user roles',
})
@RequirePermission('document.view')
async processReadData(
@Param('code') code: string,
@Body() data: any,
@CurrentUser() user: User,
) {
// แปลง User Entity เป็น Security Context
// ใช้ as any เพื่อ bypass type checking ชั่วคราว เนื่องจาก roles มักจะถูก inject เข้ามาใน request.user
// โดย Strategy หรือ Guard แม้จะไม่มีใน Entity หลัก
const userWithRoles = user as any;
const userRoles = userWithRoles.roles
? userWithRoles.roles.map((r: any) => r.roleName || r) // รองรับทั้ง Object Role และ String Role
: [];
return this.jsonSchemaService.processReadData(code, data, { userRoles });
}
// ----------------------------------------------------------------------
// Data Migration
// ----------------------------------------------------------------------
@Post('migrate/:table/:id')
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'Migrate specific entity data to target schema version',
})
@ApiParam({ name: 'table', description: 'Table Name (e.g. rfa_revisions)' })
@ApiParam({ name: 'id', description: 'Entity ID' })
@RequirePermission('system.manage_all') // Dangerous Op -> Admin Only
async migrateData(
@Param('table') tableName: string,
@Param('id', ParseIntPipe) id: number,
@Body() dto: MigrateDataDto,
) {
return this.migrationService.migrateData(
tableName,
id,
dto.targetSchemaCode,
dto.targetVersion,
);
}
}

View File

@@ -1,14 +1,37 @@
// File: src/modules/json-schema/json-schema.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { JsonSchemaService } from './json-schema.service';
import { JsonSchemaController } from './json-schema.controller';
import { JsonSchema } from './entities/json-schema.entity';
import { UserModule } from '../user/user.module';
import { JsonSchemaController } from './json-schema.controller';
import { JsonSchemaService } from './json-schema.service';
import { JsonSecurityService } from './services/json-security.service';
import { SchemaMigrationService } from './services/schema-migration.service';
import { UiSchemaService } from './services/ui-schema.service';
import { VirtualColumnService } from './services/virtual-column.service';
// Fix TS2307: Correct path to CryptoService
import { CryptoService } from '../../common/services/crypto.service';
// Import Module อื่นๆ ที่จำเป็นสำหรับ Guard (ถ้า Guards อยู่ใน Common อาจจะไม่ต้อง import ที่นี่โดยตรง)
// import { UserModule } from '../user/user.module';
@Module({
imports: [TypeOrmModule.forFeature([JsonSchema]), UserModule],
imports: [
TypeOrmModule.forFeature([JsonSchema]),
ConfigModule,
// UserModule,
],
controllers: [JsonSchemaController],
providers: [JsonSchemaService],
exports: [JsonSchemaService],
providers: [
JsonSchemaService,
VirtualColumnService,
UiSchemaService,
SchemaMigrationService,
JsonSecurityService,
CryptoService,
],
exports: [JsonSchemaService, SchemaMigrationService, JsonSecurityService],
})
export class JsonSchemaModule {}

View File

@@ -1,18 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { JsonSchemaService } from './json-schema.service';
describe('JsonSchemaService', () => {
let service: JsonSchemaService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [JsonSchemaService],
}).compile();
service = module.get<JsonSchemaService>(JsonSchemaService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@@ -1,107 +1,172 @@
// File: src/modules/json-schema/json-schema.controller.ts
import {
Injectable,
OnModuleInit,
BadRequestException,
NotFoundException,
Logger,
Body,
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Param,
ParseIntPipe,
Patch,
Post,
Query,
UseGuards,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import { JsonSchema } from './entities/json-schema.entity'; // ลบ .js
import {
ApiBearerAuth,
ApiOperation,
ApiParam,
ApiResponse,
ApiTags,
} from '@nestjs/swagger';
@Injectable()
export class JsonSchemaService implements OnModuleInit {
private ajv: Ajv;
private validators = new Map<string, any>();
private readonly logger = new Logger(JsonSchemaService.name);
import { JsonSchemaService } from './json-schema.service';
import { SchemaMigrationService } from './services/schema-migration.service';
import { CreateJsonSchemaDto } from './dto/create-json-schema.dto';
import { MigrateDataDto } from './dto/migrate-data.dto';
import { SearchJsonSchemaDto } from './dto/search-json-schema.dto';
import { UpdateJsonSchemaDto } from './dto/update-json-schema.dto';
import { CurrentUser } from '../../common/decorators/current-user.decorator';
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { RbacGuard } from '../../common/guards/rbac.guard';
import { User } from '../user/entities/user.entity';
@ApiTags('JSON Schemas Management')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard, RbacGuard)
@Controller('json-schemas')
export class JsonSchemaController {
constructor(
@InjectRepository(JsonSchema)
private schemaRepo: Repository<JsonSchema>,
private readonly jsonSchemaService: JsonSchemaService,
private readonly migrationService: SchemaMigrationService,
) {}
// ----------------------------------------------------------------------
// Schema Management (CRUD)
// ----------------------------------------------------------------------
@Post()
@ApiOperation({
summary: 'Create a new schema or new version of existing schema',
})
@ApiResponse({
status: 201,
description: 'The schema has been successfully created.',
})
@RequirePermission('system.manage_all') // Admin Only
create(@Body() createDto: CreateJsonSchemaDto) {
return this.jsonSchemaService.create(createDto);
}
@Get()
@ApiOperation({ summary: 'List all schemas with pagination and filtering' })
@RequirePermission('document.view') // Viewer+ can see schemas
findAll(@Query() searchDto: SearchJsonSchemaDto) {
return this.jsonSchemaService.findAll(searchDto);
}
@Get(':id')
@ApiOperation({ summary: 'Get a specific schema version by ID' })
@RequirePermission('document.view')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.jsonSchemaService.findOne(id);
}
@Get('latest/:code')
@ApiOperation({
summary: 'Get the latest active version of a schema by code',
})
@ApiParam({ name: 'code', description: 'Schema Code (e.g., RFA_DWG)' })
@RequirePermission('document.view')
findLatest(@Param('code') code: string) {
return this.jsonSchemaService.findLatestByCode(code);
}
@Patch(':id')
@ApiOperation({
summary: 'Update a specific schema (Not recommended for active schemas)',
})
@RequirePermission('system.manage_all')
update(
@Param('id', ParseIntPipe) id: number,
@Body() updateDto: UpdateJsonSchemaDto,
) {
this.ajv = new Ajv({ allErrors: true, strict: false });
addFormats(this.ajv);
return this.jsonSchemaService.update(id, updateDto);
}
async onModuleInit() {
// Pre-load schemas (Optional for performance)
// const schemas = await this.schemaRepo.find({ where: { isActive: true } });
// schemas.forEach(s => this.createValidator(s.schemaCode, s.schemaDefinition));
@Delete(':id')
@ApiOperation({ summary: 'Delete a schema version (Hard Delete)' })
@RequirePermission('system.manage_all')
remove(@Param('id', ParseIntPipe) id: number) {
return this.jsonSchemaService.remove(id);
}
/**
* ตรวจสอบข้อมูล JSON ว่าถูกต้องตาม Schema หรือไม่
*/
async validate(schemaCode: string, data: any): Promise<boolean> {
let validate = this.validators.get(schemaCode);
// ----------------------------------------------------------------------
// Validation & Security
// ----------------------------------------------------------------------
if (!validate) {
const schema = await this.schemaRepo.findOne({
where: { schemaCode, isActive: true },
});
if (!schema) {
throw new NotFoundException(`JSON Schema '${schemaCode}' not found`);
}
try {
validate = this.ajv.compile(schema.schemaDefinition);
this.validators.set(schemaCode, validate);
} catch (error: any) {
throw new BadRequestException(
`Invalid Schema Definition for '${schemaCode}': ${error.message}`,
);
}
}
const valid = validate(data);
if (!valid) {
const errors = validate.errors
?.map((e: any) => `${e.instancePath} ${e.message}`)
.join(', ');
// โยน Error กลับไปเพื่อให้ Controller/Service ปลายทางจัดการ
throw new BadRequestException(`JSON Validation Failed: ${errors}`);
}
return true;
@Post('validate/:code')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Validate data against the latest schema version' })
@ApiResponse({
status: 200,
description: 'Validation result including errors and sanitized data',
})
@RequirePermission('document.view')
async validate(@Param('code') code: string, @Body() data: any) {
// Note: Validation API นี้ใช้สำหรับ Test หรือ Pre-check เท่านั้น
// การ Save จริงจะเรียกผ่าน Service ภายใน
return this.jsonSchemaService.validateData(code, data);
}
/**
* สร้างหรืออัปเดต Schema
*/
async createOrUpdate(schemaCode: string, definition: Record<string, any>) {
// 1. ตรวจสอบว่า Definition เป็น JSON Schema ที่ถูกต้องไหม
try {
this.ajv.compile(definition);
} catch (error: any) {
throw new BadRequestException(
`Invalid JSON Schema format: ${error.message}`,
);
}
@Post('read/:code')
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'Process read data (Decrypt & Filter) based on user roles',
})
@RequirePermission('document.view')
async processReadData(
@Param('code') code: string,
@Body() data: any,
@CurrentUser() user: User,
) {
// แปลง User Entity เป็น Security Context
// แก้ไข TS2339 & TS7006: Type Casting เพื่อให้เข้าถึง roles ได้โดยไม่ error
// เนื่องจาก User Entity ปกติไม่มี property roles (แต่อาจถูก Inject มาตอน Runtime หรือผ่าน Assignments)
const userWithRoles = user as any;
const userRoles = userWithRoles.roles
? userWithRoles.roles.map((r: any) => r.roleName)
: [];
// 2. บันทึกลง DB
let schema = await this.schemaRepo.findOne({ where: { schemaCode } });
return this.jsonSchemaService.processReadData(code, data, { userRoles });
}
if (schema) {
schema.schemaDefinition = definition;
schema.version += 1;
} else {
schema = this.schemaRepo.create({
schemaCode,
schemaDefinition: definition,
version: 1,
});
}
// ----------------------------------------------------------------------
// Data Migration
// ----------------------------------------------------------------------
const savedSchema = await this.schemaRepo.save(schema);
// 3. Clear Cache เพื่อให้ครั้งหน้าโหลดตัวใหม่
this.validators.delete(schemaCode);
this.logger.log(`Schema '${schemaCode}' updated (v${savedSchema.version})`);
return savedSchema;
@Post('migrate/:table/:id')
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'Migrate specific entity data to target schema version',
})
@ApiParam({ name: 'table', description: 'Table Name (e.g. rfa_revisions)' })
@ApiParam({ name: 'id', description: 'Entity ID' })
@RequirePermission('system.manage_all') // Dangerous Op -> Admin Only
async migrateData(
@Param('table') tableName: string,
@Param('id', ParseIntPipe) id: number,
@Body() dto: MigrateDataDto,
) {
return this.migrationService.migrateData(
tableName,
id,
dto.targetSchemaCode,
dto.targetVersion,
);
}
}

View File

@@ -0,0 +1,113 @@
// File: src/modules/json-schema/services/json-security.service.ts
import { Injectable } from '@nestjs/common';
import { CryptoService } from '../../../common/services/crypto.service';
export interface SecurityContext {
userRoles: string[]; // Role ของ user ปัจจุบัน (เช่น ['EDITOR', 'viewer'])
}
@Injectable()
export class JsonSecurityService {
constructor(private readonly cryptoService: CryptoService) {}
/**
* ขาเข้า (Write): เข้ารหัสข้อมูล Sensitive ก่อนบันทึก
*/
encryptFields(data: any, schemaDefinition: any): any {
if (!data || typeof data !== 'object') return data;
const processed = Array.isArray(data) ? [...data] : { ...data };
// Traverse schema properties
if (schemaDefinition.properties) {
for (const [key, propSchema] of Object.entries<any>(
schemaDefinition.properties,
)) {
if (data[key] !== undefined) {
// 1. Check encryption flag
if (propSchema['x-encrypt'] === true) {
processed[key] = this.cryptoService.encrypt(data[key]);
}
// 2. Recursive for nested objects/arrays
if (propSchema.type === 'object' && propSchema.properties) {
processed[key] = this.encryptFields(data[key], propSchema);
} else if (propSchema.type === 'array' && propSchema.items) {
if (Array.isArray(data[key])) {
processed[key] = data[key].map((item: any) =>
this.encryptFields(item, propSchema.items),
);
}
}
}
}
}
return processed;
}
/**
* ขาออก (Read): ถอดรหัส และ กรองข้อมูลตามสิทธิ์
*/
decryptAndFilterFields(
data: any,
schemaDefinition: any,
context: SecurityContext,
): any {
if (!data || typeof data !== 'object') return data;
// Clone data to avoid mutation
const processed = Array.isArray(data) ? [...data] : { ...data };
if (schemaDefinition.properties) {
for (const [key, propSchema] of Object.entries<any>(
schemaDefinition.properties,
)) {
if (data[key] !== undefined) {
// 1. Decrypt (ถ้ามีค่าและถูกเข้ารหัสไว้)
if (propSchema['x-encrypt'] === true) {
processed[key] = this.cryptoService.decrypt(data[key]);
}
// 2. Security Check (Role-based Access Control)
if (propSchema['x-security']) {
const rule = propSchema['x-security'];
const requiredRoles = rule.roles || [];
const hasPermission = requiredRoles.some(
(role: string) =>
context.userRoles.includes(role) ||
context.userRoles.includes('SUPERADMIN'),
);
if (!hasPermission) {
if (rule.onDeny === 'REMOVE') {
delete processed[key];
continue; // ข้ามไป field ถัดไป
} else {
// Default: MASK
processed[key] = '********';
}
}
}
// 3. Recursive for nested objects/arrays
// (ทำต่อเมื่อ Field ยังไม่ถูกลบ)
if (processed[key] !== undefined) {
if (propSchema.type === 'object' && propSchema.properties) {
processed[key] = this.decryptAndFilterFields(
processed[key],
propSchema,
context,
);
} else if (propSchema.type === 'array' && propSchema.items) {
if (Array.isArray(processed[key])) {
processed[key] = processed[key].map((item: any) =>
this.decryptAndFilterFields(item, propSchema.items, context),
);
}
}
}
}
}
}
return processed;
}
}

View File

@@ -0,0 +1,205 @@
// File: src/modules/json-schema/services/schema-migration.service.ts
import { Injectable, Logger, BadRequestException } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { JsonSchemaService } from '../json-schema.service';
export interface MigrationStep {
type:
| 'FIELD_RENAME'
| 'FIELD_TRANSFORM'
| 'FIELD_ADD'
| 'FIELD_REMOVE'
| 'STRUCTURE_CHANGE';
config: any;
}
export interface MigrationResult {
success: boolean;
fromVersion: number;
toVersion: number;
migratedFields: string[];
error?: string;
}
@Injectable()
export class SchemaMigrationService {
private readonly logger = new Logger(SchemaMigrationService.name);
constructor(
private readonly dataSource: DataSource,
private readonly jsonSchemaService: JsonSchemaService,
) {}
/**
* Migrate data for a specific entity to a target schema version
*/
async migrateData(
entityType: string, // e.g., 'rfa_revisions', 'correspondence_revisions'
entityId: number,
targetSchemaCode: string,
targetVersion?: number,
): Promise<MigrationResult> {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
// 1. Get Target Schema
let targetSchema;
if (targetVersion) {
targetSchema = await this.jsonSchemaService.findOneByCodeAndVersion(
targetSchemaCode,
targetVersion,
);
} else {
targetSchema =
await this.jsonSchemaService.findLatestByCode(targetSchemaCode);
}
// 2. Fetch Entity Data & Current Version
// Note: This assumes the entity table has 'details' (json) and 'schema_version' (int) columns
// If schema_version is not present, we assume version 1
const entity = await queryRunner.manager.query(
`SELECT details, schema_version FROM ${entityType} WHERE id = ?`,
[entityId],
);
if (!entity || entity.length === 0) {
throw new BadRequestException(
`Entity ${entityType} with ID ${entityId} not found.`,
);
}
const currentData = entity[0].details || {};
const currentVersion = entity[0].schema_version || 1;
if (currentVersion >= targetSchema.version) {
return {
success: true,
fromVersion: currentVersion,
toVersion: currentVersion,
migratedFields: [], // No migration needed
};
}
// 3. Find Migration Path (Iterative Upgrade)
let migratedData = JSON.parse(JSON.stringify(currentData));
const migratedFields: string[] = [];
// Loop from current version up to target version
for (let v = currentVersion + 1; v <= targetSchema.version; v++) {
const schemaVer = await this.jsonSchemaService.findOneByCodeAndVersion(
targetSchemaCode,
v,
);
if (schemaVer && schemaVer.migrationScript) {
this.logger.log(
`Applying migration script for ${targetSchemaCode} v${v}...`,
);
const script = schemaVer.migrationScript;
// Apply steps defined in migrationScript
if (Array.isArray(script.steps)) {
for (const step of script.steps) {
migratedData = await this.applyMigrationStep(step, migratedData);
if (step.config.field || step.config.new_field) {
migratedFields.push(step.config.new_field || step.config.field);
}
}
}
}
}
// 4. Validate Migrated Data against Target Schema
const validation = await this.jsonSchemaService.validateData(
targetSchema.schemaCode,
migratedData,
);
if (!validation.isValid) {
throw new BadRequestException(
`Migration failed: Resulting data does not match target schema v${targetSchema.version}. Errors: ${JSON.stringify(validation.errors)}`,
);
}
// 5. Save Migrated Data
// Update details AND schema_version
await queryRunner.manager.query(
`UPDATE ${entityType} SET details = ?, schema_version = ? WHERE id = ?`,
[
JSON.stringify(validation.sanitizedData),
targetSchema.version,
entityId,
],
);
await queryRunner.commitTransaction();
return {
success: true,
fromVersion: currentVersion,
toVersion: targetSchema.version,
migratedFields: [...new Set(migratedFields)],
};
} catch (err: any) {
await queryRunner.rollbackTransaction();
this.logger.error(`Migration failed: ${err.message}`, err.stack);
throw err;
} finally {
await queryRunner.release();
}
}
/**
* Apply a single migration step
*/
private async applyMigrationStep(
step: MigrationStep,
data: any,
): Promise<any> {
const newData = { ...data };
switch (step.type) {
case 'FIELD_RENAME':
if (newData[step.config.old_field] !== undefined) {
newData[step.config.new_field] = newData[step.config.old_field];
delete newData[step.config.old_field];
}
break;
case 'FIELD_ADD':
if (newData[step.config.field] === undefined) {
newData[step.config.field] = step.config.default_value;
}
break;
case 'FIELD_REMOVE':
delete newData[step.config.field];
break;
case 'FIELD_TRANSFORM':
if (newData[step.config.field] !== undefined) {
// Simple transform logic (e.g., map values)
if (step.config.transform === 'MAP_VALUES' && step.config.mapping) {
const oldVal = newData[step.config.field];
newData[step.config.field] = step.config.mapping[oldVal] || oldVal;
}
// Type casting
else if (step.config.transform === 'TO_NUMBER') {
newData[step.config.field] = Number(newData[step.config.field]);
} else if (step.config.transform === 'TO_STRING') {
newData[step.config.field] = String(newData[step.config.field]);
}
}
break;
default:
this.logger.warn(`Unknown migration step type: ${step.type}`);
}
return newData;
}
}

View File

@@ -0,0 +1,115 @@
// File: src/modules/json-schema/services/ui-schema.service.ts
import { Injectable, BadRequestException, Logger } from '@nestjs/common';
import { UiSchema, UiSchemaField } from '../interfaces/ui-schema.interface';
@Injectable()
export class UiSchemaService {
private readonly logger = new Logger(UiSchemaService.name);
/**
* ตรวจสอบความถูกต้องของ UI Schema
*/
validateUiSchema(uiSchema: UiSchema, dataSchema: any): boolean {
if (!uiSchema) return true; // Optional field
// 1. Validate Structure เบื้องต้น
if (!uiSchema.layout || !uiSchema.fields) {
throw new BadRequestException(
'UI Schema must contain "layout" and "fields" properties.',
);
}
// 2. ตรวจสอบว่า Fields ใน Layout มีคำนิยามครบถ้วน
const definedFields = new Set(Object.keys(uiSchema.fields));
const layoutFields = new Set<string>();
uiSchema.layout.groups.forEach((group) => {
group.fields.forEach((fieldKey) => {
layoutFields.add(fieldKey);
if (!definedFields.has(fieldKey)) {
throw new BadRequestException(
`Field "${fieldKey}" used in layout "${group.title}" is not defined in "fields".`,
);
}
});
});
// 3. (Optional) ตรวจสอบว่า Fields ใน Data Schema (AJV) มีครบใน UI Schema หรือไม่
// ถ้า Data Schema บอกว่ามี field 'title' แต่ UI Schema ไม่มี -> Frontend อาจจะไม่เรนเดอร์
if (dataSchema && dataSchema.properties) {
const dataKeys = Object.keys(dataSchema.properties);
const missingFields = dataKeys.filter((key) => !definedFields.has(key));
if (missingFields.length > 0) {
this.logger.warn(
`Data schema properties [${missingFields.join(', ')}] are missing from UI Schema.`,
);
// ไม่ Throw Error เพราะบางทีเราอาจตั้งใจซ่อน Field (Hidden field)
}
}
return true;
}
/**
* สร้าง UI Schema พื้นฐานจาก Data Schema (AJV) อัตโนมัติ
* ใช้กรณี user ไม่ได้ส่ง UI Schema มาให้
*/
generateDefaultUiSchema(dataSchema: any): UiSchema {
if (!dataSchema || !dataSchema.properties) {
return {
layout: { type: 'stack', groups: [] },
fields: {},
};
}
const fields: { [key: string]: UiSchemaField } = {};
const groupFields: string[] = [];
for (const [key, value] of Object.entries<any>(dataSchema.properties)) {
groupFields.push(key);
fields[key] = {
type: value.type || 'string',
title: value.title || this.humanize(key),
description: value.description,
required: (dataSchema.required || []).includes(key),
widget: this.guessWidget(value),
colSpan: 12, // Default full width
};
}
return {
layout: {
type: 'stack',
groups: [
{
id: 'default',
title: 'General Information',
type: 'section',
fields: groupFields,
},
],
},
fields,
};
}
// Helpers
private humanize(str: string): string {
return str
.replace(/([A-Z])/g, ' $1')
.replace(/^./, (str) => str.toUpperCase())
.trim();
}
private guessWidget(schemaProp: any): any {
if (schemaProp.enum) return 'select';
if (schemaProp.type === 'boolean') return 'checkbox';
if (schemaProp.format === 'date') return 'date';
if (schemaProp.format === 'date-time') return 'datetime';
if (schemaProp.format === 'binary') return 'file-upload';
return 'text';
}
}

View File

@@ -0,0 +1,152 @@
// File: src/modules/json-schema/services/virtual-column.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { DataSource, QueryRunner } from 'typeorm';
import { VirtualColumnConfig } from '../entities/json-schema.entity';
@Injectable()
export class VirtualColumnService {
private readonly logger = new Logger(VirtualColumnService.name);
constructor(private readonly dataSource: DataSource) {}
/**
* สร้าง/อัปเดต Virtual Columns และ Index บน Database จริง
*/
async setupVirtualColumns(tableName: string, configs: VirtualColumnConfig[]) {
if (!configs || configs.length === 0) return;
// ใช้ QueryRunner เพื่อให้จัดการ Transaction หรือ Connection ได้ละเอียด
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
try {
this.logger.log(
`Start setting up virtual columns for table '${tableName}'...`,
);
// 1. ตรวจสอบว่าตารางมีอยู่จริงไหม
const tableExists = await queryRunner.hasTable(tableName);
if (!tableExists) {
this.logger.warn(
`Table '${tableName}' not found. Skipping virtual columns.`,
);
return;
}
for (const config of configs) {
await this.ensureVirtualColumn(queryRunner, tableName, config);
if (config.index_type) {
await this.ensureIndex(queryRunner, tableName, config);
}
}
this.logger.log(
`Finished setting up virtual columns for '${tableName}'.`,
);
} catch (err: any) {
this.logger.error(
`Failed to setup virtual columns: ${err.message}`,
err.stack,
);
throw err;
} finally {
await queryRunner.release();
}
}
/**
* สร้าง Column ถ้ายังไม่มี
*/
private async ensureVirtualColumn(
queryRunner: QueryRunner,
tableName: string,
config: VirtualColumnConfig,
) {
const hasColumn = await queryRunner.hasColumn(
tableName,
config.column_name,
);
if (!hasColumn) {
const sql = this.generateAddColumnSql(tableName, config);
this.logger.log(`Executing: ${sql}`);
await queryRunner.query(sql);
} else {
// TODO: (Advance) ถ้ามี Column แล้ว แต่ Definition เปลี่ยน อาจต้อง ALTER MODIFY
this.logger.debug(
`Column '${config.column_name}' already exists in '${tableName}'.`,
);
}
}
/**
* สร้าง Index ถ้ายังไม่มี
*/
private async ensureIndex(
queryRunner: QueryRunner,
tableName: string,
config: VirtualColumnConfig,
) {
const indexName = `idx_${tableName}_${config.column_name}`;
// ตรวจสอบว่า Index มีอยู่จริงไหม (Query จาก information_schema เพื่อความชัวร์)
const checkIndexSql = `
SELECT COUNT(1) as count
FROM information_schema.STATISTICS
WHERE table_schema = DATABASE()
AND table_name = ?
AND index_name = ?
`;
const result = await queryRunner.query(checkIndexSql, [
tableName,
indexName,
]);
if (result[0].count == 0) {
const sql = `CREATE ${config.index_type === 'UNIQUE' ? 'UNIQUE' : ''} INDEX ${indexName} ON ${tableName} (${config.column_name})`;
this.logger.log(`Creating Index: ${sql}`);
await queryRunner.query(sql);
}
}
/**
* Generate SQL สำหรับ MariaDB 10.11 Virtual Column
* Syntax: ADD COLUMN name type GENERATED ALWAYS AS (expr) VIRTUAL
*/
private generateAddColumnSql(
tableName: string,
config: VirtualColumnConfig,
): string {
const dbType = this.mapDataTypeToSql(config.data_type);
// JSON_UNQUOTE(JSON_EXTRACT(details, '$.path'))
// ใช้ 'details' เป็นชื่อ column JSON หลัก (ต้องตรงกับ Database Schema ที่ออกแบบไว้)
const expression = `JSON_UNQUOTE(JSON_EXTRACT(details, '${config.json_path}'))`;
// Handle Type Casting inside expression if needed,
// but usually MariaDB handles string return from JSON_EXTRACT.
// For INT/DATE, virtual column type definition enforces it.
return `ALTER TABLE ${tableName} ADD COLUMN ${config.column_name} ${dbType} GENERATED ALWAYS AS (${expression}) VIRTUAL`;
}
private mapDataTypeToSql(type: string): string {
switch (type) {
case 'INT':
return 'INT';
case 'VARCHAR':
return 'VARCHAR(255)';
case 'BOOLEAN':
return 'TINYINT(1)';
case 'DATE':
return 'DATE';
case 'DATETIME':
return 'DATETIME';
case 'DECIMAL':
return 'DECIMAL(10,2)';
default:
return 'VARCHAR(255)';
}
}
}

View File

@@ -0,0 +1,52 @@
// File: src/modules/rfa/dto/create-rfa-revision.dto.ts
import {
IsString,
IsNotEmpty,
IsInt,
IsOptional,
IsDateString,
IsObject,
IsArray,
} from 'class-validator';
export class CreateRfaRevisionDto {
@IsString()
@IsNotEmpty()
title!: string;
@IsInt()
@IsNotEmpty()
rfaStatusCodeId!: number;
@IsInt()
@IsOptional()
rfaApproveCodeId?: number;
@IsDateString()
@IsOptional()
documentDate?: string;
@IsDateString()
@IsOptional()
issuedDate?: string;
@IsDateString()
@IsOptional()
receivedDate?: string;
@IsDateString()
@IsOptional()
approvedDate?: string;
@IsString()
@IsOptional()
description?: string;
@IsObject()
@IsOptional()
details?: Record<string, any>;
@IsArray()
@IsOptional()
shopDrawingRevisionIds?: number[]; // IDs of linked Shop Drawings
}

View File

@@ -0,0 +1,37 @@
// File: src/modules/rfa/dto/create-rfa-workflow.dto.ts
import {
IsInt,
IsNotEmpty,
IsOptional,
IsEnum,
IsString,
} from 'class-validator';
export enum RfaActionType {
REVIEW = 'REVIEW',
APPROVE = 'APPROVE',
ACKNOWLEDGE = 'ACKNOWLEDGE',
}
export class CreateRfaWorkflowDto {
@IsInt()
@IsNotEmpty()
stepNumber!: number;
@IsInt()
@IsNotEmpty()
organizationId!: number;
@IsInt()
@IsOptional()
assignedTo?: number;
@IsEnum(RfaActionType)
@IsOptional()
actionType?: RfaActionType;
@IsString()
@IsOptional()
comments?: string;
}

View File

@@ -1,37 +1,26 @@
// File: src/modules/rfa/dto/create-rfa.dto.ts
// File: src/modules/rfa/dto/create-rfa-revision.dto.ts
import {
IsInt,
IsString,
IsNotEmpty,
IsInt,
IsOptional,
IsDateString,
IsObject,
IsArray,
IsNotEmpty,
} from 'class-validator';
export class CreateRfaDto {
@IsInt()
@IsNotEmpty()
projectId!: number;
@IsInt()
@IsNotEmpty()
rfaTypeId!: number;
@IsInt()
@IsOptional()
disciplineId?: number; // [Req 6B] สาขางาน (จำเป็นสำหรับการรันเลข RFA)
export class CreateRfaRevisionDto {
@IsString()
@IsNotEmpty()
title!: string;
@IsInt()
@IsNotEmpty()
toOrganizationId!: number; // ส่งถึงใคร (สำหรับ Routing Step 1)
rfaStatusCodeId!: number;
@IsString()
@IsInt()
@IsOptional()
description?: string;
rfaApproveCodeId?: number;
@IsDateString()
@IsOptional()
@@ -39,10 +28,25 @@ export class CreateRfaDto {
@IsDateString()
@IsOptional()
dueDate?: string; // กำหนดวันตอบกลับ
issuedDate?: string;
@IsDateString()
@IsOptional()
receivedDate?: string;
@IsDateString()
@IsOptional()
approvedDate?: string;
@IsString()
@IsOptional()
description?: string;
@IsObject()
@IsOptional()
details?: Record<string, any>;
@IsArray()
@IsInt({ each: true })
@IsOptional()
shopDrawingRevisionIds?: number[]; // Shop Drawings ที่แนบมา
}
shopDrawingRevisionIds?: number[]; // IDs of linked Shop Drawings
}

View File

@@ -1,4 +1,4 @@
import { PartialType } from '@nestjs/swagger';
import { CreateRfaDto } from './create-rfa.dto';
import { CreateRfaRevisionDto } from './create-rfa-revision.dto';
export class UpdateRfaDto extends PartialType(CreateRfaDto) {}
export class UpdateRfaDto extends PartialType(CreateRfaRevisionDto) {}

View File

@@ -1,3 +1,4 @@
// File: src/modules/rfa/entities/rfa-revision.entity.ts
import {
Entity,
PrimaryGeneratedColumn,
@@ -8,6 +9,7 @@ import {
JoinColumn,
OneToMany,
Unique,
Index,
} from 'typeorm';
import { Rfa } from './rfa.entity';
import { Correspondence } from '../../correspondence/entities/correspondence.entity';
@@ -15,7 +17,7 @@ import { RfaStatusCode } from './rfa-status-code.entity';
import { RfaApproveCode } from './rfa-approve-code.entity';
import { User } from '../../user/entities/user.entity';
import { RfaItem } from './rfa-item.entity';
import { RfaWorkflow } from './rfa-workflow.entity'; // Import เพิ่ม
import { RfaWorkflow } from './rfa-workflow.entity';
@Entity('rfa_revisions')
@Unique(['rfaId', 'revisionNumber'])
@@ -63,6 +65,20 @@ export class RfaRevision {
@Column({ type: 'text', nullable: true })
description?: string;
// ✅ [New] เพิ่ม field details สำหรับเก็บข้อมูล Dynamic ของ RFA (เช่น Method Statement Details)
@Column({ type: 'json', nullable: true })
details?: any;
// ✅ [New] Virtual Column: ดึงจำนวนแบบที่แนบ (drawingCount) จาก JSON
@Column({
name: 'v_ref_drawing_count',
type: 'int',
generatedType: 'VIRTUAL',
asExpression: "JSON_UNQUOTE(JSON_EXTRACT(details, '$.drawingCount'))",
nullable: true,
})
vRefDrawingCount?: number;
@CreateDateColumn({ name: 'created_at' })
createdAt!: Date;

View File

@@ -0,0 +1,44 @@
// File: src/modules/workflow-engine/entities/workflow-history.entity.ts
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { WorkflowInstance } from './workflow-instance.entity';
@Entity('workflow_histories')
export class WorkflowHistory {
@PrimaryGeneratedColumn('uuid')
id!: string;
@ManyToOne(() => WorkflowInstance, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'instance_id' })
instance!: WorkflowInstance;
@Column({ name: 'instance_id' })
instanceId!: string;
@Column({ name: 'from_state', length: 50 })
fromState!: string;
@Column({ name: 'to_state', length: 50 })
toState!: string;
@Column({ length: 50 })
action!: string;
@Column({ name: 'action_by_user_id', nullable: true })
actionByUserId?: number; // User ID ของผู้ดำเนินการ
@Column({ type: 'text', nullable: true })
comment?: string;
@Column({ type: 'json', nullable: true })
metadata?: Record<string, any>; // เก็บข้อมูลเพิ่มเติม เช่น Snapshot ของ Context ณ ตอนนั้น
@CreateDateColumn({ name: 'created_at' })
createdAt!: Date;
}

View File

@@ -0,0 +1,62 @@
// File: src/modules/workflow-engine/entities/workflow-instance.entity.ts
import {
Column,
CreateDateColumn,
Entity,
Index,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { WorkflowDefinition } from './workflow-definition.entity';
export enum WorkflowStatus {
ACTIVE = 'ACTIVE',
COMPLETED = 'COMPLETED',
CANCELLED = 'CANCELLED',
TERMINATED = 'TERMINATED',
}
@Entity('workflow_instances')
@Index(['entityType', 'entityId']) // Index สำหรับค้นหาตามเอกสาร
@Index(['currentState']) // Index สำหรับ Filter ตามสถานะ
export class WorkflowInstance {
@PrimaryGeneratedColumn('uuid')
id!: string;
// เชื่อมโยงกับ Definition ที่ใช้ตอนสร้าง Instance นี้
@ManyToOne(() => WorkflowDefinition)
@JoinColumn({ name: 'definition_id' })
definition!: WorkflowDefinition;
@Column({ name: 'definition_id' })
definitionId!: string;
// Polymorphic Relation: เชื่อมกับเอกสารได้หลายประเภท (RFA, CORR, etc.)
@Column({ name: 'entity_type', length: 50 })
entityType!: string;
@Column({ name: 'entity_id', length: 50 })
entityId!: string; // รองรับทั้ง ID แบบ Int และ UUID (เก็บเป็น String)
@Column({ name: 'current_state', length: 50 })
currentState!: string;
@Column({
type: 'enum',
enum: WorkflowStatus,
default: WorkflowStatus.ACTIVE,
})
status!: WorkflowStatus;
// Context เฉพาะของ Instance นี้ (เช่น ตัวแปรที่ส่งต่อระหว่าง State)
@Column({ type: 'json', nullable: true })
context?: Record<string, any>;
@CreateDateColumn({ name: 'created_at' })
createdAt!: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt!: Date;
}

View File

@@ -1,6 +1,6 @@
// File: src/modules/workflow-engine/workflow-dsl.service.ts
import { Injectable, BadRequestException } from '@nestjs/common';
import { BadRequestException, Injectable } from '@nestjs/common';
export interface WorkflowState {
initial?: boolean;
@@ -17,7 +17,7 @@ export interface TransitionRule {
export interface RequirementRule {
role?: string;
user?: string;
condition?: string; // e.g. "amount > 5000" (Advanced)
condition?: string;
}
export interface EventRule {
@@ -36,11 +36,12 @@ export interface CompiledWorkflow {
export class WorkflowDslService {
/**
* คอมไพล์ DSL Input ให้เป็น Standard Execution Tree
* @param dsl ข้อมูลดิบจาก User (JSON/Object)
* @returns CompiledWorkflow Object ที่พร้อมใช้งาน
*/
compile(dsl: any): CompiledWorkflow {
// 1. Basic Structure Validation
if (!dsl || typeof dsl !== 'object') {
throw new BadRequestException('DSL must be a valid JSON object.');
}
if (!dsl.states || !Array.isArray(dsl.states)) {
throw new BadRequestException(
'DSL syntax error: "states" array is required.',
@@ -55,7 +56,6 @@ export class WorkflowDslService {
const stateMap = new Set<string>();
// 2. First Pass: Collect all state names and normalize structure
for (const rawState of dsl.states) {
if (!rawState.name) {
throw new BadRequestException(
@@ -71,7 +71,6 @@ export class WorkflowDslService {
transitions: {},
};
// Normalize transitions "on:"
if (rawState.on) {
for (const [action, rule] of Object.entries(rawState.on)) {
const rawRule = rule as any;
@@ -86,15 +85,11 @@ export class WorkflowDslService {
compiled.states[rawState.name] = normalizedState;
}
// 3. Second Pass: Validate Integrity
this.validateIntegrity(compiled, stateMap);
return compiled;
}
/**
* ตรวจสอบความสมบูรณ์ของ Workflow Logic
*/
private validateIntegrity(compiled: CompiledWorkflow, stateMap: Set<string>) {
let hasInitial = false;
@@ -107,19 +102,13 @@ export class WorkflowDslService {
hasInitial = true;
}
// ตรวจสอบ Transitions
if (state.transitions) {
for (const [action, rule] of Object.entries(state.transitions)) {
// 1. ปลายทางต้องมีอยู่จริง
if (!stateMap.has(rule.to)) {
throw new BadRequestException(
`DSL Error: State "${stateName}" transitions via "${action}" to unknown state "${rule.to}".`,
);
}
// 2. Action name convention (Optional but recommended)
if (!/^[A-Z0-9_]+$/.test(action)) {
// Warning or Strict Error could be here
}
}
}
}
@@ -129,18 +118,11 @@ export class WorkflowDslService {
}
}
/**
* ประเมินผล (Evaluate) การเปลี่ยนสถานะ
* @param compiled ข้อมูล Workflow ที่ Compile แล้ว
* @param currentState สถานะปัจจุบัน
* @param action การกระทำ
* @param context ข้อมูลประกอบ (User roles, etc.)
*/
evaluate(
compiled: CompiledWorkflow,
currentState: string,
action: string,
context: any,
context: any = {}, // Default empty object
): { nextState: string; events: EventRule[] } {
const stateConfig = compiled.states[currentState];
@@ -164,7 +146,6 @@ export class WorkflowDslService {
);
}
// Check Requirements (RBAC Logic inside Engine)
if (transition.requirements && transition.requirements.length > 0) {
this.checkRequirements(transition.requirements, context);
}
@@ -175,22 +156,19 @@ export class WorkflowDslService {
};
}
/**
* ตรวจสอบเงื่อนไขสิทธิ์ (Requirements)
*/
private checkRequirements(requirements: RequirementRule[], context: any) {
const userRoles = context.roles || [];
const userId = context.userId;
const safeContext = context || {};
const userRoles = safeContext.roles || [];
const userId = safeContext.userId;
const isAllowed = requirements.some((req) => {
// กรณีเช็ค Role
if (req.role) {
return userRoles.includes(req.role);
}
// กรณีเช็ค Specific User
if (req.user) {
return userId === req.user;
}
// Future: Add Condition Logic Evaluation here
return false;
});

View File

@@ -1,26 +1,27 @@
// File: src/modules/workflow-engine/workflow-engine.controller.ts
import {
Controller,
Post,
Body,
Controller,
Get,
Query,
Patch,
Param,
ParseUUIDPipe,
Patch,
Post,
Query,
UseGuards,
} from '@nestjs/common'; // เพิ่ม Patch, Param
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { WorkflowEngineService } from './workflow-engine.service';
} from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { CreateWorkflowDefinitionDto } from './dto/create-workflow-definition.dto';
import { EvaluateWorkflowDto } from './dto/evaluate-workflow.dto';
import { GetAvailableActionsDto } from './dto/get-available-actions.dto'; // [NEW]
import { UpdateWorkflowDefinitionDto } from './dto/update-workflow-definition.dto'; // [NEW]
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { GetAvailableActionsDto } from './dto/get-available-actions.dto';
import { UpdateWorkflowDefinitionDto } from './dto/update-workflow-definition.dto';
import { WorkflowEngineService } from './workflow-engine.service';
@ApiTags('Workflow Engine (DSL)')
@Controller('workflow-engine')
@UseGuards(JwtAuthGuard) // Protect all endpoints
@UseGuards(JwtAuthGuard)
export class WorkflowEngineController {
constructor(private readonly workflowService: WorkflowEngineService) {}
@@ -42,24 +43,20 @@ export class WorkflowEngineController {
@Get('actions')
@ApiOperation({ summary: 'Get available actions for current state' })
async getAvailableActions(@Query() query: GetAvailableActionsDto) {
// [UPDATED] ใช้ DTO แทนแยก Query
return this.workflowService.getAvailableActions(
query.workflow_code,
query.current_state,
);
}
// [OPTIONAL/RECOMMENDED] เพิ่ม Endpoint สำหรับ Update (PATCH)
@Patch('definitions/:id')
@ApiOperation({
summary: 'Update workflow status or details (e.g. Deactivate)',
summary: 'Update workflow status or details (DSL Re-compile)',
})
async updateDefinition(
@Param('id') id: string,
@Body() dto: UpdateWorkflowDefinitionDto, // [NEW] ใช้ Update DTO
@Param('id', ParseUUIDPipe) id: string, // เพิ่ม ParseUUIDPipe เพื่อ Validate ID
@Body() dto: UpdateWorkflowDefinitionDto,
) {
// *หมายเหตุ: คุณต้องไปเพิ่ม method update() ใน Service ด้วยถ้าจะใช้ Endpoint นี้
// return this.workflowService.update(id, dto);
return { message: 'Update logic not implemented yet', id, ...dto };
return this.workflowService.update(id, dto);
}
}

View File

@@ -2,21 +2,23 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkflowDefinition } from './entities/workflow-definition.entity';
import { WorkflowHistory } from './entities/workflow-history.entity'; // [New]
import { WorkflowInstance } from './entities/workflow-instance.entity'; // [New]
import { WorkflowDslService } from './workflow-dsl.service';
import { WorkflowEngineController } from './workflow-engine.controller';
import { WorkflowEngineService } from './workflow-engine.service';
import { WorkflowDslService } from './workflow-dsl.service'; // [New] ต้องสร้างไฟล์นี้ตามแผน Phase 6A
import { WorkflowEngineController } from './workflow-engine.controller'; // [New] ต้องสร้างไฟล์นี้ตามแผน Phase 6A
import { WorkflowDefinition } from './entities/workflow-definition.entity'; // [New] ต้องสร้างไฟล์นี้ตามแผน Phase 6A
@Module({
imports: [
// เชื่อมต่อกับตาราง workflow_definitions
TypeOrmModule.forFeature([WorkflowDefinition]),
TypeOrmModule.forFeature([
WorkflowDefinition,
WorkflowInstance, // [New]
WorkflowHistory, // [New]
]),
],
controllers: [WorkflowEngineController], // เพิ่ม Controller สำหรับรับ API
providers: [
WorkflowEngineService, // Service หลัก
WorkflowDslService, // [New] Service สำหรับ Compile/Validate DSL
],
exports: [WorkflowEngineService], // Export ให้ module อื่นใช้เหมือนเดิม
controllers: [WorkflowEngineController],
providers: [WorkflowEngineService, WorkflowDslService],
exports: [WorkflowEngineService],
})
export class WorkflowEngineModule {}

View File

@@ -1,20 +1,29 @@
// File: src/modules/workflow-engine/workflow-engine.service.ts
import {
Injectable,
NotFoundException,
BadRequestException,
Injectable,
Logger,
NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { DataSource, Repository } from 'typeorm';
// Entities
import { WorkflowDefinition } from './entities/workflow-definition.entity';
import { WorkflowDslService, CompiledWorkflow } from './workflow-dsl.service';
import { WorkflowHistory } from './entities/workflow-history.entity';
import {
WorkflowInstance,
WorkflowStatus,
} from './entities/workflow-instance.entity';
// Services & Interfaces
import { CreateWorkflowDefinitionDto } from './dto/create-workflow-definition.dto';
import { EvaluateWorkflowDto } from './dto/evaluate-workflow.dto';
import { UpdateWorkflowDefinitionDto } from './dto/update-workflow-definition.dto';
import { CompiledWorkflow, WorkflowDslService } from './workflow-dsl.service';
// Interface สำหรับ Backward Compatibility (Logic เดิม)
// Legacy Interface (Backward Compatibility)
export enum WorkflowAction {
APPROVE = 'APPROVE',
REJECT = 'REJECT',
@@ -35,11 +44,16 @@ export class WorkflowEngineService {
constructor(
@InjectRepository(WorkflowDefinition)
private readonly workflowDefRepo: Repository<WorkflowDefinition>,
@InjectRepository(WorkflowInstance)
private readonly instanceRepo: Repository<WorkflowInstance>,
@InjectRepository(WorkflowHistory)
private readonly historyRepo: Repository<WorkflowHistory>,
private readonly dslService: WorkflowDslService,
private readonly dataSource: DataSource, // ใช้สำหรับ Transaction
) {}
// =================================================================
// [NEW] DSL & Workflow Engine (Phase 6A)
// [PART 1] Definition Management (Phase 6A)
// =================================================================
/**
@@ -48,8 +62,10 @@ export class WorkflowEngineService {
async createDefinition(
dto: CreateWorkflowDefinitionDto,
): Promise<WorkflowDefinition> {
// 1. Compile & Validate DSL
const compiled = this.dslService.compile(dto.dsl);
// 2. Check latest version
const latest = await this.workflowDefRepo.findOne({
where: { workflow_code: dto.workflow_code },
order: { version: 'DESC' },
@@ -57,6 +73,7 @@ export class WorkflowEngineService {
const nextVersion = latest ? latest.version + 1 : 1;
// 3. Save new version
const entity = this.workflowDefRepo.create({
workflow_code: dto.workflow_code,
version: nextVersion,
@@ -65,9 +82,16 @@ export class WorkflowEngineService {
is_active: dto.is_active ?? true,
});
return this.workflowDefRepo.save(entity);
const saved = await this.workflowDefRepo.save(entity);
this.logger.log(
`Created Workflow Definition: ${saved.workflow_code} v${saved.version}`,
);
return saved;
}
/**
* อัปเดต Definition (Re-compile DSL)
*/
async update(
id: string,
dto: UpdateWorkflowDefinitionDto,
@@ -95,33 +119,9 @@ export class WorkflowEngineService {
return this.workflowDefRepo.save(definition);
}
async evaluate(dto: EvaluateWorkflowDto): Promise<any> {
const definition = await this.workflowDefRepo.findOne({
where: { workflow_code: dto.workflow_code, is_active: true },
order: { version: 'DESC' },
});
if (!definition) {
throw new NotFoundException(
`No active workflow definition found for "${dto.workflow_code}"`,
);
}
const compiled: CompiledWorkflow = definition.compiled;
const result = this.dslService.evaluate(
compiled,
dto.current_state,
dto.action,
dto.context || {},
);
this.logger.log(
`Workflow Evaluated: ${dto.workflow_code} [${dto.current_state}] --${dto.action}--> [${result.nextState}]`,
);
return result;
}
/**
* ดึง Action ที่ทำได้ ณ State ปัจจุบัน
*/
async getAvailableActions(
workflowCode: string,
currentState: string,
@@ -140,25 +140,206 @@ export class WorkflowEngineService {
}
// =================================================================
// [LEGACY] Backward Compatibility for Correspondence/RFA Modules
// คืนค่า Logic เดิมเพื่อไม่ให้ Module อื่น Error (TS2339)
// [PART 2] Runtime Engine (Phase 3.1)
// =================================================================
/**
* เริ่มต้น Workflow Instance ใหม่สำหรับเอกสาร
*/
async createInstance(
workflowCode: string,
entityType: string,
entityId: string,
initialContext: Record<string, any> = {},
): Promise<WorkflowInstance> {
// 1. หา Definition ล่าสุด
const definition = await this.workflowDefRepo.findOne({
where: { workflow_code: workflowCode, is_active: true },
order: { version: 'DESC' },
});
if (!definition) {
throw new NotFoundException(
`Workflow "${workflowCode}" not found or inactive.`,
);
}
// 2. หา Initial State จาก Compiled Structure
const compiled: CompiledWorkflow = definition.compiled;
const initialState = Object.keys(compiled.states).find(
(key) => compiled.states[key].initial,
);
if (!initialState) {
throw new BadRequestException(
`Workflow "${workflowCode}" has no initial state defined.`,
);
}
// 3. สร้าง Instance
const instance = this.instanceRepo.create({
definition,
entityType,
entityId,
currentState: initialState,
status: WorkflowStatus.ACTIVE,
context: initialContext,
});
const savedInstance = await this.instanceRepo.save(instance);
this.logger.log(
`Started Workflow Instance: ${workflowCode} for ${entityType}:${entityId}`,
);
return savedInstance;
}
/**
* ดำเนินการเปลี่ยนสถานะ (Transition) ของ Instance จริงแบบ Transactional
*/
async processTransition(
instanceId: string,
action: string,
userId: number,
comment?: string,
payload: Record<string, any> = {},
) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
// 1. Lock Instance เพื่อป้องกัน Race Condition (Pessimistic Write Lock)
const instance = await queryRunner.manager.findOne(WorkflowInstance, {
where: { id: instanceId },
relations: ['definition'],
lock: { mode: 'pessimistic_write' },
});
if (!instance) {
throw new NotFoundException(
`Workflow Instance "${instanceId}" not found.`,
);
}
if (instance.status !== WorkflowStatus.ACTIVE) {
throw new BadRequestException(
`Workflow is not active (Status: ${instance.status}).`,
);
}
// 2. Evaluate Logic ผ่าน DSL Service
const compiled: CompiledWorkflow = instance.definition.compiled;
const context = { ...instance.context, userId, ...payload }; // Merge Context
// * DSL Service จะ throw error ถ้า action ไม่ถูกต้อง หรือสิทธิ์ไม่พอ
const evaluation = this.dslService.evaluate(
compiled,
instance.currentState,
action,
context,
);
const fromState = instance.currentState;
const toState = evaluation.nextState;
// 3. อัปเดต Instance
instance.currentState = toState;
instance.context = context; // อัปเดต Context ด้วย
// เช็คว่าเป็น Terminal State หรือไม่?
if (compiled.states[toState].terminal) {
instance.status = WorkflowStatus.COMPLETED;
}
await queryRunner.manager.save(instance);
// 4. บันทึก History (Audit Trail)
const history = this.historyRepo.create({
instanceId: instance.id,
fromState,
toState,
action,
actionByUserId: userId,
comment,
metadata: {
events: evaluation.events,
payload,
},
});
await queryRunner.manager.save(history);
// 5. Trigger Events (Integration Point)
// ในอนาคตสามารถ Inject NotificationService มาเรียกตรงนี้ได้
if (evaluation.events && evaluation.events.length > 0) {
this.logger.log(
`Triggering ${evaluation.events.length} events for instance ${instanceId}`,
);
// await this.eventHandler.handle(evaluation.events);
}
await queryRunner.commitTransaction();
this.logger.log(
`Transition: ${instanceId} [${fromState}] --${action}--> [${toState}] by User:${userId}`,
);
return {
success: true,
nextState: toState,
events: evaluation.events,
isCompleted: instance.status === WorkflowStatus.COMPLETED,
};
} catch (err) {
await queryRunner.rollbackTransaction();
this.logger.error(
`Transition Failed for ${instanceId}: ${(err as Error).message}`,
);
throw err;
} finally {
await queryRunner.release();
}
}
/**
* (Utility) Evaluate แบบไม่บันทึกผล (Dry Run) สำหรับ Test หรือ Preview
*/
async evaluate(dto: EvaluateWorkflowDto): Promise<any> {
const definition = await this.workflowDefRepo.findOne({
where: { workflow_code: dto.workflow_code, is_active: true },
order: { version: 'DESC' },
});
if (!definition) {
throw new NotFoundException(`Workflow "${dto.workflow_code}" not found`);
}
return this.dslService.evaluate(
definition.compiled,
dto.current_state,
dto.action,
dto.context || {},
);
}
// =================================================================
// [PART 3] Legacy Support (Backward Compatibility)
// รักษา Logic เดิมไว้เพื่อให้ Module อื่น (Correspondence/RFA) ทำงานต่อได้
// =================================================================
/**
* คำนวณสถานะถัดไปแบบ Linear Sequence (Logic เดิม)
* ใช้สำหรับ CorrespondenceService และ RfaService ที่ยังไม่ได้ Refactor
* @deprecated แนะนำให้เปลี่ยนไปใช้ processTransition แทนเมื่อ Refactor เสร็จ
*/
processAction(
currentSequence: number,
totalSteps: number,
action: string, // รับเป็น string เพื่อความยืดหยุ่น
action: string,
returnToSequence?: number,
): TransitionResult {
// Map string action to enum logic
switch (action) {
case WorkflowAction.APPROVE:
case WorkflowAction.ACKNOWLEDGE:
case 'APPROVE': // Case sensitive handling fallback
case 'APPROVE':
case 'ACKNOWLEDGE':
if (currentSequence >= totalSteps) {
return {
@@ -193,7 +374,6 @@ export class WorkflowEngineService {
};
default:
// กรณีส่ง Action อื่นมา ให้ถือว่าเป็น Approve (หรือจะ Throw Error ก็ได้)
this.logger.warn(
`Unknown legacy action: ${action}, treating as next step.`,
);

View File

@@ -22,6 +22,27 @@
"forceConsistentCasingInFileNames": true,
"noImplicitAny": true, // ห้ามใช้ Any โดยไม่จำเป็น
"strictBindCallApply": true,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
"paths": {
"@backend": ["./src"],
"@backend/*": ["./src/*"],
"@modules": ["./src/modules"],
"@common": ["./src/common"],
"@config": ["./src/common/config"],
"@circulation": ["./src/modules/circulation"],
"@correspondence": ["./src/modules/correspondence"],
"@document-numbering": ["./src/modules/document-numbering"],
"@drawing": ["./src/modules/drawing"],
"@json-schema": ["./src/modules/json-schema"],
"@master": ["./src/modules/master"],
"@monitoring": ["./src/modules/monitoring"],
"@notification": ["./src/modules/notification"],
"@project": ["./src/modules/project"],
"@rfa": ["./src/modules/rfa"],
"@search": ["./src/modules/search"],
"@transmittal": ["./src/modules/transmittal"],
"@users": ["./src/modules/users"],
"@workflow-engine": ["./src/modules/workflow-engine"]
}
}
}

58
docs/GEM.md Normal file
View File

@@ -0,0 +1,58 @@
# บทบาท: คุณคือ Programmer ที่เชี่ยวชาญ
- การจัดการฐานข้อมูล (SQL, Relational Databases, Data Analysis)
- การวิเคราะห์ฐานข้อมูล (Database Analysis)
- การพัฒนา Backend (NodeJS, NestJS)
- การพัฒนา Frontend (NextJS)
- ระบบควบคุมการเข้าถึง (RBAC, ABAC)
- การ debug และแก้ไข error ในโค้ด
# Basic data:
1. Application Requirements file: 0_Requirements_V1_4_4.md
2. Full Stack JS file: 1_FullStackJS_V1_4_4.md
3. Backend Development Plan:
- 2_Backend_Plan_V1_4_4.md
- 2_Backend_Plan_Phase6A_V1_4_3.md
- 2_Backend_Plan_V1_4_4.Phase_Addition.md
4. Frontend Development Plan: 3_Frontend_Plan_V1_4_4.md
5. Data files:
- 4_Data_Dictionary_V1_4_4.md
- 8_lcbp3_v1_4_4.sql
- 8_lcbp3_v1_4_4_seed.sql
# rules:
- ใช้ภาษาไทยใน comments
- เขียนโค้ดให้อ่านง่าย, ใส่ path/filename ในบรรทัดแรกของโค้ด
- การอัพเดทโค้ด ให้แก้ไขจากต้นฉบับเป็น โค้ดที่สมบูรณ์
- เขียน documentation สำหรับ function สำคัญ
- ใช้ข้อมูลพื้นฐานที่ให้มา (Basic data) เพื่อให้คำแนะนำและโค้ดที่สอดคล้องกับเอกสารโครงการ
- ต้องตรวจสอบกับ 8_lcbp3_v1_4_4_seed.sql ก่อน สร้างข้อมูลสมมุติ (Mockup data) ถ้ามีข้อมูลอยู่แล้วให้ใช้ข้อมูลที่มีอยู่
# เป้าหมายและจุดประสงค์:
- ให้ความช่วยเหลือผู้ใช้ในงานที่เกี่ยวข้องกับการพัฒนาซอฟต์แวร์ โดยเฉพาะอย่างยิ่งในส่วนของ JavaScript (NodeJS, NestJS, NextJS) และฐานข้อมูล (SQL, Relational Databases)
- ให้คำปรึกษาเกี่ยวกับการจัดการข้อมูล, การออกแบบฐานข้อมูลเชิงสัมพันธ์, และการใช้โมเดลการควบคุมการเข้าถึง (RBAC, ABAC)
- ให้คำแนะนำเกี่ยวกับการแก้ไขปัญหาหรือการพัฒนาซอฟต์แวร์
- ช่วยเหลือในการวิเคราะห์และแก้ไขข้อผิดพลาด (debug และ error) ในโค้ดตามที่ผู้ใช้ระบุ
# พฤติกรรมและกฎเพิ่มเติม:
1. การเริ่มต้นและการโต้ตอบ:
a) ทักทายผู้ใช้ด้วยภาษาไทยอย่างเป็นมิตร และสอบถามเกี่ยวกับปัญหาหรือความช่วยเหลือที่ต้องการในด้านการเขียนโปรแกรมหรือฐานข้อมูล
b) ตอบคำถามทางเทคนิคอย่างแม่นยำและเป็นมืออาชีพ โดยใช้ศัพท์เฉพาะทางที่ถูกต้อง
c) จำกัดจำนวนประโยคในการตอบกลับแต่ละครั้งให้กระชับและตรงประเด็นเพื่อความรวดเร็วในการสื่อสาร
d) ให้วิเคราะห์ปัญกาอย่างละเอียดและรอบคอบ ไม่ต้องรีบตอบ
2. การจัดการโค้ดและข้อมูล:
a) เมื่อผู้ใช้ขอให้อัพเดทโค้ด ให้ทำการแสดงโค้ดฉบับเสมบูรณ์ที่ได้รับการแก้ไขแล้ว
b) ต้องแน่ใจว่าโค้ดที่สร้างขึ้นมานั้นอ่านง่ายและมี comments เป็นภาษาไทยตามที่ระบุใน rules
c) สำหรับฟังก์ชันที่มีความซับซ้อนหรือมีความสำคัญต่อระบบ ต้องเขียน documentation อธิบายวัตถุประสงค์, พารามิเตอร์, และผลลัพธ์ของฟังก์ชันนั้นๆ ด้วยภาษาไทย
d) หากต้องอ้างอิงถึงโครงสร้างข้อมูลหรือข้อกำหนดใดๆ ให้ตรวจสอบจากไฟล์ Basic data ที่ผู้ใช้ให้มาก่อนเสมอ ถ้าไม่พบ ให้แจ้งผู้ใช้ทราบ
e) ถ้ามีการอ้างอิงถึงโค้ดที่อยู่ใน Phase หรือ Task ก่อนหน้า ให้สอบถามผู้ใช้เพื่อให้ upload ไฟล์โค้ดที่อ้างอิง (ไม่สร้างใหม่ เพื่อประหยัดเวลา)
3. โทนโดยรวม:
- ใช้ภาษาไทยในการสื่อสารเป็นหลัก ยกเว้นศัพท์เทคนิค
- มีความมั่นใจและแสดงออกถึงความเชี่ยวชาญในฐานะโปรแกรมเมอร์ผู้เชี่ยวชาญ
- มีความเป็นระเบียบและให้คำแนะนำที่เป็นขั้นตอน

View File

@@ -1,162 +0,0 @@
# **🌳 โครงสร้างไฟล์และโฟลเดอร์ทั้งหมด \- DMS v1.3.0 Backend**
DMS\_Backend\_Project/
├── Dockerfile
├── docker-compose.yml
├── nest-cli.json
├── package.json
├── tsconfig.json
└── src/
├── app.module.ts
├── main.ts
├── common/
│ ├── common.module.ts
│ ├── audit-log/
│ │ ├── audit-log.interceptor.ts
│ │ └── audit-log.service.ts
│ ├── auth/
│ │ ├── auth.controller.ts
│ │ ├── auth.module.ts
│ │ ├── auth.service.spec.ts
│ │ ├── auth.service.ts
│ │ ├── decorators/
│ │ │ ├── current-user.decorator.ts
│ │ │ └── require-permission.decorator.ts
│ │ ├── dto/
│ │ │ └── login.dto.ts
│ │ ├── guards/
│ │ │ ├── rbac.guard.spec.ts
│ │ │ └── rbac.guard.ts
│ │ └── strategies/
│ │ └── jwt.strategy.ts
│ ├── config/
│ │ └── typeorm.config.ts (ตามที่ระบุใน app.module)
│ ├── entities/
│ │ ├── attachment.entity.ts
│ │ ├── audit-log.entity.ts
│ │ ├── circulation-action.entity.ts
│ │ ├── circulation-assignee.entity.ts
│ │ ├── circulation-attachment.entity.ts
│ │ ├── circulation-recipient.entity.ts
│ │ ├── circulation-status-code.entity.ts
│ │ ├── circulation.entity.ts
│ │ ├── contract-drawing-attachment.entity.ts
│ │ ├── contract-drawing-category.entity.ts
│ │ ├── contract-drawing-sub-category.entity.ts
│ │ ├── contract-drawing-volume.entity.ts
│ │ ├── contract-drawing.entity.ts
│ │ ├── correspondence-attachment.entity.ts
│ │ ├── correspondence-recipient.entity.ts
│ │ ├── correspondence-revision.entity.ts
│ │ ├── correspondence-status.entity.ts
│ │ ├── correspondence-type.entity.ts
│ │ ├── correspondence.entity.ts
│ │ ├── document-number-counter.entity.ts
│ │ ├── document-number-format.entity.ts
│ │ ├── permission.entity.ts
│ │ ├── rfa-approve-code.entity.ts
│ │ ├── rfa-item.entity.ts
│ │ ├── rfa-revision.entity.ts
│ │ ├── rfa-status-code.entity.ts
│ │ ├── rfa-type.entity.ts
│ │ ├── rfa.entity.ts
│ │ ├── role.entity.ts
│ │ ├── shop-drawing-main-category.entity.ts
│ │ ├── shop-drawing-revision-attachment.entity.ts
│ │ ├── shop-drawing-revision-contract-ref.entity.ts
│ │ ├── shop-drawing-revision.entity.ts
│ │ ├── shop-drawing-sub-category.entity.ts
│ │ ├── shop-drawing.entity.ts
│ │ ├── tag.entity.ts
│ │ ├── transmittal-item.entity.ts
│ │ ├── transmittal.entity.ts
│ │ ├── user.entity.ts
│ │ └── views/
│ │ ├── view-current-correspondence.entity.ts
│ │ └── view-current-rfa.entity.ts
│ ├── exceptions/
│ │ └── http-exception.filter.ts
│ ├── file-storage/
│ │ ├── file-storage.service.ts
│ │ └── file.controller.ts
│ └── security/
│ └── rate-limiter.module.ts
└── modules/
├── caching/
│ └── caching.module.ts
├── circulation/
│ ├── circulation.controller.ts
│ ├── circulation.module.ts
│ ├── circulation.service.ts
│ └── dto/
│ ├── add-action.dto.ts
│ ├── assignee.dto.ts
│ ├── attachment.dto.ts
│ ├── create-circulation.dto.ts
│ └── recipient.dto.ts
├── correspondence/
│ ├── correspondence.controller.ts
│ ├── correspondence.module.ts
│ ├── correspondence.service.ts
│ └── dto/
│ ├── create-correspondence.dto.ts
│ └── query-correspondence.dto.ts
├── document-numbering/
│ ├── admin-numbering.controller.ts
│ ├── document-numbering.module.ts
│ ├── document-numbering.service.ts
│ └── dto/
│ ├── admin-create-number-format.dto.ts
│ └── admin-update-number-format.dto.ts
├── drawing/
│ ├── drawing.controller.ts
│ ├── drawing.module.ts
│ ├── drawing.service.ts
│ └── dto/
│ ├── attachment.dto.ts
│ ├── create-contract-drawing.dto.ts
│ ├── create-shop-drawing-revision.dto.ts
│ └── create-shop-drawing.dto.ts
├── health/
│ ├── health.controller.ts
│ └── health.module.ts
├── master-data/
│ ├── admin-master-data.controller.ts
│ ├── master-data.module.ts
│ ├── master-data.service.ts
│ └── dto/
│ └── create-tag.dto.ts
├── notification/
│ ├── notification.module.ts
│ └── notification.service.ts
├── project/
│ └── (ยังไม่ได้สร้างไฟล์)
├── rfa/
│ ├── rfa.controller.ts
│ ├── rfa.module.ts
│ ├── rfa.service.ts
│ └── dto/
│ └── create-rfa.dto.ts
├── search/
│ ├── search.controller.ts
│ ├── search.module.ts
│ ├── search.service.ts
│ └── dto/
│ └── advanced-search.dto.ts
├── transmittal/
│ ├── transmittal.controller.ts
│ ├── transmittal.module.ts
│ ├── transmittal.service.ts
│ └── dto/
│ ├── create-transmittal-item.dto.ts
│ └── create-transmittal.dto.ts
└── user/
├── admin-roles.controller.ts
├── admin-users.controller.ts
├── roles.service.ts
├── user.module.ts
├── user.service.ts
└── dto/
├── admin-assign-permissions.dto.ts
├── admin-create-role.dto.ts
└── admin-create-user.dto.ts

View File

@@ -1,100 +0,0 @@
# **🗂️ สรุปโครงสร้างไฟล์และโฟลเดอร์ (Backend NestJS) \- DMS v1.3.0**
นี่คือภาพรวมสถาปัตยกรรมโฟลเดอร์ทั้งหมดของโปรเจกต์ NestJS API ที่เราได้พัฒนาขึ้น โดยแบ่งตามหลักการ Separation of Concerns
## **📂 (Root) โฟลเดอร์หลักของโปรเจกต์**
ไฟล์เหล่านี้จะอยู่ที่ระดับบนสุดของโปรเจกต์ (นอก src/)
* Dockerfile
* **หน้าที่:** คำสั่งสำหรับ Build Docker Image (Multi-stage build) เพื่อนำไป Deploy
* docker-compose.yml
* **หน้าที่:** ไฟล์ตั้งค่าสำหรับ QNAP Container Station ใช้กำหนด Services, Networks, และ (สำคัญที่สุด) Environment Variables (เช่น DATABASE\_HOST, JWT\_SECRET)
* package.json
* **หน้าที่:** เก็บรายการ Dependencies ทั้งหมด (เช่น @nestjs/core, typeorm, bcrypt, @nestjs/elasticsearch)
* .gitignore, tsconfig.json, nest-cli.json
* **หน้าที่:** ไฟล์ตั้งค่าพื้นฐานของโปรเจกต์ TypeScript และ NestJS
## **📂 src/ (Source Code)**
นี่คือหัวใจหลักของแอปพลิเคชัน
### **1\. src/ (Root Files)**
* main.ts
* **หน้าที่:** จุดเริ่มต้น (Entry Point) ของแอปพลิเคชัน
* **การตั้งค่า:**
* ใช้ helmet() (Security)
* ใช้ RateLimiterGuard (Security)
* ใช้ ValidationPipe (Global Pipe สำหรับ DTO)
* ใช้ HttpExceptionFilter (Global Filter สำหรับ Error Handling)
* ตั้งค่า SwaggerModule (สำหรับสร้าง API Docs)
* ตั้งค่า Global Prefix (/api/v1)
* app.module.ts
* **หน้าที่:** โมดูลหลัก (Root Module)
* **การตั้งค่า:**
* Import ConfigModule (สำหรับ .env)
* Import TypeOrmModule (เชื่อมต่อฐานข้อมูล)
* **Import โมดูลหลักและโมดูล Feature ทั้งหมด** (เช่น CommonModule, UserModule, CorrespondenceModule, SearchModule ฯลฯ)
### **2\. src/common/ (The Foundation)**
โฟลเดอร์นี้คือ "รากฐาน" เก็บโค้ดที่โมดูลอื่นต้องใช้ร่วมกัน (Shared Logic)
* common.module.ts
* **หน้าที่:** ไฟล์ที่รวบรวมและ export Service/Module ทั้งหมดใน common/ ให้โมดูลอื่นเรียกใช้ง่ายๆ
* auth/
* **หน้าที่:** จัดการการยืนยันตัวตน (Authentication) และสิทธิ์ (RBAC)
* auth.module.ts, auth.service.ts, auth.controller.ts (สำหรับ /login, /me)
* strategies/jwt.strategy.ts (ตรรกะการถอดรหัส JWT)
* guards/rbac.guard.ts (ตัวตรวจสอบสิทธิ์ RBACGuard)
* decorators/require-permission.decorator.ts (@RequirePermission)
* decorators/current-user.decorator.ts (@CurrentUser)
* entities/
* **หน้าที่:** **ศูนย์รวม Entities ทั้งหมด (44+ ไฟล์)** ที่ Map กับตารางใน MariaDB
* เช่น user.entity.ts, role.entity.ts, correspondence.entity.ts, rfa.entity.ts, shop-drawing.entity.ts, circulation.entity.ts, attachment.entity.ts, audit-log.entity.ts ฯลฯ
* views/: โฟลเดอร์ย่อยสำหรับ View Entities (เช่น view-current-correspondence.entity.ts)
* config/
* **หน้าที่:** เก็บ Config ที่ซับซ้อน เช่น typeorm.config.ts (ตั้งค่าการเชื่อมต่อ TypeORM)
* exceptions/
* http-exception.filter.ts (Global Error Handler)
* file-storage/
* file-storage.service.ts (Logic การบันทึกไฟล์ลง Disk)
* file.controller.ts (API Endpoint /files/upload)
* audit-log/
* audit-log.service.ts (Service บันทึก Log)
* audit-log.interceptor.ts (Interceptor ดักจับการกระทำ)
* security/
* rate-limiter.module.ts (ตั้งค่า Rate Limiting)
### **3\. src/modules/ (The Features)**
โฟลเดอร์นี้เก็บ "Business Logic" โดยแยกเป็น Feature Modules อย่างชัดเจน
* user/ (Phase 1\)
* **หน้าที่:** API สำหรับ Admin Panel (จัดการ Users, Roles, Permissions)
* admin-users.controller.ts, admin-roles.controller.ts, user.service.ts, roles.service.ts
* project/ (Phase 2\)
* **หน้าที่:** API สำหรับจัดการโปรเจกต์ (ยังไม่ได้สร้างไฟล์ แต่มีในแผน)
* document-numbering/ (Phase 2\)
* **หน้าที่:** API สำหรับ Admin (ตั้งค่า Format เลขที่) และ Service (generateNextDocumentNumber)
* master-data/ (Phase 2\)
* **หน้าที่:** API สำหรับ Admin (จัดการ Master Data เช่น Tags, RFA Types, Corr. Types)
* correspondence/ (Phase 2\)
* **หน้าที่:** Logic หลักในการสร้าง/ค้นหา เอกสารโต้ตอบ
* rfa/ (Phase 3\)
* **หน้าที่:** Logic หลักในการสร้าง/ค้นหา เอกสารขออนุมัติ (RFA)
* drawing/ (Phase 3\)
* **หน้าที่:** Logic หลักในการสร้าง/ค้นหา แบบแปลน (Contract & Shop Drawing)
* circulation/ (Phase 3\)
* **หน้าที่:** Logic หลักในการสร้าง/ค้นหา ใบเวียนภายใน
* transmittal/ (Phase 3\)
* **หน้าที่:** Logic หลักในการสร้าง/ค้นหา เอกสารนำส่ง
* search/ (Phase 4\)
* **หน้าที่:** API (/search) และ Service ที่เชื่อมต่อกับ Elasticsearch
* notification/ (Phase 4\)
* **หน้าที่:** Service สำหรับยิง Webhook ไปยัง N8N
* caching/ (Phase 4\)
* **หน้าที่:** โมดูลตั้งค่า Caching (Global)
* health/ (Phase 5 \- Deploy)
* **หน้าที่:** API (/health) สำหรับ Health Check

View File

@@ -1,436 +0,0 @@
# โครงสร้างโฟลเดอร์และไฟล์ทั้งหมดสำหรับ **Backend (NestJS)** ตามแผนงาน **LCBP3-DMS v1.4.3** ตั้งแต่ Phase 0 ถึง Phase 6 (T0-T6.2) ที่ได้ดำเนินการไปแล้ว
โครงสร้างนี้ออกแบบตามหลัก **Domain-Driven Design** และ **Modular Architecture** ที่ระบุไว้ในแผนพัฒนา
---
## 📂 **backend/** (Backend Application)
* [x] `.env` (สำหรับ Local Dev เท่านั้น ห้าม commit)
* [x] `.gitignore`
* [x] `docker-compose.yml` (Configuration หลักสำหรับ Deploy)
* [x] `docker-compose.override.yml` (สำหรับ Inject Secrets ตอน Dev)
* [x] `package.json`
* [x] `pnpm-lock.yaml`
* [x] `tsconfig.json`
* [x] `nest-cli.json`
* [x] `README.md`
---
## 📂 **backend/src/** (Source Code)
### **📄 Entry Points**
* [x] `main.ts` (Application Bootstrap, Swagger, Global Pipes)
* [x] `app.module.ts` (Root Module ที่รวมทุก Modules เข้าด้วยกัน)
### **📁 src/common/** (Shared Resources)
* [x] **common.module.ts**
* **auth/**
* **dto/**
* [x] **login.dto.ts**
* [x] **register.dto.ts**
* [x] **auth.controller.spec.ts**
* [x] **auth.controller.ts**
* [x] **auth.module.ts**
* [x] **auth.service.spec.ts**
* [x] **auth.service.ts**
* **config/** (Configuration Service)
* [x] **env.validation.ts**
* **decorators/**
* [x] **audit.decorator.ts**
* [x] `current-user.decorator.ts`
* [x] `require-permission.decorator.ts`
* **entities/**
* [x] **audit-log.entity.ts**
* [x] **base.entity.ts**
* **exceptions/**
* [x] `http-exception.filter.ts` (Global Filter)
* **file-storage/** (Two-Phase Storage System)
* **entities/**
* [x] **attachment.entity.ts**
* [x] **file-storage.controller.spec.ts**
* [x] **file-storage.controller.ts**
* [x] **file-storage.module.ts**
* [x] **file-storage.service.spec.ts**
* [x] `file-storage.service.ts` (Upload, Scan Virus, Commit)
* [x] `guards/`
* [x] `jwt-auth.guard.ts`
* [x] **jwt.strategy.ts**
* [x] `rbac.guard.ts` (ตรวจสอบสิทธิ์ 4 ระดับ)
* **interceptors/**
* [x] `audit-log.interceptor.ts` (เก็บ Log ลง DB)
* [x] `transform.interceptor.ts` (Standard Response Format)
* **resilience/** (Circuit Breaker & Retry)
### **📁 src/modules/** (Feature Modules)
1. **user/** (User Management & RBAC)
* [x] `dto/`
* [x] **assign-user-role.dto.ts**
* [x] `create-user.dto.ts`
* [x] `update-user.dto.ts`
* [x] `entities/`
* [x] `user.entity.ts`
* [x] `role.entity.ts`
* [x] `permission.entity.ts`
* [x] `user-preference.entity.ts`
* [x] **user-assignment.service.ts**
* [x] `user.controller.ts`
* [x] `user.module.ts`
* [x] `user.service.ts`
* [x] **user.service.spec.ts**
2. **project/** (Project Structure)
* [x] `dto/`
* [x] **create-project.dto.ts**
* [x] `search-project.dto.ts`
* [x] `update-project.dto.ts`
* [x] `entities/`
* [x] `contract-organization.entity.ts`
* [x] `contract.entity.ts`
* [x] `organization.entity.ts`
* [x] `project-organization.entity.ts` (Junction)
* [x] **project.entity.ts**
* [x] **project.controller.spec.ts**
* [x] `project.controller.ts`
* [x] `project.module.ts`
* [x] **project.service.spec.ts**
* [x] `project.service.ts`
3. **correspondence/** (Core Document System)
* [x] `dto/`
* [x] `add-reference.dto.ts`
* [x] `create-correspondence.dto.ts`
* [x] `search-correspondence.dto.ts`
* [x] **submit-correspondence.dto.ts**
* [x] `workflow-action.dto.ts`
* [x] `entities/`
* [x] `correspondence-reference.entity.ts`
* [x] `correspondence-revision.entity.ts`
* [x] `correspondence-routing.entity.ts` (Unified Workflow)
* [x] `correspondence-status.entity.ts`
* [x] `correspondence-type.entity.ts`
* [x] `correspondence.entity.ts`
* [x] **routing-template-step.entity.ts**
* [x] `routing-template.entity.ts`
* [x] **correspondence.controller.spec.ts**
* [x] `correspondence.controller.ts`
* [x] `correspondence.module.ts`
* [x] **correspondence.service.spec.ts**
* [x] `correspondence.service.ts` (Impersonation & Workflow Logic)
4. **drawing/** (Contract & Shop Drawings)
* [x] `dto/`
* [x] `create-contract-drawing.dto.ts`
* [x] `create-shop-drawing-revision.dto.ts`
* [x] `create-shop-drawing.dto.ts`
* [x] `search-contract-drawing.dto.ts`
* [x] `search-shop-drawing.dto.ts`
* [x] `update-contract-drawing.dto.ts`
* [x] `entities/`
* [x] `contract-drawing-sub-category.entity.ts`
* [x] `contract-drawing-volume.entity.ts`
* [x] `contract-drawing.entity.ts`
* [x] `shop-drawing-main-category.entity.ts`
* [x] `shop-drawing-revision.entity.ts`
* [x] `shop-drawing-sub-category.entity.ts`
* [x] `shop-drawing.entity.ts`
* [x] `contract-drawing.controller.ts`
* [x] `contract-drawing.service.ts`
* [x] `drawing-master-data.controller.ts`
* [x] `drawing-master-data.service.ts`
* [x] `drawing.module.ts`
* [x] `shop-drawing.controller.ts`
* [x] `shop-drawing.service.ts`
4. **rfa/** (Request for Approval & Advanced Workflow)
* [x] `dto/`
* [x] `create-rfa.dto.ts`
* [x] `search-rfa.dto.ts`
* [x] `update-rfa.dto.ts`
* [x] `entities/`
* [x] `rfa-approve-code.entity.ts`
* [x] `rfa-item.entity.ts`
* [x] `rfa-revision.entity.ts`
* [x] `rfa-status-code.entity.ts`
* [x] `rfa-type.entity.ts`
* [x] `rfa-workflow-template-step.entity.ts`
* [x] `rfa-workflow-template.entity.ts`
* [x] `rfa-workflow.entity.ts`
* [x] `rfa.entity.ts`
* [x] `rfa.controller.ts`
* [x] `rfa.module.ts`
* [x] `rfa.service.ts` (Unified Workflow Integration)
5. **circulation/** (Internal Routing)
* [x] `dto/`
* [x] `create-circulation.dto.ts`
* [x] `update-circulation-routing.dto.ts`
* [x] `search-circulation.dto.ts`
* [x] `entities/`
* [x] `circulation-routing.entity.ts`
* [x] `circulation-status-code.entity.ts`
* [x] `circulation.entity.ts`
* [x] `circulation.controller.ts`
* [x] `circulation.module.ts`
* [x] `circulation.service.ts`
6. **transmittal/** (Document Forwarding)
* [x] `dto/`
* [x] `create-transmittal.dto.ts`
* [x] `search-transmittal.dto.ts`
* [x] **update-transmittal.dto.ts**
* [x] `entities/`
* [x] `transmittal-item.entity.ts`
* [x] `transmittal.entity.ts`
* [x] `transmittal.controller.ts`
* [x] `transmittal.module.ts`
* [x] `transmittal.service.ts`
7. **notification/** (System Alerts)
* [x] `dto/`
* [x] `create-notification.dto.ts`
* [x] `search-notification.dto.ts`
* [x] `entities/`
* [x] `notification.entity.ts`
* [x] `notification-cleanup.service.ts` (Cron Job)
* [x] `notification.controller.ts`
* [x] `notification.gateway.ts`
* [x] `notification.module.ts` (Real-time WebSocket)
* [x] `notification.processor.ts` (Consumer/Worker for Email & Line)
* [x] `notification.service.ts` (Producer)
8. **search/** (Elasticsearch)
* [x] `dto/search-query.dto.ts`
* [x] `search.controller.ts`
* [x] `search.module.ts`
* [x] `search.service.ts` (Indexing & Searching)
9. **document-numbering/** (Internal Service)
* [x] `entities/`
* [x] `document-number-format.entity.ts`
* [x] `document-number-counter.entity.ts`
* [x] `document-numbering.module.ts`
* [x] **document-numbering.service.spec.ts**
* [x] `document-numbering.service.ts` (Double-Lock Mechanism)
10. **workflow-engine/** (Unified Logic)
* [x] `interfaces/workflow.interface.ts`
* [x] `workflow-engine.module.ts`
* [x] **workflow-engine.service.spec.ts**
* [x] `workflow-engine.service.ts` (State Machine Logic)
11. **json-schema/** (Validation)
* [x] `dto/`
* [x] `create-json-schema.dto.ts`+
* [x] `search-json-schema.dto.ts`
* [x] `update-json-schema.dto.ts`
* [x] `entities/`
* [x] `json-schema.entity.ts`
* [x] **json-schema.controller.spec.ts**
* [x] **json-schema.controller.ts**
* [x] `json-schema.module.ts`
* [x] **json-schema.service.spec.ts**
* [x] `json-schema.service.ts`
---
นี่คือโครงสร้าง **Folder Structure** ของโปรเจกต์ Backend (NestJS) ฉบับสมบูรณ์ ที่รวบรวมจาก **Requirements**, **FullStackJS Guidelines**, **Data Dictionary** และสิ่งที่เราได้ **Implement ไปแล้วตั้งแต่ Phase 0 ถึง Phase 6.2** ครับ
โครงสร้างนี้เป็นแบบ **Domain-Driven Modular Architecture** ครับ
```text
lcbp3-backend/
├── .env # Environment variables (Local Dev)
├── .gitignore
├── docker-compose.yml # Main config for services (DB, Redis, ES, App)
├── docker-compose.override.yml # Secrets injection for Dev
├── nest-cli.json
├── package.json
├── pnpm-lock.yaml
├── README.md
├── tsconfig.json
└── src/
├── main.ts # Entry point (Swagger, Helmet, Global Pipes)
├── app.module.ts # Root Module
├── common/ # 🛠️ Shared Resources
│ ├── auth/
│ │ ├── guards/
│ │ │ ├── jwt-auth.guard.ts
│ │ │ └── rbac.guard.ts
│ │ └── strategies/
│ │ └── jwt.strategy.ts
│ ├── config/ # Config Service
│ ├── decorators/
│ │ ├── current-user.decorator.ts
│ │ └── require-permission.decorator.ts
│ ├── exceptions/
│ │ └── http-exception.filter.ts
│ ├── file-storage/ # 📂 Two-Phase Storage System
│ │ ├── dto/
│ │ ├── entities/
│ │ │ └── attachment.entity.ts
│ │ ├── file-storage.module.ts
│ │ └── file-storage.service.ts
│ ├── interceptors/
│ │ ├── audit-log.interceptor.ts
│ │ └── transform.interceptor.ts
│ └── resilience/ # Circuit Breaker & Retry logic
└── modules/ # 📦 Feature Modules
├── user/ # 👤 User & RBAC
│ ├── dto/
│ │ ├── create-user.dto.ts
│ │ └── update-user.dto.ts
│ ├── entities/
│ │ ├── permission.entity.ts
│ │ ├── role.entity.ts
│ │ ├── user-preference.entity.ts
│ │ └── user.entity.ts
│ ├── user.controller.ts
│ ├── user.module.ts
│ └── user.service.ts
├── project/ # 🏢 Projects & Organizations
│ ├── dto/
│ │ ├── create-project.dto.ts
│ │ ├── search-project.dto.ts
│ │ └── update-project.dto.ts
│ ├── entities/
│ │ ├── contract-organization.entity.ts
│ │ ├── contract.entity.ts
│ │ ├── organization.entity.ts
│ │ ├── project-organization.entity.ts
│ │ └── project.entity.ts
│ ├── project.controller.ts
│ ├── project.module.ts
│ └── project.service.ts
├── correspondence/ # ✉️ Core Document
│ ├── dto/
│ │ ├── add-reference.dto.ts
│ │ ├── create-correspondence.dto.ts
│ │ ├── search-correspondence.dto.ts
│ │ └── workflow-action.dto.ts
│ ├── entities/
│ │ ├── correspondence-reference.entity.ts
│ │ ├── correspondence-revision.entity.ts
│ │ ├── correspondence-routing.entity.ts
│ │ ├── correspondence-status.entity.ts
│ │ ├── correspondence-type.entity.ts
│ │ ├── correspondence.entity.ts
│ │ └── routing-template.entity.ts
│ ├── correspondence.controller.ts
│ ├── correspondence.module.ts
│ └── correspondence.service.ts
├── drawing/ # 📐 Shop & Contract Drawings
│ ├── dto/
│ │ ├── create-contract-drawing.dto.ts
│ │ ├── create-shop-drawing-revision.dto.ts
│ │ ├── create-shop-drawing.dto.ts
│ │ ├── search-contract-drawing.dto.ts
│ │ ├── search-shop-drawing.dto.ts
│ │ └── update-contract-drawing.dto.ts
│ ├── entities/
│ │ ├── contract-drawing-sub-category.entity.ts
│ │ ├── contract-drawing-volume.entity.ts
│ │ ├── contract-drawing.entity.ts
│ │ ├── shop-drawing-main-category.entity.ts
│ │ ├── shop-drawing-revision.entity.ts
│ │ ├── shop-drawing-sub-category.entity.ts
│ │ └── shop-drawing.entity.ts
│ ├── contract-drawing.controller.ts
│ ├── contract-drawing.service.ts
│ ├── drawing-master-data.controller.ts
│ ├── drawing-master-data.service.ts
│ ├── drawing.module.ts
│ ├── shop-drawing.controller.ts
│ └── shop-drawing.service.ts
├── rfa/ # ✅ Request for Approval
│ ├── dto/
│ │ ├── create-rfa.dto.ts
│ │ ├── search-rfa.dto.ts
│ │ └── update-rfa.dto.ts
│ ├── entities/
│ │ ├── rfa-approve-code.entity.ts
│ │ ├── rfa-item.entity.ts
│ │ ├── rfa-revision.entity.ts
│ │ ├── rfa-status-code.entity.ts
│ │ ├── rfa-type.entity.ts
│ │ ├── rfa-workflow-template-step.entity.ts
│ │ ├── rfa-workflow-template.entity.ts
│ │ ├── rfa-workflow.entity.ts
│ │ └── rfa.entity.ts
│ ├── rfa.controller.ts
│ ├── rfa.module.ts
│ └── rfa.service.ts
├── circulation/ # 🔄 Internal Routing
│ ├── dto/
│ │ ├── create-circulation.dto.ts
│ │ ├── search-circulation.dto.ts
│ │ └── update-circulation-routing.dto.ts
│ ├── entities/
│ │ ├── circulation-routing.entity.ts
│ │ ├── circulation-status-code.entity.ts
│ │ └── circulation.entity.ts
│ ├── circulation.controller.ts
│ ├── circulation.module.ts
│ └── circulation.service.ts
├── transmittal/ # 📤 Outgoing Documents
│ ├── dto/
│ │ ├── create-transmittal.dto.ts
│ │ └── search-transmittal.dto.ts
│ ├── entities/
│ │ ├── transmittal-item.entity.ts
│ │ └── transmittal.entity.ts
│ ├── transmittal.controller.ts
│ ├── transmittal.module.ts
│ └── transmittal.service.ts
├── notification/ # 🔔 Real-time & Queue
│ ├── dto/
│ │ ├── create-notification.dto.ts
│ │ └── search-notification.dto.ts
│ ├── entities/
│ │ └── notification.entity.ts
│ ├── notification-cleanup.service.ts
│ ├── notification.controller.ts
│ ├── notification.gateway.ts
│ ├── notification.module.ts
│ ├── notification.processor.ts
│ └── notification.service.ts
├── search/ # 🔍 Elasticsearch
│ ├── dto/
│ │ └── search-query.dto.ts
│ ├── search.controller.ts
│ ├── search.module.ts
│ └── search.service.ts
├── document-numbering/ # 🔢 Internal Numbering Service
│ ├── entities/
│ │ ├── document-number-counter.entity.ts
│ │ └── document-number-format.entity.ts
│ ├── document-numbering.module.ts
│ └── document-numbering.service.ts
├── workflow-engine/ # ⚙️ Unified Logic
│ ├── interfaces/
│ │ └── workflow.interface.ts
│ ├── workflow-engine.module.ts
│ └── workflow-engine.service.ts
└── json-schema/ # 📋 Validation Logic
├── json-schema.module.ts
└── json-schema.service.ts
```

View File

@@ -4,7 +4,7 @@
## VSCode Shortcut
Markdown preview Ctrl+Shift+V
Markdown preview Ctrl+Shift+V
## สร้างโครงสร้างโฟลเดอร์สำหรับ lcbp3-backend
@@ -80,14 +80,16 @@ git push -u origin main
## **สร้าง NestJS Project ใหม่**
* ขั้นตอนที่ 1: ติดตั้ง NestJS CLI (ถ้ายังไม่ได้ติดตั้ง)
* npm install -g @nestjs/cli
- ขั้นตอนที่ 1: ติดตั้ง NestJS CLI (ถ้ายังไม่ได้ติดตั้ง)
* ขั้นตอนที่ 2: สร้างโปรเจกต์ใหม่
* nest new backend
* nest new . /อยู่ในโฟลเดอร์ที่สร้างไว้แล้ว และต้องการสร้างโปรเจกต์ลงในโฟลเดอร์นั้นโดยตรง:
- npm install -g @nestjs/cli
* ขั้นตอนที่ 3: ติดตั้ง Dependencies เพิ่มเติมสำหรับ DMS
- ขั้นตอนที่ 2: สร้างโปรเจกต์ใหม่
- nest new backend
- nest new . /อยู่ในโฟลเดอร์ที่สร้างไว้แล้ว และต้องการสร้างโปรเจกต์ลงในโฟลเดอร์นั้นโดยตรง:
- ขั้นตอนที่ 3: ติดตั้ง Dependencies เพิ่มเติมสำหรับ DMS
```bash
# Core & Database
@@ -123,302 +125,143 @@ npm install --save-dev @nestjs/testing jest @types/jest @types/passport-jwt @typ
การควบคุมพฤติกรรมของผมต้องทำผ่าน **"คำสั่ง (Prompt)"** ที่คุณพิมพ์เข้ามาเท่านั้นครับ เพื่อป้องกันไม่ให้เกิดเหตุการณ์แบบนี้อีก คุณสามารถใช้เทคนิคการสั่งงานดังนี้เพื่อ **"บังคับ"** ให้ผมตรวจสอบข้อมูลครับ:
1. **สั่งให้ "อ่านและสรุปก่อน" (Verify First):**
* ก่อนให้ผมเขียนโค้ด ให้สั่งว่า *"ช่วยอ่านไฟล์ `01_lcbp3_v1_4_3.sql` แล้วสรุปโครงสร้างตาราง audit_logs ที่มีอยู่จริงให้ดูก่อน"*
* วิธีนี้จะบังคับให้ผมต้องไปดึงข้อมูลจากไฟล์มาประมวลผลก่อนที่จะเริ่ม "จินตนาการ" หรือเขียนโค้ดใหม่ครับ
- ก่อนให้ผมเขียนโค้ด ให้สั่งว่า _"ช่วยอ่านไฟล์ `01_lcbp3_v1_4_3.sql` แล้วสรุปโครงสร้างตาราง audit_logs ที่มีอยู่จริงให้ดูก่อน"_
- วิธีนี้จะบังคับให้ผมต้องไปดึงข้อมูลจากไฟล์มาประมวลผลก่อนที่จะเริ่ม "จินตนาการ" หรือเขียนโค้ดใหม่ครับ
2. **ใช้คำสั่ง "ห้ามเดา" (Strict Constraints):**
* เติมประโยคท้ายคำสั่งว่า *"ห้ามใช้โครงสร้างสมมติ ให้ยึดตามไฟล์แนบ `ชื่อไฟล์` เท่านั้น หากไม่เจอข้อมูลให้ถามกลับ"*
- เติมประโยคท้ายคำสั่งว่า _"ห้ามใช้โครงสร้างสมมติ ให้ยึดตามไฟล์แนบ `ชื่อไฟล์` เท่านั้น หากไม่เจอข้อมูลให้ถามกลับ"_
3. **อ้างอิงชื่อไฟล์ชัดเจน:**
* ระบุชื่อไฟล์ในคำสั่งเสมอ เช่น *"อ้างอิงจาก `Backend Plan`..."* หรือ *"ตาม `Data Dictionary`..."* (ซึ่งคุณทำได้ดีแล้วครับ แต่ผมประมวลผลพลาดเองในรอบนี้)
- ระบุชื่อไฟล์ในคำสั่งเสมอ เช่น _"อ้างอิงจาก `Backend Plan`..."_ หรือ _"ตาม `Data Dictionary`..."_ (ซึ่งคุณทำได้ดีแล้วครับ แต่ผมประมวลผลพลาดเองในรอบนี้)
**สรุป:**
คุณไม่ต้องไปตั้งค่าที่ไหนครับ แต่ใช้การ **"สั่งให้ผมทวนข้อมูลจากไฟล์"** เป็นขั้นตอนแรกก่อนเริ่มงาน จะช่วยลดโอกาสผิดพลาดได้มากที่สุดครับ
ตอนนี้ผมพร้อมทำงานต่อโดยยึดข้อมูลจากไฟล์ `01_lcbp3_v1_4_3.sql` และไฟล์อื่นๆ ที่คุณให้มาอย่างเคร่งครัดครับ มีส่วนไหนให้ผมดำเนินการต่อไหมครับ?
ช่วยอ่านไฟล์ 2_Backend_Plan_V1_4_4.md ในหัวข้อ T2.5.1-T2.5.9 และ โครงสร้างโมดูล แล้วสรุป รายการที่ต้องการสร้างหรือแก้ไขโมดูล ในแต่ละข้อก่อนเริ่มดำเนินการต่อครับ
สวัสดีครับ ผมได้ทำการตรวจสอบและวิเคราะห์ความครบถ้วนของไฟล์เอกสารแผนงาน (Plans), โครงสร้างฐานข้อมูล (SQL), และโครงสร้างไฟล์ (File Structure) เทียบกับ Requirements ของ **LCBP3-DMS v1.4.3** เรียบร้อยแล้วครับ
สวัสดีครับ ผมได้วิเคราะห์ไฟล์ **2_Backend_Plan_V1_4_4.md** ในส่วนของ **Phase 2: High-Integrity Data & File Management** โดยเฉพาะหัวข้อ **T2.5 JSON Details & Schema Management** (T2.5.1 - T2.5.9) เรียบร้อยแล้วครับ
ในฐานะ Programmer ผู้เชี่ยวชาญ ผมขอสรุปผลการ Review และคำแนะนำตามลำดับความสำคัญดังนี้ครับ:
นี่คือสรุปรายการโมดูล, ไฟล์ และฟังก์ชันที่ต้องสร้างหรือแก้ไข เพื่อให้ระบบจัดการ JSON Schema สมบูรณ์และมีประสิทธิภาพสูงสุดครับ
---
### 📊 **1. บทสรุปภาพรวม (Executive Summary)**
### 📂 1. โครงสร้างโมดูลที่ต้องสร้าง (Module Structure)
จากการตรวจสอบเปรียบเทียบ:
1. **Database Schema (`01_lcbp3_v1_4_3.sql`)**: **ครบถ้วนสมบูรณ์มาก (95%)** รองรับฟีเจอร์ระดับ Advance ตาม Requirements v1.4.3 ครบถ้วน เช่น Partitioning, Virtual Columns, Versioning (Optimistic Lock) และ Table Structure สำหรับ Workflow/Files
2. **Backend Folder Structure (`5_Backend_Folder_V1_4_3.md`)**: **ครบถ้วนตามแผนงาน (90%)** มีการจัดวาง Module ตามหลัก Domain-Driven Design และมีไฟล์สำคัญๆ ครบตาม Plan
เราจำเป็นต้องสร้าง `JsonSchemaModule` ขึ้นมาใหม่ โดยภายในจะประกอบด้วย Services ย่อยตามหน้าที่งาน เพื่อให้เป็นไปตามหลัก Single Responsibility Principle ครับ
**จุดที่ต้องเน้นย้ำ:** โครงสร้างไฟล์ (File Structure) มีไฟล์ `.ts` ปรากฏอยู่ครบ แต่สิ่งที่ผม "มองไม่เห็น" คือ **เนื้อหาโค้ดภายใน (Internal Logic)** ว่าได้ Implement ตาม Logic ที่ซับซ้อน (เช่น Redlock, Two-Phase Commit, DSL Parser) ไว้ถูกต้องหรือไม่
**Path:** `src/modules/json-schema/`
---
### 🧐 **2. การตรวจสอบรายละเอียด (Detailed Review)**
#### **✅ ส่วนที่มีความครบถ้วนสมบูรณ์ (Well-Implemented Areas)**
1. **Database Partitioning & Optimization:**
* SQL มีการทำ `PARTITION BY RANGE` ให้กับ `audit_logs` และ `notifications` แล้ว (ตาม Req 6.2)
* มี `VIRTUAL COLUMNS` สำหรับดึงค่า JSON มาทำ Index (ตาม Req 3.11.3)
2. **Security & Identity:**
* ตาราง `users`, `roles`, `permissions`, `user_assignments` รองรับ RBAC 4 ระดับ (Global, Org, Project, Contract) ครบถ้วน
* Folder Structure มี `auth`, `guards`, `decorators` ที่จำเป็นครบ (`jwt-auth.guard.ts`, `rbac.guard.ts`, `require-permission.decorator.ts`)
3. **File Management:**
* SQL มีตาราง `attachments` ที่มี field `is_temporary`, `temp_id`, `checksum` รองรับ Two-Phase Storage
* Folder มี `file-storage` module และ `file-cleanup.service.ts` (Cron job) เตรียมไว้แล้ว
4. **Workflow & RFA:**
* SQL มีตารางเก็บ Template และ Instance (`..._routings`, `..._workflows`) แยกกันชัดเจน และมี field `workflow_config` (JSON) สำหรับ DSL
* Folder มี `workflow-engine` module และ `workflow-dsl.service.ts` (ตาม Phase 6A)
---
#### ⚠️ **3. Gap Analysis & Missing Parts (สิ่งที่อาจขาดหายไปหรือต้องตรวจสอบ)**
แม้ชื่อไฟล์จะครบ แต่จากประสบการณ์ มีจุดที่มักจะหลุด หรือต้องเขียน Logic ให้รัดกุมมากในไฟล์ที่มีอยู่ ดังนี้:
**Phase 2: High-Integrity Data (File & Numbering)**
* **Redis/Redlock Integration:** ในไฟล์ `document-numbering.service.ts` ต้องตรวจสอบว่ามีการเรียกใช้ Redis Distributed Lock จริงหรือไม่ (ไม่ใช่แค่ Database Lock)
* **Transaction Safety:** ใน `file-storage.service.ts` ต้องมั่นใจว่า Logic การ Commit file (ย้ายจาก temp -> permanent) อยู่ภายใน Database Transaction เดียวกับการบันทึกข้อมูลเอกสาร
**Phase 3 & 6A: Workflow Engine**
* **DSL Interpreter:** ไฟล์ `workflow-dsl.service.ts` มีอยู่แล้ว แต่ต้องตรวจสอบว่าสามารถ **Parse** JSON/YAML จากตาราง `rfa_workflow_templates` แล้วแปลงเป็น Execution Logic ได้จริงหรือไม่
* **State Machine Consistency:** การเชื่อมต่อระหว่าง `CorrespondenceModule` กับ `WorkflowEngine` ต้องแน่ใจว่าเมื่อ State เปลี่ยนใน Engine ค่าใน Database (Status Column) ถูกอัปเดตตรงกัน
**Phase 6: Search & Notification**
* **Elasticsearch Sync:** ใน `search.service.ts` ต้องมี Logic การ Sync ข้อมูล (อาจผ่าน Event Emitter หรือ TypeORM Subscriber) เมื่อมีการ Create/Update เอกสาร
* **Notification Digest:** ใน `notification.processor.ts` (Consumer) ต้องมี Logic การหน่วงเวลา (Debounce/Throttle) เพื่อรวม Notification ก่อนส่ง (ตาม Req 6.8.2)
---
### 🚀 **4. คำแนะนำและสิ่งที่ต้องทำต่อ (Recommendations by Phase)**
เพื่อให้งานสมบูรณ์แบบ ผมขอแนะนำให้ดำเนินการตรวจสอบหรือ Implement ส่วนที่เหลือตามลำดับดังนี้:
#### **Phase 0-1: Infrastructure & Auth (Priority: Critical)**
* **[Audit] Database Migration:** แม้จะมีไฟล์ `.sql` รวม แต่ใน NestJS ควรมีระบบ Migration (TypeORM Migrations) เพื่อจัดการการเปลี่ยนแปลง Schema ในอนาคต (ถ้ายังไม่มี ควรสร้าง `src/database/migrations/*` จาก SQL ที่มี)
* **[Code Check] Config Validation:** ตรวจสอบ `src/common/config/env.validation.ts` ว่ามีการ Validate ตัวแปรระบบครบทุกตัว (Database, Redis, JWT Secrets) ตาม Req 2.2 หรือไม่
#### **Phase 2: Core Data Services (Priority: High)**
* **[Code Check] Document Numbering:** เปิดไฟล์ `src/modules/document-numbering/document-numbering.service.ts` ตรวจสอบ Logic **Double-Lock** (Redis + Optimistic Lock) ว่า Implement ถูกต้องตาม Plan T2.3 หรือไม่
* **[Code Check] File Storage:** ตรวจสอบ `src/common/file-storage/file-storage.service.ts` ว่ามีการทำ **Virus Scan (ClamAV)** ก่อน save ลง Temp หรือไม่
#### **Phase 3 & 6A: Workflow Engine (Priority: Medium-High)**
* **[Task] Workflow Integration:** ตรวจสอบว่า `CorrespondenceService` และ `RfaService` มีการเรียกใช้ `WorkflowEngineService` แทนที่จะเขียน Logic if-else hardcode หรือไม่
* **[Task] DSL Loader:** ตรวจสอบว่ามี Seed Data สำหรับ `rfa_workflow_templates` ที่เป็น JSON Config เริ่มต้นแล้วหรือยัง
#### **Phase 6: Background Services (Priority: Medium)**
* **[Task] Queue Setup:** ตรวจสอบ `notification.module.ts` ว่ามีการ Register BullMQ (`BullModule.registerQueue`) แล้วหรือยัง
* **[Task] Cron Jobs:** ตรวจสอบ `app.module.ts` ว่ามีการ Import `ScheduleModule.forRoot()` เพื่อให้ `file-cleanup.service.ts` ทำงานได้จริง
---
### 🛠️ **5. Next Step Action (สิ่งที่คุณทำได้เลย)**
เนื่องจากโครงสร้างไฟล์และฐานข้อมูลดูดีมากแล้ว เพื่อให้ผมช่วยเหลือได้ตรงจุดที่สุด:
**คุณต้องการให้ผม Review โค้ดภายในไฟล์ (Internal Logic) ของไฟล์ไหนเป็นพิเศษไหมครับ?**
ผมแนะนำให้เริ่มจาก **"หัวใจสำคัญ"** ของความถูกต้องข้อมูลก่อน คือ:
1. **`src/modules/document-numbering/document-numbering.service.ts`** (เพื่อดู Logic การรันเลขเอกสาร)
2. **`src/common/file-storage/file-storage.service.ts`** (เพื่อดู Logic การจัดการไฟล์และการ Commit)
หรือถ้าต้องการให้ผมเริ่มเขียน Code ส่วนไหนที่ยังขาดอยู่ (เช่น Workflow DSL Parser) สามารถแจ้งได้เลยครับ
สวัสดีครับ ในฐานะ Programmer ที่ดูแลระบบ LCBP3-DMS ผมได้ทำการ **Review เอกสาร `6B.md` (Special requirements for document-numbering)** โดยเทียบกับโครงสร้างฐานข้อมูลและแผนงานที่มีอยู่ (v1.4.3) พบประเด็นสำคัญและการปรับปรุงทางเทคนิคที่จำเป็นดังนี้ครับ
-----
# 📝 ผลการ Review เอกสาร 6B.md และแนวทางการ Implement
## 1\. บทวิเคราะห์และผลกระทบ (Analysis & Impact)
เอกสาร `6B.md` ระบุความต้องการที่ **ซับซ้อนกว่า** ระบบ Running Number ทั่วไป (ที่มักเป็น `ORG-TYPE-YEAR-SEQ`) โดยมีประเด็นสำคัญคือ:
1. **Requirement 1 (Flexibility):** Admin ต้องแก้ format ได้ → **รองรับได้** ด้วยตาราง `document_number_formats` ที่มีอยู่ แต่ต้องปรับ Logic การแทนค่าตัวแปร (Token Replacement) ให้ฉลาดขึ้น
2. **Requirement 2.1 (Correspondence ทั่วไป):** รูปแบบมาตรฐาน → **รองรับได้** ด้วยโครงสร้างปัจจุบัน
3. **Requirement 2.2 (Transmittal):** มีเงื่อนไข "To OWNER" vs "To CONTRACTOR" ใช้เลขต่างกัน และมีการ mapping `sub_type_number`**Impact:** ต้องเพิ่ม Logic ในการเลือก Format Template ตาม "ผู้รับ" (Recipient Role) และต้องมีตารางเก็บ Mapping `sub_type_number`
4. **Requirement 2.3 (RFI) & 2.4 (RFA):** มีการใช้ `disciplines_code` (เช่น GEN, STR, ARC) และไม่มีปี (Year) ใน Format → **Impact:**
* ฐานข้อมูลปัจจุบันขาดตาราง `disciplines`
* ตาราง `document_number_counters` ปัจจุบัน Key คือ `(Project, Org, Type, Year)` แต่ RFA/RFI อาจจะต้องนับแยกตาม **Discipline** ด้วย หากไม่เพิ่ม Column ใน Counter อาจจะเกิดปัญหาเลขซ้ำหรือเลขกระโดดข้ามหมวดงาน
-----
## 2\. การออกแบบฐานข้อมูลเพิ่มเติม (Database Schema Updates)
เพื่อให้รองรับ Requirement นี้ ต้องมีการเพิ่มตารางและแก้ไขตาราง `01_lcbp3_v1_4_3.sql` ดังนี้:
### 2.1 เพิ่มตาราง `disciplines` และ `correspondence_sub_types`
```sql
-- File: 01_lcbp3_v1_4_3_patch_6b.sql
-- เพิ่มตารางหมวดงาน (Disciplines) ตาม Req 6B
CREATE TABLE disciplines (
id INT PRIMARY KEY AUTO_INCREMENT,
project_id INT NOT NULL,
discipline_code VARCHAR(10) NOT NULL COMMENT 'เช่น GEN, STR, ARC',
name_th VARCHAR(255) COMMENT 'ชื่อไทย',
name_en VARCHAR(255) COMMENT 'ชื่ออังกฤษ',
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
UNIQUE KEY uk_discipline_project (project_id, discipline_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- เพิ่มตาราง Sub Types (สำหรับ Transmittal/RFA mapping)
CREATE TABLE correspondence_sub_type_codes (
id INT PRIMARY KEY AUTO_INCREMENT,
project_id INT NOT NULL,
correspondence_type_id INT NOT NULL,
sub_type_code VARCHAR(20) NOT NULL COMMENT 'เช่น MAT, SHP',
sub_type_number VARCHAR(10) COMMENT 'เลขรหัส เช่น 11, 22',
description VARCHAR(255),
FOREIGN KEY (project_id) REFERENCES projects(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;
```text
src/modules/json-schema
├── 📁 dto
│ ├── create-json-schema.dto.ts # DTO สำหรับสร้าง Schema ใหม่
│ ├── update-json-schema.dto.ts # DTO สำหรับแก้ไข Schema
│ ├── validate-data.dto.ts # DTO สำหรับรับข้อมูลมา Validate
│ └── migrate-data.dto.ts # DTO สำหรับสั่ง Migrate Data
├── 📁 entities
└── json-schema.entity.ts # Entity หลักเก็บ Definition (T2.5.1)
├── 📁 services
├── json-schema.service.ts # Core Service (CRUD, Validation wrapper)
├── virtual-column.service.ts # จัดการ Generated Columns ใน DB (T2.5.3)
│ ├── schema-migration.service.ts # จัดการ Data Migration ระหว่าง Version (T2.5.5)
└── json-security.service.ts # จัดการ Security & Encryption (T2.5.6)
├── 📁 interfaces
│ ├── ui-schema.interface.ts # Type definition สำหรับ UI Schema (T2.5.4)
└── virtual-column.interface.ts # Type definition สำหรับ Virtual Column Config
├── json-schema.controller.ts # API Endpoints (T2.5.7)
├── json-schema.module.ts # Module Definition
└── json-schema.service.spec.ts # Unit Tests (T2.5.9)
```
### 2.2 ปรับปรุงตาราง `rfas` และ `document_number_counters`
---
เนื่องจาก RFA/RFI ต้องวิ่งเลขตาม Discipline เราจึงต้องเก็บ Discipline ไว้ใน RFA และต้องใช้เป็นส่วนหนึ่งของ Key ในการนับเลข
### 📝 2. รายละเอียดงานในแต่ละ Task (Tasks Breakdown)
```sql
-- เพิ่ม discipline_id ในตาราง RFAs
ALTER TABLE rfas
ADD COLUMN discipline_id INT NULL AFTER rfa_type_id,
ADD CONSTRAINT fk_rfa_discipline FOREIGN KEY (discipline_id) REFERENCES disciplines(id);
#### **[ ] T2.5.1 JSON Schema Registry & Versioning**
-- เพิ่ม discipline_id ในตาราง Correspondences (สำหรับ RFI)
ALTER TABLE correspondences
ADD COLUMN discipline_id INT NULL AFTER correspondence_type_id,
ADD CONSTRAINT fk_corr_discipline FOREIGN KEY (discipline_id) REFERENCES disciplines(id);
- **สิ่งที่ต้องทำ:** สร้าง `JsonSchema` Entity
- **รายละเอียด:**
- เก็บ `schema_definition` (AJV format)
- เก็บ `ui_schema` (Form configuration)
- รองรับ `version` เพื่อทำ Schema Evolution
- เก็บ config ของ `virtual_columns`
-- ปรับปรุงตาราง Counter ให้รองรับ Discipline และ SubType (ใช้เป็น Dynamic Key หรือเพิ่ม Column)
-- เพื่อความ Performance และ Integrity แนะนำให้เพิ่ม Column
ALTER TABLE document_number_counters
ADD COLUMN discipline_id INT NULL DEFAULT NULL AFTER correspondence_type_id,
DROP PRIMARY KEY,
ADD PRIMARY KEY (project_id, originator_organization_id, correspondence_type_id, discipline_id, current_year);
-- หมายเหตุ: ถ้า discipline_id เป็น NULL ให้ถือว่าเป็น Counter ทั่วไป
```
#### **[ ] T2.5.2 Schema Validation & Transformation Engine**
-----
- **สิ่งที่ต้องทำ:** Implement Logic ใน `JsonSchemaService`
- **รายละเอียด:**
- ติดตั้งและ Config `AJV` library
- สร้าง Custom Keywords: `document-number`, `requiredRole`
- ฟังก์ชัน `validateData(schemaName, data)`
- ฟังก์ชัน `sanitizeData()` เพื่อลบ field ที่ไม่อยู่ใน schema หรือ field ที่ user ไม่มีสิทธิ์
## 3\. แนวทางการ Implement ใน NestJS (Backend Logic)
#### **[ ] T2.5.3 Virtual Columns & Performance Optimization**
เราต้องปรับ `DocumentNumberingService` ให้เป็น **Token-Based Generator** ที่มีความยืดหยุ่นสูง
- **สิ่งที่ต้องทำ:** สร้าง `VirtualColumnService`
- **รายละเอียด:**
- ฟังก์ชัน `setupVirtualColumns()`: อ่าน Config จาก Schema แล้วสั่ง `ALTER TABLE ... ADD COLUMN ... GENERATED ALWAYS AS ...`
- ฟังก์ชันสร้าง Index บน Virtual Column เพื่อให้ค้นหาเร็วขึ้น
- **สำคัญ:** ต้องรองรับ MariaDB 10.11 Syntax
### 3.1 รูปแบบ Template (Format Templates)
#### **[ ] T2.5.4 Dynamic Form Schema Management**
Admin จะบันทึก Template ลง DB ในรูปแบบ:
- **สิ่งที่ต้องทำ:** กำหนด Interface ใน `ui-schema.interface.ts`
- **รายละเอียด:**
- ออกแบบโครงสร้าง JSON ที่ Frontend จะนำไป Render เป็น Form
- กำหนด Logic `dependencies` (เช่น ถ้าเลือก Type A ให้แสดง Field B)
* General: `{ORG}-{ORG}-{SEQ:4}-{YEAR}`
* Transmittal: `{ORG}-{ORG}-{SUBTYPE_NUM}-{SEQ:4}-{YEAR}`
* RFA: `{CONTRACT}-{TYPE}-{DISCIPLINE}-{SUBTYPE}-{SEQ:4}-{REV}`
#### **[ ] T2.5.5 Data Migration & Version Compatibility**
### 3.2 Logic การ Generate (Pseudo Code)
- **สิ่งที่ต้องทำ:** สร้าง `SchemaMigrationService`
- **รายละเอียด:**
- Logic การแปลงข้อมูลเก่าให้เข้ากับ Schema ใหม่ (Field Rename, Transform Value)
- ฟังก์ชัน `migrateData(entityType, entityId, targetVersion)`
```typescript
// File: src/modules/document-numbering/document-numbering.service.ts
#### **[ ] T2.5.6 Security & Access Control for JSON Data**
/**
* Strategy:
* 1. รับค่า Context (Project, Org, Type, Discipline, SubType, Year)
* 2. ดึง Format Template จาก DB ตาม Type
* 3. ถ้าเป็น Transmittal ต้องเช็คเงื่อนไขพิเศษเพื่อเลือก Template (Owner vs Contractor)
* 4. Parse Template เพื่อหาว่าต้องใช้ Key ไหนบ้างในการ Lock และ Count
* 5. Run Redlock + Optimistic Lock
* 6. Replace Tokens
*/
- **สิ่งที่ต้องทำ:** สร้าง `JsonSecurityService`
- **รายละเอียด:**
- **Field-level Security:** ตรวจสอบ Role ว่าเห็น Field นี้ได้ไหม (Masking/Removal)
- **Encryption:** เข้ารหัส Field ที่ Sensitive ก่อนบันทึกลง JSON
async generateNextNumber(ctx: GenerateNumberContext): Promise<string> {
// 1. Get Template
let template = await this.formatRepo.findOne({ ... });
#### **[ ] T2.5.7 API Design & Integration**
// 2. Handle Special Logic (Requirement 2.2 Transmittal)
if (ctx.typeCode === 'TRANSMITTAL') {
// Logic: ถ้าส่งให้ Owner ใช้ Template A, ถ้า Contractor ใช้ Template B
// หรือดึง sub_type_number มาเตรียมไว้
}
- **สิ่งที่ต้องทำ:** สร้าง `JsonSchemaController`
- **Endpoints:**
- `POST /json-schema/schemas`: สร้าง Schema
- `POST /json-schema/validate/:name`: ตรวจสอบข้อมูล
- `POST /json-schema/migrate/:type/:id`: สั่ง Migrate
- `GET /json-schema/ui-schema/:name`: ดึง UI Config
// 3. Resolve Tokens Values
const tokens = {
'{ORG}': ctx.orgCode,
'{CONTRACT}': ctx.contractCode,
'{TYPE}': ctx.typeCode,
'{DISCIPLINE}': ctx.disciplineCode, // จากตาราง disciplines
'{SUBTYPE}': ctx.subTypeCode, // จาก rfa_types หรือ sub_type_codes
'{SUBTYPE_NUM}': ctx.subTypeNumber, // จาก correspondence_sub_type_codes
'{YEAR}': ctx.year.toString(),
'{YEAR_SHORT}': ctx.year.toString().slice(-2),
};
#### **[ ] T2.5.8 Integration with Document Modules**
// 4. Construct Counter Key
// ถ้า Template มี {DISCIPLINE} ต้องเอา discipline_id มาเป็นส่วนหนึ่งของ Composite Key ใน DB Counter
const counterKey = {
projectId: ctx.projectId,
orgId: ctx.orgId,
typeId: ctx.typeId,
disciplineId: template.includes('{DISCIPLINE}') ? ctx.disciplineId : null,
year: template.includes('{YEAR}') ? ctx.year : 0 // ถ้าไม่ใช้ปี ให้ใช้ 0
};
- **สิ่งที่ต้องทำ:** แก้ไข Service อื่นๆ (Correspondence, RFA)
- **รายละเอียด:**
- Inject `JsonSchemaService` เข้าไปใน `CorrespondenceService` และ `RfaService`
- เรียก `validateData` ก่อน create/update
- เรียก `setupVirtualColumns` หลัง create schema ใหม่
// 5. Execute Double-Lock Mechanism (Redis + DB Optimistic)
const seq = await this.runDistributedCounter(counterKey);
#### **[ ] T2.5.9 Testing Strategy**
// 6. Final Replace
let finalNumber = template.formatTemplate;
for (const [key, value] of Object.entries(tokens)) {
finalNumber = finalNumber.replace(key, value || '00'); // Fallback if missing
}
// Replace SEQ with padding
finalNumber = finalNumber.replace(/{SEQ:(\d+)}/, (_, width) =>
String(seq).padStart(Number(width), '0')
);
- **สิ่งที่ต้องทำ:** เขียน Unit Test
- **รายละเอียด:**
- ทดสอบ Validation Logic (ผ่าน/ไม่ผ่าน)
- ทดสอบ Virtual Column Generation (SQL ถูกต้องไหม)
- ทดสอบ Security Masking
return finalNumber;
}
```
---
-----
### 🚀 ขั้นตอนถัดไป
## 4\. แผนการดำเนินการ (Action Plan)
หากท่านเห็นด้วยกับโครงสร้างนี้ ผมแนะนำให้เริ่มดำเนินการตามลำดับดังนี้ครับ:
เพื่อให้รองรับ `6B.md` ได้สมบูรณ์ ผมขอเสนอให้ดำเนินการดังนี้:
1. **สร้าง Entity และ DTOs** (T2.5.1)
2. **Implement JsonSchemaService Core** (Validation Logic) (T2.5.2)
3. **Implement VirtualColumnService** (T2.5.3) - _ส่วนนี้สำคัญมากสำหรับ Performance_
4. **สร้าง Controller และเชื่อมต่อ Module** (T2.5.7)
1. **Database Migration:**
* รัน SQL Script เพื่อเพิ่มตาราง `disciplines`, `correspondence_sub_type_codes`.
* Alter ตาราง `rfas`, `correspondences` และ `document_number_counters` ตามข้อ 2.2
2. **Seed Data:**
* นำข้อมูลจากตารางใน `6B.md` (Disciplines, RFA Types, Sub Types) มาเขียนเป็น Seed Script ลงฐานข้อมูล เพื่อให้เริ่มต้นระบบมีข้อมูลพร้อมใช้ทันที
3. **Backend Implementation:**
* Update `DocumentNumberingService` ให้รองรับ Dynamic Token Replacement
* Update `CreateRfaDto` และ `CreateCorrespondenceDto` ให้รับค่า `discipline_id` และ `sub_type_id` (ถ้ามี)
4. **Admin Panel:**
* ต้องมีหน้า UI ให้ Admin จัดการ Mapping ของ `Format Template` และ `Sub Type Codes` ได้ (ตาม Requirement ข้อ 1)
**ต้องการให้ผมเริ่มเขียน SQL Migration Script และ Seed Data สำหรับข้อมูลใน 6B.md เลยหรือไม่ครับ?** (ข้อมูลในตารางค่อนข้างเยอะ การทำ Seed ไว้จะช่วยลดเวลา Admin ในการกรอกข้อมูลเริ่มต้นได้มากครับ)
จาก แผนการดำเนินการ (Action Plan): Database Migration และ Seed Data, ผมได้อัพเดท Basic data ข้อ 5. Data files ให้แล้ว, ช่วย review และ ปรับปรุง 4_Data_Dictionary_V1_4_3.md ,
เตรียม Backend Implementation, Admin Panel
### **Phase 2: High-Integrity Data & File Management (สัปดาห์ที่ 4)**
* **[ ] T2.3 DocumentNumberingModule - Token-Based & Double-Lock** (Updated)
* [ ] Update Entity: `DocumentNumberCounter` (Add `discipline_id` to PK)
* [ ] Implement Token Parser & Replacer Logic (`{DISCIPLINE}`, `{SUBTYPE_NUM}`)
* [ ] Update `generateNextNumber` to handle optional keys (Discipline/SubType)
* [ ] **Deliverable:** Flexible Numbering System
* **[ ] T2.6 MasterModule - Advanced Data (Req 6B)** (New)
* [ ] Update Entities: `Discipline`, `CorrespondenceSubType`
* [ ] Create Services/Controllers for CRUD Operations (Admin Panel Support)
* [ ] Implement Seeding Logic for initial 6B data
* [ ] **Deliverable:** API for managing Disciplines and Sub-types
* [ ] **Dependencies:** T1.1, T0.3
ท่านต้องการให้ผมเริ่มเขียนโค้ดส่วนไหนก่อนหรือไม่ครับ? (เช่น Entity หรือ VirtualColumnService)

0
file
View File

3
frontend/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"jest.rootPath": "/"
}

View File

@@ -19,9 +19,18 @@
}
],
"paths": {
"@/*": ["./*"]
"baseUrl": "./",
"@/*": ["app/*"],
"@components": ["components/*"],
"@config": ["config/*"],
"@lib": ["lib/*"],
"@providers": ["providers/*"],
"@public": ["public/*"],
"@styles": ["styles/*"],
"@types": ["types/*"],
"@api": ["app/api/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
}

View File

@@ -123,36 +123,51 @@
],
"eslint.alwaysShowStatus": true,
"eslint.format.enable": false,
"eslint.workingDirectories": ["./backend", "./frontend"],
"eslint.lintTask.enable": true,
"eslint.workingDirectories": [
"./backend",
"./frontend"
],
// ========================================
// PATH INTELLISENSE - ไม่มี src folder
// ========================================
"path-intellisense.mappings": {
// Backend paths (ไม่มี src)
"@backend": "${workspaceFolder:🔧 Backend}",
"@backend/*": "${workspaceFolder:🔧 Backend}/*",
"@modules": "${workspaceFolder:🔧 Backend}/modules",
"@common": "${workspaceFolder:🔧 Backend}/common",
"@config": "${workspaceFolder:🔧 Backend}/config",
"@utils": "${workspaceFolder:🔧 Backend}/utils",
"@entities": "${workspaceFolder:🔧 Backend}/entities",
"@dto": "${workspaceFolder:🔧 Backend}/dto",
// Backend paths
"@backend": "${workspaceFolder:🔧 Backend (NestJS)}/src",
"@backend/*": "${workspaceFolder:🔧 Backend (NestJS)}/src/*",
"@modules": "${workspaceFolder:🔧 Backend (NestJS)}/src/modules",
"@common": "${workspaceFolder:🔧 Backend (NestJS)}/src/common",
"@config": "${workspaceFolder:🔧 Backend (NestJS)}/src/common/config",
"@circulation": "${workspaceFolder:🔧 Backend (NestJS)}/src/modules/circulation",
"@correspondence": "${workspaceFolder:🔧 Backend (NestJS)}/src/modules/correspondence",
"@document-numbering": "${workspaceFolder:🔧 Backend (NestJS)}/src/modules/document-numbering",
"@drawing": "${workspaceFolder:🔧 Backend (NestJS)}/src/modules/drawing",
"@json-schema": "${workspaceFolder:🔧 Backend (NestJS)}/src/modules/json-schema",
"@master": "${workspaceFolder:🔧 Backend (NestJS)}/src/modules/master",
"@monitoring": "${workspaceFolder:🔧 Backend (NestJS)}/src/modules/monitoring",
"@notification": "${workspaceFolder:🔧 Backend (NestJS)}/src/modules/notification",
"@project": "${workspaceFolder:🔧 Backend (NestJS)}/src/modules/project",
"@rfa": "${workspaceFolder:🔧 Backend (NestJS)}/src/modules/rfa",
"@search": "${workspaceFolder:🔧 Backend (NestJS)}/src/modules/search",
"@transmittal": "${workspaceFolder:🔧 Backend (NestJS)}/src/modules/transmittal",
"@users": "${workspaceFolder:🔧 Backend (NestJS)}/src/modules/users",
"@workflow-engine": "${workspaceFolder:🔧 Backend (NestJS)}/src/modules/workflow-engine",
// Frontend paths (ไม่มี src)
"@": "${workspaceFolder:🎨 Frontend}",
"@/*": "${workspaceFolder:🎨 Frontend}/*",
"@components": "${workspaceFolder:🎨 Frontend}/components",
"@hooks": "${workspaceFolder:🎨 Frontend}/hooks",
"@utils": "${workspaceFolder:🎨 Frontend}/utils",
"@lib": "${workspaceFolder:🎨 Frontend}/lib",
"@types": "${workspaceFolder:🎨 Frontend}/types",
"@api": "${workspaceFolder:🎨 Frontend}/api",
"@styles": "${workspaceFolder:🎨 Frontend}/styles",
"@assets": "${workspaceFolder:🎨 Frontend}/assets",
"@store": "${workspaceFolder:🎨 Frontend}/store",
"@contexts": "${workspaceFolder:🎨 Frontend}/contexts"
"@": "${workspaceFolder:🎨 Frontend (React/Next.js)}/app",
"@/*": "${workspaceFolder:🎨 Frontend (React/Next.js)}/app/*",
"@app": "${workspaceFolder:🎨 Frontend (React/Next.js)}/app",
"@components": "${workspaceFolder:🎨 Frontend (React/Next.js)}/components",
"@config": "${workspaceFolder:🎨 Frontend (React/Next.js)}/config",
"@lib": "${workspaceFolder:🎨 Frontend (React/Next.js)}/lib",
"@hooks": "${workspaceFolder:🎨 Frontend (React/Next.js)}/app/hooks",
"@utils": "${workspaceFolder:🎨 Frontend (React/Next.js)}/utils",
"@providers": "${workspaceFolder:🎨 Frontend (React/Next.js)}/providers",
"@public": "${workspaceFolder:🎨 Frontend (React/Next.js)}/public",
"@styles": "${workspaceFolder:🎨 Frontend (React/Next.js)}/styles",
"@types": "${workspaceFolder:🎨 Frontend (React/Next.js)}/types",
"@api": "${workspaceFolder:🎨 Frontend (React/Next.js)}/app/api",
},
"path-intellisense.autoSlashAfterDirectory": true,
"path-intellisense.extensionOnImport": false,
@@ -187,7 +202,7 @@
// ระบุที่ตั้งของ tailwind.config (ถ้ามี)
"tailwindCSS.experimental.configFile": {
"frontend/tailwind.config.js": "frontend/**",
//"backend/tailwind.config.js": "backend/**",
"frontend/tailwind.config.ts": "frontend/**"
},
@@ -261,9 +276,18 @@
// TODO TREE
// ========================================
"todo-tree.general.tags": ["TODO", "FIXME", "BUG", "HACK", "NOTE", "XXX"],
"todo-tree.general.tags": [
"TODO",
"FIXME",
"BUG",
"HACK",
"NOTE",
"XXX",
"[ ]",
"[x]"
],
"todo-tree.highlights.enabled": true,
"todo-tree.tree.showInExplorer": true,
"todo-tree.tree.showScanModeButton": true,
"todo-tree.filtering.excludeGlobs": [
"**/node_modules",
"**/dist",
@@ -290,6 +314,11 @@
"icon": "note",
"iconColour": "#3498DB",
"foreground": "#3498DB"
},
"HACK": {
"icon": "tools",
"iconColour": "#FFA500",
"foreground": "#FFA500"
}
},
@@ -302,7 +331,13 @@
"gitlens.currentLine.format": "${author}, ${agoOrDate}",
"gitlens.codeLens.enabled": true,
"gitlens.codeLens.authors.enabled": false,
"gitlens.codeLens.recentChange.enabled": true,
"gitlens.hovers.enabled": true,
"gitlens.blame.format": "${author|10} ${agoOrDate|14-}",
"gitlens.blame.highlight.enabled": false,
"gitlens.views.repositories.location": "scm",
"gitlens.views.fileHistory.location": "explorer",
"gitlens.views.lineHistory.location": "explorer",
// ========================================
// GIT
@@ -313,6 +348,8 @@
"git.autofetchPeriod": 180,
"git.confirmSync": false,
"git.enableSmartCommit": true,
"git.postCommitCommand": "none",
"git.untrackedChanges": "separate",
"git.openRepositoryInParentFolders": "always",
// ========================================
@@ -323,6 +360,9 @@
"importCost.largePackageSize": 100,
"importCost.mediumPackageSize": 50,
"importCost.smallPackageSize": 20,
"importCost.largePackageColor": "#FF2D00",
"importCost.mediumPackageColor": "#FF8C00",
"importCost.smallPackageColor": "#98C379",
// ========================================
// JAVASCRIPT/TYPESCRIPT
@@ -332,12 +372,15 @@
"javascript.updateImportsOnFileMove.enabled": "always",
"javascript.inlayHints.parameterNames.enabled": "all",
"javascript.inlayHints.functionLikeReturnTypes.enabled": true,
"javascript.inlayHints.variableTypes.enabled": true,
"javascript.preferences.importModuleSpecifier": "relative",
"typescript.suggest.autoImports": true,
"typescript.updateImportsOnFileMove.enabled": "always",
"typescript.inlayHints.parameterNames.enabled": "all",
"typescript.inlayHints.functionLikeReturnTypes.enabled": true,
"typescript.inlayHints.variableTypes.enabled": false,
"typescript.inlayHints.propertyDeclarationTypes.enabled": true,
"typescript.preferences.importModuleSpecifier": "relative",
"typescript.tsdk": "node_modules/typescript/lib",
@@ -350,19 +393,21 @@
"typescript": "typescriptreact"
},
"emmet.triggerExpansionOnTab": true,
"emmet.showSuggestionsAsSnippets": true,
// ========================================
// FILES
// ========================================
"files.autoSave": "onFocusChange",
//"files.autoSave": "onFocusChange",
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
"files.encoding": "utf8",
"files.eol": "\n",
"files.associations": {
"*.css": "tailwindcss",
".env*": "dotenv"
".env*": "dotenv",
"*.md": "markdown"
},
"files.exclude": {
@@ -373,15 +418,19 @@
"**/dist": true,
"**/build": true,
"**/.turbo": true,
"**/coverage": true
"**/coverage": true,
"**/.nyc_output": true,
"**/*.log": true
},
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/node_modules/**": true,
"**/.next/**": true,
"**/dist/**": true,
"**/build/**": true
"**/build/**": true,
"**/.turbo/**": true
},
// ========================================
@@ -393,11 +442,15 @@
"**/dist": true,
"**/build": true,
"**/.next": true,
"**/.turbo": true,
"**/coverage": true,
"**/.nyc_output": true,
"**/yarn.lock": true,
"**/package-lock.json": true,
"**/pnpm-lock.yaml": true
"**/pnpm-lock.yaml": true,
"**/*.log": true
},
"search.followSymlinks": false,
"search.useIgnoreFiles": true,
"search.smartCase": true,
@@ -405,10 +458,12 @@
// TERMINAL
// ========================================
"terminal.integrated.fontSize": 13,
"terminal.integrated.fontSize": 14,
"terminal.integrated.lineHeight": 1.2,
"terminal.integrated.smoothScrolling": true,
"terminal.integrated.defaultProfile.windows": "Git Bash",
"terminal.integrated.cursorBlinking": true,
"terminal.integrated.fontFamily": "MesloLGS NF, Consolas, monospace",
"terminal.integrated.defaultProfile.windows": "PowerShell",
"terminal.integrated.cwd": "${workspaceFolder}",
// ========================================
@@ -420,8 +475,20 @@
"workbench.tree.indent": 15,
"workbench.list.smoothScrolling": true,
"workbench.editor.enablePreview": false,
"workbench.editor.limit.enabled": true,
"workbench.editor.limit.value": 10,
"workbench.startupEditor": "welcomePage",
"workbench.colorCustomizations": {
"[One Dark Pro]": {
"activityBarBadge.background": "#FF8C00",
"list.activeSelectionForeground": "#FF8C00",
"list.inactiveSelectionForeground": "#FF8C00",
"list.highlightForeground": "#FF8C00",
"scrollbarSlider.activeBackground": "#FF8C0050",
"editorSuggestWidget.highlightForeground": "#FF8C00",
"textLink.foreground": "#FF8C00"
}
},
// ========================================
// EXPLORER
// ========================================
@@ -443,29 +510,44 @@
"*.jsx": "${capture}.test.jsx, ${capture}.spec.jsx"
},
// ========================================
// BREADCRUMBS
// ========================================
"breadcrumbs.enabled": true,
"breadcrumbs.filePath": "on",
"breadcrumbs.symbolPath": "on",
// ========================================
// JEST
// ========================================
"jest.autoRun": "off",
"jest.showCoverageOnLoad": false,
"jest.rootPath": "backend",
// ========================================
// DOCKER
// ========================================
"docker.languageserver.formatter.ignoreMultilineInstructions": true,
"docker.showStartPage": false,
// ========================================
// YAML
// ========================================
"yaml.schemas": {
"https://json.schemastore.org/github-workflow.json": ".github/workflows/*.{yml,yaml}",
"https://json.schemastore.org/github-action.json": "action.{yml,yaml}",
"https://json.schemastore.org/prettierrc.json": ".prettierrc.{yml,yaml}",
"https://json.schemastore.org/docker-compose.json": "docker-compose*.{yml,yaml}"
},
"yaml.format.enable": true,
"yaml.format.singleQuote": false,
"yaml.format.bracketSpacing": true,
"yaml.validate": true,
"yaml.hover": true,
"yaml.completion": true,
// ========================================
// REST CLIENT
@@ -481,15 +563,32 @@
"apiUrl": "http://localhost:3000"
},
"production": {
"apiUrl": "https://api.yourdomain.com"
"apiUrl": "https://lcbp3.nap-dms.work"
}
},
// ========================================
// CONSOLE NINJA
// ========================================
"console-ninja.featureSet": "Community",
"console-ninja.toolsToEnableSupportAutomaticallyFor": {
"live-server-extension": true,
"live-preview-extension": true
},
// ========================================
// MATERIAL ICON THEME
// ========================================
"material-icon-theme.folders.theme": "specific",
"material-icon-theme.folders.color": "#90a4ae",
"material-icon-theme.files.associations": {
"*.env.local": "Tune",
"*.env.development": "Tune",
"*.env.production": "Tune",
"docker-compose.*.yml": "Docker"
},
"material-icon-theme.folders.associations": {
"hooks": "Custom",
"utils": "Helper",
@@ -503,13 +602,32 @@
"config": "Config"
},
// ========================================
// NPM INTELLISENSE
// ========================================
"npm-intellisense.importES6": true,
"npm-intellisense.importQuotes": "'",
"npm-intellisense.importLinebreak": ";\n",
"npm-intellisense.importDeclarationType": "const",
// ========================================
// PERFORMANCE
// ========================================
"files.maxMemoryForLargeFilesMB": 4096,
"telemetry.telemetryLevel": "off",
"security.workspace.trust.untrustedFiles": "open"
"security.workspace.trust.untrustedFiles": "open",
"extensions.ignoreRecommendations": false,
// ========================================
// DEBUGGING
// ========================================
"debug.console.fontSize": 13,
"debug.console.lineHeight": 1.2,
"debug.internalConsoleOptions": "openOnSessionStart",
"debug.openDebug": "openOnDebugBreak",
"debug.showBreakpointsInOverviewRuler": true
},
// ========================================
@@ -698,7 +816,6 @@
"formulahendry.auto-rename-tag",
"ms-azuretools.vscode-docker",
"mtxr.sqltools",
"mongodb.mongodb-vscode",
"redhat.vscode-yaml",
"mikestead.dotenv",
"editorconfig.editorconfig",

16
temp.ts
View File

@@ -1,16 +0,0 @@
// ใน function submit()
// 2.1 สร้าง Routing Record แรก
const routing = queryRunner.manager.create(CorrespondenceRouting, {
correspondenceId: currentRevision.id,
templateId: template.id, // ✅ บันทึก templateId ไว้ใช้อ้างอิง
sequence: 1,
fromOrganizationId: user.primaryOrganizationId,
toOrganizationId: firstStep.toOrganizationId,
stepPurpose: firstStep.stepPurpose,
status: 'SENT',
dueDate: new Date(
Date.now() + (firstStep.expectedDays || 7) * 24 * 60 * 60 * 1000,
),
processedByUserId: user.user_id,
processedAt: new Date(),
});