feat: [barobill] 구독/과금/사용량 관리 API 이관
- BarobillBillingService 생성 (구독 CRUD, 월별 과금 배치, 사용량 과금, 집계) - BarobillUsageService 생성 (회원별 사용량 조회, 통계, 정책 기반 과금 계산) - BarobillBillingController 생성 (9개 엔드포인트) - 구독 관리, 월별 현황, 과금 배치, 연간 추이, 정책 관리 - BarobillUsageController 생성 (4개 엔드포인트) - 사용량 목록/통계, 회원별 상세, 과금 정책 정보 - finance.php 라우트 등록 (barobill/billing/*, barobill/usage/*)
This commit is contained in:
172
app/Http/Controllers/Api/V1/BarobillBillingController.php
Normal file
172
app/Http/Controllers/Api/V1/BarobillBillingController.php
Normal file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Barobill\BarobillBillingService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class BarobillBillingController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private BarobillBillingService $billingService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 구독 목록
|
||||
*/
|
||||
public function subscriptions(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'member_id' => 'nullable|integer',
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(function () use ($data) {
|
||||
return [
|
||||
'subscriptions' => $this->billingService->getSubscriptions($data['member_id'] ?? null),
|
||||
];
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 구독 등록/수정
|
||||
*/
|
||||
public function saveSubscription(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'member_id' => 'required|integer|exists:barobill_members,id',
|
||||
'service_type' => 'required|in:bank_account,card,hometax',
|
||||
'monthly_fee' => 'nullable|integer|min:0',
|
||||
'started_at' => 'nullable|date',
|
||||
'ended_at' => 'nullable|date|after_or_equal:started_at',
|
||||
'is_active' => 'nullable|boolean',
|
||||
'memo' => 'nullable|string|max:500',
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(function () use ($data) {
|
||||
return $this->billingService->saveSubscription(
|
||||
$data['member_id'],
|
||||
$data['service_type'],
|
||||
$data
|
||||
);
|
||||
}, __('message.created'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 구독 해지
|
||||
*/
|
||||
public function cancelSubscription(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
$this->billingService->cancelSubscription($id);
|
||||
|
||||
return ['cancelled' => true];
|
||||
}, __('message.deleted'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 월별 과금 현황 목록
|
||||
*/
|
||||
public function billingList(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'billing_month' => 'nullable|string|date_format:Y-m',
|
||||
'tenant_id' => 'nullable|integer',
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(function () use ($data) {
|
||||
$billingMonth = $data['billing_month'] ?? Carbon::now()->format('Y-m');
|
||||
$tenantId = $data['tenant_id'] ?? null;
|
||||
|
||||
return [
|
||||
'list' => $this->billingService->getMonthlyBillingList($billingMonth, $tenantId),
|
||||
'total' => $this->billingService->getMonthlyTotal($billingMonth, $tenantId),
|
||||
'billing_month' => $billingMonth,
|
||||
];
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 월별 과금 통계
|
||||
*/
|
||||
public function billingStats(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'billing_month' => 'nullable|string|date_format:Y-m',
|
||||
'tenant_id' => 'nullable|integer',
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(function () use ($data) {
|
||||
$billingMonth = $data['billing_month'] ?? Carbon::now()->format('Y-m');
|
||||
|
||||
return $this->billingService->getMonthlyTotal($billingMonth, $data['tenant_id'] ?? null);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 월별 과금 배치 처리
|
||||
*/
|
||||
public function processBilling(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'billing_month' => 'nullable|string|date_format:Y-m',
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(function () use ($data) {
|
||||
return $this->billingService->processMonthlyBilling($data['billing_month'] ?? null);
|
||||
}, __('message.created'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 연간 과금 추이
|
||||
*/
|
||||
public function yearlyTrend(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'year' => 'nullable|integer|min:2020|max:2030',
|
||||
'tenant_id' => 'nullable|integer',
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(function () use ($data) {
|
||||
$year = $data['year'] ?? Carbon::now()->year;
|
||||
|
||||
return [
|
||||
'trend' => $this->billingService->getYearlyTrend($year, $data['tenant_id'] ?? null),
|
||||
'year' => $year,
|
||||
];
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 과금 정책 목록
|
||||
*/
|
||||
public function pricingPolicies()
|
||||
{
|
||||
return ApiResponse::handle(function () {
|
||||
return ['policies' => $this->billingService->getPricingPolicies()];
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 과금 정책 수정
|
||||
*/
|
||||
public function updatePricingPolicy(Request $request, int $id)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'name' => 'nullable|string|max:100',
|
||||
'description' => 'nullable|string|max:500',
|
||||
'free_quota' => 'nullable|integer|min:0',
|
||||
'free_quota_unit' => 'nullable|string|max:10',
|
||||
'additional_unit' => 'nullable|integer|min:1',
|
||||
'additional_unit_label' => 'nullable|string|max:10',
|
||||
'additional_price' => 'nullable|integer|min:0',
|
||||
'is_active' => 'nullable|boolean',
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(function () use ($id, $data) {
|
||||
return $this->billingService->updatePricingPolicy($id, $data);
|
||||
}, __('message.updated'));
|
||||
}
|
||||
}
|
||||
96
app/Http/Controllers/Api/V1/BarobillUsageController.php
Normal file
96
app/Http/Controllers/Api/V1/BarobillUsageController.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Barobill\BarobillMember;
|
||||
use App\Services\Barobill\BarobillUsageService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class BarobillUsageController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private BarobillUsageService $usageService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 사용량 목록
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'start_date' => 'nullable|date_format:Y-m-d',
|
||||
'end_date' => 'nullable|date_format:Y-m-d',
|
||||
'tenant_id' => 'nullable|integer',
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(function () use ($data) {
|
||||
$startDate = $data['start_date'] ?? Carbon::now()->startOfMonth()->format('Y-m-d');
|
||||
$endDate = $data['end_date'] ?? Carbon::now()->format('Y-m-d');
|
||||
$tenantId = $data['tenant_id'] ?? null;
|
||||
|
||||
$usageList = $this->usageService->getUsageList($startDate, $endDate, $tenantId);
|
||||
|
||||
return [
|
||||
'data' => $usageList,
|
||||
'stats' => $this->usageService->aggregateStats($usageList),
|
||||
'meta' => [
|
||||
'start_date' => $startDate,
|
||||
'end_date' => $endDate,
|
||||
],
|
||||
];
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용량 통계
|
||||
*/
|
||||
public function stats(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'start_date' => 'nullable|date_format:Y-m-d',
|
||||
'end_date' => 'nullable|date_format:Y-m-d',
|
||||
'tenant_id' => 'nullable|integer',
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(function () use ($data) {
|
||||
$startDate = $data['start_date'] ?? Carbon::now()->startOfMonth()->format('Y-m-d');
|
||||
$endDate = $data['end_date'] ?? Carbon::now()->format('Y-m-d');
|
||||
|
||||
$usageList = $this->usageService->getUsageList($startDate, $endDate, $data['tenant_id'] ?? null);
|
||||
|
||||
return $this->usageService->aggregateStats($usageList);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 회원별 사용량 상세
|
||||
*/
|
||||
public function show(Request $request, int $memberId)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'start_date' => 'nullable|date_format:Y-m-d',
|
||||
'end_date' => 'nullable|date_format:Y-m-d',
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(function () use ($memberId, $data) {
|
||||
$member = BarobillMember::withoutGlobalScopes()->findOrFail($memberId);
|
||||
$startDate = $data['start_date'] ?? Carbon::now()->startOfMonth()->format('Y-m-d');
|
||||
$endDate = $data['end_date'] ?? Carbon::now()->format('Y-m-d');
|
||||
|
||||
return $this->usageService->getMemberUsage($member, $startDate, $endDate);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 과금 정책 정보
|
||||
*/
|
||||
public function priceInfo()
|
||||
{
|
||||
return ApiResponse::handle(function () {
|
||||
return ['prices' => BarobillUsageService::getPriceInfo()];
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
}
|
||||
305
app/Services/Barobill/BarobillBillingService.php
Normal file
305
app/Services/Barobill/BarobillBillingService.php
Normal file
@@ -0,0 +1,305 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Barobill;
|
||||
|
||||
use App\Models\Barobill\BarobillBillingRecord;
|
||||
use App\Models\Barobill\BarobillMember;
|
||||
use App\Models\Barobill\BarobillMonthlySummary;
|
||||
use App\Models\Barobill\BarobillPricingPolicy;
|
||||
use App\Models\Barobill\BarobillSubscription;
|
||||
use App\Services\Service;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class BarobillBillingService extends Service
|
||||
{
|
||||
/**
|
||||
* 구독 목록 조회
|
||||
*/
|
||||
public function getSubscriptions(?int $memberId = null): array
|
||||
{
|
||||
$query = BarobillSubscription::withoutGlobalScopes()
|
||||
->with('member');
|
||||
|
||||
if ($memberId) {
|
||||
$query->where('member_id', $memberId);
|
||||
}
|
||||
|
||||
return $query->orderBy('created_at', 'desc')->get()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 구독 등록/수정
|
||||
*/
|
||||
public function saveSubscription(int $memberId, string $serviceType, array $data): BarobillSubscription
|
||||
{
|
||||
return BarobillSubscription::withoutGlobalScopes()->updateOrCreate(
|
||||
[
|
||||
'member_id' => $memberId,
|
||||
'service_type' => $serviceType,
|
||||
],
|
||||
[
|
||||
'monthly_fee' => $data['monthly_fee'] ?? BarobillSubscription::DEFAULT_MONTHLY_FEES[$serviceType] ?? 0,
|
||||
'started_at' => $data['started_at'] ?? now(),
|
||||
'ended_at' => $data['ended_at'] ?? null,
|
||||
'is_active' => $data['is_active'] ?? true,
|
||||
'memo' => $data['memo'] ?? null,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 구독 해지
|
||||
*/
|
||||
public function cancelSubscription(int $subscriptionId): bool
|
||||
{
|
||||
$subscription = BarobillSubscription::withoutGlobalScopes()->findOrFail($subscriptionId);
|
||||
$subscription->update([
|
||||
'ended_at' => now(),
|
||||
'is_active' => false,
|
||||
]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 월정액 과금 배치 처리
|
||||
*/
|
||||
public function processMonthlyBilling(?string $billingMonth = null): array
|
||||
{
|
||||
$billingMonth = $billingMonth ?: Carbon::now()->format('Y-m');
|
||||
|
||||
$subscriptions = BarobillSubscription::withoutGlobalScopes()
|
||||
->where('is_active', true)
|
||||
->get();
|
||||
|
||||
$processed = 0;
|
||||
$skipped = 0;
|
||||
$errors = 0;
|
||||
|
||||
foreach ($subscriptions as $subscription) {
|
||||
$exists = BarobillBillingRecord::withoutGlobalScopes()
|
||||
->where('member_id', $subscription->member_id)
|
||||
->where('billing_month', $billingMonth)
|
||||
->where('service_type', $subscription->service_type)
|
||||
->where('billing_type', 'subscription')
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
$skipped++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
BarobillBillingRecord::withoutGlobalScopes()->create([
|
||||
'member_id' => $subscription->member_id,
|
||||
'billing_month' => $billingMonth,
|
||||
'service_type' => $subscription->service_type,
|
||||
'billing_type' => 'subscription',
|
||||
'quantity' => 1,
|
||||
'unit_price' => $subscription->monthly_fee,
|
||||
'total_amount' => $subscription->monthly_fee,
|
||||
'billed_at' => now(),
|
||||
'description' => $this->getServiceLabel($subscription->service_type).' 월정액',
|
||||
]);
|
||||
$processed++;
|
||||
} catch (\Throwable $e) {
|
||||
$errors++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->updateMonthlySummaries($billingMonth);
|
||||
|
||||
return [
|
||||
'processed' => $processed,
|
||||
'skipped' => $skipped,
|
||||
'errors' => $errors,
|
||||
'billing_month' => $billingMonth,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 건별 사용량 과금 기록
|
||||
*/
|
||||
public function recordUsage(int $memberId, string $serviceType, int $quantity, ?string $billingMonth = null): BarobillBillingRecord
|
||||
{
|
||||
$billingMonth = $billingMonth ?: Carbon::now()->format('Y-m');
|
||||
|
||||
$policy = BarobillPricingPolicy::withoutGlobalScopes()
|
||||
->where('service_type', $serviceType)
|
||||
->where('is_active', true)
|
||||
->first();
|
||||
|
||||
$totalAmount = 0;
|
||||
if ($policy) {
|
||||
$billing = $policy->calculateBilling($quantity);
|
||||
$totalAmount = $billing['billable_amount'] ?? 0;
|
||||
}
|
||||
|
||||
$record = BarobillBillingRecord::withoutGlobalScopes()->updateOrCreate(
|
||||
[
|
||||
'member_id' => $memberId,
|
||||
'billing_month' => $billingMonth,
|
||||
'service_type' => $serviceType,
|
||||
'billing_type' => 'usage',
|
||||
],
|
||||
[
|
||||
'quantity' => $quantity,
|
||||
'unit_price' => $totalAmount > 0 ? (int) ceil($totalAmount / max($quantity, 1)) : 0,
|
||||
'total_amount' => $totalAmount,
|
||||
'billed_at' => now(),
|
||||
'description' => $this->getServiceLabel($serviceType).' 사용량 과금',
|
||||
]
|
||||
);
|
||||
|
||||
$this->updateMonthlySummaries($billingMonth);
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* 월별 집계 갱신
|
||||
*/
|
||||
public function updateMonthlySummaries(string $billingMonth): void
|
||||
{
|
||||
$records = BarobillBillingRecord::withoutGlobalScopes()
|
||||
->where('billing_month', $billingMonth)
|
||||
->get()
|
||||
->groupBy('member_id');
|
||||
|
||||
foreach ($records as $memberId => $memberRecords) {
|
||||
$summary = [
|
||||
'bank_account_fee' => 0,
|
||||
'card_fee' => 0,
|
||||
'hometax_fee' => 0,
|
||||
'subscription_total' => 0,
|
||||
'tax_invoice_count' => 0,
|
||||
'tax_invoice_amount' => 0,
|
||||
'usage_total' => 0,
|
||||
];
|
||||
|
||||
foreach ($memberRecords as $record) {
|
||||
if ($record->billing_type === 'subscription') {
|
||||
match ($record->service_type) {
|
||||
'bank_account' => $summary['bank_account_fee'] = $record->total_amount,
|
||||
'card' => $summary['card_fee'] = $record->total_amount,
|
||||
'hometax' => $summary['hometax_fee'] = $record->total_amount,
|
||||
default => null,
|
||||
};
|
||||
$summary['subscription_total'] += $record->total_amount;
|
||||
} elseif ($record->billing_type === 'usage') {
|
||||
if ($record->service_type === 'tax_invoice') {
|
||||
$summary['tax_invoice_count'] = $record->quantity;
|
||||
$summary['tax_invoice_amount'] = $record->total_amount;
|
||||
}
|
||||
$summary['usage_total'] += $record->total_amount;
|
||||
}
|
||||
}
|
||||
|
||||
$summary['grand_total'] = $summary['subscription_total'] + $summary['usage_total'];
|
||||
|
||||
BarobillMonthlySummary::withoutGlobalScopes()->updateOrCreate(
|
||||
['member_id' => $memberId, 'billing_month' => $billingMonth],
|
||||
$summary
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 월별 과금 현황 목록
|
||||
*/
|
||||
public function getMonthlyBillingList(string $billingMonth, ?int $tenantId = null): array
|
||||
{
|
||||
$query = BarobillMonthlySummary::withoutGlobalScopes()
|
||||
->with('member')
|
||||
->where('billing_month', $billingMonth);
|
||||
|
||||
if ($tenantId) {
|
||||
$memberIds = BarobillMember::withoutGlobalScopes()
|
||||
->where('tenant_id', $tenantId)
|
||||
->pluck('id');
|
||||
$query->whereIn('member_id', $memberIds);
|
||||
}
|
||||
|
||||
return $query->get()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 월별 합계
|
||||
*/
|
||||
public function getMonthlyTotal(string $billingMonth, ?int $tenantId = null): array
|
||||
{
|
||||
$query = BarobillMonthlySummary::withoutGlobalScopes()
|
||||
->where('billing_month', $billingMonth);
|
||||
|
||||
if ($tenantId) {
|
||||
$memberIds = BarobillMember::withoutGlobalScopes()
|
||||
->where('tenant_id', $tenantId)
|
||||
->pluck('id');
|
||||
$query->whereIn('member_id', $memberIds);
|
||||
}
|
||||
|
||||
return [
|
||||
'member_count' => $query->count(),
|
||||
'bank_account_fee' => (clone $query)->sum('bank_account_fee'),
|
||||
'card_fee' => (clone $query)->sum('card_fee'),
|
||||
'hometax_fee' => (clone $query)->sum('hometax_fee'),
|
||||
'subscription_total' => (clone $query)->sum('subscription_total'),
|
||||
'tax_invoice_count' => (clone $query)->sum('tax_invoice_count'),
|
||||
'tax_invoice_amount' => (clone $query)->sum('tax_invoice_amount'),
|
||||
'usage_total' => (clone $query)->sum('usage_total'),
|
||||
'grand_total' => (clone $query)->sum('grand_total'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 연간 과금 추이
|
||||
*/
|
||||
public function getYearlyTrend(int $year, ?int $tenantId = null): array
|
||||
{
|
||||
$trend = [];
|
||||
for ($month = 1; $month <= 12; $month++) {
|
||||
$billingMonth = sprintf('%d-%02d', $year, $month);
|
||||
$total = $this->getMonthlyTotal($billingMonth, $tenantId);
|
||||
$trend[] = [
|
||||
'billing_month' => $billingMonth,
|
||||
...$total,
|
||||
];
|
||||
}
|
||||
|
||||
return $trend;
|
||||
}
|
||||
|
||||
/**
|
||||
* 과금 정책 목록
|
||||
*/
|
||||
public function getPricingPolicies(): array
|
||||
{
|
||||
return BarobillPricingPolicy::withoutGlobalScopes()
|
||||
->orderBy('sort_order')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 과금 정책 수정
|
||||
*/
|
||||
public function updatePricingPolicy(int $id, array $data): BarobillPricingPolicy
|
||||
{
|
||||
$policy = BarobillPricingPolicy::withoutGlobalScopes()->findOrFail($id);
|
||||
$policy->update($data);
|
||||
|
||||
return $policy->fresh();
|
||||
}
|
||||
|
||||
private function getServiceLabel(string $serviceType): string
|
||||
{
|
||||
return match ($serviceType) {
|
||||
'bank_account' => '계좌조회',
|
||||
'card' => '법인카드',
|
||||
'hometax' => '홈택스',
|
||||
'tax_invoice' => '전자세금계산서',
|
||||
default => $serviceType,
|
||||
};
|
||||
}
|
||||
}
|
||||
163
app/Services/Barobill/BarobillUsageService.php
Normal file
163
app/Services/Barobill/BarobillUsageService.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Barobill;
|
||||
|
||||
use App\Models\Barobill\BarobillBankTransaction;
|
||||
use App\Models\Barobill\BarobillCardTransaction;
|
||||
use App\Models\Barobill\BarobillMember;
|
||||
use App\Models\Barobill\BarobillPricingPolicy;
|
||||
use App\Models\Barobill\HometaxInvoice;
|
||||
use App\Services\Service;
|
||||
|
||||
class BarobillUsageService extends Service
|
||||
{
|
||||
/**
|
||||
* 전체 회원사 사용량 목록
|
||||
*/
|
||||
public function getUsageList(string $startDate, string $endDate, ?int $tenantId = null): array
|
||||
{
|
||||
$query = BarobillMember::withoutGlobalScopes()
|
||||
->where('status', 'active');
|
||||
|
||||
if ($tenantId) {
|
||||
$query->where('tenant_id', $tenantId);
|
||||
}
|
||||
|
||||
$members = $query->get();
|
||||
$usageList = [];
|
||||
|
||||
foreach ($members as $member) {
|
||||
$usageList[] = $this->getMemberUsage($member, $startDate, $endDate);
|
||||
}
|
||||
|
||||
return $usageList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 단일 회원사 사용량 상세
|
||||
*/
|
||||
public function getMemberUsage(BarobillMember $member, string $startDate, string $endDate): array
|
||||
{
|
||||
$taxInvoiceCount = $this->getTaxInvoiceCount($member, $startDate, $endDate);
|
||||
$bankAccountCount = $this->getBankAccountCount($member);
|
||||
$cardCount = $this->getCardCount($member);
|
||||
$hometaxCount = $this->getHometaxCount($member, $startDate, $endDate);
|
||||
|
||||
$taxInvoiceBilling = $this->calculateBillingByPolicy('tax_invoice', $taxInvoiceCount);
|
||||
|
||||
return [
|
||||
'member_id' => $member->id,
|
||||
'tenant_id' => $member->tenant_id,
|
||||
'biz_no' => $member->biz_no,
|
||||
'corp_name' => $member->corp_name,
|
||||
'barobill_id' => $member->barobill_id,
|
||||
'server_mode' => $member->server_mode ?? 'test',
|
||||
'tax_invoice_count' => $taxInvoiceCount,
|
||||
'bank_account_count' => $bankAccountCount,
|
||||
'card_count' => $cardCount,
|
||||
'hometax_count' => $hometaxCount,
|
||||
'tax_invoice_billing' => $taxInvoiceBilling,
|
||||
'total_amount' => $taxInvoiceBilling['billable_amount'] ?? 0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용량 통계 집계
|
||||
*/
|
||||
public function aggregateStats(array $usageList): array
|
||||
{
|
||||
$stats = [
|
||||
'total_members' => count($usageList),
|
||||
'total_tax_invoice_count' => 0,
|
||||
'total_bank_account_count' => 0,
|
||||
'total_card_count' => 0,
|
||||
'total_hometax_count' => 0,
|
||||
'total_amount' => 0,
|
||||
];
|
||||
|
||||
foreach ($usageList as $usage) {
|
||||
$stats['total_tax_invoice_count'] += $usage['tax_invoice_count'];
|
||||
$stats['total_bank_account_count'] += $usage['bank_account_count'];
|
||||
$stats['total_card_count'] += $usage['card_count'];
|
||||
$stats['total_hometax_count'] += $usage['hometax_count'];
|
||||
$stats['total_amount'] += $usage['total_amount'];
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 과금 정책 정보
|
||||
*/
|
||||
public static function getPriceInfo(): array
|
||||
{
|
||||
$policies = BarobillPricingPolicy::withoutGlobalScopes()
|
||||
->where('is_active', true)
|
||||
->orderBy('sort_order')
|
||||
->get();
|
||||
|
||||
$info = [];
|
||||
foreach ($policies as $policy) {
|
||||
$info[$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,
|
||||
];
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 정책 기반 과금액 계산
|
||||
*/
|
||||
public static function calculateBillingByPolicy(string $serviceType, int $usageCount): array
|
||||
{
|
||||
$policy = BarobillPricingPolicy::withoutGlobalScopes()
|
||||
->where('service_type', $serviceType)
|
||||
->where('is_active', true)
|
||||
->first();
|
||||
|
||||
if (! $policy) {
|
||||
return ['free_count' => $usageCount, 'billable_count' => 0, 'billable_amount' => 0];
|
||||
}
|
||||
|
||||
return $policy->calculateBilling($usageCount);
|
||||
}
|
||||
|
||||
protected function getTaxInvoiceCount(BarobillMember $member, string $startDate, string $endDate): int
|
||||
{
|
||||
return HometaxInvoice::withoutGlobalScopes()
|
||||
->where('tenant_id', $member->tenant_id)
|
||||
->where('invoice_type', 'sales')
|
||||
->whereBetween('write_date', [$startDate, $endDate])
|
||||
->count();
|
||||
}
|
||||
|
||||
protected function getBankAccountCount(BarobillMember $member): int
|
||||
{
|
||||
return BarobillBankTransaction::withoutGlobalScopes()
|
||||
->where('tenant_id', $member->tenant_id)
|
||||
->distinct('bank_account_num')
|
||||
->count('bank_account_num');
|
||||
}
|
||||
|
||||
protected function getCardCount(BarobillMember $member): int
|
||||
{
|
||||
return BarobillCardTransaction::withoutGlobalScopes()
|
||||
->where('tenant_id', $member->tenant_id)
|
||||
->distinct('card_num')
|
||||
->count('card_num');
|
||||
}
|
||||
|
||||
protected function getHometaxCount(BarobillMember $member, string $startDate, string $endDate): int
|
||||
{
|
||||
return HometaxInvoice::withoutGlobalScopes()
|
||||
->where('tenant_id', $member->tenant_id)
|
||||
->whereBetween('write_date', [$startDate, $endDate])
|
||||
->count();
|
||||
}
|
||||
}
|
||||
@@ -18,12 +18,14 @@
|
||||
use App\Http\Controllers\Api\V1\BankAccountController;
|
||||
use App\Http\Controllers\Api\V1\BankTransactionController;
|
||||
use App\Http\Controllers\Api\V1\BarobillBankTransactionController;
|
||||
use App\Http\Controllers\Api\V1\BarobillBillingController;
|
||||
use App\Http\Controllers\Api\V1\BarobillCardTransactionController;
|
||||
use App\Http\Controllers\Api\V1\BarobillController;
|
||||
use App\Http\Controllers\Api\V1\BarobillKakaotalkController;
|
||||
use App\Http\Controllers\Api\V1\BarobillSettingController;
|
||||
use App\Http\Controllers\Api\V1\BarobillSmsController;
|
||||
use App\Http\Controllers\Api\V1\BarobillSyncController;
|
||||
use App\Http\Controllers\Api\V1\BarobillUsageController;
|
||||
use App\Http\Controllers\Api\V1\BillController;
|
||||
use App\Http\Controllers\Api\V1\CalendarController;
|
||||
use App\Http\Controllers\Api\V1\CardController;
|
||||
@@ -423,6 +425,27 @@
|
||||
Route::get('/send-state/{sendKey}', [BarobillSmsController::class, 'sendState'])->name('v1.barobill.sms.send-state');
|
||||
});
|
||||
|
||||
// Barobill Billing API (바로빌 과금 관리)
|
||||
Route::prefix('barobill/billing')->group(function () {
|
||||
Route::get('/subscriptions', [BarobillBillingController::class, 'subscriptions'])->name('v1.barobill.billing.subscriptions');
|
||||
Route::post('/subscriptions', [BarobillBillingController::class, 'saveSubscription'])->name('v1.barobill.billing.subscriptions.store');
|
||||
Route::delete('/subscriptions/{id}', [BarobillBillingController::class, 'cancelSubscription'])->whereNumber('id')->name('v1.barobill.billing.subscriptions.cancel');
|
||||
Route::get('/list', [BarobillBillingController::class, 'billingList'])->name('v1.barobill.billing.list');
|
||||
Route::get('/stats', [BarobillBillingController::class, 'billingStats'])->name('v1.barobill.billing.stats');
|
||||
Route::post('/process', [BarobillBillingController::class, 'processBilling'])->name('v1.barobill.billing.process');
|
||||
Route::get('/yearly-trend', [BarobillBillingController::class, 'yearlyTrend'])->name('v1.barobill.billing.yearly-trend');
|
||||
Route::get('/pricing-policies', [BarobillBillingController::class, 'pricingPolicies'])->name('v1.barobill.billing.pricing-policies');
|
||||
Route::put('/pricing-policies/{id}', [BarobillBillingController::class, 'updatePricingPolicy'])->whereNumber('id')->name('v1.barobill.billing.pricing-policies.update');
|
||||
});
|
||||
|
||||
// Barobill Usage API (바로빌 사용량 조회)
|
||||
Route::prefix('barobill/usage')->group(function () {
|
||||
Route::get('', [BarobillUsageController::class, 'index'])->name('v1.barobill.usage.index');
|
||||
Route::get('/stats', [BarobillUsageController::class, 'stats'])->name('v1.barobill.usage.stats');
|
||||
Route::get('/price-info', [BarobillUsageController::class, 'priceInfo'])->name('v1.barobill.usage.price-info');
|
||||
Route::get('/{memberId}', [BarobillUsageController::class, 'show'])->whereNumber('memberId')->name('v1.barobill.usage.show');
|
||||
});
|
||||
|
||||
// Bad Debt API (악성채권 추심관리)
|
||||
Route::prefix('bad-debts')->group(function () {
|
||||
Route::get('', [BadDebtController::class, 'index'])->name('v1.bad-debts.index');
|
||||
|
||||
Reference in New Issue
Block a user