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

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