summaryrefslogtreecommitdiff
path: root/lib/basic-contract/service.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/basic-contract/service.ts')
-rw-r--r--lib/basic-contract/service.ts267
1 files changed, 258 insertions, 9 deletions
diff --git a/lib/basic-contract/service.ts b/lib/basic-contract/service.ts
index 6f4e5d53..12278c54 100644
--- a/lib/basic-contract/service.ts
+++ b/lib/basic-contract/service.ts
@@ -2862,6 +2862,10 @@ export async function requestLegalReviewAction(
}
}
+// ⚠️ SSLVW(법무관리시스템) PRGS_STAT_DSC 문자열을 그대로 저장하는 함수입니다.
+// - 상태 텍스트 및 완료 여부는 외부 시스템에 의존하므로 신뢰도가 100%는 아니고,
+// - 여기에서 관리하는 값들은 UI 표시/참고용으로만 사용해야 합니다.
+// - 최종 승인 차단 등 핵심 비즈니스 로직에서는 SSLVW 쪽 완료 시간을 직접 신뢰하지 않습니다.
const persistLegalReviewStatus = async ({
contractId,
regNo,
@@ -2904,6 +2908,121 @@ const persistLegalReviewStatus = async ({
}
/**
+ * 준법문의 요청 서버 액션
+ */
+export async function requestComplianceInquiryAction(
+ contractIds: number[]
+): Promise<{ success: boolean; message: string }> {
+ const session = await getServerSession(authOptions)
+
+ if (!session?.user) {
+ return {
+ success: false,
+ message: "로그인이 필요합니다."
+ }
+ }
+
+ // 계약서 정보 조회
+ const contracts = await db
+ .select({
+ id: basicContractView.id,
+ complianceReviewRequestedAt: basicContractView.complianceReviewRequestedAt,
+ })
+ .from(basicContractView)
+ .where(inArray(basicContractView.id, contractIds))
+
+ if (contracts.length === 0) {
+ return {
+ success: false,
+ message: "선택된 계약서를 찾을 수 없습니다."
+ }
+ }
+
+ // 준법문의 요청 가능한 계약서 필터링 (이미 요청되지 않은 것만)
+ const eligibleContracts = contracts.filter(contract =>
+ !contract.complianceReviewRequestedAt
+ )
+
+ if (eligibleContracts.length === 0) {
+ return {
+ success: false,
+ message: "준법문의 요청 가능한 계약서가 없습니다."
+ }
+ }
+
+ const currentDate = new Date()
+
+ // 트랜잭션으로 처리
+ await db.transaction(async (tx) => {
+ for (const contract of eligibleContracts) {
+ await tx
+ .update(basicContract)
+ .set({
+ complianceReviewRequestedAt: currentDate,
+ updatedAt: currentDate,
+ })
+ .where(eq(basicContract.id, contract.id))
+ }
+ })
+
+ revalidateTag("basic-contracts")
+
+ return {
+ success: true,
+ message: `${eligibleContracts.length}건의 준법문의 요청이 완료되었습니다.`
+ }
+}
+
+/**
+ * 준법문의 상태 저장 (준법문의 전용 필드 사용)
+ */
+const persistComplianceReviewStatus = async ({
+ contractId,
+ regNo,
+ progressStatus,
+}: {
+ contractId: number
+ regNo: string
+ progressStatus: string
+}) => {
+ const now = new Date()
+
+ // 완료 상태 확인 (법무검토와 동일한 패턴)
+ // ⚠️ CPVW PRGS_STAT_DSC 문자열을 기반으로 한 best-effort 휴리스틱입니다.
+ // - 외부 시스템의 상태 텍스트에 의존하므로 신뢰도가 100%는 아니고,
+ // - 여기에서 설정하는 완료 시간(complianceReviewCompletedAt)은 UI 표시용으로만 사용해야 합니다.
+ // - 버튼 활성화, 서버 액션 차단, 필터 조건 등 핵심 비즈니스 로직에서는
+ // 이 값을 신뢰하지 않도록 합니다.
+ // 완료 상태 확인 (법무검토와 동일한 패턴)
+ const isCompleted = progressStatus && (
+ progressStatus.includes('완료') ||
+ progressStatus.includes('승인') ||
+ progressStatus.includes('종료')
+ )
+
+ await db.transaction(async (tx) => {
+ // 준법문의 상태 업데이트 (준법문의 전용 필드 사용)
+ const updateData: any = {
+ complianceReviewRegNo: regNo,
+ complianceReviewProgressStatus: progressStatus,
+ updatedAt: now,
+ }
+
+ // 완료 상태인 경우 완료일 설정
+ if (isCompleted) {
+ updateData.complianceReviewCompletedAt = now
+ }
+
+ await tx
+ .update(basicContract)
+ .set(updateData)
+ .where(eq(basicContract.id, contractId))
+ })
+
+ revalidateTag("basic-contracts")
+}
+
+/**
* SSLVW 데이터로부터 법무검토 상태 업데이트
* @param sslvwData 선택된 SSLVW 데이터 배열
* @param selectedContractIds 선택된 계약서 ID 배열
@@ -3033,6 +3152,137 @@ export async function updateLegalReviewStatusFromSSLVW(
}
}
+/**
+ * CPVW 데이터로부터 준법문의 상태 업데이트
+ * @param cpvwData 선택된 CPVW 데이터 배열
+ * @param selectedContractIds 선택된 계약서 ID 배열
+ * @returns 성공 여부 및 메시지
+ */
+export async function updateComplianceReviewStatusFromCPVW(
+ cpvwData: Array<{ REG_NO?: string; reg_no?: string; PRGS_STAT_DSC?: string; prgs_stat_dsc?: string; [key: string]: any }>,
+ selectedContractIds: number[]
+): Promise<{ success: boolean; message: string; updatedCount: number; errors: string[] }> {
+ try {
+ console.log(`[updateComplianceReviewStatusFromCPVW] CPVW 데이터로부터 준법문의 상태 업데이트 시작`)
+
+ if (!cpvwData || cpvwData.length === 0) {
+ return {
+ success: false,
+ message: 'CPVW 데이터가 없습니다.',
+ updatedCount: 0,
+ errors: []
+ }
+ }
+
+ if (!selectedContractIds || selectedContractIds.length === 0) {
+ return {
+ success: false,
+ message: '선택된 계약서가 없습니다.',
+ updatedCount: 0,
+ errors: []
+ }
+ }
+
+ if (selectedContractIds.length !== 1) {
+ return {
+ success: false,
+ message: '한 개의 계약서만 선택해 주세요.',
+ updatedCount: 0,
+ errors: []
+ }
+ }
+
+ if (cpvwData.length !== 1) {
+ return {
+ success: false,
+ message: '준법문의 시스템 데이터도 한 건만 선택해 주세요.',
+ updatedCount: 0,
+ errors: []
+ }
+ }
+
+ const contractId = selectedContractIds[0]
+ const cpvwItem = cpvwData[0]
+ const regNo = String(
+ cpvwItem.REG_NO ??
+ cpvwItem.reg_no ??
+ cpvwItem.RegNo ??
+ ''
+ ).trim()
+ const progressStatus = String(
+ cpvwItem.PRGS_STAT_DSC ??
+ cpvwItem.prgs_stat_dsc ??
+ cpvwItem.PrgsStatDsc ??
+ ''
+ ).trim()
+
+ if (!regNo) {
+ return {
+ success: false,
+ message: 'REG_NO 값을 찾을 수 없습니다.',
+ updatedCount: 0,
+ errors: []
+ }
+ }
+
+ if (!progressStatus) {
+ return {
+ success: false,
+ message: 'PRGS_STAT_DSC 값을 찾을 수 없습니다.',
+ updatedCount: 0,
+ errors: []
+ }
+ }
+
+ const contract = await db
+ .select({
+ id: basicContract.id,
+ complianceReviewRegNo: basicContract.complianceReviewRegNo,
+ })
+ .from(basicContract)
+ .where(eq(basicContract.id, contractId))
+ .limit(1)
+
+ if (!contract[0]) {
+ return {
+ success: false,
+ message: `계약서(${contractId})를 찾을 수 없습니다.`,
+ updatedCount: 0,
+ errors: []
+ }
+ }
+
+ if (contract[0].complianceReviewRegNo && contract[0].complianceReviewRegNo !== regNo) {
+ console.warn(`[updateComplianceReviewStatusFromCPVW] REG_NO가 변경됩니다: ${contract[0].complianceReviewRegNo} -> ${regNo}`)
+ }
+
+ // 준법문의 상태 업데이트
+ await persistComplianceReviewStatus({
+ contractId,
+ regNo,
+ progressStatus,
+ })
+
+ console.log(`[updateComplianceReviewStatusFromCPVW] 완료: 계약서 ${contractId}, REG_NO ${regNo}, 상태 ${progressStatus}`)
+
+ return {
+ success: true,
+ message: '준법문의 상태가 업데이트되었습니다.',
+ updatedCount: 1,
+ errors: []
+ }
+
+ } catch (error) {
+ console.error('[updateComplianceReviewStatusFromCPVW] 오류:', error)
+ return {
+ success: false,
+ message: '준법문의 상태 업데이트 중 오류가 발생했습니다.',
+ updatedCount: 0,
+ errors: [error instanceof Error ? error.message : '알 수 없는 오류']
+ }
+ }
+}
+
export async function refreshLegalReviewStatusFromOracle(contractId: number): Promise<{
success: boolean
message: string
@@ -3274,12 +3524,9 @@ export async function processBuyerSignatureAction(
}
}
- if (contractData.legalReviewRequestedAt && !contractData.legalReviewCompletedAt) {
- return {
- success: false,
- message: "법무검토가 완료되지 않았습니다."
- }
- }
+ // ⚠️ 법무검토 완료 여부는 SSLVW 상태/시간에 의존하므로
+ // 여기서는 legalReviewCompletedAt 기반으로 최종승인을 막지 않습니다.
+ // (법무 상태는 UI에서 참고 정보로만 사용)
// 파일 저장 로직 (기존 파일 덮어쓰기)
const saveResult = await saveBuffer({
@@ -3373,9 +3620,9 @@ export async function prepareFinalApprovalAction(
if (contract.completedAt !== null || !contract.signedFilePath) {
return false
}
- if (contract.legalReviewRequestedAt && !contract.legalReviewCompletedAt) {
- return false
- }
+ // ⚠️ 법무검토 완료 여부는 SSLVW 상태/시간에 의존하므로
+ // 여기서는 legalReviewCompletedAt 기반으로 필터링하지 않습니다.
+ // (법무 상태는 UI에서 참고 정보로만 사용)
return true
})
@@ -3949,6 +4196,8 @@ export async function saveGtcDocumentAction({
buyerSignedAt: null,
legalReviewRequestedAt: null,
legalReviewCompletedAt: null,
+ complianceReviewRequestedAt: null,
+ complianceReviewCompletedAt: null,
updatedAt: new Date()
})
.where(eq(basicContract.id, documentId))