summaryrefslogtreecommitdiff
path: root/lib/compliance/approval-handlers.ts
blob: 11f95a3cd44d58964c11b5e8af8ba2ea4fa6f850 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
"use server"

import { resolveRedFlag, type ContractSummary } from "./red-flag-resolution"
import { revalidatePath } from "next/cache"
import { debugLog, debugError, debugSuccess } from "@/lib/debug-utils"
import { htmlListConverter, htmlTableConverter } from "@/lib/approval/template-utils"
import db from "@/db/db"
import { eq } from "drizzle-orm"
import { redFlagManagers } from "@/db/schema/compliance"
import { users } from "@/db/schema"

/**
 * RED FLAG 해소 결재 승인 핸들러
 * 
 * 결재 승인 후 자동으로 호출되어 RED FLAG를 해제합니다.
 * 
 * @param payload - 결재 상신 시 저장한 actionPayload
 */
export async function resolveRedFlagAfterApproval(payload: {
  contractIds: number[]
  requestedBy: number
  requestedAt: string
}) {
  debugLog("[RedFlagResolutionHandler] RED FLAG 해소 결재 승인 핸들러 시작", payload)

  try {
    if (!payload?.contractIds || payload.contractIds.length === 0) {
      debugError("[RedFlagResolutionHandler] 계약서 ID가 없습니다", payload)
      return {
        success: false,
        message: "처리할 계약서가 없습니다.",
      }
    }

    const uniqueContractIds = Array.from(new Set(payload.contractIds))
    debugLog("[RedFlagResolutionHandler] 처리할 계약서 수", { count: uniqueContractIds.length })

    // 각 계약서에 대해 RED FLAG 해소 처리
    // approvalId는 resolveRedFlag 내부에서 조회하므로 여기서는 전달하지 않음
    const results = await Promise.allSettled(
      uniqueContractIds.map(async (contractId) => {
        const result = await resolveRedFlag(contractId, {
          revalidate: false,
        })
        return { contractId, result }
      })
    )

    const successful = results.filter((r) => r.status === "fulfilled").length
    const failed = results.filter((r) => r.status === "rejected").length

    debugLog("[RedFlagResolutionHandler] 처리 결과", {
      total: uniqueContractIds.length,
      successful,
      failed,
    })

    if (failed > 0) {
      const errors = results
        .filter((r) => r.status === "rejected")
        .map((r) => (r as PromiseRejectedResult).reason)
      debugError("[RedFlagResolutionHandler] 일부 계약서 처리 실패", errors)
    }

    await revalidatePath("/evcp/basic-contract")
    await revalidatePath("/evcp/compliance")

    debugSuccess("[RedFlagResolutionHandler] RED FLAG 해소 완료", {
      successful,
      failed,
    })

    return {
      success: true,
      message: `${successful}개 계약서의 RED FLAG가 해제되었습니다.`,
      updated: successful,
    }
  } catch (error) {
    debugError("[RedFlagResolutionHandler] RED FLAG 해소 처리 중 오류 발생", error)
    throw error
  }
}

/**
 * 구매기획 담당자 EP ID 조회
 */
export async function getPurchasingManagerEpId(): Promise<string | null> {
  const [manager] = await db
    .select({
      purchasingManagerId: redFlagManagers.purchasingManagerId,
    })
    .from(redFlagManagers)
    .orderBy(redFlagManagers.createdAt)
    .limit(1)

  if (!manager?.purchasingManagerId) {
    return null
  }

  const [user] = await db
    .select({
      epId: users.epId,
    })
    .from(users)
    .where(eq(users.id, manager.purchasingManagerId))
    .limit(1)

  return user?.epId ?? null
}

/**
 * RED FLAG 해소요청 데이터를 결재 템플릿 변수로 매핑
 * 
 * @param contracts - RED FLAG가 있는 계약서 목록
 * @param meta - 요청자 정보
 * @returns 템플릿 변수 객체 (Record<string, string>)
 */
export async function mapRedFlagResolutionToTemplateVariables(
  contracts: ContractSummary[],
  meta: { requesterName: string; requestedAt: Date }
): Promise<Record<string, string>> {
  const summaryRows = contracts.map((contract) => ({
    contractId: contract.contractId,
    vendorName: contract.vendorName ?? "-",
    templateName: contract.templateName ?? "-",
    redFlagCount: contract.triggeredFlags.length,
  }))

  const summaryTable = await htmlTableConverter(summaryRows, [
    { key: "contractId", label: "계약 ID" },
    { key: "vendorName", label: "업체명" },
    { key: "templateName", label: "템플릿" },
    { key: "redFlagCount", label: "RED FLAG 수" },
  ])

  const detailSections = await Promise.all(
    contracts.map(async (contract) => {
      const questionList = contract.triggeredFlags.map((flag, index) => {
        const prefix = flag.questionNumber || `${index + 1}`
        return `${prefix}. ${flag.questionText}`
      })

      const listHtml = await htmlListConverter(questionList)
      return `
        <div style="margin-bottom: 24px;">
          <div style="font-weight:600;margin-bottom:8px;">
            계약 ID: ${contract.contractId} / ${contract.vendorName ?? "-"}
          </div>
          <div>${listHtml}</div>
        </div>
      `
    })
  )

  const detailHtml = detailSections.join("")
  const formattedDate = new Intl.DateTimeFormat("ko-KR", {
    dateStyle: "medium",
    timeStyle: "short",
  }).format(meta.requestedAt)

  return {
    요청자이름: meta.requesterName,
    요청일시: formattedDate,
    요청사유: "컴플라이언스 Red Flag 해소를 위해 구매기획 합의를 요청드립니다.",
    RedFlag요약테이블: summaryTable,
    RedFlag상세내역: detailHtml,
  }
}