Files
sam-manage/app/Services/Barobill/BarobillUsageService.php
김보곤 806502768f fix:바로빌 API 응답 구조 수정 (CardEx, BankAccount)
- 카드 조회: CardInfoEx2 → CardEx로 수정 (실제 GetCardEx2 응답 구조)
- 계좌 조회: BankAccountInfoEx → BankAccount/BankAccountEx로 수정 (실제 GetBankAccountEx 응답 구조)
- EcardController, EaccountController의 응답 파싱 로직과 일치시킴

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 20:51:00 +09:00

382 lines
14 KiB
PHP

<?php
namespace App\Services\Barobill;
use App\Models\Barobill\BarobillMember;
use App\Models\Barobill\BarobillPricingPolicy;
use App\Models\Barobill\HometaxInvoice;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Collection;
/**
* 바로빌 사용량 집계 서비스
*
* 바로빌 API에는 직접적인 "사용량 집계" API가 없으므로,
* 각 서비스별 내역 조회 API를 통해 건수를 집계합니다.
*
* 과금 정책 (DB 관리):
* - 전자세금계산서: 기본 100건 무료, 추가 50건 단위 5,000원
* - 계좌조회: 기본 1계좌 무료, 추가 1계좌당 10,000원
* - 카드등록: 기본 5장 무료, 추가 1장당 5,000원
*/
class BarobillUsageService
{
protected BarobillService $barobillService;
public function __construct(BarobillService $barobillService)
{
$this->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);
}
}