diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-18 00:23:40 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-18 00:23:40 +0000 |
| commit | cf8dac0c6490469dab88a560004b0c07dbd48612 (patch) | |
| tree | b9e76061e80d868331e6b4277deecb9086f845f3 /lib/rfq-last/service.ts | |
| parent | e5745fc0268bbb5770bc14a55fd58a0ec30b466e (diff) | |
(대표님) rfq, 계약, 서명 등
Diffstat (limited to 'lib/rfq-last/service.ts')
| -rw-r--r-- | lib/rfq-last/service.ts | 134 |
1 files changed, 122 insertions, 12 deletions
diff --git a/lib/rfq-last/service.ts b/lib/rfq-last/service.ts index 723a69fe..85db1ea7 100644 --- a/lib/rfq-last/service.ts +++ b/lib/rfq-last/service.ts @@ -16,6 +16,7 @@ import { addDays, format } from "date-fns" import { ko, enUS } from "date-fns/locale" import { generateBasicContractsForVendor } from "../basic-contract/gen-service"; import { writeFile, mkdir } from "fs/promises"; +import { generateItbRfqCode } from "../itb/service"; export async function getRfqs(input: GetRfqsSchema) { @@ -37,17 +38,14 @@ export async function getRfqs(input: GetRfqsSchema) { break; case "itb": // ITB: projectCompany가 있는 경우 - typeFilter = and( - isNotNull(rfqsLastView.projectCompany), - ne(rfqsLastView.projectCompany, '') - ); + typeFilter = + like(rfqsLastView.rfqCode,'I%') + + ; break; case "rfq": // RFQ: prNumber가 있는 경우 - typeFilter = and( - isNotNull(rfqsLastView.prNumber), - ne(rfqsLastView.prNumber, '') - ); + typeFilter = like(rfqsLastView.rfqCode,'R%'); break; } } @@ -1854,6 +1852,26 @@ export async function getRfqWithDetails(rfqId: number) { ) .orderBy(desc(rfqLastDetailsView.detailId)); + const tbeSessionsData = await db + .select({ + vendorId: rfqLastTbeSessions.vendorId, + sessionCode: rfqLastTbeSessions.sessionCode, + status: rfqLastTbeSessions.status, + evaluationResult: rfqLastTbeSessions.evaluationResult, + conditionalRequirements: rfqLastTbeSessions.conditionalRequirements, + conditionsFulfilled: rfqLastTbeSessions.conditionsFulfilled, + plannedStartDate: rfqLastTbeSessions.plannedStartDate, + actualStartDate: rfqLastTbeSessions.actualStartDate, + actualEndDate: rfqLastTbeSessions.actualEndDate, + }) + .from(rfqLastTbeSessions) + .where(eq(rfqLastTbeSessions.rfqsLastId, rfqId)); + + const tbeByVendor = tbeSessionsData.reduce((acc, tbe) => { + acc[tbe.vendorId] = tbe; + return acc; + }, {} as Record<number, typeof tbeSessionsData[0]>); + return { success: true, data: { @@ -1990,6 +2008,12 @@ export async function getRfqWithDetails(rfqId: number) { emailResentCount: d.emailResentCount, lastEmailSentAt: d.lastEmailSentAt, emailStatus: d.emailStatus, + + // TBE 정보 추가 + tbeSession: d.vendorId ? tbeByVendor[d.vendorId] : null, + tbeStatus: d.vendorId ? tbeByVendor[d.vendorId]?.status : null, + tbeEvaluationResult: d.vendorId ? tbeByVendor[d.vendorId]?.evaluationResult : null, + tbeSessionCode: d.vendorId ? tbeByVendor[d.vendorId]?.sessionCode : null, })), } }; @@ -2987,19 +3011,25 @@ async function prepareEmailAttachments(rfqId: number, attachmentIds: number[]) { for (const { attachment, revision } of attachments) { if (revision?.filePath) { + + const cleanPath = revision.filePath.startsWith('/api/files') + ? revision.filePath.slice('/api/files'.length) + : revision.filePath; + try { const isProduction = process.env.NODE_ENV === "production"; - const fullPath = isProduction + + const fullPath = !isProduction ? path.join( process.cwd(), `public`, - revision.filePath + cleanPath ) : path.join( `${process.env.NAS_PATH}`, - revision.filePath + cleanPath ); const fileBuffer = await fs.readFile(fullPath); @@ -3009,7 +3039,8 @@ async function prepareEmailAttachments(rfqId: number, attachmentIds: number[]) { contentType: revision.fileType || 'application/octet-stream' }); } catch (error) { - console.error(`첨부파일 읽기 실패: ${revision.filePath}`, error); + + console.error(`첨부파일 읽기 실패: ${cleanPath}`, error); } } } @@ -4320,4 +4351,83 @@ export async function updateShortList( console.error("Short List 업데이트 실패:", error); throw new Error("Short List 업데이트에 실패했습니다."); } +} + +interface AssignPicParams { + rfqIds: number[]; + picUserId: number; +} + +export async function assignPicToRfqs({ rfqIds, picUserId }: AssignPicParams) { + try { + const session = await getServerSession(authOptions); + if (!session?.user) { + throw new Error("인증이 필요합니다."); + } + + // 선택된 담당자 정보 조회 + const picUser = await db.query.users.findFirst({ + where: eq(users.id, picUserId), + }); + + if (!picUser) { + throw new Error("선택한 담당자를 찾을 수 없습니다."); + } + + // RFQ 코드가 "I"로 시작하는 것들만 필터링 (추가 검증) + const targetRfqs = await db.query.rfqsLast.findMany({ + where: inArray(rfqsLast.id, rfqIds), + }); + + // "I"로 시작하는 RFQ만 필터링 + const validRfqs = targetRfqs.filter(rfq => rfq.rfqCode?.startsWith("I")); + + if (validRfqs.length === 0) { + throw new Error("담당자를 지정할 수 있는 ITB가 없습니다."); + } + + // 트랜잭션으로 처리하여 동시성 문제 방지 + const updatedCount = await db.transaction(async (tx) => { + let successCount = 0; + + for (const rfq of validRfqs) { + // 각 RFQ에 대해 새로운 코드 생성 + const newRfqCode = await generateItbRfqCode(picUser.id); + + // RFQ 업데이트 + const result = await tx.update(rfqsLast) + .set({ + rfqCode: newRfqCode, // 새로운 RFQ 코드로 업데이트 + pic: picUser.id, + picCode: picUser.userCode || undefined, + picName: picUser.name, + status: "구매담당지정", // 상태도 업데이트 + updatedBy: parseInt(session.user.id), + updatedAt: new Date(), + }) + .where(eq(rfqsLast.id, rfq.id)); + + if (result) { + successCount++; + console.log(`RFQ ${rfq.rfqCode} -> ${newRfqCode} 업데이트 완료`); + } + } + + return successCount; + }); + + revalidatePath("/evcp/rfq-last"); + + return { + success: true, + message: `${updatedCount}건의 ITB에 담당자가 지정되고 코드가 재발급되었습니다.`, + updatedCount + }; + } catch (error) { + console.error("담당자 지정 오류:", error); + return { + success: false, + message: error instanceof Error ? error.message : "담당자 지정 중 오류가 발생했습니다." + }; + } }
\ No newline at end of file |
