260323:1050 fix CI : Verify Build frontend #02 correct _???
CI / CD Pipeline / build (push) Successful in 15m14s
CI / CD Pipeline / release (push) Failing after 20s
CI / CD Pipeline / deploy (push) Has been skipped

This commit is contained in:
admin
2026-03-23 10:50:20 +07:00
parent 32141f519a
commit e3c476f011
31 changed files with 3587 additions and 374 deletions
+837 -63
View File
@@ -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)
### 📊 Project Status: UAT Ready, Security Hardened (2026-03-19)
**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, 0 `any`, 0 console.log |
| Frontend | ✅ Quality Hardened | Next.js 16.2.0, React 19.2.4, 0 `any`, 0 `console.log` |
| Database | ✅ Schema v1.8.0 Stable | MariaDB 11.8, No-migration (ADR-009) |
| Documentation | ✅ **10/10 Gaps Closed** | Product Vision → Release Policy |
| Documentation | ✅ 10/10 Gaps Closed | Product Vision → Release Policy |
| AI Migration | 🔄 Pre-migration Setup | n8n + Ollama (ADR-017/018) |
| Testing | 🔄 UAT In Progress | Per `01-05-acceptance-criteria.md` |
| Deployment | 📋 Pending Go-Live | Blue-Green, QNAP Container Station |
| Deployment | 📋 Pending Go-Live Gate | Blue-Green, QNAP Container Station |
| ADR-019 UUID | 🔄 Phase 5.4 Pending | 4 frontend files still use `parseInt()` on UUID |
**Domain:** `np-dms.work`
- **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
---
## 💻 Tech Stack & Constraints
## 💻 Tech Stack (Exact Versions from repo)
- **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)
### Backend
## 🛡️ Security & Integrity Rules
- **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`
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.
### Frontend
## 📋 Spec Guidelines
- **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
- 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.
### Tooling & Testing
### 📁 Key Spec Documents (Quick Reference)
- **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
| เอกสาร | Path | ใช้เมื่อ |
| -------------------- | ----------------------------------------------------------- | ----------------------------------- |
### Notifications
- BullMQ Queue → Email (Nodemailer 8.0.3) / LINE Notify / In-App
### AI / Migration
- **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** | `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 |
| **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 |
| **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) |
| **UUID Implementation** | `05-Engineering-Guidelines/05-07-hybrid-uuid-implementation-plan.md` | ADR-019 UUID Migration (Phase 16) |
| **Backend Guidelines** | `05-Engineering-Guidelines/05-02-backend-guidelines.md` | NestJS patterns & best practices |
| **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) |
### ADR Reference (All 17 + Patch + ADR-019)
### 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
```
<type>(<scope>): <description>
[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/<description> # ฟีเจอร์ใหม่
fix/<issue-number>-<description> # แก้ bug
spec/<category>/<description> # แก้ specs
adr/<number>-<description> # ADR ใหม่/แก้ไข
refactor/<description> # 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: <module-name>
Requirement: <อ้างอิง user story จาก 01-02-business-rules/>
Steps:
1. ตรวจสอบ glossary และ edge cases
2. ออกแบบ DTO + Schema ตาม ADR-019
3. สร้าง Service + Controller พร้อม CASL guard
4. เขียน unit test สำหรับ business logic
5. อัพเดท API docs (Swagger)
Output: Code + Test + Spec reference
```
### เมื่อต้องการ debug
```
[DEBUG]
Issue: <อธิบายปัญหา>
File: <path/to/file>
Error: <error message/log>
Steps taken: <สิ่งที่ลองแก้ไขแล้ว>
Request: วิเคราะห์ตาม spec + แนะนำวิธีแก้ที่สอดคล้องกับ ADRs
```
### เมื่อต้องการ review code
```
[CODE REVIEW]
File: <path/to/file>
Focus: <security/performance/uuid/i18n>
Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table
```
---
## 📦 Infrastructure Quick Reference
### 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 - <brief description>`
---
## ✅ 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)
+838 -60
View File
@@ -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)
### 📊 Project Status: UAT Ready, Security Hardened (2026-03-19)
**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, 0 `any`, 0 console.log |
| Frontend | ✅ Quality Hardened | Next.js 16.2.0, React 19.2.4, 0 `any`, 0 `console.log` |
| Database | ✅ Schema v1.8.0 Stable | MariaDB 11.8, No-migration (ADR-009) |
| Documentation | ✅ **10/10 Gaps Closed** | Product Vision → Release Policy |
| Documentation | ✅ 10/10 Gaps Closed | Product Vision → Release Policy |
| AI Migration | 🔄 Pre-migration Setup | n8n + Ollama (ADR-017/018) |
| Testing | 🔄 UAT In Progress | Per `01-05-acceptance-criteria.md` |
| Deployment | 📋 Pending Go-Live | Blue-Green, QNAP Container Station |
| Deployment | 📋 Pending Go-Live Gate | Blue-Green, QNAP Container Station |
| ADR-019 UUID | 🔄 Phase 5.4 Pending | 4 frontend files still use `parseInt()` on UUID |
**Domain:** `np-dms.work`
- **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
---
## 💻 Tech Stack & Constraints
## 💻 Tech Stack (Exact Versions from repo)
- **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)
### Backend
## 🛡️ Security & Integrity Rules
- **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`
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.
### Frontend
## 📋 Spec Guidelines
- **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
- 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.
### Tooling & Testing
### 📁 Key Spec Documents (Quick Reference)
- **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
| เอกสาร | Path | ใช้เมื่อ |
| -------------------- | ----------------------------------------------------------- | ----------------------------------- |
### Notifications
- BullMQ Queue → Email (Nodemailer 8.0.3) / LINE Notify / In-App
### AI / Migration
- **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** | `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 |
| **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 |
| **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) |
| **UUID Implementation** | `05-Engineering-Guidelines/05-07-hybrid-uuid-implementation-plan.md` | ADR-019 UUID Migration (Phase 16) |
| **Backend Guidelines** | `05-Engineering-Guidelines/05-02-backend-guidelines.md` | NestJS patterns & best practices |
| **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) |
### ADR Reference (All 17 + Patch + ADR-019)
### 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
```
<type>(<scope>): <description>
[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/<description> # ฟีเจอร์ใหม่
fix/<issue-number>-<description> # แก้ bug
spec/<category>/<description> # แก้ specs
adr/<number>-<description> # ADR ใหม่/แก้ไข
refactor/<description> # 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: <module-name>
Requirement: <อ้างอิง user story จาก 01-02-business-rules/>
Steps:
1. ตรวจสอบ glossary และ edge cases
2. ออกแบบ DTO + Schema ตาม ADR-019
3. สร้าง Service + Controller พร้อม CASL guard
4. เขียน unit test สำหรับ business logic
5. อัพเดท API docs (Swagger)
Output: Code + Test + Spec reference
```
### เมื่อต้องการ debug
```
[DEBUG]
Issue: <อธิบายปัญหา>
File: <path/to/file>
Error: <error message/log>
Steps taken: <สิ่งที่ลองแก้ไขแล้ว>
Request: วิเคราะห์ตาม spec + แนะนำวิธีแก้ที่สอดคล้องกับ ADRs
```
### เมื่อต้องการ review code
```
[CODE REVIEW]
File: <path/to/file>
Focus: <security/performance/uuid/i18n>
Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table
```
---
## 📦 Infrastructure Quick Reference
### 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 - <brief description>`
---
## ✅ 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)
+837 -62
View File
@@ -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)
### 📊 Project Status: UAT Ready, Security Hardened (2026-03-19)
**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, 0 `any`, 0 console.log |
| Frontend | ✅ Quality Hardened | Next.js 16.2.0, React 19.2.4, 0 `any`, 0 `console.log` |
| Database | ✅ Schema v1.8.0 Stable | MariaDB 11.8, No-migration (ADR-009) |
| Documentation | ✅ **10/10 Gaps Closed** | Product Vision → Release Policy |
| Documentation | ✅ 10/10 Gaps Closed | Product Vision → Release Policy |
| AI Migration | 🔄 Pre-migration Setup | n8n + Ollama (ADR-017/018) |
| Testing | 🔄 UAT In Progress | Per `01-05-acceptance-criteria.md` |
| Deployment | 📋 Pending Go-Live | Blue-Green, QNAP Container Station |
| Deployment | 📋 Pending Go-Live Gate | Blue-Green, QNAP Container Station |
| ADR-019 UUID | 🔄 Phase 5.4 Pending | 4 frontend files still use `parseInt()` on UUID |
**Domain:** `np-dms.work`
- **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
---
## 💻 Tech Stack & Constraints
## 💻 Tech Stack (Exact Versions from repo)
- **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)
### Backend
## 🛡️ Security & Integrity Rules
- **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`
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.
### Frontend
## 📋 Spec Guidelines
- **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
- 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.
### Tooling & Testing
### 📁 Key Spec Documents (Quick Reference)
- **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
| เอกสาร | Path | ใช้เมื่อ |
| -------------------- | ----------------------------------------------------------- | ----------------------------------- |
### Notifications
- BullMQ Queue → Email (Nodemailer 8.0.3) / LINE Notify / In-App
### AI / Migration
- **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** | `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 |
| **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 |
| **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) |
| **UUID Implementation** | `05-Engineering-Guidelines/05-07-hybrid-uuid-implementation-plan.md` | ADR-019 UUID Migration (Phase 16) |
| **Backend Guidelines** | `05-Engineering-Guidelines/05-02-backend-guidelines.md` | NestJS patterns & best practices |
| **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) |
### ADR Reference (All 17 + Patch + ADR-019)
### 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
```
<type>(<scope>): <description>
[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/<description> # ฟีเจอร์ใหม่
fix/<issue-number>-<description> # แก้ bug
spec/<category>/<description> # แก้ specs
adr/<number>-<description> # ADR ใหม่/แก้ไข
refactor/<description> # 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: <module-name>
Requirement: <อ้างอิง user story จาก 01-02-business-rules/>
Steps:
1. ตรวจสอบ glossary และ edge cases
2. ออกแบบ DTO + Schema ตาม ADR-019
3. สร้าง Service + Controller พร้อม CASL guard
4. เขียน unit test สำหรับ business logic
5. อัพเดท API docs (Swagger)
Output: Code + Test + Spec reference
```
### เมื่อต้องการ debug
```
[DEBUG]
Issue: <อธิบายปัญหา>
File: <path/to/file>
Error: <error message/log>
Steps taken: <สิ่งที่ลองแก้ไขแล้ว>
Request: วิเคราะห์ตาม spec + แนะนำวิธีแก้ที่สอดคล้องกับ ADRs
```
### เมื่อต้องการ review code
```
[CODE REVIEW]
File: <path/to/file>
Focus: <security/performance/uuid/i18n>
Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table
```
---
## 📦 Infrastructure Quick Reference
### 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 - <brief description>`
---
## ✅ 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)
+783
View File
@@ -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 ได้จริงทั้งก้อน** 🚀
@@ -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);
@@ -23,8 +23,17 @@ export default function NewTemplatePage() {
const handleSave = async (data: Partial<NumberingTemplate>) => {
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');
}
@@ -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<NumberingTemplate>) => {
const handleSave = async (data: SaveTemplateDto) => {
try {
await saveTemplateMutation.mutateAsync(data);
toast.success(data.id ? 'Template updated' : 'Template created');
@@ -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<unknown>[] = [
const columns: ColumnDef<CorrespondenceType>[] = [
{
accessorKey: 'typeCode',
accessorKey: 'type_code',
header: 'Code',
cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('typeCode')}</span>,
cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('type_code')}</span>,
},
{
accessorKey: 'typeName',
accessorKey: 'type_name',
header: 'Name',
},
{
accessorKey: 'sortOrder',
accessorKey: 'sort_order',
header: 'Sort Order',
},
{
accessorKey: 'isActive',
accessorKey: 'is_active',
header: 'Status',
cell: ({ row }) => (
<span
className={`px-2 py-1 rounded-full text-xs ${
row.getValue('isActive') ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
row.getValue('is_active') ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
}`}
>
{row.getValue('isActive') ? 'Active' : 'Inactive'}
{row.getValue('is_active') ? 'Active' : 'Inactive'}
</span>
),
},
@@ -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' },
]}
/>
</div>
@@ -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<unknown>[] = [
const columns: ColumnDef<Discipline>[] = [
{
accessorKey: 'disciplineCode',
accessorKey: 'discipline_code',
header: 'Code',
cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('disciplineCode')}</span>,
cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('discipline_code')}</span>,
},
{
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 }) => (
<span
className={`px-2 py-1 rounded-full text-xs ${
row.getValue('isActive') ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
row.getValue('is_active') ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
}`}
>
{row.getValue('isActive') ? 'Active' : 'Inactive'}
{row.getValue('is_active') ? 'Active' : 'Inactive'}
</span>
),
},
];
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<string, unknown>[]).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() {
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Contracts</SelectItem>
{contracts.map((c: unknown) => (
{contracts.map((c: { id: number; contractCode: string; contractName: string }) => (
<SelectItem key={c.id} value={String(c.id)}>
{c.contractName} ({c.contractCode})
</SelectItem>
@@ -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' },
]}
/>
</div>
@@ -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<unknown>[] = [
const columns: ColumnDef<DrawingCategory>[] = [
{
accessorKey: 'subTypeCode',
accessorKey: 'sub_type_code',
header: 'Code',
cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('subTypeCode')}</span>,
cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('sub_type_code')}</span>,
},
{
accessorKey: 'subTypeName',
accessorKey: 'sub_type_name',
header: 'Name',
},
{
accessorKey: 'subTypeNumber',
accessorKey: 'sub_type_number',
header: 'Running Code',
cell: ({ row }) => <span className="font-mono">{row.getValue('subTypeNumber') || '-'}</span>,
cell: ({ row }) => <span className="font-mono">{row.getValue('sub_type_number') || '-'}</span>,
},
];
@@ -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<string, unknown>) =>
masterDataService.createSubType({
...(data as unknown as Parameters<typeof masterDataService.createSubType>[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<typeof masterDataService.updateRfaType>[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' },
]}
/>
</div>
@@ -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<unknown>[] = [
const columns: ColumnDef<RfaType>[] = [
{
accessorKey: 'typeCode',
accessorKey: 'type_code',
header: 'Code',
cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('typeCode')}</span>,
cell: ({ row }) => <span className="font-mono font-bold">{row.getValue('type_code')}</span>,
},
{
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 }) => (
<span
className={`px-2 py-1 rounded-full text-xs ${
row.getValue('isActive') ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
row.getValue('is_active') ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
}`}
>
{row.getValue('isActive') ? 'Active' : 'Inactive'}
{row.getValue('is_active') ? 'Active' : 'Inactive'}
</span>
),
},
];
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<string, unknown>[]).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() {
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Contracts</SelectItem>
{contracts.map((c: unknown) => (
{contracts.map((c: { id: number | string; contract_name?: string; contract_code?: string; contractName?: string; contractCode?: string }) => (
<SelectItem key={c.id} value={String(c.id)}>
{c.contractName} ({c.contractCode})
{c.contractName || c.contract_name} ({c.contractCode || c.contract_code})
</SelectItem>
))}
</SelectContent>
@@ -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' },
]}
/>
</div>
@@ -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<string, unknown>) => ({
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<Record<string, unknown>>[] = [
const columns: ColumnDef<Tag>[] = [
{
accessorKey: 'project_id',
header: 'Project',
cell: ({ row }) => {
const item = row.original as Record<string, unknown>;
const project = item.project as Record<string, unknown> | null;
const item = row.original as Tag & { project?: { id?: number | string; projectName?: string; projectCode?: string } };
const project = item.project;
if (!project) return <span className="text-muted-foreground italic">Global</span>;
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<string, unknown>[]).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<string, unknown>) =>
@@ -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 = {
@@ -194,7 +194,7 @@ export default function CreateCirculationPage() {
<FormField
control={form.control}
name="assigneeIds"
render={({ _field }) => (
render={({ field: _field }) => (
<FormItem className="flex flex-col">
<FormLabel>Assignees</FormLabel>
<Popover open={assigneeOpen} onOpenChange={setAssigneeOpen}>
@@ -72,7 +72,6 @@ export function GenericCrudTable<T extends { id?: number; uuid?: string }>({
const {
data: rawData,
isLoading,
_refetch,
} = useQuery({
queryKey,
queryFn: fetchFn,
@@ -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';
@@ -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;
@@ -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';
@@ -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[] }) {
+15 -20
View File
@@ -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() {
<SelectValue placeholder="Select Category" />
</SelectTrigger>
<SelectContent>
{contractCategories?.map(
(c: { id: number; catName?: string; catCode?: string; name?: string }) => (
{contractCategories?.map((c: ContractDrawingCategory) => (
<SelectItem key={c.id} value={String(c.id)}>
{c.catName || c.catCode || c.name}
</SelectItem>
)
)}
))}
</SelectContent>
</Select>
{formErrors.mapCatId && <p className="text-sm text-destructive">{formErrors.mapCatId.message}</p>}
@@ -287,13 +290,11 @@ export function DrawingUploadForm() {
<SelectValue placeholder="Select Main Category" />
</SelectTrigger>
<SelectContent>
{shopMainCats?.map(
(c: { id: number; mainCategoryName?: string; mainCategoryCode?: string; name?: string }) => (
{shopMainCats?.map((c: ShopMainCategory) => (
<SelectItem key={c.id} value={String(c.id)}>
{c.mainCategoryName || c.mainCategoryCode || c.name}
</SelectItem>
)
)}
))}
</SelectContent>
</Select>
{formErrors.mainCategoryId && (
@@ -307,13 +308,11 @@ export function DrawingUploadForm() {
<SelectValue placeholder="Select Sub Category" />
</SelectTrigger>
<SelectContent>
{shopSubCats?.map(
(c: { id: number; subCategoryName?: string; subCategoryCode?: string; name?: string }) => (
{shopSubCats?.map((c: ShopSubCategory) => (
<SelectItem key={c.id} value={String(c.id)}>
{c.subCategoryName || c.subCategoryCode || c.name}
</SelectItem>
)
)}
))}
</SelectContent>
</Select>
{formErrors.subCategoryId && (
@@ -365,13 +364,11 @@ export function DrawingUploadForm() {
<SelectValue placeholder="Select Main Category" />
</SelectTrigger>
<SelectContent>
{shopMainCats?.map(
(c: { id: number; mainCategoryName?: string; mainCategoryCode?: string; name?: string }) => (
{shopMainCats?.map((c: ShopMainCategory) => (
<SelectItem key={c.id} value={String(c.id)}>
{c.mainCategoryName || c.mainCategoryCode || c.name}
</SelectItem>
)
)}
))}
</SelectContent>
</Select>
{formErrors.mainCategoryId && (
@@ -385,13 +382,11 @@ export function DrawingUploadForm() {
<SelectValue placeholder="Select Sub Category" />
</SelectTrigger>
<SelectContent>
{shopSubCats?.map(
(c: { id: number; subCategoryName?: string; subCategoryCode?: string; name?: string }) => (
{shopSubCats?.map((c: ShopSubCategory) => (
<SelectItem key={c.id} value={String(c.id)}>
{c.subCategoryName || c.subCategoryCode || c.name}
</SelectItem>
)
)}
))}
</SelectContent>
</Select>
{formErrors.subCategoryId && (
@@ -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<unknown[]>([]); // Replace with AuditLog type
const [logs, setLogs] = useState<NumberingAuditLog[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
@@ -51,7 +52,7 @@ export function AuditLogsTable() {
<TableRow key={log.id}>
<TableCell>{format(new Date(log.createdAt), 'yyyy-MM-dd HH:mm:ss')}</TableCell>
<TableCell>{log.operation}</TableCell>
<TableCell>{log.generatedNumber}</TableCell>
<TableCell>{log.documentNumber}</TableCell>
<TableCell>{log.createdBy || 'System'}</TableCell>
<TableCell>{log.status}</TableCell>
</TableRow>
@@ -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';
@@ -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<NumberingTemplate>) => 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({
</SelectTrigger>
<SelectContent>
<SelectItem value="__default__">Default (All Types)</SelectItem>
{correspondenceTypes.map((type: unknown) => {
const typedType = type as { id: number; typeCode: string; typeName: string };
{correspondenceTypes.map((type) => {
return (
<SelectItem key={typedType.id} value={typedType.id.toString()}>
{typedType.typeCode} - {typedType.typeName}
<SelectItem key={type.id} value={type.id.toString()}>
{type.typeCode} - {type.typeName}
</SelectItem>
);
})}
@@ -141,7 +141,7 @@ export function TemplateEditor({
</SelectTrigger>
<SelectContent>
<SelectItem value="0">All Disciplines</SelectItem>
{disciplines.map((d: unknown) => (
{disciplines.map((d) => (
<SelectItem key={d.id} value={d.id.toString()}>
{d.disciplineCode} - {d.codeNameEn || d.codeNameTh}
</SelectItem>
@@ -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);
+1 -1
View File
@@ -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;
+2 -2
View File
@@ -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<NumberingTemplate>) => numberingApi.saveTemplate(data),
mutationFn: (data: SaveTemplateDto) => numberingApi.saveTemplate(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: numberingKeys.templates() });
},
+26 -16
View File
@@ -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 = <T>(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<Tag[]> => {
const response = await apiClient.get('/master/tags', { params });
// Support both wrapped and unwrapped scenarios
return response.data.data || response.data;
return extractArrayData<Tag>(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<Discipline[]> => {
const response = await apiClient.get('/master/disciplines', {
params: { contractId },
});
return extractArrayData(response.data);
return extractArrayData<Discipline>(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<DrawingCategory[]> => {
const response = await apiClient.get('/master/sub-types', {
params: { contractId, correspondenceTypeId: typeId },
});
return extractArrayData(response.data);
return extractArrayData<DrawingCategory>(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<RfaType[]> => {
const response = await apiClient.get('/master/rfa-types', {
params: { contractId },
});
return extractArrayData(response.data);
return extractArrayData<RfaType>(response.data);
},
/** สร้างประเภท RFA ใหม่ */
@@ -173,9 +183,9 @@ export const masterDataService = {
// --- Document Numbering Format (Admin Config) ---
// --- Correspondence Types Management ---
getCorrespondenceTypes: async () => {
getCorrespondenceTypes: async (): Promise<CorrespondenceType[]> => {
const response = await apiClient.get('/master/correspondence-types');
return extractArrayData(response.data);
return extractArrayData<CorrespondenceType>(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<ContractDrawingCategory[]> => {
const response = await apiClient.get('/drawings/contract/categories', {
params: { projectId },
});
return extractArrayData(response.data);
return extractArrayData<ContractDrawingCategory>(response.data);
},
getShopMainCategories: async (projectId: number) => {
getShopMainCategories: async (projectId: number): Promise<ShopMainCategory[]> => {
const response = await apiClient.get('/drawings/shop/main-categories', { params: { projectId } });
return extractArrayData(response.data);
return extractArrayData<ShopMainCategory>(response.data);
},
getShopSubCategories: async (projectId: number, mainCategoryId?: number) => {
getShopSubCategories: async (projectId: number, mainCategoryId?: number): Promise<ShopSubCategory[]> => {
const response = await apiClient.get('/drawings/shop/sub-categories', {
params: { projectId, mainCategoryId },
});
return extractArrayData(response.data);
return extractArrayData<ShopSubCategory>(response.data);
},
};
+1 -1
View File
@@ -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)
+11 -2
View File
@@ -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<string, unknown>;
}
export interface NumberingAuditLog {
id: number;
documentNumber: string;
operation: string;
status: string;
createdAt: string;
createdBy?: string;
}
export interface NumberingMetrics {
audit: AuditLog[];
audit: NumberingAuditLog[];
errors: AuditErrorRecord[];
}
+65
View File
@@ -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
}
@@ -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**.
@@ -15,7 +13,7 @@ You value **Data Integrity**, **Security**, and **Clean Architecture**.
### 📊 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) |