feat:수당 지급 탭 + 수당지급현황통계 페이지 추가
- 정산관리에 수당 지급 탭 추가 (파트너별 그룹핑 지급 대기 목록) - 파트너별 상세 건 목록 HTMX 확장 기능 - 수당지급현황통계 페이지 (Chart.js 4개 차트 + 월별 요약 테이블) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -180,6 +180,143 @@ public function subscriptionTab(Request $request): View
|
||||
return view('finance.settlement.partials.subscription-tab');
|
||||
}
|
||||
|
||||
/**
|
||||
* 수당 지급 탭 (파트너별 그룹핑)
|
||||
*/
|
||||
public function paymentTab(Request $request): View
|
||||
{
|
||||
// approved 상태 수당을 partner_id 기준 GROUP BY
|
||||
$partnerPayments = SalesCommission::where('status', SalesCommission::STATUS_APPROVED)
|
||||
->selectRaw('
|
||||
partner_id,
|
||||
GROUP_CONCAT(id) as commission_ids,
|
||||
COUNT(*) as count,
|
||||
SUM(partner_commission) as partner_total,
|
||||
SUM(manager_commission) as manager_total,
|
||||
SUM(COALESCE(referrer_commission, 0)) as referrer_total
|
||||
')
|
||||
->groupBy('partner_id')
|
||||
->get();
|
||||
|
||||
// 파트너 정보 eager load
|
||||
$partners = SalesPartner::with('user')
|
||||
->whereIn('id', $partnerPayments->pluck('partner_id'))
|
||||
->get()
|
||||
->keyBy('id');
|
||||
|
||||
// 통계 카드 데이터
|
||||
$now = now();
|
||||
$paymentStats = [
|
||||
'waiting_count' => $partnerPayments->sum('count'),
|
||||
'waiting_amount' => $partnerPayments->sum(fn ($p) => $p->partner_total + $p->manager_total + $p->referrer_total),
|
||||
'this_month_paid_count' => SalesCommission::where('status', SalesCommission::STATUS_PAID)
|
||||
->whereYear('actual_payment_date', $now->year)
|
||||
->whereMonth('actual_payment_date', $now->month)
|
||||
->count(),
|
||||
'this_month_paid_amount' => SalesCommission::where('status', SalesCommission::STATUS_PAID)
|
||||
->whereYear('actual_payment_date', $now->year)
|
||||
->whereMonth('actual_payment_date', $now->month)
|
||||
->selectRaw('SUM(partner_commission + manager_commission + COALESCE(referrer_commission, 0)) as total')
|
||||
->value('total') ?? 0,
|
||||
'partner_total' => $partnerPayments->sum('partner_total'),
|
||||
'manager_referrer_total' => $partnerPayments->sum('manager_total') + $partnerPayments->sum('referrer_total'),
|
||||
];
|
||||
|
||||
return view('finance.settlement.partials.payment-tab', compact('partnerPayments', 'partners', 'paymentStats'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 파트너별 수당 건 상세 (HTMX partial)
|
||||
*/
|
||||
public function paymentPartnerDetail(int $partnerId): View
|
||||
{
|
||||
$commissions = SalesCommission::where('status', SalesCommission::STATUS_APPROVED)
|
||||
->where('partner_id', $partnerId)
|
||||
->with(['management.tenant', 'manager'])
|
||||
->orderBy('scheduled_payment_date')
|
||||
->get();
|
||||
|
||||
return view('finance.settlement.partials.payment-partner-detail', compact('commissions', 'partnerId'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 수당지급현황통계 페이지
|
||||
*/
|
||||
public function paymentStats(Request $request): View|Response
|
||||
{
|
||||
if ($request->header('HX-Request') && !$request->header('HX-Boosted')) {
|
||||
return response('', 200)->header('HX-Redirect', route('finance.settlement.payment-stats'));
|
||||
}
|
||||
|
||||
$year = (int) $request->input('year', now()->year);
|
||||
|
||||
// 통계 카드
|
||||
$totalPaidAmount = SalesCommission::where('status', SalesCommission::STATUS_PAID)
|
||||
->whereYear('actual_payment_date', $year)
|
||||
->selectRaw('SUM(partner_commission + manager_commission + COALESCE(referrer_commission, 0)) as total')
|
||||
->value('total') ?? 0;
|
||||
|
||||
$totalPaidCount = SalesCommission::where('status', SalesCommission::STATUS_PAID)
|
||||
->whereYear('actual_payment_date', $year)
|
||||
->count();
|
||||
|
||||
$activePartners = SalesCommission::where('status', SalesCommission::STATUS_PAID)
|
||||
->whereYear('actual_payment_date', $year)
|
||||
->distinct('partner_id')
|
||||
->count('partner_id');
|
||||
|
||||
$avgCommission = $totalPaidCount > 0 ? round($totalPaidAmount / $totalPaidCount) : 0;
|
||||
|
||||
$statsCards = [
|
||||
'total_paid_amount' => $totalPaidAmount,
|
||||
'total_paid_count' => $totalPaidCount,
|
||||
'active_partners' => $activePartners,
|
||||
'avg_commission' => $avgCommission,
|
||||
];
|
||||
|
||||
// 차트 1 & 4: 월별 지급 추이 (해당 연도)
|
||||
$monthlyTrend = SalesCommission::where('status', SalesCommission::STATUS_PAID)
|
||||
->whereYear('actual_payment_date', $year)
|
||||
->selectRaw("
|
||||
DATE_FORMAT(actual_payment_date, '%Y-%m') as month,
|
||||
SUM(partner_commission) as partner_total,
|
||||
SUM(manager_commission) as manager_total,
|
||||
SUM(COALESCE(referrer_commission, 0)) as referrer_total,
|
||||
COUNT(*) as count
|
||||
")
|
||||
->groupByRaw("DATE_FORMAT(actual_payment_date, '%Y-%m')")
|
||||
->orderBy('month')
|
||||
->get();
|
||||
|
||||
// 차트 2: 수당 유형별 비율
|
||||
$typeRatio = SalesCommission::where('status', SalesCommission::STATUS_PAID)
|
||||
->whereYear('actual_payment_date', $year)
|
||||
->selectRaw("
|
||||
SUM(partner_commission) as partner_total,
|
||||
SUM(manager_commission) as manager_total,
|
||||
SUM(COALESCE(referrer_commission, 0)) as referrer_total
|
||||
")
|
||||
->first();
|
||||
|
||||
// 차트 3: 파트너별 수당 Top 10
|
||||
$topPartners = SalesCommission::where('status', SalesCommission::STATUS_PAID)
|
||||
->whereYear('actual_payment_date', $year)
|
||||
->selectRaw('partner_id, SUM(partner_commission + manager_commission + COALESCE(referrer_commission, 0)) as total')
|
||||
->groupBy('partner_id')
|
||||
->orderByDesc('total')
|
||||
->limit(10)
|
||||
->get();
|
||||
|
||||
$topPartnerNames = SalesPartner::with('user')
|
||||
->whereIn('id', $topPartners->pluck('partner_id'))
|
||||
->get()
|
||||
->keyBy('id');
|
||||
|
||||
return view('finance.settlement.payment-stats', compact(
|
||||
'year', 'statsCards', 'monthlyTrend', 'typeRatio', 'topPartners', 'topPartnerNames'
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 통합 통계 데이터
|
||||
*/
|
||||
|
||||
@@ -42,6 +42,15 @@ class="inline-flex items-center gap-2 px-4 py-2 bg-gray-600 hover:bg-gray-700 te
|
||||
class="border-b-2 py-3 px-4 text-sm font-medium whitespace-nowrap transition-colors">
|
||||
수당 정산
|
||||
</button>
|
||||
<button @click="activeTab = 'payment'"
|
||||
hx-get="{{ route('finance.settlement.payment') }}"
|
||||
hx-target="#payment-content"
|
||||
hx-trigger="click once"
|
||||
hx-indicator="#payment-loading"
|
||||
:class="activeTab === 'payment' ? 'border-indigo-500 text-indigo-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'"
|
||||
class="border-b-2 py-3 px-4 text-sm font-medium whitespace-nowrap transition-colors">
|
||||
수당 지급
|
||||
</button>
|
||||
<button @click="activeTab = 'partner'"
|
||||
hx-get="{{ route('finance.settlement.partner-summary') }}"
|
||||
hx-target="#partner-content"
|
||||
@@ -108,7 +117,20 @@ class="border-b-2 py-3 px-4 text-sm font-medium whitespace-nowrap transition-col
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 탭 2: 파트너별 현황 (HTMX lazy load) --}}
|
||||
{{-- 탭 2: 수당 지급 (HTMX lazy load) --}}
|
||||
<div x-show="activeTab === 'payment'" x-cloak x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100">
|
||||
<div id="payment-content">
|
||||
<div id="payment-loading" class="flex items-center justify-center py-12">
|
||||
<svg class="w-8 h-8 animate-spin text-indigo-600" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<span class="ml-3 text-gray-500">로딩 중...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 탭 3: 파트너별 현황 (HTMX lazy load) --}}
|
||||
<div x-show="activeTab === 'partner'" x-cloak x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100">
|
||||
<div id="partner-content">
|
||||
<div id="partner-loading" class="flex items-center justify-center py-12">
|
||||
@@ -121,7 +143,7 @@ class="border-b-2 py-3 px-4 text-sm font-medium whitespace-nowrap transition-col
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 탭 3: 컨설팅비용 (HTMX lazy load) --}}
|
||||
{{-- 탭 4: 컨설팅비용 (HTMX lazy load) --}}
|
||||
<div x-show="activeTab === 'consulting'" x-cloak x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100">
|
||||
<div id="consulting-content">
|
||||
<div id="consulting-loading" class="flex items-center justify-center py-12">
|
||||
@@ -134,7 +156,7 @@ class="border-b-2 py-3 px-4 text-sm font-medium whitespace-nowrap transition-col
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 탭 4: 고객사정산 (HTMX lazy load) --}}
|
||||
{{-- 탭 5: 고객사정산 (HTMX lazy load) --}}
|
||||
<div x-show="activeTab === 'customer'" x-cloak x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100">
|
||||
<div id="customer-content">
|
||||
<div id="customer-loading" class="flex items-center justify-center py-12">
|
||||
@@ -147,7 +169,7 @@ class="border-b-2 py-3 px-4 text-sm font-medium whitespace-nowrap transition-col
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 탭 5: 구독관리 (HTMX lazy load) --}}
|
||||
{{-- 탭 6: 구독관리 (HTMX lazy load) --}}
|
||||
<div x-show="activeTab === 'subscription'" x-cloak x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100">
|
||||
<div id="subscription-content">
|
||||
<div id="subscription-loading" class="flex items-center justify-center py-12">
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
{{-- 파트너별 수당 건 상세 목록 (HTMX partial) --}}
|
||||
@if ($commissions->isEmpty())
|
||||
<div class="px-8 py-4 text-sm text-gray-500">해당 파트너의 승인 완료 건이 없습니다.</div>
|
||||
@else
|
||||
<div class="px-8 py-3">
|
||||
<table class="min-w-full divide-y divide-gray-200 border border-gray-100 rounded">
|
||||
<thead class="bg-gray-100">
|
||||
<tr>
|
||||
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500">고객사</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500">입금구분</th>
|
||||
<th class="px-3 py-2 text-right text-xs font-medium text-gray-500">입금액</th>
|
||||
<th class="px-3 py-2 text-right text-xs font-medium text-gray-500">파트너수당</th>
|
||||
<th class="px-3 py-2 text-right text-xs font-medium text-gray-500">매니저수당</th>
|
||||
<th class="px-3 py-2 text-right text-xs font-medium text-gray-500">유치수당</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500">지급예정일</th>
|
||||
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500">매니저</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100 bg-white">
|
||||
@foreach ($commissions as $c)
|
||||
<tr class="hover:bg-blue-50">
|
||||
<td class="px-3 py-2 text-sm text-gray-900">
|
||||
{{ $c->management?->tenant?->name ?? '-' }}
|
||||
</td>
|
||||
<td class="px-3 py-2 text-sm">
|
||||
@if ($c->payment_type === 'deposit')
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800">계약금</span>
|
||||
@else
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">잔금</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-3 py-2 text-sm text-right text-gray-900">{{ number_format($c->payment_amount) }}원</td>
|
||||
<td class="px-3 py-2 text-sm text-right text-gray-900">{{ number_format($c->partner_commission) }}원</td>
|
||||
<td class="px-3 py-2 text-sm text-right text-gray-900">{{ number_format($c->manager_commission) }}원</td>
|
||||
<td class="px-3 py-2 text-sm text-right text-gray-900">{{ number_format($c->referrer_commission ?? 0) }}원</td>
|
||||
<td class="px-3 py-2 text-sm text-gray-600">{{ $c->scheduled_payment_date?->format('Y-m-d') ?? '-' }}</td>
|
||||
<td class="px-3 py-2 text-sm text-gray-600">{{ $c->manager?->name ?? '-' }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
@@ -0,0 +1,302 @@
|
||||
{{-- 수당 지급 탭: 통계카드 + 파트너별 지급 대기 목록 --}}
|
||||
<div id="payment-tab-container">
|
||||
{{-- 통계 카드 --}}
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-orange-500">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">지급 대기</p>
|
||||
<p class="text-xl font-bold text-orange-600">{{ number_format($paymentStats['waiting_amount']) }}원</p>
|
||||
</div>
|
||||
<div class="w-10 h-10 bg-orange-100 rounded-full flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-orange-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 mt-1">{{ $paymentStats['waiting_count'] }}건 승인 완료</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-green-500">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">이번달 지급완료</p>
|
||||
<p class="text-xl font-bold text-green-600">{{ number_format($paymentStats['this_month_paid_amount']) }}원</p>
|
||||
</div>
|
||||
<div class="w-10 h-10 bg-green-100 rounded-full flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 mt-1">{{ $paymentStats['this_month_paid_count'] }}건 완료</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-indigo-500">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">파트너 수당</p>
|
||||
<p class="text-xl font-bold text-indigo-600">{{ number_format($paymentStats['partner_total']) }}원</p>
|
||||
</div>
|
||||
<div class="w-10 h-10 bg-indigo-100 rounded-full flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 mt-1">지급 대기 파트너 수당 합계</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-purple-500">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">매니저+유치 수당</p>
|
||||
<p class="text-xl font-bold text-purple-600">{{ number_format($paymentStats['manager_referrer_total']) }}원</p>
|
||||
</div>
|
||||
<div class="w-10 h-10 bg-purple-100 rounded-full flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 mt-1">지급 대기 매니저+유치 합계</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 상단 액션 바 --}}
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center gap-2" id="payment-bulk-actions" style="display: none;">
|
||||
<span class="text-sm text-gray-600"><span id="payment-selected-count">0</span>건 선택</span>
|
||||
<button type="button" onclick="paymentBulkMarkPaid()"
|
||||
class="px-3 py-1.5 bg-green-600 hover:bg-green-700 text-white text-sm rounded-lg transition-colors">
|
||||
선택 지급완료
|
||||
</button>
|
||||
</div>
|
||||
<a href="{{ route('finance.settlement.payment-stats') }}"
|
||||
hx-get="{{ route('finance.settlement.payment-stats') }}"
|
||||
class="inline-flex items-center gap-1 text-sm text-indigo-600 hover:text-indigo-800">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
|
||||
</svg>
|
||||
지급현황통계
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{{-- 파트너별 지급 대기 테이블 --}}
|
||||
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left">
|
||||
<input type="checkbox" onchange="paymentToggleSelectAll(this)" class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500">
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">파트너</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">유형</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">계좌정보</th>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">건수</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">파트너수당</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">매니저수당</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">유치수당</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">총액</th>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">액션</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
@forelse ($partnerPayments as $pp)
|
||||
@php
|
||||
$partner = $partners->get($pp->partner_id);
|
||||
$totalAmount = $pp->partner_total + $pp->manager_total + $pp->referrer_total;
|
||||
@endphp
|
||||
@if ($partner)
|
||||
<tr class="hover:bg-gray-50 cursor-pointer payment-partner-row"
|
||||
data-partner-id="{{ $pp->partner_id }}"
|
||||
data-ids="{{ $pp->commission_ids }}">
|
||||
<td class="px-4 py-3" onclick="event.stopPropagation()">
|
||||
<input type="checkbox"
|
||||
class="payment-checkbox rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
|
||||
value="{{ $pp->commission_ids }}"
|
||||
onchange="paymentUpdateSelection()">
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<button type="button"
|
||||
class="text-left hover:text-indigo-600"
|
||||
hx-get="{{ route('finance.settlement.payment-partner-detail', $pp->partner_id) }}"
|
||||
hx-target="#partner-detail-{{ $pp->partner_id }}"
|
||||
hx-trigger="click once"
|
||||
hx-swap="innerHTML">
|
||||
<div class="font-medium text-gray-900">
|
||||
{{ $partner->user?->name ?? '-' }}
|
||||
<span class="text-xs text-gray-400">({{ $partner->partner_code }})</span>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">
|
||||
@if ($partner->partner_type === 'corporate')
|
||||
{{ $partner->company_name }}
|
||||
@endif
|
||||
</div>
|
||||
</button>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
@if ($partner->partner_type === 'corporate')
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800">단체</span>
|
||||
@else
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-800">개인</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm text-gray-600">
|
||||
@if ($partner->bank_name && $partner->account_number)
|
||||
{{ $partner->bank_name }} {{ $partner->account_number }}
|
||||
<div class="text-xs text-gray-400">{{ $partner->account_holder }}</div>
|
||||
@else
|
||||
<span class="text-xs text-red-500">계좌 미등록</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center text-sm font-medium text-gray-900">{{ $pp->count }}건</td>
|
||||
<td class="px-4 py-3 text-right text-sm text-gray-900">{{ number_format($pp->partner_total) }}원</td>
|
||||
<td class="px-4 py-3 text-right text-sm text-gray-900">{{ number_format($pp->manager_total) }}원</td>
|
||||
<td class="px-4 py-3 text-right text-sm text-gray-900">{{ number_format($pp->referrer_total) }}원</td>
|
||||
<td class="px-4 py-3 text-right text-sm font-bold text-gray-900">{{ number_format($totalAmount) }}원</td>
|
||||
<td class="px-4 py-3 text-center" onclick="event.stopPropagation()">
|
||||
<button type="button"
|
||||
onclick="paymentMarkPaidPartner('{{ $pp->commission_ids }}')"
|
||||
class="px-2 py-1 bg-green-600 hover:bg-green-700 text-white text-xs rounded transition-colors">
|
||||
지급완료
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{{-- 확장 영역: 파트너별 상세 건 목록 --}}
|
||||
<tr class="bg-gray-50">
|
||||
<td colspan="10" class="px-0 py-0">
|
||||
<div id="partner-detail-{{ $pp->partner_id }}"></div>
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="10" class="px-4 py-12 text-center text-gray-500">
|
||||
<svg class="w-12 h-12 mx-auto text-gray-300 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
<p class="text-lg font-medium">지급 대기 건이 없습니다</p>
|
||||
<p class="text-sm mt-1">모든 승인된 수당이 지급 처리되었습니다.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 지급완료 모달 --}}
|
||||
<div id="payment-mark-paid-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center">
|
||||
<div class="bg-white rounded-lg shadow-xl max-w-md w-full mx-4">
|
||||
<div class="p-6 border-b border-gray-200">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-lg font-semibold text-gray-800">지급완료 처리</h3>
|
||||
<button type="button" onclick="closePaymentMarkPaidModal()" class="text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">이체 참조번호</label>
|
||||
<input type="text" id="payment-bank-reference"
|
||||
class="w-full border-gray-300 rounded-lg shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
|
||||
placeholder="이체 참조번호 입력 (선택사항)">
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 mb-4">
|
||||
<span id="payment-modal-count">0</span>건의 수당을 지급완료 처리합니다.
|
||||
</p>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button type="button" onclick="closePaymentMarkPaidModal()"
|
||||
class="px-4 py-2 text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors">
|
||||
취소
|
||||
</button>
|
||||
<button type="button" onclick="confirmPaymentMarkPaid()"
|
||||
class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors">
|
||||
지급완료 확인
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let paymentSelectedIds = [];
|
||||
let paymentPendingIds = [];
|
||||
|
||||
function paymentUpdateSelection() {
|
||||
paymentSelectedIds = [];
|
||||
document.querySelectorAll('.payment-checkbox:checked').forEach(cb => {
|
||||
cb.value.split(',').forEach(id => paymentSelectedIds.push(parseInt(id)));
|
||||
});
|
||||
|
||||
const bulkActions = document.getElementById('payment-bulk-actions');
|
||||
const selectedCount = document.getElementById('payment-selected-count');
|
||||
|
||||
if (paymentSelectedIds.length > 0) {
|
||||
bulkActions.style.display = 'flex';
|
||||
selectedCount.textContent = paymentSelectedIds.length;
|
||||
} else {
|
||||
bulkActions.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function paymentToggleSelectAll(checkbox) {
|
||||
document.querySelectorAll('.payment-checkbox').forEach(cb => { cb.checked = checkbox.checked; });
|
||||
paymentUpdateSelection();
|
||||
}
|
||||
|
||||
function paymentMarkPaidPartner(commissionIds) {
|
||||
paymentPendingIds = commissionIds.split(',').map(Number);
|
||||
document.getElementById('payment-modal-count').textContent = paymentPendingIds.length;
|
||||
document.getElementById('payment-bank-reference').value = '';
|
||||
document.getElementById('payment-mark-paid-modal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function paymentBulkMarkPaid() {
|
||||
if (paymentSelectedIds.length === 0) { alert('선택된 항목이 없습니다.'); return; }
|
||||
paymentPendingIds = [...paymentSelectedIds];
|
||||
document.getElementById('payment-modal-count').textContent = paymentPendingIds.length;
|
||||
document.getElementById('payment-bank-reference').value = '';
|
||||
document.getElementById('payment-mark-paid-modal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closePaymentMarkPaidModal() {
|
||||
document.getElementById('payment-mark-paid-modal').classList.add('hidden');
|
||||
paymentPendingIds = [];
|
||||
}
|
||||
|
||||
function confirmPaymentMarkPaid() {
|
||||
const bankReference = document.getElementById('payment-bank-reference').value;
|
||||
|
||||
fetch('{{ route("finance.sales-commissions.bulk-mark-paid") }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ ids: paymentPendingIds, bank_reference: bankReference })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert(data.message);
|
||||
closePaymentMarkPaidModal();
|
||||
// 수당 지급 탭 새로고침
|
||||
htmx.ajax('GET', '{{ route("finance.settlement.payment") }}', { target: '#payment-content' });
|
||||
} else {
|
||||
alert(data.message || '오류가 발생했습니다.');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('오류가 발생했습니다.');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
349
resources/views/finance/settlement/payment-stats.blade.php
Normal file
349
resources/views/finance/settlement/payment-stats.blade.php
Normal file
@@ -0,0 +1,349 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', '수당지급현황통계')
|
||||
|
||||
@section('content')
|
||||
<div class="px-4 py-6">
|
||||
{{-- 페이지 헤더 --}}
|
||||
<div class="flex flex-col lg:flex-row lg:justify-between lg:items-center gap-4 mb-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-800">수당지급현황통계</h1>
|
||||
<p class="text-sm text-gray-500 mt-1">{{ $year }}년 수당 지급 현황 종합 통계</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="{{ route('finance.settlement', ['tab' => 'payment']) }}"
|
||||
class="inline-flex items-center gap-1 px-4 py-2 text-gray-600 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors text-sm">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
|
||||
</svg>
|
||||
정산관리
|
||||
</a>
|
||||
<form method="GET" action="{{ route('finance.settlement.payment-stats') }}" class="flex items-center gap-2">
|
||||
<select name="year" class="border-gray-300 rounded-lg shadow-sm text-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||
@for ($y = now()->year; $y >= now()->year - 3; $y--)
|
||||
<option value="{{ $y }}" {{ $year == $y ? 'selected' : '' }}>{{ $y }}년</option>
|
||||
@endfor
|
||||
</select>
|
||||
<button type="submit" class="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg transition-colors text-sm">
|
||||
조회
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 통계 카드 --}}
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-green-500">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">총 지급액</p>
|
||||
<p class="text-xl font-bold text-green-600">{{ number_format($statsCards['total_paid_amount']) }}원</p>
|
||||
</div>
|
||||
<div class="w-10 h-10 bg-green-100 rounded-full flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 mt-1">{{ $year }}년 지급 합계</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-blue-500">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">총 지급 건수</p>
|
||||
<p class="text-xl font-bold text-blue-600">{{ number_format($statsCards['total_paid_count']) }}건</p>
|
||||
</div>
|
||||
<div class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 mt-1">{{ $year }}년 총 건수</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-indigo-500">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">지급 파트너 수</p>
|
||||
<p class="text-xl font-bold text-indigo-600">{{ $statsCards['active_partners'] }}명</p>
|
||||
</div>
|
||||
<div class="w-10 h-10 bg-indigo-100 rounded-full flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 mt-1">{{ $year }}년 지급 대상</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-purple-500">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">건당 평균 수당</p>
|
||||
<p class="text-xl font-bold text-purple-600">{{ number_format($statsCards['avg_commission']) }}원</p>
|
||||
</div>
|
||||
<div class="w-10 h-10 bg-purple-100 rounded-full flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 12l3-3 3 3 4-4M8 21l4-4 4 4M3 4h18M4 4h16v12a1 1 0 01-1 1H5a1 1 0 01-1-1V4z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 mt-1">전체 평균</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 차트 영역 --}}
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
{{-- 차트 1: 월별 지급 추이 (Stacked Bar) --}}
|
||||
<div class="bg-white rounded-lg shadow-sm p-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 mb-4">월별 지급 추이</h3>
|
||||
<div class="relative" style="height: 300px;">
|
||||
<canvas id="monthlyTrendChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 차트 2: 수당 유형별 비율 (Doughnut) --}}
|
||||
<div class="bg-white rounded-lg shadow-sm p-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 mb-4">수당 유형별 비율</h3>
|
||||
<div class="relative" style="height: 300px;">
|
||||
<canvas id="typeRatioChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 차트 3: 파트너별 수당 Top 10 (Horizontal Bar) --}}
|
||||
<div class="bg-white rounded-lg shadow-sm p-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 mb-4">파트너별 수당 Top 10</h3>
|
||||
<div class="relative" style="height: 300px;">
|
||||
<canvas id="topPartnersChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 차트 4: 월별 지급 건수 추이 (Line) --}}
|
||||
<div class="bg-white rounded-lg shadow-sm p-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 mb-4">월별 지급 건수 추이</h3>
|
||||
<div class="relative" style="height: 300px;">
|
||||
<canvas id="monthlyCountChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 월별 지급 요약 테이블 --}}
|
||||
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200">
|
||||
<h3 class="text-sm font-semibold text-gray-700">월별 지급 요약</h3>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">월</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">파트너수당</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">매니저수당</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">유치수당</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">합계</th>
|
||||
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">건수</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
@php $grandPartner = 0; $grandManager = 0; $grandReferrer = 0; $grandTotal = 0; $grandCount = 0; @endphp
|
||||
@forelse ($monthlyTrend->reverse() as $row)
|
||||
@php
|
||||
$rowTotal = $row->partner_total + $row->manager_total + $row->referrer_total;
|
||||
$grandPartner += $row->partner_total;
|
||||
$grandManager += $row->manager_total;
|
||||
$grandReferrer += $row->referrer_total;
|
||||
$grandTotal += $rowTotal;
|
||||
$grandCount += $row->count;
|
||||
@endphp
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3 text-sm font-medium text-gray-900">{{ \Carbon\Carbon::parse($row->month . '-01')->format('n월') }}</td>
|
||||
<td class="px-4 py-3 text-sm text-right text-gray-900">{{ number_format($row->partner_total) }}원</td>
|
||||
<td class="px-4 py-3 text-sm text-right text-gray-900">{{ number_format($row->manager_total) }}원</td>
|
||||
<td class="px-4 py-3 text-sm text-right text-gray-900">{{ number_format($row->referrer_total) }}원</td>
|
||||
<td class="px-4 py-3 text-sm text-right font-bold text-gray-900">{{ number_format($rowTotal) }}원</td>
|
||||
<td class="px-4 py-3 text-sm text-center text-gray-900">{{ $row->count }}건</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="px-4 py-8 text-center text-gray-500">{{ $year }}년 지급 데이터가 없습니다.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
@if ($monthlyTrend->isNotEmpty())
|
||||
<tr class="bg-gray-50 font-bold">
|
||||
<td class="px-4 py-3 text-sm text-gray-900">합계</td>
|
||||
<td class="px-4 py-3 text-sm text-right text-gray-900">{{ number_format($grandPartner) }}원</td>
|
||||
<td class="px-4 py-3 text-sm text-right text-gray-900">{{ number_format($grandManager) }}원</td>
|
||||
<td class="px-4 py-3 text-sm text-right text-gray-900">{{ number_format($grandReferrer) }}원</td>
|
||||
<td class="px-4 py-3 text-sm text-right text-indigo-700">{{ number_format($grandTotal) }}원</td>
|
||||
<td class="px-4 py-3 text-sm text-center text-gray-900">{{ $grandCount }}건</td>
|
||||
</tr>
|
||||
@endif
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const formatCurrency = (val) => {
|
||||
if (val >= 1000000) return (val / 1000000).toFixed(1) + 'M';
|
||||
if (val >= 1000) return (val / 1000).toFixed(0) + 'K';
|
||||
return val.toString();
|
||||
};
|
||||
|
||||
const defaultOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { position: 'bottom', labels: { boxWidth: 12, padding: 15, font: { size: 11 } } }
|
||||
}
|
||||
};
|
||||
|
||||
// 월별 데이터
|
||||
const monthlyData = @json($monthlyTrend);
|
||||
const months = monthlyData.map(d => {
|
||||
const parts = d.month.split('-');
|
||||
return parseInt(parts[1]) + '월';
|
||||
});
|
||||
|
||||
// 차트 1: 월별 지급 추이 (Stacked Bar)
|
||||
new Chart(document.getElementById('monthlyTrendChart'), {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: months,
|
||||
datasets: [
|
||||
{
|
||||
label: '파트너수당',
|
||||
data: monthlyData.map(d => Number(d.partner_total)),
|
||||
backgroundColor: 'rgba(79, 70, 229, 0.8)',
|
||||
},
|
||||
{
|
||||
label: '매니저수당',
|
||||
data: monthlyData.map(d => Number(d.manager_total)),
|
||||
backgroundColor: 'rgba(16, 185, 129, 0.8)',
|
||||
},
|
||||
{
|
||||
label: '유치수당',
|
||||
data: monthlyData.map(d => Number(d.referrer_total)),
|
||||
backgroundColor: 'rgba(245, 158, 11, 0.8)',
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
...defaultOptions,
|
||||
scales: {
|
||||
x: { stacked: true },
|
||||
y: {
|
||||
stacked: true,
|
||||
ticks: { callback: val => formatCurrency(val) }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 차트 2: 수당 유형별 비율 (Doughnut)
|
||||
const typeData = @json($typeRatio);
|
||||
new Chart(document.getElementById('typeRatioChart'), {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: ['파트너수당', '매니저수당', '유치수당'],
|
||||
datasets: [{
|
||||
data: [
|
||||
Number(typeData?.partner_total || 0),
|
||||
Number(typeData?.manager_total || 0),
|
||||
Number(typeData?.referrer_total || 0)
|
||||
],
|
||||
backgroundColor: [
|
||||
'rgba(79, 70, 229, 0.8)',
|
||||
'rgba(16, 185, 129, 0.8)',
|
||||
'rgba(245, 158, 11, 0.8)'
|
||||
],
|
||||
borderWidth: 2,
|
||||
borderColor: '#fff'
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
...defaultOptions,
|
||||
plugins: {
|
||||
...defaultOptions.plugins,
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(ctx) {
|
||||
const total = ctx.dataset.data.reduce((a, b) => a + b, 0);
|
||||
const pct = total > 0 ? ((ctx.raw / total) * 100).toFixed(1) : 0;
|
||||
return ctx.label + ': ' + Number(ctx.raw).toLocaleString() + '원 (' + pct + '%)';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 차트 3: 파트너별 수당 Top 10 (Horizontal Bar)
|
||||
const topPartnersData = @json($topPartners);
|
||||
const topPartnerNames = @json($topPartnerNames->map(fn ($p) => $p->user?->name ?? $p->partner_code));
|
||||
new Chart(document.getElementById('topPartnersChart'), {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: topPartnersData.map(d => topPartnerNames[d.partner_id] || 'ID:' + d.partner_id),
|
||||
datasets: [{
|
||||
label: '총 수당',
|
||||
data: topPartnersData.map(d => Number(d.total)),
|
||||
backgroundColor: 'rgba(79, 70, 229, 0.7)',
|
||||
borderColor: 'rgba(79, 70, 229, 1)',
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
...defaultOptions,
|
||||
indexAxis: 'y',
|
||||
scales: {
|
||||
x: { ticks: { callback: val => formatCurrency(val) } }
|
||||
},
|
||||
plugins: {
|
||||
...defaultOptions.plugins,
|
||||
legend: { display: false }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 차트 4: 월별 지급 건수 추이 (Line)
|
||||
new Chart(document.getElementById('monthlyCountChart'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: months,
|
||||
datasets: [{
|
||||
label: '지급 건수',
|
||||
data: monthlyData.map(d => Number(d.count)),
|
||||
borderColor: 'rgba(79, 70, 229, 1)',
|
||||
backgroundColor: 'rgba(79, 70, 229, 0.1)',
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
pointBackgroundColor: 'rgba(79, 70, 229, 1)',
|
||||
pointRadius: 4,
|
||||
pointHoverRadius: 6
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
...defaultOptions,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: { stepSize: 1 }
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
...defaultOptions.plugins,
|
||||
legend: { display: false }
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@@ -1002,6 +1002,9 @@
|
||||
Route::get('/settlement/consulting', [\App\Http\Controllers\Finance\SettlementController::class, 'consultingTab'])->name('settlement.consulting');
|
||||
Route::get('/settlement/customer', [\App\Http\Controllers\Finance\SettlementController::class, 'customerTab'])->name('settlement.customer');
|
||||
Route::get('/settlement/subscription', [\App\Http\Controllers\Finance\SettlementController::class, 'subscriptionTab'])->name('settlement.subscription');
|
||||
Route::get('/settlement/payment', [\App\Http\Controllers\Finance\SettlementController::class, 'paymentTab'])->name('settlement.payment');
|
||||
Route::get('/settlement/payment-partner-detail/{partnerId}', [\App\Http\Controllers\Finance\SettlementController::class, 'paymentPartnerDetail'])->name('settlement.payment-partner-detail');
|
||||
Route::get('/settlement/payment-stats', [\App\Http\Controllers\Finance\SettlementController::class, 'paymentStats'])->name('settlement.payment-stats');
|
||||
|
||||
// 영업수수료정산 (실제 구현 - CRUD API는 그대로 유지)
|
||||
Route::prefix('sales-commissions')->name('sales-commissions.')->group(function () {
|
||||
|
||||
Reference in New Issue
Block a user