summaryrefslogtreecommitdiff
path: root/lib/pq/service.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pq/service.ts')
-rw-r--r--lib/pq/service.ts182
1 files changed, 168 insertions, 14 deletions
diff --git a/lib/pq/service.ts b/lib/pq/service.ts
index ba0ce3c5..f15790eb 100644
--- a/lib/pq/service.ts
+++ b/lib/pq/service.ts
@@ -9,11 +9,12 @@ import { asc, desc, ilike, inArray, and, gte, lte, not, or, eq, count,isNull,SQL
import { z } from "zod"
import { revalidateTag, unstable_noStore, revalidatePath} from "next/cache";
import { format } from "date-fns"
-import { pqCriterias, vendorCriteriaAttachments, vendorInvestigations, vendorPQSubmissions, vendorPqCriteriaAnswers, vendorPqReviewLogs, siteVisitRequests, vendorSiteVisitInfo, siteVisitRequestAttachments } from "@/db/schema/pq"
+import { pqCriterias, vendorCriteriaAttachments, vendorInvestigations, vendorInvestigationAttachments, vendorPQSubmissions, vendorPqCriteriaAnswers, vendorPqReviewLogs, siteVisitRequests, vendorSiteVisitInfo, siteVisitRequestAttachments } from "@/db/schema/pq"
import { sendEmail } from "../mail/sendEmail";
import { decryptWithServerAction } from '@/components/drm/drmUtils'
import { vendorAttachments, vendors } from "@/db/schema/vendors";
+import { vendorRegularRegistrations } from "@/db/schema/vendorRegistrations";
import { saveFile, saveDRMFile } from "@/lib/file-stroage";
import { GetVendorsSchema } from "../vendors/validations";
import { selectVendors } from "../vendors/repository";
@@ -2462,6 +2463,8 @@ export async function requestInvestigationAction(
// 캐시 무효화
revalidateTag("vendor-investigations");
revalidateTag("pq-submissions");
+ revalidateTag("vendor-pq-submissions");
+ revalidatePath("/evcp/pq_new");
return {
success: true,
@@ -2589,8 +2592,32 @@ export async function sendInvestigationResultsAction(input: {
vendorCode: investigation.vendorCode || "N/A",
vendorName: investigation.vendorName || "N/A",
- // 실사 정보
- auditItem: investigation.pqItems || investigation.projectName || "N/A",
+ // 실사 정보 - pqItems를 itemCode-itemName 형태로 모든 항목 표시
+ auditItem: (() => {
+ if (investigation.pqItems) {
+ try {
+ const parsed = typeof investigation.pqItems === 'string'
+ ? JSON.parse(investigation.pqItems)
+ : investigation.pqItems;
+ if (Array.isArray(parsed)) {
+ return parsed.map(item => {
+ if (typeof item === 'string') return item;
+ if (typeof item === 'object') {
+ const code = item.itemCode || item.code || "";
+ const name = item.itemName || item.name || "";
+ if (code && name) return `${code}-${name}`;
+ return name || code || String(item);
+ }
+ return String(item);
+ }).join(', ');
+ }
+ return String(parsed);
+ } catch {
+ return String(investigation.pqItems);
+ }
+ }
+ return investigation.projectName || "N/A";
+ })(),
auditFactoryAddress: investigation.investigationAddress || "N/A",
auditMethod: getInvestigationMethodLabel(investigation.investigationMethod || ""),
auditResult: investigation.evaluationResult === "APPROVED" ? "Pass(승인)" :
@@ -2607,12 +2634,16 @@ export async function sendInvestigationResultsAction(input: {
}
// 이메일 발송
- await sendEmail({
- to: investigation.vendorEmail,
- subject: emailContext.subject,
- template: "audit-result-notice",
- context: emailContext,
- })
+ if (investigation.vendorEmail) {
+ await sendEmail({
+ to: investigation.vendorEmail,
+ subject: emailContext.subject,
+ template: "audit-result-notice",
+ context: emailContext,
+ })
+ } else {
+ throw new Error("벤더 이메일 주소가 없습니다.")
+ }
return { success: true, investigationId: investigation.id }
} catch (error) {
@@ -2636,6 +2667,65 @@ export async function sendInvestigationResultsAction(input: {
updatedAt: new Date(),
})
.where(inArray(vendorInvestigations.id, successfulInvestigationIds))
+
+ // 정규업체등록관리에 레코드 생성 로직
+ const successfulInvestigations = investigations.filter(inv =>
+ successfulInvestigationIds.includes(inv.id)
+ );
+
+ for (const investigation of successfulInvestigations) {
+ // 1. 미실사 PQ는 제외 (이미 COMPLETED 상태인 것만 처리하므로 실사된 것들)
+ // 2. 승인된 실사만 정규업체등록 대상
+ if (investigation.evaluationResult === "APPROVED") {
+ try {
+ // 기존 정규업체등록 레코드 확인
+ const existingRegistration = await tx
+ .select({ id: vendorRegularRegistrations.id })
+ .from(vendorRegularRegistrations)
+ .where(eq(vendorRegularRegistrations.vendorId, investigation.vendorId))
+ .limit(1);
+
+ // 프로젝트 PQ의 경우 기존 레코드가 있으면 skip, 없으면 생성
+ // 일반 PQ의 경우 무조건 생성 (이미 체크는 위에서 함)
+ if (existingRegistration.length === 0) {
+ // pqItems를 majorItems로 변환 - JSON 통째로 넘겨줌
+ let majorItemsJson = null;
+ if (investigation.pqItems) {
+ try {
+ // 이미 파싱된 객체거나 JSON 문자열인 경우 모두 처리
+ const parsed = typeof investigation.pqItems === 'string'
+ ? JSON.parse(investigation.pqItems)
+ : investigation.pqItems;
+
+ // 원본 구조를 최대한 보존하면서 JSON으로 저장
+ majorItemsJson = JSON.stringify(parsed);
+ } catch {
+ // 파싱 실패 시 문자열로 저장
+ majorItemsJson = JSON.stringify([{
+ itemCode: "UNKNOWN",
+ itemName: String(investigation.pqItems)
+ }]);
+ }
+ }
+
+ await tx.insert(vendorRegularRegistrations).values({
+ vendorId: investigation.vendorId,
+ status: "audit_pass", // 실사 통과 상태로 시작
+ majorItems: majorItemsJson,
+ registrationRequestDate: new Date().toISOString().split('T')[0], // date 타입으로 변환
+ remarks: `PQ 실사 통과로 자동 생성 (PQ번호: ${investigation.pqNumber || 'N/A'})`,
+ });
+
+ console.log(`✅ 정규업체등록 레코드 생성: 벤더 ID ${investigation.vendorId}`);
+ } else {
+ console.log(`⏭️ 정규업체등록 레코드 이미 존재: 벤더 ID ${investigation.vendorId} (Skip)`);
+ }
+ } catch (error) {
+ console.error(`❌ 정규업체등록 레코드 생성 실패 (벤더 ID: ${investigation.vendorId}):`, error);
+ // 정규업체등록 생성 실패는 전체 프로세스를 중단하지 않음
+ }
+ }
+ }
}
return {
@@ -2649,6 +2739,7 @@ export async function sendInvestigationResultsAction(input: {
// 캐시 무효화
revalidateTag("vendor-investigations")
revalidateTag("pq-submissions")
+ revalidateTag("vendor-regular-registrations")
return {
success: true,
@@ -3578,6 +3669,7 @@ export async function updateInvestigationDetailsAction(input: {
confirmedAt?: Date;
evaluationResult?: "APPROVED" | "SUPPLEMENT" | "REJECTED";
investigationNotes?: string;
+ attachments?: File[];
}) {
try {
const updateData: any = {
@@ -3595,11 +3687,72 @@ export async function updateInvestigationDetailsAction(input: {
if (input.investigationNotes !== undefined) {
updateData.investigationNotes = input.investigationNotes;
}
+ // evaluationResult가 APPROVED라면 investigationStatus를 "COMPLETED"(완료됨)로 변경
+ if (input.evaluationResult === "APPROVED") {
+ updateData.investigationStatus = "COMPLETED";
+ }
- await db
- .update(vendorInvestigations)
- .set(updateData)
- .where(eq(vendorInvestigations.id, input.investigationId));
+ // 트랜잭션으로 실사 정보 업데이트와 첨부파일 저장을 함께 처리
+ await db.transaction(async (tx) => {
+ // 1. 실사 정보 업데이트
+ await tx
+ .update(vendorInvestigations)
+ .set(updateData)
+ .where(eq(vendorInvestigations.id, input.investigationId));
+
+ // 2. 첨부파일 처리
+ if (input.attachments && input.attachments.length > 0) {
+ for (const file of input.attachments) {
+ try {
+ console.log(`📁 실사 첨부파일 처리 중: ${file.name} (${file.size} bytes)`);
+
+ // saveFile을 사용하여 파일 저장
+ const saveResult = await saveFile({
+ file,
+ directory: `vendor-investigation/${input.investigationId}`,
+ originalName: file.name,
+ userId: "investigation-update"
+ });
+
+ if (!saveResult.success) {
+ console.error(`❌ 파일 저장 실패: ${file.name}`, saveResult.error);
+ throw new Error(`파일 저장 실패: ${file.name} - ${saveResult.error}`);
+ }
+
+ console.log(`✅ 파일 저장 완료: ${file.name} -> ${saveResult.fileName}`);
+
+ // 파일 타입 결정
+ let attachmentType = "OTHER";
+ if (file.type.includes("pdf")) {
+ attachmentType = "REPORT";
+ } else if (file.type.includes("image")) {
+ attachmentType = "PHOTO";
+ } else if (
+ file.type.includes("word") ||
+ file.type.includes("document") ||
+ file.name.toLowerCase().includes("report")
+ ) {
+ attachmentType = "DOCUMENT";
+ }
+
+ // DB에 첨부파일 레코드 생성
+ await tx.insert(vendorInvestigationAttachments).values({
+ investigationId: input.investigationId,
+ fileName: saveResult.originalName!,
+ originalFileName: file.name,
+ filePath: saveResult.publicPath!,
+ fileSize: file.size,
+ mimeType: file.type || 'application/octet-stream',
+ attachmentType: attachmentType as "REPORT" | "PHOTO" | "DOCUMENT" | "CERTIFICATE" | "OTHER",
+ });
+
+ } catch (error) {
+ console.error(`❌ 첨부파일 처리 오류: ${file.name}`, error);
+ throw new Error(`첨부파일 처리 중 오류가 발생했습니다: ${file.name}`);
+ }
+ }
+ }
+ });
revalidateTag("pq-submissions");
revalidatePath("/evcp/pq_new");
@@ -3611,9 +3764,10 @@ export async function updateInvestigationDetailsAction(input: {
} catch (error) {
console.error("실사 정보 업데이트 오류:", error);
+ const errorMessage = error instanceof Error ? error.message : "실사 정보 업데이트 중 오류가 발생했습니다.";
return {
success: false,
- error: "실사 정보 업데이트 중 오류가 발생했습니다."
+ error: errorMessage
};
}
}