summaryrefslogtreecommitdiff
path: root/lib/general-contracts/utils.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/general-contracts/utils.ts')
-rw-r--r--lib/general-contracts/utils.ts304
1 files changed, 304 insertions, 0 deletions
diff --git a/lib/general-contracts/utils.ts b/lib/general-contracts/utils.ts
new file mode 100644
index 00000000..ec15a3a1
--- /dev/null
+++ b/lib/general-contracts/utils.ts
@@ -0,0 +1,304 @@
+import { format } from "date-fns"
+
+/**
+ * ContractSummary 인터페이스 (UI 컴포넌트와 맞춤)
+ */
+interface ContractSummary {
+ basicInfo: Record<string, any>
+ items: Record<string, any>[]
+ subcontractChecklist: Record<string, any> | null
+ storageInfo?: Record<string, any>[] // 임치(물품보관) 계약 정보
+}
+
+/**
+ * 계약 데이터를 템플릿 변수로 매핑하는 함수
+ *
+ * @param contractSummary 계약 요약 정보
+ * @returns PDFTron 템플릿에 적용할 변수 맵 (Key-Value)
+ */
+export function mapContractDataToTemplateVariables(contractSummary: ContractSummary) {
+ const { basicInfo, items, storageInfo } = contractSummary
+ const firstItem = items && items.length > 0 ? items[0] : {}
+
+ // 날짜 포맷팅 헬퍼 (YYYY-MM-DD)
+ const formatDate = (date: any) => {
+ if (!date) return ''
+ try {
+ const d = new Date(date)
+ if (isNaN(d.getTime())) return String(date)
+ const year = d.getFullYear()
+ const month = String(d.getMonth() + 1).padStart(2, '0')
+ const day = String(d.getDate()).padStart(2, '0')
+ return `${year}-${month}-${day}`
+ } catch {
+ return String(date)
+ }
+ }
+
+ // 금액 포맷팅 헬퍼 (천단위 콤마)
+ const formatCurrency = (amount: any) => {
+ if (amount === undefined || amount === null || amount === '') return ''
+ const num = Number(amount)
+ if (isNaN(num)) return String(amount)
+ return num.toLocaleString('ko-KR')
+ }
+
+ // 비율 포맷팅 (소수점 제거 등 필요 시)
+ const formatRate = (rate: any) => {
+ if (!rate) return ''
+ return String(rate)
+ }
+
+ // 1. 프로젝트 정보 (Items에서 프로젝트명 추출 시도)
+ const projectName = basicInfo.projectName || (items.length > 0 ? items[0].projectName : '') || "Following potential projects"
+ const projectCode = basicInfo.projectCode || (items.length > 0 ? items[0].projectCode : '') || ''
+
+ // 2. 계약금액 표시 로직 (단가/물량 계약은 '별첨 참조')
+ const contractScope = basicInfo.contractScope || ''
+ let displayContractAmount = formatCurrency(basicInfo.contractAmount)
+ let displayContractAmountText = ''
+
+ if (contractScope === '단가' || contractScope === '물량(실적)') {
+ displayContractAmount = '별첨 참조'
+ displayContractAmountText = '별첨 참조'
+ } else {
+ displayContractAmountText = displayContractAmount
+ }
+
+ // 공급가액 & 부가세 (임시 계산 로직 제거)
+ // 실제로는 taxType에 따라 다를 수 있음 (영세율 등) - 데이터가 있으면 매핑
+ const supplyPrice = basicInfo.supplyPrice ? formatCurrency(basicInfo.supplyPrice) : ''
+ const vat = basicInfo.vat ? formatCurrency(basicInfo.vat) : ''
+
+ // 3. 지급조건 상세 텍스트 생성
+ // 납품 전
+ const prePaymentData = basicInfo.paymentBeforeDelivery || {}
+ let prePaymentText = ''
+ const prePaymentParts: string[] = []
+ if (prePaymentData.apBond) prePaymentParts.push(`AP Bond(${prePaymentData.apBondPercent}%)`)
+ if (prePaymentData.drawingSubmission) prePaymentParts.push(`도면제출(${prePaymentData.drawingSubmissionPercent}%)`)
+ if (prePaymentData.materialPurchase) prePaymentParts.push(`소재구매(${prePaymentData.materialPurchasePercent}%)`)
+ if (prePaymentData.additionalCondition) prePaymentParts.push(`추가조건(${prePaymentData.additionalConditionPercent}%)`)
+ if (prePaymentParts.length > 0) {
+ prePaymentText = `(선급금) ${prePaymentParts.join(', ')}`
+ }
+
+ // 납품
+ const deliveryPaymentText = basicInfo.paymentDelivery ? `(본품 납품) ${basicInfo.paymentDelivery}` : ''
+
+ // 납품 후
+ const postPaymentData = basicInfo.paymentAfterDelivery || {}
+ let postPaymentText = ''
+ const postPaymentParts: string[] = []
+ if (postPaymentData.commissioning) postPaymentParts.push(`Commissioning(${postPaymentData.commissioningPercent}%)`)
+ if (postPaymentData.finalDocument) postPaymentParts.push(`최종문서(${postPaymentData.finalDocumentPercent}%)`)
+ if (postPaymentData.other) postPaymentParts.push(`기타(${postPaymentData.otherText})`)
+ if (postPaymentParts.length > 0) {
+ postPaymentText = `(납품 외) ${postPaymentParts.join(', ')}`
+ }
+
+ // 4. 보증금 및 위약금 (DB 필드값 사용, 임시 계산 제거)
+ // DB에 해당 필드가 없으면 빈 값으로 매핑됨.
+ const contractDepositAmount = basicInfo.contractDepositAmount || ''
+ const defectDepositAmount = basicInfo.defectDepositAmount || ''
+ const paymentDepositAmount = basicInfo.paymentDepositAmount || ''
+ const unfairJointActPenaltyAmount = basicInfo.unfairJointActPenaltyAmount || ''
+
+ // 지체상금
+ const liquidatedDamagesRate = basicInfo.liquidatedDamagesPercent || '0'
+
+ // 5. 조건 텍스트 변환 (JSON -> String)
+ // 계약해지조건
+ let terminationConditionsText = ''
+ if (basicInfo.contractTerminationConditions) {
+ try {
+ const cond = typeof basicInfo.contractTerminationConditions === 'string'
+ ? JSON.parse(basicInfo.contractTerminationConditions)
+ : basicInfo.contractTerminationConditions
+
+ const active: string[] = []
+ if (cond.standardTermination) active.push('표준 계약해지조건')
+ if (cond.projectNotAwarded) active.push('프로젝트 미수주 시')
+ if (cond.other) active.push('기타')
+ terminationConditionsText = active.join(', ')
+ } catch (e) {}
+ }
+
+ // 계약성립조건
+ let establishmentConditionsText = ''
+ if (basicInfo.contractEstablishmentConditions) {
+ try {
+ const cond = typeof basicInfo.contractEstablishmentConditions === 'string'
+ ? JSON.parse(basicInfo.contractEstablishmentConditions)
+ : basicInfo.contractEstablishmentConditions
+
+ const active: string[] = []
+ if (cond.regularVendorRegistration) active.push('정규업체 등록(실사 포함) 시')
+ if (cond.projectAward) active.push('프로젝트 수주 시')
+ if (cond.ownerApproval) active.push('선주 승인 시')
+ if (cond.other) active.push('기타')
+ establishmentConditionsText = active.join(', ')
+ } catch (e) {}
+ }
+
+ // 품질/하자보증기간 텍스트
+ let warrantyPeriodText = ''
+ if (basicInfo.warrantyPeriod) {
+ try {
+ const wp = typeof basicInfo.warrantyPeriod === 'string' ? JSON.parse(basicInfo.warrantyPeriod) : basicInfo.warrantyPeriod
+ const parts: string[] = []
+ if (wp.납품후?.enabled) parts.push(`납품 후 ${wp.납품후.period}개월`)
+ if (wp.인도후?.enabled) parts.push(`인도 후 ${wp.인도후.period}개월`)
+ if (wp.작업후?.enabled) parts.push(`작업 후 ${wp.작업후.period}개월`)
+ if (wp.기타?.enabled) parts.push(`기타`)
+ warrantyPeriodText = parts.join(', ')
+ } catch(e) {}
+ }
+
+ // 6. 임치(물품보관) 계약 관련 (SG)
+ const storageItems = storageInfo || []
+ // 템플릿에서 루프를 지원하지 않을 경우를 대비한 텍스트 포맷 (Fallback)
+ const storageTableText = storageItems.length > 0
+ ? storageItems.map((item, idx) =>
+ `${idx + 1}. PO No.: ${item.poNumber || '-'}, 호선: ${item.hullNumber || '-'}, 미입고 잔여금액: ${formatCurrency(item.remainingAmount)}`
+ ).join('\n')
+ : ''
+
+
+ // ═══════════════════════════════════════════════════════════════
+ // 변수 매핑 시작
+ // ═══════════════════════════════════════════════════════════════
+ const variables: Record<string, any> = {
+ // ----------------------------------
+ // 시스템/공통
+ // ----------------------------------
+ todayDate: formatDate(new Date()), // {{Today}} : 현재 날짜
+
+ // ----------------------------------
+ // 계약 기본 정보
+ // ----------------------------------
+ contractName: basicInfo.contractName || basicInfo.name || '', // {{계약명}}
+ contractNumber: basicInfo.contractNumber || '', // {{계약번호}}
+ contractDate: formatDate(basicInfo.registeredAt || basicInfo.createdAt), // {{계약일자}}
+
+ // ----------------------------------
+ // 프로젝트 정보
+ // ----------------------------------
+ projectName: projectName, // {{프로젝트}}, {{대상호선}} : 없으면 'Following potential projects'
+ projectCode: projectCode, // {{프로젝트코드}}
+
+ // ----------------------------------
+ // 금액 정보
+ // ----------------------------------
+ contractAmount: displayContractAmount, // {{계약금액}} : '별첨 참조' 또는 금액
+ supplyPrice: supplyPrice, // (공급가액)
+ vat: vat, // (부가가치세)
+ contractCurrency: basicInfo.currency || 'KRW', // 통화
+
+ // ----------------------------------
+ // 협력업체(Vendor) 정보
+ // ----------------------------------
+ vendorName: basicInfo.vendorName || '', // {{VendorName}}, {{계약업체}}, {{수탁자}}
+ vendorAddress: basicInfo.vendorAddress || basicInfo.address || '', // {{VendorAddress}}, {{수탁자 주소}}, {{보관장소}}
+ vendorCeoName: basicInfo.vendorCeoName || basicInfo.representativeName || '', // {{Vendor_CEO_Name}}, {{대표이사}}
+ // vendorPhone, vendorEmail 등 필요시 추가
+
+ // ----------------------------------
+ // 당사(SHI) 정보 (고정값/설정값)
+ // ----------------------------------
+ shiAddress: "경기도 성남시 분당구 판교로 227번길 23", // {{SHI_Address}}, {{위탁자 주소}}
+ shiCeoName: "최성안", // {{SHI_CEO_Name}}, {{대표이사}}
+
+ // ----------------------------------
+ // 품목 정보
+ // ----------------------------------
+ // Frame Agreement 등의 {{자재그룹}}, {{자재그룹명}}
+ itemGroup: firstItem.itemCode || '', // {{자재그룹}} : 일단 ItemCode 매핑 (자재그룹 코드가 별도로 있다면 수정 필요)
+ itemGroupName: firstItem.itemInfo || '', // {{자재그룹명}} : ItemInfo 매핑
+ pkgNo: firstItem.itemCode || '', // {{PKG No.}}
+ pkgName: firstItem.itemInfo || '', // {{PKG명}}
+
+ // 일반 계약품목 / 임치 대상품목
+ itemDescription: firstItem.itemInfo || firstItem.description || basicInfo.contractName || '', // {{계약품목}}, {{계약내용}}
+ itemInfo: firstItem.itemInfo || '', // {{Item 정보}}
+ itemName: firstItem.itemInfo || '', // {{ItemName}}
+
+ // OF 배상품목
+ reimbursementItem: firstItem.itemInfo || '', // {{배상품목}}
+
+ // ----------------------------------
+ // 사양 및 공급범위
+ // ----------------------------------
+ // {{사양 및 공급범위}} : 사양서 파일 유무에 따라 텍스트 변경
+ // 실제 파일 존재 여부를 여기서 알기 어려우므로 specificationType으로 판단
+ scopeOfSupply: basicInfo.specificationType === '첨부서류 참조'
+ ? '사양서 파일 참조(As per Technical agreement)'
+ : (basicInfo.specificationManualText || basicInfo.contractName || ''),
+
+ // ----------------------------------
+ // 계약 기간 및 유효기간
+ // ----------------------------------
+ contractPeriod: `${formatDate(basicInfo.startDate)} ~ ${formatDate(basicInfo.endDate)}`, // {{계약기간}}, {{FA 유효기간}}, {{보관날짜}}
+ contractStartDate: formatDate(basicInfo.startDate),
+ contractEndDate: formatDate(basicInfo.endDate),
+ validityEndDate: formatDate(basicInfo.validityEndDate || basicInfo.endDate), // {{LOI 유효기간}}, {{계약체결유효기간}}
+
+ // ----------------------------------
+ // 인도/지급 조건
+ // ----------------------------------
+ incoterms: basicInfo.deliveryTerm || '', // {{Incoterms}}, {{물품인도조건}}
+ paymentTerms: basicInfo.paymentTerm || '', // {{지급조건}}, {{대금지불조건}} - 코드값(L003 등)일 수 있음
+
+ // 상세 지급조건 (선급금, 납품, 납품 외)
+ // 템플릿에 (선급금) ... (본품 납품) ... 항목이 미리 적혀있는지, 변수로 넣어야 하는지에 따라 다름
+ // 예시에서는 줄글로 보임. 각각 매핑.
+ prePaymentCondition: prePaymentText, // (선급금) 조건 텍스트
+ deliveryPaymentCondition: deliveryPaymentText, // (본품 납품) 조건 텍스트
+ postPaymentCondition: postPaymentText, // (납품 외) 조건 텍스트
+
+ // ----------------------------------
+ // 보증기간 및 보증금
+ // ----------------------------------
+ warrantyPeriod: warrantyPeriodText, // {{품질/하자보증기간}}
+
+ // 금액 계산 필드들 (DB 필드값이 없으면 빈 값)
+ contractDeposit: formatCurrency(contractDepositAmount), // {{계약보증금}}
+ defectDeposit: formatCurrency(defectDepositAmount), // {{하자보증금}}
+ paymentDeposit: formatCurrency(paymentDepositAmount), // {{지급보증금}}
+
+ unfairJointActPenalty: formatCurrency(unfairJointActPenaltyAmount), // {{부정담합위약금}}, {{부당한공동행위}}
+
+ // 지체상금
+ liquidatedDamagesRate: formatRate(liquidatedDamagesRate), // {{지체상금비율}}
+ // liquidatedDamages: formatCurrency(liquidatedDamagesAmount), // 금액이 필요한 경우 사용
+
+ // ----------------------------------
+ // 기타 조건
+ // ----------------------------------
+ terminationConditions: terminationConditionsText, // {{계약해지조건}}
+ establishmentConditions: establishmentConditionsText, // {{계약성립조건}}
+ subcontractInterlocking: basicInfo.interlockingSystem || 'N', // {{하도급연동}}
+
+ // ----------------------------------
+ // 참조/연결 정보
+ // ----------------------------------
+ // OF의 {{관련계약번호}}
+ linkedContractNumber: basicInfo.linkedPoNumber || basicInfo.linkedBidNumber || basicInfo.linkedRfqOrItb || '',
+
+ // ----------------------------------
+ // 임치(물품보관) 계약 (SG)
+ // ----------------------------------
+ storageTableText: storageTableText, // {{storageTableText}} (fallback)
+ // PDFTron에서 배열을 받아 테이블 루프를 돌릴 수 있다면 아래 키를 사용
+ storageList: storageItems,
+ }
+
+ // 3. 모든 키를 순회하며 undefined나 null을 빈 문자열로 변환 (안전장치)
+ Object.keys(variables).forEach(key => {
+ if (variables[key] === undefined || variables[key] === null) {
+ variables[key] = ''
+ }
+ })
+
+ return variables
+}