summaryrefslogtreecommitdiff
path: root/lib/vendor-basic-info
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendor-basic-info')
-rw-r--r--lib/vendor-basic-info/basic-info-client.tsx198
-rw-r--r--lib/vendor-basic-info/use-credit-integration.ts326
2 files changed, 474 insertions, 50 deletions
diff --git a/lib/vendor-basic-info/basic-info-client.tsx b/lib/vendor-basic-info/basic-info-client.tsx
index ce8e4dfc..82133f28 100644
--- a/lib/vendor-basic-info/basic-info-client.tsx
+++ b/lib/vendor-basic-info/basic-info-client.tsx
@@ -27,6 +27,7 @@ import { AdditionalInfoDialog } from "@/components/vendor-regular-registrations/
import { getSiteVisitRequestsByVendorId } from "@/lib/site-visit/service";
import { fetchVendorRegistrationStatus } from "@/lib/vendor-regular-registrations/service";
import { getVendorAttachmentsByType, getVendorPeriodicGrade, getVendorTypeInfo } from "@/lib/vendor-info/service";
+import { useCreditIntegration } from "./use-credit-integration";
// downloadFile은 동적으로 import
import {
Table,
@@ -327,6 +328,20 @@ export default function BasicInfoClient({
const [editMode, setEditMode] = useState(false);
const [isPending, startTransition] = useTransition();
+ // 신용평가사 데이터 통합 훅
+ const {
+ loading: creditLoading,
+ error: creditError,
+ creditResults,
+ selectedCreditService,
+ bestResult,
+ getCurrentResult,
+ handleCreditServiceChange,
+ transformCreditToSalesData,
+ creditServices,
+ reload: reloadCreditData
+ } = useCreditIntegration(vendorId);
+
// 다이얼로그 상태
const [pqDialogOpen, setPqDialogOpen] = useState(false);
const [siteVisitDialogOpen, setSiteVisitDialogOpen] = useState(false);
@@ -1178,6 +1193,71 @@ export default function BasicInfoClient({
subtitle="(3개년)"
noPadding={true}
content={
+ <div className="space-y-4">
+ {/* 신용평가사 선택 */}
+ <div className="p-4 border-b bg-muted/30">
+ <div className="flex items-center justify-between mb-2">
+ <span className="text-sm font-medium">신용평가사 데이터</span>
+ {creditLoading && (
+ <div className="flex items-center gap-2 text-sm text-muted-foreground">
+ <div className="animate-spin h-4 w-4 border-2 border-primary border-t-transparent rounded-full"></div>
+ 로딩 중...
+ </div>
+ )}
+ </div>
+
+ <div className="flex items-center gap-4">
+ <Select
+ value={selectedCreditService}
+ onValueChange={handleCreditServiceChange}
+ >
+ <SelectTrigger className="w-48">
+ <SelectValue placeholder="신용평가사 선택" />
+ </SelectTrigger>
+ <SelectContent>
+ {creditServices.map((service) => {
+ const result = creditResults.find(r => r.code === service.code);
+ return (
+ <SelectItem key={service.code} value={service.code}>
+ {service.name}
+ {service.code !== 'auto' && result && (
+ <span className="ml-2 text-xs text-muted-foreground">
+ {result.success
+ ? `(${result.dataCount}개 항목)`
+ : '(조회 실패)'
+ }
+ </span>
+ )}
+ </SelectItem>
+ );
+ })}
+ </SelectContent>
+ </Select>
+
+ {getCurrentResult() && (
+ <div className="text-sm text-muted-foreground">
+ 선택됨: <span className="font-medium">{getCurrentResult()?.name}</span>
+ {selectedCreditService === 'auto' && bestResult && (
+ <span className="ml-1">({bestResult.dataCount}개 항목으로 자동선택)</span>
+ )}
+ </div>
+ )}
+
+ {creditError && (
+ <div className="text-sm text-destructive">
+ {creditError}
+ </div>
+ )}
+
+ {!creditLoading && !creditError && creditResults.length > 0 && !getCurrentResult()?.data && (
+ <div className="text-sm text-muted-foreground">
+ 신용평가 데이터가 없습니다
+ </div>
+ )}
+ </div>
+ </div>
+
+ {/* 테이블 */}
<Table>
<TableHeader>
<TableRow>
@@ -1263,59 +1343,77 @@ export default function BasicInfoClient({
</TableRow>
</TableHeader>
<TableBody>
- {["20231231", "20221231", "20211231"].map((dateKey) => {
- const year = dateKey;
- const salesData = initialData.salesInfo?.[year];
- const metricsData = initialData.calculatedMetrics?.[dateKey];
+ {(() => {
+ // 신용평가사 데이터 우선 사용, 없으면 기존 데이터 사용
+ const currentResult = getCurrentResult();
+ const creditTransformedData = currentResult?.data ? transformCreditToSalesData(currentResult.data) : null;
+
+ const salesData = creditTransformedData?.salesInfo || initialData.salesInfo || {};
+ const metricsData = creditTransformedData?.calculatedMetrics || initialData.calculatedMetrics || {};
+
+ // 실제 데이터에서 연도 추출 (고정 연도 사용하지 않음)
+ const years = Object.keys(salesData).sort().reverse().slice(0, 3);
+
+ // 데이터가 없는 경우 기본 연도 사용
+ if (years.length === 0 && !getCurrentResult()?.data) {
+ years.push("20231231", "20221231", "20211231");
+ }
+
+ return years.map((dateKey) => {
+ const formattedDate = dateKey; // YYYYMMDD 형식으로 표시
+ const yearSalesData = salesData[dateKey];
+ const yearMetricsData = metricsData[dateKey];
- return (
- <TableRow key={dateKey}>
- <TableCell className="text-center font-medium border-r bg-yellow-50">
- {year}
- </TableCell>
- <TableCell className="text-right border-r">
- {salesData
- ? (
- parseInt(salesData.totalDebt.replace(/,/g, "")) +
- parseInt(salesData.totalEquity.replace(/,/g, ""))
- ).toLocaleString()
- : "-"}
- </TableCell>
- <TableCell className="text-right border-r">
- {salesData?.totalDebt || "-"}
- </TableCell>
- <TableCell className="text-right border-r">
- {salesData?.totalEquity || "-"}
- </TableCell>
- <TableCell className="text-right border-r">
- {salesData?.operatingProfit || "-"}
- </TableCell>
- <TableCell className="text-right border-r">
- {salesData?.netIncome || "-"}
- </TableCell>
- <TableCell className="text-right border-r">
- {metricsData?.debtRatio?.toFixed(1) || "-"}
- </TableCell>
- <TableCell className="text-right border-r">
- {metricsData?.borrowingDependency?.toFixed(1) || "-"}
- </TableCell>
- <TableCell className="text-right border-r">
- {metricsData?.operatingMargin?.toFixed(1) || "-"}
- </TableCell>
- <TableCell className="text-right border-r">
- {metricsData?.netMargin?.toFixed(1) || "-"}
- </TableCell>
- <TableCell className="text-right border-r">
- {metricsData?.salesGrowth?.toFixed(1) || "-"}
- </TableCell>
- <TableCell className="text-right">
- {metricsData?.currentRatio?.toFixed(1) || "-"}
- </TableCell>
- </TableRow>
- );
- })}
+ return (
+ <TableRow key={dateKey}>
+ <TableCell className="text-center font-medium border-r bg-yellow-50">
+ {formattedDate}
+ </TableCell>
+ <TableCell className="text-right border-r">
+ {yearSalesData && yearSalesData.totalDebt && yearSalesData.totalEquity
+ ? (
+ parseInt(yearSalesData.totalDebt.replace(/,/g, "")) +
+ parseInt(yearSalesData.totalEquity.replace(/,/g, ""))
+ ).toLocaleString()
+ : "-"}
+ </TableCell>
+ <TableCell className="text-right border-r">
+ {yearSalesData?.totalDebt || "-"}
+ </TableCell>
+ <TableCell className="text-right border-r">
+ {yearSalesData?.totalEquity || "-"}
+ </TableCell>
+ <TableCell className="text-right border-r">
+ {yearSalesData?.operatingProfit || "-"}
+ </TableCell>
+ <TableCell className="text-right border-r">
+ {yearSalesData?.netIncome || "-"}
+ </TableCell>
+ <TableCell className="text-right border-r">
+ {yearMetricsData?.debtRatio ? yearMetricsData.debtRatio.toFixed(1) : "-"}
+ </TableCell>
+ <TableCell className="text-right border-r">
+ {yearMetricsData?.borrowingDependency ? yearMetricsData.borrowingDependency.toFixed(1) : "-"}
+ </TableCell>
+ <TableCell className="text-right border-r">
+ {yearMetricsData?.operatingMargin ? yearMetricsData.operatingMargin.toFixed(1) : "-"}
+ </TableCell>
+ <TableCell className="text-right border-r">
+ {yearMetricsData?.netMargin ? yearMetricsData.netMargin.toFixed(1) : "-"}
+ </TableCell>
+ <TableCell className="text-right border-r">
+ {yearMetricsData?.salesGrowth ? yearMetricsData.salesGrowth.toFixed(1) : "-"}
+ </TableCell>
+ <TableCell className="text-right">
+ {yearMetricsData?.currentRatio ? yearMetricsData.currentRatio.toFixed(1) : "-"}
+ </TableCell>
+ </TableRow>
+ );
+ });
+ })()}
</TableBody>
</Table>
+ </div>
}
/>
diff --git a/lib/vendor-basic-info/use-credit-integration.ts b/lib/vendor-basic-info/use-credit-integration.ts
new file mode 100644
index 00000000..5cdfd04a
--- /dev/null
+++ b/lib/vendor-basic-info/use-credit-integration.ts
@@ -0,0 +1,326 @@
+'use client';
+
+import { useState, useEffect, useCallback } from 'react';
+import { getCreditInfo } from '@/lib/oracle-db/nonsap/services/creditService';
+
+// 신용평가사 옵션
+const creditServices = [
+ { code: 'I', name: '이크레더블' },
+ { code: 'K', name: '한국기업데이터' },
+ { code: 'N', name: '나이스디앤비' },
+ { code: 'E', name: 'NICE신용평가사' }
+];
+
+interface CreditData {
+ // 기본 정보
+ RESNO: string;
+ EENTNM: string;
+ OPEDT: string;
+ REPR_NM: string;
+ TYSCALE: string;
+ RELCMP: string;
+ ADR: string;
+ LISTYN: string;
+ GOODSNM: string;
+ TELNO: string;
+ FAXNO: string;
+ FSTRDRT: string;
+
+ // 매출순위
+ MO_1: string;
+ MO_4: string;
+ MO_2: string;
+ MO_5: string;
+ MO_3: string;
+ MO_6: string;
+
+ // 매입순위
+ MI_1: string;
+ MI_4: string;
+ MI_2: string;
+ MI_5: string;
+ MI_3: string;
+ MI_6: string;
+
+ // 지분관계
+ GIBUN_RL_1: string;
+ LSH_STK_RATE1: string;
+ GIBUN_RL_2: string;
+ LSH_STK_RATE2: string;
+ GIBUN_RL_3: string;
+ LSH_STK_RATE3: string;
+
+ // 기타 정보
+ HAPGYE: string;
+ NOTICE: string;
+ NOTICE_DT: string;
+ HPGBNCR_TY: string;
+ FCLOSDT: string;
+ LASTGRD: string;
+ DECISION: string;
+ FRISKRV: string;
+ EXPIRE_GB: string;
+ FWATCHD: string;
+ SUVDT: string;
+ ESETDTT: string;
+
+ // 재무년월
+ bs_dt0: string;
+ bs_dt1: string;
+ bs_dt2: string;
+
+ // 재무현황
+ bs59_0: string; // 총자산
+ bs59_1: string;
+ bs59_2: string;
+ bs91_0: string; // 부채총계
+ bs91_1: string;
+ bs91_2: string;
+ bs113_0: string; // 자본총계
+ bs113_1: string;
+ bs113_2: string;
+ pl01_0: string; // 매출액
+ pl01_1: string;
+ pl01_2: string;
+ pl27_0: string; // 영업이익
+ pl27_1: string;
+ pl27_2: string;
+ pl71_0: string; // 당기순이익
+ pl71_1: string;
+ pl71_2: string;
+
+ // 재무비율
+ TR0053: string; // 부채비율
+ TR0052: string;
+ TR0051: string;
+ TR0513: string; // 차입금의존도
+ TR0512: string;
+ TR0511: string;
+ TR0523: string; // 영업이익율
+ TR0522: string;
+ TR0521: string;
+ TR0103: string; // 매출순이익율
+ TR0102: string;
+ TR0101: string;
+ TR0223: string; // 매출액증가율
+ TR0222: string;
+ TR0221: string;
+ TR0013: string; // 유동비율
+ TR0012: string;
+ TR0011: string;
+}
+
+interface CreditServiceResult {
+ code: string;
+ name: string;
+ data: CreditData | null;
+ dataCount: number;
+ success: boolean;
+ error: string | null;
+}
+
+export function useCreditIntegration(vendorId: string) {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState<string | null>(null);
+ const [creditResults, setCreditResults] = useState<CreditServiceResult[]>([]);
+ const [selectedCreditService, setSelectedCreditService] = useState<string>('auto');
+ const [bestResult, setBestResult] = useState<CreditServiceResult | null>(null);
+
+ // 데이터 개수를 계산하는 함수
+ const calculateDataCount = (data: CreditData | null): number => {
+ if (!data) return 0;
+
+ let count = 0;
+ const fieldsToCheck = [
+ // 재무현황 필드들
+ 'bs59_0', 'bs59_1', 'bs59_2', // 총자산
+ 'bs91_0', 'bs91_1', 'bs91_2', // 부채총계
+ 'bs113_0', 'bs113_1', 'bs113_2', // 자본총계
+ 'pl01_0', 'pl01_1', 'pl01_2', // 매출액
+ 'pl27_0', 'pl27_1', 'pl27_2', // 영업이익
+ 'pl71_0', 'pl71_1', 'pl71_2', // 당기순이익
+ // 재무비율 필드들
+ 'TR0053', 'TR0052', 'TR0051', // 부채비율
+ 'TR0513', 'TR0512', 'TR0511', // 차입금의존도
+ 'TR0523', 'TR0522', 'TR0521', // 영업이익율
+ 'TR0103', 'TR0102', 'TR0101', // 순이익율
+ 'TR0223', 'TR0222', 'TR0221', // 매출액증가율
+ 'TR0013', 'TR0012', 'TR0011', // 유동비율
+ ];
+
+ fieldsToCheck.forEach(field => {
+ const value = data[field as keyof CreditData];
+ if (value && value.toString().trim() !== '' && value.toString().trim() !== '0') {
+ count++;
+ }
+ });
+
+ return count;
+ };
+
+ // 모든 신용평가사 데이터를 병렬로 조회
+ const loadAllCreditData = useCallback(async () => {
+ if (!vendorId) return;
+
+ setLoading(true);
+ setError(null);
+
+ try {
+ const promises = creditServices.map(async (service) => {
+ try {
+ const result = await getCreditInfo(vendorId, service.code);
+ const data = result && result.length > 0 ? result[0] : null;
+ const dataCount = calculateDataCount(data);
+
+ return {
+ code: service.code,
+ name: service.name,
+ data,
+ dataCount,
+ success: true,
+ error: null
+ };
+ } catch (err) {
+ console.error(`Error loading credit data for ${service.name}:`, err);
+ return {
+ code: service.code,
+ name: service.name,
+ data: null,
+ dataCount: 0,
+ success: false,
+ error: `${service.name} 데이터 조회 실패`
+ };
+ }
+ });
+
+ const results = await Promise.all(promises);
+ setCreditResults(results);
+
+ // 모든 신용평가사 조회가 실패했는지 확인
+ const allFailed = results.every(result => !result.success);
+ if (allFailed) {
+ const failedServices = results.filter(r => !r.success).map(r => r.name).join(', ');
+ setError(`모든 신용평가사 데이터 조회에 실패했습니다: ${failedServices}`);
+ setBestResult(null);
+ return;
+ }
+
+ // 부분적으로 실패한 경우에도 성공한 것들로 진행
+ const successfulResults = results.filter(result => result.success);
+
+ // 데이터 개수가 가장 많은 결과 찾기 (성공한 것들 중에서)
+ const best = successfulResults.reduce((prev, current) => {
+ return current.dataCount > prev.dataCount ? current : prev;
+ }, successfulResults[0]);
+
+ setBestResult(best && best.dataCount > 0 ? best : null);
+
+ // 성공했지만 모든 데이터가 비어있는 경우
+ if (successfulResults.length > 0 && successfulResults.every(r => r.dataCount === 0)) {
+ setError(null); // 오류는 아니므로 에러 메시지 지우기
+ }
+
+ } catch (err) {
+ setError('신용평가 데이터를 불러오는 중 예상치 못한 오류가 발생했습니다.');
+ console.error(err);
+ setBestResult(null);
+ } finally {
+ setLoading(false);
+ }
+ }, [vendorId]);
+
+ // 신용평가사 선택 변경 핸들러
+ const handleCreditServiceChange = (code: string) => {
+ setSelectedCreditService(code);
+ };
+
+ // 현재 선택된 결과 반환
+ const getCurrentResult = (): CreditServiceResult | null => {
+ if (selectedCreditService === 'auto') {
+ return bestResult;
+ }
+ return creditResults.find(r => r.code === selectedCreditService) || null;
+ };
+
+ // Credit 데이터를 Basic 페이지 형식으로 변환
+ const transformCreditToSalesData = (creditData: CreditData | null) => {
+ if (!creditData) return null;
+
+ // 날짜 변환 함수 (20.12.31 -> 20201231)
+ const convertDateToYYYYMMDD = (dateStr: string): string => {
+ if (!dateStr) return '';
+ const parts = dateStr.split('.');
+ if (parts.length >= 3) {
+ let year = parseInt(parts[0]);
+ const month = parts[1].padStart(2, '0');
+ const day = parts[2].padStart(2, '0');
+
+ // 2자리 연도를 4자리로 변환
+ if (year >= 0 && year <= 30) {
+ year = 2000 + year;
+ } else if (year >= 70 && year <= 99) {
+ year = 1900 + year;
+ }
+
+ return `${year}${month}${day}`;
+ }
+ return '';
+ };
+
+ // 숫자 값 정리 함수
+ const cleanNumber = (value: string): string => {
+ if (!value) return '0';
+ return value.replace(/,/g, '').trim();
+ };
+
+ const salesInfo: { [year: string]: { totalSales: string; totalDebt: string; totalEquity: string; operatingProfit: string; netIncome: string; } } = {};
+ const calculatedMetrics: { [year: string]: { debtRatio: number; borrowingDependency: number; operatingMargin: number; netMargin: number; salesGrowth: number; currentRatio: number; } } = {};
+
+ // 3개년 데이터 변환
+ for (let i = 0; i < 3; i++) {
+ const yearKey = convertDateToYYYYMMDD(creditData[`bs_dt${i}` as keyof CreditData] as string);
+ if (!yearKey) continue;
+
+ // 매출정보 변환
+ salesInfo[yearKey] = {
+ totalSales: cleanNumber(creditData[`pl01_${i}` as keyof CreditData] as string),
+ totalDebt: cleanNumber(creditData[`bs91_${i}` as keyof CreditData] as string),
+ totalEquity: cleanNumber(creditData[`bs113_${i}` as keyof CreditData] as string),
+ operatingProfit: cleanNumber(creditData[`pl27_${i}` as keyof CreditData] as string),
+ netIncome: cleanNumber(creditData[`pl71_${i}` as keyof CreditData] as string),
+ };
+
+ // 계산된 지표 변환 (i=0은 최신년도, i=2는 가장 오래된 년도)
+ calculatedMetrics[yearKey] = {
+ debtRatio: parseFloat(creditData[`TR005${3-i}` as keyof CreditData] as string) || 0, // TR0053, TR0052, TR0051
+ borrowingDependency: parseFloat(creditData[`TR051${3-i}` as keyof CreditData] as string) || 0, // TR0513, TR0512, TR0511
+ operatingMargin: parseFloat(creditData[`TR052${3-i}` as keyof CreditData] as string) || 0, // TR0523, TR0522, TR0521
+ netMargin: parseFloat(creditData[`TR010${3-i}` as keyof CreditData] as string) || 0, // TR0103, TR0102, TR0101
+ salesGrowth: parseFloat(creditData[`TR022${3-i}` as keyof CreditData] as string) || 0, // TR0223, TR0222, TR0221
+ currentRatio: parseFloat(creditData[`TR001${3-i}` as keyof CreditData] as string) || 0, // TR0013, TR0012, TR0011
+ };
+ }
+
+ return { salesInfo, calculatedMetrics };
+ };
+
+ // 초기 데이터 로드
+ useEffect(() => {
+ if (vendorId) {
+ loadAllCreditData();
+ }
+ }, [vendorId, loadAllCreditData]);
+
+ return {
+ loading,
+ error,
+ creditResults,
+ selectedCreditService,
+ bestResult,
+ getCurrentResult,
+ handleCreditServiceChange,
+ transformCreditToSalesData,
+ creditServices: [...creditServices, { code: 'auto', name: '자동선택 (최적)' }],
+ reload: loadAllCreditData
+ };
+}