summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/api/pos/download-on-demand-partners/route.ts1
-rw-r--r--app/api/revalidate/approval/route.ts101
-rw-r--r--ecosystem.config.js4
-rw-r--r--lib/approval-log/service.ts160
-rw-r--r--lib/approval/approval-workflow.ts92
-rw-r--r--lib/approval/cache-utils.ts82
-rw-r--r--lib/approval/index.ts7
-rw-r--r--lib/pq/service.ts28
-rw-r--r--lib/rfq-last/shared/rfq-items-dialog.tsx1
-rw-r--r--lib/vendor-investigation/handlers.ts24
10 files changed, 411 insertions, 89 deletions
diff --git a/app/api/pos/download-on-demand-partners/route.ts b/app/api/pos/download-on-demand-partners/route.ts
index d2941537..0e146c80 100644
--- a/app/api/pos/download-on-demand-partners/route.ts
+++ b/app/api/pos/download-on-demand-partners/route.ts
@@ -241,3 +241,4 @@ export async function GET(request: NextRequest) {
}
}
+
diff --git a/app/api/revalidate/approval/route.ts b/app/api/revalidate/approval/route.ts
new file mode 100644
index 00000000..ed4da139
--- /dev/null
+++ b/app/api/revalidate/approval/route.ts
@@ -0,0 +1,101 @@
+import { revalidateTag } from 'next/cache';
+import { NextRequest, NextResponse } from 'next/server';
+
+/**
+ * 결재 시스템 캐시 무효화 API
+ *
+ * 백그라운드 프로세스(폴링 서비스)에서 request 컨텍스트 없이
+ * 캐시를 무효화하기 위한 API 라우트
+ *
+ * 사용법:
+ * await fetch('/api/revalidate/approval', {
+ * method: 'POST',
+ * headers: { 'Content-Type': 'application/json' },
+ * body: JSON.stringify({ tags: ['approval-logs'] })
+ * });
+ */
+export async function POST(request: NextRequest) {
+ try {
+ const body = await request.json();
+ const { tags, secret } = body;
+
+ // 선택적 보안: 환경 변수로 시크릿 키 검증
+ // 내부 서버에서만 호출되므로 필수는 아님
+ if (process.env.REVALIDATION_SECRET && secret !== process.env.REVALIDATION_SECRET) {
+ return NextResponse.json(
+ { success: false, message: 'Invalid secret' },
+ { status: 401 }
+ );
+ }
+
+ // 캐시 태그 무효화
+ if (Array.isArray(tags)) {
+ for (const tag of tags) {
+ revalidateTag(tag);
+ console.log(`[Cache Revalidation] Tag revalidated: ${tag}`);
+ }
+ } else if (typeof tags === 'string') {
+ revalidateTag(tags);
+ console.log(`[Cache Revalidation] Tag revalidated: ${tags}`);
+ }
+
+ return NextResponse.json({
+ success: true,
+ revalidated: true,
+ tags: Array.isArray(tags) ? tags : [tags],
+ timestamp: new Date().toISOString(),
+ });
+ } catch (error) {
+ console.error('[Cache Revalidation] Error:', error);
+ return NextResponse.json(
+ {
+ success: false,
+ message: error instanceof Error ? error.message : 'Revalidation failed',
+ },
+ { status: 500 }
+ );
+ }
+}
+
+// GET 요청으로도 사용 가능 (개발/테스트용)
+export async function GET(request: NextRequest) {
+ const { searchParams } = new URL(request.url);
+ const tag = searchParams.get('tag');
+ const secret = searchParams.get('secret');
+
+ if (process.env.REVALIDATION_SECRET && secret !== process.env.REVALIDATION_SECRET) {
+ return NextResponse.json(
+ { success: false, message: 'Invalid secret' },
+ { status: 401 }
+ );
+ }
+
+ if (!tag) {
+ return NextResponse.json(
+ { success: false, message: 'Tag parameter is required' },
+ { status: 400 }
+ );
+ }
+
+ try {
+ revalidateTag(tag);
+ console.log(`[Cache Revalidation] Tag revalidated: ${tag}`);
+
+ return NextResponse.json({
+ success: true,
+ revalidated: true,
+ tag,
+ timestamp: new Date().toISOString(),
+ });
+ } catch (error) {
+ console.error('[Cache Revalidation] Error:', error);
+ return NextResponse.json(
+ {
+ success: false,
+ message: error instanceof Error ? error.message : 'Revalidation failed',
+ },
+ { status: 500 }
+ );
+ }
+}
+
diff --git a/ecosystem.config.js b/ecosystem.config.js
index d1059d19..60f1330a 100644
--- a/ecosystem.config.js
+++ b/ecosystem.config.js
@@ -2,8 +2,8 @@ module.exports = {
apps: [
{
name: 'evcp',
- script: 'npm',
- args: 'run start',
+ script: './node_modules/next/dist/bin/next',
+ args: 'start',
cwd: './',
instances: 1,
autorestart: true,
diff --git a/lib/approval-log/service.ts b/lib/approval-log/service.ts
index 5690e0f9..64ae40a2 100644
--- a/lib/approval-log/service.ts
+++ b/lib/approval-log/service.ts
@@ -10,6 +10,7 @@ import {
or,
sql,
} from 'drizzle-orm';
+import { unstable_cache } from 'next/cache';
import db from '@/db/db';
import { approvalLogs } from '@/db/schema/knox/approvals';
@@ -39,85 +40,106 @@ interface ListInput {
sort?: Array<{ id: string; desc: boolean }>;
}
+/**
+ * 결재 로그 목록 조회 (캐시 적용)
+ *
+ * 캐시 태그: 'approval-logs'
+ * 캐시 무효화: /api/revalidate/approval 호출 시
+ */
export async function getApprovalLogList(input: ListInput) {
- const offset = (input.page - 1) * input.perPage;
+ // 캐시 키 생성 (검색/필터/정렬 조건 포함)
+ const cacheKey = `approval-logs-${JSON.stringify(input)}`;
+
+ return unstable_cache(
+ async () => {
+ const offset = (input.page - 1) * input.perPage;
- /* ------------------------------------------------------------------
- * WHERE 절 구성
- * ----------------------------------------------------------------*/
- const advancedWhere = filterColumns({
- table: approvalLogs,
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- filters: (input.filters ?? []) as any,
- joinOperator: (input.joinOperator ?? 'and') as 'and' | 'or',
- });
+ /* ------------------------------------------------------------------
+ * WHERE 절 구성
+ * ----------------------------------------------------------------*/
+ const advancedWhere = filterColumns({
+ table: approvalLogs,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ filters: (input.filters ?? []) as any,
+ joinOperator: (input.joinOperator ?? 'and') as 'and' | 'or',
+ });
- // 전역 검색 (subject, content, emailAddress, userId)
- let globalWhere;
- if (input.search) {
- const s = `%${input.search}%`;
- globalWhere = or(
- ilike(approvalLogs.subject, s),
- ilike(approvalLogs.content, s),
- ilike(approvalLogs.emailAddress, s),
- ilike(approvalLogs.userId, s),
- );
- }
+ // 전역 검색 (subject, content, emailAddress, userId)
+ let globalWhere;
+ if (input.search) {
+ const s = `%${input.search}%`;
+ globalWhere = or(
+ ilike(approvalLogs.subject, s),
+ ilike(approvalLogs.content, s),
+ ilike(approvalLogs.emailAddress, s),
+ ilike(approvalLogs.userId, s),
+ );
+ }
- let where = eq(approvalLogs.isDeleted, false); // 기본적으로 삭제되지 않은 것만 조회
+ let where = eq(approvalLogs.isDeleted, false); // 기본적으로 삭제되지 않은 것만 조회
- if (advancedWhere && globalWhere) {
- where = and(where, advancedWhere, globalWhere);
- } else if (advancedWhere) {
- where = and(where, advancedWhere);
- } else if (globalWhere) {
- where = and(where, globalWhere);
- }
+ if (advancedWhere && globalWhere) {
+ const combined = and(where, advancedWhere, globalWhere);
+ if (combined) where = combined;
+ } else if (advancedWhere) {
+ const combined = and(where, advancedWhere);
+ if (combined) where = combined;
+ } else if (globalWhere) {
+ const combined = and(where, globalWhere);
+ if (combined) where = combined;
+ }
- /* ------------------------------------------------------------------
- * ORDER BY 절 구성
- * ----------------------------------------------------------------*/
- let orderBy;
- try {
- orderBy = input.sort && input.sort.length > 0
- ? input.sort
- .map((item) => {
- if (!item || !item.id || typeof item.id !== 'string') return null;
- if (!(item.id in approvalLogs)) return null;
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- const col = approvalLogs[item.id];
- return item.desc ? desc(col) : asc(col);
- })
- .filter((v): v is Exclude<typeof v, null> => v !== null)
- : [desc(approvalLogs.createdAt)];
- } catch {
- orderBy = [desc(approvalLogs.createdAt)];
- }
+ /* ------------------------------------------------------------------
+ * ORDER BY 절 구성
+ * ----------------------------------------------------------------*/
+ let orderBy;
+ try {
+ orderBy = input.sort && input.sort.length > 0
+ ? input.sort
+ .map((item) => {
+ if (!item || !item.id || typeof item.id !== 'string') return null;
+ if (!(item.id in approvalLogs)) return null;
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ const col = approvalLogs[item.id];
+ return item.desc ? desc(col) : asc(col);
+ })
+ .filter((v): v is Exclude<typeof v, null> => v !== null)
+ : [desc(approvalLogs.createdAt)];
+ } catch {
+ orderBy = [desc(approvalLogs.createdAt)];
+ }
- /* ------------------------------------------------------------------
- * 데이터 조회
- * ----------------------------------------------------------------*/
- const data = await db
- .select()
- .from(approvalLogs)
- .where(where)
- .orderBy(...orderBy)
- .limit(input.perPage)
- .offset(offset);
+ /* ------------------------------------------------------------------
+ * 데이터 조회
+ * ----------------------------------------------------------------*/
+ const data = await db
+ .select()
+ .from(approvalLogs)
+ .where(where)
+ .orderBy(...orderBy)
+ .limit(input.perPage)
+ .offset(offset);
- const totalResult = await db
- .select({ count: count() })
- .from(approvalLogs)
- .where(where);
+ const totalResult = await db
+ .select({ count: count() })
+ .from(approvalLogs)
+ .where(where);
- const total = totalResult[0]?.count ?? 0;
- const pageCount = Math.ceil(total / input.perPage);
+ const total = totalResult[0]?.count ?? 0;
+ const pageCount = Math.ceil(total / input.perPage);
- return {
- data,
- pageCount,
- };
+ return {
+ data,
+ pageCount,
+ };
+ },
+ [cacheKey],
+ {
+ tags: ['approval-logs'], // 캐시 태그
+ revalidate: 60, // 60초마다 자동 재검증 (폴백)
+ }
+ )();
}
// ----------------------------------------------------
diff --git a/lib/approval/approval-workflow.ts b/lib/approval/approval-workflow.ts
index 4c7eec09..fc6a0dcf 100644
--- a/lib/approval/approval-workflow.ts
+++ b/lib/approval/approval-workflow.ts
@@ -180,6 +180,11 @@ export async function withApproval<T>(
* - Knox 상신 성공 후 DB 업데이트 실패 위험 제거
*/
+ // 캐시 무효화 (결재 상신 시)
+ console.log(`[Approval Workflow] Revalidating cache after approval submission`);
+ const { revalidateApprovalLogs } = await import('./cache-utils');
+ await revalidateApprovalLogs();
+
let pendingActionId: number | undefined;
try {
@@ -253,38 +258,81 @@ export async function withApproval<T>(
* @returns 액션 실행 결과
*/
export async function executeApprovedAction(apInfId: string) {
+ // debug-utils import
+ const { debugLog, debugError, debugSuccess } = await import('@/lib/debug-utils');
+
+ debugLog('[executeApprovedAction] 시작', { apInfId });
+
try {
// 핸들러 자동 초기화 (폴링 서비스의 격리 문제 해결)
+ debugLog('[executeApprovedAction] 핸들러 초기화 중');
await ensureHandlersInitialized();
+ debugLog('[executeApprovedAction] 핸들러 초기화 완료');
// 1. apInfId로 pendingAction 조회
+ debugLog('[executeApprovedAction] pendingAction 조회 중', { apInfId });
const pendingAction = await db.query.pendingActions.findFirst({
where: eq(pendingActions.apInfId, apInfId),
});
if (!pendingAction) {
+ debugLog('[executeApprovedAction] pendingAction 없음 (결재만 존재)', { apInfId });
console.log(`[Approval Workflow] No pending action found for approval: ${apInfId}`);
return null; // 결재만 있고 실행할 액션이 없는 경우
}
+ debugLog('[executeApprovedAction] pendingAction 조회 완료', {
+ id: pendingAction.id,
+ actionType: pendingAction.actionType,
+ status: pendingAction.status,
+ createdBy: pendingAction.createdBy,
+ });
+
// 이미 실행되었거나 실패한 액션은 스킵
if (['executed', 'failed'].includes(pendingAction.status)) {
+ debugLog('[executeApprovedAction] 이미 처리된 액션 스킵', {
+ apInfId,
+ status: pendingAction.status,
+ });
console.log(`[Approval Workflow] Pending action already processed: ${apInfId} (${pendingAction.status})`);
return null;
}
// 2. 등록된 핸들러 조회
+ debugLog('[executeApprovedAction] 핸들러 조회 중', {
+ actionType: pendingAction.actionType,
+ });
const handler = actionHandlers.get(pendingAction.actionType);
if (!handler) {
+ debugError('[executeApprovedAction] 핸들러를 찾을 수 없음', {
+ actionType: pendingAction.actionType,
+ availableHandlers: Array.from(actionHandlers.keys()),
+ });
console.error('[Approval Workflow] Available handlers:', Array.from(actionHandlers.keys()));
throw new Error(`Handler not found for action type: ${pendingAction.actionType}`);
}
+ debugLog('[executeApprovedAction] 핸들러 조회 완료', {
+ actionType: pendingAction.actionType,
+ });
// 3. 실제 액션 실행
+ debugLog('[executeApprovedAction] 핸들러 실행 시작', {
+ actionType: pendingAction.actionType,
+ apInfId,
+ payloadKeys: Object.keys(pendingAction.actionPayload || {}),
+ });
console.log(`[Approval Workflow] Executing action: ${pendingAction.actionType} (${apInfId})`);
+
const result = await handler(pendingAction.actionPayload);
+ debugSuccess('[executeApprovedAction] 핸들러 실행 완료', {
+ actionType: pendingAction.actionType,
+ apInfId,
+ resultKeys: result ? Object.keys(result) : [],
+ });
+
// 4. 실행 완료 상태 업데이트
+ debugLog('[executeApprovedAction] 상태 업데이트 중 (executed)');
await db.update(pendingActions)
.set({
status: 'executed',
@@ -292,21 +340,46 @@ export async function executeApprovedAction(apInfId: string) {
executionResult: result,
})
.where(eq(pendingActions.apInfId, apInfId));
+ debugLog('[executeApprovedAction] 상태 업데이트 완료 (executed)');
+ // 5. 캐시 무효화 (백그라운드에서도 동작)
+ debugLog('[executeApprovedAction] 캐시 무효화 중');
+ const { revalidateApprovalLogs } = await import('./cache-utils');
+ await revalidateApprovalLogs();
+ debugLog('[executeApprovedAction] 캐시 무효화 완료');
+
+ debugSuccess('[executeApprovedAction] 전체 프로세스 완료', {
+ actionType: pendingAction.actionType,
+ apInfId,
+ });
console.log(`[Approval Workflow] ✅ Successfully executed: ${pendingAction.actionType} (${apInfId})`);
return result;
} catch (error) {
+ debugError('[executeApprovedAction] 실행 중 에러 발생', {
+ apInfId,
+ error: error instanceof Error ? error.message : String(error),
+ stack: error instanceof Error ? error.stack : undefined,
+ });
console.error(`[Approval Workflow] ❌ Failed to execute action for ${apInfId}:`, error);
// 실패 상태 업데이트
- await db.update(pendingActions)
- .set({
- status: 'failed',
- errorMessage: error instanceof Error ? error.message : String(error),
- executedAt: new Date(),
- })
- .where(eq(pendingActions.apInfId, apInfId));
+ try {
+ debugLog('[executeApprovedAction] 상태 업데이트 중 (failed)');
+ await db.update(pendingActions)
+ .set({
+ status: 'failed',
+ errorMessage: error instanceof Error ? error.message : String(error),
+ executedAt: new Date(),
+ })
+ .where(eq(pendingActions.apInfId, apInfId));
+ debugLog('[executeApprovedAction] 상태 업데이트 완료 (failed)');
+ } catch (updateError) {
+ debugError('[executeApprovedAction] 상태 업데이트 실패', {
+ apInfId,
+ updateError: updateError instanceof Error ? updateError.message : String(updateError),
+ });
+ }
throw error;
}
@@ -337,6 +410,11 @@ export async function handleRejectedAction(apInfId: string, reason?: string) {
})
.where(eq(pendingActions.apInfId, apInfId));
+ // 캐시 무효화 (백그라운드에서도 동작)
+ console.log(`[Approval Workflow] Revalidating cache for rejected action: ${apInfId}`);
+ const { revalidateApprovalLogs } = await import('./cache-utils');
+ await revalidateApprovalLogs();
+
// TODO: 요청자에게 알림 발송 등 추가 처리
} catch (error) {
console.error(`[Approval Workflow] Failed to handle rejected action for approval ${apInfId}:`, error);
diff --git a/lib/approval/cache-utils.ts b/lib/approval/cache-utils.ts
new file mode 100644
index 00000000..f4c56c25
--- /dev/null
+++ b/lib/approval/cache-utils.ts
@@ -0,0 +1,82 @@
+/**
+ * 결재 시스템 캐시 무효화 유틸리티
+ *
+ * 백그라운드 프로세스(폴링 서비스)에서 request 컨텍스트 없이
+ * Next.js 캐시를 무효화하기 위한 헬퍼 함수들
+ */
+
+/**
+ * 결재 관련 캐시 무효화
+ *
+ * @param tags - 무효화할 캐시 태그 배열
+ * @returns 무효화 결과
+ *
+ * @example
+ * // 결재 로그 캐시 무효화
+ * await revalidateApprovalCache(['approval-logs']);
+ *
+ * // 여러 캐시 동시 무효화
+ * await revalidateApprovalCache(['approval-logs', 'pending-actions']);
+ */
+export async function revalidateApprovalCache(tags: string[]) {
+ try {
+ // 내부 API 호출로 캐시 무효화
+ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000';
+ const response = await fetch(`${baseUrl}/api/revalidate/approval`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ tags,
+ secret: process.env.REVALIDATION_SECRET, // 선택적 보안
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`Cache revalidation failed: ${response.statusText}`);
+ }
+
+ const result = await response.json();
+ console.log(`[Approval Cache] Successfully revalidated tags:`, tags);
+ return result;
+ } catch (error) {
+ // 캐시 무효화 실패는 치명적이지 않으므로 로그만 남기고 진행
+ console.error('[Approval Cache] Failed to revalidate cache:', error);
+ console.warn('[Approval Cache] Continuing despite cache revalidation failure');
+ return { success: false, error };
+ }
+}
+
+/**
+ * 결재 로그 캐시 무효화
+ */
+export async function revalidateApprovalLogs() {
+ return revalidateApprovalCache(['approval-logs']);
+}
+
+/**
+ * Pending Actions 캐시 무효화
+ */
+export async function revalidatePendingActions() {
+ return revalidateApprovalCache(['pending-actions']);
+}
+
+/**
+ * 결재 관련 모든 캐시 무효화
+ */
+export async function revalidateAllApprovalCaches() {
+ return revalidateApprovalCache([
+ 'approval-logs',
+ 'pending-actions',
+ 'approval-templates',
+ ]);
+}
+
+/**
+ * 특정 결재 ID의 상세 캐시 무효화
+ */
+export async function revalidateApprovalDetail(apInfId: string) {
+ return revalidateApprovalCache([`approval-log-${apInfId}`]);
+}
+
diff --git a/lib/approval/index.ts b/lib/approval/index.ts
index 644c5fa8..82abac9a 100644
--- a/lib/approval/index.ts
+++ b/lib/approval/index.ts
@@ -32,3 +32,10 @@ export {
export type { TemplateVariables, ApprovalConfig, ApprovalResult } from './types';
+export {
+ revalidateApprovalCache,
+ revalidateApprovalLogs,
+ revalidatePendingActions,
+ revalidateAllApprovalCaches,
+ revalidateApprovalDetail,
+} from './cache-utils';
diff --git a/lib/pq/service.ts b/lib/pq/service.ts
index b39bf7bd..fd751b0f 100644
--- a/lib/pq/service.ts
+++ b/lib/pq/service.ts
@@ -2853,6 +2853,9 @@ export async function requestInvestigationAction(
forecastedAt: Date,
investigationAddress: string,
investigationNotes?: string
+ },
+ options?: {
+ skipRevalidation?: boolean; // ✅ 핸들러에서 호출 시 revalidation 건너뛰기
}
) {
try {
@@ -2917,11 +2920,13 @@ export async function requestInvestigationAction(
return created;
});
- // 캐시 무효화
- revalidateTag("vendor-investigations");
- revalidateTag("pq-submissions");
- revalidateTag("vendor-pq-submissions");
- revalidatePath("/evcp/pq_new");
+ // 캐시 무효화 (핸들러에서 호출 시에는 건너뛰기)
+ if (!options?.skipRevalidation) {
+ revalidateTag("vendor-investigations");
+ revalidateTag("pq-submissions");
+ revalidateTag("vendor-pq-submissions");
+ revalidatePath("/evcp/pq_new");
+ }
return {
success: true,
@@ -2966,9 +2971,11 @@ export async function cancelInvestigationAction(investigationIds: number[]) {
return updatedInvestigations
})
- // 캐시 무효화
- revalidateTag("vendor-investigations")
- revalidateTag("pq-submissions")
+ // 캐시 무효화 (핸들러에서 호출 시에는 건너뛰기)
+ if (!options?.skipRevalidation) {
+ revalidateTag("vendor-investigations")
+ revalidateTag("pq-submissions")
+ }
return {
success: true,
@@ -2987,7 +2994,10 @@ export async function cancelInvestigationAction(investigationIds: number[]) {
// 실사 재의뢰 서버 액션
export async function reRequestInvestigationAction(
investigationIds: number[],
- currentUser?: { id: number } // ✅ 핸들러에서 호출 시 사용자 정보 전달
+ currentUser?: { id: number }, // ✅ 핸들러에서 호출 시 사용자 정보 전달
+ options?: {
+ skipRevalidation?: boolean; // ✅ 핸들러에서 호출 시 revalidation 건너뛰기
+ }
) {
try {
let userId: number | null = null;
diff --git a/lib/rfq-last/shared/rfq-items-dialog.tsx b/lib/rfq-last/shared/rfq-items-dialog.tsx
index c25670fc..4b41897b 100644
--- a/lib/rfq-last/shared/rfq-items-dialog.tsx
+++ b/lib/rfq-last/shared/rfq-items-dialog.tsx
@@ -487,3 +487,4 @@ export function RfqItemsDialog({
)
}
+
diff --git a/lib/vendor-investigation/handlers.ts b/lib/vendor-investigation/handlers.ts
index 3165df06..b7da952a 100644
--- a/lib/vendor-investigation/handlers.ts
+++ b/lib/vendor-investigation/handlers.ts
@@ -37,18 +37,34 @@ export async function requestPQInvestigationInternal(payload: {
throw new Error(errorMessage);
}
+ // ✅ Date 문자열을 Date 객체로 변환
+ // DB의 jsonb에서 읽을 때 Date 객체가 ISO 문자열로 변환되어 있음
+ const forecastedAt = typeof payload.forecastedAt === 'string'
+ ? new Date(payload.forecastedAt)
+ : payload.forecastedAt;
+
+ debugLog('[PQInvestigationHandler] Date 변환 완료', {
+ originalType: typeof payload.forecastedAt,
+ convertedType: typeof forecastedAt,
+ forecastedAt: forecastedAt.toISOString(),
+ });
+
try {
// 기존 PQ 서비스 함수 사용 (DB 트랜잭션 포함)
const { requestInvestigationAction } = await import('@/lib/pq/service');
+ debugLog('[PQInvestigationHandler] requestInvestigationAction 호출');
const result = await requestInvestigationAction(
payload.pqSubmissionIds,
{ id: payload.currentUserId, epId: null, email: undefined }, // ✅ 실제 사용자 ID 전달
{
qmManagerId: payload.qmManagerId,
- forecastedAt: payload.forecastedAt,
+ forecastedAt: forecastedAt, // ✅ Date 객체 전달
investigationAddress: payload.investigationAddress,
investigationNotes: payload.investigationNotes,
+ },
+ {
+ skipRevalidation: true, // ✅ 폴링 컨텍스트에서는 revalidation 건너뛰기
}
);
@@ -133,9 +149,13 @@ export async function reRequestPQInvestigationInternal(payload: {
// 기존 PQ 서비스 함수 사용
const { reRequestInvestigationAction } = await import('@/lib/pq/service');
+ debugLog('[PQReRequestHandler] reRequestInvestigationAction 호출');
const result = await reRequestInvestigationAction(
payload.investigationIds,
- { id: payload.currentUserId } // ✅ 실제 사용자 ID 전달
+ { id: payload.currentUserId }, // ✅ 실제 사용자 ID 전달
+ {
+ skipRevalidation: true, // ✅ 폴링 컨텍스트에서는 revalidation 건너뛰기
+ }
);
if (!result.success) {