feat(migration): ADR-028 migration architecture refactor
- เพิ่ม POST /api/ai/jobs + GET /api/ai/jobs/:jobId endpoints (FR-001, FR-002) - เพิ่ม BullMQ Worker MigrateDocumentWorker + OCR auto-detect (FR-003, FR-004) - เพิ่ม cleanup-temp-files + expire-pending-reviews workers (FR-005, FR-005a/b) - สร้าง SQL deltas: tags, correspondence_tags, alter migration_review_queue (FR-006, ADR-009) - เพิ่ม MigrationReviewService.commitRecord() + SELECT FOR UPDATE (FR-007, FR-007a) - เพิ่ม CASL permission migration.commit + MigrationReviewController (FR-007) - สร้าง TagsModule + TagsService + TagsController (US3) - สร้าง Migration Review Queue frontend page + ReviewQueueTable (US2) - อัปเดต n8n guide: deterministic Idempotency-Key + token pre-flight (FR-001a, FR-010a/b) - สร้าง spec.md, plan.md, tasks.md, data-model.md, contracts/, quickstart.md - สร้าง ADR-028 document + validation-report.md (PASS 32/32 tasks, 173/173 tests)
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
// File: hooks/use-migration-review.ts
|
||||
// Change Log:
|
||||
// - 2026-05-22: Initial creation for US2 - Staging Migration Review Hooks (T023)
|
||||
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import apiClient from '@/lib/api/client';
|
||||
import { MigrationReviewQueueItem, MigrationReviewStatus, PaginatedResponse } from '@/types/migration';
|
||||
import { CommitMigrationReviewDto } from '@/types/dto/migration/migration-review.dto';
|
||||
import { toast } from 'sonner';
|
||||
import { getApiErrorMessage } from '@/types/api-error';
|
||||
|
||||
interface WrappedData<T> {
|
||||
data?: T;
|
||||
}
|
||||
|
||||
interface CommitMigrationReviewRequest extends CommitMigrationReviewDto {
|
||||
idempotencyKey: string;
|
||||
}
|
||||
|
||||
const extractData = <T>(value: unknown): T => {
|
||||
let current: unknown = value;
|
||||
for (let index = 0; index < 5; index += 1) {
|
||||
if (!current || typeof current !== 'object' || !('data' in current)) {
|
||||
return current as T;
|
||||
}
|
||||
current = (current as WrappedData<unknown>).data;
|
||||
}
|
||||
return current as T;
|
||||
};
|
||||
|
||||
export const migrationReviewKeys = {
|
||||
all: ['migration-review'] as const,
|
||||
queue: (status?: MigrationReviewStatus, page?: number, limit?: number) =>
|
||||
[...migrationReviewKeys.all, 'queue', status ?? 'ALL', page ?? 1, limit ?? 10] as const,
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook สำหรับดึงรายการใน Staging Review Queue แบบทำ Pagination และกรองตาม Status
|
||||
*/
|
||||
export function useMigrationReviewQueue(status?: MigrationReviewStatus, page: number = 1, limit: number = 10) {
|
||||
return useQuery({
|
||||
queryKey: migrationReviewKeys.queue(status, page, limit),
|
||||
queryFn: async (): Promise<PaginatedResponse<MigrationReviewQueueItem>> => {
|
||||
const response = await apiClient.get('/migration/queue', {
|
||||
params: { status, page, limit },
|
||||
});
|
||||
return extractData<PaginatedResponse<MigrationReviewQueueItem>>(response.data);
|
||||
},
|
||||
placeholderData: (prev) => prev,
|
||||
staleTime: 10 * 1000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook สำหรับยืนยันการนำเข้าข้อมูล (Execute Import / Commit) ไปยังระบบจริง
|
||||
*/
|
||||
export function useCommitMigrationReview() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async ({ idempotencyKey, ...payload }: CommitMigrationReviewRequest) => {
|
||||
const response = await apiClient.post('/ai/migration/review', payload, {
|
||||
headers: {
|
||||
'Idempotency-Key': idempotencyKey,
|
||||
},
|
||||
});
|
||||
return extractData<{ success: boolean; message: string; correspondencePublicId: string }>(response.data);
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success('นำเข้าเอกสารสำเร็จ', {
|
||||
description: 'เอกสารได้รับการบันทึกเข้าระบบจริงเรียบร้อยแล้ว',
|
||||
});
|
||||
void queryClient.invalidateQueries({ queryKey: migrationReviewKeys.all });
|
||||
},
|
||||
onError: (error: unknown) => {
|
||||
const errMsg = getApiErrorMessage(error, 'เกิดข้อผิดพลาดในการนำเข้าเอกสาร');
|
||||
toast.error('ไม่สามารถนำเข้าเอกสารได้', {
|
||||
description: errMsg,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook สำหรับปฏิเสธเอกสารใน Review Queue
|
||||
*/
|
||||
export function useRejectMigrationReview() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (id: number) => {
|
||||
const response = await apiClient.post(`/migration/queue/${id}/reject`);
|
||||
return extractData<{ message: string; id: number }>(response.data);
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success('ปฏิเสธเอกสารเรียบร้อย', {
|
||||
description: 'สถานะเอกสารถูกตั้งค่าเป็น REJECTED',
|
||||
});
|
||||
void queryClient.invalidateQueries({ queryKey: migrationReviewKeys.all });
|
||||
},
|
||||
onError: (error: unknown) => {
|
||||
const errMsg = getApiErrorMessage(error, 'เกิดข้อผิดพลาดในการปฏิเสธเอกสาร');
|
||||
toast.error('ไม่สามารถปฏิเสธเอกสารได้', {
|
||||
description: errMsg,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user