diff --git a/app/Http/Controllers/Finance/SettlementController.php b/app/Http/Controllers/Finance/SettlementController.php index 2b6f6b61..061dc8e7 100644 --- a/app/Http/Controllers/Finance/SettlementController.php +++ b/app/Http/Controllers/Finance/SettlementController.php @@ -269,7 +269,40 @@ public function customerTab(Request $request): View */ public function subscriptionTab(Request $request): View { - return view('finance.settlement.partials.subscription-tab'); + // subscription_fee > 0인 계약상품이 있는 관리건 조회 + $query = SalesTenantManagement::with([ + 'tenant', 'tenantProspect', 'salesPartner.user', + 'manager', 'contractProducts.product', 'contractProducts.category', + ]) + ->contracted() + ->whereHas('contractProducts', fn($q) => $q->where('subscription_fee', '>', 0)); + + // 검색 필터 + if ($search = $request->input('search')) { + $query->where(function ($q) use ($search) { + $q->whereHas('tenant', fn($t) => $t->where('company_name', 'like', "%{$search}%")) + ->orWhereHas('tenantProspect', fn($t) => $t->where('company_name', 'like', "%{$search}%")); + }); + } + + // 상태 필터 + if ($status = $request->input('status')) { + if ($status !== 'all') { + $query->where('status', $status); + } + } + + $managements = $query->orderBy('contracted_at', 'desc')->get(); + + // 통계 계산 + $stats = [ + 'activeCount' => $managements->count(), + 'monthlyRecurring' => $managements->sum(fn($m) => $m->contractProducts->sum('subscription_fee')), + 'totalProducts' => $managements->sum(fn($m) => $m->contractProducts->where('subscription_fee', '>', 0)->count()), + ]; + $stats['yearlyRecurring'] = $stats['monthlyRecurring'] * 12; + + return view('finance.settlement.partials.subscription-tab', compact('managements', 'stats')); } /** diff --git a/resources/views/finance/settlement/index.blade.php b/resources/views/finance/settlement/index.blade.php index ccd7111d..f12fcbef 100644 --- a/resources/views/finance/settlement/index.blade.php +++ b/resources/views/finance/settlement/index.blade.php @@ -369,55 +369,6 @@ function onTenantSelect(managementId) { }); } - // 구독관리 탭 Alpine 컴포넌트 - function subscriptionManager() { - return { - items: [], stats: { activeCount: 0, monthlyRecurring: 0, yearlyRecurring: 0, totalUsers: 0 }, - loading: false, saving: false, showModal: false, modalMode: 'add', editingId: null, - searchTerm: '', filterStatus: 'all', filterPlan: 'all', - form: { customer: '', plan: 'Starter', monthlyFee: 0, billingCycle: 'monthly', startDate: '', nextBilling: '', status: 'active', users: 0, memo: '' }, - formatCurrency(val) { return Number(val || 0).toLocaleString(); }, - filteredItems() { - return this.items.filter(item => { - const matchSearch = !this.searchTerm || (item.customer || '').toLowerCase().includes(this.searchTerm.toLowerCase()); - const matchStatus = this.filterStatus === 'all' || item.status === this.filterStatus; - const matchPlan = this.filterPlan === 'all' || item.plan === this.filterPlan; - return matchSearch && matchStatus && matchPlan; - }); - }, - async fetchData() { - this.loading = true; - try { - const res = await fetch('/finance/subscriptions/list'); - const data = await res.json(); - if (data.success) { this.items = data.data; this.stats = data.stats; } - } finally { this.loading = false; } - }, - openModal(mode, item = null) { - this.modalMode = mode; this.editingId = item?.id || null; - this.form = item ? { ...item } : { customer: '', plan: 'Starter', monthlyFee: 0, billingCycle: 'monthly', startDate: new Date().toISOString().split('T')[0], nextBilling: '', status: 'active', users: 0, memo: '' }; - this.showModal = true; - }, - async saveItem() { - if (!this.form.customer || !this.form.monthlyFee) { alert('필수 항목을 입력해주세요.'); return; } - this.saving = true; - try { - const url = this.modalMode === 'add' ? '/finance/subscriptions/store' : '/finance/subscriptions/' + this.editingId; - const body = { ...this.form, monthlyFee: parseInt(this.form.monthlyFee) || 0, users: parseInt(this.form.users) || 0 }; - const res = await fetch(url, { method: this.modalMode === 'add' ? 'POST' : 'PUT', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify(body) }); - const data = await res.json(); - if (!res.ok) { alert(data.errors ? Object.values(data.errors).flat().join('\n') : data.message || '저장 실패'); return; } - this.showModal = false; this.fetchData(); - } finally { this.saving = false; } - }, - async deleteItem(id) { - if (!confirm('정말 삭제하시겠습니까?')) return; - await fetch('/finance/subscriptions/' + id, { method: 'DELETE', headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content } }); - this.fetchData(); - } - }; - } - // 초기 탭이 기본(commission)이 아닌 경우 HTMX 콘텐츠 자동 로드 document.addEventListener('DOMContentLoaded', function() { const initialTab = '{{ $initialTab }}'; diff --git a/resources/views/finance/settlement/partials/subscription-tab.blade.php b/resources/views/finance/settlement/partials/subscription-tab.blade.php index fdc65ffc..13d232fb 100644 --- a/resources/views/finance/settlement/partials/subscription-tab.blade.php +++ b/resources/views/finance/settlement/partials/subscription-tab.blade.php @@ -1,54 +1,52 @@ -{{-- 구독관리 탭 (Blade + Alpine.js) --}} -
+{{-- 구독관리 탭 (서버 렌더링 Blade) --}} +
{{-- 통계 카드 --}} -
-
-

활성 구독

-

0개

-
+
+

활성 구독

+

{{ $stats['activeCount'] }}건

+
+

월 반복 수익(MRR)

-

0원

+

{{ number_format($stats['monthlyRecurring']) }}원

연 반복 수익(ARR)

-

0원

+

{{ number_format($stats['yearlyRecurring']) }}원

-

총 사용자

-

0명

+

구독 상품

+

{{ $stats['totalProducts'] }}개

- {{-- 필터 + 등록 버튼 --}} -
-
-
- - -
+ {{-- 검색/필터 --}} +
+
- - + +
- - - - + + +
- -
+
+ +
+
{{-- 테이블 --}} @@ -57,97 +55,102 @@ - - - - - - - - + + + + + + + - - - - + @empty + + + + @endforelse
고객사플랜월 요금결제주기다음 결제사용자상태관리고객사담당 파트너구독 상품월 구독료계약일개발상태입금상태
+ 구독 상품이 있는 고객사가 없습니다. +
- - {{-- 모달 --}} -
-
-
-

- -
-
-
-
-
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
- -
-
-
-
-
- - -
-
-
-