summaryrefslogtreecommitdiff
path: root/lib/compliance
diff options
context:
space:
mode:
Diffstat (limited to 'lib/compliance')
-rw-r--r--lib/compliance/red-flag-resolution.ts303
-rw-r--r--lib/compliance/services.ts2
2 files changed, 305 insertions, 0 deletions
diff --git a/lib/compliance/red-flag-resolution.ts b/lib/compliance/red-flag-resolution.ts
new file mode 100644
index 00000000..184630f6
--- /dev/null
+++ b/lib/compliance/red-flag-resolution.ts
@@ -0,0 +1,303 @@
+"use server"
+
+import db from "@/db/db"
+import { eq, and } from "drizzle-orm"
+import { complianceResponses, redFlagManagers } from "@/db/schema/compliance"
+import { users } from "@/db/schema"
+import { basicContract } from "@/db/schema/basicContractDocumnet"
+import { vendors } from "@/db/schema/vendors"
+import { getServerSession } from "next-auth"
+import { authOptions } from "@/app/api/auth/[...nextauth]/route"
+import {
+ submitApproval,
+ createSubmitApprovalRequest,
+ createApprovalLine,
+ type ApprovalLine
+} from "@/lib/knox-api/approval/approval"
+import { getTriggeredRedFlagQuestions } from "./red-flag-notifier"
+import { revalidatePath } from "next/cache"
+
+/**
+ * RED FLAG 해소요청 - 구매기획 담당자에게 합의 요청
+ */
+export async function requestRedFlagResolution(contractIds: number[]): Promise<{
+ success: boolean
+ message: string
+ failed: number[]
+}> {
+ try {
+ const session = await getServerSession(authOptions)
+ if (!session?.user) {
+ return {
+ success: false,
+ message: "인증이 필요합니다.",
+ failed: contractIds
+ }
+ }
+
+ const currentUser = session.user
+ const userId = currentUser.id
+ const epId = currentUser.epId || ""
+ const emailAddress = currentUser.email || ""
+
+ if (!epId || !emailAddress) {
+ return {
+ success: false,
+ message: "사용자 정보가 불완전합니다. epId와 email이 필요합니다.",
+ failed: contractIds
+ }
+ }
+
+ // 구매기획 담당자 조회
+ const managerRow = await db
+ .select({
+ purchasingManagerId: redFlagManagers.purchasingManagerId,
+ })
+ .from(redFlagManagers)
+ .orderBy(redFlagManagers.createdAt)
+ .limit(1)
+
+ const purchasingManagerId = managerRow[0]?.purchasingManagerId
+ if (!purchasingManagerId) {
+ return {
+ success: false,
+ message: "구매기획 담당자가 설정되지 않았습니다.",
+ failed: contractIds
+ }
+ }
+
+ // 구매기획 담당자 정보 조회
+ const purchasingManager = await db
+ .select({
+ id: users.id,
+ name: users.name,
+ email: users.email,
+ epId: users.epId,
+ })
+ .from(users)
+ .where(eq(users.id, purchasingManagerId))
+ .limit(1)
+
+ if (!purchasingManager[0] || !purchasingManager[0].epId || !purchasingManager[0].email) {
+ return {
+ success: false,
+ message: "구매기획 담당자 정보를 찾을 수 없습니다.",
+ failed: contractIds
+ }
+ }
+
+ const pm = purchasingManager[0]
+
+ // 각 계약서에 대해 RED FLAG 해소요청 처리
+ const failed: number[] = []
+
+ for (const contractId of contractIds) {
+ try {
+ // 계약서 정보 조회
+ const contractInfo = await db
+ .select({
+ id: basicContract.id,
+ vendorId: basicContract.vendorId,
+ templateId: basicContract.templateId,
+ vendorName: vendors.vendorName,
+ vendorCode: vendors.vendorCode,
+ })
+ .from(basicContract)
+ .leftJoin(vendors, eq(basicContract.vendorId, vendors.id))
+ .where(eq(basicContract.id, contractId))
+ .limit(1)
+
+ if (!contractInfo[0]) {
+ failed.push(contractId)
+ continue
+ }
+
+ const contract = contractInfo[0]
+
+ // RED FLAG 발생 여부 확인
+ const triggeredFlags = await getTriggeredRedFlagQuestions(contractId)
+ if (triggeredFlags.length === 0) {
+ // RED FLAG가 없는 경우는 스킵
+ continue
+ }
+
+ // 이미 해소요청이 진행 중인지 확인
+ const existingResponse = await db
+ .select()
+ .from(complianceResponses)
+ .where(eq(complianceResponses.basicContractId, contractId))
+ .limit(1)
+
+ if (existingResponse[0]?.redFlagResolutionApprovalId) {
+ // 이미 해소요청이 진행 중
+ continue
+ }
+
+ // 합의 요청 본문 생성
+ const triggeredQuestionsText = triggeredFlags
+ .map((flag, idx) => `${idx + 1}. ${flag.questionText}`)
+ .join("\n")
+
+ const contents = `
+RED FLAG 해소요청
+
+계약서 ID: ${contractId}
+업체명: ${contract.vendorName || "정보 없음"}
+업체코드: ${contract.vendorCode || "정보 없음"}
+
+발생한 RED FLAG 질문:
+${triggeredQuestionsText}
+
+위 RED FLAG에 대한 해소를 요청드립니다.
+합의해 주시면 RED FLAG가 해제됩니다.
+ `.trim()
+
+ const subject = `[RED FLAG 해소요청] ${contract.vendorName || "협력업체"} - 계약서 ID: ${contractId}`
+
+ // 결재 경로 생성
+ // 기안자: 현재 사용자
+ const drafterLine: ApprovalLine = await createApprovalLine(
+ { epId, emailAddress },
+ "0", // 기안
+ "1"
+ )
+
+ // 합의자: 구매기획 담당자
+ const approverLine: ApprovalLine = await createApprovalLine(
+ { epId: pm.epId, emailAddress: pm.email },
+ "2", // 합의
+ "2"
+ )
+
+ const approvalLines = [drafterLine, approverLine]
+
+ // 결재 상신 요청 생성
+ const approvalRequest = await createSubmitApprovalRequest(
+ contents,
+ subject,
+ approvalLines,
+ {
+ contentsType: "TEXT",
+ docSecuType: "PERSONAL",
+ notifyOption: "0",
+ urgYn: "N",
+ importantYn: "N",
+ }
+ )
+
+ // 결재 상신
+ const approvalResponse = await submitApproval(
+ approvalRequest,
+ {
+ userId,
+ epId,
+ emailAddress,
+ }
+ )
+
+ if (approvalResponse.result === "success") {
+ // compliance_responses 업데이트 (red_flag_resolution_approval_id 저장)
+ if (existingResponse[0]) {
+ await db
+ .update(complianceResponses)
+ .set({
+ redFlagResolutionApprovalId: approvalResponse.data.apInfId,
+ updatedAt: new Date(),
+ })
+ .where(eq(complianceResponses.id, existingResponse[0].id))
+ } else {
+ // compliance_response가 없는 경우 생성 (템플릿 ID는 계약서에서 가져와야 함)
+ // 이 경우는 실제로는 발생하지 않을 수 있지만, 안전을 위해 처리
+ console.warn(`Compliance response not found for contract ${contractId}`)
+ }
+ } else {
+ failed.push(contractId)
+ }
+ } catch (error) {
+ console.error(`Error processing contract ${contractId}:`, error)
+ failed.push(contractId)
+ }
+ }
+
+ revalidatePath("/evcp/basic-contract")
+
+ if (failed.length === 0) {
+ return {
+ success: true,
+ message: `${contractIds.length}건의 RED FLAG 해소요청이 완료되었습니다.`,
+ failed: []
+ }
+ } else if (failed.length < contractIds.length) {
+ return {
+ success: true,
+ message: `${contractIds.length - failed.length}건 성공, ${failed.length}건 실패`,
+ failed
+ }
+ } else {
+ return {
+ success: false,
+ message: "모든 RED FLAG 해소요청이 실패했습니다.",
+ failed
+ }
+ }
+ } catch (error) {
+ console.error("RED FLAG 해소요청 오류:", error)
+ return {
+ success: false,
+ message: `RED FLAG 해소요청 중 오류가 발생했습니다: ${error instanceof Error ? error.message : "알 수 없는 오류"}`,
+ failed: contractIds
+ }
+ }
+}
+
+/**
+ * RED FLAG 해소 처리 (합의 완료 시 호출)
+ */
+export async function resolveRedFlag(contractId: number, approvalId: string): Promise<{
+ success: boolean
+ message: string
+}> {
+ try {
+ // compliance_responses 조회
+ const response = await db
+ .select()
+ .from(complianceResponses)
+ .where(
+ and(
+ eq(complianceResponses.basicContractId, contractId),
+ eq(complianceResponses.redFlagResolutionApprovalId, approvalId)
+ )
+ )
+ .limit(1)
+
+ if (!response[0]) {
+ return {
+ success: false,
+ message: "해소요청 정보를 찾을 수 없습니다.",
+ }
+ }
+
+ // RED FLAG 해제 처리
+ await db
+ .update(complianceResponses)
+ .set({
+ redFlagResolvedAt: new Date(),
+ updatedAt: new Date(),
+ })
+ .where(eq(complianceResponses.id, response[0].id))
+
+ revalidatePath("/evcp/basic-contract")
+
+ return {
+ success: true,
+ message: "RED FLAG가 해제되었습니다.",
+ }
+ } catch (error) {
+ console.error("RED FLAG 해제 오류:", error)
+ return {
+ success: false,
+ message: `RED FLAG 해제 중 오류가 발생했습니다: ${error instanceof Error ? error.message : "알 수 없는 오류"}`,
+ }
+ }
+}
+
diff --git a/lib/compliance/services.ts b/lib/compliance/services.ts
index 6541bdd1..2856cb0a 100644
--- a/lib/compliance/services.ts
+++ b/lib/compliance/services.ts
@@ -1123,6 +1123,8 @@ export async function getComplianceResponseByBasicContractId(basicContractId: nu
basicContractId: complianceResponses.basicContractId,
status: complianceResponses.status,
completedAt: complianceResponses.completedAt,
+ redFlagResolvedAt: complianceResponses.redFlagResolvedAt,
+ redFlagResolutionApprovalId: complianceResponses.redFlagResolutionApprovalId,
createdAt: complianceResponses.createdAt,
updatedAt: complianceResponses.updatedAt,
})