barobillService = $barobillService; } /** * 전체 테넌트 사용량 목록 조회 * * @param string $startDate 시작일 (YYYYMMDD) * @param string $endDate 종료일 (YYYYMMDD) * @param int|null $tenantId 특정 테넌트만 조회 (null이면 전체) * @param bool $productionOnly 운영 모드만 조회 (기본값: true) * @return array */ public function getUsageList(string $startDate, string $endDate, ?int $tenantId = null, bool $productionOnly = true): array { $query = BarobillMember::query() ->where('status', 'active') ->with('tenant:id,company_name'); // 운영 모드만 조회 (테스트 모드 제외) if ($productionOnly) { $query->where('server_mode', 'production'); } if ($tenantId) { $query->where('tenant_id', $tenantId); } $members = $query->get(); $usageList = []; foreach ($members as $member) { $usage = $this->getMemberUsage($member, $startDate, $endDate); $usageList[] = $usage; } return $usageList; } /** * 단일 회원사 사용량 조회 * * @param BarobillMember $member * @param string $startDate 시작일 (YYYYMMDD) * @param string $endDate 종료일 (YYYYMMDD) * @return array */ public function getMemberUsage(BarobillMember $member, string $startDate, string $endDate): array { // 해당 회원사의 서버 모드로 전환 (테스트/운영) $this->barobillService->setServerMode($member->server_mode ?? 'test'); $taxInvoiceCount = $this->getTaxInvoiceCount($member, $startDate, $endDate); $bankAccountCount = $this->getBankAccountCount($member, $startDate, $endDate); $cardCount = $this->getCardCount($member, $startDate, $endDate); $hometaxCount = $this->getHometaxCount($member, $startDate, $endDate); // 전자세금계산서만 정책 기반 과금액 계산 (다른 서비스는 월정액) $taxInvoiceBilling = self::calculateBillingByPolicy('tax_invoice', $taxInvoiceCount); $taxInvoiceAmount = $taxInvoiceBilling['billable_amount']; return [ 'member_id' => $member->id, 'tenant_id' => $member->tenant_id, 'tenant_name' => $member->tenant?->company_name ?? '-', 'biz_no' => $member->biz_no, 'formatted_biz_no' => $member->formatted_biz_no, 'corp_name' => $member->corp_name, 'barobill_id' => $member->barobill_id, // 건수 'tax_invoice_count' => $taxInvoiceCount, 'bank_account_count' => $bankAccountCount, 'card_count' => $cardCount, 'hometax_count' => $hometaxCount, // 금액 (전자세금계산서만 건별 과금, 나머지는 월정액) 'tax_invoice_amount' => $taxInvoiceAmount, 'tax_invoice_billing' => $taxInvoiceBilling, 'total_amount' => $taxInvoiceAmount, ]; } /** * 통계 데이터 집계 * * @param array $usageList 사용량 목록 * @return array */ public function aggregateStats(array $usageList): array { $totalMembers = count($usageList); $totalTaxInvoice = 0; $totalBankAccount = 0; $totalCard = 0; $totalHometax = 0; $totalAmount = 0; foreach ($usageList as $usage) { $totalTaxInvoice += $usage['tax_invoice_count']; $totalBankAccount += $usage['bank_account_count']; $totalCard += $usage['card_count']; $totalHometax += $usage['hometax_count']; $totalAmount += $usage['total_amount']; } return [ 'total_members' => $totalMembers, 'total_tax_invoice' => $totalTaxInvoice, 'total_bank_account' => $totalBankAccount, 'total_card' => $totalCard, 'total_hometax' => $totalHometax, 'total_amount' => $totalAmount, ]; } /** * 전자세금계산서 발행 건수 조회 * * HometaxInvoice 테이블에서 해당 테넌트의 매출 세금계산서 건수를 카운트합니다. * (매출 = 발행한 세금계산서) */ protected function getTaxInvoiceCount(BarobillMember $member, string $startDate, string $endDate): int { try { // YYYYMMDD -> YYYY-MM-DD 형식 변환 $start = substr($startDate, 0, 4) . '-' . substr($startDate, 4, 2) . '-' . substr($startDate, 6, 2); $end = substr($endDate, 0, 4) . '-' . substr($endDate, 4, 2) . '-' . substr($endDate, 6, 2); return HometaxInvoice::where('tenant_id', $member->tenant_id) ->where('invoice_type', 'sales') // 매출 (발행한 세금계산서) ->whereBetween('write_date', [$start, $end]) ->count(); } catch (\Exception $e) { Log::warning('세금계산서 건수 조회 실패', [ 'member_id' => $member->id, 'tenant_id' => $member->tenant_id, 'error' => $e->getMessage(), ]); return 0; } } /** * 등록 계좌 수 조회 * * 월정액 과금 기준: 등록된 계좌 개수 */ protected function getBankAccountCount(BarobillMember $member, string $startDate, string $endDate): int { try { $result = $this->barobillService->getBankAccounts($member->biz_no, true); Log::info('계좌 조회 API 응답', [ 'member_id' => $member->id, 'biz_no' => $member->biz_no, 'server_mode' => $member->server_mode, 'success' => $result['success'] ?? false, 'data_type' => isset($result['data']) ? gettype($result['data']) : 'null', 'data' => $result['data'] ?? null, ]); if ($result['success'] && isset($result['data'])) { // 배열이면 count if (is_array($result['data'])) { return count($result['data']); } // 객체에 계좌 목록이 있으면 if (is_object($result['data'])) { // GetBankAccountEx 응답: BankAccount 또는 BankAccountEx 배열 if (isset($result['data']->BankAccount)) { $accounts = $result['data']->BankAccount; return is_array($accounts) ? count($accounts) : 1; } if (isset($result['data']->BankAccountEx)) { $accounts = $result['data']->BankAccountEx; return is_array($accounts) ? count($accounts) : 1; } } } } catch (\Exception $e) { Log::warning('등록 계좌 수 조회 실패', [ 'member_id' => $member->id, 'error' => $e->getMessage(), ]); } return 0; } /** * 등록 카드 수 조회 * * 월정액 과금 기준: 등록된 카드 개수 */ protected function getCardCount(BarobillMember $member, string $startDate, string $endDate): int { try { $result = $this->barobillService->getCards($member->biz_no, true); Log::info('카드 조회 API 응답', [ 'member_id' => $member->id, 'biz_no' => $member->biz_no, 'server_mode' => $member->server_mode, 'success' => $result['success'] ?? false, 'data_type' => isset($result['data']) ? gettype($result['data']) : 'null', 'data' => $result['data'] ?? null, ]); if ($result['success'] && isset($result['data'])) { // 배열이면 count if (is_array($result['data'])) { return count($result['data']); } // 객체에 카드 목록이 있으면 if (is_object($result['data'])) { // GetCardEx2 응답: CardEx 배열 if (isset($result['data']->CardEx)) { $cards = $result['data']->CardEx; return is_array($cards) ? count($cards) : 1; } } } } catch (\Exception $e) { Log::warning('등록 카드 수 조회 실패', [ 'member_id' => $member->id, 'error' => $e->getMessage(), ]); } return 0; } /** * 홈텍스 매입/매출 건수 조회 * * HometaxInvoice 테이블에서 해당 테넌트의 전체 세금계산서 건수를 카운트합니다. * (매입 + 매출 모두 포함) */ protected function getHometaxCount(BarobillMember $member, string $startDate, string $endDate): int { try { // YYYYMMDD -> YYYY-MM-DD 형식 변환 $start = substr($startDate, 0, 4) . '-' . substr($startDate, 4, 2) . '-' . substr($startDate, 6, 2); $end = substr($endDate, 0, 4) . '-' . substr($endDate, 4, 2) . '-' . substr($endDate, 6, 2); return HometaxInvoice::where('tenant_id', $member->tenant_id) ->whereBetween('write_date', [$start, $end]) ->count(); } catch (\Exception $e) { Log::warning('홈텍스 건수 조회 실패', [ 'member_id' => $member->id, 'tenant_id' => $member->tenant_id, 'error' => $e->getMessage(), ]); return 0; } } /** * 서비스별 과금 정책 정보 반환 (DB에서 조회) */ public static function getPriceInfo(): array { $policies = BarobillPricingPolicy::active()->orderBy('sort_order')->get(); $priceInfo = []; foreach ($policies as $policy) { $priceInfo[$policy->service_type] = [ 'name' => $policy->name, 'free_quota' => $policy->free_quota, 'free_quota_unit' => $policy->free_quota_unit, 'additional_unit' => $policy->additional_unit, 'additional_unit_label' => $policy->additional_unit_label, 'additional_price' => $policy->additional_price, 'description' => $policy->policy_description, ]; } // 기본값 설정 (DB에 정책이 없는 경우) $defaults = [ 'tax_invoice' => [ 'name' => '계산서 발행', 'free_quota' => 100, 'free_quota_unit' => '건', 'additional_unit' => 50, 'additional_unit_label' => '건', 'additional_price' => 5000, 'description' => '기본 100건 무료, 추가 50건 단위 5,000원', ], 'bank_account' => [ 'name' => '계좌조회 수집', 'free_quota' => 1, 'free_quota_unit' => '개', 'additional_unit' => 1, 'additional_unit_label' => '계좌', 'additional_price' => 10000, 'description' => '기본 1계좌 무료, 추가 1계좌당 10,000원', ], 'card' => [ 'name' => '법인카드 등록', 'free_quota' => 5, 'free_quota_unit' => '장', 'additional_unit' => 1, 'additional_unit_label' => '장', 'additional_price' => 5000, 'description' => '기본 5장 무료, 추가 1장당 5,000원', ], 'hometax' => [ 'name' => '홈텍스 매입/매출', 'free_quota' => 0, 'free_quota_unit' => '건', 'additional_unit' => 1, 'additional_unit_label' => '건', 'additional_price' => 0, 'description' => '월정액 (별도 협의)', ], ]; // 없는 정책은 기본값으로 채움 foreach ($defaults as $type => $default) { if (!isset($priceInfo[$type])) { $priceInfo[$type] = $default; } } return $priceInfo; } /** * 사용량에 따른 과금액 계산 * * @param string $serviceType 서비스 유형 * @param int $usageCount 사용량/등록 수 * @return array ['free_count' => int, 'billable_count' => int, 'billable_amount' => int] */ public static function calculateBillingByPolicy(string $serviceType, int $usageCount): array { $policy = BarobillPricingPolicy::getByServiceType($serviceType); if (!$policy) { // 기본 정책이 없으면 과금 없음 return [ 'free_count' => $usageCount, 'billable_count' => 0, 'billable_amount' => 0, ]; } return $policy->calculateBilling($usageCount); } }