feat:쿠콘 API 신용평가 조회 기능 구현
- CooconConfig 모델 및 마이그레이션 추가 - CooconService 클래스 구현 (OA12~OA17 API) - CreditController 확장 (설정 관리, 조회 기능) - 설정 관리 화면 추가 (CRUD, 활성화 토글) - 사업자번호 조회 화면 업데이트 (API 연동) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,9 @@
|
||||
namespace App\Http\Controllers\Credit;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Coocon\CooconConfig;
|
||||
use App\Services\Coocon\CooconService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\View\View;
|
||||
@@ -22,6 +25,220 @@ public function inquiry(Request $request): View|Response
|
||||
return response('', 200)->header('HX-Redirect', route('credit.inquiry.index'));
|
||||
}
|
||||
|
||||
return view('credit.inquiry.index');
|
||||
$service = new CooconService();
|
||||
$hasConfig = $service->hasConfig();
|
||||
|
||||
return view('credit.inquiry.index', [
|
||||
'hasConfig' => $hasConfig,
|
||||
'apiTypes' => CooconService::API_NAMES,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 신용정보 조회 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');
|
||||
|
||||
$service = new CooconService();
|
||||
|
||||
if (!$service->hasConfig()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '쿠콘 API 설정이 없습니다. 설정을 먼저 등록해주세요.',
|
||||
], 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),
|
||||
};
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $result,
|
||||
'company_key' => $companyKey,
|
||||
'api_type' => $apiType,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 설정 관리 페이지
|
||||
*/
|
||||
public function settings(Request $request): View|Response
|
||||
{
|
||||
if ($request->header('HX-Request')) {
|
||||
return response('', 200)->header('HX-Redirect', route('credit.settings.index'));
|
||||
}
|
||||
|
||||
$configs = CooconConfig::orderBy('environment')
|
||||
->orderBy('is_active', 'desc')
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
|
||||
return view('credit.settings.index', [
|
||||
'configs' => $configs,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 설정 생성 폼
|
||||
*/
|
||||
public function createConfig(): View
|
||||
{
|
||||
return view('credit.settings.create');
|
||||
}
|
||||
|
||||
/**
|
||||
* 설정 저장
|
||||
*/
|
||||
public function storeConfig(Request $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:100',
|
||||
'environment' => 'required|in:test,production',
|
||||
'api_key' => 'required|string|max:100',
|
||||
'base_url' => 'required|url|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'is_active' => 'boolean',
|
||||
]);
|
||||
|
||||
// 같은 환경에서 활성화된 설정이 이미 있으면 비활성화
|
||||
if ($validated['is_active'] ?? false) {
|
||||
CooconConfig::where('environment', $validated['environment'])
|
||||
->where('is_active', true)
|
||||
->update(['is_active' => false]);
|
||||
}
|
||||
|
||||
$config = CooconConfig::create($validated);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '설정이 저장되었습니다.',
|
||||
'data' => $config,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 설정 수정 폼
|
||||
*/
|
||||
public function editConfig(int $id): View
|
||||
{
|
||||
$config = CooconConfig::findOrFail($id);
|
||||
|
||||
return view('credit.settings.edit', [
|
||||
'config' => $config,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 설정 업데이트
|
||||
*/
|
||||
public function updateConfig(Request $request, int $id): JsonResponse
|
||||
{
|
||||
$config = CooconConfig::findOrFail($id);
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:100',
|
||||
'environment' => 'required|in:test,production',
|
||||
'api_key' => 'required|string|max:100',
|
||||
'base_url' => 'required|url|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'is_active' => 'boolean',
|
||||
]);
|
||||
|
||||
// 같은 환경에서 활성화된 설정이 이미 있으면 비활성화 (자기 자신 제외)
|
||||
if ($validated['is_active'] ?? false) {
|
||||
CooconConfig::where('environment', $validated['environment'])
|
||||
->where('is_active', true)
|
||||
->where('id', '!=', $id)
|
||||
->update(['is_active' => false]);
|
||||
}
|
||||
|
||||
$config->update($validated);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '설정이 수정되었습니다.',
|
||||
'data' => $config,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 설정 삭제
|
||||
*/
|
||||
public function deleteConfig(int $id): JsonResponse
|
||||
{
|
||||
$config = CooconConfig::findOrFail($id);
|
||||
$config->delete();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '설정이 삭제되었습니다.',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 설정 활성화/비활성화 토글
|
||||
*/
|
||||
public function toggleConfig(int $id): JsonResponse
|
||||
{
|
||||
$config = CooconConfig::findOrFail($id);
|
||||
|
||||
if (!$config->is_active) {
|
||||
// 같은 환경에서 활성화된 설정이 이미 있으면 비활성화
|
||||
CooconConfig::where('environment', $config->environment)
|
||||
->where('is_active', true)
|
||||
->update(['is_active' => false]);
|
||||
}
|
||||
|
||||
$config->update(['is_active' => !$config->is_active]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => $config->is_active ? '설정이 활성화되었습니다.' : '설정이 비활성화되었습니다.',
|
||||
'is_active' => $config->is_active,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* API 연결 테스트
|
||||
*/
|
||||
public function testConnection(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'company_key' => 'required|string|max:20',
|
||||
]);
|
||||
|
||||
$companyKey = $request->input('company_key');
|
||||
$service = new CooconService();
|
||||
|
||||
if (!$service->hasConfig()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '쿠콘 API 설정이 없습니다.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
// 신용요약정보 API로 테스트
|
||||
$result = $service->getCreditSummary($companyKey);
|
||||
|
||||
return response()->json([
|
||||
'success' => $result['success'],
|
||||
'message' => $result['success'] ? 'API 연결 테스트 성공' : 'API 연결 테스트 실패',
|
||||
'result' => $result,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
107
app/Models/Coocon/CooconConfig.php
Normal file
107
app/Models/Coocon/CooconConfig.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Coocon;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* 쿠콘 API 설정 모델
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $environment
|
||||
* @property string $api_key
|
||||
* @property string $base_url
|
||||
* @property string|null $description
|
||||
* @property bool $is_active
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property \Carbon\Carbon|null $deleted_at
|
||||
*/
|
||||
class CooconConfig extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'environment',
|
||||
'api_key',
|
||||
'base_url',
|
||||
'description',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* 활성화된 테스트 서버 설정 조회
|
||||
*/
|
||||
public static function getActiveTest(): ?self
|
||||
{
|
||||
return self::where('environment', 'test')
|
||||
->where('is_active', true)
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* 활성화된 운영 서버 설정 조회
|
||||
*/
|
||||
public static function getActiveProduction(): ?self
|
||||
{
|
||||
return self::where('environment', 'production')
|
||||
->where('is_active', true)
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 환경에 맞는 활성 설정 조회
|
||||
*/
|
||||
public static function getActive(bool $isTestMode = false): ?self
|
||||
{
|
||||
return $isTestMode ? self::getActiveTest() : self::getActiveProduction();
|
||||
}
|
||||
|
||||
/**
|
||||
* 환경 라벨
|
||||
*/
|
||||
public function getEnvironmentLabelAttribute(): string
|
||||
{
|
||||
return match ($this->environment) {
|
||||
'test' => '테스트서버',
|
||||
'production' => '운영서버',
|
||||
default => $this->environment,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 상태 라벨
|
||||
*/
|
||||
public function getStatusLabelAttribute(): string
|
||||
{
|
||||
return $this->is_active ? '활성' : '비활성';
|
||||
}
|
||||
|
||||
/**
|
||||
* 상태 색상 (Tailwind)
|
||||
*/
|
||||
public function getStatusColorAttribute(): string
|
||||
{
|
||||
return $this->is_active ? 'green' : 'gray';
|
||||
}
|
||||
|
||||
/**
|
||||
* 마스킹된 API 키 (앞 8자리만 표시)
|
||||
*/
|
||||
public function getMaskedApiKeyAttribute(): string
|
||||
{
|
||||
if (strlen($this->api_key) <= 8) {
|
||||
return $this->api_key;
|
||||
}
|
||||
|
||||
return substr($this->api_key, 0, 8) . str_repeat('*', 8) . '...';
|
||||
}
|
||||
}
|
||||
311
app/Services/Coocon/CooconService.php
Normal file
311
app/Services/Coocon/CooconService.php
Normal file
@@ -0,0 +1,311 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Coocon;
|
||||
|
||||
use App\Models\Coocon\CooconConfig;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* 쿠콘 API 서비스
|
||||
* 나이스평가정보 연동 API
|
||||
*/
|
||||
class CooconService
|
||||
{
|
||||
private ?CooconConfig $config = null;
|
||||
private bool $isTestMode = true;
|
||||
|
||||
/**
|
||||
* API ID 상수
|
||||
*/
|
||||
public const API_CREDIT_SUMMARY = 'OA12'; // 신용요약정보
|
||||
public const API_SHORT_TERM_OVERDUE = 'OA13'; // 단기연체정보 (한국신용정보원)
|
||||
public const API_NEGATIVE_INFO_KCI = 'OA14'; // 신용도판단정보 (한국신용정보원)
|
||||
public const API_NEGATIVE_INFO_CB = 'OA15'; // 신용도판단정보 (신용정보사)
|
||||
public const API_SUSPENSION_INFO = 'OA16'; // 당좌거래정지정보 (금융결제원)
|
||||
public const API_WORKOUT_INFO = 'OA17'; // 법정관리/워크아웃정보
|
||||
|
||||
/**
|
||||
* API 이름 목록
|
||||
*/
|
||||
public const API_NAMES = [
|
||||
self::API_CREDIT_SUMMARY => '신용요약정보',
|
||||
self::API_SHORT_TERM_OVERDUE => '단기연체정보 (한국신용정보원)',
|
||||
self::API_NEGATIVE_INFO_KCI => '신용도판단정보 (한국신용정보원)',
|
||||
self::API_NEGATIVE_INFO_CB => '신용도판단정보 (신용정보사)',
|
||||
self::API_SUSPENSION_INFO => '당좌거래정지정보 (금융결제원)',
|
||||
self::API_WORKOUT_INFO => '법정관리/워크아웃정보',
|
||||
];
|
||||
|
||||
/**
|
||||
* 기본 URL
|
||||
*/
|
||||
private const BASE_URL_TEST = 'https://dev2.coocon.co.kr:8443/sol/gateway/oapi_relay.jsp';
|
||||
private const BASE_URL_PRODUCTION = 'https://sgw.coocon.co.kr/sol/gateway/oapi_relay.jsp';
|
||||
|
||||
public function __construct(bool $isTestMode = true)
|
||||
{
|
||||
$this->isTestMode = $isTestMode;
|
||||
$this->loadConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 설정 로드
|
||||
*/
|
||||
private function loadConfig(): void
|
||||
{
|
||||
$this->config = CooconConfig::getActive($this->isTestMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 설정 재로드
|
||||
*/
|
||||
public function reloadConfig(): self
|
||||
{
|
||||
$this->loadConfig();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 테스트 모드 설정
|
||||
*/
|
||||
public function setTestMode(bool $isTestMode): self
|
||||
{
|
||||
$this->isTestMode = $isTestMode;
|
||||
$this->loadConfig();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 설정 존재 여부
|
||||
*/
|
||||
public function hasConfig(): bool
|
||||
{
|
||||
return $this->config !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 설정 조회
|
||||
*/
|
||||
public function getConfig(): ?CooconConfig
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* API URL 조회
|
||||
*/
|
||||
private function getBaseUrl(): string
|
||||
{
|
||||
if ($this->config && $this->config->base_url) {
|
||||
return $this->config->base_url;
|
||||
}
|
||||
|
||||
return $this->isTestMode ? self::BASE_URL_TEST : self::BASE_URL_PRODUCTION;
|
||||
}
|
||||
|
||||
/**
|
||||
* API 호출 공통 메서드
|
||||
*/
|
||||
private function callApi(string $apiId, array $params = []): array
|
||||
{
|
||||
if (!$this->config) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => '쿠콘 API 설정이 없습니다. 설정을 먼저 등록해주세요.',
|
||||
'code' => 'NO_CONFIG',
|
||||
];
|
||||
}
|
||||
|
||||
$url = $this->getBaseUrl();
|
||||
$trSeq = $this->generateTransactionSequence();
|
||||
|
||||
$requestData = array_merge([
|
||||
'API_KEY' => $this->config->api_key,
|
||||
'API_ID' => $apiId,
|
||||
'TR_SEQ' => $trSeq,
|
||||
], $params);
|
||||
|
||||
Log::info('쿠콘 API 호출', [
|
||||
'api_id' => $apiId,
|
||||
'tr_seq' => $trSeq,
|
||||
'url' => $url,
|
||||
'params' => array_merge($params, ['API_KEY' => '***masked***']),
|
||||
]);
|
||||
|
||||
try {
|
||||
$response = Http::timeout(30)
|
||||
->withHeaders([
|
||||
'Content-Type' => 'application/json',
|
||||
])
|
||||
->post($url, $requestData);
|
||||
|
||||
$result = $response->json();
|
||||
|
||||
Log::info('쿠콘 API 응답', [
|
||||
'api_id' => $apiId,
|
||||
'tr_seq' => $trSeq,
|
||||
'rslt_cd' => $result['RSLT_CD'] ?? 'N/A',
|
||||
'rslt_msg' => $result['RSLT_MSG'] ?? 'N/A',
|
||||
]);
|
||||
|
||||
if (!$response->successful()) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'HTTP 오류: ' . $response->status(),
|
||||
'code' => 'HTTP_ERROR',
|
||||
'http_status' => $response->status(),
|
||||
];
|
||||
}
|
||||
|
||||
$rsltCode = $result['RSLT_CD'] ?? '';
|
||||
if ($rsltCode === '00000000') {
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => $result['RSLT_DATA'] ?? [],
|
||||
'tr_seq' => $result['TR_SEQ'] ?? $trSeq,
|
||||
'message' => $result['RSLT_MSG'] ?? '정상처리되었습니다.',
|
||||
'raw' => $result,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $result['RSLT_MSG'] ?? '알 수 없는 오류',
|
||||
'code' => $rsltCode,
|
||||
'raw' => $result,
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
Log::error('쿠콘 API 호출 실패', [
|
||||
'api_id' => $apiId,
|
||||
'tr_seq' => $trSeq,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => '쿠콘 API 호출 중 오류가 발생했습니다: ' . $e->getMessage(),
|
||||
'code' => 'EXCEPTION',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 거래일련번호 생성
|
||||
*/
|
||||
private function generateTransactionSequence(): string
|
||||
{
|
||||
return date('YmdHis') . substr(microtime(), 2, 6);
|
||||
}
|
||||
|
||||
/**
|
||||
* 신용요약정보 조회 (OA12)
|
||||
*
|
||||
* @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나
|
||||
*/
|
||||
public function getCreditSummary(string $companyKey): array
|
||||
{
|
||||
return $this->callApi(self::API_CREDIT_SUMMARY, [
|
||||
'Companykey' => $companyKey,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 단기연체정보 조회 (OA13) - 한국신용정보원
|
||||
*
|
||||
* @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나
|
||||
* @param string|null $reqDate 기준일자 (YYYYMMDD), 미입력시 현재 날짜
|
||||
*/
|
||||
public function getShortTermOverdueInfo(string $companyKey, ?string $reqDate = null): array
|
||||
{
|
||||
$params = ['Companykey' => $companyKey];
|
||||
|
||||
if ($reqDate) {
|
||||
$params['reqDate'] = $reqDate;
|
||||
}
|
||||
|
||||
return $this->callApi(self::API_SHORT_TERM_OVERDUE, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 신용도판단정보 조회 (OA14) - 한국신용정보원 (공공정보 포함)
|
||||
*
|
||||
* @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나
|
||||
*/
|
||||
public function getNegativeInfoKCI(string $companyKey): array
|
||||
{
|
||||
return $this->callApi(self::API_NEGATIVE_INFO_KCI, [
|
||||
'Companykey' => $companyKey,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 신용도판단정보 조회 (OA15) - 신용정보사
|
||||
*
|
||||
* @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나
|
||||
*/
|
||||
public function getNegativeInfoCB(string $companyKey): array
|
||||
{
|
||||
return $this->callApi(self::API_NEGATIVE_INFO_CB, [
|
||||
'Companykey' => $companyKey,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 당좌거래정지정보 조회 (OA16) - 금융결제원
|
||||
*
|
||||
* @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나
|
||||
*/
|
||||
public function getSuspensionInfo(string $companyKey): array
|
||||
{
|
||||
return $this->callApi(self::API_SUSPENSION_INFO, [
|
||||
'Companykey' => $companyKey,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 법정관리/워크아웃정보 조회 (OA17)
|
||||
*
|
||||
* @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나
|
||||
* @param int $pageNo 페이지 번호
|
||||
* @param int $pageSize 페이지 사이즈
|
||||
*/
|
||||
public function getWorkoutInfo(string $companyKey, int $pageNo = 1, int $pageSize = 10): array
|
||||
{
|
||||
return $this->callApi(self::API_WORKOUT_INFO, [
|
||||
'Companykey' => $companyKey,
|
||||
'pageNo' => (string) $pageNo,
|
||||
'pageSize' => (string) $pageSize,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 전체 신용정보 조회 (모든 API 호출)
|
||||
*
|
||||
* @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나
|
||||
*/
|
||||
public function getAllCreditInfo(string $companyKey): array
|
||||
{
|
||||
$results = [];
|
||||
|
||||
// 신용요약정보
|
||||
$results['summary'] = $this->getCreditSummary($companyKey);
|
||||
|
||||
// 단기연체정보
|
||||
$results['shortTermOverdue'] = $this->getShortTermOverdueInfo($companyKey);
|
||||
|
||||
// 신용도판단정보 (한국신용정보원)
|
||||
$results['negativeInfoKCI'] = $this->getNegativeInfoKCI($companyKey);
|
||||
|
||||
// 신용도판단정보 (신용정보사)
|
||||
$results['negativeInfoCB'] = $this->getNegativeInfoCB($companyKey);
|
||||
|
||||
// 당좌거래정지정보
|
||||
$results['suspensionInfo'] = $this->getSuspensionInfo($companyKey);
|
||||
|
||||
// 법정관리/워크아웃정보
|
||||
$results['workoutInfo'] = $this->getWorkoutInfo($companyKey);
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('coocon_configs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name', 100)->comment('설정 이름');
|
||||
$table->enum('environment', ['test', 'production'])->default('test')->comment('환경');
|
||||
$table->string('api_key', 100)->comment('API 키');
|
||||
$table->string('base_url', 255)->comment('API 기본 URL');
|
||||
$table->text('description')->nullable()->comment('설명');
|
||||
$table->boolean('is_active')->default(false)->comment('활성화 여부');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('coocon_configs');
|
||||
}
|
||||
};
|
||||
@@ -8,40 +8,75 @@
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-6 flex-shrink-0">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-800">신용평가 조회</h1>
|
||||
<p class="text-sm text-gray-500 mt-1">기업 신용평가 정보를 조회합니다</p>
|
||||
<p class="text-sm text-gray-500 mt-1">기업 신용평가 정보를 조회합니다 (쿠콘 API)</p>
|
||||
</div>
|
||||
<a href="{{ route('credit.settings.index') }}"
|
||||
class="inline-flex items-center gap-2 px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
API 설정
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if(!$hasConfig)
|
||||
<!-- API 설정 없음 경고 -->
|
||||
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-6 flex-shrink-0">
|
||||
<div class="flex items-center gap-3">
|
||||
<svg class="w-6 h-6 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
<div>
|
||||
<p class="font-medium text-yellow-800">쿠콘 API 설정이 필요합니다</p>
|
||||
<p class="text-sm text-yellow-700">신용평가 조회를 위해 먼저 <a href="{{ route('credit.settings.index') }}" class="underline font-medium">API 설정</a>을 등록해주세요.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- 조회 폼 -->
|
||||
<div class="bg-white rounded-lg shadow-sm p-6 mb-6 flex-shrink-0">
|
||||
<form id="inquiryForm" class="flex flex-wrap gap-4 items-end">
|
||||
<!-- 사업자번호 입력 -->
|
||||
<div class="flex-1 min-w-[200px]">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">사업자번호</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">사업자번호 / 법인번호 <span class="text-red-500">*</span></label>
|
||||
<input type="text"
|
||||
name="biz_no"
|
||||
id="bizNoInput"
|
||||
placeholder="000-00-00000"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
name="company_key"
|
||||
id="companyKeyInput"
|
||||
placeholder="사업자번호 또는 법인번호 입력"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
{{ !$hasConfig ? 'disabled' : '' }}>
|
||||
</div>
|
||||
|
||||
<!-- 상호명 입력 -->
|
||||
<div class="flex-1 min-w-[200px]">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">상호명</label>
|
||||
<input type="text"
|
||||
name="corp_name"
|
||||
id="corpNameInput"
|
||||
placeholder="회사명 입력"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<!-- API 유형 선택 -->
|
||||
<div class="min-w-[200px]">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">조회 항목</label>
|
||||
<select name="api_type"
|
||||
id="apiTypeSelect"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
{{ !$hasConfig ? 'disabled' : '' }}>
|
||||
<option value="all">전체 조회</option>
|
||||
@foreach($apiTypes as $code => $name)
|
||||
<option value="{{ $code }}">{{ $name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 조회 버튼 -->
|
||||
<div>
|
||||
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg transition flex items-center gap-2">
|
||||
<button type="submit"
|
||||
id="btnSearch"
|
||||
class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg transition flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
{{ !$hasConfig ? 'disabled' : '' }}>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
조회
|
||||
<span id="btnSearchText">조회</span>
|
||||
<svg id="searchSpinner" class="w-5 h-5 animate-spin hidden" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -50,12 +85,17 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc
|
||||
<!-- 조회 결과 영역 -->
|
||||
<div id="result-container" class="bg-white rounded-lg shadow-sm flex-1 min-h-0 overflow-auto">
|
||||
<!-- 초기 안내 메시지 -->
|
||||
<div class="flex flex-col items-center justify-center h-full text-gray-400 p-12">
|
||||
<div id="initial-message" class="flex flex-col items-center justify-center h-full text-gray-400 p-12">
|
||||
<svg class="w-16 h-16 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
<p class="text-lg font-medium">신용평가 조회</p>
|
||||
<p class="text-sm mt-1">사업자번호 또는 상호명을 입력하여 조회하세요</p>
|
||||
<p class="text-sm mt-1">사업자번호 또는 법인번호를 입력하여 조회하세요</p>
|
||||
</div>
|
||||
|
||||
<!-- 결과 표시 영역 -->
|
||||
<div id="result-content" class="hidden p-6">
|
||||
<!-- 결과가 여기에 렌더링됨 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -63,19 +103,297 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.getElementById('inquiryForm').addEventListener('submit', function(e) {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const form = document.getElementById('inquiryForm');
|
||||
const resultContainer = document.getElementById('result-container');
|
||||
const initialMessage = document.getElementById('initial-message');
|
||||
const resultContent = document.getElementById('result-content');
|
||||
const btnSearch = document.getElementById('btnSearch');
|
||||
const btnSearchText = document.getElementById('btnSearchText');
|
||||
const searchSpinner = document.getElementById('searchSpinner');
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
|
||||
|
||||
// API 이름 매핑
|
||||
const apiNames = @json($apiTypes);
|
||||
|
||||
// 조회 폼 제출
|
||||
form.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const bizNo = document.getElementById('bizNoInput').value.trim();
|
||||
const corpName = document.getElementById('corpNameInput').value.trim();
|
||||
const companyKey = document.getElementById('companyKeyInput').value.trim();
|
||||
const apiType = document.getElementById('apiTypeSelect').value;
|
||||
|
||||
if (!bizNo && !corpName) {
|
||||
showToast('사업자번호 또는 상호명을 입력해주세요.', 'warning');
|
||||
if (!companyKey) {
|
||||
showToast('사업자번호 또는 법인번호를 입력해주세요.', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: 신용평가 조회 API 연동
|
||||
showToast('신용평가 조회 기능은 준비 중입니다.', 'info');
|
||||
// 로딩 상태
|
||||
btnSearch.disabled = true;
|
||||
btnSearchText.textContent = '조회 중...';
|
||||
searchSpinner.classList.remove('hidden');
|
||||
|
||||
try {
|
||||
const response = await fetch('{{ route('credit.inquiry.search') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': csrfToken,
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
company_key: companyKey,
|
||||
api_type: apiType,
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
renderResults(result.data, companyKey, apiType);
|
||||
showToast('조회가 완료되었습니다.', 'success');
|
||||
} else {
|
||||
showToast(result.error || '조회에 실패했습니다.', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('조회 오류:', error);
|
||||
showToast('조회 중 오류가 발생했습니다.', 'error');
|
||||
} finally {
|
||||
btnSearch.disabled = false;
|
||||
btnSearchText.textContent = '조회';
|
||||
searchSpinner.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// 결과 렌더링
|
||||
function renderResults(data, companyKey, apiType) {
|
||||
initialMessage.classList.add('hidden');
|
||||
resultContent.classList.remove('hidden');
|
||||
|
||||
let html = `
|
||||
<div class="mb-4 pb-4 border-b">
|
||||
<h3 class="text-lg font-semibold text-gray-800">조회 결과</h3>
|
||||
<p class="text-sm text-gray-500">사업자/법인번호: <span class="font-mono font-medium">${companyKey}</span></p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 각 API 결과를 탭 형태로 표시
|
||||
const tabs = [];
|
||||
const contents = [];
|
||||
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
const apiName = getApiName(key);
|
||||
const isSuccess = value.success;
|
||||
const tabId = `tab-${key}`;
|
||||
|
||||
tabs.push(`
|
||||
<button type="button"
|
||||
class="api-tab px-4 py-2 text-sm font-medium rounded-t-lg border-b-2 transition
|
||||
${tabs.length === 0 ? 'border-blue-600 text-blue-600 bg-blue-50' : 'border-transparent text-gray-500 hover:text-gray-700 hover:bg-gray-50'}"
|
||||
data-tab="${tabId}">
|
||||
<span class="flex items-center gap-1">
|
||||
${isSuccess ? '<span class="w-2 h-2 bg-green-500 rounded-full"></span>' : '<span class="w-2 h-2 bg-red-500 rounded-full"></span>'}
|
||||
${apiName}
|
||||
</span>
|
||||
</button>
|
||||
`);
|
||||
|
||||
contents.push(`
|
||||
<div id="${tabId}" class="api-content ${contents.length === 0 ? '' : 'hidden'}">
|
||||
${renderApiResult(key, value)}
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
html += `
|
||||
<div class="border-b mb-4">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
${tabs.join('')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="api-contents">
|
||||
${contents.join('')}
|
||||
</div>
|
||||
`;
|
||||
|
||||
resultContent.innerHTML = html;
|
||||
|
||||
// 탭 클릭 이벤트
|
||||
resultContent.querySelectorAll('.api-tab').forEach(tab => {
|
||||
tab.addEventListener('click', function() {
|
||||
const tabId = this.dataset.tab;
|
||||
|
||||
// 모든 탭 비활성화
|
||||
resultContent.querySelectorAll('.api-tab').forEach(t => {
|
||||
t.classList.remove('border-blue-600', 'text-blue-600', 'bg-blue-50');
|
||||
t.classList.add('border-transparent', 'text-gray-500');
|
||||
});
|
||||
|
||||
// 클릭한 탭 활성화
|
||||
this.classList.remove('border-transparent', 'text-gray-500');
|
||||
this.classList.add('border-blue-600', 'text-blue-600', 'bg-blue-50');
|
||||
|
||||
// 모든 컨텐츠 숨기기
|
||||
resultContent.querySelectorAll('.api-content').forEach(c => c.classList.add('hidden'));
|
||||
|
||||
// 선택한 컨텐츠 표시
|
||||
document.getElementById(tabId).classList.remove('hidden');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// API 이름 가져오기
|
||||
function getApiName(key) {
|
||||
const nameMap = {
|
||||
'summary': '신용요약정보 (OA12)',
|
||||
'shortTermOverdue': '단기연체정보 (OA13)',
|
||||
'negativeInfoKCI': '신용도판단정보-한국신용정보원 (OA14)',
|
||||
'negativeInfoCB': '신용도판단정보-신용정보사 (OA15)',
|
||||
'suspensionInfo': '당좌거래정지정보 (OA16)',
|
||||
'workoutInfo': '법정관리/워크아웃정보 (OA17)',
|
||||
};
|
||||
return nameMap[key] || key;
|
||||
}
|
||||
|
||||
// API 결과 렌더링
|
||||
function renderApiResult(key, result) {
|
||||
if (!result.success) {
|
||||
return `
|
||||
<div class="bg-red-50 border border-red-200 rounded-lg p-4">
|
||||
<div class="flex items-center gap-2 text-red-800">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="font-medium">조회 실패</span>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-red-700">${result.error || '알 수 없는 오류가 발생했습니다.'}</p>
|
||||
${result.code ? `<p class="text-xs text-red-600 mt-1">오류 코드: ${result.code}</p>` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
const data = result.data;
|
||||
|
||||
// 데이터가 없는 경우
|
||||
if (!data || (Array.isArray(data) && data.length === 0) || Object.keys(data).length === 0) {
|
||||
return `
|
||||
<div class="bg-gray-50 border border-gray-200 rounded-lg p-4 text-center">
|
||||
<svg class="w-12 h-12 text-gray-400 mx-auto mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
<p class="text-gray-600">조회된 데이터가 없습니다.</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 성공 메시지와 데이터 표시
|
||||
let html = `
|
||||
<div class="bg-green-50 border border-green-200 rounded-lg p-3 mb-4">
|
||||
<div class="flex items-center gap-2 text-green-800">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="font-medium">조회 성공</span>
|
||||
</div>
|
||||
${result.message ? `<p class="text-sm text-green-700 mt-1">${result.message}</p>` : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 데이터 테이블 또는 JSON 표시
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
html += renderDataTable(data);
|
||||
} else if (typeof data === 'object') {
|
||||
html += renderDataObject(data);
|
||||
}
|
||||
|
||||
// Raw 데이터 토글
|
||||
html += `
|
||||
<div class="mt-4">
|
||||
<button type="button" class="toggle-raw text-sm text-gray-500 hover:text-gray-700 flex items-center gap-1">
|
||||
<svg class="w-4 h-4 transform transition" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
원본 데이터 보기
|
||||
</button>
|
||||
<div class="raw-data hidden mt-2">
|
||||
<pre class="bg-gray-900 text-gray-100 rounded-lg p-4 text-xs overflow-auto max-h-64">${JSON.stringify(result.raw || result, null, 2)}</pre>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
// 데이터 테이블 렌더링 (배열)
|
||||
function renderDataTable(data) {
|
||||
if (data.length === 0) return '';
|
||||
|
||||
const headers = Object.keys(data[0]);
|
||||
|
||||
let html = `
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
${headers.map(h => `<th class="px-3 py-2 text-left font-medium text-gray-700">${h}</th>`).join('')}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y">
|
||||
`;
|
||||
|
||||
data.forEach(row => {
|
||||
html += '<tr class="hover:bg-gray-50">';
|
||||
headers.forEach(h => {
|
||||
const value = row[h];
|
||||
html += `<td class="px-3 py-2 text-gray-600">${formatValue(value)}</td>`;
|
||||
});
|
||||
html += '</tr>';
|
||||
});
|
||||
|
||||
html += `
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
// 데이터 오브젝트 렌더링
|
||||
function renderDataObject(data) {
|
||||
let html = '<div class="space-y-2">';
|
||||
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
html += `
|
||||
<div class="flex border-b pb-2">
|
||||
<div class="w-1/3 text-sm font-medium text-gray-700">${key}</div>
|
||||
<div class="w-2/3 text-sm text-gray-600">${formatValue(value)}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
// 값 포맷팅
|
||||
function formatValue(value) {
|
||||
if (value === null || value === undefined) return '<span class="text-gray-400">-</span>';
|
||||
if (typeof value === 'object') return `<pre class="text-xs bg-gray-100 p-1 rounded">${JSON.stringify(value, null, 2)}</pre>`;
|
||||
return String(value);
|
||||
}
|
||||
|
||||
// Raw 데이터 토글 이벤트 위임
|
||||
resultContent.addEventListener('click', function(e) {
|
||||
if (e.target.closest('.toggle-raw')) {
|
||||
const btn = e.target.closest('.toggle-raw');
|
||||
const rawData = btn.nextElementSibling;
|
||||
const icon = btn.querySelector('svg');
|
||||
|
||||
rawData.classList.toggle('hidden');
|
||||
icon.classList.toggle('rotate-90');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
12
resources/views/credit/settings/edit.blade.php
Normal file
12
resources/views/credit/settings/edit.blade.php
Normal file
@@ -0,0 +1,12 @@
|
||||
{{-- 수정 폼에 사용할 데이터 (JavaScript에서 파싱) --}}
|
||||
<script type="application/json" id="config-data">
|
||||
{
|
||||
"id": {{ $config->id }},
|
||||
"name": "{{ $config->name }}",
|
||||
"environment": "{{ $config->environment }}",
|
||||
"api_key": "{{ $config->api_key }}",
|
||||
"base_url": "{{ $config->base_url }}",
|
||||
"description": "{{ $config->description ?? '' }}",
|
||||
"is_active": {{ $config->is_active ? 'true' : 'false' }}
|
||||
}
|
||||
</script>
|
||||
429
resources/views/credit/settings/index.blade.php
Normal file
429
resources/views/credit/settings/index.blade.php
Normal file
@@ -0,0 +1,429 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', '쿠콘 API 설정')
|
||||
|
||||
@section('content')
|
||||
<div class="flex flex-col h-full">
|
||||
<!-- 페이지 헤더 -->
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-6 flex-shrink-0">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-800">쿠콘 API 설정</h1>
|
||||
<p class="text-sm text-gray-500 mt-1">신용평가 API 연동을 위한 쿠콘 설정을 관리합니다</p>
|
||||
</div>
|
||||
<button type="button"
|
||||
id="btn-add"
|
||||
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition flex items-center gap-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
새 설정 추가
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 설정 목록 -->
|
||||
<div class="bg-white rounded-lg shadow-sm flex-1 overflow-hidden">
|
||||
@if($configs->isEmpty())
|
||||
<div class="flex flex-col items-center justify-center h-full text-gray-400 p-12">
|
||||
<svg class="w-16 h-16 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
<p class="text-lg font-medium">등록된 설정이 없습니다</p>
|
||||
<p class="text-sm mt-1">새 설정을 추가하여 쿠콘 API를 연동하세요</p>
|
||||
</div>
|
||||
@else
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead class="bg-gray-50 border-b">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">설정명</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">환경</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">API Key</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">상태</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">등록일</th>
|
||||
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">관리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
@foreach($configs as $config)
|
||||
<tr class="hover:bg-gray-50" data-id="{{ $config->id }}">
|
||||
<td class="px-6 py-4">
|
||||
<div>
|
||||
<div class="font-medium text-gray-900">{{ $config->name }}</div>
|
||||
@if($config->description)
|
||||
<div class="text-sm text-gray-500">{{ Str::limit($config->description, 50) }}</div>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
|
||||
{{ $config->environment === 'production' ? 'bg-red-100 text-red-800' : 'bg-blue-100 text-blue-800' }}">
|
||||
{{ $config->environment_label }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<code class="text-sm text-gray-600 bg-gray-100 px-2 py-1 rounded">{{ $config->masked_api_key }}</code>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
|
||||
{{ $config->is_active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800' }}">
|
||||
{{ $config->status_label }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">
|
||||
{{ $config->created_at->format('Y-m-d H:i') }}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-right">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button type="button"
|
||||
class="btn-toggle p-1.5 text-gray-500 hover:text-{{ $config->is_active ? 'gray' : 'green' }}-600 hover:bg-gray-100 rounded-lg transition"
|
||||
data-id="{{ $config->id }}"
|
||||
title="{{ $config->is_active ? '비활성화' : '활성화' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@if($config->is_active)
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
|
||||
@else
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
@endif
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn-edit p-1.5 text-gray-500 hover:text-blue-600 hover:bg-gray-100 rounded-lg transition"
|
||||
data-id="{{ $config->id }}"
|
||||
title="수정">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn-delete p-1.5 text-gray-500 hover:text-red-600 hover:bg-gray-100 rounded-lg transition"
|
||||
data-id="{{ $config->id }}"
|
||||
title="삭제">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 설정 추가/수정 모달 -->
|
||||
<div id="config-modal" class="fixed inset-0 z-50 hidden">
|
||||
<div class="fixed inset-0 bg-black/50" id="modal-backdrop"></div>
|
||||
<div class="fixed inset-0 flex items-center justify-center p-4">
|
||||
<div class="bg-white rounded-xl shadow-xl w-full max-w-lg relative">
|
||||
<div class="flex items-center justify-between p-6 border-b">
|
||||
<h3 class="text-lg font-semibold text-gray-800" id="modal-title">새 설정 추가</h3>
|
||||
<button type="button" id="btn-modal-close" class="text-gray-400 hover:text-gray-600 transition">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<form id="config-form">
|
||||
<input type="hidden" id="config-id" name="id">
|
||||
<div class="p-6 space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">설정명 <span class="text-red-500">*</span></label>
|
||||
<input type="text"
|
||||
id="config-name"
|
||||
name="name"
|
||||
required
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="예: 테스트 서버 설정">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">환경 <span class="text-red-500">*</span></label>
|
||||
<select id="config-environment"
|
||||
name="environment"
|
||||
required
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<option value="test">테스트서버</option>
|
||||
<option value="production">운영서버</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">API Key <span class="text-red-500">*</span></label>
|
||||
<input type="text"
|
||||
id="config-api-key"
|
||||
name="api_key"
|
||||
required
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="쿠콘에서 발급받은 API Key">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Base URL <span class="text-red-500">*</span></label>
|
||||
<input type="url"
|
||||
id="config-base-url"
|
||||
name="base_url"
|
||||
required
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="https://dev2.coocon.co.kr:8443/sol/gateway/oapi_relay.jsp">
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
테스트: https://dev2.coocon.co.kr:8443/sol/gateway/oapi_relay.jsp<br>
|
||||
운영: https://sgw.coocon.co.kr/sol/gateway/oapi_relay.jsp
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">설명</label>
|
||||
<textarea id="config-description"
|
||||
name="description"
|
||||
rows="3"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="설정에 대한 설명 (선택사항)"></textarea>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="checkbox"
|
||||
id="config-is-active"
|
||||
name="is_active"
|
||||
class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500">
|
||||
<label for="config-is-active" class="text-sm text-gray-700">활성화 (같은 환경에서 하나만 활성화 가능)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end gap-3 p-6 border-t bg-gray-50 rounded-b-xl">
|
||||
<button type="button"
|
||||
id="btn-cancel"
|
||||
class="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-100 transition">
|
||||
취소
|
||||
</button>
|
||||
<button type="submit"
|
||||
id="btn-submit"
|
||||
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition flex items-center gap-2">
|
||||
<svg class="w-4 h-4 hidden animate-spin" id="submit-spinner" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
저장
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 삭제 확인 모달 -->
|
||||
<div id="delete-modal" class="fixed inset-0 z-50 hidden">
|
||||
<div class="fixed inset-0 bg-black/50" id="delete-backdrop"></div>
|
||||
<div class="fixed inset-0 flex items-center justify-center p-4">
|
||||
<div class="bg-white rounded-xl shadow-xl w-full max-w-sm relative">
|
||||
<div class="p-6 text-center">
|
||||
<div class="w-12 h-12 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-2">설정 삭제</h3>
|
||||
<p class="text-gray-600 mb-6">이 설정을 삭제하시겠습니까?<br>삭제된 설정은 복구할 수 없습니다.</p>
|
||||
<input type="hidden" id="delete-id">
|
||||
<div class="flex gap-3">
|
||||
<button type="button"
|
||||
id="btn-delete-cancel"
|
||||
class="flex-1 px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-100 transition">
|
||||
취소
|
||||
</button>
|
||||
<button type="button"
|
||||
id="btn-delete-confirm"
|
||||
class="flex-1 px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition">
|
||||
삭제
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const modal = document.getElementById('config-modal');
|
||||
const deleteModal = document.getElementById('delete-modal');
|
||||
const form = document.getElementById('config-form');
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
|
||||
|
||||
// 모달 열기 (추가)
|
||||
document.getElementById('btn-add').addEventListener('click', function() {
|
||||
document.getElementById('modal-title').textContent = '새 설정 추가';
|
||||
document.getElementById('config-id').value = '';
|
||||
form.reset();
|
||||
modal.classList.remove('hidden');
|
||||
});
|
||||
|
||||
// 모달 닫기
|
||||
function closeModal() {
|
||||
modal.classList.add('hidden');
|
||||
}
|
||||
|
||||
document.getElementById('btn-modal-close').addEventListener('click', closeModal);
|
||||
document.getElementById('btn-cancel').addEventListener('click', closeModal);
|
||||
document.getElementById('modal-backdrop').addEventListener('click', closeModal);
|
||||
|
||||
// 수정 버튼
|
||||
document.querySelectorAll('.btn-edit').forEach(btn => {
|
||||
btn.addEventListener('click', async function() {
|
||||
const id = this.dataset.id;
|
||||
try {
|
||||
const response = await fetch(`{{ url('credit/settings') }}/${id}/edit`);
|
||||
const html = await response.text();
|
||||
|
||||
// 파싱하여 데이터 추출 (간단한 방법)
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
const data = JSON.parse(doc.getElementById('config-data')?.textContent || '{}');
|
||||
|
||||
document.getElementById('modal-title').textContent = '설정 수정';
|
||||
document.getElementById('config-id').value = data.id || id;
|
||||
document.getElementById('config-name').value = data.name || '';
|
||||
document.getElementById('config-environment').value = data.environment || 'test';
|
||||
document.getElementById('config-api-key').value = data.api_key || '';
|
||||
document.getElementById('config-base-url').value = data.base_url || '';
|
||||
document.getElementById('config-description').value = data.description || '';
|
||||
document.getElementById('config-is-active').checked = data.is_active || false;
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
} catch (error) {
|
||||
showToast('설정 정보를 불러오는데 실패했습니다.', 'error');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 삭제 버튼
|
||||
document.querySelectorAll('.btn-delete').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
document.getElementById('delete-id').value = this.dataset.id;
|
||||
deleteModal.classList.remove('hidden');
|
||||
});
|
||||
});
|
||||
|
||||
// 삭제 모달 닫기
|
||||
document.getElementById('btn-delete-cancel').addEventListener('click', function() {
|
||||
deleteModal.classList.add('hidden');
|
||||
});
|
||||
document.getElementById('delete-backdrop').addEventListener('click', function() {
|
||||
deleteModal.classList.add('hidden');
|
||||
});
|
||||
|
||||
// 삭제 확인
|
||||
document.getElementById('btn-delete-confirm').addEventListener('click', async function() {
|
||||
const id = document.getElementById('delete-id').value;
|
||||
try {
|
||||
const response = await fetch(`{{ url('credit/settings') }}/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': csrfToken,
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
showToast(result.message, 'success');
|
||||
setTimeout(() => location.reload(), 500);
|
||||
} else {
|
||||
showToast(result.error || '삭제에 실패했습니다.', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('삭제 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
deleteModal.classList.add('hidden');
|
||||
});
|
||||
|
||||
// 활성화/비활성화 토글
|
||||
document.querySelectorAll('.btn-toggle').forEach(btn => {
|
||||
btn.addEventListener('click', async function() {
|
||||
const id = this.dataset.id;
|
||||
try {
|
||||
const response = await fetch(`{{ url('credit/settings') }}/${id}/toggle`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': csrfToken,
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
showToast(result.message, 'success');
|
||||
setTimeout(() => location.reload(), 500);
|
||||
} else {
|
||||
showToast(result.error || '상태 변경에 실패했습니다.', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('상태 변경 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 폼 제출
|
||||
form.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const id = document.getElementById('config-id').value;
|
||||
const isEdit = !!id;
|
||||
const spinner = document.getElementById('submit-spinner');
|
||||
const submitBtn = document.getElementById('btn-submit');
|
||||
|
||||
spinner.classList.remove('hidden');
|
||||
submitBtn.disabled = true;
|
||||
|
||||
const formData = {
|
||||
name: document.getElementById('config-name').value,
|
||||
environment: document.getElementById('config-environment').value,
|
||||
api_key: document.getElementById('config-api-key').value,
|
||||
base_url: document.getElementById('config-base-url').value,
|
||||
description: document.getElementById('config-description').value,
|
||||
is_active: document.getElementById('config-is-active').checked,
|
||||
};
|
||||
|
||||
try {
|
||||
const url = isEdit ? `{{ url('credit/settings') }}/${id}` : '{{ route('credit.settings.store') }}';
|
||||
const method = isEdit ? 'PUT' : 'POST';
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': csrfToken,
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showToast(result.message, 'success');
|
||||
closeModal();
|
||||
setTimeout(() => location.reload(), 500);
|
||||
} else {
|
||||
showToast(result.error || '저장에 실패했습니다.', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('저장 중 오류가 발생했습니다.', 'error');
|
||||
} finally {
|
||||
spinner.classList.add('hidden');
|
||||
submitBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
// 환경 선택 시 기본 URL 자동 설정
|
||||
document.getElementById('config-environment').addEventListener('change', function() {
|
||||
const baseUrlInput = document.getElementById('config-base-url');
|
||||
if (!baseUrlInput.value) {
|
||||
if (this.value === 'test') {
|
||||
baseUrlInput.value = 'https://dev2.coocon.co.kr:8443/sol/gateway/oapi_relay.jsp';
|
||||
} else {
|
||||
baseUrlInput.value = 'https://sgw.coocon.co.kr/sol/gateway/oapi_relay.jsp';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@@ -285,7 +285,19 @@
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
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('/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');
|
||||
Route::post('/settings', [\App\Http\Controllers\Credit\CreditController::class, 'storeConfig'])->name('settings.store');
|
||||
Route::get('/settings/{id}/edit', [\App\Http\Controllers\Credit\CreditController::class, 'editConfig'])->name('settings.edit');
|
||||
Route::put('/settings/{id}', [\App\Http\Controllers\Credit\CreditController::class, 'updateConfig'])->name('settings.update');
|
||||
Route::delete('/settings/{id}', [\App\Http\Controllers\Credit\CreditController::class, 'deleteConfig'])->name('settings.destroy');
|
||||
Route::post('/settings/{id}/toggle', [\App\Http\Controllers\Credit\CreditController::class, 'toggleConfig'])->name('settings.toggle');
|
||||
});
|
||||
|
||||
// 대시보드
|
||||
|
||||
Reference in New Issue
Block a user