From ecbb8e4cc7a052ad44bda6773614079d3741ecab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Thu, 19 Feb 2026 19:59:45 +0900 Subject: [PATCH] =?UTF-8?q?feat:=EC=88=98=EB=8B=B9=EC=A7=80=EA=B8=89?= =?UTF-8?q?=ED=98=84=ED=99=A9=ED=86=B5=EA=B3=84=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EA=B3=A0=EB=8F=84=ED=99=94=20(=EC=A2=85=ED=95=A9?= =?UTF-8?q?=20=EB=8C=80=EC=8B=9C=EB=B3=B4=EB=93=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 필터: 년/월 범위, 상태, 지급유형, 파트너, 매니저, 검색어 - 통계 카드 4→8개 (총 발생액, 지급완료, 미지급, 파트너수, 유형별 합계, 평균) - 차트 4→6개 (월별 추이, 유형비율, Top10, 상태분포 건수/금액, 파트너vs매니저) - 테이블 1→3개 탭 (월별 요약, 파트너별 결산, 매니저별 결산 + 완료율 프로그레스바) Co-Authored-By: Claude Opus 4.6 --- .../Finance/SettlementController.php | 253 ++++++- .../settlement/payment-stats.blade.php | 637 ++++++++++++++---- 2 files changed, 727 insertions(+), 163 deletions(-) diff --git a/app/Http/Controllers/Finance/SettlementController.php b/app/Http/Controllers/Finance/SettlementController.php index ff6fac9b..90fb14ac 100644 --- a/app/Http/Controllers/Finance/SettlementController.php +++ b/app/Http/Controllers/Finance/SettlementController.php @@ -7,9 +7,12 @@ use App\Models\Sales\SalesContractProduct; use App\Models\Sales\SalesPartner; use App\Models\Sales\SalesTenantManagement; +use App\Models\User; use App\Services\SalesCommissionService; +use Carbon\Carbon; use Illuminate\Http\Request; use Illuminate\Http\Response; +use Illuminate\Support\Facades\DB; use Illuminate\View\View; class SettlementController extends Controller @@ -365,73 +368,259 @@ public function paymentStats(Request $request): View|Response return response('', 200)->header('HX-Redirect', route('finance.settlement.payment-stats')); } - $year = (int) $request->input('year', now()->year); + // 필터 파라미터 + $now = now(); + $startYear = (int) $request->input('start_year', $now->year); + $startMonth = (int) $request->input('start_month', 1); + $endYear = (int) $request->input('end_year', $now->year); + $endMonth = (int) $request->input('end_month', $now->month); + $status = $request->input('status'); + $paymentType = $request->input('payment_type'); + $partnerId = $request->input('partner_id'); + $managerUserId = $request->input('manager_user_id'); + $search = $request->input('search'); - // 통계 카드 - $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; + $startDate = Carbon::create($startYear, $startMonth, 1)->startOfMonth(); + $endDate = Carbon::create($endYear, $endMonth, 1)->endOfMonth(); - $totalPaidCount = SalesCommission::where('status', SalesCommission::STATUS_PAID) - ->whereYear('actual_payment_date', $year) - ->count(); + // 공통 baseQuery 클로저 + $baseQuery = function () use ($startDate, $endDate, $status, $paymentType, $partnerId, $managerUserId, $search) { + $query = SalesCommission::query() + ->whereBetween('scheduled_payment_date', [$startDate, $endDate]); - $activePartners = SalesCommission::where('status', SalesCommission::STATUS_PAID) - ->whereYear('actual_payment_date', $year) - ->distinct('partner_id') - ->count('partner_id'); + if ($status) { + $query->where('status', $status); + } + if ($paymentType) { + $query->where('payment_type', $paymentType); + } + if ($partnerId) { + $query->where('partner_id', $partnerId); + } + if ($managerUserId) { + $query->where('manager_user_id', $managerUserId); + } + if ($search) { + $query->where(function ($q) use ($search) { + $q->whereHas('partner.user', fn ($uq) => $uq->where('name', 'like', "%{$search}%")) + ->orWhereHas('partner', fn ($pq) => $pq->where('partner_code', 'like', "%{$search}%")) + ->orWhereHas('management.tenant', fn ($tq) => $tq->where('company_name', 'like', "%{$search}%")); + }); + } - $avgCommission = $totalPaidCount > 0 ? round($totalPaidAmount / $totalPaidCount) : 0; + return $query; + }; - $statsCards = [ - 'total_paid_amount' => $totalPaidAmount, - 'total_paid_count' => $totalPaidCount, + $filters = compact('startYear', 'startMonth', 'endYear', 'endMonth', 'status', 'paymentType', 'partnerId', 'managerUserId', 'search'); + + // 통계 카드 8개 + $statsCards = $this->calculateStatsCards($baseQuery); + + // 차트 데이터 + $monthlyTrend = $this->getMonthlyTrend($baseQuery, $startDate, $endDate); + $typeRatio = $this->getTypeRatio($baseQuery); + $topPartners = $this->getTopPartners($baseQuery); + $statusDistribution = $this->getStatusDistribution($baseQuery); + $monthlyComparison = $this->getMonthlyComparison($baseQuery, $startDate, $endDate); + + // 테이블 데이터 + $partnerSettlement = $this->getPartnerSettlement($baseQuery); + $managerSettlement = $this->getManagerSettlement($baseQuery); + + // 필터 옵션 + $partners = SalesPartner::with('user')->active()->orderBy('partner_code')->get(); + $managers = User::whereIn('id', SalesCommission::whereNotNull('manager_user_id')->distinct()->pluck('manager_user_id'))->orderBy('name')->get(); + + return view('finance.settlement.payment-stats', compact( + 'filters', 'statsCards', + 'monthlyTrend', 'typeRatio', 'topPartners', 'statusDistribution', 'monthlyComparison', + 'partnerSettlement', 'managerSettlement', + 'partners', 'managers' + )); + } + + /** + * 통계 카드 8개 계산 + */ + private function calculateStatsCards(\Closure $baseQuery): array + { + $commissionSum = 'partner_commission + manager_commission + COALESCE(referrer_commission, 0)'; + + $totalAmount = (clone $baseQuery())->selectRaw("SUM({$commissionSum}) as total")->value('total') ?? 0; + $totalCount = (clone $baseQuery())->count(); + $paidAmount = (clone $baseQuery())->where('status', SalesCommission::STATUS_PAID) + ->selectRaw("SUM({$commissionSum}) as total")->value('total') ?? 0; + $unpaidAmount = (clone $baseQuery())->whereIn('status', [SalesCommission::STATUS_PENDING, SalesCommission::STATUS_APPROVED]) + ->selectRaw("SUM({$commissionSum}) as total")->value('total') ?? 0; + $activePartners = (clone $baseQuery())->distinct('partner_id')->count('partner_id'); + $partnerSum = (clone $baseQuery())->sum('partner_commission'); + $managerSum = (clone $baseQuery())->sum('manager_commission'); + $referrerSum = (clone $baseQuery())->selectRaw('SUM(COALESCE(referrer_commission, 0)) as total')->value('total') ?? 0; + $avgCommission = $totalCount > 0 ? round($totalAmount / $totalCount) : 0; + + return [ + 'total_amount' => $totalAmount, + 'paid_amount' => $paidAmount, + 'unpaid_amount' => $unpaidAmount, 'active_partners' => $activePartners, + 'partner_sum' => $partnerSum, + 'manager_sum' => $managerSum, + 'referrer_sum' => $referrerSum, 'avg_commission' => $avgCommission, ]; + } - // 차트 1 & 4: 월별 지급 추이 (해당 연도) - $monthlyTrend = SalesCommission::where('status', SalesCommission::STATUS_PAID) - ->whereYear('actual_payment_date', $year) + /** + * 월별 지급 추이 (stacked bar) + */ + private function getMonthlyTrend(\Closure $baseQuery, Carbon $startDate, Carbon $endDate): \Illuminate\Support\Collection + { + return $baseQuery() ->selectRaw(" - DATE_FORMAT(actual_payment_date, '%Y-%m') as month, + DATE_FORMAT(scheduled_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, + SUM(CASE WHEN status = 'paid' THEN partner_commission + manager_commission + COALESCE(referrer_commission, 0) ELSE 0 END) as paid_total, + SUM(CASE WHEN status IN ('pending','approved') THEN partner_commission + manager_commission + COALESCE(referrer_commission, 0) ELSE 0 END) as unpaid_total, COUNT(*) as count ") - ->groupByRaw("DATE_FORMAT(actual_payment_date, '%Y-%m')") + ->groupByRaw("DATE_FORMAT(scheduled_payment_date, '%Y-%m')") ->orderBy('month') ->get(); + } - // 차트 2: 수당 유형별 비율 - $typeRatio = SalesCommission::where('status', SalesCommission::STATUS_PAID) - ->whereYear('actual_payment_date', $year) + /** + * 수당 유형별 비율 (doughnut) + */ + private function getTypeRatio(\Closure $baseQuery): ?object + { + return $baseQuery() ->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) + /** + * 파트너별 Top 10 (horizontal bar) + */ + private function getTopPartners(\Closure $baseQuery): array + { + $topPartners = $baseQuery() ->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') + $partnerNames = 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' - )); + return [ + 'data' => $topPartners, + 'names' => $partnerNames->map(fn ($p) => $p->user?->name ?? $p->partner_code), + ]; + } + + /** + * 상태별 건수/금액 분포 (doughnut + bar) + */ + private function getStatusDistribution(\Closure $baseQuery): \Illuminate\Support\Collection + { + return $baseQuery() + ->selectRaw(" + status, + COUNT(*) as count, + SUM(partner_commission + manager_commission + COALESCE(referrer_commission, 0)) as amount + ") + ->groupBy('status') + ->get(); + } + + /** + * 파트너 vs 매니저 월별 추이 (line) - paid만 + */ + private function getMonthlyComparison(\Closure $baseQuery, Carbon $startDate, Carbon $endDate): \Illuminate\Support\Collection + { + return $baseQuery() + ->where('status', SalesCommission::STATUS_PAID) + ->selectRaw(" + DATE_FORMAT(actual_payment_date, '%Y-%m') as month, + SUM(partner_commission) as partner_total, + SUM(manager_commission) as manager_total + ") + ->groupByRaw("DATE_FORMAT(actual_payment_date, '%Y-%m')") + ->orderBy('month') + ->get(); + } + + /** + * 파트너별 결산 테이블 + */ + private function getPartnerSettlement(\Closure $baseQuery): \Illuminate\Support\Collection + { + $data = $baseQuery() + ->selectRaw(" + partner_id, + COUNT(*) as contract_count, + SUM(CASE WHEN payment_type = 'deposit' THEN partner_commission ELSE 0 END) as first_commission, + SUM(CASE WHEN payment_type = 'balance' THEN partner_commission ELSE 0 END) as second_commission, + SUM(partner_commission) as total_partner, + SUM(CASE WHEN status = 'paid' THEN partner_commission + manager_commission + COALESCE(referrer_commission, 0) ELSE 0 END) as paid_amount, + SUM(CASE WHEN status IN ('pending','approved') THEN partner_commission + manager_commission + COALESCE(referrer_commission, 0) ELSE 0 END) as unpaid_amount, + SUM(partner_commission + manager_commission + COALESCE(referrer_commission, 0)) as total_amount + ") + ->groupBy('partner_id') + ->orderByDesc('total_amount') + ->get(); + + $partnerInfo = SalesPartner::with('user') + ->whereIn('id', $data->pluck('partner_id')) + ->get() + ->keyBy('id'); + + return $data->map(function ($row) use ($partnerInfo) { + $partner = $partnerInfo->get($row->partner_id); + $row->partner_name = $partner?->user?->name ?? 'N/A'; + $row->partner_type = $partner?->partner_type ?? ''; + $row->completion_rate = $row->total_amount > 0 ? round(($row->paid_amount / $row->total_amount) * 100, 1) : 0; + return $row; + }); + } + + /** + * 매니저별 결산 테이블 + */ + private function getManagerSettlement(\Closure $baseQuery): \Illuminate\Support\Collection + { + $data = $baseQuery() + ->whereNotNull('manager_user_id') + ->selectRaw(" + manager_user_id, + COUNT(*) as contract_count, + SUM(manager_commission) as total_manager, + SUM(CASE WHEN status = 'paid' THEN manager_commission ELSE 0 END) as paid_amount, + SUM(CASE WHEN status IN ('pending','approved') THEN manager_commission ELSE 0 END) as unpaid_amount + ") + ->groupBy('manager_user_id') + ->orderByDesc('total_manager') + ->get(); + + $managerInfo = User::whereIn('id', $data->pluck('manager_user_id')) + ->get() + ->keyBy('id'); + + return $data->map(function ($row) use ($managerInfo) { + $manager = $managerInfo->get($row->manager_user_id); + $row->manager_name = $manager?->name ?? 'N/A'; + $row->completion_rate = $row->total_manager > 0 ? round(($row->paid_amount / $row->total_manager) * 100, 1) : 0; + return $row; + }); } /** diff --git a/resources/views/finance/settlement/payment-stats.blade.php b/resources/views/finance/settlement/payment-stats.blade.php index 5ebb04ad..fb13a3f7 100644 --- a/resources/views/finance/settlement/payment-stats.blade.php +++ b/resources/views/finance/settlement/payment-stats.blade.php @@ -3,12 +3,14 @@ @section('title', '수당지급현황통계') @section('content') -
+
{{-- 페이지 헤더 --}}

수당지급현황통계

-

{{ $year }}년 수당 지급 현황 종합 통계

+

+ {{ $filters['startYear'] }}년 {{ $filters['startMonth'] }}월 ~ {{ $filters['endYear'] }}년 {{ $filters['endMonth'] }}월 수당 종합 통계 +

- {{-- 통계 카드 --}} -
-
-
+ {{-- 필터 패널 --}} + + {{-- 통계 카드 8개 --}} +
+ {{-- 1. 총 수당 발생액 --}}
-

총 지급 건수

-

{{ number_format($statsCards['total_paid_count']) }}건

+

총 수당 발생액

+

{{ number_format($statsCards['total_amount']) }}

-
- - - +
+
-

{{ $year }}년 총 건수

- + {{-- 2. 지급 완료액 --}} +
+
+
+

지급 완료액

+

{{ number_format($statsCards['paid_amount']) }}

+
+
+ +
+
+
+ {{-- 3. 미지급액 --}} +
+
+
+

미지급액

+

{{ number_format($statsCards['unpaid_amount']) }}

+
+
+ +
+
+
+ {{-- 4. 활성 파트너 수 --}}
-

지급 파트너 수

-

{{ $statsCards['active_partners'] }}명

+

활성 파트너 수

+

{{ $statsCards['active_partners'] }}

-
- - - +
+
-

{{ $year }}년 지급 대상

- + {{-- 5. 파트너 수당 합계 --}}
-

건당 평균 수당

-

{{ number_format($statsCards['avg_commission']) }}원

+

파트너 수당

+

{{ number_format($statsCards['partner_sum']) }}

-
- - - +
+ +
+
+
+ {{-- 6. 매니저 수당 합계 --}} +
+
+
+

매니저 수당

+

{{ number_format($statsCards['manager_sum']) }}

+
+
+ +
+
+
+ {{-- 7. 유치 수당 합계 --}} +
+
+
+

유치 수당

+

{{ number_format($statsCards['referrer_sum']) }}

+
+
+ +
+
+
+ {{-- 8. 건당 평균 수당 --}} +
+
+
+

건당 평균 수당

+

{{ number_format($statsCards['avg_commission']) }}

+
+
+
-

전체 평균

@@ -98,7 +236,7 @@ class="inline-flex items-center gap-1 px-4 py-2 text-gray-600 bg-gray-100 hover:
{{-- 차트 1: 월별 지급 추이 (Stacked Bar) --}}
-

월별 지급 추이

+

월별 수당 추이

@@ -120,21 +258,55 @@ class="inline-flex items-center gap-1 px-4 py-2 text-gray-600 bg-gray-100 hover:
- {{-- 차트 4: 월별 지급 건수 추이 (Line) --}} + {{-- 차트 4: 상태별 건수 분포 (Doughnut) --}}
-

월별 지급 건수 추이

+

상태별 건수 분포

- + +
+
+ + {{-- 차트 5: 상태별 금액 분포 (Bar) --}} +
+

상태별 금액 분포

+
+ +
+
+ + {{-- 차트 6: 파트너 vs 매니저 추이 (Line) --}} +
+

파트너 vs 매니저 수당 추이 (지급완료)

+
+
- {{-- 월별 지급 요약 테이블 --}} + {{-- 테이블 탭 --}}
-
-

월별 지급 요약

+
+
-
+ + {{-- 월별 요약 테이블 --}} +
@@ -142,42 +314,190 @@ class="inline-flex items-center gap-1 px-4 py-2 text-gray-600 bg-gray-100 hover: - + + - @php $grandPartner = 0; $grandManager = 0; $grandReferrer = 0; $grandTotal = 0; $grandCount = 0; @endphp - @forelse ($monthlyTrend->reverse() as $row) + @php $gPartner = 0; $gManager = 0; $gReferrer = 0; $gPaid = 0; $gUnpaid = 0; $gCount = 0; @endphp + @forelse ($monthlyTrend 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; + $gPartner += $row->partner_total; + $gManager += $row->manager_total; + $gReferrer += $row->referrer_total; + $gPaid += $row->paid_total; + $gUnpaid += $row->unpaid_total; + $gCount += $row->count; @endphp - + - + + @empty - + @endforelse @if ($monthlyTrend->isNotEmpty()) - - - - - + + + + + + + + @endif + +
파트너수당 매니저수당 유치수당합계지급완료미지급 건수
{{ \Carbon\Carbon::parse($row->month . '-01')->format('n월') }}{{ \Carbon\Carbon::parse($row->month . '-01')->format('Y년 n월') }} {{ number_format($row->partner_total) }}원 {{ number_format($row->manager_total) }}원 {{ number_format($row->referrer_total) }}원{{ number_format($rowTotal) }}원{{ number_format($row->paid_total) }}원{{ number_format($row->unpaid_total) }}원 {{ $row->count }}건
{{ $year }}년 지급 데이터가 없습니다.해당 기간의 데이터가 없습니다.
합계{{ number_format($grandPartner) }}원{{ number_format($grandManager) }}원{{ number_format($grandReferrer) }}원{{ number_format($grandTotal) }}원{{ $grandCount }}건{{ number_format($gPartner) }}원{{ number_format($gManager) }}원{{ number_format($gReferrer) }}원{{ number_format($gPaid) }}원{{ number_format($gUnpaid) }}원{{ $gCount }}건
+
+ + {{-- 파트너별 결산 테이블 --}} +
+ + + + + + + + + + + + + + + + @php $pFirst = 0; $pSecond = 0; $pTotal = 0; $pPaid = 0; $pUnpaid = 0; $pCount = 0; @endphp + @forelse ($partnerSettlement as $row) + @php + $pFirst += $row->first_commission; + $pSecond += $row->second_commission; + $pTotal += $row->total_amount; + $pPaid += $row->paid_amount; + $pUnpaid += $row->unpaid_amount; + $pCount += $row->contract_count; + @endphp + + + + + + + + + + + + @empty + + + + @endforelse + @if ($partnerSettlement->isNotEmpty()) + @php $overallRate = $pTotal > 0 ? round(($pPaid / $pTotal) * 100, 1) : 0; @endphp + + + + + + + + + + + + @endif + +
파트너유형건수1차수당2차수당총수당지급완료미지급완료율
{{ $row->partner_name }} + + {{ $row->partner_type === 'corporate' ? '법인' : '개인' }} + + {{ $row->contract_count }}건{{ number_format($row->first_commission) }}원{{ number_format($row->second_commission) }}원{{ number_format($row->total_amount) }}원{{ number_format($row->paid_amount) }}원{{ number_format($row->unpaid_amount) }}원 +
+
+
+
+ {{ $row->completion_rate }}% +
+
해당 기간의 데이터가 없습니다.
합계-{{ $pCount }}건{{ number_format($pFirst) }}원{{ number_format($pSecond) }}원{{ number_format($pTotal) }}원{{ number_format($pPaid) }}원{{ number_format($pUnpaid) }}원 +
+
+
+
+ {{ $overallRate }}% +
+
+
+ + {{-- 매니저별 결산 테이블 --}} +
+ + + + + + + + + + + + + @php $mTotal = 0; $mPaid = 0; $mUnpaid = 0; $mCount = 0; @endphp + @forelse ($managerSettlement as $row) + @php + $mTotal += $row->total_manager; + $mPaid += $row->paid_amount; + $mUnpaid += $row->unpaid_amount; + $mCount += $row->contract_count; + @endphp + + + + + + + + + @empty + + + + @endforelse + @if ($managerSettlement->isNotEmpty()) + @php $mOverallRate = $mTotal > 0 ? round(($mPaid / $mTotal) * 100, 1) : 0; @endphp + + + + + + + @endif @@ -205,49 +525,64 @@ class="inline-flex items-center gap-1 px-4 py-2 text-gray-600 bg-gray-100 hover: } }; - // 월별 데이터 + const statusLabels = { pending: '대기', approved: '승인', paid: '지급완료', cancelled: '취소' }; + const statusColors = { + pending: 'rgba(251, 191, 36, 0.8)', + approved: 'rgba(59, 130, 246, 0.8)', + paid: 'rgba(16, 185, 129, 0.8)', + cancelled: 'rgba(239, 68, 68, 0.8)' + }; + + // 빈 데이터 플러그인 (doughnut) + const emptyDoughnutPlugin = { + id: 'emptyDoughnut', + afterDraw(chart) { + const { datasets } = chart.data; + if (chart.config.type !== 'doughnut') return; + const hasData = datasets.some(ds => ds.data.some(v => v > 0)); + if (!hasData) { + const { ctx, chartArea: { left, top, right, bottom } } = chart; + const cx = (left + right) / 2; + const cy = (top + bottom) / 2; + ctx.save(); + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.font = '14px sans-serif'; + ctx.fillStyle = '#9CA3AF'; + ctx.fillText('데이터 없음', cx, cy); + ctx.restore(); + } + } + }; + Chart.register(emptyDoughnutPlugin); + + // === 차트 1: 월별 수당 추이 (Stacked Bar) === 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)', - } + { 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) } - } + y: { stacked: true, ticks: { callback: val => formatCurrency(val) } } } } }); - // 차트 2: 수당 유형별 비율 (Doughnut) + // === 차트 2: 수당 유형별 비율 (Doughnut) === const typeData = @json($typeRatio); new Chart(document.getElementById('typeRatioChart'), { type: 'doughnut', @@ -259,13 +594,8 @@ class="inline-flex items-center gap-1 px-4 py-2 text-gray-600 bg-gray-100 hover: 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' + backgroundColor: ['rgba(79, 70, 229, 0.8)', 'rgba(16, 185, 129, 0.8)', 'rgba(245, 158, 11, 0.8)'], + borderWidth: 2, borderColor: '#fff' }] }, options: { @@ -285,9 +615,9 @@ class="inline-flex items-center gap-1 px-4 py-2 text-gray-600 bg-gray-100 hover: } }); - // 차트 3: 파트너별 수당 Top 10 (Horizontal Bar) - const topPartnersData = @json($topPartners); - const topPartnerNames = @json($topPartnerNames->map(fn ($p) => $p->user?->name ?? $p->partner_code)); + // === 차트 3: 파트너별 수당 Top 10 (Horizontal Bar) === + const topPartnersData = @json($topPartners['data']); + const topPartnerNames = @json($topPartners['names']); new Chart(document.getElementById('topPartnersChart'), { type: 'bar', data: { @@ -303,47 +633,92 @@ class="inline-flex items-center gap-1 px-4 py-2 text-gray-600 bg-gray-100 hover: options: { ...defaultOptions, indexAxis: 'y', - scales: { - x: { ticks: { callback: val => formatCurrency(val) } } - }, - plugins: { - ...defaultOptions.plugins, - legend: { display: false } - } + scales: { x: { ticks: { callback: val => formatCurrency(val) } } }, + plugins: { ...defaultOptions.plugins, legend: { display: false } } } }); - // 차트 4: 월별 지급 건수 추이 (Line) - new Chart(document.getElementById('monthlyCountChart'), { - type: 'line', + // === 차트 4: 상태별 건수 분포 (Doughnut) === + const statusData = @json($statusDistribution); + new Chart(document.getElementById('statusCountChart'), { + type: 'doughnut', data: { - labels: months, + labels: statusData.map(d => statusLabels[d.status] || d.status), 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 + data: statusData.map(d => Number(d.count)), + backgroundColor: statusData.map(d => statusColors[d.status] || 'rgba(156,163,175,0.8)'), + borderWidth: 2, borderColor: '#fff' }] }, options: { ...defaultOptions, - scales: { - y: { - beginAtZero: true, - ticks: { stepSize: 1 } - } - }, plugins: { ...defaultOptions.plugins, - legend: { display: false } + 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 + ': ' + ctx.raw + '건 (' + pct + '%)'; + } + } + } } } }); + + // === 차트 5: 상태별 금액 분포 (Bar) === + new Chart(document.getElementById('statusAmountChart'), { + type: 'bar', + data: { + labels: statusData.map(d => statusLabels[d.status] || d.status), + datasets: [{ + label: '금액', + data: statusData.map(d => Number(d.amount)), + backgroundColor: statusData.map(d => statusColors[d.status] || 'rgba(156,163,175,0.8)'), + borderWidth: 1, + borderColor: statusData.map(d => (statusColors[d.status] || 'rgba(156,163,175,1)').replace('0.8', '1')) + }] + }, + options: { + ...defaultOptions, + scales: { y: { ticks: { callback: val => formatCurrency(val) } } }, + plugins: { ...defaultOptions.plugins, legend: { display: false } } + } + }); + + // === 차트 6: 파트너 vs 매니저 추이 (Line) === + const comparisonData = @json($monthlyComparison); + const compMonths = comparisonData.map(d => { + const parts = d.month.split('-'); + return parseInt(parts[1]) + '월'; + }); + new Chart(document.getElementById('comparisonChart'), { + type: 'line', + data: { + labels: compMonths, + datasets: [ + { + label: '파트너수당', + data: comparisonData.map(d => Number(d.partner_total)), + borderColor: 'rgba(79, 70, 229, 1)', + backgroundColor: 'rgba(79, 70, 229, 0.1)', + fill: true, tension: 0.3, pointRadius: 4, pointHoverRadius: 6 + }, + { + label: '매니저수당', + data: comparisonData.map(d => Number(d.manager_total)), + borderColor: 'rgba(16, 185, 129, 1)', + backgroundColor: 'rgba(16, 185, 129, 0.1)', + fill: true, tension: 0.3, pointRadius: 4, pointHoverRadius: 6 + } + ] + }, + options: { + ...defaultOptions, + scales: { y: { ticks: { callback: val => formatCurrency(val) } } } + } + }); }); @endpush
매니저담당건수매니저수당지급완료미지급완료율
{{ $row->manager_name }}{{ $row->contract_count }}건{{ number_format($row->total_manager) }}원{{ number_format($row->paid_amount) }}원{{ number_format($row->unpaid_amount) }}원 +
+
+
+
+ {{ $row->completion_rate }}% +
+
해당 기간의 데이터가 없습니다.
합계{{ $mCount }}건{{ number_format($mTotal) }}원{{ number_format($mPaid) }}원{{ number_format($mUnpaid) }}원 +
+
+
+
+ {{ $mOverallRate }}% +
+