diff --git a/app/Http/Controllers/Credit/CreditController.php b/app/Http/Controllers/Credit/CreditController.php index d2d6ef55..469c0427 100644 --- a/app/Http/Controllers/Credit/CreditController.php +++ b/app/Http/Controllers/Credit/CreditController.php @@ -4,6 +4,7 @@ use App\Http\Controllers\Controller; use App\Models\Coocon\CooconConfig; +use App\Models\Credit\CreditInquiry; use App\Services\Coocon\CooconService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -16,8 +17,7 @@ class CreditController extends Controller { /** - * 신용평가 조회 페이지 - * HTMX 요청 시 전체 페이지 리로드 (스크립트 로딩을 위해) + * 신용평가 조회 이력 목록 */ public function inquiry(Request $request): View|Response { @@ -28,24 +28,55 @@ public function inquiry(Request $request): View|Response $service = new CooconService(); $hasConfig = $service->hasConfig(); + // 검색 조건 + $query = CreditInquiry::query() + ->with('user:id,name') + ->orderBy('inquired_at', 'desc'); + + // 사업자번호 검색 + if ($request->filled('company_key')) { + $companyKey = preg_replace('/[^0-9]/', '', $request->input('company_key')); + $query->where('company_key', 'like', "%{$companyKey}%"); + } + + // 기간 검색 + if ($request->filled('start_date')) { + $query->where('inquired_at', '>=', $request->input('start_date') . ' 00:00:00'); + } + if ($request->filled('end_date')) { + $query->where('inquired_at', '<=', $request->input('end_date') . ' 23:59:59'); + } + + // 이슈 있는 것만 + if ($request->boolean('has_issue')) { + $query->withIssues(); + } + + $inquiries = $query->paginate(20)->withQueryString(); + return view('credit.inquiry.index', [ 'hasConfig' => $hasConfig, 'apiTypes' => CooconService::API_NAMES, + 'inquiries' => $inquiries, + 'filters' => [ + 'company_key' => $request->input('company_key'), + 'start_date' => $request->input('start_date'), + 'end_date' => $request->input('end_date'), + 'has_issue' => $request->boolean('has_issue'), + ], ]); } /** - * 신용정보 조회 API + * 신용정보 조회 및 저장 API */ public function search(Request $request): JsonResponse { $request->validate([ 'company_key' => 'required|string|max:20', - 'api_type' => 'nullable|string|in:OA12,OA13,OA14,OA15,OA16,OA17,all', ]); - $companyKey = $request->input('company_key'); - $apiType = $request->input('api_type', 'all'); + $companyKey = preg_replace('/[^0-9]/', '', $request->input('company_key')); $service = new CooconService(); @@ -56,21 +87,110 @@ public function search(Request $request): JsonResponse ], 400); } - $result = match ($apiType) { - 'OA12' => ['summary' => $service->getCreditSummary($companyKey)], - 'OA13' => ['shortTermOverdue' => $service->getShortTermOverdueInfo($companyKey)], - 'OA14' => ['negativeInfoKCI' => $service->getNegativeInfoKCI($companyKey)], - 'OA15' => ['negativeInfoCB' => $service->getNegativeInfoCB($companyKey)], - 'OA16' => ['suspensionInfo' => $service->getSuspensionInfo($companyKey)], - 'OA17' => ['workoutInfo' => $service->getWorkoutInfo($companyKey)], - default => $service->getAllCreditInfo($companyKey), - }; + // 전체 신용정보 조회 + $apiResult = $service->getAllCreditInfo($companyKey); + + // DB에 저장 + $inquiry = CreditInquiry::createFromApiResponse( + $companyKey, + $apiResult, + auth()->id() + ); return response()->json([ 'success' => true, - 'data' => $result, + 'data' => $apiResult, + 'inquiry_key' => $inquiry->inquiry_key, + 'inquiry_id' => $inquiry->id, 'company_key' => $companyKey, - 'api_type' => $apiType, + ]); + } + + /** + * 특정 조회 이력의 원본 데이터 조회 + */ + public function getRawData(string $inquiryKey): JsonResponse + { + $inquiry = CreditInquiry::where('inquiry_key', $inquiryKey)->firstOrFail(); + + return response()->json([ + 'success' => true, + 'data' => $inquiry->getAllRawData(), + 'inquiry' => [ + 'id' => $inquiry->id, + 'inquiry_key' => $inquiry->inquiry_key, + 'company_key' => $inquiry->company_key, + 'formatted_company_key' => $inquiry->formatted_company_key, + 'company_name' => $inquiry->company_name, + 'inquired_at' => $inquiry->inquired_at->format('Y-m-d H:i:s'), + 'status' => $inquiry->status, + 'total_issue_count' => $inquiry->total_issue_count, + ], + ]); + } + + /** + * 특정 조회 이력의 리포트 데이터 조회 (가공된 형태) + */ + public function getReportData(string $inquiryKey): JsonResponse + { + $inquiry = CreditInquiry::where('inquiry_key', $inquiryKey)->firstOrFail(); + + // TODO: CreditReportTransformer 서비스를 통해 데이터 가공 + // 현재는 원본 데이터 그대로 반환 + $reportData = $this->transformToReportFormat($inquiry); + + return response()->json([ + 'success' => true, + 'data' => $reportData, + 'inquiry' => [ + 'id' => $inquiry->id, + 'inquiry_key' => $inquiry->inquiry_key, + 'company_key' => $inquiry->company_key, + 'formatted_company_key' => $inquiry->formatted_company_key, + 'inquired_at' => $inquiry->inquired_at->format('Y-m-d H:i:s'), + ], + ]); + } + + /** + * 리포트 형식으로 데이터 변환 (임시 구현) + */ + private function transformToReportFormat(CreditInquiry $inquiry): array + { + $rawData = $inquiry->getAllRawData(); + + return [ + 'company_info' => [ + 'company_key' => $inquiry->formatted_company_key, + 'company_name' => $inquiry->company_name ?? '-', + 'inquired_at' => $inquiry->inquired_at->format('Y년 m월 d일 H:i'), + ], + 'summary' => [ + 'total_issue_count' => $inquiry->total_issue_count, + 'has_issue' => $inquiry->has_issue, + 'short_term_overdue_cnt' => $inquiry->short_term_overdue_cnt, + 'negative_info_kci_cnt' => $inquiry->negative_info_kci_cnt, + 'negative_info_pb_cnt' => $inquiry->negative_info_pb_cnt, + 'negative_info_cb_cnt' => $inquiry->negative_info_cb_cnt, + 'suspension_info_cnt' => $inquiry->suspension_info_cnt, + 'workout_cnt' => $inquiry->workout_cnt, + ], + 'details' => $rawData, + ]; + } + + /** + * 조회 이력 삭제 + */ + public function deleteInquiry(int $id): JsonResponse + { + $inquiry = CreditInquiry::findOrFail($id); + $inquiry->delete(); + + return response()->json([ + 'success' => true, + 'message' => '조회 이력이 삭제되었습니다.', ]); } diff --git a/app/Models/Credit/CreditInquiry.php b/app/Models/Credit/CreditInquiry.php new file mode 100644 index 00000000..222172d3 --- /dev/null +++ b/app/Models/Credit/CreditInquiry.php @@ -0,0 +1,220 @@ + 'datetime', + 'raw_summary' => 'array', + 'raw_short_term_overdue' => 'array', + 'raw_negative_info_kci' => 'array', + 'raw_negative_info_cb' => 'array', + 'raw_suspension_info' => 'array', + 'raw_workout_info' => 'array', + 'short_term_overdue_cnt' => 'integer', + 'negative_info_kci_cnt' => 'integer', + 'negative_info_pb_cnt' => 'integer', + 'negative_info_cb_cnt' => 'integer', + 'suspension_info_cnt' => 'integer', + 'workout_cnt' => 'integer', + ]; + + /** + * 모델 부팅 시 inquiry_key 자동 생성 + */ + protected static function boot() + { + parent::boot(); + + static::creating(function ($model) { + if (empty($model->inquiry_key)) { + $model->inquiry_key = self::generateInquiryKey(); + } + if (empty($model->inquired_at)) { + $model->inquired_at = now(); + } + }); + } + + /** + * 고유 조회 키 생성 + */ + public static function generateInquiryKey(): string + { + return date('Ymd') . Str::upper(Str::random(24)); + } + + /** + * 조회자 관계 + */ + public function user(): BelongsTo + { + return $this->belongsTo(\App\Models\User::class); + } + + /** + * 총 이슈 건수 계산 + */ + public function getTotalIssueCountAttribute(): int + { + return $this->short_term_overdue_cnt + + $this->negative_info_kci_cnt + + $this->negative_info_pb_cnt + + $this->negative_info_cb_cnt + + $this->suspension_info_cnt + + $this->workout_cnt; + } + + /** + * 이슈 여부 확인 + */ + public function getHasIssueAttribute(): bool + { + return $this->total_issue_count > 0; + } + + /** + * 포맷된 사업자번호 + */ + public function getFormattedCompanyKeyAttribute(): string + { + $key = $this->company_key; + if (strlen($key) === 10) { + return substr($key, 0, 3) . '-' . substr($key, 3, 2) . '-' . substr($key, 5); + } + return $key; + } + + /** + * 전체 원본 데이터 조합 + */ + public function getAllRawData(): array + { + return [ + 'summary' => $this->raw_summary, + 'shortTermOverdue' => $this->raw_short_term_overdue, + 'negativeInfoKCI' => $this->raw_negative_info_kci, + 'negativeInfoCB' => $this->raw_negative_info_cb, + 'suspensionInfo' => $this->raw_suspension_info, + 'workoutInfo' => $this->raw_workout_info, + ]; + } + + /** + * API 응답으로부터 모델 생성 + */ + public static function createFromApiResponse(string $companyKey, array $apiResult, ?int $userId = null): self + { + // 요약 정보에서 건수 추출 + $summaryData = $apiResult['summary']['data'] ?? []; + $creditSummaryList = $summaryData['data']['creditSummaryList'][0] + ?? $summaryData['creditSummaryList'][0] + ?? []; + + // 성공/실패 상태 판단 + $successCount = 0; + $totalCount = 6; + $errors = []; + + foreach (['summary', 'shortTermOverdue', 'negativeInfoKCI', 'negativeInfoCB', 'suspensionInfo', 'workoutInfo'] as $key) { + if (isset($apiResult[$key]['success']) && $apiResult[$key]['success']) { + $successCount++; + } else { + $errors[] = $key . ': ' . ($apiResult[$key]['error'] ?? 'Unknown error'); + } + } + + $status = match (true) { + $successCount === $totalCount => 'success', + $successCount > 0 => 'partial', + default => 'failed', + }; + + return self::create([ + 'company_key' => $companyKey, + 'user_id' => $userId, + 'inquired_at' => now(), + + // 요약 건수 + 'short_term_overdue_cnt' => $creditSummaryList['shorttermOverdueCnt'] ?? 0, + 'negative_info_kci_cnt' => $creditSummaryList['negativeInfoBbCnt'] ?? 0, + 'negative_info_pb_cnt' => $creditSummaryList['negativeInfoPbCnt'] ?? 0, + 'negative_info_cb_cnt' => $creditSummaryList['negativeInfoCbCnt'] ?? 0, + 'suspension_info_cnt' => $creditSummaryList['suspensionInfoCnt'] ?? 0, + 'workout_cnt' => $creditSummaryList['workoutCnt'] ?? 0, + + // 원본 데이터 + 'raw_summary' => $apiResult['summary'] ?? null, + 'raw_short_term_overdue' => $apiResult['shortTermOverdue'] ?? null, + 'raw_negative_info_kci' => $apiResult['negativeInfoKCI'] ?? null, + 'raw_negative_info_cb' => $apiResult['negativeInfoCB'] ?? null, + 'raw_suspension_info' => $apiResult['suspensionInfo'] ?? null, + 'raw_workout_info' => $apiResult['workoutInfo'] ?? null, + + // 상태 + 'status' => $status, + 'error_message' => $status !== 'success' ? implode('; ', $errors) : null, + ]); + } + + /** + * 스코프: 사업자번호로 검색 + */ + public function scopeByCompanyKey($query, string $companyKey) + { + return $query->where('company_key', $companyKey); + } + + /** + * 스코프: 기간으로 검색 + */ + public function scopeBetweenDates($query, $startDate, $endDate) + { + return $query->whereBetween('inquired_at', [$startDate, $endDate]); + } + + /** + * 스코프: 이슈 있는 것만 + */ + public function scopeWithIssues($query) + { + return $query->where(function ($q) { + $q->where('short_term_overdue_cnt', '>', 0) + ->orWhere('negative_info_kci_cnt', '>', 0) + ->orWhere('negative_info_pb_cnt', '>', 0) + ->orWhere('negative_info_cb_cnt', '>', 0) + ->orWhere('suspension_info_cnt', '>', 0) + ->orWhere('workout_cnt', '>', 0); + }); + } +} diff --git a/database/migrations/2026_01_22_201143_create_credit_inquiries_table.php b/database/migrations/2026_01_22_201143_create_credit_inquiries_table.php new file mode 100644 index 00000000..d9e78347 --- /dev/null +++ b/database/migrations/2026_01_22_201143_create_credit_inquiries_table.php @@ -0,0 +1,57 @@ +id(); + $table->string('inquiry_key', 32)->unique()->comment('조회 고유 키'); + $table->string('company_key', 20)->index()->comment('사업자번호/법인번호'); + $table->string('company_name')->nullable()->comment('업체명'); + $table->unsignedBigInteger('user_id')->nullable()->comment('조회자 ID'); + $table->timestamp('inquired_at')->comment('조회 일시'); + + // 요약 정보 (빠른 조회용) + $table->unsignedInteger('short_term_overdue_cnt')->default(0)->comment('단기연체정보 건수'); + $table->unsignedInteger('negative_info_kci_cnt')->default(0)->comment('신용도판단정보(한국신용정보원) 건수'); + $table->unsignedInteger('negative_info_pb_cnt')->default(0)->comment('공공정보 건수'); + $table->unsignedInteger('negative_info_cb_cnt')->default(0)->comment('신용도판단정보(신용정보사) 건수'); + $table->unsignedInteger('suspension_info_cnt')->default(0)->comment('당좌거래정지정보 건수'); + $table->unsignedInteger('workout_cnt')->default(0)->comment('법정관리/워크아웃정보 건수'); + + // API 응답 원본 데이터 (JSON) + $table->json('raw_summary')->nullable()->comment('OA12 신용요약정보 원본'); + $table->json('raw_short_term_overdue')->nullable()->comment('OA13 단기연체정보 원본'); + $table->json('raw_negative_info_kci')->nullable()->comment('OA14 신용도판단정보(한국신용정보원) 원본'); + $table->json('raw_negative_info_cb')->nullable()->comment('OA15 신용도판단정보(신용정보사) 원본'); + $table->json('raw_suspension_info')->nullable()->comment('OA16 당좌거래정지정보 원본'); + $table->json('raw_workout_info')->nullable()->comment('OA17 법정관리/워크아웃정보 원본'); + + // 상태 + $table->enum('status', ['success', 'partial', 'failed'])->default('success')->comment('조회 상태'); + $table->text('error_message')->nullable()->comment('에러 메시지'); + + $table->timestamps(); + + // 인덱스 + $table->index(['company_key', 'inquired_at']); + $table->index('inquired_at'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('credit_inquiries'); + } +}; diff --git a/resources/views/credit/inquiry/index.blade.php b/resources/views/credit/inquiry/index.blade.php index 9f329acf..ab3ab536 100644 --- a/resources/views/credit/inquiry/index.blade.php +++ b/resources/views/credit/inquiry/index.blade.php @@ -8,16 +8,27 @@

신용평가 조회

-

기업 신용평가 정보를 조회합니다 (쿠콘 NICE평가정보 API)

+

기업 신용평가 조회 이력을 관리합니다

+
+
+ + + + + + + API 설정 +
- - - - - - API 설정 -
@if(!$hasConfig) @@ -35,59 +46,220 @@ class="inline-flex items-center gap-2 px-4 py-2 border border-gray-300 text-gray @endif - -
-
- -
- + +
+ +
+ -

숫자만 입력하면 자동으로 형식이 적용됩니다

+ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
- - -
- + + 초기화 +
- -
- -
- - - -

종합 신용평가 조회

-

사업자번호 또는 법인번호를 입력하여 신용도판단 정보를 조회하세요

-
-

조회 항목: 신용요약정보, 단기연체정보, 신용도판단정보

-

당좌거래정지정보, 법정관리/워크아웃정보

-
+ +
+
+ + + + + + + + + + + + + + + @forelse($inquiries as $inquiry) + + + + + + + + + + + @empty + + + + @endforelse + +
조회키사업자번호업체명조회일시상태이슈조회자액션
{{ substr($inquiry->inquiry_key, 0, 16) }}...{{ $inquiry->formatted_company_key }}{{ $inquiry->company_name ?? '-' }}{{ $inquiry->inquired_at->format('Y-m-d H:i') }} + @if($inquiry->status === 'success') + 성공 + @elseif($inquiry->status === 'partial') + 부분 + @else + 실패 + @endif + + @if($inquiry->has_issue) + {{ $inquiry->total_issue_count }}건 + @else + 없음 + @endif + {{ $inquiry->user?->name ?? '-' }} +
+ + + +
+
+ + + +

조회 이력이 없습니다.

+

신규 조회 버튼을 눌러 신용평가를 조회하세요.

+
- - +
+ + + + + + + + + @@ -96,167 +268,65 @@ class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg transition @push('scripts') @endpush diff --git a/routes/web.php b/routes/web.php index 45b8a450..54916cf2 100644 --- a/routes/web.php +++ b/routes/web.php @@ -285,11 +285,16 @@ |-------------------------------------------------------------------------- */ Route::prefix('credit')->name('credit.')->group(function () { - // 조회 + // 조회 이력 목록 Route::get('/inquiry', [\App\Http\Controllers\Credit\CreditController::class, 'inquiry'])->name('inquiry.index'); Route::post('/inquiry/search', [\App\Http\Controllers\Credit\CreditController::class, 'search'])->name('inquiry.search'); Route::post('/inquiry/test', [\App\Http\Controllers\Credit\CreditController::class, 'testConnection'])->name('inquiry.test'); + // 조회 이력 상세 데이터 + Route::get('/inquiry/{inquiryKey}/raw', [\App\Http\Controllers\Credit\CreditController::class, 'getRawData'])->name('inquiry.raw'); + Route::get('/inquiry/{inquiryKey}/report', [\App\Http\Controllers\Credit\CreditController::class, 'getReportData'])->name('inquiry.report'); + Route::delete('/inquiry/{id}', [\App\Http\Controllers\Credit\CreditController::class, 'deleteInquiry'])->name('inquiry.destroy'); + // 설정 관리 Route::get('/settings', [\App\Http\Controllers\Credit\CreditController::class, 'settings'])->name('settings.index'); Route::get('/settings/create', [\App\Http\Controllers\Credit\CreditController::class, 'createConfig'])->name('settings.create');