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.ts150
1 files changed, 147 insertions, 3 deletions
diff --git a/lib/basic-contract/agreement-comments/actions.ts b/lib/basic-contract/agreement-comments/actions.ts
index 32e9ce4c..bfcc68cf 100644
--- a/lib/basic-contract/agreement-comments/actions.ts
+++ b/lib/basic-contract/agreement-comments/actions.ts
@@ -3,7 +3,7 @@
import { revalidateTag } from "next/cache";
import db from "@/db/db";
import { eq, and, desc, inArray, sql, isNotNull, ne } from "drizzle-orm";
-import { agreementComments, basicContract, vendors, users } from "@/db/schema";
+import { agreementComments, basicContract, basicContractTemplates, vendors, users } from "@/db/schema";
import { saveFile, deleteFile } from "@/lib/file-stroage";
import { sendEmail } from "@/lib/mail/sendEmail";
import { getServerSession } from "next-auth/next";
@@ -28,6 +28,8 @@ export interface AgreementCommentData {
authorName: string | null;
comment: string;
attachments: AgreementCommentAttachment[];
+ isSubmitted: boolean;
+ submittedAt: Date | null;
createdAt: Date;
updatedAt: Date;
}
@@ -98,6 +100,8 @@ export async function getAgreementComments(
...comment,
authorType: comment.authorType as AgreementCommentAuthorType,
attachments,
+ isSubmitted: comment.isSubmitted || false,
+ submittedAt: comment.submittedAt || null,
} as AgreementCommentData;
});
@@ -163,7 +167,6 @@ export async function addAgreementComment(data: {
}
// 템플릿 이름 조회
- const { basicContractTemplates } = await import("@/db/schema");
let templateName: string | null = null;
if (contract.templateId) {
const [template] = await db
@@ -213,6 +216,8 @@ export async function addAgreementComment(data: {
authorName: data.authorName || user.name,
comment: data.comment,
attachments: uploadedAttachments.length > 0 ? JSON.stringify(uploadedAttachments) : JSON.stringify([]),
+ isSubmitted: data.shouldSendEmail || false, // 제출 여부 설정
+ submittedAt: data.shouldSendEmail ? new Date() : null, // 제출 시 제출일시 설정
} as any)
.returning();
@@ -248,6 +253,8 @@ export async function addAgreementComment(data: {
...newComment,
authorType: newComment.authorType as AgreementCommentAuthorType,
attachments: uploadedAttachments,
+ isSubmitted: newComment.isSubmitted || false,
+ submittedAt: newComment.submittedAt || null,
} as AgreementCommentData,
};
} catch (error) {
@@ -336,6 +343,144 @@ export async function deleteAgreementComment(
}
/**
+ * 코멘트 제출 (이메일 발송)
+ */
+export async function submitAgreementComment(
+ commentId: number
+): Promise<{ success: boolean; error?: string }> {
+ try {
+ const session = await getServerSession(authOptions);
+ if (!session?.user) {
+ return { success: false, error: "인증이 필요합니다." };
+ }
+
+ // 코멘트 조회
+ const [comment] = await db
+ .select()
+ .from(agreementComments)
+ .where(eq(agreementComments.id, commentId));
+
+ if (!comment) {
+ return { success: false, error: "코멘트를 찾을 수 없습니다." };
+ }
+
+ // 이미 제출된 코멘트인지 확인
+ if (comment.isSubmitted) {
+ return { success: false, error: "이미 제출된 코멘트입니다." };
+ }
+
+ // 권한 확인 (작성자만 제출 가능)
+ const user = session.user as any;
+ const isVendor = !!user.companyId;
+ const canSubmit =
+ (isVendor && comment.authorVendorId === user.companyId) ||
+ (!isVendor && comment.authorUserId === parseInt(user.id));
+
+ if (!canSubmit) {
+ return { success: false, error: "제출 권한이 없습니다." };
+ }
+
+ // 기본계약서 정보 조회 (이메일 발송을 위해)
+ const [contract] = await db
+ .select()
+ .from(basicContract)
+ .where(eq(basicContract.id, comment.basicContractId))
+ .limit(1);
+
+ if (!contract) {
+ return { success: false, error: "계약서를 찾을 수 없습니다." };
+ }
+
+ // 벤더 정보 조회 (이메일 발송용)
+ 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);
+ }
+
+ // 템플릿 이름 조회
+ 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;
+ }
+
+ // 첨부파일 정보 파싱
+ 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);
+ }
+ }
+
+ // 코멘트 제출 상태 업데이트
+ await db
+ .update(agreementComments)
+ .set({
+ isSubmitted: true,
+ submittedAt: new Date(),
+ updatedAt: new Date(),
+ } as any)
+ .where(eq(agreementComments.id, commentId));
+
+ // 이메일 알림 발송
+ try {
+ await sendCommentNotificationEmail({
+ comment: {
+ ...comment,
+ isSubmitted: true,
+ submittedAt: new Date(),
+ },
+ contract,
+ vendor,
+ requester,
+ templateName,
+ authorType: comment.authorType as AgreementCommentAuthorType,
+ authorName: comment.authorName || user.name,
+ attachmentCount: attachments.length,
+ });
+ } catch (emailError) {
+ console.error("이메일 발송 실패:", emailError);
+ // 이메일 실패는 제출 성공에 영향을 주지 않음
+ }
+
+ // 캐시 무효화: 코멘트 목록 + 기본계약서 목록
+ revalidateTag(`agreement-comments-${comment.basicContractId}`);
+ revalidateTag(`basic-contracts`); // 기본계약서 목록 새로고침
+
+ return { success: true };
+ } catch (error) {
+ console.error("코멘트 제출 실패:", error);
+ return {
+ success: false,
+ error: "코멘트 제출 중 오류가 발생했습니다.",
+ };
+ }
+}
+
+/**
* 첨부파일 업로드
*/
export async function uploadCommentAttachment(
@@ -677,7 +822,6 @@ export async function completeNegotiation(
}
// 템플릿 이름 조회
- const { basicContractTemplates } = await import("@/db/schema");
let templateName: string | null = null;
if (contract.templateId) {
const [template] = await db