summaryrefslogtreecommitdiff
path: root/lib/techsales-rfq/service.ts
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-11-18 10:33:51 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-11-18 10:33:51 +0000
commitbe5d5ab488ae875e7c56306403aba923e1784021 (patch)
treed43be7bf1204d6ba3862da24f7e2c2049d6c62fd /lib/techsales-rfq/service.ts
parent41ad2455ac47d8e2da331d7240ded1354df9a784 (diff)
(최겸) 기술영업 첨부파일 결재 등록 및 요구사항 개발
Diffstat (limited to 'lib/techsales-rfq/service.ts')
-rw-r--r--lib/techsales-rfq/service.ts145
1 files changed, 124 insertions, 21 deletions
diff --git a/lib/techsales-rfq/service.ts b/lib/techsales-rfq/service.ts
index 9a198ee5..dc5950e0 100644
--- a/lib/techsales-rfq/service.ts
+++ b/lib/techsales-rfq/service.ts
@@ -36,8 +36,9 @@ import { sendEmail } from "../mail/sendEmail";
import { formatDate } from "../utils";
import { itemShipbuilding, itemOffshoreTop, itemOffshoreHull } from "@/db/schema/items";
import { techVendors, techVendorPossibleItems, techVendorContacts } from "@/db/schema/techVendors";
-import { deleteFile, saveDRMFile, saveFile } from "@/lib/file-stroage";
-import { decryptWithServerAction } from "@/components/drm/drmUtils";
+import { deleteFile, saveFile } from "@/lib/file-stroage";
+import { decryptWithServerAction, isDRMFile } from "@/components/drm/drmUtils";
+import { TECH_SALES_RFQ_STATUSES } from "@/db/schema/techSales";
// RFQ 아이템 정보 타입
interface RfqItemInfo {
itemCode: string;
@@ -558,15 +559,25 @@ export async function sendTechSalesRfqToVendors(input: {
};
}
- // 발송 가능한 상태인지 확인
- if (rfq.status !== "RFQ Vendor Assignned" && rfq.status !== "RFQ Sent") {
+ // 발송 가능한 상태인지 확인 (결재 진행중 상태는 제외)
+ if (rfq.status !== TECH_SALES_RFQ_STATUSES.RFQ_VENDOR_ASSIGNED &&
+ rfq.status !== TECH_SALES_RFQ_STATUSES.RFQ_SENT &&
+ rfq.status !== TECH_SALES_RFQ_STATUSES.APPROVAL_IN_PROGRESS) {
return {
success: false,
message: "벤더가 할당된 RFQ 또는 이미 전송된 RFQ만 다시 전송할 수 있습니다",
};
}
+
+ // 결재 진행중 상태에서는 결재 승인 후처리 핸들러에서만 발송 가능
+ if (rfq.status === TECH_SALES_RFQ_STATUSES.APPROVAL_IN_PROGRESS) {
+ return {
+ success: false,
+ message: "결재 진행 중인 RFQ는 결재 승인 후 자동으로 발송됩니다",
+ };
+ }
- const isResend = rfq.status === "RFQ Sent";
+ const isResend = rfq.status === TECH_SALES_RFQ_STATUSES.RFQ_SENT;
// 현재 사용자 정보 조회
const sender = await db.query.users.findFirst({
@@ -615,11 +626,39 @@ export async function sendTechSalesRfqToVendors(input: {
};
}
+ // RFQ 첨부파일 중 DRM 파일 확인
+ const attachments = await db.query.techSalesAttachments.findMany({
+ where: eq(techSalesAttachments.techSalesRfqId, input.rfqId),
+ columns: {
+ id: true,
+ drmEncrypted: true,
+ fileName: true,
+ fileSize: true,
+ originalFileName: true,
+ }
+ });
+
+ const drmAttachments = attachments.filter(att => att.drmEncrypted === true);
+
+ // DRM 파일이 있으면 결재 프로세스 필요 (이 함수는 직접 발송하지 않고 결재 필요 신호 반환)
+ if (drmAttachments.length > 0) {
+ return {
+ success: false,
+ requiresApproval: true,
+ message: "DRM 파일이 포함되어 있어 결재가 필요합니다",
+ drmAttachmentIds: drmAttachments.map(att => att.id),
+ drmAttachments: drmAttachments.map(att => ({
+ fileName: att.originalFileName || att.fileName,
+ fileSize: att.fileSize,
+ })),
+ };
+ }
+
// 트랜잭션 시작
await db.transaction(async (tx) => {
// 1. RFQ 상태 업데이트 (최초 발송인 경우 rfqSendDate 설정)
const updateData: Partial<typeof techSalesRfqs.$inferInsert> = {
- status: "RFQ Sent",
+ status: TECH_SALES_RFQ_STATUSES.RFQ_SENT,
sentBy: Number(session.user.id),
updatedBy: Number(session.user.id),
updatedAt: new Date(),
@@ -1544,13 +1583,15 @@ export async function createTechSalesRfqAttachments(params: {
await db.transaction(async (tx) => {
for (const file of files) {
-
+ // DRM 파일 검출
+ const isDrmFile = await isDRMFile(file);
- const saveResult = await saveDRMFile(
+ // saveFile로 변경 (DRM 복호화하지 않고 원본 저장)
+ const saveResult = await saveFile({
file,
- decryptWithServerAction,
- `techsales-rfq/${techSalesRfqId}`
- );
+ directory: `techsales-rfq/${techSalesRfqId}`,
+ userId: String(createdBy),
+ });
if (!saveResult.success) {
throw new Error(saveResult.error || "파일 저장에 실패했습니다.");
@@ -1566,6 +1607,7 @@ export async function createTechSalesRfqAttachments(params: {
fileSize: file.size,
fileType: file.type || undefined,
description: description || undefined,
+ drmEncrypted: isDrmFile, // DRM 파일 여부 저장
createdBy,
}).returning();
@@ -1653,6 +1695,39 @@ export async function getTechSalesRfqAttachmentsByType(
}
/**
+ * 벤더용 RFQ 첨부파일 조회 (DRM 해제된 파일만 반환)
+ */
+export async function getRfqAttachmentsForVendor(techSalesRfqId: number) {
+ unstable_noStore();
+ try {
+ // DRM 해제된 파일만 조회 (drmEncrypted = false)
+ const attachments = await db.query.techSalesAttachments.findMany({
+ where: and(
+ eq(techSalesAttachments.techSalesRfqId, techSalesRfqId),
+ eq(techSalesAttachments.drmEncrypted, false)
+ ),
+ orderBy: [desc(techSalesAttachments.createdAt)],
+ columns: {
+ id: true,
+ fileName: true,
+ originalFileName: true,
+ filePath: true,
+ fileSize: true,
+ fileType: true,
+ attachmentType: true,
+ description: true,
+ createdAt: true,
+ }
+ });
+
+ return { data: attachments, error: null };
+ } catch (err) {
+ console.error("벤더용 기술영업 RFQ 첨부파일 조회 오류:", err);
+ return { data: [], error: getErrorMessage(err) };
+ }
+}
+
+/**
* 기술영업 RFQ 첨부파일 삭제
*/
export async function deleteTechSalesRfqAttachment(attachmentId: number) {
@@ -1834,17 +1909,23 @@ export async function processTechSalesRfqAttachments(params: {
}
// 2. 새 파일 업로드 처리
- if (newFiles.length > 0) {
- for (const { file, attachmentType, description } of newFiles) {
- const saveResult = await saveDRMFile(
- file,
- decryptWithServerAction,
- `techsales-rfq/${techSalesRfqId}`
- );
+ if (newFiles.length > 0) {
+ const drmFiles: Array<{ file: File; attachmentType: string; description?: string }> = [];
+
+ for (const { file, attachmentType, description } of newFiles) {
+ // DRM 파일 검출
+ const isDrmFile = await isDRMFile(file);
+
+ // saveFile로 변경 (DRM 복호화하지 않고 원본 저장)
+ const saveResult = await saveFile({
+ file,
+ directory: `techsales-rfq/${techSalesRfqId}`,
+ userId: String(createdBy),
+ });
- if (!saveResult.success) {
- throw new Error(saveResult.error || "파일 저장에 실패했습니다.");
- }
+ if (!saveResult.success) {
+ throw new Error(saveResult.error || "파일 저장에 실패했습니다.");
+ }
// DB에 첨부파일 레코드 생성
const [newAttachment] = await tx.insert(techSalesAttachments).values({
@@ -1856,10 +1937,22 @@ export async function processTechSalesRfqAttachments(params: {
fileSize: file.size,
fileType: file.type || undefined,
description: description || undefined,
+ drmEncrypted: isDrmFile, // DRM 파일 여부 저장
createdBy,
}).returning();
results.uploaded.push(newAttachment);
+
+ // DRM 파일인 경우 목록에 추가
+ if (isDrmFile) {
+ drmFiles.push({ file, attachmentType, description });
+ }
+ }
+
+ // RFQ 상태가 "RFQ Sent"이고 DRM 파일이 추가된 경우 재발송 결재 트리거
+ if (rfq.status === TECH_SALES_RFQ_STATUSES.RFQ_SENT && drmFiles.length > 0) {
+ // 트랜잭션 외부에서 처리하기 위해 에러로 전달
+ throw new Error("DRM_FILE_ADDED_TO_SENT_RFQ");
}
}
});
@@ -1878,6 +1971,16 @@ export async function processTechSalesRfqAttachments(params: {
};
} catch (err) {
console.error("기술영업 RFQ 첨부파일 일괄 처리 오류:", err);
+
+ // DRM 파일 추가로 인한 재발송 결재 필요 에러
+ if (err instanceof Error && err.message === "DRM_FILE_ADDED_TO_SENT_RFQ") {
+ return {
+ data: null,
+ error: "DRM_FILE_ADDED_TO_SENT_RFQ",
+ rfqId: techSalesRfqId,
+ };
+ }
+
return { data: null, error: getErrorMessage(err) };
}
}