diff --git a/app/Http/Controllers/Api/Admin/Barobill/BarobillBillingController.php b/app/Http/Controllers/Api/Admin/Barobill/BarobillBillingController.php index ab6822a9..70479c97 100644 --- a/app/Http/Controllers/Api/Admin/Barobill/BarobillBillingController.php +++ b/app/Http/Controllers/Api/Admin/Barobill/BarobillBillingController.php @@ -6,6 +6,7 @@ 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\Barobill\BarobillBillingService; use Illuminate\Http\JsonResponse; @@ -376,4 +377,83 @@ public function export(Request $request) return response()->stream($callback, 200, $headers); } + + // ======================================== + // 과금 정책 관리 + // ======================================== + + /** + * 과금 정책 목록 조회 + */ + public function pricingPolicies(Request $request): JsonResponse|Response + { + $policies = BarobillPricingPolicy::orderBy('sort_order')->get(); + + if ($request->header('HX-Request')) { + return response( + view('barobill.billing.partials.pricing-policies-table', [ + 'policies' => $policies, + ])->render(), + 200, + ['Content-Type' => 'text/html'] + ); + } + + return response()->json([ + 'success' => true, + 'data' => $policies, + ]); + } + + /** + * 과금 정책 수정 + */ + public function updatePricingPolicy(Request $request, int $id): JsonResponse + { + $policy = BarobillPricingPolicy::find($id); + if (!$policy) { + return response()->json([ + 'success' => false, + 'message' => '정책을 찾을 수 없습니다.', + ], 404); + } + + $validated = $request->validate([ + 'name' => 'nullable|string|max:100', + 'description' => 'nullable|string|max:255', + 'free_quota' => 'nullable|integer|min:0', + 'free_quota_unit' => 'nullable|string|max:20', + 'additional_unit' => 'nullable|integer|min:1', + 'additional_unit_label' => 'nullable|string|max:20', + 'additional_price' => 'nullable|integer|min:0', + 'is_active' => 'nullable|boolean', + ]); + + $policy->update($validated); + + return response()->json([ + 'success' => true, + 'message' => '정책이 수정되었습니다.', + 'data' => $policy->fresh(), + ]); + } + + /** + * 과금 정책 단일 조회 + */ + public function getPricingPolicy(int $id): JsonResponse + { + $policy = BarobillPricingPolicy::find($id); + if (!$policy) { + return response()->json([ + 'success' => false, + 'message' => '정책을 찾을 수 없습니다.', + ], 404); + } + + return response()->json([ + 'success' => true, + 'data' => $policy, + ]); + } } diff --git a/app/Models/Barobill/BarobillPricingPolicy.php b/app/Models/Barobill/BarobillPricingPolicy.php new file mode 100644 index 00000000..88164d33 --- /dev/null +++ b/app/Models/Barobill/BarobillPricingPolicy.php @@ -0,0 +1,157 @@ + 'integer', + 'additional_unit' => 'integer', + 'additional_price' => 'integer', + 'is_active' => 'boolean', + 'sort_order' => 'integer', + ]; + + /** + * 서비스 유형 상수 + */ + public const TYPE_CARD = 'card'; + public const TYPE_TAX_INVOICE = 'tax_invoice'; + public const TYPE_BANK_ACCOUNT = 'bank_account'; + + /** + * 서비스 유형 라벨 + */ + public static function getServiceTypeLabels(): array + { + return [ + self::TYPE_CARD => '법인카드 등록', + self::TYPE_TAX_INVOICE => '계산서 발행', + self::TYPE_BANK_ACCOUNT => '계좌조회 수집', + ]; + } + + /** + * 서비스 유형 라벨 Accessor + */ + public function getServiceTypeLabelAttribute(): string + { + return self::getServiceTypeLabels()[$this->service_type] ?? $this->service_type; + } + + /** + * 활성화된 정책만 조회 + */ + public function scopeActive($query) + { + return $query->where('is_active', true); + } + + /** + * 서비스 유형별 정책 조회 + */ + public static function getByServiceType(string $serviceType): ?self + { + return static::where('service_type', $serviceType) + ->where('is_active', true) + ->first(); + } + + /** + * 모든 활성 정책 조회 (캐시) + */ + public static function getAllActive(): \Illuminate\Database\Eloquent\Collection + { + return static::active() + ->orderBy('sort_order') + ->get(); + } + + /** + * 추가 과금액 계산 + * + * @param int $usageCount 사용량 + * @return array ['free_count' => int, 'billable_count' => int, 'billable_amount' => int] + */ + public function calculateBilling(int $usageCount): array + { + // 무료 제공량 초과분 + $excessCount = max(0, $usageCount - $this->free_quota); + + if ($excessCount === 0 || $this->additional_price === 0) { + return [ + 'free_count' => min($usageCount, $this->free_quota), + 'billable_count' => 0, + 'billable_amount' => 0, + ]; + } + + // 추가 과금 단위에 따른 계산 + // 예: 50건 단위면 51건도 100건도 동일하게 1단위 과금 + $billableUnits = ceil($excessCount / $this->additional_unit); + $billableAmount = (int) ($billableUnits * $this->additional_price); + + return [ + 'free_count' => $this->free_quota, + 'billable_count' => $excessCount, + 'billable_amount' => $billableAmount, + ]; + } + + /** + * 정책 설명 문자열 생성 + */ + public function getPolicyDescriptionAttribute(): string + { + $parts = []; + + if ($this->free_quota > 0) { + $parts[] = "기본 {$this->free_quota}{$this->free_quota_unit} 무료"; + } + + if ($this->additional_price > 0) { + $priceFormatted = number_format($this->additional_price); + if ($this->additional_unit > 1) { + $parts[] = "추가 {$this->additional_unit}{$this->additional_unit_label} 단위 {$priceFormatted}원"; + } else { + $parts[] = "추가 1{$this->additional_unit_label}당 {$priceFormatted}원"; + } + } + + return implode(', ', $parts); + } +} diff --git a/app/Services/Barobill/BarobillUsageService.php b/app/Services/Barobill/BarobillUsageService.php index 583817fc..28b73a96 100644 --- a/app/Services/Barobill/BarobillUsageService.php +++ b/app/Services/Barobill/BarobillUsageService.php @@ -3,6 +3,7 @@ namespace App\Services\Barobill; use App\Models\Barobill\BarobillMember; +use App\Models\Barobill\BarobillPricingPolicy; use Illuminate\Support\Facades\Log; use Illuminate\Support\Collection; @@ -12,22 +13,13 @@ * 바로빌 API에는 직접적인 "사용량 집계" API가 없으므로, * 각 서비스별 내역 조회 API를 통해 건수를 집계합니다. * - * 서비스 단가 (바로빌 기준): - * - 전자세금계산서: 건당 100원 - * - 계좌조회: 건당 10원 - * - 카드사용내역: 건당 10원 - * - 홈텍스매입/매출: 건당 10원 + * 과금 정책 (DB 관리): + * - 전자세금계산서: 기본 100건 무료, 추가 50건 단위 5,000원 + * - 계좌조회: 기본 1계좌 무료, 추가 1계좌당 10,000원 + * - 카드등록: 기본 3장 무료, 추가 1장당 5,000원 */ class BarobillUsageService { - /** - * 서비스별 단가 (원) - */ - public const PRICE_TAX_INVOICE = 100; // 전자세금계산서 - public const PRICE_BANK_ACCOUNT = 10; // 계좌조회 - public const PRICE_CARD = 10; // 카드사용내역 - public const PRICE_HOMETAX = 10; // 홈텍스 매입/매출 - protected BarobillService $barobillService; public function __construct(BarobillService $barobillService) @@ -258,31 +250,87 @@ protected function getHometaxCount(BarobillMember $member, string $startDate, st } /** - * 서비스별 단가 정보 반환 + * 서비스별 과금 정책 정보 반환 (DB에서 조회) */ public static function getPriceInfo(): array { - return [ + $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' => '전자세금계산서', - 'price' => self::PRICE_TAX_INVOICE, - 'unit' => '건', + 'name' => '계산서 발행', + 'free_quota' => 100, + 'free_quota_unit' => '건', + 'additional_unit' => 50, + 'additional_unit_label' => '건', + 'additional_price' => 5000, + 'description' => '기본 100건 무료, 추가 50건 단위 5,000원', ], 'bank_account' => [ - 'name' => '계좌조회', - 'price' => self::PRICE_BANK_ACCOUNT, - 'unit' => '건', + 'name' => '계좌조회 수집', + 'free_quota' => 1, + 'free_quota_unit' => '개', + 'additional_unit' => 1, + 'additional_unit_label' => '계좌', + 'additional_price' => 10000, + 'description' => '기본 1계좌 무료, 추가 1계좌당 10,000원', ], 'card' => [ - 'name' => '카드사용내역', - 'price' => self::PRICE_CARD, - 'unit' => '건', - ], - 'hometax' => [ - 'name' => '홈텍스 매입/매출', - 'price' => self::PRICE_HOMETAX, - 'unit' => '건', + 'name' => '법인카드 등록', + 'free_quota' => 3, + 'free_quota_unit' => '장', + 'additional_unit' => 1, + 'additional_unit_label' => '장', + 'additional_price' => 5000, + 'description' => '기본 3장 무료, 추가 1장당 5,000원', ], ]; + + // 없는 정책은 기본값으로 채움 + 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); } } diff --git a/database/seeders/BarobillPricingPolicySeeder.php b/database/seeders/BarobillPricingPolicySeeder.php new file mode 100644 index 00000000..7ba8202f --- /dev/null +++ b/database/seeders/BarobillPricingPolicySeeder.php @@ -0,0 +1,63 @@ + 'card', + 'name' => '법인카드 등록', + 'description' => '법인카드 등록 기본 3장 제공, 추가 시 장당 과금', + 'free_quota' => 3, + 'free_quota_unit' => '장', + 'additional_unit' => 1, + 'additional_unit_label' => '장', + 'additional_price' => 5000, + 'is_active' => true, + 'sort_order' => 1, + ], + [ + 'service_type' => 'tax_invoice', + 'name' => '계산서 발행', + 'description' => '전자세금계산서 발행 기본 100건 제공, 추가 50건 단위 과금', + 'free_quota' => 100, + 'free_quota_unit' => '건', + 'additional_unit' => 50, + 'additional_unit_label' => '건', + 'additional_price' => 5000, + 'is_active' => true, + 'sort_order' => 2, + ], + [ + 'service_type' => 'bank_account', + 'name' => '계좌조회 수집', + 'description' => '주거래 통장 계좌 기본 1개 제공, 추가 계좌당 과금', + 'free_quota' => 1, + 'free_quota_unit' => '개', + 'additional_unit' => 1, + 'additional_unit_label' => '계좌', + 'additional_price' => 10000, + 'is_active' => true, + 'sort_order' => 3, + ], + ]; + + foreach ($policies as $policy) { + BarobillPricingPolicy::updateOrCreate( + ['service_type' => $policy['service_type']], + $policy + ); + } + + $this->command->info('바로빌 과금 정책 시딩 완료!'); + } +} diff --git a/resources/views/barobill/billing/index.blade.php b/resources/views/barobill/billing/index.blade.php index 30637a00..27d2616d 100644 --- a/resources/views/barobill/billing/index.blade.php +++ b/resources/views/barobill/billing/index.blade.php @@ -80,6 +80,11 @@ class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg transitio + @if($isHeadquarters ?? false) + + @endif @@ -167,6 +172,21 @@ class="bg-white rounded-lg shadow-sm overflow-hidden flex-1 flex flex-col min-h- + + +@if($isHeadquarters ?? false) +
시더를 실행하여 기본 정책을 등록해주세요.
+| + 서비스 + | ++ 기본 제공량 + | ++ 추가 과금 + | ++ 상태 + | ++ 관리 + | +
|---|---|---|---|---|
|
+
+
+ {{ $policy->name }}
+
+
+ @if($policy->description)
+ {{ $policy->description }} + @endif + |
+ + {{ number_format($policy->free_quota) }}{{ $policy->free_quota_unit }} + 무료 + | ++ @if($policy->additional_price > 0) + @if($policy->additional_unit > 1) + {{ number_format($policy->additional_unit) }}{{ $policy->additional_unit_label }} 단위 + @else + 1{{ $policy->additional_unit_label }}당 + @endif + {{ number_format($policy->additional_price) }}원 + @else + - + @endif + | ++ + {{ $policy->is_active ? '활성' : '비활성' }} + + | ++ + | +
+ + 이 정책은 월별 과금 처리 시 적용됩니다. 기본 제공량을 초과한 사용량에 대해 추가 과금이 발생합니다. +
+