summaryrefslogtreecommitdiff
path: root/lib/vendor-regular-registrations/service.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendor-regular-registrations/service.ts')
-rw-r--r--lib/vendor-regular-registrations/service.ts400
1 files changed, 377 insertions, 23 deletions
diff --git a/lib/vendor-regular-registrations/service.ts b/lib/vendor-regular-registrations/service.ts
index b587ec23..51f4e82b 100644
--- a/lib/vendor-regular-registrations/service.ts
+++ b/lib/vendor-regular-registrations/service.ts
@@ -11,6 +11,7 @@ import { getServerSession } from "next-auth";
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
import { headers } from "next/headers";
import { sendEmail } from "@/lib/mail/sendEmail";
+import type { RegistrationRequestData } from "@/components/vendor-regular-registrations/registration-request-dialog";
import {
vendors,
vendorRegularRegistrations,
@@ -20,7 +21,8 @@ import {
basicContract,
vendorPQSubmissions,
vendorBusinessContacts,
- vendorAdditionalInfo
+ vendorAdditionalInfo,
+ basicContractTemplates
} from "@/db/schema";
import db from "@/db/db";
import { inArray, eq, desc } from "drizzle-orm";
@@ -437,7 +439,7 @@ export async function skipLegalReview(vendorIds: number[], skipReason: string) {
const newRegistration = await createVendorRegularRegistration({
vendorId: vendorId,
status: "cp_finished", // CP완료로 변경
- remarks: `법무검토 Skip: ${skipReason}`,
+ remarks: `GTC Skip: ${skipReason}`,
});
registrationId = newRegistration.id;
} else {
@@ -445,11 +447,12 @@ export async function skipLegalReview(vendorIds: number[], skipReason: string) {
registrationId = existingRegistrations[0].id;
const currentRemarks = existingRegistrations[0].remarks || "";
const newRemarks = currentRemarks
- ? `${currentRemarks}\n법무검토 Skip: ${skipReason}`
- : `법무검토 Skip: ${skipReason}`;
+ ? `${currentRemarks}\nGTC Skip: ${skipReason}`
+ : `GTC Skip: ${skipReason}`;
await updateVendorRegularRegistration(registrationId, {
status: "cp_finished", // CP완료로 변경
+ gtcSkipped: true, // GTC Skip 여부 설정
remarks: newRemarks,
});
}
@@ -470,18 +473,19 @@ export async function skipLegalReview(vendorIds: number[], skipReason: string) {
return {
success: true,
- message: `${successCount}개 업체의 법무검토를 Skip 처리했습니다.`,
+ message: `${successCount}개 업체의 GTC를 Skip 처리했습니다.`,
};
} catch (error) {
console.error("Error skipping legal review:", error);
return {
success: false,
- error: error instanceof Error ? error.message : "법무검토 Skip 처리 중 오류가 발생했습니다.",
+ error: error instanceof Error ? error.message : "GTC Skip 처리 중 오류가 발생했습니다.",
};
}
}
-// 안전적격성평가 Skip 기능
+// 안전적격성평가 Skip 기능 (삭제됨 - 개별 입력으로 대체)
+/*
export async function skipSafetyQualification(vendorIds: number[], skipReason: string) {
try {
const session = await getServerSession(authOptions);
@@ -562,6 +566,42 @@ export async function skipSafetyQualification(vendorIds: number[], skipReason: s
};
}
}
+*/
+
+// 주요품목 업데이트
+export async function updateMajorItems(
+ registrationId: number,
+ majorItems: string
+) {
+ try {
+ const session = await getServerSession(authOptions);
+ if (!session?.user) {
+ return { success: false, error: "로그인이 필요합니다." };
+ }
+
+ const result = await updateVendorRegularRegistration(registrationId, {
+ majorItems: majorItems,
+ });
+
+ if (!result) {
+ return { success: false, error: "등록 정보를 찾을 수 없습니다." };
+ }
+
+ // 캐시 무효화
+ revalidateTag("vendor-regular-registrations");
+
+ return {
+ success: true,
+ message: "주요품목이 업데이트되었습니다.",
+ };
+ } catch (error) {
+ console.error("Error updating major items:", error);
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : "주요품목 업데이트 중 오류가 발생했습니다.",
+ };
+ }
+}
// 벤더용 현황 조회 함수들
export async function fetchVendorRegistrationStatus(vendorId: number) {
@@ -570,7 +610,15 @@ export async function fetchVendorRegistrationStatus(vendorId: number) {
try {
// 벤더 기본 정보
const vendor = await db
- .select()
+ .select({
+ id: vendors.id,
+ vendorName: vendors.vendorName,
+ taxId: vendors.taxId,
+ representativeName: vendors.representativeName,
+ country: vendors.country,
+ createdAt: vendors.createdAt,
+ updatedAt: vendors.updatedAt,
+ })
.from(vendors)
.where(eq(vendors.id, vendorId))
.limit(1)
@@ -584,7 +632,23 @@ export async function fetchVendorRegistrationStatus(vendorId: number) {
// 정규업체 등록 정보
const registration = await db
- .select()
+ .select({
+ id: vendorRegularRegistrations.id,
+ vendorId: vendorRegularRegistrations.vendorId,
+ potentialCode: vendorRegularRegistrations.potentialCode,
+ status: vendorRegularRegistrations.status,
+ majorItems: vendorRegularRegistrations.majorItems,
+ registrationRequestDate: vendorRegularRegistrations.registrationRequestDate,
+ assignedDepartment: vendorRegularRegistrations.assignedDepartment,
+ assignedDepartmentCode: vendorRegularRegistrations.assignedDepartmentCode,
+ assignedUser: vendorRegularRegistrations.assignedUser,
+ assignedUserCode: vendorRegularRegistrations.assignedUserCode,
+ remarks: vendorRegularRegistrations.remarks,
+ safetyQualificationContent: vendorRegularRegistrations.safetyQualificationContent,
+ gtcSkipped: vendorRegularRegistrations.gtcSkipped,
+ createdAt: vendorRegularRegistrations.createdAt,
+ updatedAt: vendorRegularRegistrations.updatedAt,
+ })
.from(vendorRegularRegistrations)
.where(eq(vendorRegularRegistrations.vendorId, vendorId))
.limit(1)
@@ -613,12 +677,45 @@ export async function fetchVendorRegistrationStatus(vendorId: number) {
.orderBy(desc(vendorPQSubmissions.createdAt))
.limit(1)
- // 기본계약 정보
- const contractInfo = await db
- .select()
+ // 기본계약 정보 - 템플릿 정보와 함께 조회
+ const allVendorContracts = await db
+ .select({
+ templateId: basicContract.templateId,
+ templateName: basicContractTemplates.templateName,
+ status: basicContract.status,
+ createdAt: basicContract.createdAt,
+ })
.from(basicContract)
+ .leftJoin(basicContractTemplates, eq(basicContract.templateId, basicContractTemplates.id))
.where(eq(basicContract.vendorId, vendorId))
- .limit(1)
+ .orderBy(desc(basicContract.createdAt))
+
+ // 계약 필터링 (기술자료, 비밀유지 제외)
+ const filteredContracts = allVendorContracts.filter(contract =>
+ contract.templateName &&
+ !contract.templateName.includes("기술자료") &&
+ !contract.templateName.includes("비밀유지")
+ )
+
+ // 템플릿 이름별로 가장 최신 계약만 유지
+ const vendorContracts = filteredContracts.reduce((acc: typeof filteredContracts, contract) => {
+ const existing = acc.find((c: typeof contract) => c.templateName === contract.templateName)
+ if (!existing || (contract.createdAt && existing.createdAt && contract.createdAt > existing.createdAt)) {
+ return acc.filter((c: typeof contract) => c.templateName !== contract.templateName).concat(contract)
+ }
+ return acc
+ }, [] as typeof filteredContracts)
+
+ console.log(`🏢 Partners 벤더 ID ${vendorId} 기본계약 정보:`, {
+ allContractsCount: allVendorContracts.length,
+ filteredContractsCount: filteredContracts.length,
+ finalContractsCount: vendorContracts.length,
+ vendorContracts: vendorContracts.map((c: any) => ({
+ templateName: c.templateName,
+ status: c.status,
+ createdAt: c.createdAt
+ }))
+ })
// 업무담당자 정보
const businessContacts = await db
@@ -636,15 +733,15 @@ export async function fetchVendorRegistrationStatus(vendorId: number) {
// 문서 제출 현황 계산
const documentStatus = {
businessRegistration: vendorFiles.some(f => f.attachmentType === "BUSINESS_REGISTRATION"),
- creditEvaluation: vendorFiles.some(f => f.attachmentType === "CREDIT_EVALUATION"),
- bankCopy: vendorFiles.some(f => f.attachmentType === "BANK_COPY"),
+ creditEvaluation: vendorFiles.some(f => f.attachmentType === "CREDIT_REPORT"), // CREDIT_EVALUATION -> CREDIT_REPORT
+ bankCopy: vendorFiles.some(f => f.attachmentType === "BANK_ACCOUNT_COPY"), // BANK_COPY -> BANK_ACCOUNT_COPY
auditResult: investigationFiles.length > 0, // DocumentStatusDialog에서 사용하는 키
- cpDocument: !!contractInfo[0]?.status && contractInfo[0].status === "completed",
- gtc: !!contractInfo[0]?.status && contractInfo[0].status === "completed",
- standardSubcontract: !!contractInfo[0]?.status && contractInfo[0].status === "completed",
- safetyHealth: !!contractInfo[0]?.status && contractInfo[0].status === "completed",
- ethics: !!contractInfo[0]?.status && contractInfo[0].status === "completed",
- domesticCredit: !!contractInfo[0]?.status && contractInfo[0].status === "completed",
+ cpDocument: vendorContracts.some(c => c.status === "COMPLETED"),
+ gtc: vendorContracts.some(c => c.templateName?.includes("GTC") && c.status === "COMPLETED"),
+ standardSubcontract: vendorContracts.some(c => c.templateName?.includes("표준하도급") && c.status === "COMPLETED"),
+ safetyHealth: vendorContracts.some(c => c.templateName?.includes("안전보건") && c.status === "COMPLETED"),
+ ethics: vendorContracts.some(c => c.templateName?.includes("윤리") && c.status === "COMPLETED"),
+ domesticCredit: vendorContracts.some(c => c.templateName?.includes("신용") && c.status === "COMPLETED"),
safetyQualification: investigationFiles.length > 0,
}
@@ -656,6 +753,26 @@ export async function fetchVendorRegistrationStatus(vendorId: number) {
const requiredContactTypes = ["sales", "design", "delivery", "quality", "tax_invoice"]
const existingContactTypes = businessContacts.map(contact => contact.contactType)
const missingContactTypes = requiredContactTypes.filter(type => !existingContactTypes.includes(type))
+
+ // 추가정보 완료 여부 (업무담당자 + 추가정보 테이블 모두 필요)
+ const contactsCompleted = missingContactTypes.length === 0
+ const additionalInfoTableCompleted = !!additionalInfo[0]
+ const additionalInfoCompleted = contactsCompleted && additionalInfoTableCompleted
+
+ console.log(`🔍 Partners 벤더 ID ${vendorId} 전체 데이터:`, {
+ vendor: vendor[0],
+ registration: registration[0],
+ safetyQualificationContent: registration[0]?.safetyQualificationContent,
+ gtcSkipped: registration[0]?.gtcSkipped,
+ requiredContactTypes,
+ existingContactTypes,
+ missingContactTypes,
+ contactsCompleted,
+ additionalInfoTableCompleted,
+ additionalInfoData: additionalInfo[0],
+ finalAdditionalInfoCompleted: additionalInfoCompleted,
+ basicContractsCount: vendorContracts.length
+ })
return {
success: true,
@@ -666,10 +783,10 @@ export async function fetchVendorRegistrationStatus(vendorId: number) {
missingDocuments,
businessContacts,
missingContactTypes,
- additionalInfo: additionalInfo[0] || null,
+ additionalInfo: additionalInfoCompleted, // boolean 값으로 변경
pqSubmission: pqSubmission[0] || null,
auditPassed: investigationFiles.length > 0,
- contractInfo: contractInfo[0] || null,
+ basicContracts: vendorContracts, // 기본계약 정보 추가
incompleteItemsCount: {
documents: missingDocuments.length,
contacts: missingContactTypes.length,
@@ -823,3 +940,240 @@ export async function saveVendorAdditionalInfo(
}
}
}
+
+// 안전적격성 평가 업데이트
+export async function updateSafetyQualification(
+ registrationId: number,
+ safetyQualificationContent: string
+) {
+ try {
+ const session = await getServerSession(authOptions);
+ if (!session?.user) {
+ return { success: false, error: "로그인이 필요합니다." };
+ }
+
+ const result = await updateVendorRegularRegistration(registrationId, {
+ safetyQualificationContent: safetyQualificationContent.trim(),
+ });
+
+ if (!result) {
+ return { success: false, error: "등록 정보를 찾을 수 없습니다." };
+ }
+
+ // 캐시 무효화
+ revalidateTag("vendor-regular-registrations");
+
+ return {
+ success: true,
+ message: "안전적격성 평가가 등록되었습니다.",
+ };
+ } catch (error) {
+ console.error("Error updating safety qualification:", error);
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : "안전적격성 평가 등록 중 오류가 발생했습니다.",
+ };
+ }
+}
+
+// GTC Skip 처리
+export async function updateGtcSkip(
+ registrationId: number,
+ skipReason: string
+) {
+ try {
+ const session = await getServerSession(authOptions);
+ if (!session?.user) {
+ return { success: false, error: "로그인이 필요합니다." };
+ }
+
+ // 현재 비고 가져오기
+ const existingRegistration = await getVendorRegularRegistrationById(registrationId);
+ if (!existingRegistration) {
+ return { success: false, error: "등록 정보를 찾을 수 없습니다." };
+ }
+
+ const currentRemarks = existingRegistration.remarks || "";
+ const newRemarks = currentRemarks
+ ? `${currentRemarks}\nGTC Skip: ${skipReason}`
+ : `GTC Skip: ${skipReason}`;
+
+ const result = await updateVendorRegularRegistration(registrationId, {
+ gtcSkipped: true,
+ remarks: newRemarks,
+ });
+
+ if (!result) {
+ return { success: false, error: "등록 정보를 찾을 수 없습니다." };
+ }
+
+ // 캐시 무효화
+ revalidateTag("vendor-regular-registrations");
+
+ return {
+ success: true,
+ message: "GTC Skip이 처리되었습니다.",
+ };
+ } catch (error) {
+ console.error("Error updating GTC skip:", error);
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : "GTC Skip 처리 중 오류가 발생했습니다.",
+ };
+ }
+}
+
+// 정규업체 등록 요청을 위한 상세 데이터 조회
+export async function fetchRegistrationRequestData(registrationId: number) {
+ try {
+ // 등록 정보 조회
+ const registration = await db
+ .select()
+ .from(vendorRegularRegistrations)
+ .where(eq(vendorRegularRegistrations.id, registrationId))
+ .limit(1);
+
+ if (!registration[0]) {
+ return { success: false, error: "등록 정보를 찾을 수 없습니다." };
+ }
+
+ // 벤더 정보 조회
+ const vendor = await db
+ .select({
+ id: vendors.id,
+ vendorName: vendors.vendorName,
+ taxId: vendors.taxId,
+ representativeName: vendors.representativeName,
+ representativeBirth: vendors.representativeBirth,
+ representativeEmail: vendors.representativeEmail,
+ representativePhone: vendors.representativePhone,
+ representativeWorkExpirence: vendors.representativeWorkExpirence,
+ country: vendors.country,
+ corporateRegistrationNumber: vendors.corporateRegistrationNumber,
+ address: vendors.address,
+ phone: vendors.phone,
+ email: vendors.email,
+ createdAt: vendors.createdAt,
+ updatedAt: vendors.updatedAt,
+ })
+ .from(vendors)
+ .where(eq(vendors.id, registration[0].vendorId))
+ .limit(1);
+
+ if (!vendor[0]) {
+ return { success: false, error: "벤더 정보를 찾을 수 없습니다." };
+ }
+
+ // 업무담당자 정보 조회
+ const businessContacts = await db
+ .select()
+ .from(vendorBusinessContacts)
+ .where(eq(vendorBusinessContacts.vendorId, vendor[0].id));
+
+ // 추가정보 조회
+ const additionalInfo = await db
+ .select()
+ .from(vendorAdditionalInfo)
+ .where(eq(vendorAdditionalInfo.vendorId, vendor[0].id))
+ .limit(1);
+
+ return {
+ success: true,
+ data: {
+ registration: registration[0],
+ vendor: vendor[0],
+ businessContacts,
+ additionalInfo: additionalInfo[0] || null,
+ }
+ };
+
+ } catch (error) {
+ console.error("정규업체 등록 요청 데이터 조회 오류:", error);
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : "데이터 조회 중 오류가 발생했습니다."
+ };
+ }
+}
+
+// 정규업체 등록 요청 서버 액션
+export async function submitRegistrationRequest(
+ registrationId: number,
+ requestData: RegistrationRequestData
+) {
+ try {
+ const session = await getServerSession(authOptions);
+ if (!session?.user) {
+ return { success: false, error: "인증이 필요합니다." };
+ }
+
+ // 현재 등록 정보 조회
+ const registration = await db
+ .select()
+ .from(vendorRegularRegistrations)
+ .where(eq(vendorRegularRegistrations.id, registrationId))
+ .limit(1);
+
+ if (!registration[0]) {
+ return { success: false, error: "등록 정보를 찾을 수 없습니다." };
+ }
+
+ // 조건충족 상태인지 확인
+ if (registration[0].status !== "approval_ready") {
+ return { success: false, error: "조건충족 상태가 아닙니다." };
+ }
+
+ // 정규업체 등록 요청 데이터를 JSON으로 저장
+ const registrationRequestData = {
+ requestDate: new Date(),
+ requestedBy: session.user.id,
+ requestedByName: session.user.name,
+ requestData: requestData,
+ status: "requested" // 요청됨
+ };
+
+ // 상태를 '등록요청됨'으로 변경하고 요청 데이터 저장
+ await db
+ .update(vendorRegularRegistrations)
+ .set({
+ status: "registration_requested",
+ remarks: `정규업체 등록 요청됨 - ${new Date().toISOString()}\n요청자: ${session.user.name}`,
+ updatedAt: new Date(),
+ })
+ .where(eq(vendorRegularRegistrations.id, registrationId));
+
+ // TODO: MDG 인터페이스 연동
+ // await sendToMDG(registrationRequestData);
+
+ // TODO: Knox 결재 연동
+ // - 사업자등록증, 신용평가보고서, 개인정보동의서, 통장사본
+ // - 실사결과 보고서
+ // - CP문서, GTC문서, 비밀유지계약서
+ // await initiateKnoxApproval(registrationRequestData);
+
+ console.log("✅ 정규업체 등록 요청 데이터:", {
+ registrationId,
+ companyName: requestData.companyNameKor,
+ businessNumber: requestData.businessNumber,
+ representative: requestData.representativeNameKor,
+ requestedBy: session.user.name,
+ requestDate: new Date().toISOString()
+ });
+
+ // 캐시 무효화
+ revalidateTag("vendor-regular-registrations");
+ revalidateTag(`vendor-regular-registration-${registrationId}`);
+
+ return {
+ success: true,
+ message: "정규업체 등록 요청이 성공적으로 제출되었습니다.\nKnox 결재 시스템과 MDG 인터페이스 연동은 추후 구현 예정입니다."
+ };
+
+ } catch (error) {
+ console.error("정규업체 등록 요청 오류:", error);
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : "정규업체 등록 요청 중 오류가 발생했습니다."
+ };
+ }
+} \ No newline at end of file