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