- 카드 조회: CardInfoEx2 → CardEx로 수정 (실제 GetCardEx2 응답 구조) - 계좌 조회: BankAccountInfoEx → BankAccount/BankAccountEx로 수정 (실제 GetBankAccountEx 응답 구조) - EcardController, EaccountController의 응답 파싱 로직과 일치시킴 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
382 lines
14 KiB
PHP
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);
|
|
}
|
|
}
|