summaryrefslogtreecommitdiff
path: root/lib/compliance
diff options
context:
space:
mode:
author0-Zz-ang <s1998319@gmail.com>2025-11-20 17:39:10 +0900
committer0-Zz-ang <s1998319@gmail.com>2025-11-20 17:39:10 +0900
commitf95fbb0719c5754360472d066b0bbed4bda6a40a (patch)
treed58b69d97c1dc300ccba8174d27c3a2023d34af6 /lib/compliance
parent088431a6572d87f7ee287252a4357e6c332d04f7 (diff)
(박서영)준법설문조사 redFlag관련사항수정
Diffstat (limited to 'lib/compliance')
-rw-r--r--lib/compliance/compliance-response-detail.tsx69
-rw-r--r--lib/compliance/red-flag-notifier.ts55
2 files changed, 86 insertions, 38 deletions
diff --git a/lib/compliance/compliance-response-detail.tsx b/lib/compliance/compliance-response-detail.tsx
index 709f3ede..4c2062d0 100644
--- a/lib/compliance/compliance-response-detail.tsx
+++ b/lib/compliance/compliance-response-detail.tsx
@@ -112,6 +112,17 @@ export function ComplianceResponseDetail({ templateId, responseId, promises }: C
return question ? question.hasFileUpload : false
}
+ const isQuestionRedFlag = (questionId: number) => {
+ const question = questions.find(q => q.id === questionId)
+ return question ? question.isRedFlag : false
+ }
+
+ const isRedFlagTriggered = (answer: any) => {
+ const isRedFlag = isQuestionRedFlag(answer.questionId)
+ const answerValue = (answer.answerValue ?? '').toString().trim().toUpperCase()
+ return isRedFlag && answerValue === 'YES'
+ }
+
// 파일 다운로드 핸들러
const handleFileDownload = async (file: any) => {
try {
@@ -245,22 +256,51 @@ export function ComplianceResponseDetail({ templateId, responseId, promises }: C
</div>
) : (
<div className="space-y-4">
- {answers.map((answer) => (
- <div key={answer.id} className="border rounded-lg p-4 space-y-3">
- <div className="flex items-center gap-2 pb-3 border-b">
- <Badge variant="outline">
- {getQuestionNumber(answer.questionId)}
- </Badge>
- <span className="font-medium">
- {getQuestionText(answer.questionId)}
- </span>
- </div>
- <div className="space-y-3">
+ {answers.map((answer) => {
+ const redFlagTriggered = isRedFlagTriggered(answer)
+ return (
+ <div
+ key={answer.id}
+ className={`border-2 rounded-lg p-4 space-y-3 ${
+ redFlagTriggered
+ ? 'border-red-500 bg-red-50/50'
+ : 'border-gray-200'
+ }`}
+ >
+ <div className="flex items-center gap-2 pb-3 border-b">
+ <Badge
+ variant={redFlagTriggered ? "destructive" : "outline"}
+ >
+ {getQuestionNumber(answer.questionId)}
+ </Badge>
+ <span className="font-medium">
+ {getQuestionText(answer.questionId)}
+ </span>
+ {/* {isQuestionRedFlag(answer.questionId) && (
+ <Badge variant="destructive" className="ml-auto">
+ RED FLAG
+ </Badge>
+ )} */}
+ </div>
+ <div className="space-y-3">
{/* 답변 값 */}
{answer.answerValue && (
<div>
<label className="text-sm font-medium text-muted-foreground">답변</label>
- <p className="mt-1 p-2 bg-muted rounded">{answer.answerValue}</p>
+ <div className="mt-1 flex items-center gap-2">
+ <p className={`flex-1 p-2 rounded ${
+ redFlagTriggered
+ ? 'bg-red-100 text-red-900 font-semibold'
+ : 'bg-muted'
+ }`}>
+ {answer.answerValue}
+ </p>
+ {/* {redFlagTriggered && (
+ <Badge variant="destructive" className="shrink-0">
+ Red Flag 발생
+ </Badge>
+ )} */}
+ </div>
</div>
)}
@@ -331,9 +371,10 @@ export function ComplianceResponseDetail({ templateId, responseId, promises }: C
'-'
}
</div>
+ </div>
</div>
- </div>
- ))}
+ )
+ })}
</div>
)}
</CardContent>
diff --git a/lib/compliance/red-flag-notifier.ts b/lib/compliance/red-flag-notifier.ts
index ad55de54..27ca2a03 100644
--- a/lib/compliance/red-flag-notifier.ts
+++ b/lib/compliance/red-flag-notifier.ts
@@ -9,6 +9,7 @@ import {
} from '@/db/schema';
import { and, desc, eq } from 'drizzle-orm';
import { sendEmail } from '@/lib/mail/sendEmail';
+import { getComplianceResponseByBasicContractId } from '@/lib/compliance/services';
export type TriggeredRedFlagInfo = {
questionId: number;
@@ -65,8 +66,6 @@ export async function notifyComplianceRedFlagManagers(params: {
return;
}
- const recipientEmails = new Set<string>();
-
const managerRow = await db
.select({
purchasingManagerId: redFlagManagers.purchasingManagerId,
@@ -95,14 +94,6 @@ export async function notifyComplianceRedFlagManagers(params: {
fetchUserEmail(managerIds?.complianceManagerId),
]);
- if (purchasingEmail) {
- recipientEmails.add(purchasingEmail);
- }
-
- if (complianceEmail) {
- recipientEmails.add(complianceEmail);
- }
-
const contractOwner = await db
.select({
requestedBy: basicContract.requestedBy,
@@ -114,29 +105,45 @@ export async function notifyComplianceRedFlagManagers(params: {
.limit(1);
const ownerRecord = contractOwner[0];
- if (ownerRecord?.requestedByEmail) {
- recipientEmails.add(ownerRecord.requestedByEmail);
- }
+ const contractRequestorEmail = ownerRecord?.requestedByEmail;
- if (recipientEmails.size === 0) {
+ // 구매 담당자가 없으면 발송하지 않음
+ if (!purchasingEmail) {
return;
}
+ // 준법설문 응답 정보 조회하여 링크 생성
+ const complianceResponse = await getComplianceResponseByBasicContractId(params.contractId);
+ let responseLink: string | null = null;
+
+ if (complianceResponse && complianceResponse.templateId && complianceResponse.id) {
+ const baseUrl = process.env.NEXT_PUBLIC_APP_URL || process.env.NEXT_PUBLIC_BASE_URL || '';
+ responseLink = `${baseUrl}/ko/evcp/compliance/${complianceResponse.templateId}/responses/${complianceResponse.id}`;
+ }
+
const context = {
contractId: params.contractId,
templateId: params.templateId ?? null,
vendorName: params.vendorName ?? '협력업체',
triggeredCount: params.triggeredQuestions.length,
+ responseLink: responseLink,
};
- await Promise.all(
- Array.from(recipientEmails).map((email) =>
- sendEmail({
- to: email,
- subject: '[eVCP] 컴플라이언스 레드플래그 알림',
- template: 'compliance-red-flag-alert',
- context,
- }),
- ),
- );
+ // CC 이메일 배열 구성
+ const ccEmails: string[] = [];
+ if (complianceEmail) {
+ ccEmails.push(complianceEmail);
+ }
+ if (contractRequestorEmail) {
+ ccEmails.push(contractRequestorEmail);
+ }
+
+ // 구매 담당자에게 To로, 준법 담당자와 계약 요청자는 CC로 발송
+ await sendEmail({
+ to: purchasingEmail,
+ cc: ccEmails.length > 0 ? ccEmails : undefined,
+ subject: '[eVCP] 컴플라이언스 레드플래그 알림',
+ template: 'compliance-red-flag-alert',
+ context,
+ });
}