diff options
Diffstat (limited to 'lib/rfq-last/service.ts')
| -rw-r--r-- | lib/rfq-last/service.ts | 376 |
1 files changed, 343 insertions, 33 deletions
diff --git a/lib/rfq-last/service.ts b/lib/rfq-last/service.ts index 9943c02d..02429b6a 100644 --- a/lib/rfq-last/service.ts +++ b/lib/rfq-last/service.ts @@ -2847,7 +2847,7 @@ export async function sendRfqToVendors({ const picInfo = await getPicInfo(rfqData.picId, rfqData.picName); // 3. 프로젝트 정보 조회 - const projectInfo = rfqData.projectId + const projectInfo = rfqData.projectId ? await getProjectInfo(rfqData.projectId) : null; @@ -2856,7 +2856,7 @@ export async function sendRfqToVendors({ const designAttachments = await getDesignAttachments(rfqId); // 5. 벤더별 처리 - const { results, errors, savedContracts, tbeSessionsCreated } = + const { results, errors, savedContracts, tbeSessionsCreated } = await processVendors({ rfqId, rfqData, @@ -2979,17 +2979,26 @@ async function prepareEmailAttachments(rfqId: number, attachmentIds: number[]) { ); const emailAttachments = []; - + for (const { attachment, revision } of attachments) { if (revision?.filePath) { try { - const fullPath = path.join( - process.cwd(), - `${process.env.NAS_PATH}`, + + const isProduction = process.env.NODE_ENV === "production"; + + const fullPath = isProduction + + path.join( + process.cwd(), + `public`, + revision.filePath + ) + : path.join( + `${process.env.NAS_PATH}`, revision.filePath ); const fileBuffer = await fs.readFile(fullPath); - + emailAttachments.push({ filename: revision.originalFileName, content: fileBuffer, @@ -3052,9 +3061,9 @@ async function processVendors({ // PDF 저장 디렉토리 준비 const contractsDir = path.join( - process.cwd(), - `${process.env.NAS_PATH}`, - "contracts", + process.cwd(), + `${process.env.NAS_PATH}`, + "contracts", "generated" ); await mkdir(contractsDir, { recursive: true }); @@ -3077,18 +3086,18 @@ async function processVendors({ }); results.push(vendorResult.result); - + if (vendorResult.contracts) { savedContracts.push(...vendorResult.contracts); } - + if (vendorResult.tbeSession) { tbeSessionsCreated.push(vendorResult.tbeSession); } } catch (error) { console.error(`벤더 ${vendor.vendorName} 처리 실패:`, error); - + errors.push({ vendorId: vendor.vendorId, vendorName: vendor.vendorName, @@ -3182,7 +3191,7 @@ function prepareEmailRecipients(vendor: any, picEmail: string) { vendor.customEmails?.forEach((custom: any) => { if (custom.email !== vendor.selectedMainEmail && - !vendor.additionalEmails.includes(custom.email)) { + !vendor.additionalEmails.includes(custom.email)) { ccEmails.push(custom.email); } }); @@ -3235,14 +3244,14 @@ async function handleRfqDetail({ ); // 새 detail 생성 - const { - id, - updatedBy, - updatedAt, - isLatest, - sendVersion: oldSendVersion, - emailResentCount, - ...restRfqDetail + const { + id, + updatedBy, + updatedAt, + isLatest, + sendVersion: oldSendVersion, + emailResentCount, + ...restRfqDetail } = rfqDetail; const [newRfqDetail] = await tx @@ -3265,7 +3274,7 @@ async function handleRfqDetail({ }) .returning(); - await tx + await tx .update(basicContract) .set({ rfqCompanyId: newRfqDetail.id, @@ -3273,7 +3282,7 @@ async function handleRfqDetail({ .where( and( eq(basicContract.rfqCompanyId, rfqDetail.id), - eq(rfqLastDetails.vendorsId, vendor.vendorId), + eq(basicContract.vendorId, vendor.vendorId), ) ); @@ -3382,7 +3391,7 @@ async function createOrUpdateContract({ }) .where(eq(basicContract.id, existingContract.id)) .returning(); - + return { ...updated, isUpdated: true }; } else { // 새로 생성 @@ -3401,7 +3410,7 @@ async function createOrUpdateContract({ updatedAt: new Date() }) .returning(); - + return { ...created, isUpdated: false }; } } @@ -3503,11 +3512,11 @@ async function handleTbeSession({ sessionType: "initial", status: "준비중", evaluationResult: null, - plannedStartDate: rfqData.dueDate - ? addDays(new Date(rfqData.dueDate), 1) + plannedStartDate: rfqData.dueDate + ? addDays(new Date(rfqData.dueDate), 1) : addDays(new Date(), 14), - plannedEndDate: rfqData.dueDate - ? addDays(new Date(rfqData.dueDate), 7) + plannedEndDate: rfqData.dueDate + ? addDays(new Date(rfqData.dueDate), 7) : addDays(new Date(), 21), leadEvaluatorId: rfqData.picId, createdBy: Number(currentUser.id), @@ -3536,11 +3545,11 @@ async function handleTbeSession({ async function generateTbeSessionCode(tx: any) { const year = new Date().getFullYear(); const pattern = `TBE-${year}-%`; - + const [lastTbeSession] = await tx .select({ sessionCode: rfqLastTbeSessions.sessionCode }) .from(rfqLastTbeSessions) - .where(like(rfqLastTbeSessions.sessionCode,pattern )) + .where(like(rfqLastTbeSessions.sessionCode, pattern)) .orderBy(sql`${rfqLastTbeSessions.sessionCode} DESC`) .limit(1); @@ -3624,7 +3633,7 @@ async function updateRfqStatus(rfqId: number, userId: number) { updatedAt: new Date() }) .where(eq(rfqsLast.id, rfqId)); - } +} export async function updateRfqDueDate( rfqId: number, @@ -4006,4 +4015,305 @@ function getTemplateNameByType( case "기술자료": return "기술"; default: return contractType; } +} + + +export async function updateAttachmentTypes( + attachmentIds: number[], + attachmentType: "구매" | "설계" +) { + try { + // 권한 체크 등 필요시 추가 + + await db + .update(rfqLastVendorAttachments) + .set({ attachmentType }) + .where(inArray(rfqLastVendorAttachments.id, attachmentIds)); + + // 페이지 리밸리데이션 + // revalidatePath("/rfq"); + + return { success: true, message: `${attachmentIds.length}개 항목이 "${attachmentType}"로 변경되었습니다.` }; + } catch (error) { + console.error("Failed to update attachment types:", error); + return { success: false, message: "문서 유형 변경에 실패했습니다." }; + } +} + +// 단일 RFQ 밀봉 토글 +export async function toggleRfqSealed(rfqId: number) { + try { + const session = await getServerSession(authOptions) + + if (!session?.user) { + throw new Error("인증이 필요합니다.") + } + // 현재 상태 조회 + const [currentRfq] = await db + .select({ rfqSealedYn: rfqsLast.rfqSealedYn }) + .from(rfqsLast) + .where(eq(rfqsLast.id, rfqId)); + + if (!currentRfq) { + throw new Error("RFQ를 찾을 수 없습니다."); + } + + // 상태 토글 + const [updated] = await db + .update(rfqsLast) + .set({ + rfqSealedYn: !currentRfq.rfqSealedYn, + updatedBy: Number(session.user.id), + updatedAt: new Date(), + }) + .where(eq(rfqsLast.id, rfqId)) + .returning(); + + revalidatePath("/evcp/rfq-last"); + + return { + success: true, + data: updated, + message: updated.rfqSealedYn ? "견적이 밀봉되었습니다." : "견적 밀봉이 해제되었습니다.", + }; + } catch (error) { + console.error("RFQ 밀봉 상태 변경 실패:", error); + return { + success: false, + error: error instanceof Error ? error.message : "알 수 없는 오류가 발생했습니다.", + }; + } +} + +// 여러 RFQ 일괄 밀봉 +export async function sealMultipleRfqs(rfqIds: number[]) { + try { + const session = await getServerSession(authOptions) + + if (!session?.user) { + throw new Error("인증이 필요합니다.") + } + + if (!rfqIds || rfqIds.length === 0) { + throw new Error("선택된 RFQ가 없습니다."); + } + + const updated = await db + .update(rfqsLast) + .set({ + rfqSealedYn: true, + updatedBy: Number(session.user.id), + updatedAt: new Date(), + }) + .where(inArray(rfqsLast.id, rfqIds)) + .returning(); + + revalidatePath("/evcp/rfq-last"); + + return { + success: true, + count: updated.length, + message: `${updated.length}건의 견적이 밀봉되었습니다.`, + }; + } catch (error) { + console.error("RFQ 일괄 밀봉 실패:", error); + return { + success: false, + error: error instanceof Error ? error.message : "알 수 없는 오류가 발생했습니다.", + }; + } +} + +// 여러 RFQ 일괄 밀봉 해제 +export async function unsealMultipleRfqs(rfqIds: number[]) { + try { + const session = await getServerSession(authOptions) + + if (!session?.user) { + throw new Error("인증이 필요합니다.") + } + if (!rfqIds || rfqIds.length === 0) { + throw new Error("선택된 RFQ가 없습니다."); + } + + const updated = await db + .update(rfqsLast) + .set({ + rfqSealedYn: false, + updatedBy: Number(session.user.id), + updatedAt: new Date(), + }) + .where(inArray(rfqsLast.id, rfqIds)) + .returning(); + + revalidatePath("/evcp/rfq-last"); + + return { + success: true, + count: updated.length, + message: `${updated.length}건의 견적 밀봉이 해제되었습니다.`, + }; + } catch (error) { + console.error("RFQ 밀봉 해제 실패:", error); + return { + success: false, + error: error instanceof Error ? error.message : "알 수 없는 오류가 발생했습니다.", + }; + } +} + +// 단일 RFQ 밀봉 (밀봉만) +export async function sealRfq(rfqId: number) { + try { + const session = await getServerSession(authOptions) + + if (!session?.user) { + throw new Error("인증이 필요합니다.") + } + + const [updated] = await db + .update(rfqsLast) + .set({ + rfqSealedYn: true, + updatedBy: Number(session.user.id), + updatedAt: new Date(), + }) + .where(eq(rfqsLast.id, rfqId)) + .returning(); + + if (!updated) { + throw new Error("RFQ를 찾을 수 없습니다."); + } + + revalidatePath("/evcp/rfq-last"); + + return { + success: true, + data: updated, + message: "견적이 밀봉되었습니다.", + }; + } catch (error) { + console.error("RFQ 밀봉 실패:", error); + return { + success: false, + error: error instanceof Error ? error.message : "알 수 없는 오류가 발생했습니다.", + }; + } +} + +// 단일 RFQ 밀봉 해제 +export async function unsealRfq(rfqId: number) { + try { + const session = await getServerSession(authOptions) + + if (!session?.user) { + throw new Error("인증이 필요합니다.") + } + + const [updated] = await db + .update(rfqsLast) + .set({ + rfqSealedYn: false, + updatedBy: Number(session.user.id), + updatedAt: new Date(), + }) + .where(eq(rfqsLast.id, rfqId)) + .returning(); + + if (!updated) { + throw new Error("RFQ를 찾을 수 없습니다."); + } + + revalidatePath("/evcp/rfq-last"); + + return { + success: true, + data: updated, + message: "견적 밀봉이 해제되었습니다.", + }; + } catch (error) { + console.error("RFQ 밀봉 해제 실패:", error); + return { + success: false, + error: error instanceof Error ? error.message : "알 수 없는 오류가 발생했습니다.", + }; + } +} + + + +export async function updateShortList( + rfqId: number, + vendorIds: number[], + shortListStatus: boolean = true +) { + try { + // 권한 체크 등 필요한 검증 + const session = await getServerSession(authOptions) + + if (!session?.user) { + throw new Error("인증이 필요합니다.") + } + + // 트랜잭션으로 처리 + const result = await db.transaction(async (tx) => { + // 해당 RFQ의 모든 벤더들의 shortList를 먼저 false로 설정 (선택적) + // 만약 선택된 것만 true로 하고 나머지는 그대로 두려면 이 부분 제거 + await tx + .update(rfqLastDetails) + .set({ + shortList: false, + updatedBy: session.user.id, + updatedAt: new Date() + }) + .where( + and( + eq(rfqLastDetails.rfqsLastId, rfqId), + eq(rfqLastDetails.isLatest, true) + ) + ); + + // 선택된 벤더들의 shortList를 true로 설정 + if (vendorIds.length > 0) { + const updates = await Promise.all( + vendorIds.map(vendorId => + tx + .update(rfqLastDetails) + .set({ + shortList: shortListStatus, + updatedBy: session.user.id, + updatedAt: new Date() + }) + .where( + and( + eq(rfqLastDetails.rfqsLastId, rfqId), + eq(rfqLastDetails.vendorsId, vendorId), + eq(rfqLastDetails.isLatest, true) + ) + ) + .returning() + ) + ); + + return { + success: true, + updatedCount: updates.length, + vendorIds + }; + } + + return { + success: true, + updatedCount: 0, + vendorIds: [] + }; + }); + + // revalidatePath(`/buyer/rfq/${rfqId}`); + return result; + + } catch (error) { + console.error("Short List 업데이트 실패:", error); + throw new Error("Short List 업데이트에 실패했습니다."); + } }
\ No newline at end of file |
