feat:수당지급현황통계 페이지 고도화 (종합 대시보드)

- 필터: 년/월 범위, 상태, 지급유형, 파트너, 매니저, 검색어
- 통계 카드 4→8개 (총 발생액, 지급완료, 미지급, 파트너수, 유형별 합계, 평균)
- 차트 4→6개 (월별 추이, 유형비율, Top10, 상태분포 건수/금액, 파트너vs매니저)
- 테이블 1→3개 탭 (월별 요약, 파트너별 결산, 매니저별 결산 + 완료율 프로그레스바)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
김보곤
2026-02-19 19:59:45 +09:00
parent c5edefc5a3
commit ecbb8e4cc7
2 changed files with 727 additions and 163 deletions

View File

@@ -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;
});
}
/**