From f7a43600a35a25621004230fa92bac3d1722bcaa Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 28 Nov 2025 17:12:05 +0700 Subject: [PATCH] 251128:1700 Backend to T3.1.1 --- .vscode/extensions.json | 14 +- ...4_3.md => 2_Backend_Plan_V1_4_4.Phase6A.md | 0 2_Backend_Plan_V1_4_4.Phase_Addition.md | 709 +++++++ 2_Backend_Plan_V1_4_4.md | 1693 ++++++++++++++++- 3_Frontend_Plan_V1_4_4.md | 524 +++-- 4_Data_Dictionary_V1_4_4.md | 310 +-- 5_Backend_Folder_V1_4_3.md | 573 ------ 8_lcbp3_v1_4_4.sql | 28 +- Backend_folder.md | 301 --- Frontend_folder.md | 159 -- backend/src/common/services/crypto.service.ts | 49 +- .../dto/create-routing-template.dto.ts | 57 + .../correspondence-revision.entity.ts | 26 + .../entities/correspondence-routing.entity.ts | 9 +- .../entities/routing-template-step.entity.ts | 18 +- .../json-schema/dto/create-json-schema.dto.ts | 52 +- .../json-schema/dto/migrate-data.dto.ts | 19 + .../entities/json-schema.entity.ts | 29 +- .../interfaces/ui-schema.interface.ts | 84 + .../interfaces/validation-result.interface.ts | 34 + .../json-schema/json-schema.controller.ts | 168 +- .../modules/json-schema/json-schema.module.ts | 35 +- .../json-schema/json-schema.service.spec.ts | 18 - .../json-schema/json-schema.service.ts | 243 ++- .../services/json-security.service.ts | 113 ++ .../services/schema-migration.service.ts | 205 ++ .../json-schema/services/ui-schema.service.ts | 115 ++ .../services/virtual-column.service.ts | 152 ++ .../rfa/dto/create-rfa-revision.dto.ts | 52 + .../rfa/dto/create-rfa-workflow.dto.ts | 37 + backend/src/modules/rfa/dto/create-rfa.dto.ts | 50 +- backend/src/modules/rfa/dto/update-rfa.dto.ts | 4 +- .../rfa/entities/rfa-revision.entity.ts | 18 +- .../entities/workflow-history.entity.ts | 44 + .../entities/workflow-instance.entity.ts | 62 + .../workflow-engine/workflow-dsl.service.ts | 44 +- .../workflow-engine.controller.ts | 35 +- .../workflow-engine/workflow-engine.module.ts | 24 +- .../workflow-engine.service.ts | 262 ++- backend/tsconfig.json | 23 +- docs/GEM.md | 58 + docs/Project_File_Tree_TH.md | 162 -- docs/Project_Structure_Summary_TH.md | 100 - docs/T0-T6.2.md | 436 ----- docs/prompt.md | 367 +--- file | 0 frontend/.vscode/settings.json | 3 + frontend/tsconfig.json | 13 +- nap-dms.lcbp3.code-workspace | 193 +- temp.ts | 16 - 50 files changed, 4891 insertions(+), 2849 deletions(-) rename 2_Backend_Plan_Phase6A_V1_4_3.md => 2_Backend_Plan_V1_4_4.Phase6A.md (100%) create mode 100644 2_Backend_Plan_V1_4_4.Phase_Addition.md delete mode 100644 5_Backend_Folder_V1_4_3.md delete mode 100644 Backend_folder.md delete mode 100644 Frontend_folder.md create mode 100644 backend/src/modules/correspondence/dto/create-routing-template.dto.ts create mode 100644 backend/src/modules/json-schema/dto/migrate-data.dto.ts create mode 100644 backend/src/modules/json-schema/interfaces/ui-schema.interface.ts create mode 100644 backend/src/modules/json-schema/interfaces/validation-result.interface.ts delete mode 100644 backend/src/modules/json-schema/json-schema.service.spec.ts create mode 100644 backend/src/modules/json-schema/services/json-security.service.ts create mode 100644 backend/src/modules/json-schema/services/schema-migration.service.ts create mode 100644 backend/src/modules/json-schema/services/ui-schema.service.ts create mode 100644 backend/src/modules/json-schema/services/virtual-column.service.ts create mode 100644 backend/src/modules/rfa/dto/create-rfa-revision.dto.ts create mode 100644 backend/src/modules/rfa/dto/create-rfa-workflow.dto.ts create mode 100644 backend/src/modules/workflow-engine/entities/workflow-history.entity.ts create mode 100644 backend/src/modules/workflow-engine/entities/workflow-instance.entity.ts create mode 100644 docs/GEM.md delete mode 100644 docs/Project_File_Tree_TH.md delete mode 100644 docs/Project_Structure_Summary_TH.md delete mode 100644 docs/T0-T6.2.md delete mode 100644 file create mode 100644 frontend/.vscode/settings.json delete mode 100644 temp.ts diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 2ce8029..2d72e84 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -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", diff --git a/2_Backend_Plan_Phase6A_V1_4_3.md b/2_Backend_Plan_V1_4_4.Phase6A.md similarity index 100% rename from 2_Backend_Plan_Phase6A_V1_4_3.md rename to 2_Backend_Plan_V1_4_4.Phase6A.md diff --git a/2_Backend_Plan_V1_4_4.Phase_Addition.md b/2_Backend_Plan_V1_4_4.Phase_Addition.md new file mode 100644 index 0000000..ceb66eb --- /dev/null +++ b/2_Backend_Plan_V1_4_4.Phase_Addition.md @@ -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 ที่ซับซ้อนและการวิเคราะห์ข้อมูลเชิงลึก diff --git a/2_Backend_Plan_V1_4_4.md b/2_Backend_Plan_V1_4_4.md index 723022c..f9cb330 100644 --- a/2_Backend_Plan_V1_4_4.md +++ b/2_Backend_Plan_V1_4_4.md @@ -4,14 +4,15 @@ **วันที่:** 2025-11-26 **อ้างอิง:** Requirements v1.4.3 & FullStackJS Guidelines v1.4.3 **Classification:** Internal Technical Documentation +**การแก้ไข:** เพิ่ม T2.5.1-T2.5.9, T3.1.1-T3.1.8, ------ +--- ## 🎯 **ภาพรวมโครงการ** พัฒนา Backend สำหรับระบบบริหารจัดการเอกสารโครงการ (Document Management System) ที่มีความปลอดภัยสูง รองรับการทำงานพร้อมกัน (Concurrency) ได้อย่างถูกต้องแม่นยำ มีสถาปัตยกรรมที่ยืดหยุ่นต่อการขยายตัว และรองรับการจัดการเอกสารที่ซับซ้อน มีระบบ Workflow การอนุมัติ และการควบคุมสิทธิ์แบบ RBAC 4 ระดับ พร้อมมาตรการความปลอดภัยที่ทันสมัย ------ +--- ## 📐 **สถาปัตยกรรมระบบ** @@ -36,44 +37,314 @@ ### **โครงสร้างโมดูล (Domain-Driven)** -```tree -src/ -├── common/ # Shared Module -│ ├── auth/ # JWT, Guards, RBAC -│ ├── config/ # Configuration Management -│ ├── decorators/ # @RequirePermission, @RateLimit -│ ├── entities/ # Base Entities -│ ├── exceptions/ # Global Filters -│ ├── file-storage/ # FileStorageService (Virus Scanning + Two-Phase) -│ ├── guards/ # RBAC Guard, RateLimitGuard -│ ├── interceptors/ # Audit, Transform, Performance, Idempotency -│ ├── resilience/ # Circuit Breaker, Retry Patterns -│ ├── security/ # Input Validation, XSS Protection -│ ├── idempotency/ # [New] Idempotency Logic -│ └── maintenance/ # [New] Maintenance Mode Guard -├── modules/ -│ ├── user/ # Users, Roles, Permissions -│ ├── project/ # Projects, Contracts, Organizations -│ ├── master/ # Master Data Management -│ ├── correspondence/ # Correspondence Management -│ ├── rfa/ # RFA & Workflows -│ ├── drawing/ # Shop/Contract Drawings -│ ├── circulation/ # Internal Circulation -│ ├── transmittal/ # Transmittals -│ ├── search/ # Elasticsearch -│ ├── monitoring/ # Metrics, Health Checks -│ ├── workflow-engine/ # [New] Unified Workflow Logic -│ ├── document-numbering/ # [Update] Double-Locking Logic -│ ├── notification/ # [Update] Queue & Digest -│ └── file-storage/ # [Update] Two-Phase Commit -└── database/ # Migrations & Seeds +``` +📁backend +├── .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 +├── 📁scripts +│ ├── debug-db.ts +│ └── verify-workflow.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 +└── 📁src + ├── app.controller.spec.ts + ├── app.controller.ts + ├── app.module.ts + ├── app.service.ts + ├── main.ts + ├── redlock.d.ts + ├── 📁common + │ ├── 📁auth + │ │ ├── 📁dto + │ │ │ ├── login.dto.ts + │ │ │ └── register.dto.ts + │ │ ├── 📁strategies + │ │ │ ├── jwt-refresh.strategy.ts + │ │ │ └── jwt.strategy.ts + │ │ ├── auth.controller.spec.ts + │ │ ├── auth.controller.ts + │ │ ├── auth.module.ts + │ │ ├── auth.service.spec.ts + │ │ └── auth.service.ts + │ ├── 📁config + │ │ ├── env.validation.ts + │ │ └── redis.config.ts + │ ├── 📁decorators + │ │ ├── audit.decorator.ts + │ │ ├── bypass-maintenance.decorator.ts + │ │ ├── circuit-breaker.decorator.ts + │ │ ├── current-user.decorator.ts + │ │ ├── idempotency.decorator.ts + │ │ ├── require-permission.decorator.ts + │ │ └── retry.decorator.ts + │ ├── 📁entities + │ │ ├── audit-log.entity.ts + │ │ └── base.entity.ts + │ ├── 📁exceptions + │ │ └── http-exception.filter.ts + │ ├── 📁file-storage + │ │ ├── 📁entities + │ │ │ └── attachment.entity.ts + │ │ ├── file-cleanup.service.ts + │ │ ├── file-storage.controller.spec.ts + │ │ ├── file-storage.controller.ts + │ │ ├── file-storage.module.ts + │ │ ├── file-storage.service.spec.ts + │ │ └── file-storage.service.ts + │ ├── 📁guards + │ │ ├── jwt-auth.guard.ts + │ │ ├── jwt-refresh.guard.ts + │ │ ├── maintenance-mode.guard.ts + │ │ └── rbac.guard.ts + │ ├── 📁idempotency + │ │ ├── idempotency.interceptor.ts + │ │ └── performance.interceptor.ts + │ ├── 📁interceptors + │ │ ├── audit-log.interceptor.ts + │ │ ├── idempotency.interceptor.ts + │ │ ├── performance.interceptor.ts + │ │ └── transform.interceptor.ts + │ ├── 📁maintenance + │ ├── 📁resilience + │ │ └── resilience.module.ts + │ └── 📁security + │ ├── 📁services + │ │ ├── crypto.service.ts + │ │ └── request-context.service.ts + │ └── common.module.ts + ├── 📁database + │ ├── 📁migrations + │ └── 📁seeds + │ └── workflow-definitions.seed.ts + └── 📁modules + ├── 📁circulation + │ ├── 📁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 + ├── 📁correspondence + │ ├── 📁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 + ``` ------ +--- ## 🗓️ **แผนการพัฒนาแบบ Phase-Based** -- *(Dependency Diagram ถูกละไว้เพื่อประหยัดพื้นที่ เนื่องจากมีการอ้างอิงจากแผนเดิม)* +- _(Dependency Diagram ถูกละไว้เพื่อประหยัดพื้นที่ เนื่องจากมีการอ้างอิงจากแผนเดิม)_ ## **Phase 0: Infrastructure & Configuration (สัปดาห์ที่ 1)** @@ -117,7 +388,7 @@ src/ - [ ] **Deliverable:** Code อยู่ใน Version Control - [ ] **Dependencies:** T0.1, T0.2, T0.3 ------ +--- ## **Phase 1: Core Foundation & Security (สัปดาห์ที่ 2-3)** @@ -131,8 +402,8 @@ src/ - [ ] สร้าง Global Exception Filter (ไม่เปิดเผย sensitive information) - [ ] สร้าง Response Transform Interceptor - [ ] สร้าง Audit Log Interceptor - - [ ] **[New] Idempotency Interceptor:** ตรวจสอบ Header `Idempotency-Key` และ Cache Response เดิมใน Redis - - [ ] **[New] Maintenance Mode Middleware:** ตรวจสอบ Flag ใน **Redis Key** เพื่อ Block API ระหว่างปรับปรุงระบบ **(Admin ใช้ Redis/Admin UI ในการ Toggle สถานะ)** + - [ ] **Idempotency Interceptor:** ตรวจสอบ Header `Idempotency-Key` และ Cache Response เดิมใน Redis + - [ ] **Maintenance Mode Middleware:** ตรวจสอบ Flag ใน **Redis Key** เพื่อ Block API ระหว่างปรับปรุงระบบ **(Admin ใช้ Redis/Admin UI ในการ Toggle สถานะ)** - [ ] สร้าง RequestContextService - สำหรับเก็บข้อมูลระหว่าง Request - [ ] สร้าง ConfigService - Centralized configuration management - [ ] สร้าง CryptoService - สำหรับ encryption/decryption @@ -211,7 +482,7 @@ src/ - [ ] **Deliverable:** จัดการโครงสร้างโปรเจกต์ได้ - [ ] **Dependencies:** T1.1, T1.2, T0.3 ------ +--- ## **Phase 2: High-Integrity Data & File Management (สัปดาห์ที่ 4)** @@ -252,12 +523,7 @@ src/ - [ ] DocumentNumberCounter - [ ] สร้าง DocumentNumberingService: - [ ] generateNextNumber(projectId, orgId, typeId, year) → string - - [ ] ใช้ **Double-Lock Mechanism**: - 1. Acquire **Redis Lock** (Key: `doc_num:{project}:{type}`) - 2. Read DB & Calculate Next Number - 3. Update DB with **Optimistic Lock** Check (ใช้ `@VersionColumn()`) - 4. Release Redis Lock - 5. Retry on Failure ด้วย exponential backoff + - [ ] ใช้ **Double-Lock Mechanism**: 1. Acquire **Redis Lock** (Key: `doc_num:{project}:{type}`) 2. Read DB & Calculate Next Number 3. Update DB with **Optimistic Lock** Check (ใช้ `@VersionColumn()`) 4. Release Redis Lock 5. Retry on Failure ด้วย exponential backoff - [ ] Fallback mechanism เมื่อการขอเลขล้มเหลว - [ ] Format ตาม Template: {ORG_CODE}-{TYPE_CODE}-{YEAR_SHORT}-{SEQ:4} - **ไม่มี Controller** (Internal Service เท่านั้น) @@ -266,10 +532,10 @@ src/ - [ ] **Dependencies:** T1.1, T0.3 * **[ ] 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 + - [ ] 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.4 SecurityModule - Enhanced Security** @@ -293,14 +559,782 @@ src/ - [ ] **Deliverable:** JSON schema system ทำงานได้ - [ ] **Dependencies:** T1.1 -* **[ ] 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 +### 🚀 **T2.5 JSON Details & Schema Management - Enhanced Implementation Plan** ------ +#### 📋 **Overview** + +สร้างระบบจัดการ JSON Schema ที่ครอบคลุมสำหรับ dynamic document details, validation, transformation และ performance optimization + +--- + +#### 🎯 **Enhanced Task Breakdown for T2.5** + +##### **[ ] T2.5.1 JSON Schema Registry & Versioning System** + +- [ ] **Schema Entity Design** สำหรับเก็บ JSON schemas ทุกประเภท +- [ ] **Version Control System** สำหรับ schema evolution +- [ ] **Migration Strategy** สำหรับ backward-compatible changes +- [ ] **Schema Inheritance** สำหรับ shared field definitions + +```typescript +// Schema Entity Design +@Entity() +export class JsonSchema { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column() + name: string; // 'CORRESPONDENCE_GENERIC', 'RFA_DWG', 'CIRCULATION_INTERNAL' + + @Column() + entity_type: string; // 'correspondence', 'rfa', 'circulation' + + @Column() + version: number; + + @Column('json') + schema_definition: any; // AJV JSON Schema + + @Column('json') + ui_schema: any; // UI configuration for form generation + + @Column({ default: true }) + is_active: boolean; + + @Column('json', { nullable: true }) + migration_script: any; // Data transformation rules + + @CreateDateColumn() + created_at: Date; + + @UpdateDateColumn() + updated_at: Date; + + // Virtual columns configuration for performance + @Column('json', { nullable: true }) + virtual_columns: VirtualColumnConfig[]; +} + +interface VirtualColumnConfig { + json_path: string; // '$.projectId' + column_name: string; // 'ref_project_id' + data_type: 'INT' | 'VARCHAR' | 'BOOLEAN' | 'DATE'; + index_type?: 'INDEX' | 'UNIQUE' | 'FULLTEXT'; + is_required: boolean; +} +``` + +##### **[ ] T2.5.2 Schema Validation & Transformation Engine** + +- [ ] **AJV Integration** สำหรับ high-performance JSON validation +- [ ] **Custom Validators** สำหรับ business rule validation +- [ ] **Data Transformation** สำหรับ schema version migration +- [ ] **Sanitization Service** สำหรับ data cleansing + +```typescript +@Injectable() +export class JsonSchemaService { + private ajv: Ajv; + + constructor() { + this.ajv = new Ajv({ + allErrors: true, + coerceTypes: true, + useDefaults: true, + removeAdditional: true, + formats: { + 'date-time': true, + email: true, + uri: true, + 'document-number': this.documentNumberFormat, + }, + }); + + // Register custom formats and keywords + this.registerCustomValidators(); + } + + async validateData( + schemaName: string, + data: any, + options: ValidationOptions = {} + ): Promise { + const schema = await this.getSchema(schemaName); + const validate = this.ajv.compile(schema); + + const isValid = validate(data); + + if (!isValid) { + return { + isValid: false, + errors: validate.errors, + sanitizedData: null, + }; + } + + // Apply data transformation if needed + const sanitizedData = await this.sanitizeData(data, schema, options); + + return { + isValid: true, + errors: [], + sanitizedData, + }; + } + + private async sanitizeData( + data: any, + schema: any, + options: ValidationOptions + ): Promise { + const sanitized = { ...data }; + + // Remove unknown properties if not allowed + if (options.removeAdditional !== false) { + const allowedProperties = this.extractPropertyNames(schema); + Object.keys(sanitized).forEach((key) => { + if (!allowedProperties.includes(key)) { + delete sanitized[key]; + } + }); + } + + // Apply custom sanitizers based on field type + await this.applyFieldSanitizers(sanitized, schema); + + return sanitized; + } + + private registerCustomValidators(): void { + // Custom format for document numbers + this.ajv.addFormat('document-number', { + type: 'string', + validate: (value: string) => { + return /^[A-Z]{3,5}-[A-Z]{2,4}-\d{4}-\d{3,5}$/.test(value); + }, + }); + + // Custom keyword for role-based access + this.ajv.addKeyword({ + keyword: 'requiredRole', + type: 'string', + compile: (requiredRole: string) => { + return (data: any, dataPath: string, parentData: any) => { + // Check if user has required role for this field + const userContext = this.getUserContext(); + return userContext.roles.includes(requiredRole); + }; + }, + }); + } +} +``` + +##### **[ ] T2.5.3 Virtual Columns & Performance Optimization** + +- [ ] **Virtual Column Generator** สำหรับ JSON field indexing +- [ ] **Migration Scripts** สำหรับสร้าง generated columns +- [ ] **Query Optimizer** สำหรับใช้ virtual columns ใน search +- [ ] **Performance Monitoring** สำหรับ JSON query performance + +```typescript +@Injectable() +export class VirtualColumnService { + constructor( + private dataSource: DataSource, + private configService: ConfigService + ) {} + + async setupVirtualColumns( + tableName: string, + schemaConfig: VirtualColumnConfig[] + ): Promise { + const connection = this.dataSource.manager.connection; + + for (const config of schemaConfig) { + await this.createVirtualColumn(tableName, config); + } + } + + private async createVirtualColumn( + tableName: string, + config: VirtualColumnConfig + ): Promise { + const columnDefinition = this.generateColumnDefinition(config); + + const sql = ` + ALTER TABLE ${tableName} + ADD COLUMN ${config.column_name} ${columnDefinition} + `; + + await this.dataSource.query(sql); + + // Create index if specified + if (config.index_type) { + await this.createIndex(tableName, config); + } + } + + private generateColumnDefinition(config: VirtualColumnConfig): string { + const dataType = this.mapDataType(config.data_type); + const jsonPath = this.escapeJsonPath(config.json_path); + + return `${dataType} GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(details, '${jsonPath}'))) VIRTUAL`; + } + + private async createIndex( + tableName: string, + config: VirtualColumnConfig + ): Promise { + const indexName = `idx_${tableName}_${config.column_name}`; + const sql = ` + CREATE ${config.index_type} INDEX ${indexName} + ON ${tableName} (${config.column_name}) + `; + + await this.dataSource.query(sql); + } +} + +// Example virtual column configuration for correspondence +const correspondenceVirtualColumns: VirtualColumnConfig[] = [ + { + json_path: '$.projectId', + column_name: 'ref_project_id', + data_type: 'INT', + index_type: 'INDEX', + is_required: true, + }, + { + json_path: '$.priority', + column_name: 'ref_priority', + data_type: 'VARCHAR', + index_type: 'INDEX', + is_required: false, + }, + { + json_path: '$.dueDate', + column_name: 'ref_due_date', + data_type: 'DATE', + index_type: 'INDEX', + is_required: false, + }, +]; +``` + +##### **[ ] T2.5.4 Dynamic Form Schema Management** + +- [ ] **UI Schema Definition** สำหรับ frontend form generation +- [ ] **Field Dependency System** สำหรับ conditional fields +- [ ] **Validation Rule Sync** ระหว่าง backend-frontend +- [ ] **Form Template Registry** สำหรับ reusable form patterns + +```typescript +interface UiSchema { + type: 'object'; + properties: { + [key: string]: UiSchemaField; + }; + required?: string[]; + layout?: { + type: 'tabs' | 'sections' | 'steps'; + groups: LayoutGroup[]; + }; +} + +interface UiSchemaField { + type: 'string' | 'number' | 'boolean' | 'array' | 'object'; + widget?: 'text' | 'textarea' | 'select' | 'radio' | 'checkbox' | 'date'; + title: string; + description?: string; + placeholder?: string; + enum?: string[]; + enumNames?: string[]; + dependencies?: FieldDependency[]; + conditions?: FieldCondition[]; + properties?: { [key: string]: UiSchemaField }; // for nested objects + items?: UiSchemaField; // for arrays +} + +interface FieldDependency { + field: string; + condition: { + operator: 'equals' | 'notEquals' | 'contains' | 'greaterThan'; + value: any; + }; + actions: { + visibility?: boolean; + required?: boolean; + options?: string[]; + }; +} + +// Example: RFA Drawing Schema +const rfaDwgSchema: UiSchema = { + type: 'object', + layout: { + type: 'tabs', + groups: [ + { + title: 'Basic Information', + fields: ['title', 'description', 'discipline'], + }, + { + title: 'Drawing Details', + fields: ['drawingReferences', 'revision', 'approvalType'], + }, + { + title: 'Technical Specifications', + fields: ['materials', 'dimensions', 'tolerances'], + }, + ], + }, + properties: { + title: { + type: 'string', + widget: 'text', + title: 'Drawing Title', + placeholder: 'Enter drawing title...', + required: true, + }, + discipline: { + type: 'string', + widget: 'select', + title: 'Discipline', + enum: ['CIVIL', 'STRUCTURAL', 'MECHANICAL', 'ELECTRICAL', 'PLUMBING'], + enumNames: [ + 'Civil', + 'Structural', + 'Mechanical', + 'Electrical', + 'Plumbing', + ], + }, + drawingReferences: { + type: 'array', + title: 'Related Contract Drawings', + items: { + type: 'string', + widget: 'select', + title: 'Drawing Number', + }, + }, + approvalType: { + type: 'string', + widget: 'radio', + title: 'Approval Type', + enum: ['FULL_APPROVAL', 'PARTIAL_APPROVAL', 'COMMENTS_ONLY'], + enumNames: ['Full Approval', 'Partial Approval', 'Comments Only'], + }, + }, + required: ['title', 'discipline', 'approvalType'], +}; +``` + +##### **[ ] T2.5.5 Data Migration & Version Compatibility** + +- [ ] **Schema Migration Service** สำหรับอัพเกรด data ระหว่าง versions +- [ ] **Data Transformation Pipeline** สำหรับ backward compatibility +- [ ] **Version Rollback Mechanism** สำหรับ emergency situations +- [ ] **Migration Testing Framework** สำหรับ 确保 data integrity + +```typescript +@Injectable() +export class SchemaMigrationService { + async migrateData( + entityType: string, + entityId: string, + targetVersion: number + ): Promise { + const currentData = await this.getCurrentData(entityType, entityId); + const currentVersion = await this.getCurrentSchemaVersion( + entityType, + entityId + ); + + const migrationPath = await this.findMigrationPath( + currentVersion, + targetVersion + ); + + let migratedData = currentData; + + for (const migrationStep of migrationPath) { + migratedData = await this.applyMigrationStep(migrationStep, migratedData); + } + + // Validate migrated data against target schema + const validationResult = await this.validateAgainstSchema( + migratedData, + targetVersion + ); + + if (!validationResult.isValid) { + throw new MigrationError( + 'MIGRATION_VALIDATION_FAILED', + validationResult.errors + ); + } + + await this.saveMigratedData( + entityType, + entityId, + migratedData, + targetVersion + ); + + return { + success: true, + fromVersion: currentVersion, + toVersion: targetVersion, + migratedFields: this.getMigratedFields(currentData, migratedData), + }; + } + + private async applyMigrationStep( + step: MigrationStep, + data: any + ): Promise { + switch (step.type) { + case 'FIELD_RENAME': + return this.renameField(data, step.config); + case 'FIELD_TRANSFORM': + return this.transformField(data, step.config); + case 'FIELD_ADD': + return this.addField(data, step.config); + case 'FIELD_REMOVE': + return this.removeField(data, step.config); + case 'STRUCTURE_CHANGE': + return this.restructureData(data, step.config); + default: + throw new MigrationError('UNKNOWN_MIGRATION_TYPE'); + } + } +} + +// Example migration configuration +const migrationSteps = [ + { + from_version: 1, + to_version: 2, + type: 'FIELD_RENAME', + config: { + old_field: 'project_id', + new_field: 'ref_project_id', + }, + }, + { + from_version: 2, + to_version: 3, + type: 'FIELD_TRANSFORM', + config: { + field: 'priority', + transform: 'MAP_VALUES', + mapping: { + HIGH: 'URGENT', + MEDIUM: 'NORMAL', + LOW: 'LOW', + }, + }, + }, +]; +``` + +##### **[ ] T2.5.6 Security & Access Control for JSON Data** + +- [ ] **Field-level Security** 基于 user roles +- [ ] **Data Encryption** สำหรับ sensitive fields +- [ ] **Audit Logging** สำหรับ JSON data changes +- [ ] **Input Sanitization** สำหรับป้องกัน XSS และ injection + +```typescript +@Injectable() +export class JsonSecurityService { + async applyFieldLevelSecurity( + data: any, + schema: any, + userContext: UserContext + ): Promise { + const securedData = { ...data }; + const securityRules = await this.getSecurityRules(schema.name); + + for (const [fieldPath, fieldConfig] of Object.entries(schema.properties)) { + const fieldRules = securityRules[fieldPath]; + + if (fieldRules && !this.hasFieldAccess(fieldRules, userContext)) { + // Remove or mask field based on security rules + if (fieldRules.on_deny === 'REMOVE') { + this.deleteField(securedData, fieldPath); + } else if (fieldRules.on_deny === 'MASK') { + this.maskField(securedData, fieldPath, fieldRules.mask_pattern); + } + } + } + + return securedData; + } + + async encryptSensitiveFields(data: any, schema: any): Promise { + const encryptedData = { ...data }; + const sensitiveFields = this.getSensitiveFields(schema); + + for (const fieldPath of sensitiveFields) { + const fieldValue = this.getFieldValue(data, fieldPath); + if (fieldValue) { + const encrypted = await this.cryptoService.encrypt( + fieldValue, + 'field-level' + ); + this.setFieldValue(encryptedData, fieldPath, encrypted); + } + } + + return encryptedData; + } + + private getSensitiveFields(schema: any): string[] { + const sensitiveFields: string[] = []; + + const traverseSchema = (obj: any, path: string = '') => { + if (obj.properties) { + for (const [key, value] of Object.entries(obj.properties)) { + const currentPath = path ? `${path}.${key}` : key; + + if (value.sensitive) { + sensitiveFields.push(currentPath); + } + + if (value.properties || value.items) { + traverseSchema(value, currentPath); + } + } + } + }; + + traverseSchema(schema); + return sensitiveFields; + } +} +``` + +##### **[ ] T2.5.7 API Design & Integration** + +- [ ] **Schema Management API** สำหรับ CRUD operations +- [ ] **Validation API** สำหรับ validate data against schemas +- [ ] **Migration API** สำหรับ manage data migrations +- [ ] **Integration Hooks** สำหรับ other modules + +```typescript +@Controller('json-schema') +export class JsonSchemaController { + @Post('validate/:schemaName') + @RequirePermission('schema.validate') + async validateData( + @Param('schemaName') schemaName: string, + @Body() dto: ValidateDataDto + ): Promise { + return this.jsonSchemaService.validateData( + schemaName, + dto.data, + dto.options + ); + } + + @Post('schemas') + @RequirePermission('schema.manage') + async createSchema(@Body() dto: CreateSchemaDto): Promise { + return this.jsonSchemaService.createSchema(dto); + } + + @Post('migrate/:entityType/:entityId') + @RequirePermission('data.migrate') + async migrateData( + @Param('entityType') entityType: string, + @Param('entityId') entityId: string, + @Body() dto: MigrateDataDto + ): Promise { + return this.migrationService.migrateData( + entityType, + entityId, + dto.targetVersion + ); + } + + @Get('ui-schema/:schemaName') + @RequirePermission('schema.view') + async getUiSchema( + @Param('schemaName') schemaName: string + ): Promise { + return this.schemaService.getUiSchema(schemaName); + } +} +``` + +### **[ ] T2.5.8 Integration with Document Modules** + +- [ ] **Correspondence Module Integration** +- [ ] **RFA Module Integration** +- [ ] **Circulation Module Integration** +- [ ] **Drawing Module Integration** + +```typescript +// Example: Correspondence Service Integration +@Injectable() +export class CorrespondenceService { + constructor( + private jsonSchemaService: JsonSchemaService, + private detailsService: DetailsService + ) {} + + async createCorrespondence( + dto: CreateCorrespondenceDto + ): Promise { + // 1. Validate details against schema + const validationResult = await this.jsonSchemaService.validateData( + `CORRESPONDENCE_${dto.type}`, + dto.details + ); + + if (!validationResult.isValid) { + throw new ValidationError('INVALID_DETAILS', validationResult.errors); + } + + // 2. Apply security and sanitization + const secureDetails = await this.detailsService.sanitizeDetails( + validationResult.sanitizedData, + dto.type + ); + + // 3. Create correspondence entity + const correspondence = this.correspondenceRepository.create({ + ...dto, + details: secureDetails, + schema_version: await this.getCurrentSchemaVersion( + `CORRESPONDENCE_${dto.type}` + ), + }); + + // 4. Setup virtual columns for performance + await this.setupVirtualColumns(correspondence); + + return this.correspondenceRepository.save(correspondence); + } + + async searchCorrespondences( + filters: SearchFilters + ): Promise { + // Use virtual columns for efficient filtering + const query = this.correspondenceRepository.createQueryBuilder('c'); + + if (filters.projectId) { + query.andWhere('c.ref_project_id = :projectId', { + projectId: filters.projectId, + }); + } + + if (filters.priority) { + query.andWhere('c.ref_priority = :priority', { + priority: filters.priority, + }); + } + + return query.getMany(); + } +} +``` + +##### **[ ] T2.5.9 Testing Strategy** + +- [ ] **Unit Tests** สำหรับ schema validation และ transformation +- [ ] **Integration Tests** สำหรับ end-to-end data flow +- [ ] **Performance Tests** สำหรับ virtual columns และ large datasets +- [ ] **Security Tests** สำหรับ field-level security + +```typescript +describe('JsonSchemaService', () => { + describe('validateData', () => { + it('should validate correct RFA_DWG data successfully', async () => { + const testData = { + title: 'Structural Beam Details', + discipline: 'STRUCTURAL', + drawingReferences: ['CD-STR-001', 'CD-STR-002'], + approvalType: 'FULL_APPROVAL', + materials: ['STEEL_A36', 'CONCRETE_40MPA'], + }; + + const result = await jsonSchemaService.validateData('RFA_DWG', testData); + + expect(result.isValid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + it('should reject invalid discipline value', async () => { + const testData = { + title: 'Test Drawing', + discipline: 'INVALID_DISCIPLINE', // Not in enum + approvalType: 'FULL_APPROVAL', + }; + + const result = await jsonSchemaService.validateData('RFA_DWG', testData); + + expect(result.isValid).toBe(false); + expect(result.errors[0].message).toContain('discipline'); + }); + }); +}); + +describe('VirtualColumnService', () => { + it('should improve search performance with virtual columns', async () => { + // Create test data with JSON details + await createTestCorrespondences(1000); + + // Search without virtual column (JSON_EXTRACT) + const startTime1 = Date.now(); + const result1 = await correspondenceRepository + .createQueryBuilder('c') + .where("JSON_EXTRACT(c.details, '$.projectId') = :projectId", { + projectId: 123, + }) + .getMany(); + const time1 = Date.now() - startTime1; + + // Search with virtual column + const startTime2 = Date.now(); + const result2 = await correspondenceRepository + .createQueryBuilder('c') + .where('c.ref_project_id = :projectId', { projectId: 123 }) + .getMany(); + const time2 = Date.now() - startTime2; + + expect(time2).toBeLessThan(time1 * 0.5); // At least 2x faster + expect(result1.length).toEqual(result2.length); + }); +}); +``` + +#### 🔗 **Critical Dependencies** + +- **T1.1** (Common Module) - สำหรับ base entities และ shared services +- **T1.4** (RBAC Guard) - สำหรับ field-level security +- **T0.3** (Database) - สำหรับ virtual columns implementation +- **T3.2** (Correspondence) - สำหรับ integration testing + +#### 🎯 **Success Metrics** + +- ✅ JSON validation performance: < 10ms ต่อ request +- ✅ Virtual columns improve search performance 5x+ +- ✅ Support schema evolution without data loss +- ✅ Field-level security enforced across all modules +- ✅ 100% test coverage สำหรับ core validation logic + +* **[ ] 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 + +--- ## **Phase 3: Unified Workflow Engine (สัปดาห์ที่ 5-6)** @@ -317,12 +1351,525 @@ src/ - [ ] **Deliverable:** Unified Workflow Engine พร้อมใช้งาน - [ ] **Dependencies:** T1.1 +- **[ ] T3.1.1 Workflow DSL Specification & Grammar** + - [ ] Define EBNF Grammar สำหรับ Workflow DSL + - [ ] Create YAML Schema สำหรับ human-friendly workflow definitions + - [ ] Design JSON Schema สำหรับ compiled workflow representations + +```yaml +# ตัวอย่าง DSL Structure + +workflow: RFA_APPROVAL +version: 1.0 +description: "RFA Approval Workflow with Parallel Reviews" + +states: + +- name: DRAFT + initial: true + metadata: + color: "gray" + icon: "draft" + on: + SUBMIT: + to: TECHNICAL_REVIEW + conditions: - expression: "user.hasRole('ENGINEER')" + requirements: - role: "ENGINEER" + events: - type: "notify" + target: "reviewers" + template: "NEW_RFA_SUBMITTED" - type: "assign" + target: "technical_lead" + +- name: TECHNICAL_REVIEW + metadata: + color: "blue" + icon: "review" + on: + APPROVE: + to: MANAGERIAL_REVIEW + conditions: - expression: "user.department === context.document.department" + REQUEST_CHANGES: + to: DRAFT + events: - type: "notify" + target: "creator" + template: "CHANGES_REQUESTED" + ESCALATE: + to: ESCALATED_REVIEW + conditions: - expression: "document.priority === 'HIGH'" + +- name: PARALLEL_APPROVAL + parallel: true + branches: + + - MANAGERIAL_REVIEW + - FINANCIAL_REVIEW + on: + ALL_APPROVED: + to: APPROVED + ANY_REJECTED: + to: REJECTED + +- name: APPROVED + terminal: true + metadata: + color: "green" + icon: "approved" + Versioning Strategy สำหรับ workflow definitions +``` + +- **[ ] T3.1.2 Workflow Core Entities & Database Schema** + + - [ ] WorkflowDefinition Entity + - [ ] WorkflowInstance Entity + - [ ] WorkflowHistory Entity + - [ ] WorkflowTransition Entity + +```typescript +// Core Entities Design +@Entity() +export class WorkflowDefinition { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column() + name: string; + + @Column() + version: number; + + @Column('json') + dsl_raw: any; // YAML/JSON DSL + + @Column('json') + compiled_schema: any; // Normalized JSON + + @Column({ default: true }) + is_active: boolean; + + @CreateDateColumn() + created_at: Date; + + @VersionColumn() + version: number; +} + +@Entity() +export class WorkflowInstance { + @PrimaryGeneratedColumn('uuid') + id: string; + + @ManyToOne(() => WorkflowDefinition) + definition: WorkflowDefinition; + + @Column() + entity_type: string; // 'correspondence', 'rfa', 'circulation' + + @Column() + entity_id: string; + + @Column() + current_state: string; + + @Column('json') + context: any; // Workflow-specific data + + @Column('json') + history: any[]; // State transition history + + @CreateDateColumn() + created_at: Date; + + @UpdateDateColumn() + updated_at: Date; +} +``` + +- **[ ] T3.1.3 DSL Parser & Compiler Service** + + - [ ] YAML Parser สำหรับอ่าน DSL definitions + - [ ] Syntax Validator สำหรับ compile-time validation + - [ ] Schema Compiler สำหรับแปลง DSL → Normalized JSON + - [ ] Version Migration สำหรับอัพเกรด workflow definitions + +```typescript +@Injectable() +export class WorkflowDslService { + async parseAndValidate(dslContent: string): Promise { + // 1. Parse YAML + const rawDefinition = yaml.parse(dslContent); + + // 2. Validate Syntax + await this.validateSyntax(rawDefinition); + + // 3. Compile to Normalized JSON + const compiled = await this.compileDefinition(rawDefinition); + + // 4. Validate Business Rules + await this.validateBusinessRules(compiled); + + return compiled; + } + + private async validateSyntax(definition: any): Promise { + const rules = [ + // ต้องมีอย่างน้อย 1 initial state + () => definition.states.some((s) => s.initial), + // Terminal states ต้องไม่มี transitions + () => !definition.states.filter((s) => s.terminal).some((s) => s.on), + // State names must be unique + () => + new Set(definition.states.map((s) => s.name)).size === + definition.states.length, + // Transition targets must exist + () => this.validateTransitionTargets(definition), + ]; + + for (const rule of rules) { + if (!rule()) { + throw new WorkflowValidationError('DSL_VALIDATION_FAILED'); + } + } + } +} +``` + +- **[ ] T3.1.4 Workflow Runtime Engine** + - [ ] State Machine Engine สำหรับจัดการ state transitions + - [ ] Condition Evaluator สำหรับประเมิน conditional transitions + - [ ] Permission Checker สำหรับตรวจสอบสิทธิ์ในการเปลี่ยนสถานะ + - [ ] Event Dispatcher สำหรับจัดการ workflow events + +```typescript +****@Injectable() +export class WorkflowEngineService { + async processTransition( + instanceId: string, + action: string, + context: WorkflowContext + ): Promise { + // 1. Load Workflow Instance + const instance = await this.findInstance(instanceId); + const definition = await this.getCompiledDefinition(instance.definition_id); + + // 2. Validate Current State & Action + const currentState = definition.states[instance.current_state]; + const transition = currentState.transitions[action]; + + if (!transition) { + throw new WorkflowError('INVALID_TRANSITION'); + } + + // 3. Check Permissions & Conditions + await this.validatePermissions(transition, context); + await this.validateConditions(transition, context); + + // 4. Execute Pre-Transition Hooks + await this.executeHooks('pre_transition', instance, transition, context); + + // 5. Perform State Transition + const previousState = instance.current_state; + instance.current_state = transition.to; + + // 6. Record History + await this.recordTransitionHistory(instance, { + from: previousState, + to: transition.to, + action, + user: context.userId, + timestamp: new Date(), + metadata: context.metadata + }); + + // 7. Execute Post-Transition Events + await this.executeEvents(transition.events, instance, context); + + // 8. Execute Post-Transition Hooks + await this.executeHooks('post_transition', instance, transition, context); + + // 9. Save Instance + await this.saveInstance(instance); + + return { + success: true, + previousState, + newState: transition.to, + instanceId: instance.id + }; + } + + async getAvailableActions( + instanceId: string, + context: WorkflowContext + ): Promise { + const instance = await this.findInstance(instanceId); + const definition = await this.getCompiledDefinition(instance.definition_id); + const currentState = definition.states[instance.current_state]; + + return Object.keys(currentState.transitions).filter(action => { + const transition = currentState.transitions[action]; + return this.isActionAvailable(transition, context); + }); + } +} +``` + +- **[ ] T3.1.5 Advanced Feature Implementation** + - [ ] Parallel Approval Flows สำหรับ multi-department approvals + - [ ] Conditional Transitions ด้วย expression evaluation + - [ ] Timeout & Escalation สำหรับขั้นตอนที่เกินกำหนด + - [ ] Rollback & Compensation สำหรับย้อนกลับสถานะ + +```typescript +// Parallel Workflow Support +interface ParallelState { + parallel: true; + branches: string[]; + completion_policy: 'ALL' | 'ANY' | 'MAJORITY'; + on: { + [completionType: string]: { + to: string; + conditions?: Condition[]; + }; + }; +} + +// Conditional Transition Support +interface ConditionalTransition { + to: string; + conditions: Array<{ + expression: string; // "user.role === 'MANAGER' && document.value > 10000" + evaluator?: 'javascript' | 'jsonlogic'; + }>; + requirements?: PermissionRequirement[]; +} + +// Timeout & Escalation +interface StateWithTimeout { + timeout_seconds: number; + on_timeout: { + action: string; + escalate_to?: string; + notify?: string[]; + }; +} +``` + +- **[ ] T3.1.6 Event System & Integration** + - [ ] Event Types (notify, assign, webhook, auto_action) + - [ ] Template Engine สำหรับ dynamic messages + - [ ] Webhook Support สำหรับ external integrations + - [ ] Notification Service Integration + +```typescript +@Injectable() +export class WorkflowEventService { + async executeEvents( + events: WorkflowEvent[], + instance: WorkflowInstance, + context: WorkflowContext + ): Promise { + for (const event of events) { + switch (event.type) { + case 'notify': + await this.handleNotifyEvent(event, instance, context); + break; + case 'assign': + await this.handleAssignEvent(event, instance, context); + break; + case 'webhook': + await this.handleWebhookEvent(event, instance, context); + break; + case 'auto_action': + await this.handleAutoActionEvent(event, instance, context); + break; + } + } + } + + private async handleNotifyEvent( + event: NotifyEvent, + instance: WorkflowInstance, + context: WorkflowContext + ): Promise { + const recipients = await this.resolveRecipients( + event.target, + instance, + context + ); + const message = await this.renderTemplate( + event.template, + instance, + context + ); + + await this.notificationService.send({ + type: 'workflow', + recipients, + subject: message.subject, + body: message.body, + metadata: { + workflow_instance_id: instance.id, + state: instance.current_state, + action: context.action, + }, + }); + } +} +``` + +- **[ ] T3.1.7 API Design & Controllers** + - [ ] REST API Endpoints สำหรับ workflow management + - [ ] WebSocket Support สำหรับ real-time updates + - [ ] Admin API สำหรับ workflow definition management + - [ ] Integration Hooks สำหรับ external systems + +```typescript +@Controller('workflow') +export class WorkflowEngineController { + @Post('instances/:id/transition') + @RequirePermission('workflow.execute') + async processTransition( + @Param('id') instanceId: string, + @Body() dto: WorkflowTransitionDto + ): Promise { + return this.workflowEngine.processTransition( + instanceId, + dto.action, + dto.context + ); + } + + @Get('instances/:id/actions') + @RequirePermission('workflow.view') + async getAvailableActions( + @Param('id') instanceId: string, + @Query() context: WorkflowContext + ): Promise { + return this.workflowEngine.getAvailableActions(instanceId, context); + } + + @Post('definitions') + @RequirePermission('workflow.manage') + async createWorkflowDefinition( + @Body() dto: CreateWorkflowDefinitionDto + ): Promise { + return this.workflowDslService.compileAndSave(dto.dslContent); + } + + @Get('instances/:id/history') + @RequirePermission('workflow.view') + async getWorkflowHistory( + @Param('id') instanceId: string + ): Promise { + return this.workflowHistoryService.getHistory(instanceId); + } +} +``` + +- **[ ] T3.1.8 Integration with Existing Modules** + - [ ] Correspondence Module Integration + - [ ] RFA Module Integration + - [ ] Circulation Module Integration + - [ ] Notification Module Integration + +```typescript +// Correspondence Integration Example +@Injectable() +export class CorrespondenceWorkflowService { + constructor( + private workflowEngine: WorkflowEngineService, + private correspondenceService: CorrespondenceService + ) {} + + async submitCorrespondence( + correspondenceId: string, + userId: string + ): Promise { + const correspondence = await this.correspondenceService.findById( + correspondenceId + ); + + // Create workflow instance + const instance = await this.workflowEngine.createInstance({ + definition: 'CORRESPONDENCE_ROUTING', + entity_type: 'correspondence', + entity_id: correspondenceId, + context: { + document: correspondence, + user: userId, + }, + }); + + // Process initial transition + await this.workflowEngine.processTransition(instance.id, 'SUBMIT', { + userId, + metadata: { correspondenceId }, + }); + } +} +``` + +- **[ ] T3.1.9 Testing Strategy** + - [ ] Unit Tests สำหรับ DSL parser และ state machine + - [ ] Integration Tests สำหรับ end-to-end workflow execution + - [ ] Performance Tests สำหรับ high-concurrency scenarios + - [ ] Security Tests สำหรับ permission validation. + +```typescript +describe('WorkflowEngineService', () => { + describe('processTransition', () => { + it('should successfully transition state with valid permissions', async () => { + // Arrange + const instance = await createTestInstance(); + const context = { userId: 'user1', roles: ['APPROVER'] }; + + // Act + const result = await workflowEngine.processTransition( + instance.id, + 'APPROVE', + context + ); + + // Assert + expect(result.success).toBe(true); + expect(result.newState).toBe('APPROVED'); + }); + + it('should reject transition without required permissions', async () => { + // Arrange + const instance = await createTestInstance(); + const context = { userId: 'user2', roles: ['VIEWER'] }; + + // Act & Assert + await expect( + workflowEngine.processTransition(instance.id, 'APPROVE', context) + ).rejects.toThrow(WorkflowError); + }); + }); +}); +``` + +- **🔗 Critical Dependencies of T3.1.1-T3.1.8** + + - T1.1 (Common Module) - สำหรับ base entities และ shared services + - T1.4 (RBAC Guard) - สำหรับ permission checking + - T2.5 (JSON Schema) - สำหรับ DSL validation + - T6.2 (Notification) - สำหรับ event handling + +- **🎯 Success Metrics** + + - ✅ Support ทั้ง Correspondence Routing และ RFA Workflow + - ✅ DSL ที่ human-readable และ editable โดยไม่ต้องแก้โค้ด + - ✅ Performance: < 50ms ต่อ state transition + - ✅ 100% test coverage สำหรับ core workflow logic + - ✅ Complete audit trail สำหรับทุก workflow instance + - **[ ] T3.2 CorrespondenceModule - Basic CRUD** - [ ] สร้าง Entities (Correspondence, Revision, Recipient, Tag, Reference, Attachment) - [ ] สร้าง CorrespondenceService (Create with Document Numbering, Update with new Revision, Soft Delete) - [ ] สร้าง Controllers (POST/GET/PUT/DELETE /correspondences) - - [ ] [New] Implement Impersonation Logic: ตรวจสอบ originatorId ใน DTO หากมีการส่งมา ต้องเช็คว่า User ปัจจุบันมีสิทธิ์กระทำการแทนหรือไม่ (Superadmin) + - [ ] Implement Impersonation Logic: ตรวจสอบ originatorId ใน DTO หากมีการส่งมา ต้องเช็คว่า User ปัจจุบันมีสิทธิ์กระทำการแทนหรือไม่ (Superadmin) - [ ] **Security:** Implement permission checks สำหรับ document access - [ ] **Deliverable:** สร้าง/แก้ไข/ดูเอกสารได้ - [ ] **Dependencies:** T1.1, T1.2, T1.3, T1.4, T1.5, T2.3, T2.2, T2.5 @@ -345,7 +1892,7 @@ src/ - [ ] **Deliverable:** ระบบส่งต่อเอกสารทำงานได้สมบูรณ์ด้วย Unified Engine - [ ] **Dependencies:** T3.1, T3.2 ------ +--- ## **Phase 4: Drawing & Advanced Workflows (สัปดาห์ที่ 7-8)** @@ -382,7 +1929,7 @@ src/ - [ ] **Deliverable:** RFA Workflow ทำงานได้ด้วย Unified Engine - [ ] **Dependencies:** T3.2, T4.2, T2.5, T6.2 ------ +--- ## **Phase 5: Workflow Systems & Resilience (สัปดาห์ที่ 8-9)** @@ -408,7 +1955,7 @@ src/ - [ ] **Deliverable:** สร้าง Transmittal ได้ - [ ] **Dependencies:** T3.2 ------ +--- ## **Phase 6: Notification & Resilience (สัปดาห์ที่ 9)** @@ -462,7 +2009,7 @@ src/ - [ ] **Deliverable:** Database Performance และ Scalability ดีขึ้น - [ ] **Dependencies:** T0.3 ------ +--- ## **Phase 7: Testing & Hardening (สัปดาห์ที่ 10-12)** @@ -489,7 +2036,7 @@ src/ - [ ] ทดสอบ Replay Attack โดยใช้ `Idempotency-Key` ซ้ำ - [ ] ทดสอบ Maintenance Mode Block API ได้จริง - [ ] ทดสอบ RBAC 4-Level ทำงานถูกต้อง 100% - - [ ] **Deliverable:** Security และ Idempotency ทำงานได้ตาม设计要求 + - [ ] **Deliverable:** Security และ Idempotency ทำงานได้ตาม 设计要求 - **[ ] T7.4 Unit Testing (80% Coverage)** @@ -518,7 +2065,7 @@ src/ - [ ] Database Optimization (Review Indexes, Query Optimization, Pagination) - [ ] **Deliverable:** Response Time < 200ms (90th percentile) ------ +--- ## **Phase 8: Documentation & Deployment (สัปดาห์ที่ 14)** @@ -533,12 +2080,12 @@ src/ - **[ ] T8.5 Production Deployment** - **[ ] T8.6 Handover to Frontend Team** ------ +--- ## 📊 **สรุป Timeline** -| Phase | ระยะเวลา | จำนวนงาน | Output หลัก | -| :------ | :----------- | :----------- | :--------------------------------------------- | +| Phase | ระยะเวลา | จำนวนงาน | Output หลัก | +| :------ | :------------- | :----------- | :--------------------------------------------- | | Phase 0 | 1 สัปดาห์ | 4 | Infrastructure Ready + Security Base | | Phase 1 | 2 สัปดาห์ | 5 | Auth & User Management + RBAC + Idempotency | | Phase 2 | 1 สัปดาห์ | 5 | High-Integrity Data & File Management | @@ -552,14 +2099,22 @@ src/ ## **Document Control:** -- **Document:** Backend Development Plan v1.4.3 +- **Document:** Backend Development Plan v1.4.4 - **Version:** 1.4 -- **Date:** 2025-11-26 +- **Date:** 2025-11-28 - **Author:** NAP LCBP3-DMS & Gemini - **Status:** FINAL-Rev.04 - **Classification:** Internal Technical Documentation - **Approved By:** Nattanin ------ +--- `End of Backend Development Plan v1.4.4` + +``` + +``` + +``` + +``` diff --git a/3_Frontend_Plan_V1_4_4.md b/3_Frontend_Plan_V1_4_4.md index 83c3308..730b7e6 100644 --- a/3_Frontend_Plan_V1_4_4.md +++ b/3_Frontend_Plan_V1_4_4.md @@ -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 | diff --git a/4_Data_Dictionary_V1_4_4.md b/4_Data_Dictionary_V1_4_4.md index dcf328c..62d5d49 100644 --- a/4_Data_Dictionary_V1_4_4.md +++ b/4_Data_Dictionary_V1_4_4.md @@ -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 (วิว และ โปรซีเดอร์)** diff --git a/5_Backend_Folder_V1_4_3.md b/5_Backend_Folder_V1_4_3.md deleted file mode 100644 index 98c99fc..0000000 --- a/5_Backend_Folder_V1_4_3.md +++ /dev/null @@ -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 - -``` - ---- diff --git a/8_lcbp3_v1_4_4.sql b/8_lcbp3_v1_4_4.sql index d97f7e2..60b36e6 100644 --- a/8_lcbp3_v1_4_4.sql +++ b/8_lcbp3_v1_4_4.sql @@ -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; \ No newline at end of file +SET FOREIGN_KEY_CHECKS = 1; diff --git a/Backend_folder.md b/Backend_folder.md deleted file mode 100644 index 4a54a1e..0000000 --- a/Backend_folder.md +++ /dev/null @@ -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 -``` \ No newline at end of file diff --git a/Frontend_folder.md b/Frontend_folder.md deleted file mode 100644 index 836c6bf..0000000 --- a/Frontend_folder.md +++ /dev/null @@ -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 -``` \ No newline at end of file diff --git a/backend/src/common/services/crypto.service.ts b/backend/src/common/services/crypto.service.ts index fd84781..d783e7f 100644 --- a/backend/src/common/services/crypto.service.ts +++ b/backend/src/common/services/crypto.service.ts @@ -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; + } } } diff --git a/backend/src/modules/correspondence/dto/create-routing-template.dto.ts b/backend/src/modules/correspondence/dto/create-routing-template.dto.ts new file mode 100644 index 0000000..decd723 --- /dev/null +++ b/backend/src/modules/correspondence/dto/create-routing-template.dto.ts @@ -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[]; +} diff --git a/backend/src/modules/correspondence/entities/correspondence-revision.entity.ts b/backend/src/modules/correspondence/entities/correspondence-revision.entity.ts index 5bc20e0..50987e3 100644 --- a/backend/src/modules/correspondence/entities/correspondence-revision.entity.ts +++ b/backend/src/modules/correspondence/entities/correspondence-revision.entity.ts @@ -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; diff --git a/backend/src/modules/correspondence/entities/correspondence-routing.entity.ts b/backend/src/modules/correspondence/entities/correspondence-routing.entity.ts index df2903f..0906289 100644 --- a/backend/src/modules/correspondence/entities/correspondence-routing.entity.ts +++ b/backend/src/modules/correspondence/entities/correspondence-routing.entity.ts @@ -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; diff --git a/backend/src/modules/correspondence/entities/routing-template-step.entity.ts b/backend/src/modules/correspondence/entities/routing-template-step.entity.ts index 8d1f677..8af4d3e 100644 --- a/backend/src/modules/correspondence/entities/routing-template-step.entity.ts +++ b/backend/src/modules/correspondence/entities/routing-template-step.entity.ts @@ -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; } diff --git a/backend/src/modules/json-schema/dto/create-json-schema.dto.ts b/backend/src/modules/json-schema/dto/create-json-schema.dto.ts index c6f2cc4..0bf2fa9 100644 --- a/backend/src/modules/json-schema/dto/create-json-schema.dto.ts +++ b/backend/src/modules/json-schema/dto/create-json-schema.dto.ts @@ -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; // โครงสร้าง JSON Schema (Standard Format) + schemaDefinition!: Record; + + @IsObject() + @IsOptional() + uiSchema?: Record; + + @IsArray() + @IsOptional() + @ValidateNested({ each: true }) + @Type(() => VirtualColumnConfigDto) + virtualColumns?: VirtualColumnConfigDto[]; + + @IsObject() + @IsOptional() + migrationScript?: Record; @IsBoolean() @IsOptional() - isActive?: boolean; // สถานะการใช้งาน + isActive?: boolean; } diff --git a/backend/src/modules/json-schema/dto/migrate-data.dto.ts b/backend/src/modules/json-schema/dto/migrate-data.dto.ts new file mode 100644 index 0000000..ba6b710 --- /dev/null +++ b/backend/src/modules/json-schema/dto/migrate-data.dto.ts @@ -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; +} + diff --git a/backend/src/modules/json-schema/entities/json-schema.entity.ts b/backend/src/modules/json-schema/entities/json-schema.entity.ts index 4faeea8..4aa8490 100644 --- a/backend/src/modules/json-schema/entities/json-schema.entity.ts +++ b/backend/src/modules/json-schema/entities/json-schema.entity.ts @@ -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; diff --git a/backend/src/modules/json-schema/interfaces/ui-schema.interface.ts b/backend/src/modules/json-schema/interfaces/ui-schema.interface.ts new file mode 100644 index 0000000..7633873 --- /dev/null +++ b/backend/src/modules/json-schema/interfaces/ui-schema.interface.ts @@ -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; // เช่น กรอง 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; // Config เพิ่มเติมเฉพาะ Layout type +} + +export interface UiSchema { + layout: LayoutConfig; + fields: { + [key: string]: UiSchemaField; + }; +} + diff --git a/backend/src/modules/json-schema/interfaces/validation-result.interface.ts b/backend/src/modules/json-schema/interfaces/validation-result.interface.ts new file mode 100644 index 0000000..f9add0e --- /dev/null +++ b/backend/src/modules/json-schema/interfaces/validation-result.interface.ts @@ -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; +} + diff --git a/backend/src/modules/json-schema/json-schema.controller.ts b/backend/src/modules/json-schema/json-schema.controller.ts index a9c8152..b20ac88 100644 --- a/backend/src/modules/json-schema/json-schema.controller.ts +++ b/backend/src/modules/json-schema/json-schema.controller.ts @@ -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, + ); } } diff --git a/backend/src/modules/json-schema/json-schema.module.ts b/backend/src/modules/json-schema/json-schema.module.ts index 175f535..1481f0f 100644 --- a/backend/src/modules/json-schema/json-schema.module.ts +++ b/backend/src/modules/json-schema/json-schema.module.ts @@ -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 {} diff --git a/backend/src/modules/json-schema/json-schema.service.spec.ts b/backend/src/modules/json-schema/json-schema.service.spec.ts deleted file mode 100644 index af68f31..0000000 --- a/backend/src/modules/json-schema/json-schema.service.spec.ts +++ /dev/null @@ -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); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/backend/src/modules/json-schema/json-schema.service.ts b/backend/src/modules/json-schema/json-schema.service.ts index d5ece1b..1a0f1a1 100644 --- a/backend/src/modules/json-schema/json-schema.service.ts +++ b/backend/src/modules/json-schema/json-schema.service.ts @@ -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(); - 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, + 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 { - 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) { - // 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, + ); } } diff --git a/backend/src/modules/json-schema/services/json-security.service.ts b/backend/src/modules/json-schema/services/json-security.service.ts new file mode 100644 index 0000000..57d5e4a --- /dev/null +++ b/backend/src/modules/json-schema/services/json-security.service.ts @@ -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( + 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( + 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; + } +} diff --git a/backend/src/modules/json-schema/services/schema-migration.service.ts b/backend/src/modules/json-schema/services/schema-migration.service.ts new file mode 100644 index 0000000..59b6e14 --- /dev/null +++ b/backend/src/modules/json-schema/services/schema-migration.service.ts @@ -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 { + 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 { + 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; + } +} + diff --git a/backend/src/modules/json-schema/services/ui-schema.service.ts b/backend/src/modules/json-schema/services/ui-schema.service.ts new file mode 100644 index 0000000..1835c10 --- /dev/null +++ b/backend/src/modules/json-schema/services/ui-schema.service.ts @@ -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(); + + 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(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'; + } +} + diff --git a/backend/src/modules/json-schema/services/virtual-column.service.ts b/backend/src/modules/json-schema/services/virtual-column.service.ts new file mode 100644 index 0000000..ad55764 --- /dev/null +++ b/backend/src/modules/json-schema/services/virtual-column.service.ts @@ -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)'; + } + } +} + diff --git a/backend/src/modules/rfa/dto/create-rfa-revision.dto.ts b/backend/src/modules/rfa/dto/create-rfa-revision.dto.ts new file mode 100644 index 0000000..515a3e8 --- /dev/null +++ b/backend/src/modules/rfa/dto/create-rfa-revision.dto.ts @@ -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; + + @IsArray() + @IsOptional() + shopDrawingRevisionIds?: number[]; // IDs of linked Shop Drawings +} diff --git a/backend/src/modules/rfa/dto/create-rfa-workflow.dto.ts b/backend/src/modules/rfa/dto/create-rfa-workflow.dto.ts new file mode 100644 index 0000000..e584964 --- /dev/null +++ b/backend/src/modules/rfa/dto/create-rfa-workflow.dto.ts @@ -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; +} + diff --git a/backend/src/modules/rfa/dto/create-rfa.dto.ts b/backend/src/modules/rfa/dto/create-rfa.dto.ts index 70da3e3..515a3e8 100644 --- a/backend/src/modules/rfa/dto/create-rfa.dto.ts +++ b/backend/src/modules/rfa/dto/create-rfa.dto.ts @@ -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; @IsArray() - @IsInt({ each: true }) @IsOptional() - shopDrawingRevisionIds?: number[]; // Shop Drawings ที่แนบมา -} \ No newline at end of file + shopDrawingRevisionIds?: number[]; // IDs of linked Shop Drawings +} diff --git a/backend/src/modules/rfa/dto/update-rfa.dto.ts b/backend/src/modules/rfa/dto/update-rfa.dto.ts index 0a4dcbe..57fddcf 100644 --- a/backend/src/modules/rfa/dto/update-rfa.dto.ts +++ b/backend/src/modules/rfa/dto/update-rfa.dto.ts @@ -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) {} diff --git a/backend/src/modules/rfa/entities/rfa-revision.entity.ts b/backend/src/modules/rfa/entities/rfa-revision.entity.ts index 02348dc..8181856 100644 --- a/backend/src/modules/rfa/entities/rfa-revision.entity.ts +++ b/backend/src/modules/rfa/entities/rfa-revision.entity.ts @@ -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; diff --git a/backend/src/modules/workflow-engine/entities/workflow-history.entity.ts b/backend/src/modules/workflow-engine/entities/workflow-history.entity.ts new file mode 100644 index 0000000..91bde3b --- /dev/null +++ b/backend/src/modules/workflow-engine/entities/workflow-history.entity.ts @@ -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; // เก็บข้อมูลเพิ่มเติม เช่น Snapshot ของ Context ณ ตอนนั้น + + @CreateDateColumn({ name: 'created_at' }) + createdAt!: Date; +} diff --git a/backend/src/modules/workflow-engine/entities/workflow-instance.entity.ts b/backend/src/modules/workflow-engine/entities/workflow-instance.entity.ts new file mode 100644 index 0000000..cc36e5d --- /dev/null +++ b/backend/src/modules/workflow-engine/entities/workflow-instance.entity.ts @@ -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; + + @CreateDateColumn({ name: 'created_at' }) + createdAt!: Date; + + @UpdateDateColumn({ name: 'updated_at' }) + updatedAt!: Date; +} diff --git a/backend/src/modules/workflow-engine/workflow-dsl.service.ts b/backend/src/modules/workflow-engine/workflow-dsl.service.ts index 47facb3..c22d4a5 100644 --- a/backend/src/modules/workflow-engine/workflow-dsl.service.ts +++ b/backend/src/modules/workflow-engine/workflow-dsl.service.ts @@ -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(); - // 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) { 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; }); diff --git a/backend/src/modules/workflow-engine/workflow-engine.controller.ts b/backend/src/modules/workflow-engine/workflow-engine.controller.ts index 258b609..68ac888 100644 --- a/backend/src/modules/workflow-engine/workflow-engine.controller.ts +++ b/backend/src/modules/workflow-engine/workflow-engine.controller.ts @@ -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); } } diff --git a/backend/src/modules/workflow-engine/workflow-engine.module.ts b/backend/src/modules/workflow-engine/workflow-engine.module.ts index abae558..2c4cbb7 100644 --- a/backend/src/modules/workflow-engine/workflow-engine.module.ts +++ b/backend/src/modules/workflow-engine/workflow-engine.module.ts @@ -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 {} diff --git a/backend/src/modules/workflow-engine/workflow-engine.service.ts b/backend/src/modules/workflow-engine/workflow-engine.service.ts index d32deff..5ab8326 100644 --- a/backend/src/modules/workflow-engine/workflow-engine.service.ts +++ b/backend/src/modules/workflow-engine/workflow-engine.service.ts @@ -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, + @InjectRepository(WorkflowInstance) + private readonly instanceRepo: Repository, + @InjectRepository(WorkflowHistory) + private readonly historyRepo: Repository, 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 { + // 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 { - 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 = {}, + ): Promise { + // 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 = {}, + ) { + 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 { + 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.`, ); diff --git a/backend/tsconfig.json b/backend/tsconfig.json index e74ae8f..7b6bc23 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -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"] + } } } diff --git a/docs/GEM.md b/docs/GEM.md new file mode 100644 index 0000000..56c58ec --- /dev/null +++ b/docs/GEM.md @@ -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. โทนโดยรวม: + - ใช้ภาษาไทยในการสื่อสารเป็นหลัก ยกเว้นศัพท์เทคนิค + - มีความมั่นใจและแสดงออกถึงความเชี่ยวชาญในฐานะโปรแกรมเมอร์ผู้เชี่ยวชาญ + - มีความเป็นระเบียบและให้คำแนะนำที่เป็นขั้นตอน diff --git a/docs/Project_File_Tree_TH.md b/docs/Project_File_Tree_TH.md deleted file mode 100644 index b27e6a5..0000000 --- a/docs/Project_File_Tree_TH.md +++ /dev/null @@ -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 \ No newline at end of file diff --git a/docs/Project_Structure_Summary_TH.md b/docs/Project_Structure_Summary_TH.md deleted file mode 100644 index 5d7a55d..0000000 --- a/docs/Project_Structure_Summary_TH.md +++ /dev/null @@ -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 \ No newline at end of file diff --git a/docs/T0-T6.2.md b/docs/T0-T6.2.md deleted file mode 100644 index ad6f92c..0000000 --- a/docs/T0-T6.2.md +++ /dev/null @@ -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 -``` diff --git a/docs/prompt.md b/docs/prompt.md index bc10c29..11ee9c3 100644 --- a/docs/prompt.md +++ b/docs/prompt.md @@ -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 { - // 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 \ No newline at end of file +ท่านต้องการให้ผมเริ่มเขียนโค้ดส่วนไหนก่อนหรือไม่ครับ? (เช่น Entity หรือ VirtualColumnService) diff --git a/file b/file deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/.vscode/settings.json b/frontend/.vscode/settings.json new file mode 100644 index 0000000..e584018 --- /dev/null +++ b/frontend/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "jest.rootPath": "/" +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 1ba6986..cb8f1e7 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -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"] -} \ No newline at end of file +} diff --git a/nap-dms.lcbp3.code-workspace b/nap-dms.lcbp3.code-workspace index 22e9684..ee91a74 100644 --- a/nap-dms.lcbp3.code-workspace +++ b/nap-dms.lcbp3.code-workspace @@ -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", diff --git a/temp.ts b/temp.ts deleted file mode 100644 index 6b3342c..0000000 --- a/temp.ts +++ /dev/null @@ -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(), - }); \ No newline at end of file