summaryrefslogtreecommitdiff
path: root/lib/rfq-last/service.ts
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-09-15 14:41:01 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-09-15 14:41:01 +0000
commit4ee8b24cfadf47452807fa2af801385ed60ab47c (patch)
treee1d1fb029f0cf5519c517494bf9a545505c35700 /lib/rfq-last/service.ts
parent265859d691a01cdcaaf9154f93c38765bc34df06 (diff)
(대표님) 작업사항 - rfqLast, tbeLast, pdfTron, userAuth
Diffstat (limited to 'lib/rfq-last/service.ts')
-rw-r--r--lib/rfq-last/service.ts376
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