feat:수당지급현황통계 페이지 고도화 (종합 대시보드)
- 필터: 년/월 범위, 상태, 지급유형, 파트너, 매니저, 검색어 - 통계 카드 4→8개 (총 발생액, 지급완료, 미지급, 파트너수, 유형별 합계, 평균) - 차트 4→6개 (월별 추이, 유형비율, Top10, 상태분포 건수/금액, 파트너vs매니저) - 테이블 1→3개 탭 (월별 요약, 파트너별 결산, 매니저별 결산 + 완료율 프로그레스바) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user