From 77812534914cc34c2cd842df99f94ed523699488 Mon Sep 17 00:00:00 2001 From: hskwon Date: Mon, 22 Dec 2025 15:30:38 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Phase=208.3=20=ED=9A=8C=EC=82=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사업자등록번호 유효성 검사 API (바로빌 연동) - 회사 추가 신청/승인/반려 워크플로우 - 신청 승인 시 테넌트 자동 생성 및 사용자 연결 - 관리자용 신청 목록/상세 조회 - 사용자용 내 신청 목록 조회 - Swagger 문서 및 i18n 메시지 추가 --- .../Controllers/Api/V1/CompanyController.php | 91 ++++ .../V1/Company/CheckBusinessNumberRequest.php | 28 ++ .../Company/CompanyRequestActionRequest.php | 20 + .../V1/Company/CompanyRequestIndexRequest.php | 29 ++ .../V1/Company/CompanyRequestStoreRequest.php | 34 ++ app/Models/CompanyRequest.php | 237 ++++++++++ app/Services/BarobillService.php | 142 ++++++ app/Services/CompanyService.php | 258 +++++++++++ app/Swagger/v1/CompanyApi.php | 419 ++++++++++++++++++ ...2_100001_create_company_requests_table.php | 45 ++ lang/ko/error.php | 9 + lang/ko/message.php | 8 + routes/api.php | 12 + 13 files changed, 1332 insertions(+) create mode 100644 app/Http/Controllers/Api/V1/CompanyController.php create mode 100644 app/Http/Requests/V1/Company/CheckBusinessNumberRequest.php create mode 100644 app/Http/Requests/V1/Company/CompanyRequestActionRequest.php create mode 100644 app/Http/Requests/V1/Company/CompanyRequestIndexRequest.php create mode 100644 app/Http/Requests/V1/Company/CompanyRequestStoreRequest.php create mode 100644 app/Models/CompanyRequest.php create mode 100644 app/Services/CompanyService.php create mode 100644 app/Swagger/v1/CompanyApi.php create mode 100644 database/migrations/2025_12_22_100001_create_company_requests_table.php diff --git a/app/Http/Controllers/Api/V1/CompanyController.php b/app/Http/Controllers/Api/V1/CompanyController.php new file mode 100644 index 0000000..a1d7d0d --- /dev/null +++ b/app/Http/Controllers/Api/V1/CompanyController.php @@ -0,0 +1,91 @@ +companyService->checkBusinessNumber( + $request->validated()['business_number'] + ); + + return ApiResponse::handle('message.company.checked', $result); + } + + /** + * 회사 추가 신청 + */ + public function request(CompanyRequestStoreRequest $request): JsonResponse + { + $result = $this->companyService->createRequest($request->validated()); + + return ApiResponse::handle('message.company.request_created', $result, 201); + } + + /** + * 회사 추가 신청 목록 (관리자용) + */ + public function requests(CompanyRequestIndexRequest $request): JsonResponse + { + $result = $this->companyService->getRequests($request->validated()); + + return ApiResponse::handle('message.fetched', $result); + } + + /** + * 회사 추가 신청 상세 + */ + public function showRequest(int $id): JsonResponse + { + $result = $this->companyService->getRequest($id); + + return ApiResponse::handle('message.fetched', $result); + } + + /** + * 회사 추가 신청 승인 + */ + public function approve(int $id): JsonResponse + { + $result = $this->companyService->approveRequest($id); + + return ApiResponse::handle('message.company.request_approved', $result); + } + + /** + * 회사 추가 신청 반려 + */ + public function reject(CompanyRequestActionRequest $request, int $id): JsonResponse + { + $result = $this->companyService->rejectRequest($id, $request->validated()['reason'] ?? null); + + return ApiResponse::handle('message.company.request_rejected', $result); + } + + /** + * 내 회사 추가 신청 목록 + */ + public function myRequests(CompanyRequestIndexRequest $request): JsonResponse + { + $result = $this->companyService->getMyRequests($request->validated()); + + return ApiResponse::handle('message.fetched', $result); + } +} diff --git a/app/Http/Requests/V1/Company/CheckBusinessNumberRequest.php b/app/Http/Requests/V1/Company/CheckBusinessNumberRequest.php new file mode 100644 index 0000000..435d0cf --- /dev/null +++ b/app/Http/Requests/V1/Company/CheckBusinessNumberRequest.php @@ -0,0 +1,28 @@ + 'required|string|min:10|max:13', + ]; + } + + public function messages(): array + { + return [ + 'business_number.required' => __('validation.required', ['attribute' => '사업자등록번호']), + 'business_number.min' => __('validation.min.string', ['attribute' => '사업자등록번호', 'min' => 10]), + ]; + } +} diff --git a/app/Http/Requests/V1/Company/CompanyRequestActionRequest.php b/app/Http/Requests/V1/Company/CompanyRequestActionRequest.php new file mode 100644 index 0000000..e3c5e01 --- /dev/null +++ b/app/Http/Requests/V1/Company/CompanyRequestActionRequest.php @@ -0,0 +1,20 @@ + 'nullable|string|max:500', + ]; + } +} diff --git a/app/Http/Requests/V1/Company/CompanyRequestIndexRequest.php b/app/Http/Requests/V1/Company/CompanyRequestIndexRequest.php new file mode 100644 index 0000000..cc3dd44 --- /dev/null +++ b/app/Http/Requests/V1/Company/CompanyRequestIndexRequest.php @@ -0,0 +1,29 @@ + ['nullable', Rule::in(CompanyRequest::STATUSES)], + 'search' => 'nullable|string|max:100', + 'start_date' => 'nullable|date', + 'end_date' => 'nullable|date|after_or_equal:start_date', + 'sort_by' => ['nullable', Rule::in(['created_at', 'company_name', 'status', 'processed_at'])], + 'sort_dir' => ['nullable', Rule::in(['asc', 'desc'])], + 'per_page' => 'nullable|integer|min:1|max:100', + 'page' => 'nullable|integer|min:1', + ]; + } +} diff --git a/app/Http/Requests/V1/Company/CompanyRequestStoreRequest.php b/app/Http/Requests/V1/Company/CompanyRequestStoreRequest.php new file mode 100644 index 0000000..7e7b4f7 --- /dev/null +++ b/app/Http/Requests/V1/Company/CompanyRequestStoreRequest.php @@ -0,0 +1,34 @@ + 'required|string|min:10|max:13', + 'company_name' => 'required|string|max:200', + 'ceo_name' => 'nullable|string|max:50', + 'address' => 'nullable|string|max:300', + 'phone' => 'nullable|string|max:30', + 'email' => 'nullable|email|max:100', + 'message' => 'nullable|string|max:1000', + ]; + } + + public function messages(): array + { + return [ + 'business_number.required' => __('validation.required', ['attribute' => '사업자등록번호']), + 'company_name.required' => __('validation.required', ['attribute' => '회사명']), + ]; + } +} diff --git a/app/Models/CompanyRequest.php b/app/Models/CompanyRequest.php new file mode 100644 index 0000000..0a9e99f --- /dev/null +++ b/app/Models/CompanyRequest.php @@ -0,0 +1,237 @@ + '대기중', + self::STATUS_APPROVED => '승인', + self::STATUS_REJECTED => '반려', + ]; + + // ========================================================================= + // 모델 설정 + // ========================================================================= + + protected $fillable = [ + 'user_id', + 'business_number', + 'company_name', + 'ceo_name', + 'address', + 'phone', + 'email', + 'status', + 'message', + 'reject_reason', + 'barobill_response', + 'approved_by', + 'created_tenant_id', + 'processed_at', + ]; + + protected $casts = [ + 'barobill_response' => 'array', + 'processed_at' => 'datetime', + ]; + + protected $attributes = [ + 'status' => self::STATUS_PENDING, + ]; + + // ========================================================================= + // 관계 + // ========================================================================= + + /** + * 신청자 + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + /** + * 처리자 (승인/반려) + */ + public function approver(): BelongsTo + { + return $this->belongsTo(User::class, 'approved_by'); + } + + /** + * 생성된 테넌트 + */ + public function createdTenant(): BelongsTo + { + return $this->belongsTo(Tenant::class, 'created_tenant_id'); + } + + // ========================================================================= + // 스코프 + // ========================================================================= + + /** + * 대기중인 신청만 + */ + public function scopePending($query) + { + return $query->where('status', self::STATUS_PENDING); + } + + /** + * 승인된 신청만 + */ + public function scopeApproved($query) + { + return $query->where('status', self::STATUS_APPROVED); + } + + /** + * 반려된 신청만 + */ + public function scopeRejected($query) + { + return $query->where('status', self::STATUS_REJECTED); + } + + /** + * 상태 필터 + */ + public function scopeOfStatus($query, string $status) + { + return $query->where('status', $status); + } + + // ========================================================================= + // 접근자 + // ========================================================================= + + /** + * 상태 라벨 + */ + public function getStatusLabelAttribute(): string + { + return self::STATUS_LABELS[$this->status] ?? $this->status; + } + + /** + * 대기중 여부 + */ + public function getIsPendingAttribute(): bool + { + return $this->status === self::STATUS_PENDING; + } + + /** + * 승인됨 여부 + */ + public function getIsApprovedAttribute(): bool + { + return $this->status === self::STATUS_APPROVED; + } + + /** + * 반려됨 여부 + */ + public function getIsRejectedAttribute(): bool + { + return $this->status === self::STATUS_REJECTED; + } + + /** + * 사업자등록번호 포맷 (하이픈 포함) + */ + public function getFormattedBusinessNumberAttribute(): string + { + $num = preg_replace('/[^0-9]/', '', $this->business_number); + if (strlen($num) === 10) { + return substr($num, 0, 3).'-'.substr($num, 3, 2).'-'.substr($num, 5, 5); + } + + return $this->business_number; + } + + // ========================================================================= + // 헬퍼 메서드 + // ========================================================================= + + /** + * 승인 처리 + */ + public function approve(int $approverId, int $tenantId): bool + { + if (! $this->is_pending) { + return false; + } + + $this->status = self::STATUS_APPROVED; + $this->approved_by = $approverId; + $this->created_tenant_id = $tenantId; + $this->processed_at = now(); + + return $this->save(); + } + + /** + * 반려 처리 + */ + public function reject(int $approverId, ?string $reason = null): bool + { + if (! $this->is_pending) { + return false; + } + + $this->status = self::STATUS_REJECTED; + $this->approved_by = $approverId; + $this->reject_reason = $reason; + $this->processed_at = now(); + + return $this->save(); + } +} diff --git a/app/Services/BarobillService.php b/app/Services/BarobillService.php index 970bbf4..1d8451f 100644 --- a/app/Services/BarobillService.php +++ b/app/Services/BarobillService.php @@ -119,6 +119,148 @@ public function testConnection(): array } } + // ========================================================================= + // 사업자등록번호 검증 + // ========================================================================= + + /** + * 사업자등록번호 유효성 검사 (휴폐업 조회) + * + * 바로빌 API를 통해 사업자등록번호의 유효성을 검증합니다. + * 바로빌 설정이 없는 경우 기본 형식 검증만 수행합니다. + * + * @param string $businessNumber 사업자등록번호 (10자리, 하이픈 제거) + * @return array{valid: bool, status: string, status_label: string, corp_name: ?string, ceo_name: ?string, message: string} + */ + public function checkBusinessNumber(string $businessNumber): array + { + // 하이픈 제거 및 숫자만 추출 + $businessNumber = preg_replace('/[^0-9]/', '', $businessNumber); + + // 기본 형식 검증 (10자리) + if (strlen($businessNumber) !== 10) { + return [ + 'valid' => false, + 'status' => 'invalid_format', + 'status_label' => '형식 오류', + 'corp_name' => null, + 'ceo_name' => null, + 'message' => __('error.company.invalid_business_number_format'), + ]; + } + + // 체크섬 검증 (사업자등록번호 자체 유효성) + if (! $this->validateBusinessNumberChecksum($businessNumber)) { + return [ + 'valid' => false, + 'status' => 'invalid_checksum', + 'status_label' => '유효하지 않음', + 'corp_name' => null, + 'ceo_name' => null, + 'message' => __('error.company.invalid_business_number'), + ]; + } + + // 바로빌 API 조회 시도 + try { + $response = $this->callApi('CheckCorpNum', [ + 'CorpNum' => $businessNumber, + ]); + + // 바로빌 응답 해석 + if (isset($response['CorpState'])) { + $state = $response['CorpState']; + $isValid = in_array($state, ['01', '02']); // 01: 사업중, 02: 휴업 + $statusLabel = match ($state) { + '01' => '사업중', + '02' => '휴업', + '03' => '폐업', + default => '조회 불가', + }; + + return [ + 'valid' => $isValid, + 'status' => $state, + 'status_label' => $statusLabel, + 'corp_name' => $response['CorpName'] ?? null, + 'ceo_name' => $response['CEOName'] ?? null, + 'message' => $isValid + ? __('message.company.business_number_valid') + : __('error.company.business_closed'), + ]; + } + + // 응답 형식이 다른 경우 (결과 코드 방식) + if (isset($response['Result'])) { + $isValid = $response['Result'] >= 0; + + return [ + 'valid' => $isValid, + 'status' => $isValid ? 'active' : 'unknown', + 'status_label' => $isValid ? '유효함' : '조회 불가', + 'corp_name' => $response['CorpName'] ?? null, + 'ceo_name' => $response['CEOName'] ?? null, + 'message' => $isValid + ? __('message.company.business_number_valid') + : ($response['Message'] ?? __('error.company.check_failed')), + ]; + } + + // 기본 응답 (체크섬만 통과한 경우) + return [ + 'valid' => true, + 'status' => 'format_valid', + 'status_label' => '형식 유효', + 'corp_name' => null, + 'ceo_name' => null, + 'message' => __('message.company.business_number_format_valid'), + ]; + } catch (\Exception $e) { + // API 호출 실패 시 형식 검증 결과만 반환 + Log::warning('바로빌 사업자번호 조회 실패', [ + 'business_number' => $businessNumber, + 'error' => $e->getMessage(), + ]); + + return [ + 'valid' => true, + 'status' => 'format_valid', + 'status_label' => '형식 유효 (API 조회 불가)', + 'corp_name' => null, + 'ceo_name' => null, + 'message' => __('message.company.business_number_format_valid'), + ]; + } + } + + /** + * 사업자등록번호 체크섬 검증 + * + * @param string $businessNumber 10자리 사업자등록번호 + */ + private function validateBusinessNumberChecksum(string $businessNumber): bool + { + if (strlen($businessNumber) !== 10) { + return false; + } + + $digits = str_split($businessNumber); + $multipliers = [1, 3, 7, 1, 3, 7, 1, 3, 5]; + $sum = 0; + + for ($i = 0; $i < 9; $i++) { + $sum += intval($digits[$i]) * $multipliers[$i]; + } + + // 8번째 자리 (인덱스 8)에 대한 추가 처리 + $sum += intval(floor(intval($digits[8]) * 5 / 10)); + + $remainder = $sum % 10; + $checkDigit = (10 - $remainder) % 10; + + return $checkDigit === intval($digits[9]); + } + // ========================================================================= // 세금계산서 발행 // ========================================================================= diff --git a/app/Services/CompanyService.php b/app/Services/CompanyService.php new file mode 100644 index 0000000..ee53179 --- /dev/null +++ b/app/Services/CompanyService.php @@ -0,0 +1,258 @@ +barobillService->checkBusinessNumber($businessNumber); + + // 이미 등록된 회사인지 확인 + $normalizedNumber = preg_replace('/[^0-9]/', '', $businessNumber); + $existingTenant = Tenant::query() + ->where('business_num', $normalizedNumber) + ->orWhere('business_num', $this->formatBusinessNumber($normalizedNumber)) + ->first(); + + if ($existingTenant) { + $result['already_exists'] = true; + $result['existing_company_name'] = $existingTenant->company_name; + } else { + $result['already_exists'] = false; + $result['existing_company_name'] = null; + } + + return $result; + } + + /** + * 사업자등록번호 포맷 (하이픈 추가) + */ + private function formatBusinessNumber(string $number): string + { + $number = preg_replace('/[^0-9]/', '', $number); + if (strlen($number) === 10) { + return substr($number, 0, 3).'-'.substr($number, 3, 2).'-'.substr($number, 5, 5); + } + + return $number; + } + + // ========================================================================= + // 회사 추가 신청 + // ========================================================================= + + /** + * 회사 추가 신청 생성 + */ + public function createRequest(array $data): CompanyRequest + { + $userId = $this->apiUserId(); + $businessNumber = preg_replace('/[^0-9]/', '', $data['business_number']); + + // 중복 신청 확인 (대기중인 신청이 있는지) + $existingRequest = CompanyRequest::query() + ->where('user_id', $userId) + ->where('business_number', $businessNumber) + ->where('status', CompanyRequest::STATUS_PENDING) + ->first(); + + if ($existingRequest) { + throw new BadRequestHttpException(__('error.company.request_already_exists')); + } + + // 이미 등록된 회사인지 확인 + $existingTenant = Tenant::query() + ->where('business_num', $businessNumber) + ->orWhere('business_num', $this->formatBusinessNumber($businessNumber)) + ->first(); + + if ($existingTenant) { + // 이미 존재하는 회사에 합류 신청 + // 해당 회사의 관리자에게 알림을 보내거나 별도 처리 가능 + throw new BadRequestHttpException(__('error.company.already_registered')); + } + + // 사업자등록번호 유효성 검사 + $validationResult = $this->barobillService->checkBusinessNumber($businessNumber); + + return CompanyRequest::create([ + 'user_id' => $userId, + 'business_number' => $businessNumber, + 'company_name' => $data['company_name'], + 'ceo_name' => $data['ceo_name'] ?? $validationResult['ceo_name'] ?? null, + 'address' => $data['address'] ?? null, + 'phone' => $data['phone'] ?? null, + 'email' => $data['email'] ?? null, + 'message' => $data['message'] ?? null, + 'status' => CompanyRequest::STATUS_PENDING, + 'barobill_response' => $validationResult, + ]); + } + + /** + * 회사 추가 신청 목록 (관리자용) + */ + public function getRequests(array $params): LengthAwarePaginator + { + $query = CompanyRequest::query() + ->with(['user:id,name,email', 'approver:id,name,email', 'createdTenant:id,company_name,code']); + + // 상태 필터 + if (! empty($params['status'])) { + $query->ofStatus($params['status']); + } + + // 검색 (사업자번호, 회사명, 신청자명) + if (! empty($params['search'])) { + $search = $params['search']; + $query->where(function ($q) use ($search) { + $q->where('business_number', 'like', "%{$search}%") + ->orWhere('company_name', 'like', "%{$search}%") + ->orWhereHas('user', function ($uq) use ($search) { + $uq->where('name', 'like', "%{$search}%"); + }); + }); + } + + // 날짜 범위 필터 + if (! empty($params['start_date'])) { + $query->whereDate('created_at', '>=', $params['start_date']); + } + if (! empty($params['end_date'])) { + $query->whereDate('created_at', '<=', $params['end_date']); + } + + // 정렬 + $sortBy = $params['sort_by'] ?? 'created_at'; + $sortDir = $params['sort_dir'] ?? 'desc'; + $query->orderBy($sortBy, $sortDir); + + $perPage = $params['per_page'] ?? 20; + + return $query->paginate($perPage); + } + + /** + * 회사 추가 신청 상세 + */ + public function getRequest(int $id): CompanyRequest + { + return CompanyRequest::query() + ->with(['user:id,name,email', 'approver:id,name,email', 'createdTenant:id,company_name,code']) + ->findOrFail($id); + } + + // ========================================================================= + // 신청 처리 (관리자) + // ========================================================================= + + /** + * 회사 추가 신청 승인 + */ + public function approveRequest(int $id): CompanyRequest + { + $userId = $this->apiUserId(); + $request = CompanyRequest::findOrFail($id); + + if (! $request->is_pending) { + throw new BadRequestHttpException(__('error.company.request_not_pending')); + } + + return DB::transaction(function () use ($request, $userId) { + // 테넌트 생성 + $tenantCode = $this->tenantService->generateTenantCode($request->company_name); + $tenant = Tenant::create([ + 'code' => $tenantCode, + 'company_name' => $request->company_name, + 'business_num' => $request->business_number, + 'ceo_name' => $request->ceo_name, + 'address' => $request->address, + 'phone' => $request->phone, + 'email' => $request->email, + ]); + + // 신청자를 테넌트에 연결 + UserTenant::create([ + 'user_id' => $request->user_id, + 'tenant_id' => $tenant->id, + 'is_active' => true, + 'is_default' => true, + 'joined_at' => now(), + ]); + + // 신청 승인 처리 + $request->approve($userId, $tenant->id); + + return $request->fresh(['user', 'approver', 'createdTenant']); + }); + } + + /** + * 회사 추가 신청 반려 + */ + public function rejectRequest(int $id, ?string $reason = null): CompanyRequest + { + $userId = $this->apiUserId(); + $request = CompanyRequest::findOrFail($id); + + if (! $request->is_pending) { + throw new BadRequestHttpException(__('error.company.request_not_pending')); + } + + $request->reject($userId, $reason); + + return $request->fresh(['user', 'approver']); + } + + // ========================================================================= + // 내 신청 목록 (사용자용) + // ========================================================================= + + /** + * 내 회사 추가 신청 목록 + */ + public function getMyRequests(array $params): LengthAwarePaginator + { + $userId = $this->apiUserId(); + + $query = CompanyRequest::query() + ->where('user_id', $userId) + ->with(['approver:id,name,email', 'createdTenant:id,company_name,code']); + + // 상태 필터 + if (! empty($params['status'])) { + $query->ofStatus($params['status']); + } + + // 정렬 + $sortBy = $params['sort_by'] ?? 'created_at'; + $sortDir = $params['sort_dir'] ?? 'desc'; + $query->orderBy($sortBy, $sortDir); + + $perPage = $params['per_page'] ?? 20; + + return $query->paginate($perPage); + } +} diff --git a/app/Swagger/v1/CompanyApi.php b/app/Swagger/v1/CompanyApi.php new file mode 100644 index 0000000..d7e05c1 --- /dev/null +++ b/app/Swagger/v1/CompanyApi.php @@ -0,0 +1,419 @@ +id(); + $table->unsignedBigInteger('user_id')->comment('신청자 ID'); + $table->string('business_number', 20)->comment('사업자등록번호'); + $table->string('company_name', 200)->comment('회사명'); + $table->string('ceo_name', 50)->nullable()->comment('대표자명'); + $table->string('address', 300)->nullable()->comment('주소'); + $table->string('phone', 30)->nullable()->comment('전화번호'); + $table->string('email', 100)->nullable()->comment('이메일'); + $table->string('status', 20)->default('pending')->comment('상태 (pending, approved, rejected)'); + $table->text('message')->nullable()->comment('신청 메시지'); + $table->text('reject_reason')->nullable()->comment('반려 사유'); + $table->json('barobill_response')->nullable()->comment('바로빌 검증 응답'); + $table->unsignedBigInteger('approved_by')->nullable()->comment('승인/반려 처리자'); + $table->unsignedBigInteger('created_tenant_id')->nullable()->comment('생성된 테넌트 ID'); + $table->timestamp('processed_at')->nullable()->comment('처리일시'); + $table->timestamps(); + + $table->index('user_id', 'idx_company_requests_user_id'); + $table->index('business_number', 'idx_company_requests_business_number'); + $table->index('status', 'idx_company_requests_status'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('company_requests'); + } +}; diff --git a/lang/ko/error.php b/lang/ko/error.php index 687c512..fa956e5 100644 --- a/lang/ko/error.php +++ b/lang/ko/error.php @@ -312,4 +312,13 @@ 'not_found' => '내보내기 요청을 찾을 수 없습니다.', 'failed' => '내보내기 처리 중 오류가 발생했습니다.', ], + + // 회사 추가 신청 관련 + 'company' => [ + 'not_found' => '회사 추가 신청을 찾을 수 없습니다.', + 'already_pending' => '이미 대기 중인 동일 사업자등록번호 신청이 있습니다.', + 'already_processed' => '이미 처리된 신청입니다.', + 'tenant_creation_failed' => '테넌트 생성에 실패했습니다.', + 'invalid_business_number' => '유효하지 않은 사업자등록번호입니다.', + ], ]; diff --git a/lang/ko/message.php b/lang/ko/message.php index 1434784..b7fdcec 100644 --- a/lang/ko/message.php +++ b/lang/ko/message.php @@ -367,4 +367,12 @@ 'cancelled' => '결제가 취소되었습니다.', 'refunded' => '환불이 완료되었습니다.', ], + + // 회사 추가 신청 관리 + 'company' => [ + 'checked' => '사업자등록번호 확인이 완료되었습니다.', + 'request_created' => '회사 추가 신청이 접수되었습니다.', + 'request_approved' => '회사 추가 신청이 승인되었습니다.', + 'request_rejected' => '회사 추가 신청이 반려되었습니다.', + ], ]; diff --git a/routes/api.php b/routes/api.php index 30d1618..ec18888 100644 --- a/routes/api.php +++ b/routes/api.php @@ -21,6 +21,7 @@ use App\Http\Controllers\Api\V1\ClassificationController; use App\Http\Controllers\Api\V1\ClientController; use App\Http\Controllers\Api\V1\ClientGroupController; +use App\Http\Controllers\Api\V1\CompanyController; use App\Http\Controllers\Api\V1\CommonController; use App\Http\Controllers\Api\V1\DashboardController; use App\Http\Controllers\Api\V1\DepartmentController; @@ -476,6 +477,17 @@ Route::get('/{id}/statement', [PaymentController::class, 'statement'])->whereNumber('id')->name('v1.payments.statement'); }); + // Company API (회사 추가 관리) + Route::prefix('companies')->group(function () { + Route::post('/check', [CompanyController::class, 'check'])->name('v1.companies.check'); // 사업자등록번호 검증 + Route::post('/request', [CompanyController::class, 'request'])->name('v1.companies.request'); // 회사 추가 신청 + Route::get('/requests', [CompanyController::class, 'requests'])->name('v1.companies.requests.index'); // 신청 목록 (관리자) + Route::get('/requests/{id}', [CompanyController::class, 'showRequest'])->whereNumber('id')->name('v1.companies.requests.show'); // 신청 상세 + Route::post('/requests/{id}/approve', [CompanyController::class, 'approve'])->whereNumber('id')->name('v1.companies.requests.approve'); // 승인 + Route::post('/requests/{id}/reject', [CompanyController::class, 'reject'])->whereNumber('id')->name('v1.companies.requests.reject'); // 반려 + Route::get('/my-requests', [CompanyController::class, 'myRequests'])->name('v1.companies.my-requests'); // 내 신청 목록 + }); + // Sale API (매출 관리) Route::prefix('sales')->group(function () { Route::get('', [SaleController::class, 'index'])->name('v1.sales.index');