summaryrefslogtreecommitdiff
path: root/lib/vendor-investigation/service.ts
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-11-03 10:15:45 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-11-03 10:15:45 +0000
commitf2fafe555b65f9207c2c6e216b7d7b2ff83af866 (patch)
tree4a230e4bde10a612150a299922bc04cb15b0930f /lib/vendor-investigation/service.ts
parent1e857a0b1443ad2124caf3d180b7195651fe33e4 (diff)
(최겸) 구매 PQ/실사 수정
Diffstat (limited to 'lib/vendor-investigation/service.ts')
-rw-r--r--lib/vendor-investigation/service.ts198
1 files changed, 178 insertions, 20 deletions
diff --git a/lib/vendor-investigation/service.ts b/lib/vendor-investigation/service.ts
index 3ccbe880..c365a7ad 100644
--- a/lib/vendor-investigation/service.ts
+++ b/lib/vendor-investigation/service.ts
@@ -1,6 +1,6 @@
"use server"; // Next.js 서버 액션에서 직접 import하려면 (선택)
-import { items, vendorInvestigationAttachments, vendorInvestigations, vendorInvestigationsView, vendorPossibleItems, vendors, siteVisitRequests, vendorPQSubmissions } from "@/db/schema/"
+import { items, vendorInvestigationAttachments, vendorInvestigations, vendorInvestigationsView, vendorPossibleItems, vendors, siteVisitRequests, vendorPQSubmissions, users } from "@/db/schema/"
import { GetVendorsInvestigationSchema, updateVendorInvestigationSchema, updateVendorInvestigationProgressSchema, updateVendorInvestigationResultSchema } from "./validations"
import { asc, desc, ilike, inArray, and, or, gte, lte, eq, isNull, count } from "drizzle-orm";
import { revalidateTag, unstable_noStore, revalidatePath } from "next/cache";
@@ -17,6 +17,7 @@ import { cache } from "react"
import { deleteFile } from "../file-stroage";
import { saveDRMFile } from "../file-stroage";
import { decryptWithServerAction } from "@/components/drm/drmUtils";
+import { format, addDays } from "date-fns";
export async function getVendorsInvestigation(input: GetVendorsInvestigationSchema) {
return unstable_cache(
@@ -1057,27 +1058,102 @@ export async function requestSupplementDocumentAction({
})
.where(eq(vendorInvestigations.id, investigationId));
- // 2. 서류제출 요청을 위한 방문실사 요청 생성 (서류제출용)
- const [newSiteVisitRequest] = await db
- .insert(siteVisitRequests)
- .values({
- investigationId: investigationId,
- inspectionDuration: 0, // 서류제출은 방문 시간 0
- shiAttendees: {}, // 서류제출은 참석자 없음
- vendorRequests: {
- requiredDocuments: documentRequests.requiredDocuments,
- documentSubmissionOnly: true, // 서류제출 전용 플래그
- },
- additionalRequests: documentRequests.additionalRequests,
- status: "REQUESTED",
- })
- .returning();
+ // 2. 실사, 협력업체, 발송자 정보 조회
+ const investigationResult = await db
+ .select()
+ .from(vendorInvestigations)
+ .where(eq(vendorInvestigations.id, investigationId))
+ .limit(1);
+
+ const investigation = investigationResult[0];
+ if (!investigation) {
+ throw new Error('실사 정보를 찾을 수 없습니다.');
+ }
+
+ const vendorResult = await db
+ .select()
+ .from(vendors)
+ .where(eq(vendors.id, investigation.vendorId))
+ .limit(1);
+
+ const vendor = vendorResult[0];
+ if (!vendor) {
+ throw new Error('협력업체 정보를 찾을 수 없습니다.');
+ }
+
+ const senderResult = await db
+ .select()
+ .from(users)
+ .where(eq(users.id, investigation.requesterId!))
+ .limit(1);
+
+ const sender = senderResult[0];
+ if (!sender) {
+ throw new Error('발송자 정보를 찾을 수 없습니다.');
+ }
+
+ // 마감일 계산 (발송일 + 7일 또는 실사 예정일 중 먼저 도래하는 날)
+ const deadlineDate = (() => {
+ const deadlineFromToday = addDays(new Date(), 7);
+ if (investigation.forecastedAt) {
+ const forecastedDate = new Date(investigation.forecastedAt);
+ return forecastedDate < deadlineFromToday ? forecastedDate : deadlineFromToday;
+ }
+ return deadlineFromToday;
+ })();
+
+ // 메일 제목
+ const subject = `[SHI Audit] 보완 서류제출 요청 _ ${vendor.vendorName}`;
+
+ // 메일 컨텍스트
+ const context = {
+ // 기본 정보
+ vendorName: vendor.vendorName,
+ vendorEmail: vendor.email || '',
+ requesterName: sender.name,
+ requesterTitle: 'Procurement Manager',
+ requesterEmail: sender.email,
+
+ // 보완 요청 서류
+ requiredDocuments: documentRequests.requiredDocuments || [],
+
+ // 추가 요청사항
+ additionalRequests: documentRequests.additionalRequests || null,
+
+ // 마감일
+ deadlineDate: format(deadlineDate, 'yyyy.MM.dd'),
+
+ // 포털 URL
+ portalUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/ko/partners/site-visit`,
+
+ // 현재 연도
+ currentYear: new Date().getFullYear()
+ };
+
+ // 메일 발송 (벤더 이메일로 직접 발송)
+ try {
+ await sendEmail({
+ to: vendor.email || '',
+ cc: sender.email,
+ subject,
+ template: 'supplement-document-request' as string,
+ context,
+ });
+
+ console.log('보완 서류제출 요청 메일 발송 완료:', {
+ to: vendor.email,
+ subject,
+ vendorName: vendor.vendorName
+ });
+ } catch (emailError) {
+ console.error('보완 서류제출 요청 메일 발송 실패:', emailError);
+ }
// 3. 캐시 무효화
revalidateTag("vendor-investigations");
revalidateTag("site-visit-requests");
- return { success: true, siteVisitRequestId: newSiteVisitRequest.id };
+ return { success: true };
} catch (error) {
console.error("보완-서류제출 요청 실패:", error);
return {
@@ -1325,9 +1401,91 @@ export async function submitSupplementDocumentResponseAction({
return { success: true };
} catch (error) {
console.error("보완 서류제출 응답 처리 실패:", error);
- return {
- success: false,
- error: error instanceof Error ? error.message : "알 수 없는 오류"
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : "알 수 없는 오류"
+ };
+ }
+}
+
+// QM 담당자 변경 서버 액션
+export async function updateQMManagerAction({
+ investigationId,
+ qmManagerId,
+}: {
+ investigationId: number;
+ qmManagerId: number;
+}) {
+ try {
+ // 1. 실사 정보 조회 (상태 확인)
+ const investigation = await db
+ .select({
+ investigationStatus: vendorInvestigations.investigationStatus,
+ currentQmManagerId: vendorInvestigations.qmManagerId,
+ })
+ .from(vendorInvestigations)
+ .where(eq(vendorInvestigations.id, investigationId))
+ .limit(1);
+
+ if (!investigation || investigation.length === 0) {
+ return {
+ success: false,
+ error: "실사를 찾을 수 없습니다."
+ };
+ }
+
+ const currentInvestigation = investigation[0];
+
+ // 2. 상태 검증 (계획 상태만 변경 가능)
+ if (currentInvestigation.investigationStatus !== "PLANNED") {
+ return {
+ success: false,
+ error: "계획 상태인 실사만 QM 담당자를 변경할 수 있습니다."
+ };
+ }
+
+ // 3. QM 담당자 정보 조회
+ const qmManager = await db
+ .select({
+ id: users.id,
+ name: users.name,
+ email: users.email,
+ })
+ .from(users)
+ .where(eq(users.id, qmManagerId))
+ .limit(1);
+
+ if (!qmManager || qmManager.length === 0) {
+ return {
+ success: false,
+ error: "존재하지 않는 QM 담당자입니다."
+ };
+ }
+
+ const qmUser = qmManager[0];
+
+ // 4. QM 담당자 업데이트
+ await db
+ .update(vendorInvestigations)
+ .set({
+ qmManagerId: qmManagerId,
+ updatedAt: new Date(),
+ })
+ .where(eq(vendorInvestigations.id, investigationId));
+
+ // 5. 캐시 무효화
+ revalidateTag("vendor-investigations");
+
+ return {
+ success: true,
+ message: "QM 담당자가 성공적으로 변경되었습니다."
+ };
+
+ } catch (error) {
+ console.error("QM 담당자 변경 오류:", error);
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : "QM 담당자 변경 중 오류가 발생했습니다."
};
}
}