690325:2132 Fixing Naming convention missunderstand #01
CI / CD Pipeline / build (push) Failing after 38m8s
CI / CD Pipeline / deploy (push) Has been skipped

This commit is contained in:
2026-03-25 21:32:47 +07:00
parent 509fe7b597
commit d36d4b0bf4
32 changed files with 648 additions and 527 deletions
+170 -158
View File
@@ -3,8 +3,8 @@ trigger: always_on
--- ---
# NAP-DMS Project Context & Rules # NAP-DMS Project Context & Rules
- For: Gemeni CLI and Gemini. - For: Gemeni CLI and Gemini.
- Version: 1.8.3 (Enforcement Tiers Added) | Last synced from repo: 2026-03-21 - Version: 1.8.4 (Accuracy Pass) | Last synced from repo: 2026-03-24
- Repo: [https://git.np-dms.work/np-dms/lcbp3](https://git.np-dms.work/np-dms/lcbp3) - Repo: [https://git.np-dms.work/np-dms/lcbp3](https://git.np-dms.work/np-dms/lcbp3)
--- ---
@@ -67,16 +67,16 @@ Best practice — ทำตามถ้าทำได้ ไม่ block:
**LCBP3-DMS (Laem Chabang Port Phase 3 - Document Management System)** **LCBP3-DMS (Laem Chabang Port Phase 3 - Document Management System)**
ระบบบริหารจัดการเอกสารโครงการก่อสร้างท่าเรือแหลมฉบังระยะที่ 3 ระบบบริหารจัดการเอกสารโครงการก่อสร้างท่าเรือแหลมฉบังระยะที่ 3
**Version:** 1.8.3 (Enforcement Tiers Added) | **Status:** UAT In Progress, Security Hardened (2026-03-19) **Version:** 1.8.3 (Enforcement Tiers Added) | **Status:** UAT In Progress, Security Hardened (2026-03-19)
| Area | Status | Notes | | Area | Status | Notes |
| ------------- | ---------------------- | ------------------------------------------------------ | | ------------- | ---------------------- | ------------------------------------------------------ |
| Backend | ✅ Production Ready | NestJS 11, Express v5, 0 Vulnerabilities | | Backend | ✅ Production Ready | NestJS 11, Express v5, 0 Vulnerabilities |
| Frontend | ✅ Quality Hardened | Next.js 16.2.0, React 19.2.4, 0 `any`, 0 `console.log` | | Frontend | ✅ Quality Hardened | Next.js 16.2.0, React 19.2.4, 0 `any`, 0 `console.log` |
| Database | ✅ Schema v1.8.0 Stable | MariaDB 11.8, No-migration (ADR-009) | | Database | ✅ Schema v1.8.0 Stable | MariaDB 11.8, No-migration (ADR-009) |
| Documentation | ✅ 10/10 Gaps Closed | Product Vision → Release Policy | | Documentation | ✅ 10/10 Gaps Closed | Product Vision → Release Policy |
| AI Migration | 🔄 Pre-migration Setup | n8n + Ollama (ADR-017/018) | | AI Migration | 🔄 Pre-migration Setup | n8n + Ollama (ADR-017/018) |
| Testing | 🔄 UAT In Progress | Per `01-05-acceptance-criteria.md` | | Testing | 🔄 UAT In Progress | Per `01-05-acceptance-criteria.md` |
| Deployment | 📋 Pending Go-Live Gate | Blue-Green, QNAP Container Station | | Deployment | 📋 Pending Go-Live Gate | Blue-Green, QNAP Container Station |
| ADR-019 UUID | 🔄 Phase 5.4 Pending | 4 frontend files still use `parseInt()` on UUID | | ADR-019 UUID | ✅ All Phases Complete | Phase 5.4 done — all UUID FK issues resolved |
**Domain:** `np-dms.work` **Domain:** `np-dms.work`
--- ---
@@ -99,7 +99,7 @@ Best practice — ทำตามถ้าทำได้ ไม่ block:
### Frontend ### Frontend
- **Next.js 16.2.0** (App Router + `proxy.ts`) + **React 19.2.4** - **Next.js 16.2.0** (App Router + `proxy.ts`) + **React 19.2.4**
- **Tailwind CSS 4.2.2** + **Shadcn/UI** - **Tailwind CSS 3.4.3** + **Shadcn/UI**
- **TanStack Query** — **Server State only** - **TanStack Query** — **Server State only**
- **Zustand** — **Client State only** - **Zustand** — **Client State only**
- **React Hook Form 7.71.2** + **Zod 4.3.6** + **@hookform/resolvers 3.9.0** — **Form State only** - **React Hook Form 7.71.2** + **Zod 4.3.6** + **@hookform/resolvers 3.9.0** — **Form State only**
@@ -125,7 +125,7 @@ Best practice — ทำตามถ้าทำได้ ไม่ block:
### Security Overrides (pnpm overrides active in root `package.json`) ### Security Overrides (pnpm overrides active in root `package.json`)
- `uuid@11.0.0`, `multer@>=2.1.1`, `undici@>=7.24.0`, `axios@>=1.13.5` — patched - 30+ security overrides active (`multer@>=2.1.1`, `undici@>=7.24.0`, `axios@>=1.13.5`, etc.)
- All 52 vulnerabilities resolved as of 2026-03-19 (27 high + 20 moderate + 5 low) - All 52 vulnerabilities resolved as of 2026-03-19 (27 high + 20 moderate + 5 low)
--- ---
@@ -133,23 +133,23 @@ Best practice — ทำตามถ้าทำได้ ไม่ block:
## 🗂️ Key Spec Files (Always Check Before Writing Code) ## 🗂️ Key Spec Files (Always Check Before Writing Code)
Spec priority: **`06-Decision-Records`** > **`05-Engineering-Guidelines`** > others Spec priority: **`06-Decision-Records`** > **`05-Engineering-Guidelines`** > others
| เอกสาร | Path (relative to `specs/`) | ใช้เมื่อ | | เอกสาร | Path (relative to `specs/`) | ใช้เมื่อ |
| ------------------------- | -------------------------------------------------------------------- | ----------------------------------- | | ------------------------- | -------------------------------------------------------------------- | ----------------------------------- |
| **Glossary** | `00-Overview/00-02-glossary.md` | ตรวจคำศัพท์ Domain ก่อนเขียนเสมอ | | **Glossary** | `00-Overview/00-02-glossary.md` | ตรวจคำศัพท์ Domain ก่อนเขียนเสมอ |
| **Schema Tables** | `03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql` | ก่อนเขียน Query ทุกครั้ง | | **Schema Tables** | `03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql` | ก่อนเขียน Query ทุกครั้ง |
| **Data Dictionary** | `03-Data-and-Storage/03-01-data-dictionary.md` | ตรวจ Field Meaning + Business Rules | | **Data Dictionary** | `03-Data-and-Storage/03-01-data-dictionary.md` | ตรวจ Field Meaning + Business Rules |
| **Seed Permissions** | `03-Data-and-Storage/lcbp3-v1.8.0-seed-permissions.sql` | ตรวจ CASL Permission Matrix | | **Seed Permissions** | `03-Data-and-Storage/lcbp3-v1.8.0-seed-permissions.sql` | ตรวจ CASL Permission Matrix |
| **Edge Cases (37 rules)** | `01-Requirements/01-06-edge-cases-and-rules.md` | ป้องกัน Bug ทุก Flow | | **Edge Cases (37 rules)** | `01-Requirements/01-06-edge-cases-and-rules.md` | ป้องกัน Bug ทุก Flow |
| **Migration Scope** | `03-Data-and-Storage/03-06-migration-business-scope.md` | งาน Migration Bot (20K docs) | | **Migration Scope** | `03-Data-and-Storage/03-06-migration-business-scope.md` | งาน Migration Bot (20K docs) |
| **Release Policy** | `04-Infrastructure-OPS/04-08-release-management-policy.md` | ก่อน Deploy / Hotfix | | **Release Policy** | `04-Infrastructure-OPS/04-08-release-management-policy.md` | ก่อน Deploy / Hotfix |
| **UAT Criteria** | `01-Requirements/01-05-acceptance-criteria.md` | ตรวจความสมบูรณ์ Feature | | **UAT Criteria** | `01-Requirements/01-05-acceptance-criteria.md` | ตรวจความสมบูรณ์ Feature |
| **UUID Implementation** | `05-Engineering-Guidelines/05-07-hybrid-uuid-implementation-plan.md` | ADR-019 UUID Migration (Phase 16) | | **UUID Implementation** | `05-Engineering-Guidelines/05-07-hybrid-uuid-implementation-plan.md` | ADR-019 UUID Migration (Phase 16) |
| **Backend Guidelines** | `05-Engineering-Guidelines/05-02-backend-guidelines.md` | NestJS patterns & best practices | | **Backend Guidelines** | `05-Engineering-Guidelines/05-02-backend-guidelines.md` | NestJS patterns & best practices |
| **Frontend Guidelines** | `05-Engineering-Guidelines/05-03-frontend-guidelines.md` | Next.js patterns & best practices | | **Frontend Guidelines** | `05-Engineering-Guidelines/05-03-frontend-guidelines.md` | Next.js patterns & best practices |
| **Testing Strategy** | `05-Engineering-Guidelines/05-04-testing-strategy.md` | Coverage goals & test patterns | | **Testing Strategy** | `05-Engineering-Guidelines/05-04-testing-strategy.md` | Coverage goals & test patterns |
| **ADR-009 DB Strategy** | `06-Decision-Records/ADR-009-db-strategy.md` | Schema Change Process | | **ADR-009 DB Strategy** | `06-Decision-Records/ADR-009-db-strategy.md` | Schema Change Process |
| **ADR-018 AI Boundary** | `06-Decision-Records/ADR-018-ai-boundary.md` | AI/Ollama Integration Rules | | **ADR-018 AI Boundary** | `06-Decision-Records/ADR-018-ai-boundary.md` | AI/Ollama Integration Rules |
| **ADR-019 Hybrid ID** | `06-Decision-Records/ADR-019-hybrid-identifier-strategy.md` | Hybrid ID Strategy (INT + UUIDv7) | | **ADR-019 Hybrid ID** | `06-Decision-Records/ADR-019-hybrid-identifier-strategy.md` | Hybrid ID Strategy (INT + UUIDv7) |
### Specs Directory Structure (Brief) ### Specs Directory Structure (Brief)
@@ -163,7 +163,7 @@ specs/
├── 03-Data-and-Storage/ # Schema v1.8.0 (3 files), Seed Data, Data Dictionary, Migration Scope ├── 03-Data-and-Storage/ # Schema v1.8.0 (3 files), Seed Data, Data Dictionary, Migration Scope
├── 04-Infrastructure-OPS/# Docker, Monitoring, Deployment, Incident Response, Release Policy ├── 04-Infrastructure-OPS/# Docker, Monitoring, Deployment, Incident Response, Release Policy
├── 05-Engineering-Guidelines/ # Fullstack, Backend, Frontend, Testing, UUID Implementation ├── 05-Engineering-Guidelines/ # Fullstack, Backend, Frontend, Testing, UUID Implementation
├── 06-Decision-Records/ # 19 ADRs (ADR-001 to ADR-019) ├── 06-Decision-Records/ # 16 ADRs defined (15 with file, ADR-003/004/007 not created)
└── 99-archives/ # ประวัติ tasks เก่า └── 99-archives/ # ประวัติ tasks เก่า
``` ```
@@ -173,28 +173,30 @@ Schema is split — modify the correct file:
- `lcbp3-v1.8.0-schema-02-tables.sql`**primary reference for all queries** - `lcbp3-v1.8.0-schema-02-tables.sql`**primary reference for all queries**
- `lcbp3-v1.8.0-schema-03-views-indexes.sql` - `lcbp3-v1.8.0-schema-03-views-indexes.sql`
> **UUID Storage (MariaDB-specific):** Column type is `uuid UUID NOT NULL DEFAULT UUID()` — MariaDB native UUID, NOT MySQL's `BINARY(16)` + `UUID_TO_BIN()`/`BIN_TO_UUID()`. No transformer needed. `x.uuid` in Views directly (no conversion function).
--- ---
## 📐 ADR Reference (19 total) ## 📐 ADR Reference (16 defined)
| ADR | Topic | Key Decision | | ADR | Topic | Key Decision |
| ------- | -------------------------- | -------------------------------------------------- | | ------- | -------------------------- | ----------------------------------------------------------- |
| ADR-001 | Workflow Engine | Unified state machine for document workflows | | ADR-001 | Workflow Engine | Unified state machine for document workflows |
| ADR-002 | Doc Numbering | Redis Redlock + DB optimistic locking | | ADR-002 | Doc Numbering | Redis Redlock + DB optimistic locking |
| ADR-005 | Technology Stack | NestJS + Next.js + MariaDB + Redis | | ADR-005 | Technology Stack | NestJS + Next.js + MariaDB + Redis |
| ADR-006 | Redis Caching | Cache strategy and invalidation patterns | | ADR-006 | Redis Caching | Cache strategy and invalidation patterns |
| ADR-008 | Email Notification | BullMQ queue-based email/LINE/in-app | | ADR-008 | Email Notification | BullMQ queue-based email/LINE/in-app |
| ADR-009 | DB Strategy | No TypeORM migrations — modify schema SQL directly | | ADR-009 | DB Strategy | No TypeORM migrations — modify schema SQL directly |
| ADR-010 | Logging/Monitoring | Prometheus + Loki + Grafana stack | | ADR-010 | Logging/Monitoring | Prometheus + Loki + Grafana stack |
| ADR-011 | App Router | Next.js App Router with RSC patterns | | ADR-011 | App Router | Next.js App Router with RSC patterns |
| ADR-012 | UI Components | Shadcn/UI component library | | ADR-012 | UI Components | Shadcn/UI component library |
| ADR-013 | Form Handling | React Hook Form + Zod validation | | ADR-013 | Form Handling | React Hook Form + Zod validation |
| ADR-014 | State Management | TanStack Query (server) + Zustand (client) | | ADR-014 | State Management | TanStack Query (server) + Zustand (client) |
| ADR-015 | Deployment | Docker Compose + Gitea CI/CD | | ADR-015 | Deployment | Docker Compose + Gitea CI/CD |
| ADR-016 | Security | JWT + CASL RBAC + Helmet.js + ClamAV | | ADR-016 | Security | JWT + CASL RBAC + Helmet.js + ClamAV |
| ADR-017 | Ollama Migration | Local AI + n8n for legacy data import (~20K docs) | | ADR-017 | Ollama Migration | Local AI + n8n for legacy data import (~20K docs) |
| ADR-018 | AI Boundary (Patch 1.8.1) | AI isolation — no direct DB/storage access | | ADR-018 | AI Boundary (Patch 1.8.1) | AI isolation — no direct DB/storage access |
| ADR-019 | Hybrid Identifier Strategy | INT PK (internal) + UUIDv7 BINARY(16) (public API) | | ADR-019 | Hybrid Identifier Strategy | INT PK (internal) + UUIDv7 MariaDB native UUID (public API) |
--- ---
@@ -203,26 +205,26 @@ Schema is split — modify the correct file:
### Rule Summary ### Rule Summary
- **Internal / DB FK:** `INT AUTO_INCREMENT` (Primary Key) — never exposed - **Internal / DB FK:** `INT AUTO_INCREMENT` (Primary Key) — never exposed
- **Public API / URL:** `UUIDv7` stored as `BINARY(16)` - **Public API / URL:** `UUIDv7` stored as MariaDB native `UUID` type (auto-stored as BINARY(16), no transformer needed)
- Read `05-07-hybrid-uuid-implementation-plan.md` before any UUID-related work. - Read `05-07-hybrid-uuid-implementation-plan.md` before any UUID-related work.
### ⚠️ Phase 5.4 — Pending Fix (as of 2026-03-20) ### Phase 5.4 — COMPLETED (2026-03-21)
These files still call `parseInt()` on UUID values — **fix when touching these files**: All UUID FK issues resolved — no more `parseInt()` on UUID values:
- `frontend/components/correspondences/form.tsx` - `frontend/components/correspondences/form.tsx` ✅ fixed
- `frontend/components/user-dialog.tsx` - `frontend/components/admin/user-dialog.tsx` ✅ fixed
- `frontend/components/numbering/template-tester.tsx` - `frontend/components/numbering/template-tester.tsx` ✅ fixed
- `frontend/app/(dashboard)/rfas/page.tsx` - `frontend/app/(dashboard)/rfas/page.tsx` ✅ fixed
### UUID Serialization Behavior (TransformInterceptor) ### UUID Serialization Behavior (TransformInterceptor)
`TransformInterceptor` uses `instanceToPlain()``@Exclude()` and `@Expose()` decorators are active on all responses. `TransformInterceptor` uses `instanceToPlain()``@Exclude()` and `@Expose()` decorators are active on all responses.
| Entity Type | Behavior | | Entity Type | Behavior |
| ------------------ | ---------------------------------------------------------------------- | | ------------------ | ---------------------------------------------------------------------- |
| All entities | INT `id` has `@Exclude()`**never appears in API response** | | All entities | INT `id` has `@Exclude()`**never appears in API response** |
| Project / Contract | `uuid` has `@Expose({ name: 'id' })` → response has `id` = UUID string | | Project / Contract | `uuid` has `@Expose({ name: 'id' })` → response has `id` = UUID string |
| Other entities | Separate `uuid` field → response has `uuid`, no `id` | | Other entities | Separate `uuid` field → response has `uuid`, no `id` |
### UUID Patterns (Backend Controller) ### UUID Patterns (Backend Controller)
@@ -283,42 +285,87 @@ onValueChange={(v) => setValue("projectId", parseInt(v))}
- **Strict Mode** — all strict checks enforced. - **Strict Mode** — all strict checks enforced.
- **ZERO `any` types** — use proper types, generics, or `unknown` + type narrowing. - **ZERO `any` types** — use proper types, generics, or `unknown` + type narrowing.
- **ZERO `console.log`** — NestJS `Logger` service (backend); remove before commit (frontend). - **ZERO `console.log`** — NestJS `Logger` service (backend); remove before commit (frontend).
- Backend DTOs: fully typed with `class-validator` decorators.
- Frontend forms: fully typed with `Zod` schemas. ### 🟡 Tier 2 — IMPORTANT (CODE REVIEW)
- Prefer `readonly` for immutable properties.
- Use `satisfies` operator for type-checked object literals. ตรวจใน PR review — ไม่ block build แต่ต้องแก้ก่อน merge:
- Use `RequestWithUser` typed interface in controllers — NEVER `req: any`.
- Architecture patterns (thin controller, business logic ใน service)
- Test coverage (80%+ business logic, 70%+ backend overall)
- Cache invalidation
- **Naming conventions** — ดูรายละเอียดที่ "🚨 Naming Conventions Pain Point" ด้านล่าง
### 🟢 Tier 3 — GUIDELINES
Best practice — ทำตามถ้าทำได้ ไม่ block:
- Code style / formatting (Prettier จัดให้)
- Comment completeness
- Minor optimizations
--- ---
## 📝 Naming Conventions ## Naming Conventions Pain Point (เรื่องที่ผิดบ่อยที่สุด)
| Target | Convention | Example | **สถานะ:** พบ violations จำนวนมากใน codebase — ต้อง fix ก่อน merge เสมอ
| ------------------- | ----------- | --------------------------- |
| Files | kebab-case | `user-service.ts` | **ข้อตกลงหลัก:**
| Classes | PascalCase | `UserService` |
| Variables | camelCase | `firstName` | | Target | Convention | Example |
| DB Properties | snake_case | `user_id`, `created_at` | | ----------------------- | ----------- | ------------------------------ |
| Boolean vars | verb + noun | `isActive`, `hasPermission` | | **Files/Folders** | kebab-case | `user-service.ts` |
| **Code** | English | All identifiers in English | | **Classes** | PascalCase | `UserService` |
| **Comments / Docs** | Thai | ความคิดเห็นและเอกสารใช้ภาษาไทย | | **Variables/Functions** | camelCase | `firstName`, `getUserInfo` |
| **DB Columns** | snake_case | `user_id`, `created_at` |
| **Boolean vars** | verb + noun | `isActive`, `hasPermission` |
| **Code** | English | All identifiers in English |
| **Comments/Docs** | Thai | ความคิดเห็นและเอกสารใช้ภาษาไทย |
**❌ Common Violations พบบ่อย:**
```typescript
// ไฟล์: ใช้ PascalCase (ผิด) แทน kebab-case (ถูก)
// ❌ 1701676800000-V1_5_1_Schema_Update.ts
// ✅ 1701676800000-v1-5-1-schema-update.ts
// DTOs/Entities: ใช้ snake_case (ผิด) แทน camelCase (ถูก)
// ❌ document_number!: string;
// ✅ documentNumber!: string;
// ❌ temp_attachment_id?: number;
// ✅ tempAttachmentId?: number;
// ❌ project_id!: number;
// ✅ projectId!: number;
// Interface properties ต้อง camelCase เสมอ
// ❌ workflow_code: string;
// ✅ workflowCode: string;
```
**⚠️ ข้อควรระวัง:**
- **DB Columns** ใช้ snake_case แต่ต้องอยู่ใน `@Column({ name: 'snake_case' })` decorator เท่านั้น
- **Property names ใน TypeScript** ต้อง camelCase เสมอ — ไม่ว่าจะเป็น DTO, Entity, หรือ Interface
- **Form field names** ใน React Hook Form + Zod ต้อง camelCase
- **Type definitions** ทั้งหมดต้อง camelCase — ไม่มีข้อยกเว้น
--- ---
## 🏷️ Domain Terminology (Glossary) ## 🏷️ Domain Terminology (Glossary)
อ้างอิง `specs/00-Overview/00-02-glossary.md` เสมอ — ใช้ term ผิดจะทำให้ spec ไม่ตรง อ้างอิง `specs/00-Overview/00-02-glossary.md` เสมอ — ใช้ term ผิดจะทำให้ spec ไม่ตรง
| ✅ ใช้ (Correct) | ❌ ห้ามใช้ (Wrong) | | ✅ ใช้ (Correct) | ❌ ห้ามใช้ (Wrong) |
| ------------------ | ------------------------------------------- | | ------------------ | ------------------------------------------- |
| Correspondence | Letter, Communication, Document (generic) | | Correspondence | Letter, Communication, Document (generic) |
| RFA | Approval Request, Submit for Approval | | RFA | Approval Request, Submit for Approval |
| Transmittal | Delivery Note, Cover Letter | | Transmittal | Delivery Note, Cover Letter |
| Circulation | Distribution, Routing | | Circulation | Distribution, Routing |
| Shop Drawing | Construction Drawing (generic) | | Shop Drawing | Construction Drawing (generic) |
| Contract Drawing | Design Drawing, Blueprint | | Contract Drawing | Design Drawing, Blueprint |
| Workflow Engine | Approval Flow, Process Engine | | Workflow Engine | Approval Flow, Process Engine |
| Document Numbering | Document ID, Auto Number | | Document Numbering | Document ID, Auto Number |
| RBAC | Permission System, Access Control (generic) | | RBAC | Permission System, Access Control (generic) |
--- ---
@@ -446,15 +493,15 @@ pnpm --filter frontend test:e2e # E2E tests (Playwright)
``` ```
| Type | ใช้เมื่อ | | Type | ใช้เมื่อ |
| ---------- | ------------------------------------- | | ---------- | ---------------------------------------- |
| `feat` | เพิ่มฟีเจอร์ใหม่ | | `feat` | เพิ่มฟีเจอร์ใหม่ |
| `fix` | แก้ bug | | `fix` | แก้ bug |
| `refactor` | ปรับโครงสร้างโค้ด ไม่เปลี่ยน behavior | | `refactor` | ปรับโครงสร้างโค้ด ไม่เปลี่ยน behavior |
| `docs` | แก้ไขเอกสาร | | `docs` | แก้ไขเอกสาร |
| `test` | เพิ่ม/แก้ test | | `test` | เพิ่ม/แก้ test |
| `chore` | งาน infra, config, dependency updates | | `chore` | งาน infra, config, dependency updates |
| `style` | Formatting, linting (ไม่เปลี่ยน logic) | | `style` | Formatting, linting (ไม่เปลี่ยน logic) |
| `spec` | แก้ไข specs/ documents | | `spec` | แก้ไข specs/ documents |
| `adr` | เพิ่ม/แก้ไข Architecture Decision Record | | `adr` | เพิ่ม/แก้ไข Architecture Decision Record |
**ตัวอย่าง:** **ตัวอย่าง:**
@@ -491,12 +538,17 @@ adr/019-uuid-serialization-behavior
`.windsurf/workflows/` — ใช้สำหรับ repeatable / complex tasks เช่น: `.windsurf/workflows/` — ใช้สำหรับ repeatable / complex tasks เช่น:
- UUID migration fixes (Phase 5.4) - ADR-019 UUID pattern verification
- Spec review & gap analysis - Spec review & gap analysis
- Security audit checklist - Security audit checklist
- Release gate verification - Release gate verification
เมื่อ task ซับซ้อนและทำซ้ำได้ ให้ดู `.windsurf/workflows/` ก่อนเขียน code ใหม่
---## 🚀 Deployment Rules (ADR-015) เมื่อ task ซับซ้อนและทำซ้ำได้ ให้ดู `.windsurf/workflows/` ก่อนเขียน code ใหม่
---
## 🚀 Deployment Rules (ADR-015)
- Docker Compose on **QNAP Container Station** (production). - Docker Compose on **QNAP Container Station** (production).
- **NO `.env` files in production** — secrets in `docker-compose.yml` environment section directly. - **NO `.env` files in production** — secrets in `docker-compose.yml` environment section directly.
- Blue-Green deployment strategy. - Blue-Green deployment strategy.
@@ -507,7 +559,7 @@ adr/019-uuid-serialization-behavior
## 🚫 Forbidden Actions ## 🚫 Forbidden Actions
| ❌ Forbidden | ✅ Correct Approach | | ❌ Forbidden | ✅ Correct Approach |
| ----------------------------------------------- | --------------------------------------------------------- | | ----------------------------------------------- | --------------------------------------------------------- |
| SQL Triggers for business logic | NestJS Service methods | | SQL Triggers for business logic | NestJS Service methods |
| `.env` files in production | `docker-compose.yml` environment section | | `.env` files in production | `docker-compose.yml` environment section |
@@ -524,7 +576,7 @@ adr/019-uuid-serialization-behavior
| Generic domain terms (Letter, Blueprint, etc.) | Correct term from Glossary (`00-02-glossary.md`) | | Generic domain terms (Letter, Blueprint, etc.) | Correct term from Glossary (`00-02-glossary.md`) |
| Deploying without Release Gates | Complete `04-08-release-management-policy.md` gates | | Deploying without Release Gates | Complete `04-08-release-management-policy.md` gates |
| Starting migration without Go/No-Go Gate #1 | Gate approval first (`03-06-migration-business-scope.md`) | | Starting migration without Go/No-Go Gate #1 | Gate approval first (`03-06-migration-business-scope.md`) |
| Closing UAT without all Acceptance Criteria ✅ | Full sign-off per `01-05-acceptance-criteria.md` | | Closing UAT without all Acceptance Criteria ✅ | Full sign-off per `01-05-acceptance-criteria.md` |
| Modifying Migration Bot token scope | IP Whitelist + 7-day expiry only | | Modifying Migration Bot token scope | IP Whitelist + 7-day expiry only |
| OWASP Top 10 violations | Security checklist before every PR | | OWASP Top 10 violations | Security checklist before every PR |
@@ -563,16 +615,16 @@ adr/019-uuid-serialization-behavior
## 🎯 Windsurf Context-Aware Triggers ## 🎯 Windsurf Context-Aware Triggers
เมื่อผู้ใช้ถามเกี่ยวกับ... ให้ตรวจสอบไฟล์เหล่านี้ก่อนตอบ เมื่อผู้ใช้ถามเกี่ยวกับ... ให้ตรวจสอบไฟล์เหล่านี้ก่อนตอบ
| คำถาม/คำสั่ง | ไฟล์ที่ต้องตรวจสอบก่อน | คำตอบที่คาดหวัง | | คำถาม/คำสั่ง | ไฟล์ที่ต้องตรวจสอบก่อน | คำตอบที่คาดหวัง |
| -------------------- | ------------------------------------------------------- | ------------------------------------------------- | | -------------------- | ------------------------------------------------------- | ---------------------------------------------------------- |
| "สร้าง API ใหม่" | `05-02-backend-guidelines.md`, `schema-02-tables.sql` | NestJS Controller + Service + DTO + CASL Guard | | "สร้าง API ใหม่" | `05-02-backend-guidelines.md`, `schema-02-tables.sql` | NestJS Controller + Service + DTO + CASL Guard |
| "แก้ฟอร์ม frontend" | `05-03-frontend-guidelines.md`, `01-06-edge-cases.md` | RHF+Zod + TanStack Query + Thai comments | | "แก้ฟอร์ม frontend" | `05-03-frontend-guidelines.md`, `01-06-edge-cases.md` | RHF+Zod + TanStack Query + Thai comments |
| "เพิ่ม field ใหม่" | `ADR-009`, `data-dictionary.md`, `schema-02-tables.sql` | แก้ SQL โดยตรง + อัพเดท Data Dictionary + Entity | | "เพิ่ม field ใหม่" | `ADR-009`, `data-dictionary.md`, `schema-02-tables.sql` | แก้ SQL โดยตรง + อัพเดท Data Dictionary + Entity |
| "ตรวจสอบ UUID" | `ADR-019`, `05-07-hybrid-uuid-implementation-plan.md` | UUIDv7 BINARY(16) + TransformInterceptor behavior | | "ตรวจสอบ UUID" | `ADR-019`, `05-07-hybrid-uuid-implementation-plan.md` | UUIDv7 MariaDB native UUID + TransformInterceptor behavior |
| "สร้าง migration" | `ADR-009`, `03-06-migration-business-scope.md` | แก้ SQL schema โดยตรง + n8n workflow | | "สร้าง migration" | `ADR-009`, `03-06-migration-business-scope.md` | แก้ SQL schema โดยตรง + n8n workflow |
| "ตรวจสอบ permission" | `seed-permissions.sql`, `ADR-016` | CASL 4-Level RBAC matrix | | "ตรวจสอบ permission" | `seed-permissions.sql`, `ADR-016` | CASL 4-Level RBAC matrix |
| "deploy production" | `04-08-release-management-policy.md`, `ADR-015` | Release Gates + Blue-Green strategy | | "deploy production" | `04-08-release-management-policy.md`, `ADR-015` | Release Gates + Blue-Green strategy |
| "เพิ่ม test" | `05-04-testing-strategy.md` | Coverage goals + test patterns | | "เพิ่ม test" | `05-04-testing-strategy.md` | Coverage goals + test patterns |
--- ---
@@ -630,15 +682,13 @@ if (!entity) {
### Frontend Query Pattern ### Frontend Query Pattern
```typescript ```typescript
// [frontend-query] → TanStack Query มาตรฐาน // [frontend-query] → TanStack Query v5 มาตรฐาน
const { data, error, isLoading } = useQuery({ const { data, error, isLoading } = useQuery({
queryKey: ['correspondence', uuid], queryKey: ['correspondence', uuid],
queryFn: () => api.get(`/correspondences/${uuid}`), queryFn: () => api.get(`/correspondences/${uuid}`),
onError: (err) => {
toast.error('ไม่สามารถโหลดข้อมูลได้');
logger.error('Failed to load correspondence', { uuid, err });
},
}); });
// v5: onError ถูกลบออกจาก useQuery — จัดการ error ผ่าน return value
if (error) toast.error('ไม่สามารถโหลดข้อมูลได้');
``` ```
### Redis Cache Pattern ### Redis Cache Pattern
@@ -675,16 +725,16 @@ return null; // ❌ ทำให้ caller ต้องเช็คเอง
### Frontend (Next.js) ### Frontend (Next.js)
```typescript ```typescript
// ✅ ถูกต้อง — ใช้ TanStack Query error handling // ✅ ถูกต้อง — ใช้ TanStack Query v5 error handling
const { data, error, isLoading } = useQuery({ const { data, error, isLoading } = useQuery({
queryKey: ['correspondence', uuid], queryKey: ['correspondence', uuid],
queryFn: () => api.get(`/correspondences/${uuid}`), queryFn: () => api.get(`/correspondences/${uuid}`),
onError: (err) => {
// แสดง toast + log + fallback UI
toast.error('ไม่สามารถโหลดข้อมูลได้');
logger.error('Failed to load correspondence', { uuid, err });
},
}); });
// v5: onError removed from useQuery — handle via error return value
if (error) {
// แสดง toast + fallback UI
toast.error('ไม่สามารถโหลดข้อมูลได้');
}
``` ```
### Error Response Standard (Backend) ### Error Response Standard (Backend)
@@ -773,45 +823,6 @@ async update(uuid: string, dto: UpdateDto) {
--- ---
## 💬 Prompt Templates สำหรับถาม Windsurf
### เมื่อต้องการสร้างฟีเจอร์ใหม่
```
[NEW FEATURE]
Module: <module-name>
Requirement: <อ้างอิง user story จาก 01-02-business-rules/>
Steps:
1. ตรวจสอบ glossary และ edge cases
2. ออกแบบ DTO + Schema ตาม ADR-019
3. สร้าง Service + Controller พร้อม CASL guard
4. เขียน unit test สำหรับ business logic
5. อัพเดท API docs (Swagger)
Output: Code + Test + Spec reference
```
### เมื่อต้องการ debug
```
[DEBUG]
Issue: <อธิบายปัญหา>
File: <path/to/file>
Error: <error message/log>
Steps taken: <สิ่งที่ลองแก้ไขแล้ว>
Request: วิเคราะห์ตาม spec + แนะนำวิธีแก้ที่สอดคล้องกับ ADRs
```
### เมื่อต้องการ review code
```
[CODE REVIEW]
File: <path/to/file>
Focus: <security/performance/uuid/i18n>
Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table
```
---
## 📦 Infrastructure Quick Reference ## 📦 Infrastructure Quick Reference
### QNAP NAS (Container Station) — Production ### QNAP NAS (Container Station) — Production
@@ -859,7 +870,8 @@ Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table
| Version | Date | Changes | Updated By | | Version | Date | Changes | Updated By |
| ------- | ---------- | --------------------------------------------------------------------------------------------------------------------- | -------------- | | ------- | ---------- | --------------------------------------------------------------------------------------------------------------------- | -------------- |
| 1.8.3 | 2026-03-21 | + Rule Enforcement Tiers (🔴🟡🟢), + Tiered Development Flow | Human Dev + AI | | 1.8.4 | 2026-03-24 | Phase 5.4→✅ DONE, Tailwind 3.4.3, ADR count(16), MariaDB UUID note, TanStack v5 patterns, formatting fix | Windsurf AI |
| 1.8.3 | 2026-03-21 | + Rule Enforcement Tiers (🔴🟡🟢), + Tiered Development Flow | Human Dev + AI |
| 1.8.2 | 2026-03-21 | + Context Triggers, + Code Snippets, + Error Handling, + i18n, + Performance, + Testing Checklist, + Prompt Templates | Human Dev + AI | | 1.8.2 | 2026-03-21 | + Context Triggers, + Code Snippets, + Error Handling, + i18n, + Performance, + Testing Checklist, + Prompt Templates | Human Dev + AI |
| 1.8.1 | 2026-03-21 | + ADR-019 UUID patterns, + Phase 5.4 pending files | Claude Sonnet | | 1.8.1 | 2026-03-21 | + ADR-019 UUID patterns, + Phase 5.4 pending files | Claude Sonnet |
| 1.8.0 | 2026-03-19 | + Security overrides, + UAT criteria reference | Human Dev | | 1.8.0 | 2026-03-19 | + Security overrides, + UAT criteria reference | Human Dev |
@@ -870,7 +882,7 @@ Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table
1. แก้ไขในส่วนที่เกี่ยวข้อง 1. แก้ไขในส่วนที่เกี่ยวข้อง
2. อัพเดทตาราง Change Log ด้านบน 2. อัพเดทตาราง Change Log ด้านบน
3. เพิ่ม version number ใน header 3. เพิ่ม version number ใน header
4. Commit ด้วย message: `spec(windsurfrules): bump to v1.8.3 - <brief description>` 4. Commit ด้วย message: `spec(windsurfrules): bump to v1.8.4 - <brief description>`
--- ---
+2 -1
View File
@@ -1,3 +1,4 @@
{ {
"editor.fontSize": 20 "editor.fontSize": 20,
"npm.packageManager": "pnpm"
} }
+63 -44
View File
@@ -282,11 +282,69 @@ onValueChange={(v) => setValue("projectId", parseInt(v))}
- **Strict Mode** — all strict checks enforced. - **Strict Mode** — all strict checks enforced.
- **ZERO `any` types** — use proper types, generics, or `unknown` + type narrowing. - **ZERO `any` types** — use proper types, generics, or `unknown` + type narrowing.
- **ZERO `console.log`** — NestJS `Logger` service (backend); remove before commit (frontend). - **ZERO `console.log`** — NestJS `Logger` service (backend); remove before commit (frontend).
- Backend DTOs: fully typed with `class-validator` decorators.
- Frontend forms: fully typed with `Zod` schemas. ### 🟡 Tier 2 — IMPORTANT (CODE REVIEW)
- Prefer `readonly` for immutable properties.
- Use `satisfies` operator for type-checked object literals. ตรวจใน PR review — ไม่ block build แต่ต้องแก้ก่อน merge:
- Use `RequestWithUser` typed interface in controllers — NEVER `req: any`.
- Architecture patterns (thin controller, business logic ใน service)
- Test coverage (80%+ business logic, 70%+ backend overall)
- Cache invalidation
- **Naming conventions** — ดูรายละเอียดที่ "🚨 Naming Conventions Pain Point" ด้านล่าง
### 🟢 Tier 3 — GUIDELINES
Best practice — ทำตามถ้าทำได้ ไม่ block:
- Code style / formatting (Prettier จัดให้)
- Comment completeness
- Minor optimizations
---
## 🚨 Naming Conventions Pain Point (เรื่องที่ผิดบ่อยที่สุด)
**สถานะ:** พบ violations จำนวนมากใน codebase — ต้อง fix ก่อน merge เสมอ
**ข้อตกลงหลัก:**
| Target | Convention | Example |
| ------------------- | ----------- | --------------------------- |
| **Files/Folders** | kebab-case | `user-service.ts` |
| **Classes** | PascalCase | `UserService` |
| **Variables/Functions** | camelCase | `firstName`, `getUserInfo` |
| **DB Columns** | snake_case | `user_id`, `created_at` |
| **Boolean vars** | verb + noun | `isActive`, `hasPermission` |
| **Code** | English | All identifiers in English |
| **Comments/Docs** | Thai | ความคิดเห็นและเอกสารใช้ภาษาไทย |
**❌ Common Violations พบบ่อย:**
```typescript
// ไฟล์: ใช้ PascalCase (ผิด) แทน kebab-case (ถูก)
// ❌ 1701676800000-V1_5_1_Schema_Update.ts
// ✅ 1701676800000-v1-5-1-schema-update.ts
// DTOs/Entities: ใช้ snake_case (ผิด) แทน camelCase (ถูก)
// ❌ document_number!: string;
// ✅ documentNumber!: string;
// ❌ temp_attachment_id?: number;
// ✅ tempAttachmentId?: number;
// ❌ project_id!: number;
// ✅ projectId!: number;
// Interface properties ต้อง camelCase เสมอ
// ❌ workflow_code: string;
// ✅ workflowCode: string;
```
**⚠️ ข้อควรระวัง:**
- **DB Columns** ใช้ snake_case แต่ต้องอยู่ใน `@Column({ name: 'snake_case' })` decorator เท่านั้น
- **Property names ใน TypeScript** ต้อง camelCase เสมอ — ไม่ว่าจะเป็น DTO, Entity, หรือ Interface
- **Form field names** ใน React Hook Form + Zod ต้อง camelCase
- **Type definitions** ทั้งหมดต้อง camelCase — ไม่มีข้อยกเว้น
--- ---
@@ -774,45 +832,6 @@ async update(uuid: string, dto: UpdateDto) {
--- ---
## 💬 Prompt Templates สำหรับถาม Windsurf
### เมื่อต้องการสร้างฟีเจอร์ใหม่
```
[NEW FEATURE]
Module: <module-name>
Requirement: <อ้างอิง user story จาก 01-02-business-rules/>
Steps:
1. ตรวจสอบ glossary และ edge cases
2. ออกแบบ DTO + Schema ตาม ADR-019
3. สร้าง Service + Controller พร้อม CASL guard
4. เขียน unit test สำหรับ business logic
5. อัพเดท API docs (Swagger)
Output: Code + Test + Spec reference
```
### เมื่อต้องการ debug
```
[DEBUG]
Issue: <อธิบายปัญหา>
File: <path/to/file>
Error: <error message/log>
Steps taken: <สิ่งที่ลองแก้ไขแล้ว>
Request: วิเคราะห์ตาม spec + แนะนำวิธีแก้ที่สอดคล้องกับ ADRs
```
### เมื่อต้องการ review code
```
[CODE REVIEW]
File: <path/to/file>
Focus: <security/performance/uuid/i18n>
Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table
```
---
## 📦 Infrastructure Quick Reference ## 📦 Infrastructure Quick Reference
### QNAP NAS (Container Station) — Production ### QNAP NAS (Container Station) — Production
+172 -159
View File
@@ -1,7 +1,8 @@
# NAP-DMS Project Context & Rules # NAP-DMS Project Context & Rules
- For: Windsurf Cascade (and compatible: Codex CLI, opencode, Amp, Amazon Q, AGENTS.md tools)
- Version: 1.8.3 (Enforcement Tiers Added) | Last synced from repo: 2026-03-21 - For: Windsurf Cascade (and compatible: Codex CLI, opencode, Amp, Amazon Q, AGENTS.md tools)
- Repo: [https://git.np-dms.work/np-dms/lcbp3](https://git.np-dms.work/np-dms/lcbp3) - Version: 1.8.4 (Accuracy Pass) | Last synced from repo: 2026-03-24
- Repo: [https://git.np-dms.work/np-dms/lcbp3](https://git.np-dms.work/np-dms/lcbp3)
--- ---
@@ -64,16 +65,16 @@ Best practice — ทำตามถ้าทำได้ ไม่ block:
**LCBP3-DMS (Laem Chabang Port Phase 3 - Document Management System)** **LCBP3-DMS (Laem Chabang Port Phase 3 - Document Management System)**
ระบบบริหารจัดการเอกสารโครงการก่อสร้างท่าเรือแหลมฉบังระยะที่ 3 ระบบบริหารจัดการเอกสารโครงการก่อสร้างท่าเรือแหลมฉบังระยะที่ 3
**Version:** 1.8.3 (Enforcement Tiers Added) | **Status:** UAT In Progress, Security Hardened (2026-03-19) **Version:** 1.8.3 (Enforcement Tiers Added) | **Status:** UAT In Progress, Security Hardened (2026-03-19)
| Area | Status | Notes | | Area | Status | Notes |
| ------------- | ---------------------- | ------------------------------------------------------ | | ------------- | ---------------------- | ------------------------------------------------------ |
| Backend | ✅ Production Ready | NestJS 11, Express v5, 0 Vulnerabilities | | Backend | ✅ Production Ready | NestJS 11, Express v5, 0 Vulnerabilities |
| Frontend | ✅ Quality Hardened | Next.js 16.2.0, React 19.2.4, 0 `any`, 0 `console.log` | | Frontend | ✅ Quality Hardened | Next.js 16.2.0, React 19.2.4, 0 `any`, 0 `console.log` |
| Database | ✅ Schema v1.8.0 Stable | MariaDB 11.8, No-migration (ADR-009) | | Database | ✅ Schema v1.8.0 Stable | MariaDB 11.8, No-migration (ADR-009) |
| Documentation | ✅ 10/10 Gaps Closed | Product Vision → Release Policy | | Documentation | ✅ 10/10 Gaps Closed | Product Vision → Release Policy |
| AI Migration | 🔄 Pre-migration Setup | n8n + Ollama (ADR-017/018) | | AI Migration | 🔄 Pre-migration Setup | n8n + Ollama (ADR-017/018) |
| Testing | 🔄 UAT In Progress | Per `01-05-acceptance-criteria.md` | | Testing | 🔄 UAT In Progress | Per `01-05-acceptance-criteria.md` |
| Deployment | 📋 Pending Go-Live Gate | Blue-Green, QNAP Container Station | | Deployment | 📋 Pending Go-Live Gate | Blue-Green, QNAP Container Station |
| ADR-019 UUID | 🔄 Phase 5.4 Pending | 4 frontend files still use `parseInt()` on UUID | | ADR-019 UUID | ✅ All Phases Complete | Phase 5.4 done — all UUID FK issues resolved |
**Domain:** `np-dms.work` **Domain:** `np-dms.work`
--- ---
@@ -96,7 +97,7 @@ Best practice — ทำตามถ้าทำได้ ไม่ block:
### Frontend ### Frontend
- **Next.js 16.2.0** (App Router + `proxy.ts`) + **React 19.2.4** - **Next.js 16.2.0** (App Router + `proxy.ts`) + **React 19.2.4**
- **Tailwind CSS 4.2.2** + **Shadcn/UI** - **Tailwind CSS 3.4.3** + **Shadcn/UI**
- **TanStack Query** — **Server State only** - **TanStack Query** — **Server State only**
- **Zustand** — **Client State only** - **Zustand** — **Client State only**
- **React Hook Form 7.71.2** + **Zod 4.3.6** + **@hookform/resolvers 3.9.0** — **Form State only** - **React Hook Form 7.71.2** + **Zod 4.3.6** + **@hookform/resolvers 3.9.0** — **Form State only**
@@ -122,7 +123,7 @@ Best practice — ทำตามถ้าทำได้ ไม่ block:
### Security Overrides (pnpm overrides active in root `package.json`) ### Security Overrides (pnpm overrides active in root `package.json`)
- `uuid@11.0.0`, `multer@>=2.1.1`, `undici@>=7.24.0`, `axios@>=1.13.5` — patched - 30+ security overrides active (`multer@>=2.1.1`, `undici@>=7.24.0`, `axios@>=1.13.5`, etc.)
- All 52 vulnerabilities resolved as of 2026-03-19 (27 high + 20 moderate + 5 low) - All 52 vulnerabilities resolved as of 2026-03-19 (27 high + 20 moderate + 5 low)
--- ---
@@ -130,23 +131,23 @@ Best practice — ทำตามถ้าทำได้ ไม่ block:
## 🗂️ Key Spec Files (Always Check Before Writing Code) ## 🗂️ Key Spec Files (Always Check Before Writing Code)
Spec priority: **`06-Decision-Records`** > **`05-Engineering-Guidelines`** > others Spec priority: **`06-Decision-Records`** > **`05-Engineering-Guidelines`** > others
| เอกสาร | Path (relative to `specs/`) | ใช้เมื่อ | | เอกสาร | Path (relative to `specs/`) | ใช้เมื่อ |
| ------------------------- | -------------------------------------------------------------------- | ----------------------------------- | | ------------------------- | -------------------------------------------------------------------- | ----------------------------------- |
| **Glossary** | `00-Overview/00-02-glossary.md` | ตรวจคำศัพท์ Domain ก่อนเขียนเสมอ | | **Glossary** | `00-Overview/00-02-glossary.md` | ตรวจคำศัพท์ Domain ก่อนเขียนเสมอ |
| **Schema Tables** | `03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql` | ก่อนเขียน Query ทุกครั้ง | | **Schema Tables** | `03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql` | ก่อนเขียน Query ทุกครั้ง |
| **Data Dictionary** | `03-Data-and-Storage/03-01-data-dictionary.md` | ตรวจ Field Meaning + Business Rules | | **Data Dictionary** | `03-Data-and-Storage/03-01-data-dictionary.md` | ตรวจ Field Meaning + Business Rules |
| **Seed Permissions** | `03-Data-and-Storage/lcbp3-v1.8.0-seed-permissions.sql` | ตรวจ CASL Permission Matrix | | **Seed Permissions** | `03-Data-and-Storage/lcbp3-v1.8.0-seed-permissions.sql` | ตรวจ CASL Permission Matrix |
| **Edge Cases (37 rules)** | `01-Requirements/01-06-edge-cases-and-rules.md` | ป้องกัน Bug ทุก Flow | | **Edge Cases (37 rules)** | `01-Requirements/01-06-edge-cases-and-rules.md` | ป้องกัน Bug ทุก Flow |
| **Migration Scope** | `03-Data-and-Storage/03-06-migration-business-scope.md` | งาน Migration Bot (20K docs) | | **Migration Scope** | `03-Data-and-Storage/03-06-migration-business-scope.md` | งาน Migration Bot (20K docs) |
| **Release Policy** | `04-Infrastructure-OPS/04-08-release-management-policy.md` | ก่อน Deploy / Hotfix | | **Release Policy** | `04-Infrastructure-OPS/04-08-release-management-policy.md` | ก่อน Deploy / Hotfix |
| **UAT Criteria** | `01-Requirements/01-05-acceptance-criteria.md` | ตรวจความสมบูรณ์ Feature | | **UAT Criteria** | `01-Requirements/01-05-acceptance-criteria.md` | ตรวจความสมบูรณ์ Feature |
| **UUID Implementation** | `05-Engineering-Guidelines/05-07-hybrid-uuid-implementation-plan.md` | ADR-019 UUID Migration (Phase 16) | | **UUID Implementation** | `05-Engineering-Guidelines/05-07-hybrid-uuid-implementation-plan.md` | ADR-019 UUID Migration (Phase 16) |
| **Backend Guidelines** | `05-Engineering-Guidelines/05-02-backend-guidelines.md` | NestJS patterns & best practices | | **Backend Guidelines** | `05-Engineering-Guidelines/05-02-backend-guidelines.md` | NestJS patterns & best practices |
| **Frontend Guidelines** | `05-Engineering-Guidelines/05-03-frontend-guidelines.md` | Next.js patterns & best practices | | **Frontend Guidelines** | `05-Engineering-Guidelines/05-03-frontend-guidelines.md` | Next.js patterns & best practices |
| **Testing Strategy** | `05-Engineering-Guidelines/05-04-testing-strategy.md` | Coverage goals & test patterns | | **Testing Strategy** | `05-Engineering-Guidelines/05-04-testing-strategy.md` | Coverage goals & test patterns |
| **ADR-009 DB Strategy** | `06-Decision-Records/ADR-009-db-strategy.md` | Schema Change Process | | **ADR-009 DB Strategy** | `06-Decision-Records/ADR-009-db-strategy.md` | Schema Change Process |
| **ADR-018 AI Boundary** | `06-Decision-Records/ADR-018-ai-boundary.md` | AI/Ollama Integration Rules | | **ADR-018 AI Boundary** | `06-Decision-Records/ADR-018-ai-boundary.md` | AI/Ollama Integration Rules |
| **ADR-019 Hybrid ID** | `06-Decision-Records/ADR-019-hybrid-identifier-strategy.md` | Hybrid ID Strategy (INT + UUIDv7) | | **ADR-019 Hybrid ID** | `06-Decision-Records/ADR-019-hybrid-identifier-strategy.md` | Hybrid ID Strategy (INT + UUIDv7) |
### Specs Directory Structure (Brief) ### Specs Directory Structure (Brief)
@@ -160,7 +161,7 @@ specs/
├── 03-Data-and-Storage/ # Schema v1.8.0 (3 files), Seed Data, Data Dictionary, Migration Scope ├── 03-Data-and-Storage/ # Schema v1.8.0 (3 files), Seed Data, Data Dictionary, Migration Scope
├── 04-Infrastructure-OPS/# Docker, Monitoring, Deployment, Incident Response, Release Policy ├── 04-Infrastructure-OPS/# Docker, Monitoring, Deployment, Incident Response, Release Policy
├── 05-Engineering-Guidelines/ # Fullstack, Backend, Frontend, Testing, UUID Implementation ├── 05-Engineering-Guidelines/ # Fullstack, Backend, Frontend, Testing, UUID Implementation
├── 06-Decision-Records/ # 19 ADRs (ADR-001 to ADR-019) ├── 06-Decision-Records/ # 16 ADRs defined (15 with file, ADR-003/004/007 not created)
└── 99-archives/ # ประวัติ tasks เก่า └── 99-archives/ # ประวัติ tasks เก่า
``` ```
@@ -170,28 +171,30 @@ Schema is split — modify the correct file:
- `lcbp3-v1.8.0-schema-02-tables.sql`**primary reference for all queries** - `lcbp3-v1.8.0-schema-02-tables.sql`**primary reference for all queries**
- `lcbp3-v1.8.0-schema-03-views-indexes.sql` - `lcbp3-v1.8.0-schema-03-views-indexes.sql`
> **UUID Storage (MariaDB-specific):** Column type is `uuid UUID NOT NULL DEFAULT UUID()` — MariaDB native UUID, NOT MySQL's `BINARY(16)` + `UUID_TO_BIN()`/`BIN_TO_UUID()`. No transformer needed. `x.uuid` in Views directly (no conversion function).
--- ---
## 📐 ADR Reference (19 total) ## 📐 ADR Reference (16 defined)
| ADR | Topic | Key Decision | | ADR | Topic | Key Decision |
| ------- | -------------------------- | -------------------------------------------------- | | ------- | -------------------------- | ----------------------------------------------------------- |
| ADR-001 | Workflow Engine | Unified state machine for document workflows | | ADR-001 | Workflow Engine | Unified state machine for document workflows |
| ADR-002 | Doc Numbering | Redis Redlock + DB optimistic locking | | ADR-002 | Doc Numbering | Redis Redlock + DB optimistic locking |
| ADR-005 | Technology Stack | NestJS + Next.js + MariaDB + Redis | | ADR-005 | Technology Stack | NestJS + Next.js + MariaDB + Redis |
| ADR-006 | Redis Caching | Cache strategy and invalidation patterns | | ADR-006 | Redis Caching | Cache strategy and invalidation patterns |
| ADR-008 | Email Notification | BullMQ queue-based email/LINE/in-app | | ADR-008 | Email Notification | BullMQ queue-based email/LINE/in-app |
| ADR-009 | DB Strategy | No TypeORM migrations — modify schema SQL directly | | ADR-009 | DB Strategy | No TypeORM migrations — modify schema SQL directly |
| ADR-010 | Logging/Monitoring | Prometheus + Loki + Grafana stack | | ADR-010 | Logging/Monitoring | Prometheus + Loki + Grafana stack |
| ADR-011 | App Router | Next.js App Router with RSC patterns | | ADR-011 | App Router | Next.js App Router with RSC patterns |
| ADR-012 | UI Components | Shadcn/UI component library | | ADR-012 | UI Components | Shadcn/UI component library |
| ADR-013 | Form Handling | React Hook Form + Zod validation | | ADR-013 | Form Handling | React Hook Form + Zod validation |
| ADR-014 | State Management | TanStack Query (server) + Zustand (client) | | ADR-014 | State Management | TanStack Query (server) + Zustand (client) |
| ADR-015 | Deployment | Docker Compose + Gitea CI/CD | | ADR-015 | Deployment | Docker Compose + Gitea CI/CD |
| ADR-016 | Security | JWT + CASL RBAC + Helmet.js + ClamAV | | ADR-016 | Security | JWT + CASL RBAC + Helmet.js + ClamAV |
| ADR-017 | Ollama Migration | Local AI + n8n for legacy data import (~20K docs) | | ADR-017 | Ollama Migration | Local AI + n8n for legacy data import (~20K docs) |
| ADR-018 | AI Boundary (Patch 1.8.1) | AI isolation — no direct DB/storage access | | ADR-018 | AI Boundary (Patch 1.8.1) | AI isolation — no direct DB/storage access |
| ADR-019 | Hybrid Identifier Strategy | INT PK (internal) + UUIDv7 BINARY(16) (public API) | | ADR-019 | Hybrid Identifier Strategy | INT PK (internal) + UUIDv7 MariaDB native UUID (public API) |
--- ---
@@ -200,26 +203,26 @@ Schema is split — modify the correct file:
### Rule Summary ### Rule Summary
- **Internal / DB FK:** `INT AUTO_INCREMENT` (Primary Key) — never exposed - **Internal / DB FK:** `INT AUTO_INCREMENT` (Primary Key) — never exposed
- **Public API / URL:** `UUIDv7` stored as `BINARY(16)` - **Public API / URL:** `UUIDv7` stored as MariaDB native `UUID` type (auto-stored as BINARY(16), no transformer needed)
- Read `05-07-hybrid-uuid-implementation-plan.md` before any UUID-related work. - Read `05-07-hybrid-uuid-implementation-plan.md` before any UUID-related work.
### ⚠️ Phase 5.4 — Pending Fix (as of 2026-03-20) ### Phase 5.4 — COMPLETED (2026-03-21)
These files still call `parseInt()` on UUID values — **fix when touching these files**: All UUID FK issues resolved — no more `parseInt()` on UUID values:
- `frontend/components/correspondences/form.tsx` - `frontend/components/correspondences/form.tsx` ✅ fixed
- `frontend/components/user-dialog.tsx` - `frontend/components/admin/user-dialog.tsx` ✅ fixed
- `frontend/components/numbering/template-tester.tsx` - `frontend/components/numbering/template-tester.tsx` ✅ fixed
- `frontend/app/(dashboard)/rfas/page.tsx` - `frontend/app/(dashboard)/rfas/page.tsx` ✅ fixed
### UUID Serialization Behavior (TransformInterceptor) ### UUID Serialization Behavior (TransformInterceptor)
`TransformInterceptor` uses `instanceToPlain()``@Exclude()` and `@Expose()` decorators are active on all responses. `TransformInterceptor` uses `instanceToPlain()``@Exclude()` and `@Expose()` decorators are active on all responses.
| Entity Type | Behavior | | Entity Type | Behavior |
| ------------------ | ---------------------------------------------------------------------- | | ------------------ | ---------------------------------------------------------------------- |
| All entities | INT `id` has `@Exclude()`**never appears in API response** | | All entities | INT `id` has `@Exclude()`**never appears in API response** |
| Project / Contract | `uuid` has `@Expose({ name: 'id' })` → response has `id` = UUID string | | Project / Contract | `uuid` has `@Expose({ name: 'id' })` → response has `id` = UUID string |
| Other entities | Separate `uuid` field → response has `uuid`, no `id` | | Other entities | Separate `uuid` field → response has `uuid`, no `id` |
### UUID Patterns (Backend Controller) ### UUID Patterns (Backend Controller)
@@ -280,42 +283,87 @@ onValueChange={(v) => setValue("projectId", parseInt(v))}
- **Strict Mode** — all strict checks enforced. - **Strict Mode** — all strict checks enforced.
- **ZERO `any` types** — use proper types, generics, or `unknown` + type narrowing. - **ZERO `any` types** — use proper types, generics, or `unknown` + type narrowing.
- **ZERO `console.log`** — NestJS `Logger` service (backend); remove before commit (frontend). - **ZERO `console.log`** — NestJS `Logger` service (backend); remove before commit (frontend).
- Backend DTOs: fully typed with `class-validator` decorators.
- Frontend forms: fully typed with `Zod` schemas. ### 🟡 Tier 2 — IMPORTANT (CODE REVIEW)
- Prefer `readonly` for immutable properties.
- Use `satisfies` operator for type-checked object literals. ตรวจใน PR review — ไม่ block build แต่ต้องแก้ก่อน merge:
- Use `RequestWithUser` typed interface in controllers — NEVER `req: any`.
- Architecture patterns (thin controller, business logic ใน service)
- Test coverage (80%+ business logic, 70%+ backend overall)
- Cache invalidation
- **Naming conventions** — ดูรายละเอียดที่ "🚨 Naming Conventions Pain Point" ด้านล่าง
### 🟢 Tier 3 — GUIDELINES
Best practice — ทำตามถ้าทำได้ ไม่ block:
- Code style / formatting (Prettier จัดให้)
- Comment completeness
- Minor optimizations
--- ---
## 📝 Naming Conventions ## Naming Conventions Pain Point (เรื่องที่ผิดบ่อยที่สุด)
| Target | Convention | Example | **สถานะ:** พบ violations จำนวนมากใน codebase — ต้อง fix ก่อน merge เสมอ
| ------------------- | ----------- | --------------------------- |
| Files | kebab-case | `user-service.ts` | **ข้อตกลงหลัก:**
| Classes | PascalCase | `UserService` |
| Variables | camelCase | `firstName` | | Target | Convention | Example |
| DB Properties | snake_case | `user_id`, `created_at` | | ----------------------- | ----------- | ------------------------------ |
| Boolean vars | verb + noun | `isActive`, `hasPermission` | | **Files/Folders** | kebab-case | `user-service.ts` |
| **Code** | English | All identifiers in English | | **Classes** | PascalCase | `UserService` |
| **Comments / Docs** | Thai | ความคิดเห็นและเอกสารใช้ภาษาไทย | | **Variables/Functions** | camelCase | `firstName`, `getUserInfo` |
| **DB Columns** | snake_case | `user_id`, `created_at` |
| **Boolean vars** | verb + noun | `isActive`, `hasPermission` |
| **Code** | English | All identifiers in English |
| **Comments/Docs** | Thai | ความคิดเห็นและเอกสารใช้ภาษาไทย |
**❌ Common Violations พบบ่อย:**
```typescript
// ไฟล์: ใช้ PascalCase (ผิด) แทน kebab-case (ถูก)
// ❌ 1701676800000-V1_5_1_Schema_Update.ts
// ✅ 1701676800000-v1-5-1-schema-update.ts
// DTOs/Entities: ใช้ snake_case (ผิด) แทน camelCase (ถูก)
// ❌ document_number!: string;
// ✅ documentNumber!: string;
// ❌ temp_attachment_id?: number;
// ✅ tempAttachmentId?: number;
// ❌ project_id!: number;
// ✅ projectId!: number;
// Interface properties ต้อง camelCase เสมอ
// ❌ workflow_code: string;
// ✅ workflowCode: string;
```
**⚠️ ข้อควรระวัง:**
- **DB Columns** ใช้ snake_case แต่ต้องอยู่ใน `@Column({ name: 'snake_case' })` decorator เท่านั้น
- **Property names ใน TypeScript** ต้อง camelCase เสมอ — ไม่ว่าจะเป็น DTO, Entity, หรือ Interface
- **Form field names** ใน React Hook Form + Zod ต้อง camelCase
- **Type definitions** ทั้งหมดต้อง camelCase — ไม่มีข้อยกเว้น
--- ---
## 🏷️ Domain Terminology (Glossary) ## 🏷️ Domain Terminology (Glossary)
อ้างอิง `specs/00-Overview/00-02-glossary.md` เสมอ — ใช้ term ผิดจะทำให้ spec ไม่ตรง อ้างอิง `specs/00-Overview/00-02-glossary.md` เสมอ — ใช้ term ผิดจะทำให้ spec ไม่ตรง
| ✅ ใช้ (Correct) | ❌ ห้ามใช้ (Wrong) | | ✅ ใช้ (Correct) | ❌ ห้ามใช้ (Wrong) |
| ------------------ | ------------------------------------------- | | ------------------ | ------------------------------------------- |
| Correspondence | Letter, Communication, Document (generic) | | Correspondence | Letter, Communication, Document (generic) |
| RFA | Approval Request, Submit for Approval | | RFA | Approval Request, Submit for Approval |
| Transmittal | Delivery Note, Cover Letter | | Transmittal | Delivery Note, Cover Letter |
| Circulation | Distribution, Routing | | Circulation | Distribution, Routing |
| Shop Drawing | Construction Drawing (generic) | | Shop Drawing | Construction Drawing (generic) |
| Contract Drawing | Design Drawing, Blueprint | | Contract Drawing | Design Drawing, Blueprint |
| Workflow Engine | Approval Flow, Process Engine | | Workflow Engine | Approval Flow, Process Engine |
| Document Numbering | Document ID, Auto Number | | Document Numbering | Document ID, Auto Number |
| RBAC | Permission System, Access Control (generic) | | RBAC | Permission System, Access Control (generic) |
--- ---
@@ -443,15 +491,15 @@ pnpm --filter frontend test:e2e # E2E tests (Playwright)
``` ```
| Type | ใช้เมื่อ | | Type | ใช้เมื่อ |
| ---------- | ------------------------------------- | | ---------- | ---------------------------------------- |
| `feat` | เพิ่มฟีเจอร์ใหม่ | | `feat` | เพิ่มฟีเจอร์ใหม่ |
| `fix` | แก้ bug | | `fix` | แก้ bug |
| `refactor` | ปรับโครงสร้างโค้ด ไม่เปลี่ยน behavior | | `refactor` | ปรับโครงสร้างโค้ด ไม่เปลี่ยน behavior |
| `docs` | แก้ไขเอกสาร | | `docs` | แก้ไขเอกสาร |
| `test` | เพิ่ม/แก้ test | | `test` | เพิ่ม/แก้ test |
| `chore` | งาน infra, config, dependency updates | | `chore` | งาน infra, config, dependency updates |
| `style` | Formatting, linting (ไม่เปลี่ยน logic) | | `style` | Formatting, linting (ไม่เปลี่ยน logic) |
| `spec` | แก้ไข specs/ documents | | `spec` | แก้ไข specs/ documents |
| `adr` | เพิ่ม/แก้ไข Architecture Decision Record | | `adr` | เพิ่ม/แก้ไข Architecture Decision Record |
**ตัวอย่าง:** **ตัวอย่าง:**
@@ -488,12 +536,17 @@ adr/019-uuid-serialization-behavior
`.windsurf/workflows/` — ใช้สำหรับ repeatable / complex tasks เช่น: `.windsurf/workflows/` — ใช้สำหรับ repeatable / complex tasks เช่น:
- UUID migration fixes (Phase 5.4) - ADR-019 UUID pattern verification
- Spec review & gap analysis - Spec review & gap analysis
- Security audit checklist - Security audit checklist
- Release gate verification - Release gate verification
เมื่อ task ซับซ้อนและทำซ้ำได้ ให้ดู `.windsurf/workflows/` ก่อนเขียน code ใหม่
---## 🚀 Deployment Rules (ADR-015) เมื่อ task ซับซ้อนและทำซ้ำได้ ให้ดู `.windsurf/workflows/` ก่อนเขียน code ใหม่
---
## 🚀 Deployment Rules (ADR-015)
- Docker Compose on **QNAP Container Station** (production). - Docker Compose on **QNAP Container Station** (production).
- **NO `.env` files in production** — secrets in `docker-compose.yml` environment section directly. - **NO `.env` files in production** — secrets in `docker-compose.yml` environment section directly.
- Blue-Green deployment strategy. - Blue-Green deployment strategy.
@@ -504,7 +557,7 @@ adr/019-uuid-serialization-behavior
## 🚫 Forbidden Actions ## 🚫 Forbidden Actions
| ❌ Forbidden | ✅ Correct Approach | | ❌ Forbidden | ✅ Correct Approach |
| ----------------------------------------------- | --------------------------------------------------------- | | ----------------------------------------------- | --------------------------------------------------------- |
| SQL Triggers for business logic | NestJS Service methods | | SQL Triggers for business logic | NestJS Service methods |
| `.env` files in production | `docker-compose.yml` environment section | | `.env` files in production | `docker-compose.yml` environment section |
@@ -521,7 +574,7 @@ adr/019-uuid-serialization-behavior
| Generic domain terms (Letter, Blueprint, etc.) | Correct term from Glossary (`00-02-glossary.md`) | | Generic domain terms (Letter, Blueprint, etc.) | Correct term from Glossary (`00-02-glossary.md`) |
| Deploying without Release Gates | Complete `04-08-release-management-policy.md` gates | | Deploying without Release Gates | Complete `04-08-release-management-policy.md` gates |
| Starting migration without Go/No-Go Gate #1 | Gate approval first (`03-06-migration-business-scope.md`) | | Starting migration without Go/No-Go Gate #1 | Gate approval first (`03-06-migration-business-scope.md`) |
| Closing UAT without all Acceptance Criteria ✅ | Full sign-off per `01-05-acceptance-criteria.md` | | Closing UAT without all Acceptance Criteria ✅ | Full sign-off per `01-05-acceptance-criteria.md` |
| Modifying Migration Bot token scope | IP Whitelist + 7-day expiry only | | Modifying Migration Bot token scope | IP Whitelist + 7-day expiry only |
| OWASP Top 10 violations | Security checklist before every PR | | OWASP Top 10 violations | Security checklist before every PR |
@@ -560,16 +613,16 @@ adr/019-uuid-serialization-behavior
## 🎯 Windsurf Context-Aware Triggers ## 🎯 Windsurf Context-Aware Triggers
เมื่อผู้ใช้ถามเกี่ยวกับ... ให้ตรวจสอบไฟล์เหล่านี้ก่อนตอบ เมื่อผู้ใช้ถามเกี่ยวกับ... ให้ตรวจสอบไฟล์เหล่านี้ก่อนตอบ
| คำถาม/คำสั่ง | ไฟล์ที่ต้องตรวจสอบก่อน | คำตอบที่คาดหวัง | | คำถาม/คำสั่ง | ไฟล์ที่ต้องตรวจสอบก่อน | คำตอบที่คาดหวัง |
| -------------------- | ------------------------------------------------------- | ------------------------------------------------- | | -------------------- | ------------------------------------------------------- | ---------------------------------------------------------- |
| "สร้าง API ใหม่" | `05-02-backend-guidelines.md`, `schema-02-tables.sql` | NestJS Controller + Service + DTO + CASL Guard | | "สร้าง API ใหม่" | `05-02-backend-guidelines.md`, `schema-02-tables.sql` | NestJS Controller + Service + DTO + CASL Guard |
| "แก้ฟอร์ม frontend" | `05-03-frontend-guidelines.md`, `01-06-edge-cases.md` | RHF+Zod + TanStack Query + Thai comments | | "แก้ฟอร์ม frontend" | `05-03-frontend-guidelines.md`, `01-06-edge-cases.md` | RHF+Zod + TanStack Query + Thai comments |
| "เพิ่ม field ใหม่" | `ADR-009`, `data-dictionary.md`, `schema-02-tables.sql` | แก้ SQL โดยตรง + อัพเดท Data Dictionary + Entity | | "เพิ่ม field ใหม่" | `ADR-009`, `data-dictionary.md`, `schema-02-tables.sql` | แก้ SQL โดยตรง + อัพเดท Data Dictionary + Entity |
| "ตรวจสอบ UUID" | `ADR-019`, `05-07-hybrid-uuid-implementation-plan.md` | UUIDv7 BINARY(16) + TransformInterceptor behavior | | "ตรวจสอบ UUID" | `ADR-019`, `05-07-hybrid-uuid-implementation-plan.md` | UUIDv7 MariaDB native UUID + TransformInterceptor behavior |
| "สร้าง migration" | `ADR-009`, `03-06-migration-business-scope.md` | แก้ SQL schema โดยตรง + n8n workflow | | "สร้าง migration" | `ADR-009`, `03-06-migration-business-scope.md` | แก้ SQL schema โดยตรง + n8n workflow |
| "ตรวจสอบ permission" | `seed-permissions.sql`, `ADR-016` | CASL 4-Level RBAC matrix | | "ตรวจสอบ permission" | `seed-permissions.sql`, `ADR-016` | CASL 4-Level RBAC matrix |
| "deploy production" | `04-08-release-management-policy.md`, `ADR-015` | Release Gates + Blue-Green strategy | | "deploy production" | `04-08-release-management-policy.md`, `ADR-015` | Release Gates + Blue-Green strategy |
| "เพิ่ม test" | `05-04-testing-strategy.md` | Coverage goals + test patterns | | "เพิ่ม test" | `05-04-testing-strategy.md` | Coverage goals + test patterns |
--- ---
@@ -627,15 +680,13 @@ if (!entity) {
### Frontend Query Pattern ### Frontend Query Pattern
```typescript ```typescript
// [frontend-query] → TanStack Query มาตรฐาน // [frontend-query] → TanStack Query v5 มาตรฐาน
const { data, error, isLoading } = useQuery({ const { data, error, isLoading } = useQuery({
queryKey: ['correspondence', uuid], queryKey: ['correspondence', uuid],
queryFn: () => api.get(`/correspondences/${uuid}`), queryFn: () => api.get(`/correspondences/${uuid}`),
onError: (err) => {
toast.error('ไม่สามารถโหลดข้อมูลได้');
logger.error('Failed to load correspondence', { uuid, err });
},
}); });
// v5: onError ถูกลบออกจาก useQuery — จัดการ error ผ่าน return value
if (error) toast.error('ไม่สามารถโหลดข้อมูลได้');
``` ```
### Redis Cache Pattern ### Redis Cache Pattern
@@ -672,16 +723,16 @@ return null; // ❌ ทำให้ caller ต้องเช็คเอง
### Frontend (Next.js) ### Frontend (Next.js)
```typescript ```typescript
// ✅ ถูกต้อง — ใช้ TanStack Query error handling // ✅ ถูกต้อง — ใช้ TanStack Query v5 error handling
const { data, error, isLoading } = useQuery({ const { data, error, isLoading } = useQuery({
queryKey: ['correspondence', uuid], queryKey: ['correspondence', uuid],
queryFn: () => api.get(`/correspondences/${uuid}`), queryFn: () => api.get(`/correspondences/${uuid}`),
onError: (err) => {
// แสดง toast + log + fallback UI
toast.error('ไม่สามารถโหลดข้อมูลได้');
logger.error('Failed to load correspondence', { uuid, err });
},
}); });
// v5: onError removed from useQuery — handle via error return value
if (error) {
// แสดง toast + fallback UI
toast.error('ไม่สามารถโหลดข้อมูลได้');
}
``` ```
### Error Response Standard (Backend) ### Error Response Standard (Backend)
@@ -770,45 +821,6 @@ async update(uuid: string, dto: UpdateDto) {
--- ---
## 💬 Prompt Templates สำหรับถาม Windsurf
### เมื่อต้องการสร้างฟีเจอร์ใหม่
```
[NEW FEATURE]
Module: <module-name>
Requirement: <อ้างอิง user story จาก 01-02-business-rules/>
Steps:
1. ตรวจสอบ glossary และ edge cases
2. ออกแบบ DTO + Schema ตาม ADR-019
3. สร้าง Service + Controller พร้อม CASL guard
4. เขียน unit test สำหรับ business logic
5. อัพเดท API docs (Swagger)
Output: Code + Test + Spec reference
```
### เมื่อต้องการ debug
```
[DEBUG]
Issue: <อธิบายปัญหา>
File: <path/to/file>
Error: <error message/log>
Steps taken: <สิ่งที่ลองแก้ไขแล้ว>
Request: วิเคราะห์ตาม spec + แนะนำวิธีแก้ที่สอดคล้องกับ ADRs
```
### เมื่อต้องการ review code
```
[CODE REVIEW]
File: <path/to/file>
Focus: <security/performance/uuid/i18n>
Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table
```
---
## 📦 Infrastructure Quick Reference ## 📦 Infrastructure Quick Reference
### QNAP NAS (Container Station) — Production ### QNAP NAS (Container Station) — Production
@@ -856,7 +868,8 @@ Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table
| Version | Date | Changes | Updated By | | Version | Date | Changes | Updated By |
| ------- | ---------- | --------------------------------------------------------------------------------------------------------------------- | -------------- | | ------- | ---------- | --------------------------------------------------------------------------------------------------------------------- | -------------- |
| 1.8.3 | 2026-03-21 | + Rule Enforcement Tiers (🔴🟡🟢), + Tiered Development Flow | Human Dev + AI | | 1.8.4 | 2026-03-24 | Phase 5.4→✅ DONE, Tailwind 3.4.3, ADR count(16), MariaDB UUID note, TanStack v5 patterns, formatting fix | Windsurf AI |
| 1.8.3 | 2026-03-21 | + Rule Enforcement Tiers (🔴🟡🟢), + Tiered Development Flow | Human Dev + AI |
| 1.8.2 | 2026-03-21 | + Context Triggers, + Code Snippets, + Error Handling, + i18n, + Performance, + Testing Checklist, + Prompt Templates | Human Dev + AI | | 1.8.2 | 2026-03-21 | + Context Triggers, + Code Snippets, + Error Handling, + i18n, + Performance, + Testing Checklist, + Prompt Templates | Human Dev + AI |
| 1.8.1 | 2026-03-21 | + ADR-019 UUID patterns, + Phase 5.4 pending files | Claude Sonnet | | 1.8.1 | 2026-03-21 | + ADR-019 UUID patterns, + Phase 5.4 pending files | Claude Sonnet |
| 1.8.0 | 2026-03-19 | + Security overrides, + UAT criteria reference | Human Dev | | 1.8.0 | 2026-03-19 | + Security overrides, + UAT criteria reference | Human Dev |
@@ -867,7 +880,7 @@ Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table
1. แก้ไขในส่วนที่เกี่ยวข้อง 1. แก้ไขในส่วนที่เกี่ยวข้อง
2. อัพเดทตาราง Change Log ด้านบน 2. อัพเดทตาราง Change Log ด้านบน
3. เพิ่ม version number ใน header 3. เพิ่ม version number ใน header
4. Commit ด้วย message: `spec(windsurfrules): bump to v1.8.3 - <brief description>` 4. Commit ด้วย message: `spec(windsurfrules): bump to v1.8.4 - <brief description>`
--- ---
+3
View File
@@ -0,0 +1,3 @@
{
"npm.packageManager": "pnpm"
}
@@ -88,7 +88,7 @@ export class FormatService {
// 3. Fallback // 3. Fallback
return { return {
template: '{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR:BE}', template: '{ORG}-{RECIPIENT}-{SEQ:4}/{YEAR:BE}',
resetSequenceYearly: true, resetSequenceYearly: true,
isDefault: true, isDefault: true,
}; };
@@ -14,23 +14,23 @@ import { Type } from 'class-transformer';
export class VirtualColumnConfigDto { export class VirtualColumnConfigDto {
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
json_path!: string; jsonPath!: string;
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
column_name!: string; columnName!: string;
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
data_type!: 'INT' | 'VARCHAR' | 'BOOLEAN' | 'DATE' | 'DECIMAL' | 'DATETIME'; dataType!: 'INT' | 'VARCHAR' | 'BOOLEAN' | 'DATE' | 'DECIMAL' | 'DATETIME';
@IsString() @IsString()
@IsOptional() @IsOptional()
index_type?: 'INDEX' | 'UNIQUE' | 'FULLTEXT'; indexType?: 'INDEX' | 'UNIQUE' | 'FULLTEXT';
@IsBoolean() @IsBoolean()
@IsOptional() @IsOptional()
is_required?: boolean; isRequired?: boolean;
} }
export class CreateJsonSchemaDto { export class CreateJsonSchemaDto {
@@ -9,11 +9,11 @@ import {
} from 'typeorm'; } from 'typeorm';
export interface VirtualColumnConfig { export interface VirtualColumnConfig {
json_path: string; jsonPath: string;
column_name: string; columnName: string;
data_type: 'INT' | 'VARCHAR' | 'BOOLEAN' | 'DATE' | 'DECIMAL' | 'DATETIME'; dataType: 'INT' | 'VARCHAR' | 'BOOLEAN' | 'DATE' | 'DECIMAL' | 'DATETIME';
index_type?: 'INDEX' | 'UNIQUE' | 'FULLTEXT'; indexType?: 'INDEX' | 'UNIQUE' | 'FULLTEXT';
is_required: boolean; isRequired: boolean;
} }
@Entity('json_schemas') @Entity('json_schemas')
@@ -12,19 +12,19 @@ import {
import { Project } from '../../project/entities/project.entity'; import { Project } from '../../project/entities/project.entity';
@Entity('tags') @Entity('tags')
@Unique('ux_tag_project', ['project_id', 'tag_name']) @Unique('ux_tag_project', ['projectId', 'tagName'])
export class Tag { export class Tag {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id!: number; // เพิ่ม ! id!: number; // เพิ่ม !
@Column({ type: 'int', nullable: true }) @Column({ type: 'int', nullable: true })
project_id!: number | null; // เพิ่ม ! projectId!: number | null; // เพิ่ม !
@Column({ length: 100 }) @Column({ length: 100 })
tag_name!: string; // เพิ่ม ! tagName!: string; // เพิ่ม !
@Column({ length: 30, default: 'default' }) @Column({ length: 30, default: 'default' })
color_code!: string; // เพิ่ม ! colorCode!: string; // เพิ่ม !
@Column({ type: 'text', nullable: true }) @Column({ type: 'text', nullable: true })
description!: string | null; // เพิ่ม ! description!: string | null; // เพิ่ม !
@@ -35,14 +35,14 @@ export class Tag {
project?: Project; project?: Project;
@CreateDateColumn() @CreateDateColumn()
created_at!: Date; // เพิ่ม ! createdAt!: Date; // เพิ่ม !
@UpdateDateColumn() @UpdateDateColumn()
updated_at!: Date; // เพิ่ม ! updatedAt!: Date; // เพิ่ม !
@Column({ type: 'int', nullable: true }) @Column({ type: 'int', nullable: true })
created_by!: number | null; // เพิ่ม ! createdBy!: number | null; // เพิ่ม !
@DeleteDateColumn() @DeleteDateColumn()
deleted_at!: Date | null; // เพิ่ม ! deletedAt!: Date | null; // เพิ่ม !
} }
@@ -4,22 +4,22 @@ import { MigrationErrorType } from '../entities/migration-error.entity';
export class CreateMigrationErrorDto { export class CreateMigrationErrorDto {
@IsString() @IsString()
@IsOptional() @IsOptional()
batch_id?: string; batchId?: string;
@IsString() @IsString()
@IsOptional() @IsOptional()
document_number?: string; documentNumber?: string;
@IsString() @IsString()
@IsOptional() @IsOptional()
@IsEnum(MigrationErrorType) @IsEnum(MigrationErrorType)
error_type?: MigrationErrorType; errorType?: MigrationErrorType;
@IsString() @IsString()
@IsOptional() @IsOptional()
error_message?: string; errorMessage?: string;
@IsString() @IsString()
@IsOptional() @IsOptional()
raw_ai_response?: string; rawAiResponse?: string;
} }
@@ -10,7 +10,7 @@ import {
export class EnqueueMigrationDto { export class EnqueueMigrationDto {
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
document_number!: string; documentNumber!: string;
@IsString() @IsString()
@IsOptional() @IsOptional()
@@ -18,7 +18,7 @@ export class EnqueueMigrationDto {
@IsString() @IsString()
@IsOptional() @IsOptional()
original_subject?: string; originalSubject?: string;
@IsString() @IsString()
@IsOptional() @IsOptional()
@@ -30,27 +30,27 @@ export class EnqueueMigrationDto {
@IsString() @IsString()
@IsOptional() @IsOptional()
ai_summary?: string; aiSummary?: string;
@IsNumber() @IsNumber()
@IsOptional() @IsOptional()
project_id?: number; projectId?: number;
@IsNumber() @IsNumber()
@IsOptional() @IsOptional()
sender_org_id?: number; senderOrgId?: number;
@IsNumber() @IsNumber()
@IsOptional() @IsOptional()
receiver_org_id?: number; receiverOrgId?: number;
@IsString() @IsString()
@IsOptional() @IsOptional()
issued_date?: string; issuedDate?: string;
@IsString() @IsString()
@IsOptional() @IsOptional()
received_date?: string; receivedDate?: string;
@IsString() @IsString()
@IsOptional() @IsOptional()
@@ -58,18 +58,18 @@ export class EnqueueMigrationDto {
@IsArray() @IsArray()
@IsOptional() @IsOptional()
extracted_tags?: Record<string, string>[]; extractedTags?: Record<string, string>[];
@IsOptional() @IsOptional()
details?: Record<string, unknown>; details?: Record<string, unknown>;
@IsNumber() @IsNumber()
@IsOptional() @IsOptional()
temp_attachment_id?: number; tempAttachmentId?: number;
@IsBoolean() @IsBoolean()
@IsOptional() @IsOptional()
is_valid?: boolean; isValid?: boolean;
@IsNumber() @IsNumber()
@IsOptional() @IsOptional()
@@ -77,5 +77,5 @@ export class EnqueueMigrationDto {
@IsArray() @IsArray()
@IsOptional() @IsOptional()
ai_issues?: Record<string, unknown>[]; aiIssues?: Record<string, unknown>[];
} }
@@ -9,7 +9,7 @@ import {
export class ImportCorrespondenceDto { export class ImportCorrespondenceDto {
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
document_number!: string; documentNumber!: string;
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
@@ -21,26 +21,26 @@ export class ImportCorrespondenceDto {
@IsString() @IsString()
@IsOptional() @IsOptional()
source_file_path?: string; sourceFilePath?: string;
@IsNumber() @IsNumber()
@IsOptional() @IsOptional()
temp_attachment_id?: number; tempAttachmentId?: number;
@IsNumber() @IsNumber()
@IsOptional() @IsOptional()
ai_confidence?: number; aiConfidence?: number;
@IsOptional() @IsOptional()
ai_issues?: Record<string, unknown>[]; aiIssues?: Record<string, unknown>[];
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
migrated_by!: string; // "SYSTEM_IMPORT" migratedBy!: string; // "SYSTEM_IMPORT"
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
batch_id!: string; batchId!: string;
@IsObject() @IsObject()
@IsOptional() @IsOptional()
@@ -48,31 +48,31 @@ export class ImportCorrespondenceDto {
@IsNumber() @IsNumber()
@IsNotEmpty() @IsNotEmpty()
project_id!: number; projectId!: number;
@IsString() @IsString()
@IsOptional() @IsOptional()
issued_date?: string; issuedDate?: string;
@IsString() @IsString()
@IsOptional() @IsOptional()
received_date?: string; receivedDate?: string;
@IsString() @IsString()
@IsOptional() @IsOptional()
document_date?: string; documentDate?: string;
@IsNumber() @IsNumber()
@IsOptional() @IsOptional()
discipline_id?: number; disciplineId?: number;
@IsNumber() @IsNumber()
@IsOptional() @IsOptional()
sender_id?: number; senderId?: number;
@IsNumber() @IsNumber()
@IsOptional() @IsOptional()
receiver_id?: number; receiverId?: number;
@IsString() @IsString()
@IsOptional() @IsOptional()
+36
View File
@@ -726,6 +726,42 @@ AI-powered Document Management System
6 Automation workflow 6 Automation workflow
7 Security 7 Security
``` ```
## 💬 Prompt Templates สำหรับถาม Windsurf
### เมื่อต้องการสร้างฟีเจอร์ใหม่
```
[NEW FEATURE]
Module: <module-name>
Requirement: <อ้างอิง user story จาก 01-02-business-rules/>
Steps:
1. ตรวจสอบ glossary และ edge cases
2. ออกแบบ DTO + Schema ตาม ADR-019
3. สร้าง Service + Controller พร้อม CASL guard
4. เขียน unit test สำหรับ business logic
5. อัพเดท API docs (Swagger)
Output: Code + Test + Spec reference
```
### เมื่อต้องการ debug
```
[DEBUG]
Issue: <อธิบายปัญหา>
File: <path/to/file>
Error: <error message/log>
Steps taken: <สิ่งที่ลองแก้ไขแล้ว>
Request: วิเคราะห์ตาม spec + แนะนำวิธีแก้ที่สอดคล้องกับ ADRs
```
### เมื่อต้องการ review code
```
[CODE REVIEW]
File: <path/to/file>
Focus: <security/performance/uuid/i18n>
Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table
```
--- ---
+3
View File
@@ -0,0 +1,3 @@
{
"npm.packageManager": "pnpm"
}
@@ -8,28 +8,28 @@ import { CorrespondenceType } from '@/types/master-data';
export default function CorrespondenceTypesPage() { export default function CorrespondenceTypesPage() {
const columns: ColumnDef<CorrespondenceType>[] = [ const columns: ColumnDef<CorrespondenceType>[] = [
{ {
accessorKey: 'type_code', accessorKey: 'typeCode',
header: 'Code', header: 'Code',
cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('type_code')}</span>, cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('typeCode')}</span>,
}, },
{ {
accessorKey: 'type_name', accessorKey: 'typeName',
header: 'Name', header: 'Name',
}, },
{ {
accessorKey: 'sort_order', accessorKey: 'sortOrder',
header: 'Sort Order', header: 'Sort Order',
}, },
{ {
accessorKey: 'is_active', accessorKey: 'isActive',
header: 'Status', header: 'Status',
cell: ({ row }) => ( cell: ({ row }) => (
<span <span
className={`px-2 py-1 rounded-full text-xs ${ className={`px-2 py-1 rounded-full text-xs ${
row.getValue('is_active') ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800' row.getValue('isActive') ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
}`} }`}
> >
{row.getValue('is_active') ? 'Active' : 'Inactive'} {row.getValue('isActive') ? 'Active' : 'Inactive'}
</span> </span>
), ),
}, },
@@ -52,10 +52,10 @@ export default function CorrespondenceTypesPage() {
deleteFn={(id) => masterDataService.deleteCorrespondenceType(id)} deleteFn={(id) => masterDataService.deleteCorrespondenceType(id)}
columns={columns} columns={columns}
fields={[ fields={[
{ name: 'type_code', label: 'Code', type: 'text', required: true }, { name: 'typeCode', label: 'Code', type: 'text', required: true },
{ name: 'type_name', label: 'Name', type: 'text', required: true }, { name: 'typeName', label: 'Name', type: 'text', required: true },
{ name: 'sort_order', label: 'Sort Order', type: 'text' }, { name: 'sortOrder', label: 'Sort Order', type: 'text' },
{ name: 'is_active', label: 'Active', type: 'checkbox' }, { name: 'isActive', label: 'Active', type: 'checkbox' },
]} ]}
/> />
</div> </div>
@@ -17,28 +17,28 @@ export default function DisciplinesPage() {
const columns: ColumnDef<Discipline>[] = [ const columns: ColumnDef<Discipline>[] = [
{ {
accessorKey: 'discipline_code', accessorKey: 'disciplineCode',
header: 'Code', header: 'Code',
cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('discipline_code')}</span>, cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('disciplineCode')}</span>,
}, },
{ {
accessorKey: 'code_name_th', accessorKey: 'codeNameTh',
header: 'Name (TH)', header: 'Name (TH)',
}, },
{ {
accessorKey: 'code_name_en', accessorKey: 'codeNameEn',
header: 'Name (EN)', header: 'Name (EN)',
}, },
{ {
accessorKey: 'is_active', accessorKey: 'isActive',
header: 'Status', header: 'Status',
cell: ({ row }) => ( cell: ({ row }) => (
<span <span
className={`px-2 py-1 rounded-full text-xs ${ className={`px-2 py-1 rounded-full text-xs ${
row.getValue('is_active') ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800' row.getValue('isActive') ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
}`} }`}
> >
{row.getValue('is_active') ? 'Active' : 'Inactive'} {row.getValue('isActive') ? 'Active' : 'Inactive'}
</span> </span>
), ),
}, },
@@ -17,16 +17,16 @@ export default function RfaTypesPage() {
const columns: ColumnDef<RfaType>[] = [ const columns: ColumnDef<RfaType>[] = [
{ {
accessorKey: 'type_code', accessorKey: 'typeCode',
header: 'Code', header: 'Code',
cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('type_code')}</span>, cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('typeCode')}</span>,
}, },
{ {
accessorKey: 'type_name_th', accessorKey: 'typeNameTh',
header: 'Name (TH)', header: 'Name (TH)',
}, },
{ {
accessorKey: 'type_name_en', accessorKey: 'typeNameEn',
header: 'Name (EN)', header: 'Name (EN)',
}, },
{ {
@@ -34,15 +34,15 @@ export default function RfaTypesPage() {
header: 'Remark', header: 'Remark',
}, },
{ {
accessorKey: 'is_active', accessorKey: 'isActive',
header: 'Status', header: 'Status',
cell: ({ row }) => ( cell: ({ row }) => (
<span <span
className={`px-2 py-1 rounded-full text-xs ${ className={`px-2 py-1 rounded-full text-xs ${
row.getValue('is_active') ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800' row.getValue('isActive') ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
}`} }`}
> >
{row.getValue('is_active') ? 'Active' : 'Inactive'} {row.getValue('isActive') ? 'Active' : 'Inactive'}
</span> </span>
), ),
}, },
@@ -18,25 +18,25 @@ import { toast } from 'sonner';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
interface MigrationAiIssues { interface MigrationAiIssues {
document_date?: string; documentDate?: string;
issued_date?: string; issuedDate?: string;
received_date?: string; receivedDate?: string;
sender_id?: string | number; senderId?: string | number;
discipline_id?: string | number; disciplineId?: string | number;
source_file_path?: string; sourceFilePath?: string;
key_points?: string[]; keyPoints?: string[];
validation_results?: Array<{ message: string; severity: string }>; validationResults?: Array<{ message: string; severity: string }>;
} }
const reviewFormSchema = z.object({ const reviewFormSchema = z.object({
document_number: z.string().min(1, 'Document number is required'), documentNumber: z.string().min(1, 'Document number is required'),
subject: z.string().min(1, 'Subject is required'), subject: z.string().min(1, 'Subject is required'),
category: z.string().min(1, 'Category is required'), category: z.string().min(1, 'Category is required'),
document_date: z.string().optional(), documentDate: z.string().optional(),
issued_date: z.string().optional(), issuedDate: z.string().optional(),
received_date: z.string().optional(), receivedDate: z.string().optional(),
sender_id: z.string().optional(), senderId: z.string().optional(),
discipline_id: z.string().optional(), disciplineId: z.string().optional(),
}); });
type ReviewFormValues = z.infer<typeof reviewFormSchema>; type ReviewFormValues = z.infer<typeof reviewFormSchema>;
@@ -53,14 +53,14 @@ export default function MigrationReviewPage() {
const form = useForm<ReviewFormValues>({ const form = useForm<ReviewFormValues>({
resolver: zodResolver(reviewFormSchema), resolver: zodResolver(reviewFormSchema),
defaultValues: { defaultValues: {
document_number: '', documentNumber: '',
subject: '', subject: '',
category: '', category: '',
document_date: '', documentDate: '',
issued_date: '', issuedDate: '',
received_date: '', receivedDate: '',
sender_id: '', senderId: '',
discipline_id: '', disciplineId: '',
}, },
}); });
@@ -75,14 +75,14 @@ export default function MigrationReviewPage() {
// Pre-fill form from database item and aiIssues payload // Pre-fill form from database item and aiIssues payload
const issues = (res.aiIssues || {}) as MigrationAiIssues; const issues = (res.aiIssues || {}) as MigrationAiIssues;
form.reset({ form.reset({
document_number: res.documentNumber || '', documentNumber: res.documentNumber || '',
subject: res.title || res.originalTitle || '', subject: res.title || res.originalTitle || '',
category: res.aiSuggestedCategory || '', category: res.aiSuggestedCategory || '',
document_date: issues.document_date || '', documentDate: issues.documentDate || '',
issued_date: issues.issued_date || '', issuedDate: issues.issuedDate || '',
received_date: issues.received_date || '', receivedDate: issues.receivedDate || '',
sender_id: issues.sender_id ? String(issues.sender_id) : '', senderId: issues.senderId ? String(issues.senderId) : '',
discipline_id: issues.discipline_id ? String(issues.discipline_id) : '', disciplineId: issues.disciplineId ? String(issues.disciplineId) : '',
}); });
} }
} catch (_error) { } catch (_error) {
@@ -107,21 +107,21 @@ export default function MigrationReviewPage() {
const issues = item.aiIssues || {}; const issues = item.aiIssues || {};
const payload = { const payload = {
document_number: values.document_number, documentNumber: values.documentNumber,
subject: values.subject, subject: values.subject,
category: values.category, category: values.category,
source_file_path: issues.source_file_path || '', sourceFilePath: issues.sourceFilePath || '',
migrated_by: 'SYSTEM_IMPORT', migratedBy: 'SYSTEM_IMPORT',
batch_id: 'MANUAL_REVIEW_BATCH', batchId: 'MANUAL_REVIEW_BATCH',
project_id: 1, // Assumption or pulled from store projectId: 1, // Assumption or pulled from store
document_date: values.document_date, documentDate: values.documentDate,
issued_date: values.issued_date, issuedDate: values.issuedDate,
received_date: values.received_date, receivedDate: values.receivedDate,
sender_id: values.sender_id ? Number(values.sender_id) : undefined, senderId: values.senderId ? Number(values.senderId) : undefined,
discipline_id: values.discipline_id ? Number(values.discipline_id) : undefined, disciplineId: values.disciplineId ? Number(values.disciplineId) : undefined,
details: { details: {
tags: issues.tags || [], tags: issues.tags || [],
ai_confidence: item.aiConfidence, aiConfidence: item.aiConfidence,
}, },
}; };
@@ -162,8 +162,8 @@ export default function MigrationReviewPage() {
return <div className="py-10 text-center text-red-500">Document not found</div>; return <div className="py-10 text-center text-red-500">Document not found</div>;
} }
const pdfUrl = (item.aiIssues as MigrationAiIssues)?.source_file_path const pdfUrl = (item.aiIssues as MigrationAiIssues)?.sourceFilePath
? migrationService.getStagingFileUrl((item.aiIssues as MigrationAiIssues).source_file_path!) ? migrationService.getStagingFileUrl((item.aiIssues as MigrationAiIssues).sourceFilePath!)
: null; : null;
return ( return (
@@ -221,7 +221,7 @@ export default function MigrationReviewPage() {
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField <FormField
control={form.control} control={form.control}
name="document_number" name="documentNumber"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Document Number</FormLabel> <FormLabel>Document Number</FormLabel>
@@ -272,7 +272,7 @@ export default function MigrationReviewPage() {
/> />
<FormField <FormField
control={form.control} control={form.control}
name="discipline_id" name="disciplineId"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Discipline ID</FormLabel> <FormLabel>Discipline ID</FormLabel>
@@ -288,7 +288,7 @@ export default function MigrationReviewPage() {
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<FormField <FormField
control={form.control} control={form.control}
name="document_date" name="documentDate"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Doc Date</FormLabel> <FormLabel>Doc Date</FormLabel>
@@ -300,7 +300,7 @@ export default function MigrationReviewPage() {
/> />
<FormField <FormField
control={form.control} control={form.control}
name="issued_date" name="issuedDate"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Issued Date</FormLabel> <FormLabel>Issued Date</FormLabel>
@@ -314,7 +314,7 @@ export default function MigrationReviewPage() {
<FormField <FormField
control={form.control} control={form.control}
name="sender_id" name="senderId"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Sender Org ID</FormLabel> <FormLabel>Sender Org ID</FormLabel>
@@ -326,11 +326,11 @@ export default function MigrationReviewPage() {
)} )}
/> />
{(item.aiIssues as MigrationAiIssues)?.key_points && (item.aiIssues as MigrationAiIssues).key_points!.length > 0 && ( {(item.aiIssues as MigrationAiIssues)?.keyPoints && (item.aiIssues as MigrationAiIssues).keyPoints!.length > 0 && (
<div className="mt-6 border-t pt-4"> <div className="mt-6 border-t pt-4">
<h3 className="font-semibold text-sm mb-2 text-muted-foreground">AI Extracted Key Points</h3> <h3 className="font-semibold text-sm mb-2 text-muted-foreground">AI Extracted Key Points</h3>
<ul className="text-sm space-y-1 list-disc pl-4 text-muted-foreground"> <ul className="text-sm space-y-1 list-disc pl-4 text-muted-foreground">
{(item.aiIssues as MigrationAiIssues).key_points!.map((point: string, i: number) => ( {(item.aiIssues as MigrationAiIssues).keyPoints!.map((point: string, i: number) => (
<li key={i}>{point}</li> <li key={i}>{point}</li>
))} ))}
</ul> </ul>
+11 -11
View File
@@ -22,15 +22,15 @@ export interface CirculationRouting {
updatedAt: string; updatedAt: string;
// Joined relations from API // Joined relations from API
assignee?: { assignee?: {
user_id: number; userId: number;
username: string; username: string;
first_name?: string; firstName?: string;
last_name?: string; lastName?: string;
}; };
organization?: { organization?: {
id: number; id: number;
organization_code: string; organizationCode: string;
organization_name: string; organizationName: string;
}; };
} }
@@ -55,20 +55,20 @@ export interface Circulation {
correspondence?: { correspondence?: {
uuid: string; uuid: string;
id?: number; id?: number;
correspondence_number: string; correspondenceNumber: string;
}; };
organization?: { organization?: {
uuid: string; uuid: string;
id?: number; id?: number;
organization_code: string; organizationCode: string;
organization_name: string; organizationName: string;
}; };
creator?: { creator?: {
uuid: string; uuid: string;
user_id?: number; userId?: number;
username: string; username: string;
first_name?: string; firstName?: string;
last_name?: string; lastName?: string;
}; };
} }
+4 -4
View File
@@ -2,13 +2,13 @@
export interface CreateTagDto { export interface CreateTagDto {
/** ID โครงการ (NULL = Global) */ /** ID โครงการ (NULL = Global) */
project_id?: number | null; projectId?: number | null;
/** ชื่อ Tag (เช่น 'URGENT') */ /** ชื่อ Tag (เช่น 'URGENT') */
tag_name: string; tagName: string;
/** รหัสสี หรือชื่อคลาสสำหรับ UI */ /** รหัสสี หรือชื่อคลาสสำหรับ UI */
color_code?: string; colorCode?: string;
/** คำอธิบาย */ /** คำอธิบาย */
description?: string; description?: string;
@@ -18,7 +18,7 @@ export type UpdateTagDto = Partial<CreateTagDto>;
export interface SearchTagDto { export interface SearchTagDto {
/** ID โครงการ (ใช้กรอง Tag ของแต่ละโปรเจกต์) */ /** ID โครงการ (ใช้กรอง Tag ของแต่ละโปรเจกต์) */
project_id?: number; projectId?: number;
/** คำค้นหา (ชื่อ Tag หรือ คำอธิบาย) */ /** คำค้นหา (ชื่อ Tag หรือ คำอธิบาย) */
search?: string; search?: string;
@@ -7,13 +7,13 @@ export interface WorkflowDsl {
/** Allow extra properties for different DSL formats */ /** Allow extra properties for different DSL formats */
[key: string]: unknown; [key: string]: unknown;
states?: Record<string, WorkflowState>; states?: Record<string, WorkflowState>;
initial_state?: string; initialState?: string;
} }
export interface WorkflowState { export interface WorkflowState {
transitions?: WorkflowTransition[]; transitions?: WorkflowTransition[];
on_enter?: string[]; onEnter?: string[];
on_exit?: string[]; onExit?: string[];
} }
export interface WorkflowTransition { export interface WorkflowTransition {
@@ -26,13 +26,13 @@ export interface WorkflowTransition {
// --- Create Definition --- // --- Create Definition ---
export interface CreateWorkflowDefinitionDto { export interface CreateWorkflowDefinitionDto {
/** รหัสของ Workflow (เช่น 'RFA', 'CORRESPONDENCE') */ /** รหัสของ Workflow (เช่น 'RFA', 'CORRESPONDENCE') */
workflow_code: string; workflowCode: string;
/** นิยาม Workflow (DSL JSON Object) */ /** นิยาม Workflow (DSL JSON Object) */
dsl: WorkflowDsl; dsl: WorkflowDsl;
/** เปิดใช้งานทันทีหรือไม่ (Default: true) */ /** เปิดใช้งานทันทีหรือไม่ (Default: true) */
is_active?: boolean; isActive?: boolean;
} }
// --- Update Definition --- // --- Update Definition ---
@@ -41,10 +41,10 @@ export type UpdateWorkflowDefinitionDto = Partial<CreateWorkflowDefinitionDto>;
// --- Evaluate (ประมวลผล/ตรวจสอบ State) --- // --- Evaluate (ประมวลผล/ตรวจสอบ State) ---
export interface EvaluateWorkflowDto { export interface EvaluateWorkflowDto {
/** รหัส Workflow */ /** รหัส Workflow */
workflow_code: string; workflowCode: string;
/** สถานะปัจจุบัน */ /** สถานะปัจจุบัน */
current_state: string; currentState: string;
/** Action ที่ต้องการทำ (เช่น 'SUBMIT', 'APPROVE') */ /** Action ที่ต้องการทำ (เช่น 'SUBMIT', 'APPROVE') */
action: string; action: string;
@@ -56,8 +56,8 @@ export interface EvaluateWorkflowDto {
// --- Get Available Actions --- // --- Get Available Actions ---
export interface GetAvailableActionsDto { export interface GetAvailableActionsDto {
/** รหัส Workflow */ /** รหัส Workflow */
workflow_code: string; workflowCode: string;
/** สถานะปัจจุบัน */ /** สถานะปัจจุบัน */
current_state: string; currentState: string;
} }
+2 -2
View File
@@ -29,8 +29,8 @@ export interface RfaType {
export interface Tag { export interface Tag {
id: number; id: number;
tag_name: string; tagName: string;
color_code?: string; colorCode?: string;
description?: string; description?: string;
} }
+1
View File
@@ -676,6 +676,7 @@
"workbench.preferredDarkColorTheme": "Default Dark Modern", "workbench.preferredDarkColorTheme": "Default Dark Modern",
"scm.alwaysShowActions": false, "scm.alwaysShowActions": false,
"workbench.settings.alwaysShowAdvancedSettings": true, "workbench.settings.alwaysShowAdvancedSettings": true,
"npm.packageManager": "pnpm",
}, },
// ======================================== // ========================================
// LAUNCH CONFIGURATIONS // LAUNCH CONFIGURATIONS
@@ -803,7 +803,7 @@ services:
```bash ```bash
PUT /api/v1/document-numbering/configs/{configId} PUT /api/v1/document-numbering/configs/{configId}
{ {
"template": "{ORIGINATOR}-{RECIPIENT}-{SEQ:4}-{YEAR:B.E.}", "template": "{ORIGINATOR}-{RECIPIENT}-{SEQ:4}/{YEAR:B.E.}",
"change_reason": "เหตุผลในการเปลี่ยนแปลง" "change_reason": "เหตุผลในการเปลี่ยนแปลง"
} }
``` ```
@@ -2178,7 +2178,7 @@ VALUES (
1, 1,
NULL, NULL,
0, 0,
'{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR}', '{ORG}-{RECIPIENT}-{SEQ:4}/{YEAR}',
1, 1,
1, 1,
NULL, NULL,
@@ -2190,7 +2190,7 @@ VALUES (
2, 2,
NULL, NULL,
0, 0,
'{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR}', '{ORG}-{RECIPIENT}-{SEQ:4}/{YEAR}',
1, 1,
1, 1,
NULL, NULL,
@@ -48,14 +48,15 @@
### **2.4 ข้อตกลงในการตั้งชื่อ (Naming Conventions)** ### **2.4 ข้อตกลงในการตั้งชื่อ (Naming Conventions)**
| Entity (สิ่งที่ตั้งชื่อ) | Convention (รูปแบบ) | Example (ตัวอย่าง) | | Entity (สิ่งที่ตั้งชื่อ) | Convention (รูปแบบ) | Example (ตัวอย่าง) | Note |
| :----------------------- | :------------------ | :--------------------------------- | | :----------------------- | :------------------ | :--------------------------------- | :--------------------------------- |
| Classes | PascalCase | UserService | | Classes | PascalCase | UserService | |
| Property | snake_case | user_id | | Property (Code) | camelCase | userId, typeCode, isActive | Backend Entity, DTO, Frontend |
| Variables & Functions | camelCase | getUserInfo | | Database Column | snake_case | user_id, type_code, is_active | MariaDB column names |
| Files & Folders | kebab-case | user-service.ts | | Variables & Functions | camelCase | getUserInfo | |
| Environment Variables | UPPERCASE | DATABASE_URL | | Files & Folders | kebab-case | user-service.ts | |
| Booleans | Verb + Noun | isActive, canDelete, hasPermission | | Environment Variables | UPPERCASE | DATABASE_URL | |
| Booleans | Verb + Noun | isActive, canDelete, hasPermission | |
ใช้คำเต็ม — ไม่ใช้อักษรย่อ — ยกเว้นคำมาตรฐาน (เช่น API, URL, req, res, err, ctx) ใช้คำเต็ม — ไม่ใช้อักษรย่อ — ยกเว้นคำมาตรฐาน (เช่น API, URL, req, res, err, ctx)
@@ -225,13 +225,13 @@ The system resolves the numbering format using the following priority:
1. **Specific Format:** Search for a record matching both `project_id` and `correspondence_type_id`. 1. **Specific Format:** Search for a record matching both `project_id` and `correspondence_type_id`.
2. **Default Format:** If not found, search for a record with matching `project_id` where `correspondence_type_id` is `NULL`. 2. **Default Format:** If not found, search for a record with matching `project_id` where `correspondence_type_id` is `NULL`.
3. **System Fallback:** If neither exists, use the hardcoded system default: `{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR:BE}`. 3. **System Fallback:** If neither exists, use the hardcoded system default: `{ORG}-{RECIPIENT}-{SEQ:4}/{YEAR:BE}`.
| Priority | Scenario | Template Source | Counter Scope (Key) | Reset Behavior | | Priority | Scenario | Template Source | Counter Scope (Key) | Reset Behavior |
| -------- | --------------------- | --------------------------------------------------- | ----------------------------- | ----------------------------------- | | -------- | --------------------- | --------------------------------------------------- | ----------------------------- | ----------------------------------- |
| 1 | Specific Format Found | Database (project_id, type_id) | Specific Type (type_id) | Based on reset_sequence_yearly flag | | 1 | Specific Format Found | Database (project_id, type_id) | Specific Type (type_id) | Based on reset_sequence_yearly flag |
| 2 | Default Format Found | Database (project_id, type_id=NULL) | Shared Counter (type_id=NULL) | Based on reset_sequence_yearly flag | | 2 | Default Format Found | Database (project_id, type_id=NULL) | Shared Counter (type_id=NULL) | Based on reset_sequence_yearly flag |
| 3 | Fallback (No Config) | System Default: {ORG}-{RECIPIENT}-{SEQ:4}-{YEAR:BE} | Shared Counter (type_id=NULL) | Reset Yearly (Default: True) | | 3 | Fallback (No Config) | System Default: {ORG}-{RECIPIENT}-{SEQ:4}/{YEAR:BE} | Shared Counter (type_id=NULL) | Reset Yearly (Default: True) |
### Format Examples by Document Type ### Format Examples by Document Type
@@ -202,7 +202,7 @@ Result: NAP-PAT-LET-67-0001
1. **Specific Format**: project_id + correspondence_type_id 1. **Specific Format**: project_id + correspondence_type_id
2. **Default Format**: project_id + correspondence_type_id = NULL 2. **Default Format**: project_id + correspondence_type_id = NULL
3. **Fallback**: `{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR:BE}` 3. **Fallback**: `{ORG}-{RECIPIENT}-{SEQ:4}/{YEAR:BE}`
--- ---
@@ -175,7 +175,7 @@ src/modules/document-numbering/
- Query document_number_formats by project_id + type_id. - Query document_number_formats by project_id + type_id.
- If no result, query by project_id + NULL (Default Project Format). - If no result, query by project_id + NULL (Default Project Format).
- If still no result, apply System Default Template: `{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR:BE}`. - If still no result, apply System Default Template: `{ORG}-{RECIPIENT}-{SEQ:4}/{YEAR:BE}`.
- Determine resetSequenceYearly flag from the found format (default: true) - Determine resetSequenceYearly flag from the found format (default: true)
#### 2.2.2. Determine Counter Key: #### 2.2.2. Determine Counter Key:
+35 -3
View File
@@ -5,6 +5,7 @@ VALUES (1, 'OWNER'),
(4, 'CONTRACTOR'), (4, 'CONTRACTOR'),
(5, 'THIRD PARTY'), (5, 'THIRD PARTY'),
(6, 'GUEST'); (6, 'GUEST');
INSERT INTO organizations ( INSERT INTO organizations (
id, id,
organization_code, organization_code,
@@ -79,6 +80,7 @@ VALUES (1, 'กทท.', 'การท่าเรือแห่งประเ
), ),
(31, 'EN', 'Third Party Environment', 5), (31, 'EN', 'Third Party Environment', 5),
(32, 'CAR', 'Third Party Fishery Care', 5); (32, 'CAR', 'Third Party Fishery Care', 5);
-- Seed project -- Seed project
INSERT INTO projects (project_code, project_name) INSERT INTO projects (project_code, project_name)
VALUES ( VALUES (
@@ -105,6 +107,7 @@ VALUES (
'LCBP3-EN', 'LCBP3-EN',
'โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3 (ส่วนที่ 4) งานก่อสร้าง' 'โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3 (ส่วนที่ 4) งานก่อสร้าง'
); );
-- Seed contract -- Seed contract
-- ใช้ Subquery เพื่อดึง project_id มาเชื่อมโยง ทำให้ไม่ต้องมานั่งจัดการ ID ด้วยตัวเอง -- ใช้ Subquery เพื่อดึง project_id มาเชื่อมโยง ทำให้ไม่ต้องมานั่งจัดการ ID ด้วยตัวเอง
INSERT INTO contracts ( INSERT INTO contracts (
@@ -183,6 +186,7 @@ VALUES (
), ),
TRUE TRUE
); );
-- Seed user -- Seed user
-- Initial SUPER_ADMIN user -- Initial SUPER_ADMIN user
INSERT INTO users ( INSERT INTO users (
@@ -235,6 +239,7 @@ VALUES (
NULL, NULL,
10 10
); );
-- ========================================================== -- ==========================================================
-- Seed Roles (บทบาทพื้นฐาน 5 บทบาท ตาม Req 4.3) -- Seed Roles (บทบาทพื้นฐาน 5 บทบาท ตาม Req 4.3)
-- ========================================================== -- ==========================================================
@@ -288,6 +293,7 @@ VALUES (
'Contract', 'Contract',
'ผู้ดูแลสัญญา: จัดการสมาชิกในสัญญา, สร้าง / จัดการข้อมูลหลักเฉพาะสัญญา, และอนุมัติเอกสารในสัญญา' 'ผู้ดูแลสัญญา: จัดการสมาชิกในสัญญา, สร้าง / จัดการข้อมูลหลักเฉพาะสัญญา, และอนุมัติเอกสารในสัญญา'
); );
-- ========================================================== -- ==========================================================
-- Seed Role-Permissions Mapping (จับคู่สิทธิ์เริ่มต้น) -- Seed Role-Permissions Mapping (จับคู่สิทธิ์เริ่มต้น)
-- ========================================================== -- ==========================================================
@@ -315,6 +321,7 @@ VALUES (1, 1, 1, NULL, NULL, NULL, NULL),
(3, 3, 4, 41, NULL, NULL, 1), (3, 3, 4, 41, NULL, NULL, 1),
-- editor01: Editor role (role_id=4) at organization 41 (คคง.), assigned by superadmin -- editor01: Editor role (role_id=4) at organization 41 (คคง.), assigned by superadmin
(4, 4, 5, 10, NULL, NULL, 1); (4, 4, 5, 10, NULL, NULL, 1);
-- viewer01: Viewer role (role_id=5) at organization 10 (สคฉ.03), assigned by superadmin -- viewer01: Viewer role (role_id=5) at organization 10 (สคฉ.03), assigned by superadmin
-- ===================================================== -- =====================================================
-- == 4. การเชื่อมโยงโครงการกับองค์กร (project_organizations) == -- == 4. การเชื่อมโยงโครงการกับองค์กร (project_organizations) ==
@@ -340,6 +347,7 @@ WHERE organization_code IN (
'EN', 'EN',
'CAR' 'CAR'
); );
-- โครงการย่อย (LCBP3C1) จะมีเฉพาะองค์กรที่เกี่ยวข้อง -- โครงการย่อย (LCBP3C1) จะมีเฉพาะองค์กรที่เกี่ยวข้อง
INSERT INTO project_organizations (project_id, organization_id) INSERT INTO project_organizations (project_id, organization_id)
SELECT ( SELECT (
@@ -356,6 +364,7 @@ WHERE organization_code IN (
'คคง.', 'คคง.',
'ผรม.1 ' 'ผรม.1 '
); );
-- ทำเช่นเดียวกันสำหรับโครงการอื่นๆ (ตัวอย่าง) -- ทำเช่นเดียวกันสำหรับโครงการอื่นๆ (ตัวอย่าง)
INSERT INTO project_organizations (project_id, organization_id) INSERT INTO project_organizations (project_id, organization_id)
SELECT ( SELECT (
@@ -372,6 +381,7 @@ WHERE organization_code IN (
'คคง.', 'คคง.',
'ผรม.2' 'ผรม.2'
); );
-- ===================================================== -- =====================================================
-- == 5. การเชื่อมโยงสัญญากับองค์กร (contract_organizations) == -- == 5. การเชื่อมโยงสัญญากับองค์กร (contract_organizations) ==
-- ===================================================== -- =====================================================
@@ -403,6 +413,7 @@ VALUES (
), ),
'Designer' 'Designer'
); );
-- สัญญาที่ปรึกษาควบคุมงาน (PSLCBP3) -- สัญญาที่ปรึกษาควบคุมงาน (PSLCBP3)
INSERT INTO contract_organizations (contract_id, organization_id, role_in_contract) INSERT INTO contract_organizations (contract_id, organization_id, role_in_contract)
VALUES ( VALUES (
@@ -431,6 +442,7 @@ VALUES (
), ),
'Consultant' 'Consultant'
); );
-- สัญญางานก่อสร้าง ส่วนที่ 1 (LCBP3-C1) -- สัญญางานก่อสร้าง ส่วนที่ 1 (LCBP3-C1)
INSERT INTO contract_organizations (contract_id, organization_id, role_in_contract) INSERT INTO contract_organizations (contract_id, organization_id, role_in_contract)
VALUES ( VALUES (
@@ -459,6 +471,7 @@ VALUES (
), ),
'Contractor' 'Contractor'
); );
-- สัญญางานก่อสร้าง ส่วนที่ 2 (LCBP3-C2) -- สัญญางานก่อสร้าง ส่วนที่ 2 (LCBP3-C2)
INSERT INTO contract_organizations (contract_id, organization_id, role_in_contract) INSERT INTO contract_organizations (contract_id, organization_id, role_in_contract)
VALUES ( VALUES (
@@ -487,6 +500,7 @@ VALUES (
), ),
'Contractor' 'Contractor'
); );
-- สัญญาตรวจสอบสิ่งแวดล้อม (LCBP3-EN) -- สัญญาตรวจสอบสิ่งแวดล้อม (LCBP3-EN)
INSERT INTO contract_organizations (contract_id, organization_id, role_in_contract) INSERT INTO contract_organizations (contract_id, organization_id, role_in_contract)
VALUES ( VALUES (
@@ -515,6 +529,7 @@ VALUES (
), ),
'Consultant' 'Consultant'
); );
-- Seed correspondence_status -- Seed correspondence_status
INSERT INTO correspondence_status ( INSERT INTO correspondence_status (
status_code, status_code,
@@ -545,6 +560,7 @@ VALUES ('DRAFT', 'Draft', 10, 1),
('CCBDSN', 'Canceled by Designer', 92, 1), ('CCBDSN', 'Canceled by Designer', 92, 1),
('CCBCSC', 'Canceled by CSC', 93, 1), ('CCBCSC', 'Canceled by CSC', 93, 1),
('CCBCON', 'Canceled by Contractor', 94, 1); ('CCBCON', 'Canceled by Contractor', 94, 1);
-- Seed correspondence_types -- Seed correspondence_types
INSERT INTO correspondence_types (type_code, type_name, sort_order, is_active) INSERT INTO correspondence_types (type_code, type_name, sort_order, is_active)
VALUES ('RFA', 'Request for Approval', 1, 1), VALUES ('RFA', 'Request for Approval', 1, 1),
@@ -557,6 +573,7 @@ VALUES ('RFA', 'Request for Approval', 1, 1),
('MOM', 'Minutes of Meeting', 8, 1), ('MOM', 'Minutes of Meeting', 8, 1),
('NOTICE', 'Notice', 9, 1), ('NOTICE', 'Notice', 9, 1),
('OTHER', 'Other', 10, 1); ('OTHER', 'Other', 10, 1);
-- Seed rfa_types -- Seed rfa_types
INSERT INTO rfa_types ( INSERT INTO rfa_types (
contract_id, contract_id,
@@ -1046,6 +1063,7 @@ SELECT id,
'รายงานการฝึกปฏิบัติ' 'รายงานการฝึกปฏิบัติ'
FROM contracts FROM contracts
WHERE contract_code = 'LCBP3-C2'; WHERE contract_code = 'LCBP3-C2';
-- Seed rfa_status_codes -- Seed rfa_status_codes
INSERT INTO rfa_status_codes ( INSERT INTO rfa_status_codes (
status_code, status_code,
@@ -1060,6 +1078,7 @@ VALUES ('DFT', 'Draft', 'ฉบับร่าง', 1),
('ASB', 'AS - Built', 'แบบก่อสร้างจริง', 30), ('ASB', 'AS - Built', 'แบบก่อสร้างจริง', 30),
('OBS', 'Obsolete', 'ไม่ใช้งาน', 80), ('OBS', 'Obsolete', 'ไม่ใช้งาน', 80),
('CC', 'Canceled', 'ยกเลิก', 99); ('CC', 'Canceled', 'ยกเลิก', 99);
INSERT INTO rfa_approve_codes ( INSERT INTO rfa_approve_codes (
approve_code, approve_code,
approve_name, approve_name,
@@ -1074,12 +1093,14 @@ VALUES ('1A', 'Approved by Authority', 10, 1),
('3R', 'Revise and Resubmit', 32, 1), ('3R', 'Revise and Resubmit', 32, 1),
('4X', 'Reject', 40, 1), ('4X', 'Reject', 40, 1),
('5N', 'No Further Action', 50, 1); ('5N', 'No Further Action', 50, 1);
-- Seed circulation_status_codes -- Seed circulation_status_codes
INSERT INTO circulation_status_codes (code, description, sort_order) INSERT INTO circulation_status_codes (code, description, sort_order)
VALUES ('OPEN', 'Open', 1), VALUES ('OPEN', 'Open', 1),
('IN_REVIEW', 'In Review', 2), ('IN_REVIEW', 'In Review', 2),
('COMPLETED', 'ปCompleted', 3), ('COMPLETED', 'ปCompleted', 3),
('CANCELLED', 'Cancelled / Withdrawn', 9); ('CANCELLED', 'Cancelled / Withdrawn', 9);
-- ตาราง "แม่" ของ RFA (มีความสัมพันธ์ 1:N กับ rfa_revisions) -- ตาราง "แม่" ของ RFA (มีความสัมพันธ์ 1:N กับ rfa_revisions)
-- ========================================================== -- ==========================================================
-- SEED DATA 6B.md (Disciplines, RFA Types, Sub Types) -- SEED DATA 6B.md (Disciplines, RFA Types, Sub Types)
@@ -1343,6 +1364,7 @@ SELECT id,
'Other' 'Other'
FROM contracts FROM contracts
WHERE contract_code = 'LCBP3-C1'; WHERE contract_code = 'LCBP3-C1';
-- LCBP3-C2 -- LCBP3-C2
INSERT INTO disciplines ( INSERT INTO disciplines (
contract_id, contract_id,
@@ -1587,6 +1609,7 @@ SELECT id,
'Others' 'Others'
FROM contracts FROM contracts
WHERE contract_code = 'LCBP3-C2'; WHERE contract_code = 'LCBP3-C2';
-- 2. Seed ข้อมูล Correspondence Sub Types (Mapping RFA Types กับ Number) -- 2. Seed ข้อมูล Correspondence Sub Types (Mapping RFA Types กับ Number)
-- เนื่องจาก sub_type_code ตรงกับ RFA Type Code แต่ Req ต้องการ Mapping เป็น Number -- เนื่องจาก sub_type_code ตรงกับ RFA Type Code แต่ Req ต้องการ Mapping เป็น Number
-- LCBP3-C1 -- LCBP3-C1
@@ -1637,6 +1660,7 @@ FROM contracts c,
correspondence_types ct correspondence_types ct
WHERE c.contract_code = 'LCBP3-C1' WHERE c.contract_code = 'LCBP3-C1'
AND ct.type_code = 'RFA'; AND ct.type_code = 'RFA';
-- LCBP3-C2 -- LCBP3-C2
INSERT INTO correspondence_sub_types ( INSERT INTO correspondence_sub_types (
contract_id, contract_id,
@@ -1684,6 +1708,7 @@ FROM contracts c,
correspondence_types ct correspondence_types ct
WHERE c.contract_code = 'LCBP3-C2' WHERE c.contract_code = 'LCBP3-C2'
AND ct.type_code = 'RFA'; AND ct.type_code = 'RFA';
-- LCBP3-C3 -- LCBP3-C3
INSERT INTO correspondence_sub_types ( INSERT INTO correspondence_sub_types (
contract_id, contract_id,
@@ -1731,6 +1756,7 @@ FROM contracts c,
correspondence_types ct correspondence_types ct
WHERE c.contract_code = 'LCBP3-C3' WHERE c.contract_code = 'LCBP3-C3'
AND ct.type_code = 'RFA'; AND ct.type_code = 'RFA';
-- LCBP3-C4 -- LCBP3-C4
INSERT INTO correspondence_sub_types ( INSERT INTO correspondence_sub_types (
contract_id, contract_id,
@@ -1778,6 +1804,7 @@ FROM contracts c,
correspondence_types ct correspondence_types ct
WHERE c.contract_code = 'LCBP3-C4' WHERE c.contract_code = 'LCBP3-C4'
AND ct.type_code = 'RFA'; AND ct.type_code = 'RFA';
INSERT INTO `correspondences` ( INSERT INTO `correspondences` (
`id`, `id`,
`correspondence_number`, `correspondence_number`,
@@ -1814,6 +1841,7 @@ VALUES (
1, 1,
NULL NULL
); );
INSERT INTO `correspondence_revisions` ( INSERT INTO `correspondence_revisions` (
`id`, `id`,
`correspondence_id`, `correspondence_id`,
@@ -1852,6 +1880,7 @@ VALUES (
1, 1,
NULL NULL
); );
INSERT INTO `rfas` ( INSERT INTO `rfas` (
`id`, `id`,
`rfa_type_id`, `rfa_type_id`,
@@ -1860,6 +1889,7 @@ INSERT INTO `rfas` (
`deleted_at` `deleted_at`
) )
VALUES (2, 68, '2025-12-06 05:40:02', 1, NULL); VALUES (2, 68, '2025-12-06 05:40:02', 1, NULL);
INSERT INTO `rfa_revisions` ( INSERT INTO `rfa_revisions` (
`id`, `id`,
`rfa_id`, `rfa_id`,
@@ -1900,6 +1930,7 @@ VALUES (
NULL, NULL,
NULL NULL
); );
-- ========================================================== -- ==========================================================
-- 20. Workflow Definitions (Unified Workflow Engine) -- 20. Workflow Definitions (Unified Workflow Engine)
-- ========================================================== -- ==========================================================
@@ -2136,6 +2167,7 @@ VALUES (
NOW(), NOW(),
NOW() NOW()
); );
INSERT INTO `document_number_formats` ( INSERT INTO `document_number_formats` (
`id`, `id`,
`project_id`, `project_id`,
@@ -2153,7 +2185,7 @@ VALUES (
1, 1,
NULL, NULL,
0, 0,
'{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR}', '{ORG}-{RECIPIENT}-{SEQ:4}/{YEAR}',
1, 1,
1, 1,
NULL, NULL,
@@ -2165,10 +2197,10 @@ VALUES (
2, 2,
NULL, NULL,
0, 0,
'{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR}', '{ORG}-{RECIPIENT}-{SEQ:4}/{YEAR}',
1, 1,
1, 1,
NULL, NULL,
'2025-12-16 09:34:10', '2025-12-16 09:34:10',
'2025-12-16 09:34:10' '2025-12-16 09:34:10'
); );