summaryrefslogtreecommitdiff
path: root/lib/basic-contract/agreement-comments/actions.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/basic-contract/agreement-comments/actions.ts')
-rw-r--r--lib/basic-contract/agreement-comments/actions.ts242
1 files changed, 201 insertions, 41 deletions
diff --git a/lib/basic-contract/agreement-comments/actions.ts b/lib/basic-contract/agreement-comments/actions.ts
index 13db2fc6..c4ded36e 100644
--- a/lib/basic-contract/agreement-comments/actions.ts
+++ b/lib/basic-contract/agreement-comments/actions.ts
@@ -56,16 +56,17 @@ export async function getAgreementComments(
if (comment.attachments) {
try {
+ const attachmentData = comment.attachments;
// 문자열인 경우 파싱 시도
- if (typeof comment.attachments === 'string') {
- const trimmed = comment.attachments.trim();
+ if (typeof attachmentData === 'string') {
+ const trimmed = String(attachmentData).trim();
if (trimmed && trimmed !== '') {
attachments = JSON.parse(trimmed);
}
}
// 이미 배열인 경우 그대로 사용
- else if (Array.isArray(comment.attachments)) {
- attachments = comment.attachments;
+ else if (Array.isArray(attachmentData)) {
+ attachments = attachmentData as AgreementCommentAttachment[];
}
} catch (parseError) {
console.warn(`⚠️ [getAgreementComments] 코멘트 ${comment.id}의 attachments 파싱 실패:`, parseError);
@@ -90,12 +91,14 @@ export async function getAgreementComments(
}
/**
- * 코멘트 추가
+ * 코멘트 추가 (파일 첨부 포함)
*/
export async function addAgreementComment(data: {
basicContractId: number;
comment: string;
authorName?: string;
+ files?: File[];
+ shouldSendEmail?: boolean; // 이메일 발송 여부 (제출할 때만 true)
}): Promise<{ success: boolean; data?: AgreementCommentData; error?: string }> {
try {
const session = await getServerSession(authOptions);
@@ -152,6 +155,34 @@ export async function addAgreementComment(data: {
templateName = template?.templateName || null;
}
+ // 파일 업로드 처리 (있을 경우)
+ const uploadedAttachments: AgreementCommentAttachment[] = [];
+ if (data.files && data.files.length > 0) {
+ for (const file of data.files) {
+ try {
+ const saveResult = await saveFile({
+ file,
+ directory: "agreement-comments",
+ originalName: file.name,
+ userId: user.id?.toString(),
+ });
+
+ if (saveResult.success && saveResult.publicPath) {
+ uploadedAttachments.push({
+ id: crypto.randomUUID(),
+ fileName: file.name,
+ filePath: saveResult.publicPath,
+ fileSize: file.size,
+ uploadedAt: new Date(),
+ });
+ }
+ } catch (fileError) {
+ console.error(`파일 업로드 실패 (${file.name}):`, fileError);
+ // 개별 파일 실패는 무시하고 계속 진행
+ }
+ }
+ }
+
// 코멘트 저장
const [newComment] = await db
.insert(agreementComments)
@@ -162,24 +193,27 @@ export async function addAgreementComment(data: {
authorVendorId: isVendor ? user.companyId : null,
authorName: data.authorName || user.name,
comment: data.comment,
- attachments: JSON.stringify([]),
- })
+ attachments: uploadedAttachments.length > 0 ? JSON.stringify(uploadedAttachments) : JSON.stringify([]),
+ } as any)
.returning();
- // 이메일 알림 발송
- try {
- await sendCommentNotificationEmail({
- comment: newComment,
- contract,
- vendor,
- requester,
- templateName,
- authorType,
- authorName: data.authorName || user.name,
- });
- } catch (emailError) {
- console.error("이메일 발송 실패:", emailError);
- // 이메일 실패는 코멘트 저장 성공에 영향을 주지 않음
+ // 이메일 알림 발송 (shouldSendEmail이 true일 때만)
+ if (data.shouldSendEmail) {
+ try {
+ await sendCommentNotificationEmail({
+ comment: newComment,
+ contract,
+ vendor,
+ requester,
+ templateName,
+ authorType,
+ authorName: data.authorName || user.name,
+ attachmentCount: uploadedAttachments.length,
+ });
+ } catch (emailError) {
+ console.error("이메일 발송 실패:", emailError);
+ // 이메일 실패는 코멘트 저장 성공에 영향을 주지 않음
+ }
}
// 계약서 상태 업데이트 (협의중으로 변경)
@@ -194,7 +228,7 @@ export async function addAgreementComment(data: {
data: {
...newComment,
authorType: newComment.authorType as AgreementCommentAuthorType,
- attachments: [],
+ attachments: uploadedAttachments,
} as AgreementCommentData,
};
} catch (error) {
@@ -246,20 +280,25 @@ export async function deleteAgreementComment(
isDeleted: true,
deletedAt: new Date(),
updatedAt: new Date(),
- })
+ } as any)
.where(eq(agreementComments.id, commentId));
// 첨부파일이 있으면 파일 시스템에서도 삭제
if (comment.attachments) {
- const attachments: AgreementCommentAttachment[] = JSON.parse(
- comment.attachments
- );
- for (const attachment of attachments) {
- try {
- await deleteFile(attachment.filePath);
- } catch (fileError) {
- console.error("파일 삭제 실패:", fileError);
+ try {
+ const attachmentsStr = typeof comment.attachments === 'string'
+ ? comment.attachments
+ : JSON.stringify(comment.attachments);
+ const attachments: AgreementCommentAttachment[] = JSON.parse(attachmentsStr);
+ for (const attachment of attachments) {
+ try {
+ await deleteFile(attachment.filePath);
+ } catch (fileError) {
+ console.error("파일 삭제 실패:", fileError);
+ }
}
+ } catch (parseError) {
+ console.error("첨부파일 파싱 실패:", parseError);
}
}
@@ -322,9 +361,17 @@ export async function uploadCommentAttachment(
};
// 기존 첨부파일에 추가
- const existingAttachments: AgreementCommentAttachment[] = comment.attachments
- ? JSON.parse(comment.attachments)
- : [];
+ let existingAttachments: AgreementCommentAttachment[] = [];
+ if (comment.attachments) {
+ try {
+ const attachmentsStr = typeof comment.attachments === 'string'
+ ? comment.attachments
+ : JSON.stringify(comment.attachments);
+ existingAttachments = JSON.parse(attachmentsStr);
+ } catch (parseError) {
+ console.error("기존 첨부파일 파싱 실패:", parseError);
+ }
+ }
existingAttachments.push(newAttachment);
// DB 업데이트
@@ -333,7 +380,7 @@ export async function uploadCommentAttachment(
.set({
attachments: JSON.stringify(existingAttachments),
updatedAt: new Date(),
- })
+ } as any)
.where(eq(agreementComments.id, commentId));
revalidateTag(`agreement-comments-${comment.basicContractId}`);
@@ -372,9 +419,19 @@ export async function deleteCommentAttachment(
}
// 첨부파일 목록에서 제거
- const attachments: AgreementCommentAttachment[] = comment.attachments
- ? JSON.parse(comment.attachments)
- : [];
+ let attachments: AgreementCommentAttachment[] = [];
+ if (comment.attachments) {
+ try {
+ const attachmentsStr = typeof comment.attachments === 'string'
+ ? comment.attachments
+ : JSON.stringify(comment.attachments);
+ attachments = JSON.parse(attachmentsStr);
+ } catch (parseError) {
+ console.error("첨부파일 파싱 실패:", parseError);
+ return { success: false, error: "첨부파일 정보를 읽을 수 없습니다." };
+ }
+ }
+
const targetAttachment = attachments.find((a) => a.id === attachmentId);
if (!targetAttachment) {
@@ -389,7 +446,7 @@ export async function deleteCommentAttachment(
.set({
attachments: JSON.stringify(updatedAttachments),
updatedAt: new Date(),
- })
+ } as any)
.where(eq(agreementComments.id, commentId));
// 파일 시스템에서 삭제
@@ -449,8 +506,9 @@ async function sendCommentNotificationEmail(params: {
templateName: string | null;
authorType: AgreementCommentAuthorType;
authorName: string;
+ attachmentCount?: number;
}) {
- const { comment, contract, vendor, requester, templateName, authorType, authorName } = params;
+ const { comment, contract, vendor, requester, templateName, authorType, authorName, attachmentCount = 0 } = params;
// 수신자 결정
let recipientEmail: string | undefined;
@@ -478,7 +536,7 @@ async function sendCommentNotificationEmail(params: {
// 이메일 발송
await sendEmail({
to: recipientEmail,
- subject: `[eVCP] GTC 기본계약서 협의 코멘트 - ${templateName || '기본계약서'}`,
+ subject: `[eVCP] GTC 기본계약서 협의 코멘트 제출 - ${templateName || '기본계약서'}`,
template: "agreement-comment-notification",
context: {
language: "ko",
@@ -488,6 +546,7 @@ async function sendCommentNotificationEmail(params: {
comment: comment.comment,
templateName: templateName || '기본계약서',
vendorName: vendor?.vendorName || '',
+ attachmentCount,
contractUrl: `${process.env.NEXT_PUBLIC_APP_URL}/evcp/basic-contract/${contract.id}`,
systemUrl: process.env.NEXT_PUBLIC_APP_URL || 'https://evcp.com',
currentYear: new Date().getFullYear(),
@@ -496,6 +555,107 @@ async function sendCommentNotificationEmail(params: {
}
/**
+ * 협의 완료 처리
+ */
+export async function completeNegotiation(
+ basicContractId: number
+): Promise<{ success: boolean; error?: string }> {
+ try {
+ const session = await getServerSession(authOptions);
+ if (!session?.user) {
+ return { success: false, error: "인증이 필요합니다." };
+ }
+
+ // 기본계약서 정보 조회
+ const [contract] = await db
+ .select()
+ .from(basicContract)
+ .where(eq(basicContract.id, basicContractId))
+ .limit(1);
+
+ if (!contract) {
+ return { success: false, error: "계약서를 찾을 수 없습니다." };
+ }
+
+ // 협의 완료 상태로 업데이트
+ await db
+ .update(basicContract)
+ .set({
+ negotiationCompletedAt: new Date(),
+ updatedAt: new Date(),
+ } as any)
+ .where(eq(basicContract.id, basicContractId));
+
+ // 벤더 정보 조회 (이메일 발송용)
+ let vendor: any = null;
+ if (contract.vendorId) {
+ [vendor] = await db
+ .select()
+ .from(vendors)
+ .where(eq(vendors.id, contract.vendorId))
+ .limit(1);
+ }
+
+ // 요청자 정보 조회 (이메일 발송용)
+ let requester: any = null;
+ if (contract.requestedBy) {
+ [requester] = await db
+ .select()
+ .from(users)
+ .where(eq(users.id, contract.requestedBy))
+ .limit(1);
+ }
+
+ // 템플릿 이름 조회
+ const { basicContractTemplates } = await import("@/db/schema");
+ let templateName: string | null = null;
+ if (contract.templateId) {
+ const [template] = await db
+ .select()
+ .from(basicContractTemplates)
+ .where(eq(basicContractTemplates.id, contract.templateId))
+ .limit(1);
+ templateName = template?.templateName || null;
+ }
+
+ // 이메일 알림 발송
+ try {
+ if (requester) {
+ await sendEmail({
+ to: requester.email || '',
+ subject: `[eVCP] GTC 기본계약서 협의 완료 - ${templateName || '기본계약서'}`,
+ template: "negotiation-complete-notification",
+ context: {
+ language: "ko",
+ recipientName: requester.name || "담당자",
+ templateName: templateName || '기본계약서',
+ vendorName: vendor?.vendorName || '',
+ contractUrl: `${process.env.NEXT_PUBLIC_APP_URL}/evcp/basic-contract/${contract.id}`,
+ systemUrl: process.env.NEXT_PUBLIC_APP_URL || 'https://evcp.com',
+ currentYear: new Date().getFullYear(),
+ },
+ });
+ }
+ } catch (emailError) {
+ console.error("이메일 발송 실패:", emailError);
+ // 이메일 실패는 협의 완료 처리 성공에 영향을 주지 않음
+ }
+
+ // 캐시 무효화
+ revalidateTag(`agreement-comments-${basicContractId}`);
+ revalidateTag(`basic-contracts`);
+
+ return { success: true };
+ } catch (error) {
+ console.error("협의 완료 처리 실패:", error);
+ return {
+ success: false,
+ error: "협의 완료 처리 중 오류가 발생했습니다.",
+ };
+ }
+}
+
+/**
* 계약서 협의 상태 업데이트
* 실제로 DB 상태를 변경하지 않고, gtcData 조회 시 agreementComments 존재 여부로 판단
*/