diff --git a/.gemini/GEMINI.md b/.gemini/GEMINI.md index 4e30ef5..ca3df99 100644 --- a/.gemini/GEMINI.md +++ b/.gemini/GEMINI.md @@ -3,8 +3,8 @@ 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) +- Version: 1.8.4 (Accuracy Pass) | Last synced from repo: 2026-03-24 +- Repo: [https://git.np-dms.work/np-dms/lcbp3](https://git.np-dms.work/np-dms/lcbp3) --- @@ -67,16 +67,16 @@ Best practice — ทำตามถ้าทำได้ ไม่ block: **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 | +| Area | Status | Notes | | ------------- | ---------------------- | ------------------------------------------------------ | -| Backend | ✅ Production Ready | NestJS 11, Express v5, 0 Vulnerabilities | -| Frontend | ✅ Quality Hardened | Next.js 16.2.0, React 19.2.4, 0 `any`, 0 `console.log` | -| Database | ✅ Schema v1.8.0 Stable | MariaDB 11.8, No-migration (ADR-009) | -| Documentation | ✅ 10/10 Gaps Closed | Product Vision → Release Policy | -| AI Migration | 🔄 Pre-migration Setup | n8n + Ollama (ADR-017/018) | -| Testing | 🔄 UAT In Progress | Per `01-05-acceptance-criteria.md` | -| Deployment | 📋 Pending Go-Live Gate | Blue-Green, QNAP Container Station | -| ADR-019 UUID | 🔄 Phase 5.4 Pending | 4 frontend files still use `parseInt()` on UUID | +| Backend | ✅ Production Ready | NestJS 11, Express v5, 0 Vulnerabilities | +| Frontend | ✅ Quality Hardened | Next.js 16.2.0, React 19.2.4, 0 `any`, 0 `console.log` | +| Database | ✅ Schema v1.8.0 Stable | MariaDB 11.8, No-migration (ADR-009) | +| Documentation | ✅ 10/10 Gaps Closed | Product Vision → Release Policy | +| AI Migration | 🔄 Pre-migration Setup | n8n + Ollama (ADR-017/018) | +| Testing | 🔄 UAT In Progress | Per `01-05-acceptance-criteria.md` | +| Deployment | 📋 Pending Go-Live Gate | Blue-Green, QNAP Container Station | +| ADR-019 UUID | ✅ All Phases Complete | Phase 5.4 done — all UUID FK issues resolved | **Domain:** `np-dms.work` --- @@ -99,7 +99,7 @@ Best practice — ทำตามถ้าทำได้ ไม่ block: ### Frontend - **Next.js 16.2.0** (App Router + `proxy.ts`) + **React 19.2.4** -- **Tailwind CSS 4.2.2** + **Shadcn/UI** +- **Tailwind CSS 3.4.3** + **Shadcn/UI** - **TanStack Query** — **Server State only** - **Zustand** — **Client State only** - **React Hook Form 7.71.2** + **Zod 4.3.6** + **@hookform/resolvers 3.9.0** — **Form State only** @@ -125,7 +125,7 @@ Best practice — ทำตามถ้าทำได้ ไม่ block: ### Security Overrides (pnpm overrides active in root `package.json`) -- `uuid@11.0.0`, `multer@>=2.1.1`, `undici@>=7.24.0`, `axios@>=1.13.5` — patched +- 30+ security overrides active (`multer@>=2.1.1`, `undici@>=7.24.0`, `axios@>=1.13.5`, etc.) - All 52 vulnerabilities resolved as of 2026-03-19 (27 high + 20 moderate + 5 low) --- @@ -133,23 +133,23 @@ Best practice — ทำตามถ้าทำได้ ไม่ block: ## 🗂️ Key Spec Files (Always Check Before Writing Code) Spec priority: **`06-Decision-Records`** > **`05-Engineering-Guidelines`** > others -| เอกสาร | Path (relative to `specs/`) | ใช้เมื่อ | +| เอกสาร | Path (relative to `specs/`) | ใช้เมื่อ | | ------------------------- | -------------------------------------------------------------------- | ----------------------------------- | -| **Glossary** | `00-Overview/00-02-glossary.md` | ตรวจคำศัพท์ Domain ก่อนเขียนเสมอ | -| **Schema Tables** | `03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql` | ก่อนเขียน Query ทุกครั้ง | -| **Data Dictionary** | `03-Data-and-Storage/03-01-data-dictionary.md` | ตรวจ Field Meaning + Business Rules | -| **Seed Permissions** | `03-Data-and-Storage/lcbp3-v1.8.0-seed-permissions.sql` | ตรวจ CASL Permission Matrix | -| **Edge Cases (37 rules)** | `01-Requirements/01-06-edge-cases-and-rules.md` | ป้องกัน Bug ทุก Flow | -| **Migration Scope** | `03-Data-and-Storage/03-06-migration-business-scope.md` | งาน Migration Bot (20K docs) | -| **Release Policy** | `04-Infrastructure-OPS/04-08-release-management-policy.md` | ก่อน Deploy / Hotfix | -| **UAT Criteria** | `01-Requirements/01-05-acceptance-criteria.md` | ตรวจความสมบูรณ์ Feature | -| **UUID Implementation** | `05-Engineering-Guidelines/05-07-hybrid-uuid-implementation-plan.md` | ADR-019 UUID Migration (Phase 1–6) | -| **Backend Guidelines** | `05-Engineering-Guidelines/05-02-backend-guidelines.md` | NestJS patterns & best practices | -| **Frontend Guidelines** | `05-Engineering-Guidelines/05-03-frontend-guidelines.md` | Next.js patterns & best practices | -| **Testing Strategy** | `05-Engineering-Guidelines/05-04-testing-strategy.md` | Coverage goals & test patterns | -| **ADR-009 DB Strategy** | `06-Decision-Records/ADR-009-db-strategy.md` | Schema Change Process | -| **ADR-018 AI Boundary** | `06-Decision-Records/ADR-018-ai-boundary.md` | AI/Ollama Integration Rules | -| **ADR-019 Hybrid ID** | `06-Decision-Records/ADR-019-hybrid-identifier-strategy.md` | Hybrid ID Strategy (INT + UUIDv7) | +| **Glossary** | `00-Overview/00-02-glossary.md` | ตรวจคำศัพท์ Domain ก่อนเขียนเสมอ | +| **Schema Tables** | `03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql` | ก่อนเขียน Query ทุกครั้ง | +| **Data Dictionary** | `03-Data-and-Storage/03-01-data-dictionary.md` | ตรวจ Field Meaning + Business Rules | +| **Seed Permissions** | `03-Data-and-Storage/lcbp3-v1.8.0-seed-permissions.sql` | ตรวจ CASL Permission Matrix | +| **Edge Cases (37 rules)** | `01-Requirements/01-06-edge-cases-and-rules.md` | ป้องกัน Bug ทุก Flow | +| **Migration Scope** | `03-Data-and-Storage/03-06-migration-business-scope.md` | งาน Migration Bot (20K docs) | +| **Release Policy** | `04-Infrastructure-OPS/04-08-release-management-policy.md` | ก่อน Deploy / Hotfix | +| **UAT Criteria** | `01-Requirements/01-05-acceptance-criteria.md` | ตรวจความสมบูรณ์ Feature | +| **UUID Implementation** | `05-Engineering-Guidelines/05-07-hybrid-uuid-implementation-plan.md` | ADR-019 UUID Migration (Phase 1–6) | +| **Backend Guidelines** | `05-Engineering-Guidelines/05-02-backend-guidelines.md` | NestJS patterns & best practices | +| **Frontend Guidelines** | `05-Engineering-Guidelines/05-03-frontend-guidelines.md` | Next.js patterns & best practices | +| **Testing Strategy** | `05-Engineering-Guidelines/05-04-testing-strategy.md` | Coverage goals & test patterns | +| **ADR-009 DB Strategy** | `06-Decision-Records/ADR-009-db-strategy.md` | Schema Change Process | +| **ADR-018 AI Boundary** | `06-Decision-Records/ADR-018-ai-boundary.md` | AI/Ollama Integration Rules | +| **ADR-019 Hybrid ID** | `06-Decision-Records/ADR-019-hybrid-identifier-strategy.md` | Hybrid ID Strategy (INT + UUIDv7) | ### Specs Directory Structure (Brief) @@ -163,7 +163,7 @@ specs/ ├── 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) +├── 06-Decision-Records/ # 16 ADRs defined (15 with file, ADR-003/004/007 not created) └── 99-archives/ # ประวัติ tasks เก่า ``` @@ -173,28 +173,30 @@ Schema is split — modify the correct file: - `lcbp3-v1.8.0-schema-02-tables.sql` ← **primary reference for all queries** - `lcbp3-v1.8.0-schema-03-views-indexes.sql` +> **UUID Storage (MariaDB-specific):** Column type is `uuid UUID NOT NULL DEFAULT UUID()` — MariaDB native UUID, NOT MySQL's `BINARY(16)` + `UUID_TO_BIN()`/`BIN_TO_UUID()`. No transformer needed. `x.uuid` in Views directly (no conversion function). + --- -## 📐 ADR Reference (19 total) +## 📐 ADR Reference (16 defined) -| ADR | Topic | Key Decision | -| ------- | -------------------------- | -------------------------------------------------- | -| ADR-001 | Workflow Engine | Unified state machine for document workflows | -| ADR-002 | Doc Numbering | Redis Redlock + DB optimistic locking | -| ADR-005 | Technology Stack | NestJS + Next.js + MariaDB + Redis | -| ADR-006 | Redis Caching | Cache strategy and invalidation patterns | -| ADR-008 | Email Notification | BullMQ queue-based email/LINE/in-app | -| ADR-009 | DB Strategy | No TypeORM migrations — modify schema SQL directly | -| ADR-010 | Logging/Monitoring | Prometheus + Loki + Grafana stack | -| ADR-011 | App Router | Next.js App Router with RSC patterns | -| ADR-012 | UI Components | Shadcn/UI component library | -| ADR-013 | Form Handling | React Hook Form + Zod validation | -| 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 (~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) | +| ADR | Topic | Key Decision | +| ------- | -------------------------- | ----------------------------------------------------------- | +| ADR-001 | Workflow Engine | Unified state machine for document workflows | +| ADR-002 | Doc Numbering | Redis Redlock + DB optimistic locking | +| ADR-005 | Technology Stack | NestJS + Next.js + MariaDB + Redis | +| ADR-006 | Redis Caching | Cache strategy and invalidation patterns | +| ADR-008 | Email Notification | BullMQ queue-based email/LINE/in-app | +| ADR-009 | DB Strategy | No TypeORM migrations — modify schema SQL directly | +| ADR-010 | Logging/Monitoring | Prometheus + Loki + Grafana stack | +| ADR-011 | App Router | Next.js App Router with RSC patterns | +| ADR-012 | UI Components | Shadcn/UI component library | +| ADR-013 | Form Handling | React Hook Form + Zod validation | +| 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 (~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 MariaDB native UUID (public API) | --- @@ -203,26 +205,26 @@ Schema is split — modify the correct file: ### Rule Summary - **Internal / DB FK:** `INT AUTO_INCREMENT` (Primary Key) — never exposed -- **Public API / URL:** `UUIDv7` stored as `BINARY(16)` +- **Public API / URL:** `UUIDv7` stored as MariaDB native `UUID` type (auto-stored as BINARY(16), no transformer needed) - Read `05-07-hybrid-uuid-implementation-plan.md` before any UUID-related work. -### ⚠️ Phase 5.4 — Pending Fix (as of 2026-03-20) +### ✅ Phase 5.4 — COMPLETED (2026-03-21) -These files still call `parseInt()` on UUID values — **fix when touching these files**: +All UUID FK issues resolved — no more `parseInt()` on UUID values: -- `frontend/components/correspondences/form.tsx` -- `frontend/components/user-dialog.tsx` -- `frontend/components/numbering/template-tester.tsx` -- `frontend/app/(dashboard)/rfas/page.tsx` +- `frontend/components/correspondences/form.tsx` ✅ fixed +- `frontend/components/admin/user-dialog.tsx` ✅ fixed +- `frontend/components/numbering/template-tester.tsx` ✅ fixed +- `frontend/app/(dashboard)/rfas/page.tsx` ✅ fixed ### UUID Serialization Behavior (TransformInterceptor) `TransformInterceptor` uses `instanceToPlain()` — `@Exclude()` and `@Expose()` decorators are active on all responses. -| Entity Type | Behavior | +| Entity Type | Behavior | | ------------------ | ---------------------------------------------------------------------- | -| All entities | INT `id` has `@Exclude()` → **never appears in API response** | +| All entities | INT `id` has `@Exclude()` → **never appears in API response** | | Project / Contract | `uuid` has `@Expose({ name: 'id' })` → response has `id` = UUID string | -| Other entities | Separate `uuid` field → response has `uuid`, no `id` | +| Other entities | Separate `uuid` field → response has `uuid`, no `id` | ### UUID Patterns (Backend Controller) @@ -283,42 +285,87 @@ onValueChange={(v) => setValue("projectId", parseInt(v))} - **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`. + +### 🟡 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** — ดูรายละเอียดที่ "🚨 Naming Conventions Pain Point" ด้านล่าง + +### 🟢 Tier 3 — GUIDELINES + +Best practice — ทำตามถ้าทำได้ ไม่ block: + +- Code style / formatting (Prettier จัดให้) +- Comment completeness +- Minor optimizations --- -## 📝 Naming Conventions +## � Naming Conventions Pain Point (เรื่องที่ผิดบ่อยที่สุด) -| Target | Convention | Example | -| ------------------- | ----------- | --------------------------- | -| 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 | ความคิดเห็นและเอกสารใช้ภาษาไทย | +**สถานะ:** พบ violations จำนวนมากใน codebase — ต้อง fix ก่อน merge เสมอ + +**ข้อตกลงหลัก:** + +| Target | Convention | Example | +| ----------------------- | ----------- | ------------------------------ | +| **Files/Folders** | kebab-case | `user-service.ts` | +| **Classes** | PascalCase | `UserService` | +| **Variables/Functions** | camelCase | `firstName`, `getUserInfo` | +| **DB Columns** | snake_case | `user_id`, `created_at` | +| **Boolean vars** | verb + noun | `isActive`, `hasPermission` | +| **Code** | English | All identifiers in English | +| **Comments/Docs** | Thai | ความคิดเห็นและเอกสารใช้ภาษาไทย | + +**❌ Common Violations พบบ่อย:** + +```typescript +// ไฟล์: ใช้ PascalCase (ผิด) แทน kebab-case (ถูก) +// ❌ 1701676800000-V1_5_1_Schema_Update.ts +// ✅ 1701676800000-v1-5-1-schema-update.ts + +// DTOs/Entities: ใช้ snake_case (ผิด) แทน camelCase (ถูก) +// ❌ document_number!: string; +// ✅ documentNumber!: string; + +// ❌ temp_attachment_id?: number; +// ✅ tempAttachmentId?: number; + +// ❌ project_id!: number; +// ✅ projectId!: number; + +// Interface properties ต้อง camelCase เสมอ +// ❌ workflow_code: string; +// ✅ workflowCode: string; +``` + +**⚠️ ข้อควรระวัง:** + +- **DB Columns** ใช้ snake_case แต่ต้องอยู่ใน `@Column({ name: 'snake_case' })` decorator เท่านั้น +- **Property names ใน TypeScript** ต้อง camelCase เสมอ — ไม่ว่าจะเป็น DTO, Entity, หรือ Interface +- **Form field names** ใน React Hook Form + Zod ต้อง camelCase +- **Type definitions** ทั้งหมดต้อง camelCase — ไม่มีข้อยกเว้น --- ## 🏷️ Domain Terminology (Glossary) อ้างอิง `specs/00-Overview/00-02-glossary.md` เสมอ — ใช้ term ผิดจะทำให้ spec ไม่ตรง -| ✅ ใช้ (Correct) | ❌ ห้ามใช้ (Wrong) | +| ✅ ใช้ (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) | +| 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) | --- @@ -446,15 +493,15 @@ pnpm --filter frontend test:e2e # E2E tests (Playwright) ``` | Type | ใช้เมื่อ | -| ---------- | ------------------------------------- | -| `feat` | เพิ่มฟีเจอร์ใหม่ | -| `fix` | แก้ bug | -| `refactor` | ปรับโครงสร้างโค้ด ไม่เปลี่ยน behavior | -| `docs` | แก้ไขเอกสาร | +| ---------- | ---------------------------------------- | +| `feat` | เพิ่มฟีเจอร์ใหม่ | +| `fix` | แก้ bug | +| `refactor` | ปรับโครงสร้างโค้ด ไม่เปลี่ยน behavior | +| `docs` | แก้ไขเอกสาร | | `test` | เพิ่ม/แก้ test | -| `chore` | งาน infra, config, dependency updates | +| `chore` | งาน infra, config, dependency updates | | `style` | Formatting, linting (ไม่เปลี่ยน logic) | -| `spec` | แก้ไข specs/ documents | +| `spec` | แก้ไข specs/ documents | | `adr` | เพิ่ม/แก้ไข Architecture Decision Record | **ตัวอย่าง:** @@ -491,12 +538,17 @@ adr/019-uuid-serialization-behavior `.windsurf/workflows/` — ใช้สำหรับ repeatable / complex tasks เช่น: -- UUID migration fixes (Phase 5.4) +- ADR-019 UUID pattern verification - Spec review & gap analysis - Security audit checklist - Release gate verification - เมื่อ task ซับซ้อนและทำซ้ำได้ ให้ดู `.windsurf/workflows/` ก่อนเขียน code ใหม่ - ---## 🚀 Deployment Rules (ADR-015) + +เมื่อ task ซับซ้อนและทำซ้ำได้ ให้ดู `.windsurf/workflows/` ก่อนเขียน code ใหม่ + +--- + +## 🚀 Deployment Rules (ADR-015) + - Docker Compose on **QNAP Container Station** (production). - **NO `.env` files in production** — secrets in `docker-compose.yml` environment section directly. - Blue-Green deployment strategy. @@ -507,7 +559,7 @@ adr/019-uuid-serialization-behavior ## 🚫 Forbidden Actions -| ❌ Forbidden | ✅ Correct Approach | +| ❌ Forbidden | ✅ Correct Approach | | ----------------------------------------------- | --------------------------------------------------------- | | SQL Triggers for business logic | NestJS Service methods | | `.env` files in production | `docker-compose.yml` environment section | @@ -524,7 +576,7 @@ adr/019-uuid-serialization-behavior | Generic domain terms (Letter, Blueprint, etc.) | Correct term from Glossary (`00-02-glossary.md`) | | 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` | +| 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 | @@ -563,16 +615,16 @@ adr/019-uuid-serialization-behavior ## 🎯 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 | +| คำถาม/คำสั่ง | ไฟล์ที่ต้องตรวจสอบก่อน | คำตอบที่คาดหวัง | +| -------------------- | ------------------------------------------------------- | ---------------------------------------------------------- | +| "สร้าง 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 MariaDB native UUID + 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 | --- @@ -630,15 +682,13 @@ if (!entity) { ### Frontend Query Pattern ```typescript -// [frontend-query] → TanStack Query มาตรฐาน +// [frontend-query] → TanStack Query v5 มาตรฐาน 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 }); - }, }); +// v5: onError ถูกลบออกจาก useQuery — จัดการ error ผ่าน return value +if (error) toast.error('ไม่สามารถโหลดข้อมูลได้'); ``` ### Redis Cache Pattern @@ -675,16 +725,16 @@ return null; // ❌ ทำให้ caller ต้องเช็คเอง ### Frontend (Next.js) ```typescript -// ✅ ถูกต้อง — ใช้ TanStack Query error handling +// ✅ ถูกต้อง — ใช้ TanStack Query v5 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 }); - }, }); +// v5: onError removed from useQuery — handle via error return value +if (error) { + // แสดง toast + fallback UI + toast.error('ไม่สามารถโหลดข้อมูลได้'); +} ``` ### Error Response Standard (Backend) @@ -773,45 +823,6 @@ async update(uuid: string, dto: UpdateDto) { --- -## 💬 Prompt Templates สำหรับถาม Windsurf - -### เมื่อต้องการสร้างฟีเจอร์ใหม่ - -``` -[NEW FEATURE] -Module: -Requirement: <อ้างอิง user story จาก 01-02-business-rules/> -Steps: -1. ตรวจสอบ glossary และ edge cases -2. ออกแบบ DTO + Schema ตาม ADR-019 -3. สร้าง Service + Controller พร้อม CASL guard -4. เขียน unit test สำหรับ business logic -5. อัพเดท API docs (Swagger) -Output: Code + Test + Spec reference -``` - -### เมื่อต้องการ debug - -``` -[DEBUG] -Issue: <อธิบายปัญหา> -File: -Error: -Steps taken: <สิ่งที่ลองแก้ไขแล้ว> -Request: วิเคราะห์ตาม spec + แนะนำวิธีแก้ที่สอดคล้องกับ ADRs -``` - -### เมื่อต้องการ review code - -``` -[CODE REVIEW] -File: -Focus: -Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table -``` - ---- - ## 📦 Infrastructure Quick Reference ### QNAP NAS (Container Station) — Production @@ -859,7 +870,8 @@ Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table | Version | Date | Changes | Updated By | | ------- | ---------- | --------------------------------------------------------------------------------------------------------------------- | -------------- | -| 1.8.3 | 2026-03-21 | + Rule Enforcement Tiers (🔴🟡🟢), + Tiered Development Flow | Human Dev + AI | +| 1.8.4 | 2026-03-24 | Phase 5.4→✅ DONE, Tailwind 3.4.3, ADR count(16), MariaDB UUID note, TanStack v5 patterns, formatting fix | Windsurf AI | +| 1.8.3 | 2026-03-21 | + Rule Enforcement Tiers (🔴🟡🟢), + Tiered Development Flow | Human Dev + AI | | 1.8.2 | 2026-03-21 | + Context Triggers, + Code Snippets, + Error Handling, + i18n, + Performance, + Testing Checklist, + Prompt Templates | Human Dev + AI | | 1.8.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 | @@ -870,7 +882,7 @@ Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table 1. แก้ไขในส่วนที่เกี่ยวข้อง 2. อัพเดทตาราง Change Log ด้านบน 3. เพิ่ม version number ใน header -4. Commit ด้วย message: `spec(windsurfrules): bump to v1.8.3 - ` +4. Commit ด้วย message: `spec(windsurfrules): bump to v1.8.4 - ` --- diff --git a/.vscode/settings.json b/.vscode/settings.json index ed48914..6f19320 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "editor.fontSize": 20 + "editor.fontSize": 20, + "npm.packageManager": "pnpm" } diff --git a/.windsurfrules b/.windsurfrules index 502a3a6..97544b5 100644 --- a/.windsurfrules +++ b/.windsurfrules @@ -282,11 +282,69 @@ onValueChange={(v) => setValue("projectId", parseInt(v))} - **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`. + +### 🟡 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** — ดูรายละเอียดที่ "🚨 Naming Conventions Pain Point" ด้านล่าง + +### 🟢 Tier 3 — GUIDELINES + +Best practice — ทำตามถ้าทำได้ ไม่ block: + +- Code style / formatting (Prettier จัดให้) +- Comment completeness +- Minor optimizations + +--- + +## 🚨 Naming Conventions Pain Point (เรื่องที่ผิดบ่อยที่สุด) + +**สถานะ:** พบ violations จำนวนมากใน codebase — ต้อง fix ก่อน merge เสมอ + +**ข้อตกลงหลัก:** + +| Target | Convention | Example | +| ------------------- | ----------- | --------------------------- | +| **Files/Folders** | kebab-case | `user-service.ts` | +| **Classes** | PascalCase | `UserService` | +| **Variables/Functions** | camelCase | `firstName`, `getUserInfo` | +| **DB Columns** | snake_case | `user_id`, `created_at` | +| **Boolean vars** | verb + noun | `isActive`, `hasPermission` | +| **Code** | English | All identifiers in English | +| **Comments/Docs** | Thai | ความคิดเห็นและเอกสารใช้ภาษาไทย | + +**❌ Common Violations พบบ่อย:** + +```typescript +// ไฟล์: ใช้ PascalCase (ผิด) แทน kebab-case (ถูก) +// ❌ 1701676800000-V1_5_1_Schema_Update.ts +// ✅ 1701676800000-v1-5-1-schema-update.ts + +// DTOs/Entities: ใช้ snake_case (ผิด) แทน camelCase (ถูก) +// ❌ document_number!: string; +// ✅ documentNumber!: string; + +// ❌ temp_attachment_id?: number; +// ✅ tempAttachmentId?: number; + +// ❌ project_id!: number; +// ✅ projectId!: number; + +// Interface properties ต้อง camelCase เสมอ +// ❌ workflow_code: string; +// ✅ workflowCode: string; +``` + +**⚠️ ข้อควรระวัง:** +- **DB Columns** ใช้ snake_case แต่ต้องอยู่ใน `@Column({ name: 'snake_case' })` decorator เท่านั้น +- **Property names ใน TypeScript** ต้อง camelCase เสมอ — ไม่ว่าจะเป็น DTO, Entity, หรือ Interface +- **Form field names** ใน React Hook Form + Zod ต้อง camelCase +- **Type definitions** ทั้งหมดต้อง camelCase — ไม่มีข้อยกเว้น --- @@ -774,45 +832,6 @@ async update(uuid: string, dto: UpdateDto) { --- -## 💬 Prompt Templates สำหรับถาม Windsurf - -### เมื่อต้องการสร้างฟีเจอร์ใหม่ - -``` -[NEW FEATURE] -Module: -Requirement: <อ้างอิง user story จาก 01-02-business-rules/> -Steps: -1. ตรวจสอบ glossary และ edge cases -2. ออกแบบ DTO + Schema ตาม ADR-019 -3. สร้าง Service + Controller พร้อม CASL guard -4. เขียน unit test สำหรับ business logic -5. อัพเดท API docs (Swagger) -Output: Code + Test + Spec reference -``` - -### เมื่อต้องการ debug - -``` -[DEBUG] -Issue: <อธิบายปัญหา> -File: -Error: -Steps taken: <สิ่งที่ลองแก้ไขแล้ว> -Request: วิเคราะห์ตาม spec + แนะนำวิธีแก้ที่สอดคล้องกับ ADRs -``` - -### เมื่อต้องการ review code - -``` -[CODE REVIEW] -File: -Focus: -Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table -``` - ---- - ## 📦 Infrastructure Quick Reference ### QNAP NAS (Container Station) — Production diff --git a/AGENTS.md b/AGENTS.md index 98eee2f..3018bec 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,8 @@ # 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: Windsurf Cascade (and compatible: Codex CLI, opencode, Amp, Amazon Q, AGENTS.md tools) +- Version: 1.8.4 (Accuracy Pass) | Last synced from repo: 2026-03-24 +- Repo: [https://git.np-dms.work/np-dms/lcbp3](https://git.np-dms.work/np-dms/lcbp3) --- @@ -64,16 +65,16 @@ Best practice — ทำตามถ้าทำได้ ไม่ block: **LCBP3-DMS (Laem Chabang Port Phase 3 - Document Management System)** ระบบบริหารจัดการเอกสารโครงการก่อสร้างท่าเรือแหลมฉบังระยะที่ 3 **Version:** 1.8.3 (Enforcement Tiers Added) | **Status:** UAT In Progress, Security Hardened (2026-03-19) -| Area | Status | Notes | +| Area | Status | Notes | | ------------- | ---------------------- | ------------------------------------------------------ | -| Backend | ✅ Production Ready | NestJS 11, Express v5, 0 Vulnerabilities | -| Frontend | ✅ Quality Hardened | Next.js 16.2.0, React 19.2.4, 0 `any`, 0 `console.log` | -| Database | ✅ Schema v1.8.0 Stable | MariaDB 11.8, No-migration (ADR-009) | -| Documentation | ✅ 10/10 Gaps Closed | Product Vision → Release Policy | -| AI Migration | 🔄 Pre-migration Setup | n8n + Ollama (ADR-017/018) | -| Testing | 🔄 UAT In Progress | Per `01-05-acceptance-criteria.md` | -| Deployment | 📋 Pending Go-Live Gate | Blue-Green, QNAP Container Station | -| ADR-019 UUID | 🔄 Phase 5.4 Pending | 4 frontend files still use `parseInt()` on UUID | +| Backend | ✅ Production Ready | NestJS 11, Express v5, 0 Vulnerabilities | +| Frontend | ✅ Quality Hardened | Next.js 16.2.0, React 19.2.4, 0 `any`, 0 `console.log` | +| Database | ✅ Schema v1.8.0 Stable | MariaDB 11.8, No-migration (ADR-009) | +| Documentation | ✅ 10/10 Gaps Closed | Product Vision → Release Policy | +| AI Migration | 🔄 Pre-migration Setup | n8n + Ollama (ADR-017/018) | +| Testing | 🔄 UAT In Progress | Per `01-05-acceptance-criteria.md` | +| Deployment | 📋 Pending Go-Live Gate | Blue-Green, QNAP Container Station | +| ADR-019 UUID | ✅ All Phases Complete | Phase 5.4 done — all UUID FK issues resolved | **Domain:** `np-dms.work` --- @@ -96,7 +97,7 @@ Best practice — ทำตามถ้าทำได้ ไม่ block: ### Frontend - **Next.js 16.2.0** (App Router + `proxy.ts`) + **React 19.2.4** -- **Tailwind CSS 4.2.2** + **Shadcn/UI** +- **Tailwind CSS 3.4.3** + **Shadcn/UI** - **TanStack Query** — **Server State only** - **Zustand** — **Client State only** - **React Hook Form 7.71.2** + **Zod 4.3.6** + **@hookform/resolvers 3.9.0** — **Form State only** @@ -122,7 +123,7 @@ Best practice — ทำตามถ้าทำได้ ไม่ block: ### Security Overrides (pnpm overrides active in root `package.json`) -- `uuid@11.0.0`, `multer@>=2.1.1`, `undici@>=7.24.0`, `axios@>=1.13.5` — patched +- 30+ security overrides active (`multer@>=2.1.1`, `undici@>=7.24.0`, `axios@>=1.13.5`, etc.) - All 52 vulnerabilities resolved as of 2026-03-19 (27 high + 20 moderate + 5 low) --- @@ -130,23 +131,23 @@ Best practice — ทำตามถ้าทำได้ ไม่ block: ## 🗂️ Key Spec Files (Always Check Before Writing Code) Spec priority: **`06-Decision-Records`** > **`05-Engineering-Guidelines`** > others -| เอกสาร | Path (relative to `specs/`) | ใช้เมื่อ | +| เอกสาร | Path (relative to `specs/`) | ใช้เมื่อ | | ------------------------- | -------------------------------------------------------------------- | ----------------------------------- | -| **Glossary** | `00-Overview/00-02-glossary.md` | ตรวจคำศัพท์ Domain ก่อนเขียนเสมอ | -| **Schema Tables** | `03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql` | ก่อนเขียน Query ทุกครั้ง | -| **Data Dictionary** | `03-Data-and-Storage/03-01-data-dictionary.md` | ตรวจ Field Meaning + Business Rules | -| **Seed Permissions** | `03-Data-and-Storage/lcbp3-v1.8.0-seed-permissions.sql` | ตรวจ CASL Permission Matrix | -| **Edge Cases (37 rules)** | `01-Requirements/01-06-edge-cases-and-rules.md` | ป้องกัน Bug ทุก Flow | -| **Migration Scope** | `03-Data-and-Storage/03-06-migration-business-scope.md` | งาน Migration Bot (20K docs) | -| **Release Policy** | `04-Infrastructure-OPS/04-08-release-management-policy.md` | ก่อน Deploy / Hotfix | -| **UAT Criteria** | `01-Requirements/01-05-acceptance-criteria.md` | ตรวจความสมบูรณ์ Feature | -| **UUID Implementation** | `05-Engineering-Guidelines/05-07-hybrid-uuid-implementation-plan.md` | ADR-019 UUID Migration (Phase 1–6) | -| **Backend Guidelines** | `05-Engineering-Guidelines/05-02-backend-guidelines.md` | NestJS patterns & best practices | -| **Frontend Guidelines** | `05-Engineering-Guidelines/05-03-frontend-guidelines.md` | Next.js patterns & best practices | -| **Testing Strategy** | `05-Engineering-Guidelines/05-04-testing-strategy.md` | Coverage goals & test patterns | -| **ADR-009 DB Strategy** | `06-Decision-Records/ADR-009-db-strategy.md` | Schema Change Process | -| **ADR-018 AI Boundary** | `06-Decision-Records/ADR-018-ai-boundary.md` | AI/Ollama Integration Rules | -| **ADR-019 Hybrid ID** | `06-Decision-Records/ADR-019-hybrid-identifier-strategy.md` | Hybrid ID Strategy (INT + UUIDv7) | +| **Glossary** | `00-Overview/00-02-glossary.md` | ตรวจคำศัพท์ Domain ก่อนเขียนเสมอ | +| **Schema Tables** | `03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql` | ก่อนเขียน Query ทุกครั้ง | +| **Data Dictionary** | `03-Data-and-Storage/03-01-data-dictionary.md` | ตรวจ Field Meaning + Business Rules | +| **Seed Permissions** | `03-Data-and-Storage/lcbp3-v1.8.0-seed-permissions.sql` | ตรวจ CASL Permission Matrix | +| **Edge Cases (37 rules)** | `01-Requirements/01-06-edge-cases-and-rules.md` | ป้องกัน Bug ทุก Flow | +| **Migration Scope** | `03-Data-and-Storage/03-06-migration-business-scope.md` | งาน Migration Bot (20K docs) | +| **Release Policy** | `04-Infrastructure-OPS/04-08-release-management-policy.md` | ก่อน Deploy / Hotfix | +| **UAT Criteria** | `01-Requirements/01-05-acceptance-criteria.md` | ตรวจความสมบูรณ์ Feature | +| **UUID Implementation** | `05-Engineering-Guidelines/05-07-hybrid-uuid-implementation-plan.md` | ADR-019 UUID Migration (Phase 1–6) | +| **Backend Guidelines** | `05-Engineering-Guidelines/05-02-backend-guidelines.md` | NestJS patterns & best practices | +| **Frontend Guidelines** | `05-Engineering-Guidelines/05-03-frontend-guidelines.md` | Next.js patterns & best practices | +| **Testing Strategy** | `05-Engineering-Guidelines/05-04-testing-strategy.md` | Coverage goals & test patterns | +| **ADR-009 DB Strategy** | `06-Decision-Records/ADR-009-db-strategy.md` | Schema Change Process | +| **ADR-018 AI Boundary** | `06-Decision-Records/ADR-018-ai-boundary.md` | AI/Ollama Integration Rules | +| **ADR-019 Hybrid ID** | `06-Decision-Records/ADR-019-hybrid-identifier-strategy.md` | Hybrid ID Strategy (INT + UUIDv7) | ### Specs Directory Structure (Brief) @@ -160,7 +161,7 @@ specs/ ├── 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) +├── 06-Decision-Records/ # 16 ADRs defined (15 with file, ADR-003/004/007 not created) └── 99-archives/ # ประวัติ tasks เก่า ``` @@ -170,28 +171,30 @@ Schema is split — modify the correct file: - `lcbp3-v1.8.0-schema-02-tables.sql` ← **primary reference for all queries** - `lcbp3-v1.8.0-schema-03-views-indexes.sql` +> **UUID Storage (MariaDB-specific):** Column type is `uuid UUID NOT NULL DEFAULT UUID()` — MariaDB native UUID, NOT MySQL's `BINARY(16)` + `UUID_TO_BIN()`/`BIN_TO_UUID()`. No transformer needed. `x.uuid` in Views directly (no conversion function). + --- -## 📐 ADR Reference (19 total) +## 📐 ADR Reference (16 defined) -| ADR | Topic | Key Decision | -| ------- | -------------------------- | -------------------------------------------------- | -| ADR-001 | Workflow Engine | Unified state machine for document workflows | -| ADR-002 | Doc Numbering | Redis Redlock + DB optimistic locking | -| ADR-005 | Technology Stack | NestJS + Next.js + MariaDB + Redis | -| ADR-006 | Redis Caching | Cache strategy and invalidation patterns | -| ADR-008 | Email Notification | BullMQ queue-based email/LINE/in-app | -| ADR-009 | DB Strategy | No TypeORM migrations — modify schema SQL directly | -| ADR-010 | Logging/Monitoring | Prometheus + Loki + Grafana stack | -| ADR-011 | App Router | Next.js App Router with RSC patterns | -| ADR-012 | UI Components | Shadcn/UI component library | -| ADR-013 | Form Handling | React Hook Form + Zod validation | -| 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 (~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) | +| ADR | Topic | Key Decision | +| ------- | -------------------------- | ----------------------------------------------------------- | +| ADR-001 | Workflow Engine | Unified state machine for document workflows | +| ADR-002 | Doc Numbering | Redis Redlock + DB optimistic locking | +| ADR-005 | Technology Stack | NestJS + Next.js + MariaDB + Redis | +| ADR-006 | Redis Caching | Cache strategy and invalidation patterns | +| ADR-008 | Email Notification | BullMQ queue-based email/LINE/in-app | +| ADR-009 | DB Strategy | No TypeORM migrations — modify schema SQL directly | +| ADR-010 | Logging/Monitoring | Prometheus + Loki + Grafana stack | +| ADR-011 | App Router | Next.js App Router with RSC patterns | +| ADR-012 | UI Components | Shadcn/UI component library | +| ADR-013 | Form Handling | React Hook Form + Zod validation | +| 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 (~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 MariaDB native UUID (public API) | --- @@ -200,26 +203,26 @@ Schema is split — modify the correct file: ### Rule Summary - **Internal / DB FK:** `INT AUTO_INCREMENT` (Primary Key) — never exposed -- **Public API / URL:** `UUIDv7` stored as `BINARY(16)` +- **Public API / URL:** `UUIDv7` stored as MariaDB native `UUID` type (auto-stored as BINARY(16), no transformer needed) - Read `05-07-hybrid-uuid-implementation-plan.md` before any UUID-related work. -### ⚠️ Phase 5.4 — Pending Fix (as of 2026-03-20) +### ✅ Phase 5.4 — COMPLETED (2026-03-21) -These files still call `parseInt()` on UUID values — **fix when touching these files**: +All UUID FK issues resolved — no more `parseInt()` on UUID values: -- `frontend/components/correspondences/form.tsx` -- `frontend/components/user-dialog.tsx` -- `frontend/components/numbering/template-tester.tsx` -- `frontend/app/(dashboard)/rfas/page.tsx` +- `frontend/components/correspondences/form.tsx` ✅ fixed +- `frontend/components/admin/user-dialog.tsx` ✅ fixed +- `frontend/components/numbering/template-tester.tsx` ✅ fixed +- `frontend/app/(dashboard)/rfas/page.tsx` ✅ fixed ### UUID Serialization Behavior (TransformInterceptor) `TransformInterceptor` uses `instanceToPlain()` — `@Exclude()` and `@Expose()` decorators are active on all responses. -| Entity Type | Behavior | +| Entity Type | Behavior | | ------------------ | ---------------------------------------------------------------------- | -| All entities | INT `id` has `@Exclude()` → **never appears in API response** | +| All entities | INT `id` has `@Exclude()` → **never appears in API response** | | Project / Contract | `uuid` has `@Expose({ name: 'id' })` → response has `id` = UUID string | -| Other entities | Separate `uuid` field → response has `uuid`, no `id` | +| Other entities | Separate `uuid` field → response has `uuid`, no `id` | ### UUID Patterns (Backend Controller) @@ -280,42 +283,87 @@ onValueChange={(v) => setValue("projectId", parseInt(v))} - **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`. + +### 🟡 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** — ดูรายละเอียดที่ "🚨 Naming Conventions Pain Point" ด้านล่าง + +### 🟢 Tier 3 — GUIDELINES + +Best practice — ทำตามถ้าทำได้ ไม่ block: + +- Code style / formatting (Prettier จัดให้) +- Comment completeness +- Minor optimizations --- -## 📝 Naming Conventions +## � Naming Conventions Pain Point (เรื่องที่ผิดบ่อยที่สุด) -| Target | Convention | Example | -| ------------------- | ----------- | --------------------------- | -| 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 | ความคิดเห็นและเอกสารใช้ภาษาไทย | +**สถานะ:** พบ violations จำนวนมากใน codebase — ต้อง fix ก่อน merge เสมอ + +**ข้อตกลงหลัก:** + +| Target | Convention | Example | +| ----------------------- | ----------- | ------------------------------ | +| **Files/Folders** | kebab-case | `user-service.ts` | +| **Classes** | PascalCase | `UserService` | +| **Variables/Functions** | camelCase | `firstName`, `getUserInfo` | +| **DB Columns** | snake_case | `user_id`, `created_at` | +| **Boolean vars** | verb + noun | `isActive`, `hasPermission` | +| **Code** | English | All identifiers in English | +| **Comments/Docs** | Thai | ความคิดเห็นและเอกสารใช้ภาษาไทย | + +**❌ Common Violations พบบ่อย:** + +```typescript +// ไฟล์: ใช้ PascalCase (ผิด) แทน kebab-case (ถูก) +// ❌ 1701676800000-V1_5_1_Schema_Update.ts +// ✅ 1701676800000-v1-5-1-schema-update.ts + +// DTOs/Entities: ใช้ snake_case (ผิด) แทน camelCase (ถูก) +// ❌ document_number!: string; +// ✅ documentNumber!: string; + +// ❌ temp_attachment_id?: number; +// ✅ tempAttachmentId?: number; + +// ❌ project_id!: number; +// ✅ projectId!: number; + +// Interface properties ต้อง camelCase เสมอ +// ❌ workflow_code: string; +// ✅ workflowCode: string; +``` + +**⚠️ ข้อควรระวัง:** + +- **DB Columns** ใช้ snake_case แต่ต้องอยู่ใน `@Column({ name: 'snake_case' })` decorator เท่านั้น +- **Property names ใน TypeScript** ต้อง camelCase เสมอ — ไม่ว่าจะเป็น DTO, Entity, หรือ Interface +- **Form field names** ใน React Hook Form + Zod ต้อง camelCase +- **Type definitions** ทั้งหมดต้อง camelCase — ไม่มีข้อยกเว้น --- ## 🏷️ Domain Terminology (Glossary) อ้างอิง `specs/00-Overview/00-02-glossary.md` เสมอ — ใช้ term ผิดจะทำให้ spec ไม่ตรง -| ✅ ใช้ (Correct) | ❌ ห้ามใช้ (Wrong) | +| ✅ ใช้ (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) | +| 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) | --- @@ -443,15 +491,15 @@ pnpm --filter frontend test:e2e # E2E tests (Playwright) ``` | Type | ใช้เมื่อ | -| ---------- | ------------------------------------- | -| `feat` | เพิ่มฟีเจอร์ใหม่ | -| `fix` | แก้ bug | -| `refactor` | ปรับโครงสร้างโค้ด ไม่เปลี่ยน behavior | -| `docs` | แก้ไขเอกสาร | +| ---------- | ---------------------------------------- | +| `feat` | เพิ่มฟีเจอร์ใหม่ | +| `fix` | แก้ bug | +| `refactor` | ปรับโครงสร้างโค้ด ไม่เปลี่ยน behavior | +| `docs` | แก้ไขเอกสาร | | `test` | เพิ่ม/แก้ test | -| `chore` | งาน infra, config, dependency updates | +| `chore` | งาน infra, config, dependency updates | | `style` | Formatting, linting (ไม่เปลี่ยน logic) | -| `spec` | แก้ไข specs/ documents | +| `spec` | แก้ไข specs/ documents | | `adr` | เพิ่ม/แก้ไข Architecture Decision Record | **ตัวอย่าง:** @@ -488,12 +536,17 @@ adr/019-uuid-serialization-behavior `.windsurf/workflows/` — ใช้สำหรับ repeatable / complex tasks เช่น: -- UUID migration fixes (Phase 5.4) +- ADR-019 UUID pattern verification - Spec review & gap analysis - Security audit checklist - Release gate verification - เมื่อ task ซับซ้อนและทำซ้ำได้ ให้ดู `.windsurf/workflows/` ก่อนเขียน code ใหม่ - ---## 🚀 Deployment Rules (ADR-015) + +เมื่อ task ซับซ้อนและทำซ้ำได้ ให้ดู `.windsurf/workflows/` ก่อนเขียน code ใหม่ + +--- + +## 🚀 Deployment Rules (ADR-015) + - Docker Compose on **QNAP Container Station** (production). - **NO `.env` files in production** — secrets in `docker-compose.yml` environment section directly. - Blue-Green deployment strategy. @@ -504,7 +557,7 @@ adr/019-uuid-serialization-behavior ## 🚫 Forbidden Actions -| ❌ Forbidden | ✅ Correct Approach | +| ❌ Forbidden | ✅ Correct Approach | | ----------------------------------------------- | --------------------------------------------------------- | | SQL Triggers for business logic | NestJS Service methods | | `.env` files in production | `docker-compose.yml` environment section | @@ -521,7 +574,7 @@ adr/019-uuid-serialization-behavior | Generic domain terms (Letter, Blueprint, etc.) | Correct term from Glossary (`00-02-glossary.md`) | | 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` | +| 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 | @@ -560,16 +613,16 @@ adr/019-uuid-serialization-behavior ## 🎯 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 | +| คำถาม/คำสั่ง | ไฟล์ที่ต้องตรวจสอบก่อน | คำตอบที่คาดหวัง | +| -------------------- | ------------------------------------------------------- | ---------------------------------------------------------- | +| "สร้าง 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 MariaDB native UUID + 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 | --- @@ -627,15 +680,13 @@ if (!entity) { ### Frontend Query Pattern ```typescript -// [frontend-query] → TanStack Query มาตรฐาน +// [frontend-query] → TanStack Query v5 มาตรฐาน 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 }); - }, }); +// v5: onError ถูกลบออกจาก useQuery — จัดการ error ผ่าน return value +if (error) toast.error('ไม่สามารถโหลดข้อมูลได้'); ``` ### Redis Cache Pattern @@ -672,16 +723,16 @@ return null; // ❌ ทำให้ caller ต้องเช็คเอง ### Frontend (Next.js) ```typescript -// ✅ ถูกต้อง — ใช้ TanStack Query error handling +// ✅ ถูกต้อง — ใช้ TanStack Query v5 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 }); - }, }); +// v5: onError removed from useQuery — handle via error return value +if (error) { + // แสดง toast + fallback UI + toast.error('ไม่สามารถโหลดข้อมูลได้'); +} ``` ### Error Response Standard (Backend) @@ -770,45 +821,6 @@ async update(uuid: string, dto: UpdateDto) { --- -## 💬 Prompt Templates สำหรับถาม Windsurf - -### เมื่อต้องการสร้างฟีเจอร์ใหม่ - -``` -[NEW FEATURE] -Module: -Requirement: <อ้างอิง user story จาก 01-02-business-rules/> -Steps: -1. ตรวจสอบ glossary และ edge cases -2. ออกแบบ DTO + Schema ตาม ADR-019 -3. สร้าง Service + Controller พร้อม CASL guard -4. เขียน unit test สำหรับ business logic -5. อัพเดท API docs (Swagger) -Output: Code + Test + Spec reference -``` - -### เมื่อต้องการ debug - -``` -[DEBUG] -Issue: <อธิบายปัญหา> -File: -Error: -Steps taken: <สิ่งที่ลองแก้ไขแล้ว> -Request: วิเคราะห์ตาม spec + แนะนำวิธีแก้ที่สอดคล้องกับ ADRs -``` - -### เมื่อต้องการ review code - -``` -[CODE REVIEW] -File: -Focus: -Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table -``` - ---- - ## 📦 Infrastructure Quick Reference ### QNAP NAS (Container Station) — Production @@ -856,7 +868,8 @@ Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table | Version | Date | Changes | Updated By | | ------- | ---------- | --------------------------------------------------------------------------------------------------------------------- | -------------- | -| 1.8.3 | 2026-03-21 | + Rule Enforcement Tiers (🔴🟡🟢), + Tiered Development Flow | Human Dev + AI | +| 1.8.4 | 2026-03-24 | Phase 5.4→✅ DONE, Tailwind 3.4.3, ADR count(16), MariaDB UUID note, TanStack v5 patterns, formatting fix | Windsurf AI | +| 1.8.3 | 2026-03-21 | + Rule Enforcement Tiers (🔴🟡🟢), + Tiered Development Flow | Human Dev + AI | | 1.8.2 | 2026-03-21 | + Context Triggers, + Code Snippets, + Error Handling, + i18n, + Performance, + Testing Checklist, + Prompt Templates | Human Dev + AI | | 1.8.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 | @@ -867,7 +880,7 @@ Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table 1. แก้ไขในส่วนที่เกี่ยวข้อง 2. อัพเดทตาราง Change Log ด้านบน 3. เพิ่ม version number ใน header -4. Commit ด้วย message: `spec(windsurfrules): bump to v1.8.3 - ` +4. Commit ด้วย message: `spec(windsurfrules): bump to v1.8.4 - ` --- diff --git a/backend/.vscode/settings.json b/backend/.vscode/settings.json new file mode 100644 index 0000000..9e3a076 --- /dev/null +++ b/backend/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "npm.packageManager": "pnpm" +} diff --git a/backend/src/database/migrations/1701676800000-V1_5_1_Schema_Update.ts b/backend/src/database/migrations/1701676800000-v1-5-1-schema-update.ts similarity index 100% rename from backend/src/database/migrations/1701676800000-V1_5_1_Schema_Update.ts rename to backend/src/database/migrations/1701676800000-v1-5-1-schema-update.ts diff --git a/backend/src/database/migrations/InitialSchema.ts b/backend/src/database/migrations/initial-schema.ts similarity index 100% rename from backend/src/database/migrations/InitialSchema.ts rename to backend/src/database/migrations/initial-schema.ts diff --git a/backend/src/modules/document-numbering/services/format.service.ts b/backend/src/modules/document-numbering/services/format.service.ts index 89a06c5..e2d788c 100644 --- a/backend/src/modules/document-numbering/services/format.service.ts +++ b/backend/src/modules/document-numbering/services/format.service.ts @@ -88,7 +88,7 @@ export class FormatService { // 3. Fallback return { - template: '{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR:BE}', + template: '{ORG}-{RECIPIENT}-{SEQ:4}/{YEAR:BE}', resetSequenceYearly: true, isDefault: true, }; diff --git a/backend/src/modules/json-schema/dto/create-json-schema.dto.ts b/backend/src/modules/json-schema/dto/create-json-schema.dto.ts index 5873b20..3031b81 100644 --- a/backend/src/modules/json-schema/dto/create-json-schema.dto.ts +++ b/backend/src/modules/json-schema/dto/create-json-schema.dto.ts @@ -14,23 +14,23 @@ import { Type } from 'class-transformer'; export class VirtualColumnConfigDto { @IsString() @IsNotEmpty() - json_path!: string; + jsonPath!: string; @IsString() @IsNotEmpty() - column_name!: string; + columnName!: string; @IsString() @IsNotEmpty() - data_type!: 'INT' | 'VARCHAR' | 'BOOLEAN' | 'DATE' | 'DECIMAL' | 'DATETIME'; + dataType!: 'INT' | 'VARCHAR' | 'BOOLEAN' | 'DATE' | 'DECIMAL' | 'DATETIME'; @IsString() @IsOptional() - index_type?: 'INDEX' | 'UNIQUE' | 'FULLTEXT'; + indexType?: 'INDEX' | 'UNIQUE' | 'FULLTEXT'; @IsBoolean() @IsOptional() - is_required?: boolean; + isRequired?: boolean; } export class CreateJsonSchemaDto { diff --git a/backend/src/modules/json-schema/entities/json-schema.entity.ts b/backend/src/modules/json-schema/entities/json-schema.entity.ts index 3edf59b..7a82cc2 100644 --- a/backend/src/modules/json-schema/entities/json-schema.entity.ts +++ b/backend/src/modules/json-schema/entities/json-schema.entity.ts @@ -9,11 +9,11 @@ import { } from 'typeorm'; export interface VirtualColumnConfig { - json_path: string; - column_name: string; - data_type: 'INT' | 'VARCHAR' | 'BOOLEAN' | 'DATE' | 'DECIMAL' | 'DATETIME'; - index_type?: 'INDEX' | 'UNIQUE' | 'FULLTEXT'; - is_required: boolean; + jsonPath: string; + columnName: string; + dataType: 'INT' | 'VARCHAR' | 'BOOLEAN' | 'DATE' | 'DECIMAL' | 'DATETIME'; + indexType?: 'INDEX' | 'UNIQUE' | 'FULLTEXT'; + isRequired: boolean; } @Entity('json_schemas') diff --git a/backend/src/modules/master/entities/tag.entity.ts b/backend/src/modules/master/entities/tag.entity.ts index 5564dd0..4741527 100644 --- a/backend/src/modules/master/entities/tag.entity.ts +++ b/backend/src/modules/master/entities/tag.entity.ts @@ -12,19 +12,19 @@ import { import { Project } from '../../project/entities/project.entity'; @Entity('tags') -@Unique('ux_tag_project', ['project_id', 'tag_name']) +@Unique('ux_tag_project', ['projectId', 'tagName']) export class Tag { @PrimaryGeneratedColumn() id!: number; // เพิ่ม ! @Column({ type: 'int', nullable: true }) - project_id!: number | null; // เพิ่ม ! + projectId!: number | null; // เพิ่ม ! @Column({ length: 100 }) - tag_name!: string; // เพิ่ม ! + tagName!: string; // เพิ่ม ! @Column({ length: 30, default: 'default' }) - color_code!: string; // เพิ่ม ! + colorCode!: string; // เพิ่ม ! @Column({ type: 'text', nullable: true }) description!: string | null; // เพิ่ม ! @@ -35,14 +35,14 @@ export class Tag { project?: Project; @CreateDateColumn() - created_at!: Date; // เพิ่ม ! + createdAt!: Date; // เพิ่ม ! @UpdateDateColumn() - updated_at!: Date; // เพิ่ม ! + updatedAt!: Date; // เพิ่ม ! @Column({ type: 'int', nullable: true }) - created_by!: number | null; // เพิ่ม ! + createdBy!: number | null; // เพิ่ม ! @DeleteDateColumn() - deleted_at!: Date | null; // เพิ่ม ! + deletedAt!: Date | null; // เพิ่ม ! } diff --git a/backend/src/modules/migration/dto/create-migration-error.dto.ts b/backend/src/modules/migration/dto/create-migration-error.dto.ts index 7b9dc4c..bd552a4 100644 --- a/backend/src/modules/migration/dto/create-migration-error.dto.ts +++ b/backend/src/modules/migration/dto/create-migration-error.dto.ts @@ -4,22 +4,22 @@ import { MigrationErrorType } from '../entities/migration-error.entity'; export class CreateMigrationErrorDto { @IsString() @IsOptional() - batch_id?: string; + batchId?: string; @IsString() @IsOptional() - document_number?: string; + documentNumber?: string; @IsString() @IsOptional() @IsEnum(MigrationErrorType) - error_type?: MigrationErrorType; + errorType?: MigrationErrorType; @IsString() @IsOptional() - error_message?: string; + errorMessage?: string; @IsString() @IsOptional() - raw_ai_response?: string; + rawAiResponse?: string; } diff --git a/backend/src/modules/migration/dto/enqueue-migration.dto.ts b/backend/src/modules/migration/dto/enqueue-migration.dto.ts index 51e7237..5e901e9 100644 --- a/backend/src/modules/migration/dto/enqueue-migration.dto.ts +++ b/backend/src/modules/migration/dto/enqueue-migration.dto.ts @@ -10,7 +10,7 @@ import { export class EnqueueMigrationDto { @IsString() @IsNotEmpty() - document_number!: string; + documentNumber!: string; @IsString() @IsOptional() @@ -18,7 +18,7 @@ export class EnqueueMigrationDto { @IsString() @IsOptional() - original_subject?: string; + originalSubject?: string; @IsString() @IsOptional() @@ -30,27 +30,27 @@ export class EnqueueMigrationDto { @IsString() @IsOptional() - ai_summary?: string; + aiSummary?: string; @IsNumber() @IsOptional() - project_id?: number; + projectId?: number; @IsNumber() @IsOptional() - sender_org_id?: number; + senderOrgId?: number; @IsNumber() @IsOptional() - receiver_org_id?: number; + receiverOrgId?: number; @IsString() @IsOptional() - issued_date?: string; + issuedDate?: string; @IsString() @IsOptional() - received_date?: string; + receivedDate?: string; @IsString() @IsOptional() @@ -58,18 +58,18 @@ export class EnqueueMigrationDto { @IsArray() @IsOptional() - extracted_tags?: Record[]; + extractedTags?: Record[]; @IsOptional() details?: Record; @IsNumber() @IsOptional() - temp_attachment_id?: number; + tempAttachmentId?: number; @IsBoolean() @IsOptional() - is_valid?: boolean; + isValid?: boolean; @IsNumber() @IsOptional() @@ -77,5 +77,5 @@ export class EnqueueMigrationDto { @IsArray() @IsOptional() - ai_issues?: Record[]; + aiIssues?: Record[]; } diff --git a/backend/src/modules/migration/dto/import-correspondence.dto.ts b/backend/src/modules/migration/dto/import-correspondence.dto.ts index ad6a660..715add4 100644 --- a/backend/src/modules/migration/dto/import-correspondence.dto.ts +++ b/backend/src/modules/migration/dto/import-correspondence.dto.ts @@ -9,7 +9,7 @@ import { export class ImportCorrespondenceDto { @IsString() @IsNotEmpty() - document_number!: string; + documentNumber!: string; @IsString() @IsNotEmpty() @@ -21,26 +21,26 @@ export class ImportCorrespondenceDto { @IsString() @IsOptional() - source_file_path?: string; + sourceFilePath?: string; @IsNumber() @IsOptional() - temp_attachment_id?: number; + tempAttachmentId?: number; @IsNumber() @IsOptional() - ai_confidence?: number; + aiConfidence?: number; @IsOptional() - ai_issues?: Record[]; + aiIssues?: Record[]; @IsString() @IsNotEmpty() - migrated_by!: string; // "SYSTEM_IMPORT" + migratedBy!: string; // "SYSTEM_IMPORT" @IsString() @IsNotEmpty() - batch_id!: string; + batchId!: string; @IsObject() @IsOptional() @@ -48,31 +48,31 @@ export class ImportCorrespondenceDto { @IsNumber() @IsNotEmpty() - project_id!: number; + projectId!: number; @IsString() @IsOptional() - issued_date?: string; + issuedDate?: string; @IsString() @IsOptional() - received_date?: string; + receivedDate?: string; @IsString() @IsOptional() - document_date?: string; + documentDate?: string; @IsNumber() @IsOptional() - discipline_id?: number; + disciplineId?: number; @IsNumber() @IsOptional() - sender_id?: number; + senderId?: number; @IsNumber() @IsOptional() - receiver_id?: number; + receiverId?: number; @IsString() @IsOptional() diff --git a/docs/Pompt.md b/docs/Pompt.md index 6bbf2c6..0a4a95c 100644 --- a/docs/Pompt.md +++ b/docs/Pompt.md @@ -726,6 +726,42 @@ AI-powered Document Management System 6 Automation workflow 7 Security ``` +## 💬 Prompt Templates สำหรับถาม Windsurf + +### เมื่อต้องการสร้างฟีเจอร์ใหม่ + +``` +[NEW FEATURE] +Module: +Requirement: <อ้างอิง user story จาก 01-02-business-rules/> +Steps: +1. ตรวจสอบ glossary และ edge cases +2. ออกแบบ DTO + Schema ตาม ADR-019 +3. สร้าง Service + Controller พร้อม CASL guard +4. เขียน unit test สำหรับ business logic +5. อัพเดท API docs (Swagger) +Output: Code + Test + Spec reference +``` + +### เมื่อต้องการ debug + +``` +[DEBUG] +Issue: <อธิบายปัญหา> +File: +Error: +Steps taken: <สิ่งที่ลองแก้ไขแล้ว> +Request: วิเคราะห์ตาม spec + แนะนำวิธีแก้ที่สอดคล้องกับ ADRs +``` + +### เมื่อต้องการ review code + +``` +[CODE REVIEW] +File: +Focus: +Request: ตรวจสอบตาม spec + ADRs + Forbidden Actions table +``` --- diff --git a/frontend/.vscode/settings.json b/frontend/.vscode/settings.json new file mode 100644 index 0000000..9e3a076 --- /dev/null +++ b/frontend/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "npm.packageManager": "pnpm" +} diff --git a/frontend/app/(admin)/admin/doc-control/reference/correspondence-types/page.tsx b/frontend/app/(admin)/admin/doc-control/reference/correspondence-types/page.tsx index 96ede86..73bafd6 100644 --- a/frontend/app/(admin)/admin/doc-control/reference/correspondence-types/page.tsx +++ b/frontend/app/(admin)/admin/doc-control/reference/correspondence-types/page.tsx @@ -8,28 +8,28 @@ import { CorrespondenceType } from '@/types/master-data'; export default function CorrespondenceTypesPage() { const columns: ColumnDef[] = [ { - accessorKey: 'type_code', + accessorKey: 'typeCode', header: 'Code', - cell: ({ row }) => {row.getValue('type_code')}, + cell: ({ row }) => {row.getValue('typeCode')}, }, { - accessorKey: 'type_name', + accessorKey: 'typeName', header: 'Name', }, { - accessorKey: 'sort_order', + accessorKey: 'sortOrder', header: 'Sort Order', }, { - accessorKey: 'is_active', + accessorKey: 'isActive', header: 'Status', cell: ({ row }) => ( - {row.getValue('is_active') ? 'Active' : 'Inactive'} + {row.getValue('isActive') ? 'Active' : 'Inactive'} ), }, @@ -52,10 +52,10 @@ export default function CorrespondenceTypesPage() { deleteFn={(id) => masterDataService.deleteCorrespondenceType(id)} columns={columns} fields={[ - { 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' }, + { 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' }, ]} /> diff --git a/frontend/app/(admin)/admin/doc-control/reference/disciplines/page.tsx b/frontend/app/(admin)/admin/doc-control/reference/disciplines/page.tsx index 1ca2cfd..c9e1299 100644 --- a/frontend/app/(admin)/admin/doc-control/reference/disciplines/page.tsx +++ b/frontend/app/(admin)/admin/doc-control/reference/disciplines/page.tsx @@ -17,28 +17,28 @@ export default function DisciplinesPage() { const columns: ColumnDef[] = [ { - accessorKey: 'discipline_code', + accessorKey: 'disciplineCode', header: 'Code', - cell: ({ row }) => {row.getValue('discipline_code')}, + cell: ({ row }) => {row.getValue('disciplineCode')}, }, { - accessorKey: 'code_name_th', + accessorKey: 'codeNameTh', header: 'Name (TH)', }, { - accessorKey: 'code_name_en', + accessorKey: 'codeNameEn', header: 'Name (EN)', }, { - accessorKey: 'is_active', + accessorKey: 'isActive', header: 'Status', cell: ({ row }) => ( - {row.getValue('is_active') ? 'Active' : 'Inactive'} + {row.getValue('isActive') ? 'Active' : 'Inactive'} ), }, diff --git a/frontend/app/(admin)/admin/doc-control/reference/rfa-types/page.tsx b/frontend/app/(admin)/admin/doc-control/reference/rfa-types/page.tsx index 0fe9fe1..d38f0b7 100644 --- a/frontend/app/(admin)/admin/doc-control/reference/rfa-types/page.tsx +++ b/frontend/app/(admin)/admin/doc-control/reference/rfa-types/page.tsx @@ -17,16 +17,16 @@ export default function RfaTypesPage() { const columns: ColumnDef[] = [ { - accessorKey: 'type_code', + accessorKey: 'typeCode', header: 'Code', - cell: ({ row }) => {row.getValue('type_code')}, + cell: ({ row }) => {row.getValue('typeCode')}, }, { - accessorKey: 'type_name_th', + accessorKey: 'typeNameTh', header: 'Name (TH)', }, { - accessorKey: 'type_name_en', + accessorKey: 'typeNameEn', header: 'Name (EN)', }, { @@ -34,15 +34,15 @@ export default function RfaTypesPage() { header: 'Remark', }, { - accessorKey: 'is_active', + accessorKey: 'isActive', header: 'Status', cell: ({ row }) => ( - {row.getValue('is_active') ? 'Active' : 'Inactive'} + {row.getValue('isActive') ? 'Active' : 'Inactive'} ), }, diff --git a/frontend/app/(admin)/admin/migration/review/[id]/page.tsx b/frontend/app/(admin)/admin/migration/review/[id]/page.tsx index a9a9230..5a5594e 100644 --- a/frontend/app/(admin)/admin/migration/review/[id]/page.tsx +++ b/frontend/app/(admin)/admin/migration/review/[id]/page.tsx @@ -18,25 +18,25 @@ import { toast } from 'sonner'; import { Card, CardContent } from '@/components/ui/card'; interface MigrationAiIssues { - document_date?: string; - issued_date?: string; - received_date?: string; - sender_id?: string | number; - discipline_id?: string | number; - source_file_path?: string; - key_points?: string[]; - validation_results?: Array<{ message: string; severity: string }>; + documentDate?: string; + issuedDate?: string; + receivedDate?: string; + senderId?: string | number; + disciplineId?: string | number; + sourceFilePath?: string; + keyPoints?: string[]; + validationResults?: Array<{ message: string; severity: string }>; } const reviewFormSchema = z.object({ - document_number: z.string().min(1, 'Document number is required'), + documentNumber: z.string().min(1, 'Document number is required'), subject: z.string().min(1, 'Subject is required'), category: z.string().min(1, 'Category is required'), - document_date: z.string().optional(), - issued_date: z.string().optional(), - received_date: z.string().optional(), - sender_id: z.string().optional(), - discipline_id: z.string().optional(), + documentDate: z.string().optional(), + issuedDate: z.string().optional(), + receivedDate: z.string().optional(), + senderId: z.string().optional(), + disciplineId: z.string().optional(), }); type ReviewFormValues = z.infer; @@ -53,14 +53,14 @@ export default function MigrationReviewPage() { const form = useForm({ resolver: zodResolver(reviewFormSchema), defaultValues: { - document_number: '', + documentNumber: '', subject: '', category: '', - document_date: '', - issued_date: '', - received_date: '', - sender_id: '', - discipline_id: '', + documentDate: '', + issuedDate: '', + receivedDate: '', + senderId: '', + disciplineId: '', }, }); @@ -75,14 +75,14 @@ export default function MigrationReviewPage() { // Pre-fill form from database item and aiIssues payload const issues = (res.aiIssues || {}) as MigrationAiIssues; form.reset({ - document_number: res.documentNumber || '', + documentNumber: res.documentNumber || '', subject: res.title || res.originalTitle || '', category: res.aiSuggestedCategory || '', - document_date: issues.document_date || '', - issued_date: issues.issued_date || '', - received_date: issues.received_date || '', - sender_id: issues.sender_id ? String(issues.sender_id) : '', - discipline_id: issues.discipline_id ? String(issues.discipline_id) : '', + documentDate: issues.documentDate || '', + issuedDate: issues.issuedDate || '', + receivedDate: issues.receivedDate || '', + senderId: issues.senderId ? String(issues.senderId) : '', + disciplineId: issues.disciplineId ? String(issues.disciplineId) : '', }); } } catch (_error) { @@ -107,21 +107,21 @@ export default function MigrationReviewPage() { const issues = item.aiIssues || {}; const payload = { - document_number: values.document_number, + documentNumber: values.documentNumber, subject: values.subject, category: values.category, - source_file_path: issues.source_file_path || '', - migrated_by: 'SYSTEM_IMPORT', - batch_id: 'MANUAL_REVIEW_BATCH', - project_id: 1, // Assumption or pulled from store - document_date: values.document_date, - issued_date: values.issued_date, - received_date: values.received_date, - sender_id: values.sender_id ? Number(values.sender_id) : undefined, - discipline_id: values.discipline_id ? Number(values.discipline_id) : undefined, + sourceFilePath: issues.sourceFilePath || '', + migratedBy: 'SYSTEM_IMPORT', + batchId: 'MANUAL_REVIEW_BATCH', + projectId: 1, // Assumption or pulled from store + documentDate: values.documentDate, + issuedDate: values.issuedDate, + receivedDate: values.receivedDate, + senderId: values.senderId ? Number(values.senderId) : undefined, + disciplineId: values.disciplineId ? Number(values.disciplineId) : undefined, details: { tags: issues.tags || [], - ai_confidence: item.aiConfidence, + aiConfidence: item.aiConfidence, }, }; @@ -162,8 +162,8 @@ export default function MigrationReviewPage() { return
Document not found
; } - const pdfUrl = (item.aiIssues as MigrationAiIssues)?.source_file_path - ? migrationService.getStagingFileUrl((item.aiIssues as MigrationAiIssues).source_file_path!) + const pdfUrl = (item.aiIssues as MigrationAiIssues)?.sourceFilePath + ? migrationService.getStagingFileUrl((item.aiIssues as MigrationAiIssues).sourceFilePath!) : null; return ( @@ -221,7 +221,7 @@ export default function MigrationReviewPage() {
( Document Number @@ -272,7 +272,7 @@ export default function MigrationReviewPage() { /> ( Discipline ID @@ -288,7 +288,7 @@ export default function MigrationReviewPage() {
( Doc Date @@ -300,7 +300,7 @@ export default function MigrationReviewPage() { /> ( Issued Date @@ -314,7 +314,7 @@ export default function MigrationReviewPage() { ( Sender Org ID @@ -326,11 +326,11 @@ export default function MigrationReviewPage() { )} /> - {(item.aiIssues as MigrationAiIssues)?.key_points && (item.aiIssues as MigrationAiIssues).key_points!.length > 0 && ( + {(item.aiIssues as MigrationAiIssues)?.keyPoints && (item.aiIssues as MigrationAiIssues).keyPoints!.length > 0 && (

AI Extracted Key Points

    - {(item.aiIssues as MigrationAiIssues).key_points!.map((point: string, i: number) => ( + {(item.aiIssues as MigrationAiIssues).keyPoints!.map((point: string, i: number) => (
  • {point}
  • ))}
diff --git a/frontend/types/circulation.ts b/frontend/types/circulation.ts index 1780b9a..e6de126 100644 --- a/frontend/types/circulation.ts +++ b/frontend/types/circulation.ts @@ -22,15 +22,15 @@ export interface CirculationRouting { updatedAt: string; // Joined relations from API assignee?: { - user_id: number; + userId: number; username: string; - first_name?: string; - last_name?: string; + firstName?: string; + lastName?: string; }; organization?: { id: number; - organization_code: string; - organization_name: string; + organizationCode: string; + organizationName: string; }; } @@ -55,20 +55,20 @@ export interface Circulation { correspondence?: { uuid: string; id?: number; - correspondence_number: string; + correspondenceNumber: string; }; organization?: { uuid: string; id?: number; - organization_code: string; - organization_name: string; + organizationCode: string; + organizationName: string; }; creator?: { uuid: string; - user_id?: number; + userId?: number; username: string; - first_name?: string; - last_name?: string; + firstName?: string; + lastName?: string; }; } diff --git a/frontend/types/dto/master/tag.dto.ts b/frontend/types/dto/master/tag.dto.ts index a8acec7..98f9732 100644 --- a/frontend/types/dto/master/tag.dto.ts +++ b/frontend/types/dto/master/tag.dto.ts @@ -2,13 +2,13 @@ export interface CreateTagDto { /** ID โครงการ (NULL = Global) */ - project_id?: number | null; + projectId?: number | null; /** ชื่อ Tag (เช่น 'URGENT') */ - tag_name: string; + tagName: string; /** รหัสสี หรือชื่อคลาสสำหรับ UI */ - color_code?: string; + colorCode?: string; /** คำอธิบาย */ description?: string; @@ -18,7 +18,7 @@ export type UpdateTagDto = Partial; export interface SearchTagDto { /** ID โครงการ (ใช้กรอง Tag ของแต่ละโปรเจกต์) */ - project_id?: number; + projectId?: number; /** คำค้นหา (ชื่อ Tag หรือ คำอธิบาย) */ search?: string; diff --git a/frontend/types/dto/workflow-engine/workflow-engine.dto.ts b/frontend/types/dto/workflow-engine/workflow-engine.dto.ts index 3915e1d..29edd80 100644 --- a/frontend/types/dto/workflow-engine/workflow-engine.dto.ts +++ b/frontend/types/dto/workflow-engine/workflow-engine.dto.ts @@ -7,13 +7,13 @@ export interface WorkflowDsl { /** Allow extra properties for different DSL formats */ [key: string]: unknown; states?: Record; - initial_state?: string; + initialState?: string; } export interface WorkflowState { transitions?: WorkflowTransition[]; - on_enter?: string[]; - on_exit?: string[]; + onEnter?: string[]; + onExit?: string[]; } export interface WorkflowTransition { @@ -26,13 +26,13 @@ export interface WorkflowTransition { // --- Create Definition --- export interface CreateWorkflowDefinitionDto { /** รหัสของ Workflow (เช่น 'RFA', 'CORRESPONDENCE') */ - workflow_code: string; + workflowCode: string; /** นิยาม Workflow (DSL JSON Object) */ dsl: WorkflowDsl; /** เปิดใช้งานทันทีหรือไม่ (Default: true) */ - is_active?: boolean; + isActive?: boolean; } // --- Update Definition --- @@ -41,10 +41,10 @@ export type UpdateWorkflowDefinitionDto = Partial; // --- Evaluate (ประมวลผล/ตรวจสอบ State) --- export interface EvaluateWorkflowDto { /** รหัส Workflow */ - workflow_code: string; + workflowCode: string; /** สถานะปัจจุบัน */ - current_state: string; + currentState: string; /** Action ที่ต้องการทำ (เช่น 'SUBMIT', 'APPROVE') */ action: string; @@ -56,8 +56,8 @@ export interface EvaluateWorkflowDto { // --- Get Available Actions --- export interface GetAvailableActionsDto { /** รหัส Workflow */ - workflow_code: string; + workflowCode: string; /** สถานะปัจจุบัน */ - current_state: string; + currentState: string; } diff --git a/frontend/types/master-data.ts b/frontend/types/master-data.ts index b99b539..bf829c5 100644 --- a/frontend/types/master-data.ts +++ b/frontend/types/master-data.ts @@ -29,8 +29,8 @@ export interface RfaType { export interface Tag { id: number; - tag_name: string; - color_code?: string; + tagName: string; + colorCode?: string; description?: string; } diff --git a/lcbp3.code-workspace b/lcbp3.code-workspace index 07246ac..1623406 100644 --- a/lcbp3.code-workspace +++ b/lcbp3.code-workspace @@ -676,6 +676,7 @@ "workbench.preferredDarkColorTheme": "Default Dark Modern", "scm.alwaysShowActions": false, "workbench.settings.alwaysShowAdvancedSettings": true, + "npm.packageManager": "pnpm", }, // ======================================== // LAUNCH CONFIGURATIONS diff --git a/specs/01-requirements/01-02-business-rules/01-02-02-doc-numbering-rules.md b/specs/01-requirements/01-02-business-rules/01-02-02-doc-numbering-rules.md index d815933..8d50c38 100644 --- a/specs/01-requirements/01-02-business-rules/01-02-02-doc-numbering-rules.md +++ b/specs/01-requirements/01-02-business-rules/01-02-02-doc-numbering-rules.md @@ -803,7 +803,7 @@ services: ```bash PUT /api/v1/document-numbering/configs/{configId} { - "template": "{ORIGINATOR}-{RECIPIENT}-{SEQ:4}-{YEAR:B.E.}", + "template": "{ORIGINATOR}-{RECIPIENT}-{SEQ:4}/{YEAR:B.E.}", "change_reason": "เหตุผลในการเปลี่ยนแปลง" } ``` diff --git a/specs/03-Data-and-Storage/lcbp3-v1.8.0-seed-basic.sql b/specs/03-Data-and-Storage/lcbp3-v1.8.0-seed-basic.sql index 6a59c50..9ec56df 100644 --- a/specs/03-Data-and-Storage/lcbp3-v1.8.0-seed-basic.sql +++ b/specs/03-Data-and-Storage/lcbp3-v1.8.0-seed-basic.sql @@ -2178,7 +2178,7 @@ VALUES ( 1, NULL, 0, - '{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR}', + '{ORG}-{RECIPIENT}-{SEQ:4}/{YEAR}', 1, 1, NULL, @@ -2190,7 +2190,7 @@ VALUES ( 2, NULL, 0, - '{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR}', + '{ORG}-{RECIPIENT}-{SEQ:4}/{YEAR}', 1, 1, NULL, diff --git a/specs/05-Engineering-Guidelines/05-01-fullstack-js-guidelines.md b/specs/05-Engineering-Guidelines/05-01-fullstack-js-guidelines.md index e111d45..e0e8278 100644 --- a/specs/05-Engineering-Guidelines/05-01-fullstack-js-guidelines.md +++ b/specs/05-Engineering-Guidelines/05-01-fullstack-js-guidelines.md @@ -48,14 +48,15 @@ ### **2.4 ข้อตกลงในการตั้งชื่อ (Naming Conventions)** -| Entity (สิ่งที่ตั้งชื่อ) | Convention (รูปแบบ) | Example (ตัวอย่าง) | -| :----------------------- | :------------------ | :--------------------------------- | -| Classes | PascalCase | UserService | -| Property | snake_case | user_id | -| Variables & Functions | camelCase | getUserInfo | -| Files & Folders | kebab-case | user-service.ts | -| Environment Variables | UPPERCASE | DATABASE_URL | -| Booleans | Verb + Noun | isActive, canDelete, hasPermission | +| Entity (สิ่งที่ตั้งชื่อ) | Convention (รูปแบบ) | Example (ตัวอย่าง) | Note | +| :----------------------- | :------------------ | :--------------------------------- | :--------------------------------- | +| Classes | PascalCase | UserService | | +| Property (Code) | camelCase | userId, typeCode, isActive | Backend Entity, DTO, Frontend | +| Database Column | snake_case | user_id, type_code, is_active | MariaDB column names | +| Variables & Functions | camelCase | getUserInfo | | +| Files & Folders | kebab-case | user-service.ts | | +| Environment Variables | UPPERCASE | DATABASE_URL | | +| Booleans | Verb + Noun | isActive, canDelete, hasPermission | | ใช้คำเต็ม — ไม่ใช้อักษรย่อ — ยกเว้นคำมาตรฐาน (เช่น API, URL, req, res, err, ctx) diff --git a/specs/06-Decision-Records/ADR-002-document-numbering-strategy.md b/specs/06-Decision-Records/ADR-002-document-numbering-strategy.md index 9fe50c3..c6dc7e7 100644 --- a/specs/06-Decision-Records/ADR-002-document-numbering-strategy.md +++ b/specs/06-Decision-Records/ADR-002-document-numbering-strategy.md @@ -225,13 +225,13 @@ The system resolves the numbering format using the following priority: 1. **Specific Format:** Search for a record matching both `project_id` and `correspondence_type_id`. 2. **Default Format:** If not found, search for a record with matching `project_id` where `correspondence_type_id` is `NULL`. -3. **System Fallback:** If neither exists, use the hardcoded system default: `{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR:BE}`. +3. **System Fallback:** If neither exists, use the hardcoded system default: `{ORG}-{RECIPIENT}-{SEQ:4}/{YEAR:BE}`. | Priority | Scenario | Template Source | Counter Scope (Key) | Reset Behavior | | -------- | --------------------- | --------------------------------------------------- | ----------------------------- | ----------------------------------- | | 1 | Specific Format Found | Database (project_id, type_id) | Specific Type (type_id) | Based on reset_sequence_yearly flag | | 2 | Default Format Found | Database (project_id, type_id=NULL) | Shared Counter (type_id=NULL) | Based on reset_sequence_yearly flag | -| 3 | Fallback (No Config) | System Default: {ORG}-{RECIPIENT}-{SEQ:4}-{YEAR:BE} | Shared Counter (type_id=NULL) | Reset Yearly (Default: True) | +| 3 | Fallback (No Config) | System Default: {ORG}-{RECIPIENT}-{SEQ:4}/{YEAR:BE} | Shared Counter (type_id=NULL) | Reset Yearly (Default: True) | ### Format Examples by Document Type diff --git a/specs/99-archives/docs/20251224-document-numbering-summary.md b/specs/99-archives/docs/20251224-document-numbering-summary.md index 82f4867..f8b09ed 100644 --- a/specs/99-archives/docs/20251224-document-numbering-summary.md +++ b/specs/99-archives/docs/20251224-document-numbering-summary.md @@ -202,7 +202,7 @@ Result: NAP-PAT-LET-67-0001 1. **Specific Format**: project_id + correspondence_type_id 2. **Default Format**: project_id + correspondence_type_id = NULL -3. **Fallback**: `{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR:BE}` +3. **Fallback**: `{ORG}-{RECIPIENT}-{SEQ:4}/{YEAR:BE}` --- diff --git a/specs/99-archives/docs/backup/document-numbering.md b/specs/99-archives/docs/backup/document-numbering.md index 19ceb8a..d86a315 100644 --- a/specs/99-archives/docs/backup/document-numbering.md +++ b/specs/99-archives/docs/backup/document-numbering.md @@ -175,7 +175,7 @@ src/modules/document-numbering/ - Query document_number_formats by project_id + type_id. - If no result, query by project_id + NULL (Default Project Format). -- If still no result, apply System Default Template: `{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR:BE}`. +- If still no result, apply System Default Template: `{ORG}-{RECIPIENT}-{SEQ:4}/{YEAR:BE}`. - Determine resetSequenceYearly flag from the found format (default: true) #### 2.2.2. Determine Counter Key: diff --git a/specs/99-archives/lcbp3-v1.7.0-seed-basic.sql b/specs/99-archives/lcbp3-v1.7.0-seed-basic.sql index 06e74c6..e1a8fbd 100644 --- a/specs/99-archives/lcbp3-v1.7.0-seed-basic.sql +++ b/specs/99-archives/lcbp3-v1.7.0-seed-basic.sql @@ -5,6 +5,7 @@ VALUES (1, 'OWNER'), (4, 'CONTRACTOR'), (5, 'THIRD PARTY'), (6, 'GUEST'); + INSERT INTO organizations ( id, organization_code, @@ -79,6 +80,7 @@ VALUES (1, 'กทท.', 'การท่าเรือแห่งประเ ), (31, 'EN', 'Third Party Environment', 5), (32, 'CAR', 'Third Party Fishery Care', 5); + -- Seed project INSERT INTO projects (project_code, project_name) VALUES ( @@ -105,6 +107,7 @@ VALUES ( 'LCBP3-EN', 'โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3 (ส่วนที่ 4) งานก่อสร้าง' ); + -- Seed contract -- ใช้ Subquery เพื่อดึง project_id มาเชื่อมโยง ทำให้ไม่ต้องมานั่งจัดการ ID ด้วยตัวเอง INSERT INTO contracts ( @@ -183,6 +186,7 @@ VALUES ( ), TRUE ); + -- Seed user -- Initial SUPER_ADMIN user INSERT INTO users ( @@ -235,6 +239,7 @@ VALUES ( NULL, 10 ); + -- ========================================================== -- Seed Roles (บทบาทพื้นฐาน 5 บทบาท ตาม Req 4.3) -- ========================================================== @@ -288,6 +293,7 @@ VALUES ( 'Contract', 'ผู้ดูแลสัญญา: จัดการสมาชิกในสัญญา, สร้าง / จัดการข้อมูลหลักเฉพาะสัญญา, และอนุมัติเอกสารในสัญญา' ); + -- ========================================================== -- Seed Role-Permissions Mapping (จับคู่สิทธิ์เริ่มต้น) -- ========================================================== @@ -315,6 +321,7 @@ VALUES (1, 1, 1, NULL, NULL, NULL, NULL), (3, 3, 4, 41, NULL, NULL, 1), -- editor01: Editor role (role_id=4) at organization 41 (คคง.), assigned by superadmin (4, 4, 5, 10, NULL, NULL, 1); + -- viewer01: Viewer role (role_id=5) at organization 10 (สคฉ.03), assigned by superadmin -- ===================================================== -- == 4. การเชื่อมโยงโครงการกับองค์กร (project_organizations) == @@ -340,6 +347,7 @@ WHERE organization_code IN ( 'EN', 'CAR' ); + -- โครงการย่อย (LCBP3C1) จะมีเฉพาะองค์กรที่เกี่ยวข้อง INSERT INTO project_organizations (project_id, organization_id) SELECT ( @@ -356,6 +364,7 @@ WHERE organization_code IN ( 'คคง.', 'ผรม.1 ' ); + -- ทำเช่นเดียวกันสำหรับโครงการอื่นๆ (ตัวอย่าง) INSERT INTO project_organizations (project_id, organization_id) SELECT ( @@ -372,6 +381,7 @@ WHERE organization_code IN ( 'คคง.', 'ผรม.2' ); + -- ===================================================== -- == 5. การเชื่อมโยงสัญญากับองค์กร (contract_organizations) == -- ===================================================== @@ -403,6 +413,7 @@ VALUES ( ), 'Designer' ); + -- สัญญาที่ปรึกษาควบคุมงาน (PSLCBP3) INSERT INTO contract_organizations (contract_id, organization_id, role_in_contract) VALUES ( @@ -431,6 +442,7 @@ VALUES ( ), 'Consultant' ); + -- สัญญางานก่อสร้าง ส่วนที่ 1 (LCBP3-C1) INSERT INTO contract_organizations (contract_id, organization_id, role_in_contract) VALUES ( @@ -459,6 +471,7 @@ VALUES ( ), 'Contractor' ); + -- สัญญางานก่อสร้าง ส่วนที่ 2 (LCBP3-C2) INSERT INTO contract_organizations (contract_id, organization_id, role_in_contract) VALUES ( @@ -487,6 +500,7 @@ VALUES ( ), 'Contractor' ); + -- สัญญาตรวจสอบสิ่งแวดล้อม (LCBP3-EN) INSERT INTO contract_organizations (contract_id, organization_id, role_in_contract) VALUES ( @@ -515,6 +529,7 @@ VALUES ( ), 'Consultant' ); + -- Seed correspondence_status INSERT INTO correspondence_status ( status_code, @@ -545,6 +560,7 @@ VALUES ('DRAFT', 'Draft', 10, 1), ('CCBDSN', 'Canceled by Designer', 92, 1), ('CCBCSC', 'Canceled by CSC', 93, 1), ('CCBCON', 'Canceled by Contractor', 94, 1); + -- Seed correspondence_types INSERT INTO correspondence_types (type_code, type_name, sort_order, is_active) VALUES ('RFA', 'Request for Approval', 1, 1), @@ -557,6 +573,7 @@ VALUES ('RFA', 'Request for Approval', 1, 1), ('MOM', 'Minutes of Meeting', 8, 1), ('NOTICE', 'Notice', 9, 1), ('OTHER', 'Other', 10, 1); + -- Seed rfa_types INSERT INTO rfa_types ( contract_id, @@ -1046,6 +1063,7 @@ SELECT id, 'รายงานการฝึกปฏิบัติ' FROM contracts WHERE contract_code = 'LCBP3-C2'; + -- Seed rfa_status_codes INSERT INTO rfa_status_codes ( status_code, @@ -1060,6 +1078,7 @@ VALUES ('DFT', 'Draft', 'ฉบับร่าง', 1), ('ASB', 'AS - Built', 'แบบก่อสร้างจริง', 30), ('OBS', 'Obsolete', 'ไม่ใช้งาน', 80), ('CC', 'Canceled', 'ยกเลิก', 99); + INSERT INTO rfa_approve_codes ( approve_code, approve_name, @@ -1074,12 +1093,14 @@ VALUES ('1A', 'Approved by Authority', 10, 1), ('3R', 'Revise and Resubmit', 32, 1), ('4X', 'Reject', 40, 1), ('5N', 'No Further Action', 50, 1); + -- Seed circulation_status_codes INSERT INTO circulation_status_codes (code, description, sort_order) VALUES ('OPEN', 'Open', 1), ('IN_REVIEW', 'In Review', 2), ('COMPLETED', 'ปCompleted', 3), ('CANCELLED', 'Cancelled / Withdrawn', 9); + -- ตาราง "แม่" ของ RFA (มีความสัมพันธ์ 1:N กับ rfa_revisions) -- ========================================================== -- SEED DATA 6B.md (Disciplines, RFA Types, Sub Types) @@ -1343,6 +1364,7 @@ SELECT id, 'Other' FROM contracts WHERE contract_code = 'LCBP3-C1'; + -- LCBP3-C2 INSERT INTO disciplines ( contract_id, @@ -1587,6 +1609,7 @@ SELECT id, 'Others' FROM contracts WHERE contract_code = 'LCBP3-C2'; + -- 2. Seed ข้อมูล Correspondence Sub Types (Mapping RFA Types กับ Number) -- เนื่องจาก sub_type_code ตรงกับ RFA Type Code แต่ Req ต้องการ Mapping เป็น Number -- LCBP3-C1 @@ -1637,6 +1660,7 @@ FROM contracts c, correspondence_types ct WHERE c.contract_code = 'LCBP3-C1' AND ct.type_code = 'RFA'; + -- LCBP3-C2 INSERT INTO correspondence_sub_types ( contract_id, @@ -1684,6 +1708,7 @@ FROM contracts c, correspondence_types ct WHERE c.contract_code = 'LCBP3-C2' AND ct.type_code = 'RFA'; + -- LCBP3-C3 INSERT INTO correspondence_sub_types ( contract_id, @@ -1731,6 +1756,7 @@ FROM contracts c, correspondence_types ct WHERE c.contract_code = 'LCBP3-C3' AND ct.type_code = 'RFA'; + -- LCBP3-C4 INSERT INTO correspondence_sub_types ( contract_id, @@ -1778,6 +1804,7 @@ FROM contracts c, correspondence_types ct WHERE c.contract_code = 'LCBP3-C4' AND ct.type_code = 'RFA'; + INSERT INTO `correspondences` ( `id`, `correspondence_number`, @@ -1814,6 +1841,7 @@ VALUES ( 1, NULL ); + INSERT INTO `correspondence_revisions` ( `id`, `correspondence_id`, @@ -1852,6 +1880,7 @@ VALUES ( 1, NULL ); + INSERT INTO `rfas` ( `id`, `rfa_type_id`, @@ -1860,6 +1889,7 @@ INSERT INTO `rfas` ( `deleted_at` ) VALUES (2, 68, '2025-12-06 05:40:02', 1, NULL); + INSERT INTO `rfa_revisions` ( `id`, `rfa_id`, @@ -1900,6 +1930,7 @@ VALUES ( NULL, NULL ); + -- ========================================================== -- 20. Workflow Definitions (Unified Workflow Engine) -- ========================================================== @@ -2136,6 +2167,7 @@ VALUES ( NOW(), NOW() ); + INSERT INTO `document_number_formats` ( `id`, `project_id`, @@ -2153,7 +2185,7 @@ VALUES ( 1, NULL, 0, - '{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR}', + '{ORG}-{RECIPIENT}-{SEQ:4}/{YEAR}', 1, 1, NULL, @@ -2165,10 +2197,10 @@ VALUES ( 2, NULL, 0, - '{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR}', + '{ORG}-{RECIPIENT}-{SEQ:4}/{YEAR}', 1, 1, NULL, '2025-12-16 09:34:10', '2025-12-16 09:34:10' - ); \ No newline at end of file + );