From e3c476f011c64f1c240eab90b79c8ed849d8329d Mon Sep 17 00:00:00 2001 From: admin Date: Mon, 23 Mar 2026 10:50:20 +0700 Subject: [PATCH] 260323:1050 fix CI : Verify Build frontend #02 correct _??? --- .gemini/GEMINI.md | 916 ++++++++++++++++-- .windsurfrules | 914 +++++++++++++++-- AGENTS.md | 915 +++++++++++++++-- docs/Pompt.md | 783 +++++++++++++++ .../admin/access-control/users/page.tsx | 4 +- .../admin/doc-control/numbering/new/page.tsx | 13 +- .../admin/doc-control/numbering/page.tsx | 4 +- .../reference/correspondence-types/page.tsx | 25 +- .../reference/disciplines/page.tsx | 35 +- .../reference/drawing-categories/page.tsx | 33 +- .../doc-control/reference/rfa-types/page.tsx | 41 +- .../admin/doc-control/reference/tags/page.tsx | 17 +- .../monitoring/system-logs/numbering/page.tsx | 6 +- .../app/(dashboard)/circulation/new/page.tsx | 2 +- .../admin/reference/generic-crud-table.tsx | 1 - .../circulation/circulation-list.tsx | 3 +- .../components/dashboard/pending-tasks.tsx | 2 +- .../documents/common/server-data-table.tsx | 1 - .../components/drawings/revision-history.tsx | 2 +- frontend/components/drawings/upload-form.tsx | 65 +- .../components/numbering/audit-logs-table.tsx | 5 +- .../numbering/metrics-dashboard.tsx | 2 +- .../components/numbering/template-editor.tsx | 20 +- .../components/numbering/template-tester.tsx | 8 +- frontend/components/ui/dropdown-menu.tsx | 2 +- frontend/hooks/use-numbering.ts | 4 +- frontend/lib/services/master-data.service.ts | 42 +- frontend/proxy.ts | 2 +- frontend/types/dto/numbering.dto.ts | 13 +- frontend/types/master-data.ts | 65 ++ CLAUDE.md => specs/99-archives/.windsurfrules | 16 +- 31 files changed, 3587 insertions(+), 374 deletions(-) create mode 100644 docs/Pompt.md create mode 100644 frontend/types/master-data.ts rename CLAUDE.md => specs/99-archives/.windsurfrules (96%) diff --git a/.gemini/GEMINI.md b/.gemini/GEMINI.md index 3ad77e2..4e30ef5 100644 --- a/.gemini/GEMINI.md +++ b/.gemini/GEMINI.md @@ -1,86 +1,181 @@ --- trigger: always_on --- - # NAP-DMS Project Context & Rules + - For: Gemeni CLI and Gemini. + - Version: 1.8.3 (Enforcement Tiers Added) | Last synced from repo: 2026-03-21 + - Repo: [https://git.np-dms.work/np-dms/lcbp3](https://git.np-dms.work/np-dms/lcbp3) -> **For:** Gemeni CLI and Gemini. +--- ## 🧠 Role & Persona -Act as a **Senior Full Stack Developer** expert in **NestJS**, **Next.js**, and **TypeScript**. +Act as a **Senior Full Stack Developer** specialized in: + +- NestJS, Next.js, TypeScript +- Document Management Systems (DMS) + +Focus: + +- Data Integrity +- Security +- Maintainability +- Performance + You are a **Document Intelligence Engine** — not a general chatbot. You value **Data Integrity**, **Security**, and **Clean Architecture**. +Every response must be **precise**, **spec-compliant**, and **production-ready**. + +--- + +## 🧭 Rule Enforcement Tiers + +ทุก rule ในไฟล์นี้จัดระดับตามนี้ — ใช้ตัดสินใจว่าอะไรต้อง block ทันที อะไรรอ review ได้: + +### 🔴 Tier 1 — CRITICAL (CI BLOCKER) + +บังคับอัตโนมัติ ผ่าน CI/CD + runtime — **ผิดแล้ว build fail ทันที:** + +- Security (Auth, RBAC, Validation) +- UUID Strategy (ADR-019) — ห้าม `parseInt` / `Number` / `+` บน UUID +- Database correctness — ตรวจ schema ก่อนเขียน query เสมอ +- File upload security (ClamAV + whitelist) +- AI validation boundary (ADR-018) +- Forbidden patterns: `any`, `console.log`, UUID misuse + +### 🟡 Tier 2 — IMPORTANT (CODE REVIEW) + +ตรวจใน PR review — ไม่ block build แต่ต้องแก้ก่อน merge: + +- Architecture patterns (thin controller, business logic ใน service) +- Test coverage (80%+ business logic, 70%+ backend overall) +- Cache invalidation +- Naming conventions + +### 🟢 Tier 3 — GUIDELINES + +Best practice — ทำตามถ้าทำได้ ไม่ block: + +- Code style / formatting (Prettier จัดให้) +- Comment completeness +- Minor optimizations + +--- ## 🏗️ Project Overview -**LCBP3-DMS (Laem Chabang Port Phase 3 - Document Management System)** — Version 1.8.1 (Patch) +**LCBP3-DMS (Laem Chabang Port Phase 3 - Document Management System)** +ระบบบริหารจัดการเอกสารโครงการก่อสร้างท่าเรือแหลมฉบังระยะที่ 3 +**Version:** 1.8.3 (Enforcement Tiers Added) | **Status:** UAT In Progress, Security Hardened (2026-03-19) +| Area | Status | Notes | +| ------------- | ---------------------- | ------------------------------------------------------ | +| 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` | +| Database | ✅ Schema v1.8.0 Stable | MariaDB 11.8, No-migration (ADR-009) | +| Documentation | ✅ 10/10 Gaps Closed | Product Vision → Release Policy | +| AI Migration | 🔄 Pre-migration Setup | n8n + Ollama (ADR-017/018) | +| Testing | 🔄 UAT In Progress | Per `01-05-acceptance-criteria.md` | +| 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 | +**Domain:** `np-dms.work` -### 📊 Project Status: UAT Ready, Security Hardened (2026-03-19) +--- -| Area | Status | Notes | -| ------------- | ------------------------ | ---------------------------------------- | -| Backend | ✅ Production Ready | NestJS 11, Express v5, 0 Vulnerabilities | -| Frontend | ✅ Quality Hardened | Next.js 16.2.0, 0 `any`, 0 console.log | -| Database | ✅ Schema v1.8.0 Stable | MariaDB 11.8, No-migration (ADR-009) | -| Documentation | ✅ **10/10 Gaps Closed** | Product Vision → Release Policy | -| AI Migration | 🔄 Pre-migration Setup | n8n + Ollama (ADR-017/018) | -| Testing | 🔄 UAT In Progress | Per `01-05-acceptance-criteria.md` | -| Deployment | 📋 Pending Go-Live | Blue-Green, QNAP Container Station | +## 💻 Tech Stack (Exact Versions from repo) -- **Goal:** Manage construction documents (Correspondence, RFA, Circulation, Transmittal, Contract Drawings, Shop Drawings) - with complex multi-level approval workflows. -- **Infrastructure:** - - **QNAP NAS:** Container Station — DMS Frontend/Backend, MariaDB, Redis, Elasticsearch, Nginx Proxy Manager, n8n + n8n-db, Tika, Gitea, RocketChat, cAdvisor, exporters - - **ASUSTOR NAS:** Portainer — Monitoring Hub (Grafana, Prometheus, Loki, Promtail, uptime-kuma), Gitea Runner (act_runner), Docker Registry, cAdvisor, Cloudflared - - **Admin Desktop:** Ollama (AI Processing) — i9-9900K, 32GB RAM, RTX 2060 SUPER 8GB - - **Shared Network:** Internal VLAN — QNAP scrapes by ASUSTOR Prometheus +### Backend -## 💻 Tech Stack & Constraints +- **NestJS 11** (Express v5, Modular Architecture, 18 modules) +- **TypeORM** + **MariaDB 11.8** +- **Redis 7.2** — BullMQ (Queues) + `cache-manager-redis-store@3.0.1` (Caching) +- **Elasticsearch 9.3.4** — Full-text search +- **JWT + Passport** — Authentication +- **CASL** — 4-Level RBAC Authorization (Global / Organization / Project / Contract) +- **ClamAV** — Virus Scanning on every file upload +- **Helmet.js** — HTTP Security Headers +- **Nodemailer 8.0.3** — Email delivery +- **Swagger** — API documentation at `/api` -- **Backend:** NestJS 11 (Express v5, Modular Architecture), TypeORM, MariaDB 11.8, Redis 7.2 (BullMQ), - Elasticsearch 9.3.4, JWT + Passport, CASL (4-Level RBAC), ClamAV (Virus Scanning), Helmet.js, - cache-manager-redis-store@3.0.1 (Redis caching) -- **Frontend:** Next.js 16.2.0 (App Router, proxy.ts), Tailwind CSS 3.4.3, Shadcn/UI, - TanStack Query (**Server State**), Zustand (**Client State**), React Hook Form 7.71.2 + Zod 4.3.6 + @hookform/resolvers 3.9.0 (**Form State**), Axios -- **Testing:** Vitest 4.1.0, ESLint 9.39.1 -- **Notifications:** BullMQ Queue → Email / LINE Notify / In-App -- **AI/Migration:** Ollama (llama3.2:3b / mistral:7b) on Admin Desktop (RTX 2060 SUPER) + n8n on QNAP -- **Language:** TypeScript (Strict Mode). **NO `any` types allowed.** -- **Security**: 0 vulnerabilities (as of 2026-03-19) +### Frontend -## 🛡️ Security & Integrity Rules +- **Next.js 16.2.0** (App Router + `proxy.ts`) + **React 19.2.4** +- **Tailwind CSS 4.2.2** + **Shadcn/UI** +- **TanStack Query** — **Server State only** +- **Zustand** — **Client State only** +- **React Hook Form 7.71.2** + **Zod 4.3.6** + **@hookform/resolvers 3.9.0** — **Form State only** +- **Axios** — HTTP client -1. **Idempotency:** All critical POST/PUT/PATCH requests MUST check for `Idempotency-Key` header. -2. **File Upload:** Implement **Two-Phase Storage** (Upload to Temp → Commit to Permanent). -3. **Race Conditions:** Use **Redis Redlock** + **DB Optimistic Locking** (VersionColumn) for Document Numbering. -4. **Validation:** Use Zod (frontend) or Class-validator (backend DTO) for all inputs. -5. **Password:** bcrypt with 12 salt rounds. Enforce password policy. -6. **Rate Limiting:** Apply ThrottlerGuard on auth endpoints. -7. **AI Isolation (ADR-018):** Ollama MUST run on Admin Desktop only (NOT on QNAP/production server). AI has NO direct DB access, NO write access to uploads. Output JSON only. +### Tooling & Testing -## 📋 Spec Guidelines +- **pnpm@10.32.1** — Package manager (monorepo workspace) +- **Vitest 4.1.0** — Unit & Integration tests +- **Playwright** — E2E tests +- **ESLint 9.39.1** — Linting +- **Prettier** — Formatting -- Always follow specs in `specs/` (v1.8.1). Priority: `06-Decision-Records` > `05-Engineering-Guidelines` > others. -- Always verify database schema against **`specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql`** before writing queries. (Schema split: `01-drop`, `02-tables`, `03-views-indexes`) -- Check data dictionary at **`specs/03-Data-and-Storage/03-01-data-dictionary.md`** for field meanings and business rules. +### Notifications -### 📁 Key Spec Documents (Quick Reference) +- BullMQ Queue → Email (Nodemailer 8.0.3) / LINE Notify / In-App -| เอกสาร | Path | ใช้เมื่อ | -| -------------------- | ----------------------------------------------------------- | ----------------------------------- | -| **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 | -| **Seed Permissions** | `03-Data-and-Storage/lcbp3-v1.8.0-seed-permissions.sql` | ตรวจ CASL Permission Matrix | -| **Edge Cases** | `01-Requirements/01-06-edge-cases-and-rules.md` | 37 Rules ป้องกัน Bug | -| **Migration Scope** | `03-Data-and-Storage/03-06-migration-business-scope.md` | งาน Migration Bot | -| **Release Policy** | `04-Infrastructure-OPS/04-08-release-management-policy.md` | ก่อน Deploy / Hotfix | -| **UAT Criteria** | `01-Requirements/01-05-acceptance-criteria.md` | ตรวจความสมบูรณ์ Feature | -| **ADR-009** | `06-Decision-Records/ADR-009-db-strategy.md` | Schema Change Process | -| **ADR-018** | `06-Decision-Records/ADR-018-ai-boundary.md` | AI/Ollama Integration Rules | -| **ADR-019** | `06-Decision-Records/ADR-019-hybrid-identifier-strategy.md` | Hybrid ID Strategy (INT + UUIDv7) | +### AI / Migration -### ADR Reference (All 17 + Patch + ADR-019) +- **Ollama** (`llama3.2:3b` / `mistral:7b`) — Admin Desktop ONLY (i9-9900K, RTX 2060 SUPER 8GB, 32GB RAM) +- **n8n** — Automation & Migration Orchestration (QNAP) +- **Tika** — Document parsing (QNAP) + +### 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 +- All 52 vulnerabilities resolved as of 2026-03-19 (27 high + 20 moderate + 5 low) + +--- + +## 🗂️ Key Spec Files (Always Check Before Writing Code) + +Spec priority: **`06-Decision-Records`** > **`05-Engineering-Guidelines`** > others +| เอกสาร | Path (relative to `specs/`) | ใช้เมื่อ | +| ------------------------- | -------------------------------------------------------------------- | ----------------------------------- | +| **Glossary** | `00-Overview/00-02-glossary.md` | ตรวจคำศัพท์ Domain ก่อนเขียนเสมอ | +| **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 | +| **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 | +| **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 | +| **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 1–6) | +| **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 | +| **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-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) | + +### Specs Directory Structure (Brief) + +``` +specs/ +├── 00-Overview/ # Product Vision, Glossary, KPI Baseline, Training Plan, Stakeholder +├── 01-Requirements/ # User Stories (27), UAT Criteria, UI Wireframes (26), Edge Cases (37) +│ └── 01-02-business-rules/ # กฎธุรกิจที่ห้ามละเมิด +│ └── 01-03-modules/ # Spec ของแต่ละ Feature module +├── 02-Architecture/ # System Context, Software Architecture, Network, API Design +├── 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 +├── 05-Engineering-Guidelines/ # Fullstack, Backend, Frontend, Testing, UUID Implementation +├── 06-Decision-Records/ # 19 ADRs (ADR-001 to ADR-019) +└── 99-archives/ # ประวัติ tasks เก่า +``` + +Schema is split — modify the correct file: + +- `lcbp3-v1.8.0-schema-01-drop.sql` +- `lcbp3-v1.8.0-schema-02-tables.sql` ← **primary reference for all queries** +- `lcbp3-v1.8.0-schema-03-views-indexes.sql` + +--- + +## 📐 ADR Reference (19 total) | ADR | Topic | Key Decision | | ------- | -------------------------- | -------------------------------------------------- | @@ -97,21 +192,700 @@ You value **Data Integrity**, **Security**, and **Clean Architecture**. | ADR-014 | State Management | TanStack Query (server) + Zustand (client) | | ADR-015 | Deployment | Docker Compose + Gitea CI/CD | | ADR-016 | Security | JWT + CASL RBAC + Helmet.js + ClamAV | -| ADR-017 | Ollama Migration | Local AI + n8n for legacy data import | +| 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-019 | Hybrid Identifier Strategy | INT PK (internal) + UUIDv7 BINARY(16) (public API) | +--- + +## 🆔 Identifier Strategy (ADR-019) — CRITICAL + +### Rule Summary + +- **Internal / DB FK:** `INT AUTO_INCREMENT` (Primary Key) — never exposed +- **Public API / URL:** `UUIDv7` stored as `BINARY(16)` +- Read `05-07-hybrid-uuid-implementation-plan.md` before any UUID-related work. + +### ⚠️ Phase 5.4 — Pending Fix (as of 2026-03-20) + +These files still call `parseInt()` on UUID values — **fix when touching these files**: + +- `frontend/components/correspondences/form.tsx` +- `frontend/components/user-dialog.tsx` +- `frontend/components/numbering/template-tester.tsx` +- `frontend/app/(dashboard)/rfas/page.tsx` + +### UUID Serialization Behavior (TransformInterceptor) + +`TransformInterceptor` uses `instanceToPlain()` — `@Exclude()` and `@Expose()` decorators are active on all responses. +| Entity Type | Behavior | +| ------------------ | ---------------------------------------------------------------------- | +| All entities | INT `id` has `@Exclude()` → **never appears in API response** | +| Project / Contract | `uuid` has `@Expose({ name: 'id' })` → response has `id` = UUID string | +| Other entities | Separate `uuid` field → response has `uuid`, no `id` | + +### UUID Patterns (Backend Controller) + +```typescript +// ✅ ถูกต้อง — UUID param +@Get(':uuid') +findOne(@Param('uuid', ParseUuidPipe) uuid: string) { ... } +// ❌ ผิด — INT param บน public route +@Get(':id') +findOne(@Param('id', ParseIntPipe) id: number) { ... } +``` + +### UUID Patterns (Backend DTO — FK References) + +```typescript +// ✅ ถูกต้อง — รับ UUID จาก frontend, resolve เป็น INT ใน controller +@IsUUID() +projectUuid!: string; +@IsOptional() +@IsInt() +projectId?: number; // resolved internally, never from client +// ❌ ผิด — frontend ไม่มี INT id (ถูก @Exclude() แล้ว) +@IsInt() +projectId!: number; +``` + +### UUID Patterns (Frontend — Select/Form) + +```typescript +// ✅ ถูกต้อง +onValueChange={(v) => setValue("projectUuid", v)} +// ❌ ผิด — parseInt บน UUID string ได้ค่าผิดเสมอ +onValueChange={(v) => setValue("projectId", parseInt(v))} +``` + +--- + +## 🛡️ Security Rules (Non-Negotiable) + +1. **Idempotency:** All critical `POST` / `PUT` / `PATCH` MUST validate `Idempotency-Key` header. +2. **Two-Phase File Upload:** Upload → Temp Storage → Commit → Permanent Storage. +3. **Race Conditions:** Redis Redlock + TypeORM `@VersionColumn` for Document Numbering. +4. **Validation:** Zod (frontend) + class-validator (backend DTO) — no unvalidated inputs. +5. **Password:** bcrypt 12 salt rounds. Min 8 chars, upper + lower + number + special. Rotate every 90 days. +6. **Rate Limiting:** `ThrottlerGuard` on all auth endpoints. +7. **File Upload Policy:** Whitelist: PDF, DWG, DOCX, XLSX, ZIP. Max: 50MB. ClamAV scans every file. +8. **AI Isolation (ADR-018):** + +- Ollama runs on Admin Desktop ONLY — NEVER on QNAP/production. +- AI: NO direct DB access, NO write to `/uploads`. +- AI input/output: **JSON only**. +- All AI-triggered writes → DMS REST API → DB. + +--- + +## 📐 TypeScript Rules + +- **Strict Mode** — all strict checks enforced. +- **ZERO `any` types** — use proper types, generics, or `unknown` + type narrowing. +- **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. +- Prefer `readonly` for immutable properties. +- Use `satisfies` operator for type-checked object literals. +- Use `RequestWithUser` typed interface in controllers — NEVER `req: any`. + +--- + +## 📝 Naming Conventions + +| Target | Convention | Example | +| ------------------- | ----------- | --------------------------- | +| Files | kebab-case | `user-service.ts` | +| Classes | PascalCase | `UserService` | +| Variables | camelCase | `firstName` | +| DB Properties | snake_case | `user_id`, `created_at` | +| Boolean vars | verb + noun | `isActive`, `hasPermission` | +| **Code** | English | All identifiers in English | +| **Comments / Docs** | Thai | ความคิดเห็นและเอกสารใช้ภาษาไทย | + +--- + +## 🏷️ Domain Terminology (Glossary) + +อ้างอิง `specs/00-Overview/00-02-glossary.md` เสมอ — ใช้ term ผิดจะทำให้ spec ไม่ตรง +| ✅ ใช้ (Correct) | ❌ ห้ามใช้ (Wrong) | +| ------------------ | ------------------------------------------- | +| Correspondence | Letter, Communication, Document (generic) | +| RFA | Approval Request, Submit for Approval | +| Transmittal | Delivery Note, Cover Letter | +| Circulation | Distribution, Routing | +| Shop Drawing | Construction Drawing (generic) | +| Contract Drawing | Design Drawing, Blueprint | +| Workflow Engine | Approval Flow, Process Engine | +| Document Numbering | Document ID, Auto Number | +| RBAC | Permission System, Access Control (generic) | + +--- + +## 🏛️ Architecture Rules + +### Backend (NestJS) — 18 Modules + +``` +auth | user | project | correspondence | rfa | drawing | workflow-engine | +document-numbering | transmittal | circulation | search | dashboard | +notification | monitoring | master | organizations | json-schema | config +``` + +- **Modular Architecture** — one module per domain. +- Business logic in **Services only** — Controllers are thin (validate → delegate). +- **NO SQL Triggers** — all logic in NestJS services. +- Every protected route: `@UseGuards(JwtAuthGuard, CaslGuard)` + `@Roles()`. +- `@Roles()` must align with CASL matrix in `seed-permissions.sql`. +- All file operations through `StorageService` — never directly. +- Notifications via BullMQ — NEVER send inline. +- NEVER use `req: any` — use `RequestWithUser` interface. + +### Frontend (Next.js) — 15 Component Groups + +``` +ui/ | layout/ | common/ | correspondences/ | rfas/ | drawings/ | +workflows/ | numbering/ | dashboard/ | search/ | transmittals/ | +circulations/ | admin/ | auth/ | notifications/ +``` + +- **App Router** — Server Components by default; Client Components only when necessary. +- All API calls through `proxy.ts` — NEVER call backend directly from client. +- **TanStack Query** — server state (fetching, caching, invalidation). +- **Zustand** — UI/client state (modals, sidebar, selections). +- **React Hook Form + Zod** — all forms; no uncontrolled inputs. +- No `console.log`, no `any`, no `parseInt()` on UUIDs — remove before commit. + +--- + +## 🗄️ Database Rules (ADR-009) + +- **NO TypeORM migrations** — schema changes go directly into the SQL schema files. +- **NEVER invent table names or columns** — only use what is in `schema-02-tables.sql`. +- Always verify against schema before writing any query or TypeORM entity. +- Check `03-01-data-dictionary.md` for field meanings and business rules. + +--- + +## 🤖 AI / n8n Rules (ADR-017 & ADR-018) + +- Ollama models: `llama3.2:3b` (fast tasks) / `mistral:7b` (complex extraction). +- n8n orchestrates all migration workflows on QNAP (`n8n-workflow-lcbp3.json`). +- AI output must be validated JSON — schema-validate before any DB write via API. +- Migration Bot token: IP Whitelist + 7-day Expiry + REVOKE immediately after migration. +- **DO NOT** start Legacy Migration without Go/No-Go Gate #1 approval. +- Migration scope: ~20,000 documents in 3 Tiers — Tier 1 first (2,000 critical docs). + +--- + +## 🧪 Testing Standards + +### Coverage Goals + +| Layer | Target | Priority Areas | +| ---------------- | ------ | --------------------------------------------- | +| Backend overall | 70%+ | — | +| Business Logic | 80%+ | Services, Workflow Engine, Document Numbering | +| Controllers | 70%+ | Happy path + error cases | +| Utilities | 90%+ | Helpers, Transformers, Guards | +| Frontend overall | 60%+ | Components, Hooks, API clients | + +### Health Check Endpoints + +``` +GET /health — Overall system health +GET /health/db — MariaDB connectivity +GET /health/redis — Redis connectivity +``` + +### Test Commands + +```bash +# Backend +pnpm --filter backend test # Unit tests +pnpm --filter backend test:e2e # E2E tests +pnpm --filter backend test:cov # Coverage report +# Frontend +pnpm --filter frontend test # Unit tests (Vitest) +pnpm --filter frontend test:e2e # E2E tests (Playwright) +``` + +### Feature Testing Checklist (ก่อน PR) + +#### Backend + +- [ ] Unit test: Service business logic (min 80% coverage) +- [ ] Integration test: Controller + Guard + DTO validation +- [ ] E2E test: Happy path + 2 edge cases จาก `01-06-edge-cases.md` +- [ ] Security test: Unauthorized access, SQL injection, XSS + +#### Frontend + +- [ ] Component test: Render + interaction (Vitest + Testing Library) +- [ ] Form test: Validation success/failure cases +- [ ] E2E test: Critical user journey (Playwright) +- [ ] Accessibility: Keyboard nav + screen reader basics + +#### Before Commit + +- [ ] `pnpm lint` → 0 errors +- [ ] `pnpm test:cov` → ผ่านเกณฑ์ +- [ ] `pnpm build` → 0 warnings +- [ ] UUID pattern ตรวจสอบแล้ว (ไม่มี parseInt บน UUID) + +--- + +## 🌿 Git Conventions + +### Commit Message Format + +``` +(): +[optional body] +[optional footer: Refs #issue] +``` + +| Type | ใช้เมื่อ | +| ---------- | ------------------------------------- | +| `feat` | เพิ่มฟีเจอร์ใหม่ | +| `fix` | แก้ bug | +| `refactor` | ปรับโครงสร้างโค้ด ไม่เปลี่ยน behavior | +| `docs` | แก้ไขเอกสาร | +| `test` | เพิ่ม/แก้ test | +| `chore` | งาน infra, config, dependency updates | +| `style` | Formatting, linting (ไม่เปลี่ยน logic) | +| `spec` | แก้ไข specs/ documents | +| `adr` | เพิ่ม/แก้ไข Architecture Decision Record | + +**ตัวอย่าง:** + +``` +feat(correspondence): add create correspondence endpoint +fix(uuid): remove parseInt on projectId in rfas/page.tsx +spec(requirements): update edge cases for drawing workflow +adr(019): add UUID serialization behavior notes +``` + +### Branch Naming + +``` +feature/ # ฟีเจอร์ใหม่ +fix/- # แก้ bug +spec// # แก้ specs +adr/- # ADR ใหม่/แก้ไข +refactor/ # Refactor +``` + +**ตัวอย่าง:** + +``` +feature/correspondence-cc-support +fix/23-uuid-parseInt-rfas-page +spec/requirements/update-correspondence-workflow +adr/019-uuid-serialization-behavior +``` + +--- + +## 🌊 Windsurf Workflows + +`.windsurf/workflows/` — ใช้สำหรับ repeatable / complex tasks เช่น: + +- UUID migration fixes (Phase 5.4) +- Spec review & gap analysis +- Security audit checklist +- Release gate verification + เมื่อ task ซับซ้อนและทำซ้ำได้ ให้ดู `.windsurf/workflows/` ก่อนเขียน code ใหม่ + ---## 🚀 Deployment Rules (ADR-015) +- Docker Compose on **QNAP Container Station** (production). +- **NO `.env` files in production** — secrets in `docker-compose.yml` environment section directly. +- Blue-Green deployment strategy. +- CI/CD via **Gitea** (QNAP) + **Gitea Runner / act_runner** (ASUSTOR). +- **DO NOT deploy** without completing all Release Gates per `04-08-release-management-policy.md`. + +--- + ## 🚫 Forbidden Actions -- DO NOT use SQL Triggers (Business logic must be in NestJS services). -- DO NOT use `.env` files for production deployment — QNAP Container Station requires secrets directly in `docker-compose.yml` environment section. -- DO NOT run database migrations — modify the schema SQL file directly (ADR-009). -- DO NOT invent table names or columns — use ONLY what is defined in the schema SQL file. -- DO NOT generate code that violates OWASP Top 10 security practices. -- DO NOT use `any` TypeScript type anywhere. -- DO NOT let AI (Ollama) access production database directly — all writes go through DMS API (ADR-018). -- DO NOT bypass StorageService for file operations — all file moves must go through the API. -- DO NOT deploy to Production without completing Release Gates — see `04-08-release-management-policy.md`. -- DO NOT start Legacy Migration without Go/No-Go Gate #1 approval — see `03-06-migration-business-scope.md`. -- DO NOT modify Migration Bot Token scope — IP Whitelist + 7-day Expiry + REVOKE after migration. -- DO NOT close UAT sign-off without all Acceptance Criteria ✅ — see `01-05-acceptance-criteria.md`. +| ❌ Forbidden | ✅ Correct Approach | +| ----------------------------------------------- | --------------------------------------------------------- | +| SQL Triggers for business logic | NestJS Service methods | +| `.env` files in production | `docker-compose.yml` environment section | +| TypeORM migration files | Edit schema SQL directly (ADR-009) | +| Inventing table/column names | Verify against `schema-02-tables.sql` | +| `any` TypeScript type | Proper types / generics / `unknown` + narrowing | +| `console.log` in committed code | NestJS Logger (backend) / remove (frontend) | +| `req: any` in controllers | `RequestWithUser` typed interface | +| `parseInt()` on UUID values | Use UUID string directly (ADR-019) | +| Exposing INT PK in API responses or URLs | UUIDv7 (ADR-019) | +| AI accessing DB or storage directly | AI → DMS API → DB (ADR-018) | +| Direct file operations bypassing StorageService | `StorageService` for all file moves | +| Inline email/notification sending | BullMQ queue job | +| 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 | +| 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` | +| Modifying Migration Bot token scope | IP Whitelist + 7-day expiry only | +| OWASP Top 10 violations | Security checklist before every PR | + +--- + +## 🔄 Development Flow (Tiered) + +เลือก flow ตามประเภทงาน — ไม่ต้องอ่าน spec ทั้งหมดทุกครั้ง: + +### 🔴 Critical Work — DB / API / Security / Workflow Engine + +**MUST ทำครบทุก step:** + +1. **Glossary check** — ตรวจคำศัพท์ domain ใน `00-02-glossary.md` +2. **Read the spec** — เลือกจาก Key Spec Files table +3. **Check schema** — verify table/column ใน `schema-02-tables.sql` +4. **Check data dictionary** — ยืนยัน field meanings + business rules +5. **Scan edge cases** — `01-06-edge-cases-and-rules.md` (37 rules) +6. **Check ADRs** — ตรวจแนวทางตรงกับ decisions (esp. ADR-009, ADR-018, ADR-019) +7. **Write code** — TypeScript strict, no `any`, no `console.log`, UUID ไม่ expose + +### 🟡 Normal Work — UI / Feature / Integration + +- ดู existing patterns ในโค้ดที่มีอยู่ +- ตรวจ spec เฉพาะ module ที่เกี่ยวข้อง +- ไม่ต้องอ่าน spec ทั้งหมด + +### 🟢 Quick Fix — Bug Fix / Typo / Style + +- แก้โดยตรง +- เพิ่ม minimal test ถ้าแก้ logic +- ตรวจ forbidden patterns ก่อน commit + +--- + +## 🎯 Windsurf Context-Aware Triggers + +เมื่อผู้ใช้ถามเกี่ยวกับ... ให้ตรวจสอบไฟล์เหล่านี้ก่อนตอบ +| คำถาม/คำสั่ง | ไฟล์ที่ต้องตรวจสอบก่อน | คำตอบที่คาดหวัง | +| -------------------- | ------------------------------------------------------- | ------------------------------------------------- | +| "สร้าง 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 | +| "เพิ่ม 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 | +| "สร้าง migration" | `ADR-009`, `03-06-migration-business-scope.md` | แก้ SQL schema โดยตรง + n8n workflow | +| "ตรวจสอบ 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 | +| "เพิ่ม test" | `05-04-testing-strategy.md` | Coverage goals + test patterns | + +--- + +## 🧩 Code Snippets (Windsurf Auto-Suggest) + +### Backend DTO Pattern + +```typescript +// [dto-new] → สร้าง DTO ใหม่พร้อม validator +@IsUUID() +@ApiProperty({ description: 'Project UUID (public)' }) +projectUuid!: string; + + +@IsOptional() +@IsInt() +@ApiProperty({ required: false, description: 'Internal project ID' }) +projectId?: number; // resolved internally, never from client +``` + +### Frontend Form Pattern + +```typescript +// [form-rhf-zod] → สร้าง form schema + hook +const schema = z.object({ + projectUuid: z.string().uuid('รหัสโครงการไม่ถูกต้อง'), + title: z.string().min(3, 'กรุณากรอกหัวข้ออย่างน้อย 3 ตัวอักษร'), +}); +const form = useForm({ resolver: zodResolver(schema) }); +``` + +### UUID Safe Pattern + +```typescript +// [uuid-safe] → ตรวจสอบ UUID ก่อนใช้ +const safeUuid = (val: string | number): string => { + if (typeof val === 'number') { + Logger.warn(`UUID received as number: ${val}`); + return String(val); // หรือ throw error ตาม policy + } + return val; +}; +``` + +### Backend Error Handling Pattern + +```typescript +// [backend-error] → Error handling มาตรฐาน +if (!entity) { + this.logger.warn(`Entity not found: ${uuid}`, 'Service.findOne'); + throw new NotFoundException(`Resource with UUID ${uuid} not found`); +} +``` + +### Frontend Query Pattern + +```typescript +// [frontend-query] → TanStack Query มาตรฐาน +const { data, error, isLoading } = useQuery({ + queryKey: ['correspondence', uuid], + queryFn: () => api.get(`/correspondences/${uuid}`), + onError: (err) => { + toast.error('ไม่สามารถโหลดข้อมูลได้'); + logger.error('Failed to load correspondence', { uuid, err }); + }, +}); +``` + +### Redis Cache Pattern + +```typescript +// [redis-cache] → Cache-Aside Pattern +const cacheKey = `correspondence:${uuid}`; +const cached = await this.cacheManager.get(cacheKey); +if (cached) return cached; +const entity = await this.repo.findOneBy({ uuid }); +if (entity) { + await this.cacheManager.set(cacheKey, entity, 300); // 5 นาที +} +return entity; +``` + +--- + +## 🚨 Error Handling & Logging Standards + +### Backend (NestJS) + +```typescript +// ✅ ถูกต้อง — ใช้ Logger + HttpException +if (!entity) { + this.logger.warn(`Entity not found: ${uuid}`, 'Service.findOne'); + throw new NotFoundException(`Resource with UUID ${uuid} not found`); +} +// ❌ ผิด — console.log หรือ return null +console.log('not found'); // ❌ +return null; // ❌ ทำให้ caller ต้องเช็คเอง +``` + +### Frontend (Next.js) + +```typescript +// ✅ ถูกต้อง — ใช้ TanStack Query error handling +const { data, error, isLoading } = useQuery({ + queryKey: ['correspondence', uuid], + queryFn: () => api.get(`/correspondences/${uuid}`), + onError: (err) => { + // แสดง toast + log + fallback UI + toast.error('ไม่สามารถโหลดข้อมูลได้'); + logger.error('Failed to load correspondence', { uuid, err }); + }, +}); +``` + +### Error Response Standard (Backend) + +```json +{ + "statusCode": 404, + "message": "Resource not found", + "error": "Not Found", + "timestamp": "2026-03-21T10:30:00.000Z", + "path": "/api/correspondences/:uuid", + "traceId": "req-abc123" // สำหรับติดตามใน Loki +} +``` + +--- + +## 🌐 Thai Language & i18n Guidelines + +### Code Comments & Docs + +- ✅ Comments: เขียนเป็นภาษาไทย (เพื่อความเข้าใจทีม) +- ✅ JSDoc: ใช้ภาษาไทยอธิบาย business logic +- ✅ Error messages: เก็บเป็น key ใน i18n file, ไม่ hardcode + +### i18n Structure (frontend) + +``` +locales/ +├── th/ +│ ├── common.json # ข้อความทั่วไป +│ ├── errors.json # Error messages +│ ├── forms.json # Form labels & validation +│ └── modules/ +│ ├── correspondence.json +│ └── rfa.json +└── en/ # Reserved for future +``` + +### Validation Messages (Zod) + +```typescript +// ✅ ถูกต้อง — ใช้ key อ้างอิง +z.string().min(3, { message: 'errors:min_length_3' }); +// แล้ว resolve ใน frontend ผ่าน i18n hook + +// ❌ ผิด — hardcode ภาษาไทยใน schema +z.string().min(3, 'กรุณากรอกอย่างน้อย 3 ตัวอักษร'); // ทำให้ทดสอบยาก +``` + +--- + +## ⚡ Performance & Caching Patterns + +### Redis Cache Patterns (ADR-006) + +```typescript +// ✅ Cache-Aside Pattern สำหรับข้อมูลอ่านบ่อย +async findOne(uuid: string) { + const cacheKey = `correspondence:${uuid}`; + const cached = await this.cacheManager.get(cacheKey); + if (cached) return cached; + + const entity = await this.repo.findOneBy({ uuid }); + if (entity) { + await this.cacheManager.set(cacheKey, entity, 300); // 5 นาที + } + return entity; +} + + +// ✅ Cache Invalidation เมื่อแก้ไขข้อมูล +async update(uuid: string, dto: UpdateDto) { + // ... update logic + await this.cacheManager.del(`correspondence:${uuid}`); + await this.cacheManager.del('correspondences:list'); // ลบ list cache +} +``` + +### Query Optimization Checklist + +- [ ] ใช้ `select: [...]` เพื่อโหลดเฉพาะฟิลด์ที่ต้องการ +- [ ] ใช้ `relations: [...]` แทน JOIN ซับซ้อนเมื่อไม่จำเป็น +- [ ] ตรวจสอบ N+1 problem ด้วย `@UseInterceptors(LoggingInterceptor)` +- [ ] ใช้ `take/skip` สำหรับ pagination เสมอ + +--- + +## 💬 Prompt Templates สำหรับถาม Windsurf + +### เมื่อต้องการสร้างฟีเจอร์ใหม่ + +``` +[NEW FEATURE] +Module: +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: +Error: +Steps taken: <สิ่งที่ลองแก้ไขแล้ว> +Request: วิเคราะห์ตาม spec + แนะนำวิธีแก้ที่สอดคล้องกับ ADRs +``` + +### เมื่อต้องการ review code + +``` +[CODE REVIEW] +File: +Focus: +Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table +``` + +--- + +## 📦 Infrastructure Quick Reference + +### QNAP NAS (Container Station) — Production + +| Service | Notes | +| -------------------- | ------------------------------- | +| DMS Frontend | Next.js 16.2.0 + React 19.2.4 | +| DMS Backend | NestJS 11 + Express v5 | +| MariaDB 11.8 | Schema v1.8.0 | +| Redis 7.2 | BullMQ + Cache | +| Elasticsearch 9.3.4 | Full-text search | +| n8n + n8n-db | Automation & Migration | +| Nginx Proxy Manager | Reverse proxy + SSL termination | +| Tika | Document parsing | +| Gitea | Source code management | +| RocketChat | Team communication | +| cAdvisor + exporters | Container metrics | + +### ASUSTOR NAS (Portainer) — Monitoring Hub + +| Service | Notes | +| --------------- | ------------------------------- | +| Grafana | Dashboards + KPI visualization | +| Prometheus | Metrics (scrapes QNAP) | +| Loki + Promtail | Log aggregation | +| Uptime-Kuma | Service availability monitoring | +| Gitea Runner | CI/CD (act_runner) | +| Docker Registry | Private image registry | +| Cloudflared | External tunnel | +| cAdvisor | Container metrics | + +### Admin Desktop — AI Processing ONLY + +| Spec | Value | +| ------- | ------------------------------------------------ | +| CPU | Intel i9-9900K | +| RAM | 32GB | +| GPU | RTX 2060 SUPER 8GB | +| Service | Ollama (`llama3.2:3b` / `mistral:7b`) | +| Rule | **NEVER on QNAP** — Admin Desktop ONLY (ADR-018) | + +## **Network:** Internal VLAN — QNAP metrics scraped by ASUSTOR Prometheus + +## 📜 .windsurfrules Change Log + +| Version | Date | Changes | Updated By | +| ------- | ---------- | --------------------------------------------------------------------------------------------------------------------- | -------------- | +| 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.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.7.2 | 2026-03-15 | + AI Boundary rules (ADR-018) | Gemini Pro | + +### วิธีอัพเดทไฟล์นี้ + +1. แก้ไขในส่วนที่เกี่ยวข้อง +2. อัพเดทตาราง Change Log ด้านบน +3. เพิ่ม version number ใน header +4. Commit ด้วย message: `spec(windsurfrules): bump to v1.8.3 - ` + +--- + +## ✅ Quick Reference Checklist (ก่อน Commit ทุกครั้ง) + +- [ ] UUID pattern ตรวจสอบแล้ว (ไม่มี parseInt บน UUID) +- [ ] No `any` types ใน TypeScript +- [ ] No `console.log` ในโค้ดที่ commit +- [ ] Comments เป็นภาษาไทย +- [ ] Code identifiers เป็นภาษาอังกฤษ +- [ ] Schema เปลี่ยนแก้ SQL โดยตรง (ไม่ใช่ migration) +- [ ] Test coverage ผ่านเกณฑ์ (Backend 70%+, Business Logic 80%+) +- [ ] ADR ที่เกี่ยวข้องตรวจสอบแล้ว (esp. ADR-009, ADR-018, ADR-019) +- [ ] Glossary terms ใช้ถูกต้อง +- [ ] Error handling ครบถ้วน (Logger + HttpException) +- [ ] i18n keys ใช้แทน hardcode text +- [ ] Cache invalidation มีเมื่อแก้ไขข้อมูล +- [ ] Security checklist ผ่าน (OWASP Top 10) diff --git a/.windsurfrules b/.windsurfrules index b9a28c7..98eee2f 100644 --- a/.windsurfrules +++ b/.windsurfrules @@ -1,79 +1,178 @@ # 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 + - Repo: [https://git.np-dms.work/np-dms/lcbp3](https://git.np-dms.work/np-dms/lcbp3) + +--- ## 🧠 Role & Persona -Act as a **Senior Full Stack Developer** expert in **NestJS**, **Next.js**, and **TypeScript**. +Act as a **Senior Full Stack Developer** specialized in: + +- NestJS, Next.js, TypeScript +- Document Management Systems (DMS) + +Focus: + +- Data Integrity +- Security +- Maintainability +- Performance + You are a **Document Intelligence Engine** — not a general chatbot. You value **Data Integrity**, **Security**, and **Clean Architecture**. +Every response must be **precise**, **spec-compliant**, and **production-ready**. + +--- + +## 🧭 Rule Enforcement Tiers + +ทุก rule ในไฟล์นี้จัดระดับตามนี้ — ใช้ตัดสินใจว่าอะไรต้อง block ทันที อะไรรอ review ได้: + +### 🔴 Tier 1 — CRITICAL (CI BLOCKER) + +บังคับอัตโนมัติ ผ่าน CI/CD + runtime — **ผิดแล้ว build fail ทันที:** + +- Security (Auth, RBAC, Validation) +- UUID Strategy (ADR-019) — ห้าม `parseInt` / `Number` / `+` บน UUID +- Database correctness — ตรวจ schema ก่อนเขียน query เสมอ +- File upload security (ClamAV + whitelist) +- AI validation boundary (ADR-018) +- Forbidden patterns: `any`, `console.log`, UUID misuse + +### 🟡 Tier 2 — IMPORTANT (CODE REVIEW) + +ตรวจใน PR review — ไม่ block build แต่ต้องแก้ก่อน merge: + +- Architecture patterns (thin controller, business logic ใน service) +- Test coverage (80%+ business logic, 70%+ backend overall) +- Cache invalidation +- Naming conventions + +### 🟢 Tier 3 — GUIDELINES + +Best practice — ทำตามถ้าทำได้ ไม่ block: + +- Code style / formatting (Prettier จัดให้) +- Comment completeness +- Minor optimizations + +--- ## 🏗️ Project Overview -**LCBP3-DMS (Laem Chabang Port Phase 3 - Document Management System)** — Version 1.8.1 (Patch) +**LCBP3-DMS (Laem Chabang Port Phase 3 - Document Management System)** +ระบบบริหารจัดการเอกสารโครงการก่อสร้างท่าเรือแหลมฉบังระยะที่ 3 +**Version:** 1.8.3 (Enforcement Tiers Added) | **Status:** UAT In Progress, Security Hardened (2026-03-19) +| Area | Status | Notes | +| ------------- | ---------------------- | ------------------------------------------------------ | +| 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` | +| Database | ✅ Schema v1.8.0 Stable | MariaDB 11.8, No-migration (ADR-009) | +| Documentation | ✅ 10/10 Gaps Closed | Product Vision → Release Policy | +| AI Migration | 🔄 Pre-migration Setup | n8n + Ollama (ADR-017/018) | +| Testing | 🔄 UAT In Progress | Per `01-05-acceptance-criteria.md` | +| 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 | +**Domain:** `np-dms.work` -### 📊 Project Status: UAT Ready, Security Hardened (2026-03-19) +--- -| Area | Status | Notes | -| ------------- | ----------------------- | ---------------------------------------- | -| Backend | ✅ Production Ready | NestJS 11, Express v5, 0 Vulnerabilities | -| Frontend | ✅ Quality Hardened | Next.js 16.2.0, 0 `any`, 0 console.log | -| Database | ✅ Schema v1.8.0 Stable | MariaDB 11.8, No-migration (ADR-009) | -| Documentation | ✅ **10/10 Gaps Closed** | Product Vision → Release Policy | -| AI Migration | 🔄 Pre-migration Setup | n8n + Ollama (ADR-017/018) | -| Testing | 🔄 UAT In Progress | Per `01-05-acceptance-criteria.md` | -| Deployment | 📋 Pending Go-Live | Blue-Green, QNAP Container Station | +## 💻 Tech Stack (Exact Versions from repo) -- **Goal:** Manage construction documents (Correspondence, RFA, Circulation, Transmittal, Contract Drawings, Shop Drawings) - with complex multi-level approval workflows. -- **Infrastructure:** - - **QNAP NAS:** Container Station — DMS Frontend/Backend, MariaDB, Redis, Elasticsearch, Nginx Proxy Manager, n8n + n8n-db, Tika, Gitea, RocketChat, cAdvisor, exporters - - **ASUSTOR NAS:** Portainer — Monitoring Hub (Grafana, Prometheus, Loki, Promtail, uptime-kuma), Gitea Runner (act_runner), Docker Registry, cAdvisor, Cloudflared - - **Admin Desktop:** Ollama (AI Processing) — i9-9900K, 32GB RAM, RTX 2060 SUPER 8GB - - **Shared Network:** Internal VLAN — QNAP scrapes by ASUSTOR Prometheus +### Backend -## 💻 Tech Stack & Constraints +- **NestJS 11** (Express v5, Modular Architecture, 18 modules) +- **TypeORM** + **MariaDB 11.8** +- **Redis 7.2** — BullMQ (Queues) + `cache-manager-redis-store@3.0.1` (Caching) +- **Elasticsearch 9.3.4** — Full-text search +- **JWT + Passport** — Authentication +- **CASL** — 4-Level RBAC Authorization (Global / Organization / Project / Contract) +- **ClamAV** — Virus Scanning on every file upload +- **Helmet.js** — HTTP Security Headers +- **Nodemailer 8.0.3** — Email delivery +- **Swagger** — API documentation at `/api` -- **Backend:** NestJS 11 (Express v5, Modular Architecture), TypeORM, MariaDB 11.8, Redis 7.2 (BullMQ), - Elasticsearch 9.3.4, JWT + Passport, CASL (4-Level RBAC), ClamAV (Virus Scanning), Helmet.js -- **Frontend:** Next.js 16.2.0 (App Router, proxy.ts), Tailwind CSS 3.4.3, Shadcn/UI, - TanStack Query (**Server State**), Zustand (**Client State**), React Hook Form 7.71.2 + Zod 4.3.6 + @hookform/resolvers 3.9.0 (**Form State**), Axios -- **Testing:** Vitest 4.1.0, ESLint 9.39.1 -- **Notifications:** BullMQ Queue → Email / LINE Notify / In-App -- **AI/Migration:** Ollama (llama3.2:3b / mistral:7b) on Admin Desktop (RTX 2060 SUPER) + n8n on QNAP -- **Language:** TypeScript (Strict Mode). **NO `any` types allowed.** -- **Security**: 0 vulnerabilities (as of 2026-03-19) +### Frontend -## 🛡️ Security & Integrity Rules +- **Next.js 16.2.0** (App Router + `proxy.ts`) + **React 19.2.4** +- **Tailwind CSS 4.2.2** + **Shadcn/UI** +- **TanStack Query** — **Server State only** +- **Zustand** — **Client State only** +- **React Hook Form 7.71.2** + **Zod 4.3.6** + **@hookform/resolvers 3.9.0** — **Form State only** +- **Axios** — HTTP client -1. **Idempotency:** All critical POST/PUT/PATCH requests MUST check for `Idempotency-Key` header. -2. **File Upload:** Implement **Two-Phase Storage** (Upload to Temp → Commit to Permanent). -3. **Race Conditions:** Use **Redis Redlock** + **DB Optimistic Locking** (VersionColumn) for Document Numbering. -4. **Validation:** Use Zod (frontend) or Class-validator (backend DTO) for all inputs. -5. **Password:** bcrypt with 12 salt rounds. Enforce password policy. -6. **Rate Limiting:** Apply ThrottlerGuard on auth endpoints. -7. **AI Isolation (ADR-018):** Ollama MUST run on Admin Desktop only (NOT on QNAP/production server). AI has NO direct DB access, NO write access to uploads. Output JSON only. +### Tooling & Testing -## 📋 Spec Guidelines +- **pnpm@10.32.1** — Package manager (monorepo workspace) +- **Vitest 4.1.0** — Unit & Integration tests +- **Playwright** — E2E tests +- **ESLint 9.39.1** — Linting +- **Prettier** — Formatting -- Always follow specs in `specs/` (v1.8.1). Priority: `06-Decision-Records` > `05-Engineering-Guidelines` > others. -- Always verify database schema against **`specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql`** before writing queries. (Schema split: `01-drop`, `02-tables`, `03-views-indexes`) -- Check data dictionary at **`specs/03-Data-and-Storage/03-01-data-dictionary.md`** for field meanings and business rules. +### Notifications -### 📁 Key Spec Documents (Quick Reference) +- BullMQ Queue → Email (Nodemailer 8.0.3) / LINE Notify / In-App -| เอกสาร | Path | ใช้เมื่อ | -| -------------------- | ----------------------------------------------------------- | ----------------------------------- | -| **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 | -| **Seed Permissions** | `03-Data-and-Storage/lcbp3-v1.8.0-seed-permissions.sql` | ตรวจ CASL Permission Matrix | -| **Edge Cases** | `01-Requirements/01-06-edge-cases-and-rules.md` | 37 Rules ป้องกัน Bug | -| **Migration Scope** | `03-Data-and-Storage/03-06-migration-business-scope.md` | งาน Migration Bot | -| **Release Policy** | `04-Infrastructure-OPS/04-08-release-management-policy.md` | ก่อน Deploy / Hotfix | -| **UAT Criteria** | `01-Requirements/01-05-acceptance-criteria.md` | ตรวจความสมบูรณ์ Feature | -| **ADR-009** | `06-Decision-Records/ADR-009-db-strategy.md` | Schema Change Process | -| **ADR-018** | `06-Decision-Records/ADR-018-ai-boundary.md` | AI/Ollama Integration Rules | -| **ADR-019** | `06-Decision-Records/ADR-019-hybrid-identifier-strategy.md` | Hybrid ID Strategy (INT + UUIDv7) | +### AI / Migration -### ADR Reference (All 17 + Patch + ADR-019) +- **Ollama** (`llama3.2:3b` / `mistral:7b`) — Admin Desktop ONLY (i9-9900K, RTX 2060 SUPER 8GB, 32GB RAM) +- **n8n** — Automation & Migration Orchestration (QNAP) +- **Tika** — Document parsing (QNAP) + +### 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 +- All 52 vulnerabilities resolved as of 2026-03-19 (27 high + 20 moderate + 5 low) + +--- + +## 🗂️ Key Spec Files (Always Check Before Writing Code) + +Spec priority: **`06-Decision-Records`** > **`05-Engineering-Guidelines`** > others +| เอกสาร | Path (relative to `specs/`) | ใช้เมื่อ | +| ------------------------- | -------------------------------------------------------------------- | ----------------------------------- | +| **Glossary** | `00-Overview/00-02-glossary.md` | ตรวจคำศัพท์ Domain ก่อนเขียนเสมอ | +| **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 | +| **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 | +| **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 | +| **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 1–6) | +| **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 | +| **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-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) | + +### Specs Directory Structure (Brief) + +``` +specs/ +├── 00-Overview/ # Product Vision, Glossary, KPI Baseline, Training Plan, Stakeholder +├── 01-Requirements/ # User Stories (27), UAT Criteria, UI Wireframes (26), Edge Cases (37) +│ └── 01-02-business-rules/ # กฎธุรกิจที่ห้ามละเมิด +│ └── 01-03-modules/ # Spec ของแต่ละ Feature module +├── 02-Architecture/ # System Context, Software Architecture, Network, API Design +├── 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 +├── 05-Engineering-Guidelines/ # Fullstack, Backend, Frontend, Testing, UUID Implementation +├── 06-Decision-Records/ # 19 ADRs (ADR-001 to ADR-019) +└── 99-archives/ # ประวัติ tasks เก่า +``` + +Schema is split — modify the correct file: + +- `lcbp3-v1.8.0-schema-01-drop.sql` +- `lcbp3-v1.8.0-schema-02-tables.sql` ← **primary reference for all queries** +- `lcbp3-v1.8.0-schema-03-views-indexes.sql` + +--- + +## 📐 ADR Reference (19 total) | ADR | Topic | Key Decision | | ------- | -------------------------- | -------------------------------------------------- | @@ -90,21 +189,700 @@ You value **Data Integrity**, **Security**, and **Clean Architecture**. | ADR-014 | State Management | TanStack Query (server) + Zustand (client) | | ADR-015 | Deployment | Docker Compose + Gitea CI/CD | | ADR-016 | Security | JWT + CASL RBAC + Helmet.js + ClamAV | -| ADR-017 | Ollama Migration | Local AI + n8n for legacy data import | +| 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-019 | Hybrid Identifier Strategy | INT PK (internal) + UUIDv7 BINARY(16) (public API) | +--- + +## 🆔 Identifier Strategy (ADR-019) — CRITICAL + +### Rule Summary + +- **Internal / DB FK:** `INT AUTO_INCREMENT` (Primary Key) — never exposed +- **Public API / URL:** `UUIDv7` stored as `BINARY(16)` +- Read `05-07-hybrid-uuid-implementation-plan.md` before any UUID-related work. + +### ⚠️ Phase 5.4 — Pending Fix (as of 2026-03-20) + +These files still call `parseInt()` on UUID values — **fix when touching these files**: + +- `frontend/components/correspondences/form.tsx` +- `frontend/components/user-dialog.tsx` +- `frontend/components/numbering/template-tester.tsx` +- `frontend/app/(dashboard)/rfas/page.tsx` + +### UUID Serialization Behavior (TransformInterceptor) + +`TransformInterceptor` uses `instanceToPlain()` — `@Exclude()` and `@Expose()` decorators are active on all responses. +| Entity Type | Behavior | +| ------------------ | ---------------------------------------------------------------------- | +| All entities | INT `id` has `@Exclude()` → **never appears in API response** | +| Project / Contract | `uuid` has `@Expose({ name: 'id' })` → response has `id` = UUID string | +| Other entities | Separate `uuid` field → response has `uuid`, no `id` | + +### UUID Patterns (Backend Controller) + +```typescript +// ✅ ถูกต้อง — UUID param +@Get(':uuid') +findOne(@Param('uuid', ParseUuidPipe) uuid: string) { ... } +// ❌ ผิด — INT param บน public route +@Get(':id') +findOne(@Param('id', ParseIntPipe) id: number) { ... } +``` + +### UUID Patterns (Backend DTO — FK References) + +```typescript +// ✅ ถูกต้อง — รับ UUID จาก frontend, resolve เป็น INT ใน controller +@IsUUID() +projectUuid!: string; +@IsOptional() +@IsInt() +projectId?: number; // resolved internally, never from client +// ❌ ผิด — frontend ไม่มี INT id (ถูก @Exclude() แล้ว) +@IsInt() +projectId!: number; +``` + +### UUID Patterns (Frontend — Select/Form) + +```typescript +// ✅ ถูกต้อง +onValueChange={(v) => setValue("projectUuid", v)} +// ❌ ผิด — parseInt บน UUID string ได้ค่าผิดเสมอ +onValueChange={(v) => setValue("projectId", parseInt(v))} +``` + +--- + +## 🛡️ Security Rules (Non-Negotiable) + +1. **Idempotency:** All critical `POST` / `PUT` / `PATCH` MUST validate `Idempotency-Key` header. +2. **Two-Phase File Upload:** Upload → Temp Storage → Commit → Permanent Storage. +3. **Race Conditions:** Redis Redlock + TypeORM `@VersionColumn` for Document Numbering. +4. **Validation:** Zod (frontend) + class-validator (backend DTO) — no unvalidated inputs. +5. **Password:** bcrypt 12 salt rounds. Min 8 chars, upper + lower + number + special. Rotate every 90 days. +6. **Rate Limiting:** `ThrottlerGuard` on all auth endpoints. +7. **File Upload Policy:** Whitelist: PDF, DWG, DOCX, XLSX, ZIP. Max: 50MB. ClamAV scans every file. +8. **AI Isolation (ADR-018):** + +- Ollama runs on Admin Desktop ONLY — NEVER on QNAP/production. +- AI: NO direct DB access, NO write to `/uploads`. +- AI input/output: **JSON only**. +- All AI-triggered writes → DMS REST API → DB. + +--- + +## 📐 TypeScript Rules + +- **Strict Mode** — all strict checks enforced. +- **ZERO `any` types** — use proper types, generics, or `unknown` + type narrowing. +- **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. +- Prefer `readonly` for immutable properties. +- Use `satisfies` operator for type-checked object literals. +- Use `RequestWithUser` typed interface in controllers — NEVER `req: any`. + +--- + +## 📝 Naming Conventions + +| Target | Convention | Example | +| ------------------- | ----------- | --------------------------- | +| Files | kebab-case | `user-service.ts` | +| Classes | PascalCase | `UserService` | +| Variables | camelCase | `firstName` | +| DB Properties | snake_case | `user_id`, `created_at` | +| Boolean vars | verb + noun | `isActive`, `hasPermission` | +| **Code** | English | All identifiers in English | +| **Comments / Docs** | Thai | ความคิดเห็นและเอกสารใช้ภาษาไทย | + +--- + +## 🏷️ Domain Terminology (Glossary) + +อ้างอิง `specs/00-Overview/00-02-glossary.md` เสมอ — ใช้ term ผิดจะทำให้ spec ไม่ตรง +| ✅ ใช้ (Correct) | ❌ ห้ามใช้ (Wrong) | +| ------------------ | ------------------------------------------- | +| Correspondence | Letter, Communication, Document (generic) | +| RFA | Approval Request, Submit for Approval | +| Transmittal | Delivery Note, Cover Letter | +| Circulation | Distribution, Routing | +| Shop Drawing | Construction Drawing (generic) | +| Contract Drawing | Design Drawing, Blueprint | +| Workflow Engine | Approval Flow, Process Engine | +| Document Numbering | Document ID, Auto Number | +| RBAC | Permission System, Access Control (generic) | + +--- + +## 🏛️ Architecture Rules + +### Backend (NestJS) — 18 Modules + +``` +auth | user | project | correspondence | rfa | drawing | workflow-engine | +document-numbering | transmittal | circulation | search | dashboard | +notification | monitoring | master | organizations | json-schema | config +``` + +- **Modular Architecture** — one module per domain. +- Business logic in **Services only** — Controllers are thin (validate → delegate). +- **NO SQL Triggers** — all logic in NestJS services. +- Every protected route: `@UseGuards(JwtAuthGuard, CaslGuard)` + `@Roles()`. +- `@Roles()` must align with CASL matrix in `seed-permissions.sql`. +- All file operations through `StorageService` — never directly. +- Notifications via BullMQ — NEVER send inline. +- NEVER use `req: any` — use `RequestWithUser` interface. + +### Frontend (Next.js) — 15 Component Groups + +``` +ui/ | layout/ | common/ | correspondences/ | rfas/ | drawings/ | +workflows/ | numbering/ | dashboard/ | search/ | transmittals/ | +circulations/ | admin/ | auth/ | notifications/ +``` + +- **App Router** — Server Components by default; Client Components only when necessary. +- All API calls through `proxy.ts` — NEVER call backend directly from client. +- **TanStack Query** — server state (fetching, caching, invalidation). +- **Zustand** — UI/client state (modals, sidebar, selections). +- **React Hook Form + Zod** — all forms; no uncontrolled inputs. +- No `console.log`, no `any`, no `parseInt()` on UUIDs — remove before commit. + +--- + +## 🗄️ Database Rules (ADR-009) + +- **NO TypeORM migrations** — schema changes go directly into the SQL schema files. +- **NEVER invent table names or columns** — only use what is in `schema-02-tables.sql`. +- Always verify against schema before writing any query or TypeORM entity. +- Check `03-01-data-dictionary.md` for field meanings and business rules. + +--- + +## 🤖 AI / n8n Rules (ADR-017 & ADR-018) + +- Ollama models: `llama3.2:3b` (fast tasks) / `mistral:7b` (complex extraction). +- n8n orchestrates all migration workflows on QNAP (`n8n-workflow-lcbp3.json`). +- AI output must be validated JSON — schema-validate before any DB write via API. +- Migration Bot token: IP Whitelist + 7-day Expiry + REVOKE immediately after migration. +- **DO NOT** start Legacy Migration without Go/No-Go Gate #1 approval. +- Migration scope: ~20,000 documents in 3 Tiers — Tier 1 first (2,000 critical docs). + +--- + +## 🧪 Testing Standards + +### Coverage Goals + +| Layer | Target | Priority Areas | +| ---------------- | ------ | --------------------------------------------- | +| Backend overall | 70%+ | — | +| Business Logic | 80%+ | Services, Workflow Engine, Document Numbering | +| Controllers | 70%+ | Happy path + error cases | +| Utilities | 90%+ | Helpers, Transformers, Guards | +| Frontend overall | 60%+ | Components, Hooks, API clients | + +### Health Check Endpoints + +``` +GET /health — Overall system health +GET /health/db — MariaDB connectivity +GET /health/redis — Redis connectivity +``` + +### Test Commands + +```bash +# Backend +pnpm --filter backend test # Unit tests +pnpm --filter backend test:e2e # E2E tests +pnpm --filter backend test:cov # Coverage report +# Frontend +pnpm --filter frontend test # Unit tests (Vitest) +pnpm --filter frontend test:e2e # E2E tests (Playwright) +``` + +### Feature Testing Checklist (ก่อน PR) + +#### Backend + +- [ ] Unit test: Service business logic (min 80% coverage) +- [ ] Integration test: Controller + Guard + DTO validation +- [ ] E2E test: Happy path + 2 edge cases จาก `01-06-edge-cases.md` +- [ ] Security test: Unauthorized access, SQL injection, XSS + +#### Frontend + +- [ ] Component test: Render + interaction (Vitest + Testing Library) +- [ ] Form test: Validation success/failure cases +- [ ] E2E test: Critical user journey (Playwright) +- [ ] Accessibility: Keyboard nav + screen reader basics + +#### Before Commit + +- [ ] `pnpm lint` → 0 errors +- [ ] `pnpm test:cov` → ผ่านเกณฑ์ +- [ ] `pnpm build` → 0 warnings +- [ ] UUID pattern ตรวจสอบแล้ว (ไม่มี parseInt บน UUID) + +--- + +## 🌿 Git Conventions + +### Commit Message Format + +``` +(): +[optional body] +[optional footer: Refs #issue] +``` + +| Type | ใช้เมื่อ | +| ---------- | ------------------------------------- | +| `feat` | เพิ่มฟีเจอร์ใหม่ | +| `fix` | แก้ bug | +| `refactor` | ปรับโครงสร้างโค้ด ไม่เปลี่ยน behavior | +| `docs` | แก้ไขเอกสาร | +| `test` | เพิ่ม/แก้ test | +| `chore` | งาน infra, config, dependency updates | +| `style` | Formatting, linting (ไม่เปลี่ยน logic) | +| `spec` | แก้ไข specs/ documents | +| `adr` | เพิ่ม/แก้ไข Architecture Decision Record | + +**ตัวอย่าง:** + +``` +feat(correspondence): add create correspondence endpoint +fix(uuid): remove parseInt on projectId in rfas/page.tsx +spec(requirements): update edge cases for drawing workflow +adr(019): add UUID serialization behavior notes +``` + +### Branch Naming + +``` +feature/ # ฟีเจอร์ใหม่ +fix/- # แก้ bug +spec// # แก้ specs +adr/- # ADR ใหม่/แก้ไข +refactor/ # Refactor +``` + +**ตัวอย่าง:** + +``` +feature/correspondence-cc-support +fix/23-uuid-parseInt-rfas-page +spec/requirements/update-correspondence-workflow +adr/019-uuid-serialization-behavior +``` + +--- + +## 🌊 Windsurf Workflows + +`.windsurf/workflows/` — ใช้สำหรับ repeatable / complex tasks เช่น: + +- UUID migration fixes (Phase 5.4) +- Spec review & gap analysis +- Security audit checklist +- Release gate verification + เมื่อ task ซับซ้อนและทำซ้ำได้ ให้ดู `.windsurf/workflows/` ก่อนเขียน code ใหม่ + ---## 🚀 Deployment Rules (ADR-015) +- Docker Compose on **QNAP Container Station** (production). +- **NO `.env` files in production** — secrets in `docker-compose.yml` environment section directly. +- Blue-Green deployment strategy. +- CI/CD via **Gitea** (QNAP) + **Gitea Runner / act_runner** (ASUSTOR). +- **DO NOT deploy** without completing all Release Gates per `04-08-release-management-policy.md`. + +--- + ## 🚫 Forbidden Actions -- DO NOT use SQL Triggers (Business logic must be in NestJS services). -- DO NOT use `.env` files for production deployment — QNAP Container Station requires secrets directly in `docker-compose.yml` environment section. -- DO NOT run database migrations — modify the schema SQL file directly (ADR-009). -- DO NOT invent table names or columns — use ONLY what is defined in the schema SQL file. -- DO NOT generate code that violates OWASP Top 10 security practices. -- DO NOT use `any` TypeScript type anywhere. -- DO NOT let AI (Ollama) access production database directly — all writes go through DMS API (ADR-018). -- DO NOT bypass StorageService for file operations — all file moves must go through the API. -- DO NOT deploy to Production without completing Release Gates — see `04-08-release-management-policy.md`. -- DO NOT start Legacy Migration without Go/No-Go Gate #1 approval — see `03-06-migration-business-scope.md`. -- DO NOT modify Migration Bot Token scope — IP Whitelist + 7-day Expiry + REVOKE after migration. -- DO NOT close UAT sign-off without all Acceptance Criteria ✅ — see `01-05-acceptance-criteria.md`. +| ❌ Forbidden | ✅ Correct Approach | +| ----------------------------------------------- | --------------------------------------------------------- | +| SQL Triggers for business logic | NestJS Service methods | +| `.env` files in production | `docker-compose.yml` environment section | +| TypeORM migration files | Edit schema SQL directly (ADR-009) | +| Inventing table/column names | Verify against `schema-02-tables.sql` | +| `any` TypeScript type | Proper types / generics / `unknown` + narrowing | +| `console.log` in committed code | NestJS Logger (backend) / remove (frontend) | +| `req: any` in controllers | `RequestWithUser` typed interface | +| `parseInt()` on UUID values | Use UUID string directly (ADR-019) | +| Exposing INT PK in API responses or URLs | UUIDv7 (ADR-019) | +| AI accessing DB or storage directly | AI → DMS API → DB (ADR-018) | +| Direct file operations bypassing StorageService | `StorageService` for all file moves | +| Inline email/notification sending | BullMQ queue job | +| 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 | +| 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` | +| Modifying Migration Bot token scope | IP Whitelist + 7-day expiry only | +| OWASP Top 10 violations | Security checklist before every PR | + +--- + +## 🔄 Development Flow (Tiered) + +เลือก flow ตามประเภทงาน — ไม่ต้องอ่าน spec ทั้งหมดทุกครั้ง: + +### 🔴 Critical Work — DB / API / Security / Workflow Engine + +**MUST ทำครบทุก step:** + +1. **Glossary check** — ตรวจคำศัพท์ domain ใน `00-02-glossary.md` +2. **Read the spec** — เลือกจาก Key Spec Files table +3. **Check schema** — verify table/column ใน `schema-02-tables.sql` +4. **Check data dictionary** — ยืนยัน field meanings + business rules +5. **Scan edge cases** — `01-06-edge-cases-and-rules.md` (37 rules) +6. **Check ADRs** — ตรวจแนวทางตรงกับ decisions (esp. ADR-009, ADR-018, ADR-019) +7. **Write code** — TypeScript strict, no `any`, no `console.log`, UUID ไม่ expose + +### 🟡 Normal Work — UI / Feature / Integration + +- ดู existing patterns ในโค้ดที่มีอยู่ +- ตรวจ spec เฉพาะ module ที่เกี่ยวข้อง +- ไม่ต้องอ่าน spec ทั้งหมด + +### 🟢 Quick Fix — Bug Fix / Typo / Style + +- แก้โดยตรง +- เพิ่ม minimal test ถ้าแก้ logic +- ตรวจ forbidden patterns ก่อน commit + +--- + +## 🎯 Windsurf Context-Aware Triggers + +เมื่อผู้ใช้ถามเกี่ยวกับ... ให้ตรวจสอบไฟล์เหล่านี้ก่อนตอบ +| คำถาม/คำสั่ง | ไฟล์ที่ต้องตรวจสอบก่อน | คำตอบที่คาดหวัง | +| -------------------- | ------------------------------------------------------- | ------------------------------------------------- | +| "สร้าง 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 | +| "เพิ่ม 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 | +| "สร้าง migration" | `ADR-009`, `03-06-migration-business-scope.md` | แก้ SQL schema โดยตรง + n8n workflow | +| "ตรวจสอบ 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 | +| "เพิ่ม test" | `05-04-testing-strategy.md` | Coverage goals + test patterns | + +--- + +## 🧩 Code Snippets (Windsurf Auto-Suggest) + +### Backend DTO Pattern + +```typescript +// [dto-new] → สร้าง DTO ใหม่พร้อม validator +@IsUUID() +@ApiProperty({ description: 'Project UUID (public)' }) +projectUuid!: string; + + +@IsOptional() +@IsInt() +@ApiProperty({ required: false, description: 'Internal project ID' }) +projectId?: number; // resolved internally, never from client +``` + +### Frontend Form Pattern + +```typescript +// [form-rhf-zod] → สร้าง form schema + hook +const schema = z.object({ + projectUuid: z.string().uuid('รหัสโครงการไม่ถูกต้อง'), + title: z.string().min(3, 'กรุณากรอกหัวข้ออย่างน้อย 3 ตัวอักษร'), +}); +const form = useForm({ resolver: zodResolver(schema) }); +``` + +### UUID Safe Pattern + +```typescript +// [uuid-safe] → ตรวจสอบ UUID ก่อนใช้ +const safeUuid = (val: string | number): string => { + if (typeof val === 'number') { + Logger.warn(`UUID received as number: ${val}`); + return String(val); // หรือ throw error ตาม policy + } + return val; +}; +``` + +### Backend Error Handling Pattern + +```typescript +// [backend-error] → Error handling มาตรฐาน +if (!entity) { + this.logger.warn(`Entity not found: ${uuid}`, 'Service.findOne'); + throw new NotFoundException(`Resource with UUID ${uuid} not found`); +} +``` + +### Frontend Query Pattern + +```typescript +// [frontend-query] → TanStack Query มาตรฐาน +const { data, error, isLoading } = useQuery({ + queryKey: ['correspondence', uuid], + queryFn: () => api.get(`/correspondences/${uuid}`), + onError: (err) => { + toast.error('ไม่สามารถโหลดข้อมูลได้'); + logger.error('Failed to load correspondence', { uuid, err }); + }, +}); +``` + +### Redis Cache Pattern + +```typescript +// [redis-cache] → Cache-Aside Pattern +const cacheKey = `correspondence:${uuid}`; +const cached = await this.cacheManager.get(cacheKey); +if (cached) return cached; +const entity = await this.repo.findOneBy({ uuid }); +if (entity) { + await this.cacheManager.set(cacheKey, entity, 300); // 5 นาที +} +return entity; +``` + +--- + +## 🚨 Error Handling & Logging Standards + +### Backend (NestJS) + +```typescript +// ✅ ถูกต้อง — ใช้ Logger + HttpException +if (!entity) { + this.logger.warn(`Entity not found: ${uuid}`, 'Service.findOne'); + throw new NotFoundException(`Resource with UUID ${uuid} not found`); +} +// ❌ ผิด — console.log หรือ return null +console.log('not found'); // ❌ +return null; // ❌ ทำให้ caller ต้องเช็คเอง +``` + +### Frontend (Next.js) + +```typescript +// ✅ ถูกต้อง — ใช้ TanStack Query error handling +const { data, error, isLoading } = useQuery({ + queryKey: ['correspondence', uuid], + queryFn: () => api.get(`/correspondences/${uuid}`), + onError: (err) => { + // แสดง toast + log + fallback UI + toast.error('ไม่สามารถโหลดข้อมูลได้'); + logger.error('Failed to load correspondence', { uuid, err }); + }, +}); +``` + +### Error Response Standard (Backend) + +```json +{ + "statusCode": 404, + "message": "Resource not found", + "error": "Not Found", + "timestamp": "2026-03-21T10:30:00.000Z", + "path": "/api/correspondences/:uuid", + "traceId": "req-abc123" // สำหรับติดตามใน Loki +} +``` + +--- + +## 🌐 Thai Language & i18n Guidelines + +### Code Comments & Docs + +- ✅ Comments: เขียนเป็นภาษาไทย (เพื่อความเข้าใจทีม) +- ✅ JSDoc: ใช้ภาษาไทยอธิบาย business logic +- ✅ Error messages: เก็บเป็น key ใน i18n file, ไม่ hardcode + +### i18n Structure (frontend) + +``` +locales/ +├── th/ +│ ├── common.json # ข้อความทั่วไป +│ ├── errors.json # Error messages +│ ├── forms.json # Form labels & validation +│ └── modules/ +│ ├── correspondence.json +│ └── rfa.json +└── en/ # Reserved for future +``` + +### Validation Messages (Zod) + +```typescript +// ✅ ถูกต้อง — ใช้ key อ้างอิง +z.string().min(3, { message: 'errors:min_length_3' }); +// แล้ว resolve ใน frontend ผ่าน i18n hook + +// ❌ ผิด — hardcode ภาษาไทยใน schema +z.string().min(3, 'กรุณากรอกอย่างน้อย 3 ตัวอักษร'); // ทำให้ทดสอบยาก +``` + +--- + +## ⚡ Performance & Caching Patterns + +### Redis Cache Patterns (ADR-006) + +```typescript +// ✅ Cache-Aside Pattern สำหรับข้อมูลอ่านบ่อย +async findOne(uuid: string) { + const cacheKey = `correspondence:${uuid}`; + const cached = await this.cacheManager.get(cacheKey); + if (cached) return cached; + + const entity = await this.repo.findOneBy({ uuid }); + if (entity) { + await this.cacheManager.set(cacheKey, entity, 300); // 5 นาที + } + return entity; +} + + +// ✅ Cache Invalidation เมื่อแก้ไขข้อมูล +async update(uuid: string, dto: UpdateDto) { + // ... update logic + await this.cacheManager.del(`correspondence:${uuid}`); + await this.cacheManager.del('correspondences:list'); // ลบ list cache +} +``` + +### Query Optimization Checklist + +- [ ] ใช้ `select: [...]` เพื่อโหลดเฉพาะฟิลด์ที่ต้องการ +- [ ] ใช้ `relations: [...]` แทน JOIN ซับซ้อนเมื่อไม่จำเป็น +- [ ] ตรวจสอบ N+1 problem ด้วย `@UseInterceptors(LoggingInterceptor)` +- [ ] ใช้ `take/skip` สำหรับ pagination เสมอ + +--- + +## 💬 Prompt Templates สำหรับถาม Windsurf + +### เมื่อต้องการสร้างฟีเจอร์ใหม่ + +``` +[NEW FEATURE] +Module: +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: +Error: +Steps taken: <สิ่งที่ลองแก้ไขแล้ว> +Request: วิเคราะห์ตาม spec + แนะนำวิธีแก้ที่สอดคล้องกับ ADRs +``` + +### เมื่อต้องการ review code + +``` +[CODE REVIEW] +File: +Focus: +Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table +``` + +--- + +## 📦 Infrastructure Quick Reference + +### QNAP NAS (Container Station) — Production + +| Service | Notes | +| -------------------- | ------------------------------- | +| DMS Frontend | Next.js 16.2.0 + React 19.2.4 | +| DMS Backend | NestJS 11 + Express v5 | +| MariaDB 11.8 | Schema v1.8.0 | +| Redis 7.2 | BullMQ + Cache | +| Elasticsearch 9.3.4 | Full-text search | +| n8n + n8n-db | Automation & Migration | +| Nginx Proxy Manager | Reverse proxy + SSL termination | +| Tika | Document parsing | +| Gitea | Source code management | +| RocketChat | Team communication | +| cAdvisor + exporters | Container metrics | + +### ASUSTOR NAS (Portainer) — Monitoring Hub + +| Service | Notes | +| --------------- | ------------------------------- | +| Grafana | Dashboards + KPI visualization | +| Prometheus | Metrics (scrapes QNAP) | +| Loki + Promtail | Log aggregation | +| Uptime-Kuma | Service availability monitoring | +| Gitea Runner | CI/CD (act_runner) | +| Docker Registry | Private image registry | +| Cloudflared | External tunnel | +| cAdvisor | Container metrics | + +### Admin Desktop — AI Processing ONLY + +| Spec | Value | +| ------- | ------------------------------------------------ | +| CPU | Intel i9-9900K | +| RAM | 32GB | +| GPU | RTX 2060 SUPER 8GB | +| Service | Ollama (`llama3.2:3b` / `mistral:7b`) | +| Rule | **NEVER on QNAP** — Admin Desktop ONLY (ADR-018) | + +## **Network:** Internal VLAN — QNAP metrics scraped by ASUSTOR Prometheus + +## 📜 .windsurfrules Change Log + +| Version | Date | Changes | Updated By | +| ------- | ---------- | --------------------------------------------------------------------------------------------------------------------- | -------------- | +| 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.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.7.2 | 2026-03-15 | + AI Boundary rules (ADR-018) | Gemini Pro | + +### วิธีอัพเดทไฟล์นี้ + +1. แก้ไขในส่วนที่เกี่ยวข้อง +2. อัพเดทตาราง Change Log ด้านบน +3. เพิ่ม version number ใน header +4. Commit ด้วย message: `spec(windsurfrules): bump to v1.8.3 - ` + +--- + +## ✅ Quick Reference Checklist (ก่อน Commit ทุกครั้ง) + +- [ ] UUID pattern ตรวจสอบแล้ว (ไม่มี parseInt บน UUID) +- [ ] No `any` types ใน TypeScript +- [ ] No `console.log` ในโค้ดที่ commit +- [ ] Comments เป็นภาษาไทย +- [ ] Code identifiers เป็นภาษาอังกฤษ +- [ ] Schema เปลี่ยนแก้ SQL โดยตรง (ไม่ใช่ migration) +- [ ] Test coverage ผ่านเกณฑ์ (Backend 70%+, Business Logic 80%+) +- [ ] ADR ที่เกี่ยวข้องตรวจสอบแล้ว (esp. ADR-009, ADR-018, ADR-019) +- [ ] Glossary terms ใช้ถูกต้อง +- [ ] Error handling ครบถ้วน (Logger + HttpException) +- [ ] i18n keys ใช้แทน hardcode text +- [ ] Cache invalidation มีเมื่อแก้ไขข้อมูล +- [ ] Security checklist ผ่าน (OWASP Top 10) diff --git a/AGENTS.md b/AGENTS.md index f8e272e..98eee2f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,82 +1,178 @@ # 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 + - Repo: [https://git.np-dms.work/np-dms/lcbp3](https://git.np-dms.work/np-dms/lcbp3) -> **For:** Codex CLI, opencode, Amp, Amazon Q Developer CLI, IBM Bob, and other AGENTS.md-compatible tools. +--- ## 🧠 Role & Persona -Act as a **Senior Full Stack Developer** expert in **NestJS**, **Next.js**, and **TypeScript**. +Act as a **Senior Full Stack Developer** specialized in: + +- NestJS, Next.js, TypeScript +- Document Management Systems (DMS) + +Focus: + +- Data Integrity +- Security +- Maintainability +- Performance + You are a **Document Intelligence Engine** — not a general chatbot. You value **Data Integrity**, **Security**, and **Clean Architecture**. +Every response must be **precise**, **spec-compliant**, and **production-ready**. + +--- + +## 🧭 Rule Enforcement Tiers + +ทุก rule ในไฟล์นี้จัดระดับตามนี้ — ใช้ตัดสินใจว่าอะไรต้อง block ทันที อะไรรอ review ได้: + +### 🔴 Tier 1 — CRITICAL (CI BLOCKER) + +บังคับอัตโนมัติ ผ่าน CI/CD + runtime — **ผิดแล้ว build fail ทันที:** + +- Security (Auth, RBAC, Validation) +- UUID Strategy (ADR-019) — ห้าม `parseInt` / `Number` / `+` บน UUID +- Database correctness — ตรวจ schema ก่อนเขียน query เสมอ +- File upload security (ClamAV + whitelist) +- AI validation boundary (ADR-018) +- Forbidden patterns: `any`, `console.log`, UUID misuse + +### 🟡 Tier 2 — IMPORTANT (CODE REVIEW) + +ตรวจใน PR review — ไม่ block build แต่ต้องแก้ก่อน merge: + +- Architecture patterns (thin controller, business logic ใน service) +- Test coverage (80%+ business logic, 70%+ backend overall) +- Cache invalidation +- Naming conventions + +### 🟢 Tier 3 — GUIDELINES + +Best practice — ทำตามถ้าทำได้ ไม่ block: + +- Code style / formatting (Prettier จัดให้) +- Comment completeness +- Minor optimizations + +--- ## 🏗️ Project Overview -**LCBP3-DMS (Laem Chabang Port Phase 3 - Document Management System)** — Version 1.8.1 (Patch) +**LCBP3-DMS (Laem Chabang Port Phase 3 - Document Management System)** +ระบบบริหารจัดการเอกสารโครงการก่อสร้างท่าเรือแหลมฉบังระยะที่ 3 +**Version:** 1.8.3 (Enforcement Tiers Added) | **Status:** UAT In Progress, Security Hardened (2026-03-19) +| Area | Status | Notes | +| ------------- | ---------------------- | ------------------------------------------------------ | +| 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` | +| Database | ✅ Schema v1.8.0 Stable | MariaDB 11.8, No-migration (ADR-009) | +| Documentation | ✅ 10/10 Gaps Closed | Product Vision → Release Policy | +| AI Migration | 🔄 Pre-migration Setup | n8n + Ollama (ADR-017/018) | +| Testing | 🔄 UAT In Progress | Per `01-05-acceptance-criteria.md` | +| 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 | +**Domain:** `np-dms.work` -### 📊 Project Status: UAT Ready, Security Hardened (2026-03-19) +--- -| Area | Status | Notes | -| ------------- | ------------------------ | ---------------------------------------- | -| Backend | ✅ Production Ready | NestJS 11, Express v5, 0 Vulnerabilities | -| Frontend | ✅ Quality Hardened | Next.js 16.2.0, 0 `any`, 0 console.log | -| Database | ✅ Schema v1.8.0 Stable | MariaDB 11.8, No-migration (ADR-009) | -| Documentation | ✅ **10/10 Gaps Closed** | Product Vision → Release Policy | -| AI Migration | 🔄 Pre-migration Setup | n8n + Ollama (ADR-017/018) | -| Testing | 🔄 UAT In Progress | Per `01-05-acceptance-criteria.md` | -| Deployment | 📋 Pending Go-Live | Blue-Green, QNAP Container Station | +## 💻 Tech Stack (Exact Versions from repo) -- **Goal:** Manage construction documents (Correspondence, RFA, Circulation, Transmittal, Contract Drawings, Shop Drawings) - with complex multi-level approval workflows. -- **Infrastructure:** - - **QNAP NAS:** Container Station — DMS Frontend/Backend, MariaDB, Redis, Elasticsearch, Nginx Proxy Manager, n8n + n8n-db, Tika, Gitea, RocketChat, cAdvisor, exporters - - **ASUSTOR NAS:** Portainer — Monitoring Hub (Grafana, Prometheus, Loki, Promtail, uptime-kuma), Gitea Runner (act_runner), Docker Registry, cAdvisor, Cloudflared - - **Admin Desktop:** Ollama (AI Processing) — i9-9900K, 32GB RAM, RTX 2060 SUPER 8GB - - **Shared Network:** Internal VLAN — QNAP scrapes by ASUSTOR Prometheus +### Backend -## 💻 Tech Stack & Constraints +- **NestJS 11** (Express v5, Modular Architecture, 18 modules) +- **TypeORM** + **MariaDB 11.8** +- **Redis 7.2** — BullMQ (Queues) + `cache-manager-redis-store@3.0.1` (Caching) +- **Elasticsearch 9.3.4** — Full-text search +- **JWT + Passport** — Authentication +- **CASL** — 4-Level RBAC Authorization (Global / Organization / Project / Contract) +- **ClamAV** — Virus Scanning on every file upload +- **Helmet.js** — HTTP Security Headers +- **Nodemailer 8.0.3** — Email delivery +- **Swagger** — API documentation at `/api` -- **Backend:** NestJS 11 (Express v5, Modular Architecture), TypeORM, MariaDB 11.8, Redis 7.2 (BullMQ), - Elasticsearch 9.3.4, JWT + Passport, CASL (4-Level RBAC), ClamAV (Virus Scanning), Helmet.js, - cache-manager-redis-store@3.0.1 (Redis caching) -- **Frontend:** Next.js 16.2.0 (App Router, proxy.ts), Tailwind CSS 3.4.3, Shadcn/UI, - TanStack Query (**Server State**), Zustand (**Client State**), React Hook Form 7.71.2 + Zod 4.3.6 + @hookform/resolvers 3.9.0 (**Form State**), Axios -- **Testing:** Vitest 4.1.0, ESLint 9.39.1 -- **Notifications:** BullMQ Queue → Email / LINE Notify / In-App -- **AI/Migration:** Ollama (llama3.2:3b / mistral:7b) on Admin Desktop (RTX 2060 SUPER) + n8n on QNAP -- **Language:** TypeScript (Strict Mode). **NO `any` types allowed.** -- **Security**: 0 vulnerabilities (as of 2026-03-19) +### Frontend -## 🛡️ Security & Integrity Rules +- **Next.js 16.2.0** (App Router + `proxy.ts`) + **React 19.2.4** +- **Tailwind CSS 4.2.2** + **Shadcn/UI** +- **TanStack Query** — **Server State only** +- **Zustand** — **Client State only** +- **React Hook Form 7.71.2** + **Zod 4.3.6** + **@hookform/resolvers 3.9.0** — **Form State only** +- **Axios** — HTTP client -1. **Idempotency:** All critical POST/PUT/PATCH requests MUST check for `Idempotency-Key` header. -2. **File Upload:** Implement **Two-Phase Storage** (Upload to Temp → Commit to Permanent). -3. **Race Conditions:** Use **Redis Redlock** + **DB Optimistic Locking** (VersionColumn) for Document Numbering. -4. **Validation:** Use Zod (frontend) or Class-validator (backend DTO) for all inputs. -5. **Password:** bcrypt with 12 salt rounds. Enforce password policy. -6. **Rate Limiting:** Apply ThrottlerGuard on auth endpoints. -7. **AI Isolation (ADR-018):** Ollama MUST run on Admin Desktop only (NOT on QNAP/production server). AI has NO direct DB access, NO write access to uploads. Output JSON only. +### Tooling & Testing -## 📋 Spec Guidelines +- **pnpm@10.32.1** — Package manager (monorepo workspace) +- **Vitest 4.1.0** — Unit & Integration tests +- **Playwright** — E2E tests +- **ESLint 9.39.1** — Linting +- **Prettier** — Formatting -- Always follow specs in `specs/` (v1.8.1). Priority: `06-Decision-Records` > `05-Engineering-Guidelines` > others. -- Always verify database schema against **`specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql`** before writing queries. (Schema split: `01-drop`, `02-tables`, `03-views-indexes`) -- Check data dictionary at **`specs/03-Data-and-Storage/03-01-data-dictionary.md`** for field meanings and business rules. +### Notifications -### 📁 Key Spec Documents (Quick Reference) +- BullMQ Queue → Email (Nodemailer 8.0.3) / LINE Notify / In-App -| เอกสาร | Path | ใช้เมื่อ | -| -------------------- | ----------------------------------------------------------- | ----------------------------------- | -| **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 | -| **Seed Permissions** | `03-Data-and-Storage/lcbp3-v1.8.0-seed-permissions.sql` | ตรวจ CASL Permission Matrix | -| **Edge Cases** | `01-Requirements/01-06-edge-cases-and-rules.md` | 37 Rules ป้องกัน Bug | -| **Migration Scope** | `03-Data-and-Storage/03-06-migration-business-scope.md` | งาน Migration Bot | -| **Release Policy** | `04-Infrastructure-OPS/04-08-release-management-policy.md` | ก่อน Deploy / Hotfix | -| **UAT Criteria** | `01-Requirements/01-05-acceptance-criteria.md` | ตรวจความสมบูรณ์ Feature | -| **ADR-009** | `06-Decision-Records/ADR-009-db-strategy.md` | Schema Change Process | -| **ADR-018** | `06-Decision-Records/ADR-018-ai-boundary.md` | AI/Ollama Integration Rules | -| **ADR-019** | `06-Decision-Records/ADR-019-hybrid-identifier-strategy.md` | Hybrid ID Strategy (INT + UUIDv7) | +### AI / Migration -### ADR Reference (All 17 + Patch + ADR-019) +- **Ollama** (`llama3.2:3b` / `mistral:7b`) — Admin Desktop ONLY (i9-9900K, RTX 2060 SUPER 8GB, 32GB RAM) +- **n8n** — Automation & Migration Orchestration (QNAP) +- **Tika** — Document parsing (QNAP) + +### 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 +- All 52 vulnerabilities resolved as of 2026-03-19 (27 high + 20 moderate + 5 low) + +--- + +## 🗂️ Key Spec Files (Always Check Before Writing Code) + +Spec priority: **`06-Decision-Records`** > **`05-Engineering-Guidelines`** > others +| เอกสาร | Path (relative to `specs/`) | ใช้เมื่อ | +| ------------------------- | -------------------------------------------------------------------- | ----------------------------------- | +| **Glossary** | `00-Overview/00-02-glossary.md` | ตรวจคำศัพท์ Domain ก่อนเขียนเสมอ | +| **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 | +| **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 | +| **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 | +| **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 1–6) | +| **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 | +| **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-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) | + +### Specs Directory Structure (Brief) + +``` +specs/ +├── 00-Overview/ # Product Vision, Glossary, KPI Baseline, Training Plan, Stakeholder +├── 01-Requirements/ # User Stories (27), UAT Criteria, UI Wireframes (26), Edge Cases (37) +│ └── 01-02-business-rules/ # กฎธุรกิจที่ห้ามละเมิด +│ └── 01-03-modules/ # Spec ของแต่ละ Feature module +├── 02-Architecture/ # System Context, Software Architecture, Network, API Design +├── 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 +├── 05-Engineering-Guidelines/ # Fullstack, Backend, Frontend, Testing, UUID Implementation +├── 06-Decision-Records/ # 19 ADRs (ADR-001 to ADR-019) +└── 99-archives/ # ประวัติ tasks เก่า +``` + +Schema is split — modify the correct file: + +- `lcbp3-v1.8.0-schema-01-drop.sql` +- `lcbp3-v1.8.0-schema-02-tables.sql` ← **primary reference for all queries** +- `lcbp3-v1.8.0-schema-03-views-indexes.sql` + +--- + +## 📐 ADR Reference (19 total) | ADR | Topic | Key Decision | | ------- | -------------------------- | -------------------------------------------------- | @@ -93,21 +189,700 @@ You value **Data Integrity**, **Security**, and **Clean Architecture**. | ADR-014 | State Management | TanStack Query (server) + Zustand (client) | | ADR-015 | Deployment | Docker Compose + Gitea CI/CD | | ADR-016 | Security | JWT + CASL RBAC + Helmet.js + ClamAV | -| ADR-017 | Ollama Migration | Local AI + n8n for legacy data import | +| 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-019 | Hybrid Identifier Strategy | INT PK (internal) + UUIDv7 BINARY(16) (public API) | +--- + +## 🆔 Identifier Strategy (ADR-019) — CRITICAL + +### Rule Summary + +- **Internal / DB FK:** `INT AUTO_INCREMENT` (Primary Key) — never exposed +- **Public API / URL:** `UUIDv7` stored as `BINARY(16)` +- Read `05-07-hybrid-uuid-implementation-plan.md` before any UUID-related work. + +### ⚠️ Phase 5.4 — Pending Fix (as of 2026-03-20) + +These files still call `parseInt()` on UUID values — **fix when touching these files**: + +- `frontend/components/correspondences/form.tsx` +- `frontend/components/user-dialog.tsx` +- `frontend/components/numbering/template-tester.tsx` +- `frontend/app/(dashboard)/rfas/page.tsx` + +### UUID Serialization Behavior (TransformInterceptor) + +`TransformInterceptor` uses `instanceToPlain()` — `@Exclude()` and `@Expose()` decorators are active on all responses. +| Entity Type | Behavior | +| ------------------ | ---------------------------------------------------------------------- | +| All entities | INT `id` has `@Exclude()` → **never appears in API response** | +| Project / Contract | `uuid` has `@Expose({ name: 'id' })` → response has `id` = UUID string | +| Other entities | Separate `uuid` field → response has `uuid`, no `id` | + +### UUID Patterns (Backend Controller) + +```typescript +// ✅ ถูกต้อง — UUID param +@Get(':uuid') +findOne(@Param('uuid', ParseUuidPipe) uuid: string) { ... } +// ❌ ผิด — INT param บน public route +@Get(':id') +findOne(@Param('id', ParseIntPipe) id: number) { ... } +``` + +### UUID Patterns (Backend DTO — FK References) + +```typescript +// ✅ ถูกต้อง — รับ UUID จาก frontend, resolve เป็น INT ใน controller +@IsUUID() +projectUuid!: string; +@IsOptional() +@IsInt() +projectId?: number; // resolved internally, never from client +// ❌ ผิด — frontend ไม่มี INT id (ถูก @Exclude() แล้ว) +@IsInt() +projectId!: number; +``` + +### UUID Patterns (Frontend — Select/Form) + +```typescript +// ✅ ถูกต้อง +onValueChange={(v) => setValue("projectUuid", v)} +// ❌ ผิด — parseInt บน UUID string ได้ค่าผิดเสมอ +onValueChange={(v) => setValue("projectId", parseInt(v))} +``` + +--- + +## 🛡️ Security Rules (Non-Negotiable) + +1. **Idempotency:** All critical `POST` / `PUT` / `PATCH` MUST validate `Idempotency-Key` header. +2. **Two-Phase File Upload:** Upload → Temp Storage → Commit → Permanent Storage. +3. **Race Conditions:** Redis Redlock + TypeORM `@VersionColumn` for Document Numbering. +4. **Validation:** Zod (frontend) + class-validator (backend DTO) — no unvalidated inputs. +5. **Password:** bcrypt 12 salt rounds. Min 8 chars, upper + lower + number + special. Rotate every 90 days. +6. **Rate Limiting:** `ThrottlerGuard` on all auth endpoints. +7. **File Upload Policy:** Whitelist: PDF, DWG, DOCX, XLSX, ZIP. Max: 50MB. ClamAV scans every file. +8. **AI Isolation (ADR-018):** + +- Ollama runs on Admin Desktop ONLY — NEVER on QNAP/production. +- AI: NO direct DB access, NO write to `/uploads`. +- AI input/output: **JSON only**. +- All AI-triggered writes → DMS REST API → DB. + +--- + +## 📐 TypeScript Rules + +- **Strict Mode** — all strict checks enforced. +- **ZERO `any` types** — use proper types, generics, or `unknown` + type narrowing. +- **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. +- Prefer `readonly` for immutable properties. +- Use `satisfies` operator for type-checked object literals. +- Use `RequestWithUser` typed interface in controllers — NEVER `req: any`. + +--- + +## 📝 Naming Conventions + +| Target | Convention | Example | +| ------------------- | ----------- | --------------------------- | +| Files | kebab-case | `user-service.ts` | +| Classes | PascalCase | `UserService` | +| Variables | camelCase | `firstName` | +| DB Properties | snake_case | `user_id`, `created_at` | +| Boolean vars | verb + noun | `isActive`, `hasPermission` | +| **Code** | English | All identifiers in English | +| **Comments / Docs** | Thai | ความคิดเห็นและเอกสารใช้ภาษาไทย | + +--- + +## 🏷️ Domain Terminology (Glossary) + +อ้างอิง `specs/00-Overview/00-02-glossary.md` เสมอ — ใช้ term ผิดจะทำให้ spec ไม่ตรง +| ✅ ใช้ (Correct) | ❌ ห้ามใช้ (Wrong) | +| ------------------ | ------------------------------------------- | +| Correspondence | Letter, Communication, Document (generic) | +| RFA | Approval Request, Submit for Approval | +| Transmittal | Delivery Note, Cover Letter | +| Circulation | Distribution, Routing | +| Shop Drawing | Construction Drawing (generic) | +| Contract Drawing | Design Drawing, Blueprint | +| Workflow Engine | Approval Flow, Process Engine | +| Document Numbering | Document ID, Auto Number | +| RBAC | Permission System, Access Control (generic) | + +--- + +## 🏛️ Architecture Rules + +### Backend (NestJS) — 18 Modules + +``` +auth | user | project | correspondence | rfa | drawing | workflow-engine | +document-numbering | transmittal | circulation | search | dashboard | +notification | monitoring | master | organizations | json-schema | config +``` + +- **Modular Architecture** — one module per domain. +- Business logic in **Services only** — Controllers are thin (validate → delegate). +- **NO SQL Triggers** — all logic in NestJS services. +- Every protected route: `@UseGuards(JwtAuthGuard, CaslGuard)` + `@Roles()`. +- `@Roles()` must align with CASL matrix in `seed-permissions.sql`. +- All file operations through `StorageService` — never directly. +- Notifications via BullMQ — NEVER send inline. +- NEVER use `req: any` — use `RequestWithUser` interface. + +### Frontend (Next.js) — 15 Component Groups + +``` +ui/ | layout/ | common/ | correspondences/ | rfas/ | drawings/ | +workflows/ | numbering/ | dashboard/ | search/ | transmittals/ | +circulations/ | admin/ | auth/ | notifications/ +``` + +- **App Router** — Server Components by default; Client Components only when necessary. +- All API calls through `proxy.ts` — NEVER call backend directly from client. +- **TanStack Query** — server state (fetching, caching, invalidation). +- **Zustand** — UI/client state (modals, sidebar, selections). +- **React Hook Form + Zod** — all forms; no uncontrolled inputs. +- No `console.log`, no `any`, no `parseInt()` on UUIDs — remove before commit. + +--- + +## 🗄️ Database Rules (ADR-009) + +- **NO TypeORM migrations** — schema changes go directly into the SQL schema files. +- **NEVER invent table names or columns** — only use what is in `schema-02-tables.sql`. +- Always verify against schema before writing any query or TypeORM entity. +- Check `03-01-data-dictionary.md` for field meanings and business rules. + +--- + +## 🤖 AI / n8n Rules (ADR-017 & ADR-018) + +- Ollama models: `llama3.2:3b` (fast tasks) / `mistral:7b` (complex extraction). +- n8n orchestrates all migration workflows on QNAP (`n8n-workflow-lcbp3.json`). +- AI output must be validated JSON — schema-validate before any DB write via API. +- Migration Bot token: IP Whitelist + 7-day Expiry + REVOKE immediately after migration. +- **DO NOT** start Legacy Migration without Go/No-Go Gate #1 approval. +- Migration scope: ~20,000 documents in 3 Tiers — Tier 1 first (2,000 critical docs). + +--- + +## 🧪 Testing Standards + +### Coverage Goals + +| Layer | Target | Priority Areas | +| ---------------- | ------ | --------------------------------------------- | +| Backend overall | 70%+ | — | +| Business Logic | 80%+ | Services, Workflow Engine, Document Numbering | +| Controllers | 70%+ | Happy path + error cases | +| Utilities | 90%+ | Helpers, Transformers, Guards | +| Frontend overall | 60%+ | Components, Hooks, API clients | + +### Health Check Endpoints + +``` +GET /health — Overall system health +GET /health/db — MariaDB connectivity +GET /health/redis — Redis connectivity +``` + +### Test Commands + +```bash +# Backend +pnpm --filter backend test # Unit tests +pnpm --filter backend test:e2e # E2E tests +pnpm --filter backend test:cov # Coverage report +# Frontend +pnpm --filter frontend test # Unit tests (Vitest) +pnpm --filter frontend test:e2e # E2E tests (Playwright) +``` + +### Feature Testing Checklist (ก่อน PR) + +#### Backend + +- [ ] Unit test: Service business logic (min 80% coverage) +- [ ] Integration test: Controller + Guard + DTO validation +- [ ] E2E test: Happy path + 2 edge cases จาก `01-06-edge-cases.md` +- [ ] Security test: Unauthorized access, SQL injection, XSS + +#### Frontend + +- [ ] Component test: Render + interaction (Vitest + Testing Library) +- [ ] Form test: Validation success/failure cases +- [ ] E2E test: Critical user journey (Playwright) +- [ ] Accessibility: Keyboard nav + screen reader basics + +#### Before Commit + +- [ ] `pnpm lint` → 0 errors +- [ ] `pnpm test:cov` → ผ่านเกณฑ์ +- [ ] `pnpm build` → 0 warnings +- [ ] UUID pattern ตรวจสอบแล้ว (ไม่มี parseInt บน UUID) + +--- + +## 🌿 Git Conventions + +### Commit Message Format + +``` +(): +[optional body] +[optional footer: Refs #issue] +``` + +| Type | ใช้เมื่อ | +| ---------- | ------------------------------------- | +| `feat` | เพิ่มฟีเจอร์ใหม่ | +| `fix` | แก้ bug | +| `refactor` | ปรับโครงสร้างโค้ด ไม่เปลี่ยน behavior | +| `docs` | แก้ไขเอกสาร | +| `test` | เพิ่ม/แก้ test | +| `chore` | งาน infra, config, dependency updates | +| `style` | Formatting, linting (ไม่เปลี่ยน logic) | +| `spec` | แก้ไข specs/ documents | +| `adr` | เพิ่ม/แก้ไข Architecture Decision Record | + +**ตัวอย่าง:** + +``` +feat(correspondence): add create correspondence endpoint +fix(uuid): remove parseInt on projectId in rfas/page.tsx +spec(requirements): update edge cases for drawing workflow +adr(019): add UUID serialization behavior notes +``` + +### Branch Naming + +``` +feature/ # ฟีเจอร์ใหม่ +fix/- # แก้ bug +spec// # แก้ specs +adr/- # ADR ใหม่/แก้ไข +refactor/ # Refactor +``` + +**ตัวอย่าง:** + +``` +feature/correspondence-cc-support +fix/23-uuid-parseInt-rfas-page +spec/requirements/update-correspondence-workflow +adr/019-uuid-serialization-behavior +``` + +--- + +## 🌊 Windsurf Workflows + +`.windsurf/workflows/` — ใช้สำหรับ repeatable / complex tasks เช่น: + +- UUID migration fixes (Phase 5.4) +- Spec review & gap analysis +- Security audit checklist +- Release gate verification + เมื่อ task ซับซ้อนและทำซ้ำได้ ให้ดู `.windsurf/workflows/` ก่อนเขียน code ใหม่ + ---## 🚀 Deployment Rules (ADR-015) +- Docker Compose on **QNAP Container Station** (production). +- **NO `.env` files in production** — secrets in `docker-compose.yml` environment section directly. +- Blue-Green deployment strategy. +- CI/CD via **Gitea** (QNAP) + **Gitea Runner / act_runner** (ASUSTOR). +- **DO NOT deploy** without completing all Release Gates per `04-08-release-management-policy.md`. + +--- + ## 🚫 Forbidden Actions -- DO NOT use SQL Triggers (Business logic must be in NestJS services). -- DO NOT use `.env` files for production deployment — QNAP Container Station requires secrets directly in `docker-compose.yml` environment section. -- DO NOT run database migrations — modify the schema SQL file directly (ADR-009). -- DO NOT invent table names or columns — use ONLY what is defined in the schema SQL file. -- DO NOT generate code that violates OWASP Top 10 security practices. -- DO NOT use `any` TypeScript type anywhere. -- DO NOT let AI (Ollama) access production database directly — all writes go through DMS API (ADR-018). -- DO NOT bypass StorageService for file operations — all file moves must go through the API. -- DO NOT deploy to Production without completing Release Gates — see `04-08-release-management-policy.md`. -- DO NOT start Legacy Migration without Go/No-Go Gate #1 approval — see `03-06-migration-business-scope.md`. -- DO NOT modify Migration Bot Token scope — IP Whitelist + 7-day Expiry + REVOKE after migration. -- DO NOT close UAT sign-off without all Acceptance Criteria ✅ — see `01-05-acceptance-criteria.md`. +| ❌ Forbidden | ✅ Correct Approach | +| ----------------------------------------------- | --------------------------------------------------------- | +| SQL Triggers for business logic | NestJS Service methods | +| `.env` files in production | `docker-compose.yml` environment section | +| TypeORM migration files | Edit schema SQL directly (ADR-009) | +| Inventing table/column names | Verify against `schema-02-tables.sql` | +| `any` TypeScript type | Proper types / generics / `unknown` + narrowing | +| `console.log` in committed code | NestJS Logger (backend) / remove (frontend) | +| `req: any` in controllers | `RequestWithUser` typed interface | +| `parseInt()` on UUID values | Use UUID string directly (ADR-019) | +| Exposing INT PK in API responses or URLs | UUIDv7 (ADR-019) | +| AI accessing DB or storage directly | AI → DMS API → DB (ADR-018) | +| Direct file operations bypassing StorageService | `StorageService` for all file moves | +| Inline email/notification sending | BullMQ queue job | +| 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 | +| 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` | +| Modifying Migration Bot token scope | IP Whitelist + 7-day expiry only | +| OWASP Top 10 violations | Security checklist before every PR | + +--- + +## 🔄 Development Flow (Tiered) + +เลือก flow ตามประเภทงาน — ไม่ต้องอ่าน spec ทั้งหมดทุกครั้ง: + +### 🔴 Critical Work — DB / API / Security / Workflow Engine + +**MUST ทำครบทุก step:** + +1. **Glossary check** — ตรวจคำศัพท์ domain ใน `00-02-glossary.md` +2. **Read the spec** — เลือกจาก Key Spec Files table +3. **Check schema** — verify table/column ใน `schema-02-tables.sql` +4. **Check data dictionary** — ยืนยัน field meanings + business rules +5. **Scan edge cases** — `01-06-edge-cases-and-rules.md` (37 rules) +6. **Check ADRs** — ตรวจแนวทางตรงกับ decisions (esp. ADR-009, ADR-018, ADR-019) +7. **Write code** — TypeScript strict, no `any`, no `console.log`, UUID ไม่ expose + +### 🟡 Normal Work — UI / Feature / Integration + +- ดู existing patterns ในโค้ดที่มีอยู่ +- ตรวจ spec เฉพาะ module ที่เกี่ยวข้อง +- ไม่ต้องอ่าน spec ทั้งหมด + +### 🟢 Quick Fix — Bug Fix / Typo / Style + +- แก้โดยตรง +- เพิ่ม minimal test ถ้าแก้ logic +- ตรวจ forbidden patterns ก่อน commit + +--- + +## 🎯 Windsurf Context-Aware Triggers + +เมื่อผู้ใช้ถามเกี่ยวกับ... ให้ตรวจสอบไฟล์เหล่านี้ก่อนตอบ +| คำถาม/คำสั่ง | ไฟล์ที่ต้องตรวจสอบก่อน | คำตอบที่คาดหวัง | +| -------------------- | ------------------------------------------------------- | ------------------------------------------------- | +| "สร้าง 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 | +| "เพิ่ม 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 | +| "สร้าง migration" | `ADR-009`, `03-06-migration-business-scope.md` | แก้ SQL schema โดยตรง + n8n workflow | +| "ตรวจสอบ 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 | +| "เพิ่ม test" | `05-04-testing-strategy.md` | Coverage goals + test patterns | + +--- + +## 🧩 Code Snippets (Windsurf Auto-Suggest) + +### Backend DTO Pattern + +```typescript +// [dto-new] → สร้าง DTO ใหม่พร้อม validator +@IsUUID() +@ApiProperty({ description: 'Project UUID (public)' }) +projectUuid!: string; + + +@IsOptional() +@IsInt() +@ApiProperty({ required: false, description: 'Internal project ID' }) +projectId?: number; // resolved internally, never from client +``` + +### Frontend Form Pattern + +```typescript +// [form-rhf-zod] → สร้าง form schema + hook +const schema = z.object({ + projectUuid: z.string().uuid('รหัสโครงการไม่ถูกต้อง'), + title: z.string().min(3, 'กรุณากรอกหัวข้ออย่างน้อย 3 ตัวอักษร'), +}); +const form = useForm({ resolver: zodResolver(schema) }); +``` + +### UUID Safe Pattern + +```typescript +// [uuid-safe] → ตรวจสอบ UUID ก่อนใช้ +const safeUuid = (val: string | number): string => { + if (typeof val === 'number') { + Logger.warn(`UUID received as number: ${val}`); + return String(val); // หรือ throw error ตาม policy + } + return val; +}; +``` + +### Backend Error Handling Pattern + +```typescript +// [backend-error] → Error handling มาตรฐาน +if (!entity) { + this.logger.warn(`Entity not found: ${uuid}`, 'Service.findOne'); + throw new NotFoundException(`Resource with UUID ${uuid} not found`); +} +``` + +### Frontend Query Pattern + +```typescript +// [frontend-query] → TanStack Query มาตรฐาน +const { data, error, isLoading } = useQuery({ + queryKey: ['correspondence', uuid], + queryFn: () => api.get(`/correspondences/${uuid}`), + onError: (err) => { + toast.error('ไม่สามารถโหลดข้อมูลได้'); + logger.error('Failed to load correspondence', { uuid, err }); + }, +}); +``` + +### Redis Cache Pattern + +```typescript +// [redis-cache] → Cache-Aside Pattern +const cacheKey = `correspondence:${uuid}`; +const cached = await this.cacheManager.get(cacheKey); +if (cached) return cached; +const entity = await this.repo.findOneBy({ uuid }); +if (entity) { + await this.cacheManager.set(cacheKey, entity, 300); // 5 นาที +} +return entity; +``` + +--- + +## 🚨 Error Handling & Logging Standards + +### Backend (NestJS) + +```typescript +// ✅ ถูกต้อง — ใช้ Logger + HttpException +if (!entity) { + this.logger.warn(`Entity not found: ${uuid}`, 'Service.findOne'); + throw new NotFoundException(`Resource with UUID ${uuid} not found`); +} +// ❌ ผิด — console.log หรือ return null +console.log('not found'); // ❌ +return null; // ❌ ทำให้ caller ต้องเช็คเอง +``` + +### Frontend (Next.js) + +```typescript +// ✅ ถูกต้อง — ใช้ TanStack Query error handling +const { data, error, isLoading } = useQuery({ + queryKey: ['correspondence', uuid], + queryFn: () => api.get(`/correspondences/${uuid}`), + onError: (err) => { + // แสดง toast + log + fallback UI + toast.error('ไม่สามารถโหลดข้อมูลได้'); + logger.error('Failed to load correspondence', { uuid, err }); + }, +}); +``` + +### Error Response Standard (Backend) + +```json +{ + "statusCode": 404, + "message": "Resource not found", + "error": "Not Found", + "timestamp": "2026-03-21T10:30:00.000Z", + "path": "/api/correspondences/:uuid", + "traceId": "req-abc123" // สำหรับติดตามใน Loki +} +``` + +--- + +## 🌐 Thai Language & i18n Guidelines + +### Code Comments & Docs + +- ✅ Comments: เขียนเป็นภาษาไทย (เพื่อความเข้าใจทีม) +- ✅ JSDoc: ใช้ภาษาไทยอธิบาย business logic +- ✅ Error messages: เก็บเป็น key ใน i18n file, ไม่ hardcode + +### i18n Structure (frontend) + +``` +locales/ +├── th/ +│ ├── common.json # ข้อความทั่วไป +│ ├── errors.json # Error messages +│ ├── forms.json # Form labels & validation +│ └── modules/ +│ ├── correspondence.json +│ └── rfa.json +└── en/ # Reserved for future +``` + +### Validation Messages (Zod) + +```typescript +// ✅ ถูกต้อง — ใช้ key อ้างอิง +z.string().min(3, { message: 'errors:min_length_3' }); +// แล้ว resolve ใน frontend ผ่าน i18n hook + +// ❌ ผิด — hardcode ภาษาไทยใน schema +z.string().min(3, 'กรุณากรอกอย่างน้อย 3 ตัวอักษร'); // ทำให้ทดสอบยาก +``` + +--- + +## ⚡ Performance & Caching Patterns + +### Redis Cache Patterns (ADR-006) + +```typescript +// ✅ Cache-Aside Pattern สำหรับข้อมูลอ่านบ่อย +async findOne(uuid: string) { + const cacheKey = `correspondence:${uuid}`; + const cached = await this.cacheManager.get(cacheKey); + if (cached) return cached; + + const entity = await this.repo.findOneBy({ uuid }); + if (entity) { + await this.cacheManager.set(cacheKey, entity, 300); // 5 นาที + } + return entity; +} + + +// ✅ Cache Invalidation เมื่อแก้ไขข้อมูล +async update(uuid: string, dto: UpdateDto) { + // ... update logic + await this.cacheManager.del(`correspondence:${uuid}`); + await this.cacheManager.del('correspondences:list'); // ลบ list cache +} +``` + +### Query Optimization Checklist + +- [ ] ใช้ `select: [...]` เพื่อโหลดเฉพาะฟิลด์ที่ต้องการ +- [ ] ใช้ `relations: [...]` แทน JOIN ซับซ้อนเมื่อไม่จำเป็น +- [ ] ตรวจสอบ N+1 problem ด้วย `@UseInterceptors(LoggingInterceptor)` +- [ ] ใช้ `take/skip` สำหรับ pagination เสมอ + +--- + +## 💬 Prompt Templates สำหรับถาม Windsurf + +### เมื่อต้องการสร้างฟีเจอร์ใหม่ + +``` +[NEW FEATURE] +Module: +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: +Error: +Steps taken: <สิ่งที่ลองแก้ไขแล้ว> +Request: วิเคราะห์ตาม spec + แนะนำวิธีแก้ที่สอดคล้องกับ ADRs +``` + +### เมื่อต้องการ review code + +``` +[CODE REVIEW] +File: +Focus: +Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table +``` + +--- + +## 📦 Infrastructure Quick Reference + +### QNAP NAS (Container Station) — Production + +| Service | Notes | +| -------------------- | ------------------------------- | +| DMS Frontend | Next.js 16.2.0 + React 19.2.4 | +| DMS Backend | NestJS 11 + Express v5 | +| MariaDB 11.8 | Schema v1.8.0 | +| Redis 7.2 | BullMQ + Cache | +| Elasticsearch 9.3.4 | Full-text search | +| n8n + n8n-db | Automation & Migration | +| Nginx Proxy Manager | Reverse proxy + SSL termination | +| Tika | Document parsing | +| Gitea | Source code management | +| RocketChat | Team communication | +| cAdvisor + exporters | Container metrics | + +### ASUSTOR NAS (Portainer) — Monitoring Hub + +| Service | Notes | +| --------------- | ------------------------------- | +| Grafana | Dashboards + KPI visualization | +| Prometheus | Metrics (scrapes QNAP) | +| Loki + Promtail | Log aggregation | +| Uptime-Kuma | Service availability monitoring | +| Gitea Runner | CI/CD (act_runner) | +| Docker Registry | Private image registry | +| Cloudflared | External tunnel | +| cAdvisor | Container metrics | + +### Admin Desktop — AI Processing ONLY + +| Spec | Value | +| ------- | ------------------------------------------------ | +| CPU | Intel i9-9900K | +| RAM | 32GB | +| GPU | RTX 2060 SUPER 8GB | +| Service | Ollama (`llama3.2:3b` / `mistral:7b`) | +| Rule | **NEVER on QNAP** — Admin Desktop ONLY (ADR-018) | + +## **Network:** Internal VLAN — QNAP metrics scraped by ASUSTOR Prometheus + +## 📜 .windsurfrules Change Log + +| Version | Date | Changes | Updated By | +| ------- | ---------- | --------------------------------------------------------------------------------------------------------------------- | -------------- | +| 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.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.7.2 | 2026-03-15 | + AI Boundary rules (ADR-018) | Gemini Pro | + +### วิธีอัพเดทไฟล์นี้ + +1. แก้ไขในส่วนที่เกี่ยวข้อง +2. อัพเดทตาราง Change Log ด้านบน +3. เพิ่ม version number ใน header +4. Commit ด้วย message: `spec(windsurfrules): bump to v1.8.3 - ` + +--- + +## ✅ Quick Reference Checklist (ก่อน Commit ทุกครั้ง) + +- [ ] UUID pattern ตรวจสอบแล้ว (ไม่มี parseInt บน UUID) +- [ ] No `any` types ใน TypeScript +- [ ] No `console.log` ในโค้ดที่ commit +- [ ] Comments เป็นภาษาไทย +- [ ] Code identifiers เป็นภาษาอังกฤษ +- [ ] Schema เปลี่ยนแก้ SQL โดยตรง (ไม่ใช่ migration) +- [ ] Test coverage ผ่านเกณฑ์ (Backend 70%+, Business Logic 80%+) +- [ ] ADR ที่เกี่ยวข้องตรวจสอบแล้ว (esp. ADR-009, ADR-018, ADR-019) +- [ ] Glossary terms ใช้ถูกต้อง +- [ ] Error handling ครบถ้วน (Logger + HttpException) +- [ ] i18n keys ใช้แทน hardcode text +- [ ] Cache invalidation มีเมื่อแก้ไขข้อมูล +- [ ] Security checklist ผ่าน (OWASP Top 10) diff --git a/docs/Pompt.md b/docs/Pompt.md new file mode 100644 index 0000000..e03755f --- /dev/null +++ b/docs/Pompt.md @@ -0,0 +1,783 @@ +# 🚀 Web Developer Prompt Library (Comprehensive Edition) +คลังคำสั่ง AI สำหรับการพัฒนา Software ตั้งแต่เริ่มต้นวางกลยุทธ์จนถึงการส่งมอบงาน โดยแบ่งตามบทบาทหน้าที่ในทีมพัฒนา + +--- + +## 🏗️ 1. กลุ่มการวางแผนและวิเคราะห์ (Planning & Analysis) +*เหมาะสำหรับช่วงเริ่มโปรเจกต์ หรือต้องการสรุปภาพรวมระบบก่อนลงมือทำ* + +### 🟡 Product Owner (PO) / Strategist +**หน้าที่:** วิเคราะห์ Business Value, กลุ่มเป้าหมาย และกำหนด MVP +> **Prompt:** > ให้คุณรับบทเป็น Product Owner สำหรับโปรเจกต์ [ชื่อโปรเจกต์] ช่วยวิเคราะห์เป้าหมายธุรกิจ กลุ่มผู้ใช้หลัก ปัญหาที่ผู้ใช้เจอ และเสนอฟีเจอร์สำหรับ MVP สรุปออกมาเป็น: +> 1. เป้าหมายระบบ +> 2. กลุ่มผู้ใช้งาน +> 3. User pain points +> 4. Feature list +> 5. MVP scope +> 6. Future scope + +### 🔵 Business Analyst (BA) +**หน้าที่:** แตก Requirement ให้ละเอียด ทั้งทางเทคนิคและทางธุรกิจ +> **Prompt:** +> ให้คุณรับบทเป็น Business Analyst ช่วยวิเคราะห์ requirement ของ [ระบบ] แยกเป็น: +> - Functional / Non-functional requirements +> - User roles & Use cases +> - Business rules & Edge cases +> - Assumptions / Open questions +> Output: รายการฟีเจอร์, Roadmap และเอกสาร Requirement + +### ⚪ AI Project Manager (Orchestrator) +**หน้าที่:** วางแผนการทำงาน แบ่งเฟส และจัดการ Dependency +> **Prompt:** +> ให้คุณรับบทเป็น AI Project Manager ช่วยแบ่งการพัฒนาเว็บไซต์ [ชื่อโปรเจกต์] เป็น phase โดยสรุป: งานแต่ละ phase, ลำดับก่อนหลัง, dependency, ผลลัพธ์ที่ต้องได้ และ Definition of Done + +--- + +## 🎨 2. กลุ่มการออกแบบประสบการณ์ (UX/UI & IA) +*เน้นการคิดจากมุมมองผู้ใช้งานและการจัดระเบียบข้อมูล* + +### 🟣 UX Researcher +**หน้าที่:** วิเคราะห์พฤติกรรมผู้ใช้และสร้างเส้นทางการใช้งาน +> **Prompt:** +> ให้คุณรับบทเป็น UX Researcher วิเคราะห์ประสบการณ์ผู้ใช้สำหรับ [ประเภทเว็บ] ที่มีกลุ่มเป้าหมายเป็น [กลุ่มผู้ใช้] ช่วยสร้าง: User persona 3 แบบ, User journey, Pain points และ UX opportunities + +### 🟢 Information Architect (IA) +**หน้าที่:** วางโครงสร้างเมนูและการจัดหมวดหมู่เนื้อหา +> **Prompt:** +> ให้คุณรับบทเป็น Information Architect ช่วยออกแบบ sitemap ของเว็บไซต์ [ประเภทเว็บ] โดยมีฟีเจอร์หลักคือ [รายการ] ส่งออกเป็น: Primary/Secondary navigation, Sitemap และ Content grouping + +### 🔴 UX/UI Designer +**หน้าที่:** วาง Layout, UI Components และ Design Guideline +> **Prompt:** +> ให้คุณรับบทเป็น UX/UI Designer ออกแบบหน้า [ชื่อหน้า] สำหรับ [ประเภทเว็บ] เพื่อเป้าหมาย [เช่น เพิ่มยอดสมัคร] ช่วยเสนอ: โครงสร้างหน้า, ลำดับ section, UI components, แนวทางสี/ฟอนต์ และ Wireframe แบบ text layout + +--- + +## 💻 3. กลุ่มการพัฒนา (Development & Architecture) +*เน้นการเขียนโค้ดและการวางโครงสร้างระบบ* + +### 🛠️ Solution Architect +**หน้าที่:** ออกแบบโครงสร้างระบบภาพรวม (High-level) เน้น Scalability +> **Prompt:** +> ให้คุณรับบทเป็น Solution Architect ช่วยออกแบบ architecture สำหรับระบบ [ประเภทระบบ] ที่รองรับผู้ใช้ [จำนวน] ต้องคำนึงถึง: Scalability, Security, Data flow และ Recommended stack + +### 🚀 Full Stack Developer +**หน้าที่:** ออกแบบและเขียนโค้ดทั้งหน้าบ้านและหลังบ้าน +> **Prompt:** +> ให้คุณรับบทเป็น Senior Full Stack Developer ช่วยออกแบบและพัฒนาระบบ [ชื่อระบบ] ด้วย Tech stack: [ระบุ] โปรดส่งออกเป็น: System architecture, Folder structure, Database schema, API design และขั้นตอนการเชื่อมต่อ + +### 🔹 Frontend / Backend Developer +**หน้าที่:** เจาะลึกการเขียนโค้ดเฉพาะส่วน (Logic, API, UI) +> **Prompt (Frontend):** สร้างหน้า [ชื่อหน้า] ด้วย [เช่น React + Tailwind] เงื่อนไข: Responsive, แยก component, รองรับ Loading/Error state +> **Prompt (Backend):** ออกแบบ REST API สำหรับ [ระบบ] ด้วย [เช่น Node.js] ต้องมี: Validation, Error handling, Auth และ Database schema + +--- + +## 🛡️ 4. กลุ่มความเสถียรและความปลอดภัย (Infra, Security & QA) +*การเตรียมระบบให้พร้อมใช้งานจริงและทนทานต่อข้อผิดพลาด* + +### 🗄️ Database Designer +**หน้าที่:** ออกแบบ Schema, ความสัมพันธ์ข้อมูล และการทำ Index +> **Prompt:** +> ให้คุณรับบทเป็น Database Designer ออกแบบฐานข้อมูลสำหรับ [ชื่อระบบ] ระบุ: ตารางทั้งหมด, ฟิลด์/ชนิดข้อมูล, Primary/Foreign key, ความสัมพันธ์ และตัวอย่าง SQL schema + +### ☁️ DevOps Engineer +**หน้าที่:** การ Deployment, Docker และ CI/CD +> **Prompt:** +> ให้คุณรับบทเป็น DevOps Engineer ช่วยวางแผน deployment สำหรับเว็บ [ประเภทเว็บ] ที่ใช้ [stack] ต้องการ: Docker setup, CI/CD pipeline, Nginx config และ Backup strategy + +### 🔍 QA / Security Engineer +**หน้าที่:** ทดสอบระบบและตรวจสอบช่องโหว่ความปลอดภัย +> **Prompt (QA):** สร้าง Test cases สำหรับ [ฟีเจอร์] ครอบคลุม: Happy path, Edge cases, และ Error cases ในรูปแบบตาราง +> **Prompt (Security):** ตรวจสอบความเสี่ยงของ [ระบบ] วิเคราะห์: Auth/AuthZ risks, Input validation และ Common OWASP risks + +--- + +## 📝 5. กลุ่มการปรับปรุงและเอกสาร (Optimization & Review) +*การทำความสะอาดโค้ดและการส่งต่องาน* + +### 🧐 Senior Code Reviewer +**หน้าที่:** ตรวจสอบคุณภาพโค้ดและแนะนำแนวทาง Refactor +> **Prompt:** +> ให้คุณรับบทเป็น Senior Code Reviewer ช่วย review โค้ดนี้ในมุม: Readability, Maintainability, Performance และ Security พร้อมส่งเวอร์ชัน Refactor + +### 📖 Technical Writer +**หน้าที่:** เขียนคู่มือการติดตั้งและการใช้งาน API +> **Prompt:** +> ให้คุณรับบทเป็น Technical Writer ช่วยเขียนเอกสารสำหรับโปรเจกต์นี้: Project overview, Installation steps, Env variables และ API summary ให้อ่านง่ายสำหรับ Dev ใหม่ + +### ⚡ Performance / SEO Specialist +**หน้าที่:** ทำให้เว็บเร็วขึ้นและติดอันดับการค้นหา +> **Prompt (Performance):** เสนอแนวทางเพิ่มความเร็วเว็บ [ประเภทเว็บ] วิเคราะห์ Caching, Asset optimization และ Core Web Vitals +> **Prompt (SEO):** วางกลยุทธ์ SEO: Keyword clusters, Meta description และ Heading structure + +--- + +ดีเลย แบบนี้จะได้ “ของที่ใช้จริง” 🔥 +ผมจัดให้เป็น **Ultimate Prompt Library – AI + Automation (DMS Edition)** +โฟกัสตรง use case ของคุณโดยตรง: + +``` +OCR +Document Management System (DMS) +RAG +n8n automation +AI tagging +workflow เอกสารก่อสร้าง +``` + +ทั้งหมดเป็น **⭐ Production Prompt Templates** (ไม่ใช่ prompt สั้น ๆ) + +--- + +# 🚀 Ultimate Prompt Library – AI + Automation (DMS Edition) + +ใช้สำหรับระบบ + +``` +Document Control +Drawing Management +RFA / Transmittal +AI Search / Tagging +``` + +--- + +# 🧭 1. Document Classification (AI Tagging) + +## ⭐ Prompt: Intelligent Document Classification + +**Role** +Senior AI Document Analyst + +--- + +**Context** + +เรามีเอกสารจากโครงการก่อสร้าง เช่น + +``` +Drawings +RFAs +Transmittals +Contracts +Reports +``` + +Input คือ text ที่ extract จาก OCR: + +``` +[document_text] +``` + +--- + +**Objective** + +จำแนกประเภทเอกสาร + สร้าง metadata + +--- + +**Instructions** + +1 วิเคราะห์เนื้อหา +2 ระบุ document type +3 extract key metadata +4 สร้าง tags + +--- + +**Constraints** + +* ต้อง robust ต่อ OCR error +* รองรับภาษาไทย + อังกฤษ +* หลีกเลี่ยง hallucination + +--- + +**Output Format (JSON)** + +```json +{ + "document_type": "", + "confidence": 0.0, + "title": "", + "document_number": "", + "revision": "", + "date": "", + "discipline": "", + "tags": [], + "entities": { + "project": "", + "company": "", + "person": "" + } +} +``` + +--- + +**Quality Criteria** + +* ไม่เดาเมื่อข้อมูลไม่ชัด +* ใช้ confidence ต่ำแทน + +--- + +# 📄 2. Drawing Metadata Extraction + +## ⭐ Prompt: Drawing Parser + +**Role** +Engineering Document Specialist + +--- + +**Context** + +Drawing text จาก OCR: + +``` +[ocr_text] +``` + +--- + +**Objective** + +extract ข้อมูล drawing + +--- + +**Instructions** + +ค้นหา: + +``` +drawing number +revision +title +scale +discipline +status +``` + +--- + +**Output Format** + +```json +{ + "drawing_no": "", + "title": "", + "revision": "", + "scale": "", + "discipline": "", + "status": "", + "sheet_no": "" +} +``` + +--- + +# 📑 3. RFA Analysis + +## ⭐ Prompt: RFA Intelligence + +**Role** +Construction Document Analyst + +--- + +**Context** + +RFA content: + +``` +[rfa_text] +``` + +--- + +**Objective** + +สรุป + วิเคราะห์ + +--- + +**Instructions** + +1 สรุปเนื้อหา +2 ระบุคำถาม +3 ระบุความเสี่ยง +4 เชื่อมโยง drawing + +--- + +**Output Format** + +### Summary + +--- + +### Questions + +--- + +### Risk Analysis + +| Risk | Impact | + +--- + +### Related Drawings + +--- + +# 🔗 4. Document Linking (AI) + +## ⭐ Prompt: Smart Document Linking + +**Role** +AI Knowledge Graph Engineer + +--- + +**Context** + +เอกสารหลายรายการ: + +``` +[list_of_documents] +``` + +--- + +**Objective** + +หา relationship ระหว่างเอกสาร + +--- + +**Instructions** + +1 วิเคราะห์ document number +2 วิเคราะห์ reference +3 match ความสัมพันธ์ + +--- + +**Output Format** + +```json +[ + { + "source": "", + "target": "", + "relationship": "references / revision / related" + } +] +``` + +--- + +# 📚 5. RAG for DMS + +## ⭐ Prompt: DMS RAG Design + +**Role** +AI Retrieval Architect + +--- + +**Context** + +ระบบ DMS ต้องค้นหาเอกสารจาก + +``` +PDF +OCR text +metadata +``` + +--- + +**Objective** + +ออกแบบ RAG system + +--- + +**Instructions** + +ออกแบบ + +``` +chunking strategy +embedding +metadata filtering +retrieval ranking +``` + +--- + +**Output Format** + +### Architecture + +--- + +### Chunking Strategy + +--- + +### Retrieval Strategy + +--- + +### Query Flow + +--- + +# 🤖 6. AI Search Query Understanding + +## ⭐ Prompt: Query Understanding + +**Role** +Search AI Engineer + +--- + +**Context** + +User query: + +``` +[user_query] +``` + +--- + +**Objective** + +แปลง query เป็น structured search + +--- + +**Output Format** + +```json +{ + "intent": "", + "filters": { + "document_type": "", + "discipline": "", + "date_range": "" + }, + "keywords": [] +} +``` + +--- + +# 🔄 7. n8n Workflow Design + +## ⭐ Prompt: Automation Workflow + +**Role** +Workflow Automation Architect + +--- + +**Context** + +Flow: + +``` +Upload → OCR → AI → Save DB → Notify +``` + +--- + +**Objective** + +ออกแบบ workflow + +--- + +**Output Format** + +### Workflow Steps + +| Step | Tool | Description | + +--- + +### Error Handling + +--- + +### Retry Strategy + +--- + +# 🧾 8. OCR Pipeline Optimization + +## ⭐ Prompt: OCR Pipeline + +**Role** +Document AI Engineer + +--- + +**Objective** + +ปรับปรุง OCR accuracy + +--- + +**Instructions** + +วิเคราะห์ + +``` +image quality +language detection +post-processing +``` + +--- + +**Output Format** + +### Problems + +--- + +### Improvements + +--- + +# 🏗️ 9. DMS Database Design + +## ⭐ Prompt: DMS Schema + +**Role** +Senior Database Architect + +--- + +**Objective** + +ออกแบบ schema สำหรับ DMS + +--- + +**Output Format** + +### Tables + +| Table | Purpose | + +--- + +### Relationships + +--- + +### Index Strategy + +--- + +# ⚙️ 10. File Storage Strategy + +## ⭐ Prompt: File Storage Design + +**Role** +Cloud Storage Architect + +--- + +**Objective** + +ออกแบบ file storage + +--- + +**Output Format** + +### Storage Structure + +--- + +### Naming Convention + +--- + +### Access Control + +--- + +# 🛡️ 11. Document Security + +## ⭐ Prompt: DMS Security + +**Role** +Security Engineer + +--- + +**Objective** + +ป้องกัน document leakage + +--- + +**Output Format** + +### Risks + +--- + +### Controls + +--- + +# 📊 12. Audit & Version Control + +## ⭐ Prompt: Audit Log Design + +**Role** +System Architect + +--- + +**Objective** + +ออกแบบ audit log + +--- + +**Output Format** + +### Events + +--- + +### Schema + +--- + +# 📈 13. AI Tag Quality Improvement + +## ⭐ Prompt: Tag Optimization + +**Role** +ML Engineer + +--- + +**Objective** + +ปรับปรุง tag accuracy + +--- + +**Output Format** + +### Issues + +--- + +### Improvements + +--- + +# ⭐ Master Prompt (DMS AI System) + +**Prompt** + +ให้คุณรับบทเป็นทีม: + +``` +AI Architect +Document Controller +Backend Engineer +DevOps Engineer +``` + +ช่วยออกแบบระบบ: + +``` +AI-powered Document Management System +``` + +--- + +**Output** + +``` +1 Architecture +2 OCR pipeline +3 AI tagging +4 Database +5 Search (RAG) +6 Automation workflow +7 Security +``` + +--- + +# 📊 สรุป + +Library นี้คือ + +``` +AI + Automation Prompt Playbook +``` + +ใช้ได้กับ: + +``` +DMS +Construction projects +OCR pipeline +AI search +n8n automation +``` + +--- + +# 🔥 จุดเด่น (สำคัญมาก) + +นี่ไม่ใช่ prompt ธรรมดา แต่เป็น + +``` +Production Prompt System +``` + +ที่: + +* ลด hallucination +* บังคับ output เป็น JSON/structure +* ใช้ต่อใน code ได้ +* ใช้กับ pipeline จริงได้ + +--- + +💡 ถ้าคุณอยากไปสุดจริง ผมสามารถต่อยอดให้เป็น + +``` +AI DMS System Blueprint +``` + +ที่รวม + +* DB schema (ตรงกับ prompt) +* API design +* n8n workflow JSON +* Docker setup +* RAG implementation + +ซึ่งจะกลายเป็น **ระบบที่ build ได้จริงทั้งก้อน** 🚀 + diff --git a/frontend/app/(admin)/admin/access-control/users/page.tsx b/frontend/app/(admin)/admin/access-control/users/page.tsx index 86401bd..897d403 100644 --- a/frontend/app/(admin)/admin/access-control/users/page.tsx +++ b/frontend/app/(admin)/admin/access-control/users/page.tsx @@ -30,7 +30,7 @@ import { } from '@/components/ui/alert-dialog'; import { Skeleton } from '@/components/ui/skeleton'; -import { _Organization } from '@/types/organization'; +import { Organization } from '@/types/organization'; import { getApiErrorMessage } from '@/types/api-error'; export default function UsersPage() { @@ -49,7 +49,7 @@ export default function UsersPage() { const { data: organizations = [] } = useOrganizations(); const userList = Array.isArray(users) ? users : []; - const organizationList = Array.isArray(organizations) ? organizations : []; + const organizationList: Organization[] = Array.isArray(organizations) ? organizations : []; const deleteMutation = useDeleteUser(); const [dialogOpen, setDialogOpen] = useState(false); diff --git a/frontend/app/(admin)/admin/doc-control/numbering/new/page.tsx b/frontend/app/(admin)/admin/doc-control/numbering/new/page.tsx index a8d099f..f4c1f57 100644 --- a/frontend/app/(admin)/admin/doc-control/numbering/new/page.tsx +++ b/frontend/app/(admin)/admin/doc-control/numbering/new/page.tsx @@ -23,8 +23,17 @@ export default function NewTemplatePage() { const handleSave = async (data: Partial) => { try { - await numberingApi.saveTemplate(data); - router.push('/admin/numbering'); + // Correcting type mismatch by ensuring all required fields for SaveTemplateDto are present + await numberingApi.saveTemplate({ + projectId: data.projectId!, + correspondenceTypeId: data.correspondenceTypeId ?? null, + formatTemplate: data.formatTemplate!, + disciplineId: data.disciplineId, + description: data.description, + resetSequenceYearly: data.resetSequenceYearly, + isActive: data.isActive, + }); + router.push('/admin/doc-control/numbering'); } catch (_error) { toast.error('Failed to create template'); } diff --git a/frontend/app/(admin)/admin/doc-control/numbering/page.tsx b/frontend/app/(admin)/admin/doc-control/numbering/page.tsx index a31301b..d2f03df 100644 --- a/frontend/app/(admin)/admin/doc-control/numbering/page.tsx +++ b/frontend/app/(admin)/admin/doc-control/numbering/page.tsx @@ -6,7 +6,7 @@ import { Card } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Plus, Edit, Play } from 'lucide-react'; -import { NumberingTemplate } from '@/lib/api/numbering'; +import { NumberingTemplate, SaveTemplateDto } from '@/lib/api/numbering'; import { useTemplates, useSaveTemplate } from '@/hooks/use-numbering'; import { TemplateEditor } from '@/components/numbering/template-editor'; import { SequenceViewer } from '@/components/numbering/sequence-viewer'; @@ -71,7 +71,7 @@ export default function NumberingPage() { setIsEditing(true); }; - const handleSave = async (data: Partial) => { + const handleSave = async (data: SaveTemplateDto) => { try { await saveTemplateMutation.mutateAsync(data); toast.success(data.id ? 'Template updated' : 'Template created'); diff --git a/frontend/app/(admin)/admin/doc-control/reference/correspondence-types/page.tsx b/frontend/app/(admin)/admin/doc-control/reference/correspondence-types/page.tsx index b900755..96ede86 100644 --- a/frontend/app/(admin)/admin/doc-control/reference/correspondence-types/page.tsx +++ b/frontend/app/(admin)/admin/doc-control/reference/correspondence-types/page.tsx @@ -3,32 +3,33 @@ import { GenericCrudTable } from '@/components/admin/reference/generic-crud-table'; import { masterDataService } from '@/lib/services/master-data.service'; import { ColumnDef } from '@tanstack/react-table'; +import { CorrespondenceType } from '@/types/master-data'; export default function CorrespondenceTypesPage() { - const columns: ColumnDef[] = [ + const columns: ColumnDef[] = [ { - accessorKey: 'typeCode', + accessorKey: 'type_code', header: 'Code', - cell: ({ row }) => {row.getValue('typeCode')}, + cell: ({ row }) => {row.getValue('type_code')}, }, { - accessorKey: 'typeName', + accessorKey: 'type_name', header: 'Name', }, { - accessorKey: 'sortOrder', + accessorKey: 'sort_order', header: 'Sort Order', }, { - accessorKey: 'isActive', + accessorKey: 'is_active', header: 'Status', cell: ({ row }) => ( - {row.getValue('isActive') ? 'Active' : 'Inactive'} + {row.getValue('is_active') ? 'Active' : 'Inactive'} ), }, @@ -51,10 +52,10 @@ export default function CorrespondenceTypesPage() { deleteFn={(id) => masterDataService.deleteCorrespondenceType(id)} columns={columns} fields={[ - { name: 'typeCode', label: 'Code', type: 'text', required: true }, - { name: 'typeName', label: 'Name', type: 'text', required: true }, - { name: 'sortOrder', label: 'Sort Order', type: 'text' }, - { name: 'isActive', label: 'Active', type: 'checkbox' }, + { name: 'type_code', label: 'Code', type: 'text', required: true }, + { name: 'type_name', label: 'Name', type: 'text', required: true }, + { name: 'sort_order', label: 'Sort Order', type: 'text' }, + { name: 'is_active', label: 'Active', type: 'checkbox' }, ]} /> diff --git a/frontend/app/(admin)/admin/doc-control/reference/disciplines/page.tsx b/frontend/app/(admin)/admin/doc-control/reference/disciplines/page.tsx index f0c0fdb..1ca2cfd 100644 --- a/frontend/app/(admin)/admin/doc-control/reference/disciplines/page.tsx +++ b/frontend/app/(admin)/admin/doc-control/reference/disciplines/page.tsx @@ -4,6 +4,7 @@ import { GenericCrudTable } from '@/components/admin/reference/generic-crud-tabl import { masterDataService } from '@/lib/services/master-data.service'; import { useContracts } from '@/hooks/use-master-data'; import { ColumnDef } from '@tanstack/react-table'; +import { Discipline } from '@/types/master-data'; import { useState } from 'react'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; @@ -14,36 +15,36 @@ export default function DisciplinesPage() { // Ensure we consistently use an array const contracts = Array.isArray(contractsData) ? contractsData : []; - const columns: ColumnDef[] = [ + const columns: ColumnDef[] = [ { - accessorKey: 'disciplineCode', + accessorKey: 'discipline_code', header: 'Code', - cell: ({ row }) => {row.getValue('disciplineCode')}, + cell: ({ row }) => {row.getValue('discipline_code')}, }, { - accessorKey: 'codeNameTh', + accessorKey: 'code_name_th', header: 'Name (TH)', }, { - accessorKey: 'codeNameEn', + accessorKey: 'code_name_en', header: 'Name (EN)', }, { - accessorKey: 'isActive', + accessorKey: 'is_active', header: 'Status', cell: ({ row }) => ( - {row.getValue('isActive') ? 'Active' : 'Inactive'} + {row.getValue('is_active') ? 'Active' : 'Inactive'} ), }, ]; - const contractOptions = contracts.map((c: unknown) => ({ + const contractOptions = contracts.map((c: { id: number; contractCode: string; contractName: string }) => ({ label: `${c.contractName} (${c.contractCode})`, value: String(c.id), })); @@ -58,12 +59,12 @@ export default function DisciplinesPage() { fetchFn={async () => { const items = await masterDataService.getDisciplines(selectedContractId ? selectedContractId : undefined); // ADR-019: Map contractId INT → contract UUID for edit mode select matching - return (items as Record[]).map((item) => { - const rec = item as { contract?: { id?: number; uuid?: string }; contractId?: number }; + return items.map((item) => { + const rec = item as Discipline & { contract?: { id?: number; uuid?: string }; contractId?: number | string }; return { ...item, contractId: rec.contract?.id || rec.contract?.uuid || String(rec.contractId), - }; + } as Discipline; }); }} createFn={(data) => @@ -85,7 +86,7 @@ export default function DisciplinesPage() { All Contracts - {contracts.map((c: unknown) => ( + {contracts.map((c: { id: number; contractCode: string; contractName: string }) => ( {c.contractName} ({c.contractCode}) @@ -103,19 +104,19 @@ export default function DisciplinesPage() { options: contractOptions, }, { - name: 'disciplineCode', + name: 'discipline_code', label: 'Code', type: 'text', required: true, }, { - name: 'codeNameTh', + name: 'code_name_th', label: 'Name (TH)', type: 'text', required: true, }, - { name: 'codeNameEn', label: 'Name (EN)', type: 'text' }, - { name: 'isActive', label: 'Active', type: 'checkbox' }, + { name: 'code_name_en', label: 'Name (EN)', type: 'text' }, + { name: 'is_active', label: 'Active', type: 'checkbox' }, ]} /> diff --git a/frontend/app/(admin)/admin/doc-control/reference/drawing-categories/page.tsx b/frontend/app/(admin)/admin/doc-control/reference/drawing-categories/page.tsx index d857ae5..5b3f1eb 100644 --- a/frontend/app/(admin)/admin/doc-control/reference/drawing-categories/page.tsx +++ b/frontend/app/(admin)/admin/doc-control/reference/drawing-categories/page.tsx @@ -3,22 +3,23 @@ import { GenericCrudTable } from '@/components/admin/reference/generic-crud-table'; import { masterDataService } from '@/lib/services/master-data.service'; import { ColumnDef } from '@tanstack/react-table'; +import { DrawingCategory } from '@/types/master-data'; export default function DrawingCategoriesPage() { - const columns: ColumnDef[] = [ + const columns: ColumnDef[] = [ { - accessorKey: 'subTypeCode', + accessorKey: 'sub_type_code', header: 'Code', - cell: ({ row }) => {row.getValue('subTypeCode')}, + cell: ({ row }) => {row.getValue('sub_type_code')}, }, { - accessorKey: 'subTypeName', + accessorKey: 'sub_type_name', header: 'Name', }, { - accessorKey: 'subTypeNumber', + accessorKey: 'sub_type_number', header: 'Running Code', - cell: ({ row }) => {row.getValue('subTypeNumber') || '-'}, + cell: ({ row }) => {row.getValue('sub_type_number') || '-'}, }, ]; @@ -29,21 +30,25 @@ export default function DrawingCategoriesPage() { title="Drawing Categories Management" description="Manage drawing sub-types and categories" queryKey={['drawing-categories']} - fetchFn={() => masterDataService.getSubTypes(1)} // Default contract ID 1 + fetchFn={async () => { + const data = await masterDataService.getSubTypes(1); + return data as (DrawingCategory & { uuid?: string })[]; + }} createFn={(data: Record) => masterDataService.createSubType({ ...(data as unknown as Parameters[0]), contractId: 1, correspondenceTypeId: 3, }) - } // Assuming 3 is Drawings, hardcoded for now to prevent error - updateFn={() => Promise.reject('Not implemented yet')} - deleteFn={() => Promise.reject('Not implemented yet')} // Delete might be restricted - columns={columns} + } + updateFn={(id, data) => masterDataService.updateRfaType(id, data as Parameters[1])} + deleteFn={(id) => masterDataService.deleteRfaType(id)} + columns={columns as unknown as ColumnDef<{ id?: number; uuid?: string }>[]} fields={[ - { name: 'subTypeCode', label: 'Code', type: 'text', required: true }, - { name: 'subTypeName', label: 'Name', type: 'text', required: true }, - { name: 'subTypeNumber', label: 'Running Code', type: 'text' }, + { name: 'sub_type_code', label: 'Code', type: 'text', required: true }, + { name: 'sub_type_name', label: 'Name', type: 'text', required: true }, + { name: 'sub_type_number', label: 'Running Code', type: 'text' }, + { name: 'is_active', label: 'Active', type: 'checkbox' }, ]} /> diff --git a/frontend/app/(admin)/admin/doc-control/reference/rfa-types/page.tsx b/frontend/app/(admin)/admin/doc-control/reference/rfa-types/page.tsx index 207507c..0fe9fe1 100644 --- a/frontend/app/(admin)/admin/doc-control/reference/rfa-types/page.tsx +++ b/frontend/app/(admin)/admin/doc-control/reference/rfa-types/page.tsx @@ -5,6 +5,7 @@ import { masterDataService } from '@/lib/services/master-data.service'; import { useContracts } from '@/hooks/use-master-data'; import { ColumnDef } from '@tanstack/react-table'; import { useState } from 'react'; +import { RfaType } from '@/types/master-data'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; export default function RfaTypesPage() { @@ -14,18 +15,18 @@ export default function RfaTypesPage() { // Ensure we consistently use an array const contracts = Array.isArray(contractsData) ? contractsData : []; - const columns: ColumnDef[] = [ + const columns: ColumnDef[] = [ { - accessorKey: 'typeCode', + accessorKey: 'type_code', header: 'Code', - cell: ({ row }) => {row.getValue('typeCode')}, + cell: ({ row }) => {row.getValue('type_code')}, }, { - accessorKey: 'typeNameTh', + accessorKey: 'type_name_th', header: 'Name (TH)', }, { - accessorKey: 'typeNameEn', + accessorKey: 'type_name_en', header: 'Name (EN)', }, { @@ -33,22 +34,22 @@ export default function RfaTypesPage() { header: 'Remark', }, { - accessorKey: 'isActive', + accessorKey: 'is_active', header: 'Status', cell: ({ row }) => ( - {row.getValue('isActive') ? 'Active' : 'Inactive'} + {row.getValue('is_active') ? 'Active' : 'Inactive'} ), }, ]; - const contractOptions = contracts.map((c: unknown) => ({ - label: `${c.contractName} (${c.contractCode})`, + const contractOptions = contracts.map((c: { id: number | string; contract_name?: string; contract_code?: string; contractName?: string; contractCode?: string }) => ({ + label: `${c.contractName || c.contract_name} (${c.contractCode || c.contract_code})`, value: String(c.id), })); @@ -61,12 +62,12 @@ export default function RfaTypesPage() { fetchFn={async () => { const items = await masterDataService.getRfaTypes(selectedContractId ? selectedContractId : undefined); // ADR-019: Map contractId INT → contract UUID for edit mode select matching - return (items as Record[]).map((item) => { - const rec = item as { contract?: { id?: number; uuid?: string }; contractId?: number }; + return items.map((item) => { + const rec = item as RfaType & { contract?: { id?: number | string; uuid?: string }; contract_id?: number | string }; return { ...item, - contractId: rec.contract?.id || rec.contract?.uuid || String(rec.contractId), - }; + contractId: rec.contract?.id || rec.contract?.uuid || (rec.contract_id ? String(rec.contract_id) : null), + } as RfaType; }); }} createFn={(data) => @@ -86,9 +87,9 @@ export default function RfaTypesPage() { All Contracts - {contracts.map((c: unknown) => ( + {contracts.map((c: { id: number | string; contract_name?: string; contract_code?: string; contractName?: string; contractCode?: string }) => ( - {c.contractName} ({c.contractCode}) + {c.contractName || c.contract_name} ({c.contractCode || c.contract_code}) ))} @@ -103,11 +104,11 @@ export default function RfaTypesPage() { required: true, options: contractOptions, }, - { name: 'typeCode', label: 'Code', type: 'text', required: true }, - { name: 'typeNameTh', label: 'Name (TH)', type: 'text', required: true }, - { name: 'typeNameEn', label: 'Name (EN)', type: 'text' }, + { name: 'type_code', label: 'Code', type: 'text', required: true }, + { name: 'type_name_th', label: 'Name (TH)', type: 'text', required: true }, + { name: 'type_name_en', label: 'Name (EN)', type: 'text' }, { name: 'remark', label: 'Remark', type: 'textarea' }, - { name: 'isActive', label: 'Active', type: 'checkbox' }, + { name: 'is_active', label: 'Active', type: 'checkbox' }, ]} /> diff --git a/frontend/app/(admin)/admin/doc-control/reference/tags/page.tsx b/frontend/app/(admin)/admin/doc-control/reference/tags/page.tsx index 2d82554..b71f7f0 100644 --- a/frontend/app/(admin)/admin/doc-control/reference/tags/page.tsx +++ b/frontend/app/(admin)/admin/doc-control/reference/tags/page.tsx @@ -6,6 +6,7 @@ import { projectService } from '@/lib/services/project.service'; import { CreateTagDto } from '@/types/dto/master/tag.dto'; import { ColumnDef } from '@tanstack/react-table'; import { useQuery } from '@tanstack/react-query'; +import { Tag } from '@/types/master-data'; export default function TagsPage() { const { data: projectsData } = useQuery({ @@ -15,19 +16,19 @@ export default function TagsPage() { const projectOptions = [ { label: 'Global (All Projects)', value: '__none__' }, - ...(projectsData || []).map((p: Record) => ({ - label: (p.projectName || p.projectCode || p.project_name || p.project_code || `Project ${p.id}`) as string, + ...(projectsData || []).map((p: { id: number | string; projectName?: string; projectCode?: string }) => ({ + label: (p.projectName || p.projectCode || `Project ${p.id}`) as string, value: String(p.id), // p.id = UUID string via serialization })), ]; - const columns: ColumnDef>[] = [ + const columns: ColumnDef[] = [ { accessorKey: 'project_id', header: 'Project', cell: ({ row }) => { - const item = row.original as Record; - const project = item.project as Record | null; + const item = row.original as Tag & { project?: { id?: number | string; projectName?: string; projectCode?: string } }; + const project = item.project; if (!project) return Global; return (project.projectName || project.projectCode || `Project ${project.id}`) as React.ReactNode; }, @@ -73,12 +74,12 @@ export default function TagsPage() { fetchFn={async () => { const items = await masterDataService.getTags(); // ADR-019: Map project_id INT → project UUID for edit mode select matching - return (items as Record[]).map((item) => { - const rec = item as { project?: { id?: number; uuid?: string }; project_id?: number }; + return items.map((item) => { + const rec = item as Tag & { project?: { id?: number | string; uuid?: string }; project_id?: number | string }; return { ...item, project_id: rec.project?.id || rec.project?.uuid || (rec.project_id ? String(rec.project_id) : null), - }; + } as Tag; }); }} createFn={(data: Record) => diff --git a/frontend/app/(admin)/admin/monitoring/system-logs/numbering/page.tsx b/frontend/app/(admin)/admin/monitoring/system-logs/numbering/page.tsx index 10f5734..b5face5 100644 --- a/frontend/app/(admin)/admin/monitoring/system-logs/numbering/page.tsx +++ b/frontend/app/(admin)/admin/monitoring/system-logs/numbering/page.tsx @@ -14,7 +14,11 @@ interface NumberingError { errorMessage: string; stackTrace?: string; createdAt: string; - context?: unknown; + context?: { + projectId?: number | string; + contractId?: number | string; + [key: string]: unknown; + }; } const logService = { diff --git a/frontend/app/(dashboard)/circulation/new/page.tsx b/frontend/app/(dashboard)/circulation/new/page.tsx index 77f402d..f8a20ab 100644 --- a/frontend/app/(dashboard)/circulation/new/page.tsx +++ b/frontend/app/(dashboard)/circulation/new/page.tsx @@ -194,7 +194,7 @@ export default function CreateCirculationPage() { ( + render={({ field: _field }) => ( Assignees diff --git a/frontend/components/admin/reference/generic-crud-table.tsx b/frontend/components/admin/reference/generic-crud-table.tsx index caedf38..1eb06a6 100644 --- a/frontend/components/admin/reference/generic-crud-table.tsx +++ b/frontend/components/admin/reference/generic-crud-table.tsx @@ -72,7 +72,6 @@ export function GenericCrudTable({ const { data: rawData, isLoading, - _refetch, } = useQuery({ queryKey, queryFn: fetchFn, diff --git a/frontend/components/circulation/circulation-list.tsx b/frontend/components/circulation/circulation-list.tsx index 562695d..79e418e 100644 --- a/frontend/components/circulation/circulation-list.tsx +++ b/frontend/components/circulation/circulation-list.tsx @@ -3,9 +3,8 @@ import { Circulation, CirculationListResponse } from '@/types/circulation'; import { DataTable } from '@/components/common/data-table'; import { ColumnDef } from '@tanstack/react-table'; -import { _StatusBadge } from '@/components/common/status-badge'; import { Button } from '@/components/ui/button'; -import { Eye, _CheckCircle2 } from 'lucide-react'; +import { Eye } from 'lucide-react'; import Link from 'next/link'; import { format } from 'date-fns'; import { Badge } from '@/components/ui/badge'; diff --git a/frontend/components/dashboard/pending-tasks.tsx b/frontend/components/dashboard/pending-tasks.tsx index ee3a180..ab253e8 100644 --- a/frontend/components/dashboard/pending-tasks.tsx +++ b/frontend/components/dashboard/pending-tasks.tsx @@ -4,7 +4,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import Link from 'next/link'; import { PendingTask } from '@/types/dashboard'; -import { _AlertCircle, ArrowRight } from 'lucide-react'; +import { ArrowRight } from 'lucide-react'; interface PendingTasksProps { tasks: PendingTask[] | undefined; diff --git a/frontend/components/documents/common/server-data-table.tsx b/frontend/components/documents/common/server-data-table.tsx index 9ff26e7..35e73ee 100644 --- a/frontend/components/documents/common/server-data-table.tsx +++ b/frontend/components/documents/common/server-data-table.tsx @@ -8,7 +8,6 @@ import { useReactTable, PaginationState, SortingState, - _getPaginationRowModel, OnChangeFn, } from '@tanstack/react-table'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; diff --git a/frontend/components/drawings/revision-history.tsx b/frontend/components/drawings/revision-history.tsx index 4d76b5f..20313fc 100644 --- a/frontend/components/drawings/revision-history.tsx +++ b/frontend/components/drawings/revision-history.tsx @@ -4,7 +4,7 @@ import { DrawingRevision } from '@/types/drawing'; import { Card } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; -import { Download, _FileText } from 'lucide-react'; +import { Download } from 'lucide-react'; import { format } from 'date-fns'; export function RevisionHistory({ revisions }: { revisions: DrawingRevision[] }) { diff --git a/frontend/components/drawings/upload-form.tsx b/frontend/components/drawings/upload-form.tsx index 5eda6e1..495e31f 100644 --- a/frontend/components/drawings/upload-form.tsx +++ b/frontend/components/drawings/upload-form.tsx @@ -16,6 +16,11 @@ import { useShopSubCategories, useProjects, } from '@/hooks/use-master-data'; +import { + ShopMainCategory, + ShopSubCategory, + ContractDrawingCategory, +} from '@/types/master-data'; import { useState, useEffect } from 'react'; import { Loader2 } from 'lucide-react'; import { Textarea } from '@/components/ui/textarea'; @@ -232,13 +237,11 @@ export function DrawingUploadForm() { - {contractCategories?.map( - (c: { id: number; catName?: string; catCode?: string; name?: string }) => ( - - {c.catName || c.catCode || c.name} - - ) - )} + {contractCategories?.map((c: ContractDrawingCategory) => ( + + {c.catName || c.catCode || c.name} + + ))} {formErrors.mapCatId &&

{formErrors.mapCatId.message}

} @@ -287,13 +290,11 @@ export function DrawingUploadForm() { - {shopMainCats?.map( - (c: { id: number; mainCategoryName?: string; mainCategoryCode?: string; name?: string }) => ( - - {c.mainCategoryName || c.mainCategoryCode || c.name} - - ) - )} + {shopMainCats?.map((c: ShopMainCategory) => ( + + {c.mainCategoryName || c.mainCategoryCode || c.name} + + ))} {formErrors.mainCategoryId && ( @@ -307,13 +308,11 @@ export function DrawingUploadForm() { - {shopSubCats?.map( - (c: { id: number; subCategoryName?: string; subCategoryCode?: string; name?: string }) => ( - - {c.subCategoryName || c.subCategoryCode || c.name} - - ) - )} + {shopSubCats?.map((c: ShopSubCategory) => ( + + {c.subCategoryName || c.subCategoryCode || c.name} + + ))} {formErrors.subCategoryId && ( @@ -365,13 +364,11 @@ export function DrawingUploadForm() { - {shopMainCats?.map( - (c: { id: number; mainCategoryName?: string; mainCategoryCode?: string; name?: string }) => ( - - {c.mainCategoryName || c.mainCategoryCode || c.name} - - ) - )} + {shopMainCats?.map((c: ShopMainCategory) => ( + + {c.mainCategoryName || c.mainCategoryCode || c.name} + + ))} {formErrors.mainCategoryId && ( @@ -385,13 +382,11 @@ export function DrawingUploadForm() { - {shopSubCats?.map( - (c: { id: number; subCategoryName?: string; subCategoryCode?: string; name?: string }) => ( - - {c.subCategoryName || c.subCategoryCode || c.name} - - ) - )} + {shopSubCats?.map((c: ShopSubCategory) => ( + + {c.subCategoryName || c.subCategoryCode || c.name} + + ))} {formErrors.subCategoryId && ( diff --git a/frontend/components/numbering/audit-logs-table.tsx b/frontend/components/numbering/audit-logs-table.tsx index 2477dd3..93577d2 100644 --- a/frontend/components/numbering/audit-logs-table.tsx +++ b/frontend/components/numbering/audit-logs-table.tsx @@ -4,9 +4,10 @@ import { useEffect, useState } from 'react'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { documentNumberingService } from '@/lib/services/document-numbering.service'; import { format } from 'date-fns'; +import { NumberingAuditLog } from '@/types/dto/numbering.dto'; export function AuditLogsTable() { - const [logs, setLogs] = useState([]); // Replace with AuditLog type + const [logs, setLogs] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { @@ -51,7 +52,7 @@ export function AuditLogsTable() { {format(new Date(log.createdAt), 'yyyy-MM-dd HH:mm:ss')} {log.operation} - {log.generatedNumber} + {log.documentNumber} {log.createdBy || 'System'} {log.status} diff --git a/frontend/components/numbering/metrics-dashboard.tsx b/frontend/components/numbering/metrics-dashboard.tsx index e7d45e2..747d779 100644 --- a/frontend/components/numbering/metrics-dashboard.tsx +++ b/frontend/components/numbering/metrics-dashboard.tsx @@ -1,7 +1,7 @@ 'use client'; import { useEffect, useState } from 'react'; -import { Card, CardContent, CardHeader, CardTitle, _CardDescription } from '@/components/ui/card'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Progress } from '@/components/ui/progress'; import { documentNumberingService } from '@/lib/services/document-numbering.service'; import { NumberingMetrics } from '@/types/dto/numbering.dto'; diff --git a/frontend/components/numbering/template-editor.tsx b/frontend/components/numbering/template-editor.tsx index eee0b1e..2f0a0d2 100644 --- a/frontend/components/numbering/template-editor.tsx +++ b/frontend/components/numbering/template-editor.tsx @@ -8,8 +8,9 @@ import { Button } from '@/components/ui/button'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Checkbox } from '@/components/ui/checkbox'; import { Badge } from '@/components/ui/badge'; -import { NumberingTemplate } from '@/lib/api/numbering'; +import { NumberingTemplate, SaveTemplateDto } from '@/lib/api/numbering'; import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'; +import { CorrespondenceType, Discipline } from '@/types/master-data'; // Aligned with Backend replacement logic const VARIABLES = [ @@ -29,9 +30,9 @@ export interface TemplateEditorProps { template?: NumberingTemplate; projectId: number | string; projectName: string; - correspondenceTypes: unknown[]; - disciplines: unknown[]; - onSave: (data: Partial) => void; + correspondenceTypes: CorrespondenceType[]; + disciplines: Discipline[]; + onSave: (data: SaveTemplateDto) => void; onCancel: () => void; } @@ -62,7 +63,7 @@ export function TemplateEditor({ // Dynamic context based on selection (optional visual enhancement) if (v.key === '{TYPE}' && typeId) { - const t = (correspondenceTypes as { id: number; typeCode: string; typeName: string }[]).find( + const t = correspondenceTypes.find( (ct) => ct.id?.toString() === typeId ); if (t) replacement = t.typeCode; @@ -117,11 +118,10 @@ export function TemplateEditor({ Default (All Types) - {correspondenceTypes.map((type: unknown) => { - const typedType = type as { id: number; typeCode: string; typeName: string }; + {correspondenceTypes.map((type) => { return ( - - {typedType.typeCode} - {typedType.typeName} + + {type.typeCode} - {type.typeName} ); })} @@ -141,7 +141,7 @@ export function TemplateEditor({ All Disciplines - {disciplines.map((d: unknown) => ( + {disciplines.map((d) => ( {d.disciplineCode} - {d.codeNameEn || d.codeNameTh} diff --git a/frontend/components/numbering/template-tester.tsx b/frontend/components/numbering/template-tester.tsx index 9d2fc47..6036d80 100644 --- a/frontend/components/numbering/template-tester.tsx +++ b/frontend/components/numbering/template-tester.tsx @@ -1,6 +1,7 @@ 'use client'; import { useState } from 'react'; +import axios from 'axios'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; @@ -72,7 +73,12 @@ export function TemplateTester({ open, onOpenChange, template }: TemplateTesterP isDefault: result.isDefault, }); } catch (error: unknown) { - const errMsg = error?.response?.data?.message || error?.message || 'Unknown error'; + let errMsg = 'Unknown error'; + if (axios.isAxiosError(error)) { + errMsg = error.response?.data?.message || error.message; + } else if (error instanceof Error) { + errMsg = error.message; + } setTestResult({ number: `Error: ${errMsg}`, isDefault: false }); } finally { setLoading(false); diff --git a/frontend/components/ui/dropdown-menu.tsx b/frontend/components/ui/dropdown-menu.tsx index c2fd20e..4d5cb8e 100644 --- a/frontend/components/ui/dropdown-menu.tsx +++ b/frontend/components/ui/dropdown-menu.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; -import { _Check, ChevronRight, _Circle } from 'lucide-react'; +import { ChevronRight } from 'lucide-react'; import { cn } from '@/lib/utils'; const DropdownMenu = DropdownMenuPrimitive.Root; diff --git a/frontend/hooks/use-numbering.ts b/frontend/hooks/use-numbering.ts index b3552e9..6ab6b33 100644 --- a/frontend/hooks/use-numbering.ts +++ b/frontend/hooks/use-numbering.ts @@ -1,6 +1,6 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { documentNumberingService } from '@/lib/services/document-numbering.service'; -import { numberingApi, NumberingTemplate } from '@/lib/api/numbering'; +import { numberingApi, SaveTemplateDto } from '@/lib/api/numbering'; import { ManualOverrideDto, VoidReplaceDto, CancelNumberDto, AuditQueryParams } from '@/types/dto/numbering.dto'; export const numberingKeys = { @@ -20,7 +20,7 @@ export const useTemplates = () => { export const useSaveTemplate = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: (data: Partial) => numberingApi.saveTemplate(data), + mutationFn: (data: SaveTemplateDto) => numberingApi.saveTemplate(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: numberingKeys.templates() }); }, diff --git a/frontend/lib/services/master-data.service.ts b/frontend/lib/services/master-data.service.ts index 6589270..782c555 100644 --- a/frontend/lib/services/master-data.service.ts +++ b/frontend/lib/services/master-data.service.ts @@ -14,6 +14,16 @@ import { UpdateOrganizationDto, SearchOrganizationDto, } from '@/types/dto/organization/organization.dto'; +import { + CorrespondenceType, + Discipline, + RfaType, + Tag, + DrawingCategory, + ShopMainCategory, + ShopSubCategory, + ContractDrawingCategory, +} from '@/types/master-data'; const extractArrayData = (value: unknown): T[] => { let current: unknown = value; @@ -37,10 +47,10 @@ export const masterDataService = { // --- Tags Management --- /** ดึงรายการ Tags ทั้งหมด (Search & Pagination) */ - getTags: async (params?: SearchTagDto) => { + getTags: async (params?: SearchTagDto): Promise => { const response = await apiClient.get('/master/tags', { params }); // Support both wrapped and unwrapped scenarios - return response.data.data || response.data; + return extractArrayData(response.data); }, /** สร้าง Tag ใหม่ */ @@ -112,11 +122,11 @@ export const masterDataService = { // --- Disciplines Management (Admin / Req 6B) --- /** ดึงรายชื่อสาขางาน (มักจะกรองตาม Contract ID) */ - getDisciplines: async (contractId?: number | string) => { + getDisciplines: async (contractId?: number | string): Promise => { const response = await apiClient.get('/master/disciplines', { params: { contractId }, }); - return extractArrayData(response.data); + return extractArrayData(response.data); }, /** สร้างสาขางานใหม่ */ @@ -134,11 +144,11 @@ export const masterDataService = { // --- Sub-Types Management (Admin / Req 6B) --- /** ดึงรายชื่อประเภทย่อย (กรองตาม Contract และ Type) */ - getSubTypes: async (contractId?: number | string, typeId?: number) => { + getSubTypes: async (contractId?: number | string, typeId?: number): Promise => { const response = await apiClient.get('/master/sub-types', { params: { contractId, correspondenceTypeId: typeId }, }); - return extractArrayData(response.data); + return extractArrayData(response.data); }, /** สร้างประเภทย่อยใหม่ */ @@ -150,11 +160,11 @@ export const masterDataService = { // --- RFA Types Management (Admin) --- /** ดึงประเภท RFA ทั้งหมด */ - getRfaTypes: async (contractId?: number | string) => { + getRfaTypes: async (contractId?: number | string): Promise => { const response = await apiClient.get('/master/rfa-types', { params: { contractId }, }); - return extractArrayData(response.data); + return extractArrayData(response.data); }, /** สร้างประเภท RFA ใหม่ */ @@ -173,9 +183,9 @@ export const masterDataService = { // --- Document Numbering Format (Admin Config) --- // --- Correspondence Types Management --- - getCorrespondenceTypes: async () => { + getCorrespondenceTypes: async (): Promise => { const response = await apiClient.get('/master/correspondence-types'); - return extractArrayData(response.data); + return extractArrayData(response.data); }, createCorrespondenceType: async (data: CreateCorrespondenceTypeDto) => { @@ -206,22 +216,22 @@ export const masterDataService = { // --- Drawing Categories --- - getContractDrawingCategories: async (projectId?: number | string) => { + getContractDrawingCategories: async (projectId?: number | string): Promise => { const response = await apiClient.get('/drawings/contract/categories', { params: { projectId }, }); - return extractArrayData(response.data); + return extractArrayData(response.data); }, - getShopMainCategories: async (projectId: number) => { + getShopMainCategories: async (projectId: number): Promise => { const response = await apiClient.get('/drawings/shop/main-categories', { params: { projectId } }); - return extractArrayData(response.data); + return extractArrayData(response.data); }, - getShopSubCategories: async (projectId: number, mainCategoryId?: number) => { + getShopSubCategories: async (projectId: number, mainCategoryId?: number): Promise => { const response = await apiClient.get('/drawings/shop/sub-categories', { params: { projectId, mainCategoryId }, }); - return extractArrayData(response.data); + return extractArrayData(response.data); }, }; diff --git a/frontend/proxy.ts b/frontend/proxy.ts index 8445582..45143a3 100644 --- a/frontend/proxy.ts +++ b/frontend/proxy.ts @@ -1,6 +1,6 @@ // File: proxy.ts import { NextResponse } from 'next/server'; -import type { _NextRequest } from 'next/server'; + import { auth } from '@/lib/auth'; // รายการ Route ที่ไม่ต้อง Login ก็เข้าได้ (Public Routes) diff --git a/frontend/types/dto/numbering.dto.ts b/frontend/types/dto/numbering.dto.ts index ce86b96..47317b2 100644 --- a/frontend/types/dto/numbering.dto.ts +++ b/frontend/types/dto/numbering.dto.ts @@ -1,4 +1,4 @@ -import type { AuditLog } from '@/lib/services/audit-log.service'; + export interface AuditErrorRecord { code: string; @@ -7,8 +7,17 @@ export interface AuditErrorRecord { context?: Record; } +export interface NumberingAuditLog { + id: number; + documentNumber: string; + operation: string; + status: string; + createdAt: string; + createdBy?: string; +} + export interface NumberingMetrics { - audit: AuditLog[]; + audit: NumberingAuditLog[]; errors: AuditErrorRecord[]; } diff --git a/frontend/types/master-data.ts b/frontend/types/master-data.ts new file mode 100644 index 0000000..b99b539 --- /dev/null +++ b/frontend/types/master-data.ts @@ -0,0 +1,65 @@ +/** + * Master Data Entity Types + */ + +export interface CorrespondenceType { + id: number; + typeCode: string; + typeName: string; + isActive: boolean; + sortOrder?: number; +} + +export interface Discipline { + id: number; + disciplineCode: string; + codeNameEn: string; + codeNameTh?: string; + isActive: boolean; +} + +export interface RfaType { + id: number; + typeCode: string; + typeNameTh: string; + typeNameEn?: string; + remark?: string; + isActive: boolean; +} + +export interface Tag { + id: number; + tag_name: string; + color_code?: string; + description?: string; +} + +export interface DrawingCategory { + id: number; + subTypeCode: string; + subTypeName: string; + subTypeNumber?: string; +} + +export interface ShopMainCategory { + id: number; + mainCategoryCode: string; + mainCategoryName: string; + name?: string; // Fallback for legacy data + isActive: boolean; +} + +export interface ShopSubCategory { + id: number; + subCategoryCode: string; + subCategoryName: string; + name?: string; // Fallback for legacy data + isActive: boolean; +} + +export interface ContractDrawingCategory { + id: number; + catCode: string; + catName: string; + name?: string; // Fallback for legacy data +} diff --git a/CLAUDE.md b/specs/99-archives/.windsurfrules similarity index 96% rename from CLAUDE.md rename to specs/99-archives/.windsurfrules index b5210e8..b9a28c7 100644 --- a/CLAUDE.md +++ b/specs/99-archives/.windsurfrules @@ -1,7 +1,5 @@ # NAP-DMS Project Context & Rules -> **For:** CLUADE - ## 🧠 Role & Persona Act as a **Senior Full Stack Developer** expert in **NestJS**, **Next.js**, and **TypeScript**. @@ -14,8 +12,8 @@ You value **Data Integrity**, **Security**, and **Clean Architecture**. ### 📊 Project Status: UAT Ready, Security Hardened (2026-03-19) -| Area | Status | Notes | -| ------------- | ------------------------ | ---------------------------------------- | +| Area | Status | Notes | +| ------------- | ----------------------- | ---------------------------------------- | | Backend | ✅ Production Ready | NestJS 11, Express v5, 0 Vulnerabilities | | Frontend | ✅ Quality Hardened | Next.js 16.2.0, 0 `any`, 0 console.log | | Database | ✅ Schema v1.8.0 Stable | MariaDB 11.8, No-migration (ADR-009) | @@ -62,15 +60,15 @@ You value **Data Integrity**, **Security**, and **Clean Architecture**. ### 📁 Key Spec Documents (Quick Reference) -| เอกสาร | Path | ใช้เมื่อ | +| เอกสาร | Path | ใช้เมื่อ | | -------------------- | ----------------------------------------------------------- | ----------------------------------- | -| **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 | | **Seed Permissions** | `03-Data-and-Storage/lcbp3-v1.8.0-seed-permissions.sql` | ตรวจ CASL Permission Matrix | -| **Edge Cases** | `01-Requirements/01-06-edge-cases-and-rules.md` | 37 Rules ป้องกัน Bug | +| **Edge Cases** | `01-Requirements/01-06-edge-cases-and-rules.md` | 37 Rules ป้องกัน Bug | | **Migration Scope** | `03-Data-and-Storage/03-06-migration-business-scope.md` | งาน Migration Bot | -| **Release Policy** | `04-Infrastructure-OPS/04-08-release-management-policy.md` | ก่อน Deploy / Hotfix | -| **UAT Criteria** | `01-Requirements/01-05-acceptance-criteria.md` | ตรวจความสมบูรณ์ Feature | +| **Release Policy** | `04-Infrastructure-OPS/04-08-release-management-policy.md` | ก่อน Deploy / Hotfix | +| **UAT Criteria** | `01-Requirements/01-05-acceptance-criteria.md` | ตรวจความสมบูรณ์ Feature | | **ADR-009** | `06-Decision-Records/ADR-009-db-strategy.md` | Schema Change Process | | **ADR-018** | `06-Decision-Records/ADR-018-ai-boundary.md` | AI/Ollama Integration Rules | | **ADR-019** | `06-Decision-Records/ADR-019-hybrid-identifier-strategy.md` | Hybrid ID Strategy (INT + UUIDv7) |