// File: specs/200-fullstacks/228-migration-arch-refactor/tasks.md // Change Log: // - 2026-05-22: Generated from plan.md + spec.md for ADR-028 Migration Architecture Refactor # Tasks: ADR-028 Migration Architecture Refactor **Branch**: `228-migration-arch-refactor` | **Generated**: 2026-05-22 **Total Tasks**: 32 | **Parallel Opportunities**: 12 > **Updated**: 2026-05-22 — เพิ่ม 2 tasks จาก quizme session (FR-001a/b, FR-005a/b, FR-007a, FR-010a/b) --- ## Phase 1: Setup - [x] T001 สร้าง SQL delta file `specs/03-Data-and-Storage/deltas/2026-05-22-create-tags-tables.sql` ตาม data-model.md (tables: `tags`, `correspondence_tags`) - [x] T001b สร้าง SQL delta file `specs/03-Data-and-Storage/deltas/2026-05-22-alter-migration-review-queue.sql` เพิ่ม column `ai_job_id VARCHAR(36) NULL` ใน `migration_review_queue` (ADR-009 — ตาราง migration_review_queue สร้างโดย 03-04 SQL แต่ไม่มี column นี้) - [x] T002 Apply SQL delta ทั้งสอง (T001, T001b) ใน staging database และ verify schema ถูกต้อง - [x] T003 สร้าง NestJS module skeleton `backend/src/modules/tags/tags.module.ts` - [x] T004 สร้าง BullMQ job type constant `migrate-document` ใน `backend/src/common/constants/bullmq.constants.ts` - [x] T004b อัปเดต n8n Node 0 "Set Configuration" ใน `specs/03-Data-and-Storage/03-05-n8n-migration-setup-guide.md` — (1) Idempotency-Key format deterministic: `{batchId}:{documentNumber}` ไม่ใช่ random UUID (FR-001a); (2) token pre-flight `GET /api/auth/me` ก่อน process records (FR-010a); (3) 401 mid-batch handler → write TOKEN_EXPIRED ลง migration_progress + resumable (FR-010b) --- ## Phase 2: Foundational (Blocking Prerequisites) - [x] T005 สร้าง TypeORM Entity `Tag` ใน `backend/src/modules/tags/entities/tag.entity.ts` (fields: id, publicId UUIDv7, projectId, tagName, colorCode, description, createdBy, timestamps, deletedAt) - [x] T006 [P] สร้าง TypeORM Entity `CorrespondenceTag` ใน `backend/src/modules/tags/entities/correspondence-tag.entity.ts` (fields: correspondenceId, tagId, isAiSuggested, confidence, createdBy, createdAt) - [x] T007 สร้าง DTO `SubmitAiJobDto` ใน `backend/src/modules/ai/dto/submit-ai-job.dto.ts` (type: 'migrate-document', payload: MigrateDocumentPayload) พร้อม class-validator decorators - [x] T008 [P] สร้าง DTO `AiJobResultDto` ใน `backend/src/modules/ai/dto/ai-job-result.dto.ts` (isValid, confidence, category, summary, suggestedTags, ocrMethod, processingTimeMs) --- ## Phase 3: US1 — n8n Submits AI Job via BullMQ (P1 — Blocking) **Story Goal**: n8n สามารถส่ง PDF ผ่าน `POST /api/ai/jobs` และ poll ผลลัพธ์ได้ **Independent Test**: `curl -X POST /api/ai/jobs` พร้อม test PDF → poll จนได้ `status: completed` + AI JSON output - [x] T009 [US1] สร้าง BullMQ Worker `MigrateDocumentWorker` ใน `backend/src/modules/ai/workers/migrate-document.worker.ts` — Step 1: fetch temp file from StorageService - [x] T010 [P] [US1] เพิ่ม OCR routing logic ใน Worker — PyMuPDF Fast Path (chars > 100) หรือ PaddleOCR Slow Path — เรียกผ่าน OCR Service HTTP API (ไม่ใช่ direct Ollama) - [x] T011 [P] [US1] เพิ่ม gemma4:e2b inference ใน Worker — System Prompt + User Prompt สำหรับ metadata extraction + classification + tagging - [x] T012 [US1] เพิ่ม JSON validation + error handling ใน Worker (ADR-007) — ถ้า AI output ไม่ถูก format → mark job failed + log ใน `ai_audit_logs` - [x] T013 [US1] เพิ่ม `submitMigrationJob()` method ใน `backend/src/modules/ai/ai.service.ts` — (1) Idempotency-Key check; (2) double-check `import_transactions` (document_number + batch_id + status != FAILED) ก่อน enqueue → 409 พร้อม existingJobId ถ้าซ้ำ (FR-001b); (3) enqueue ไปยัง ai-batch queue - [x] T014 [US1] เพิ่ม `POST /api/ai/jobs` endpoint ใน `backend/src/modules/ai/ai.controller.ts` (JwtAuthGuard + CaslAbilityGuard + Idempotency-Key header validation) - [x] T015 [P] [US1] เพิ่ม `GET /api/ai/jobs/:jobId` endpoint ใน `backend/src/modules/ai/ai.controller.ts` (JwtAuthGuard + status + result retrieval) - [x] T016 [US1] เพิ่ม Scheduled BullMQ job `cleanup-temp-files` ใน `backend/src/modules/ai/workers/cleanup-temp-files.worker.ts` — ลบ temp attachments ที่ครบ 24h + ไม่มี commit **ยกเว้น** files ที่ถูก reference โดย migration_review_queue.status = PENDING (FR-005a) - [x] T016b [P] [US1] สร้าง Scheduled BullMQ job `expire-pending-reviews` ใน `backend/src/modules/migration/workers/expire-pending-reviews.worker.ts` — รันรายวัน: auto-expire PENDING records ที่ไม่มี action ภายใน 30 วัน → status = EXPIRED + cleanup temp file + BullMQ notification job แจ้ง Admin (FR-005b) --- ## Phase 4: US3 — Schema Setup: Tags Tables (P1 — Parallel กับ US1) **Story Goal**: `tags` และ `correspondence_tags` พร้อมใช้งานสำหรับ AI Tag Extraction **Independent Test**: เรียก `POST /api/tags` สร้าง tag → link ไป correspondence → ตรวจ `correspondence_tags` table - [x] T017 [P] [US3] สร้าง `TagsService` ใน `backend/src/modules/tags/tags.service.ts` (methods: create, findByProject, normalize, linkToCorrespondence) - [x] T018 [P] [US3] สร้าง `TagsController` ใน `backend/src/modules/tags/tags.controller.ts` (GET /api/tags?projectId=, POST /api/tags) พร้อม CASL guard - [x] T019 [US3] Register `TagsModule` ใน `backend/src/app.module.ts` และ add entities ใน TypeORM config --- ## Phase 5: US2 — Migration Review Queue Frontend (P2) **Story Goal**: Document Controller/Admin เห็น review queue และ Execute Import ได้ **Independent Test**: Login ด้วย DOCUMENT_CONTROLLER → เข้า `/migration/review` → เห็น PENDING records → Execute Import → ตรวจ Correspondence สร้างสำเร็จ - [x] T020a [US2] สร้าง `MigrationReviewService.commitRecord()` ใน `backend/src/modules/migration/migration-review.service.ts` — (1) `SELECT FOR UPDATE` บน migration_review_queue record → ถ้า status ไม่ใช่ PENDING → 409 ALREADY_PROCESSING (FR-007a); (2) update status เป็น PROCESSING; (3) สร้าง Correspondence, ย้าย temp attachment เป็น permanent, link tags, update import_transactions - [x] T020b [US2] เพิ่ม `POST /api/ai/migration/review` endpoint ใน `backend/src/modules/migration/migration-review.controller.ts` (JwtAuthGuard + CaslAbilityGuard `migration.commit` + Idempotency-Key) เรียก `MigrationReviewService.commitRecord()` - [x] T021 [P] [US2] เพิ่ม CASL permission `migration.commit` สำหรับ role DOCUMENT_CONTROLLER, ADMIN, SUPERADMIN ใน `backend/src/common/casl/ability.factory.ts` - [x] T022 [P] [US2] สร้าง TypeScript types สำหรับ Migration Review ใน `frontend/types/dto/migration/migration-review.dto.ts` - [x] T023 [P] [US2] สร้าง frontend hook `useMigrationReview()` ใน `frontend/hooks/use-migration-review.ts` (TanStack Query — fetch migration_review_queue + mutation execute import) - [x] T024 [US2] สร้าง Migration Review Queue page `frontend/app/(dashboard)/migration/review/page.tsx` (table: document_number, confidence, category, status, suggested_tags, actions) - [x] T025 [US2] สร้าง `ReviewQueueTable` component ใน `frontend/components/migration/review-queue-table.tsx` — รวม Tag Review (is_new highlight, approve/map/reject) --- ## Phase 6: US4 — Post-Migration Cleanup Script (P3) **Story Goal**: ลบ migration tables ชั่วคราวหลัง Gate #3 ผ่าน (ยกเว้น import_transactions) **Independent Test**: รัน cleanup script → ตรวจ 5 ตาราง drop แล้ว แต่ import_transactions ยังอยู่ - [x] T026 [US4] สร้าง SQL cleanup script `specs/03-Data-and-Storage/deltas/2026-05-22-drop-migration-tables.rollback.sql` สำหรับ restore (ถ้าจำเป็น) - [x] T027 [US4] สร้าง SQL cleanup script `specs/03-Data-and-Storage/deltas/2026-05-22-drop-migration-tables.sql` — DROP TABLE IF EXISTS สำหรับ 5 ตาราง (explicit comment: `import_transactions` ไม่ drop) --- ## Phase 7: Polish & Cross-Cutting - [x] T028 สร้าง ADR-028 document ใน `specs/06-Decision-Records/ADR-028-migration-architecture-refactor.md` — document ทุก decision จาก session นี้ (n8n→BullMQ, gemma4:e4b, PyMuPDF/PaddleOCR, token 7d, RBAC, table retention) --- ## Dependencies Graph ``` T001 → T002 (schema must apply before entities work) T005, T006 → T017, T018, T019 (entities before service/controller) T007, T008 → T013, T014, T015 (DTOs before service/endpoints) T009 → T010 → T011 → T012 (Worker steps sequential) T013, T014, T015 → T009 (Service before Worker registration) T016 (independent) T016b (independent, parallel กับ T016) T019 → (tags available for Worker) T020a → T020b → T024, T025 (service → controller → frontend) T021 → T020b (CASL permission before controller endpoint) T028 (independent — can do last) ``` ## Parallel Execution Opportunities **Group A (Phase 2 — can run in parallel):** - T005 + T006 + T007 + T008 **Group B (US1 — can run in parallel after T009):** - T010 + T011 (OCR routing + AI inference, different concerns) - T014 + T015 (POST + GET endpoints, same controller different methods) **Group C (US2 — can run in parallel after T020):** - T022 + T023 (types + hook) - T021 (CASL permission, different file) **Group D (US3 — parallel กับ US1 entirely):** - T017 + T018 (service + controller) ## Implementation Strategy **MVP Scope (Phase 1-3 + Phase 4):** - SQL delta apply + Tags entities - `POST /api/ai/jobs` + `GET /api/ai/jobs/:jobId` + BullMQ Worker - n8n สามารถรัน Migration ได้โดยผ่าน BullMQ (ตรง ADR-023A) **Full Scope (+ Phase 5-7):** - Migration Review Queue Frontend - Post-migration cleanup - ADR-028 documentation