- 정산관리에 수당 지급 탭 추가 (파트너별 그룹핑 지급 대기 목록) - 파트너별 상세 건 목록 HTMX 확장 기능 - 수당지급현황통계 페이지 (Chart.js 4개 차트 + 월별 요약 테이블) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
360 lines
13 KiB
PHP
360 lines
13 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Finance;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Sales\SalesCommission;
|
|
use App\Models\Sales\SalesPartner;
|
|
use App\Services\SalesCommissionService;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\Response;
|
|
use Illuminate\View\View;
|
|
|
|
class SettlementController extends Controller
|
|
{
|
|
public function __construct(
|
|
private SalesCommissionService $service
|
|
) {}
|
|
|
|
/**
|
|
* 통합 정산관리 메인 페이지
|
|
*/
|
|
public function index(Request $request): View|Response
|
|
{
|
|
if ($request->header('HX-Request') && !$request->header('HX-Boosted')) {
|
|
return response('', 200)->header('HX-Redirect', route('finance.settlement'));
|
|
}
|
|
|
|
$initialTab = $request->input('tab', 'commission');
|
|
|
|
// 수당 정산 탭 데이터 (기본 탭이므로 즉시 로드)
|
|
$year = $request->input('year', now()->year);
|
|
$month = $request->input('month', now()->month);
|
|
|
|
$filters = [
|
|
'scheduled_year' => $year,
|
|
'scheduled_month' => $month,
|
|
'status' => $request->input('status'),
|
|
'payment_type' => $request->input('payment_type'),
|
|
'partner_id' => $request->input('partner_id'),
|
|
'commission_type' => $request->input('commission_type'),
|
|
'search' => $request->input('search'),
|
|
];
|
|
|
|
$commissions = $this->service->getCommissions($filters);
|
|
$stats = $this->service->getSettlementStats($year, $month);
|
|
|
|
$partners = SalesPartner::with('user')
|
|
->active()
|
|
->orderBy('partner_code')
|
|
->get();
|
|
|
|
$pendingTenants = $this->service->getPendingPaymentTenants();
|
|
|
|
// 통합 통계 (페이지 상단)
|
|
$summaryStats = $this->getSummaryStats();
|
|
|
|
return view('finance.settlement.index', compact(
|
|
'initialTab',
|
|
'commissions',
|
|
'stats',
|
|
'partners',
|
|
'pendingTenants',
|
|
'year',
|
|
'month',
|
|
'filters',
|
|
'summaryStats'
|
|
));
|
|
}
|
|
|
|
/**
|
|
* 수당 통계카드 HTMX 갱신
|
|
*/
|
|
public function commissionStats(Request $request): View
|
|
{
|
|
$year = $request->input('year', now()->year);
|
|
$month = $request->input('month', now()->month);
|
|
$stats = $this->service->getSettlementStats($year, $month);
|
|
|
|
return view('finance.settlement.partials.commission.stats-cards', compact('stats', 'year', 'month'));
|
|
}
|
|
|
|
/**
|
|
* 수당 테이블 HTMX 갱신
|
|
*/
|
|
public function commissionTable(Request $request): View
|
|
{
|
|
$year = $request->input('year', now()->year);
|
|
$month = $request->input('month', now()->month);
|
|
|
|
$filters = [
|
|
'scheduled_year' => $year,
|
|
'scheduled_month' => $month,
|
|
'status' => $request->input('status'),
|
|
'payment_type' => $request->input('payment_type'),
|
|
'partner_id' => $request->input('partner_id'),
|
|
'commission_type' => $request->input('commission_type'),
|
|
'search' => $request->input('search'),
|
|
];
|
|
|
|
$commissions = $this->service->getCommissions($filters);
|
|
|
|
return view('finance.settlement.partials.commission.table', compact('commissions'));
|
|
}
|
|
|
|
/**
|
|
* 파트너별 현황 탭
|
|
*/
|
|
public function partnerSummary(Request $request): View
|
|
{
|
|
$query = SalesPartner::with('user');
|
|
|
|
// 검색
|
|
if ($search = $request->input('search')) {
|
|
$query->where(function ($q) use ($search) {
|
|
$q->where('partner_code', 'like', "%{$search}%")
|
|
->orWhereHas('user', function ($uq) use ($search) {
|
|
$uq->where('name', 'like', "%{$search}%");
|
|
});
|
|
});
|
|
}
|
|
|
|
// 유형 필터
|
|
if ($type = $request->input('type')) {
|
|
if ($type === 'individual') {
|
|
$query->where('partner_type', '!=', 'corporate');
|
|
} elseif ($type === 'corporate') {
|
|
$query->where('partner_type', 'corporate');
|
|
}
|
|
}
|
|
|
|
// 상태 필터
|
|
if ($request->input('status', 'active') === 'active') {
|
|
$query->active();
|
|
}
|
|
|
|
$partners = $query->orderBy('partner_code')->paginate(20);
|
|
|
|
// 각 파트너별 수당 집계
|
|
$partnerIds = $partners->pluck('id')->toArray();
|
|
if (!empty($partnerIds)) {
|
|
$commissionStats = SalesCommission::selectRaw('
|
|
partner_id,
|
|
SUM(CASE WHEN status = "paid" THEN partner_commission ELSE 0 END) as paid_total,
|
|
SUM(CASE WHEN status IN ("pending", "approved") THEN partner_commission ELSE 0 END) as unpaid_total,
|
|
COUNT(*) as total_count,
|
|
MAX(CASE WHEN status = "paid" THEN actual_payment_date ELSE NULL END) as last_paid_date
|
|
')
|
|
->whereIn('partner_id', $partnerIds)
|
|
->groupBy('partner_id')
|
|
->get()
|
|
->keyBy('partner_id');
|
|
} else {
|
|
$commissionStats = collect();
|
|
}
|
|
|
|
return view('finance.settlement.partials.partner-summary', compact('partners', 'commissionStats'));
|
|
}
|
|
|
|
/**
|
|
* 컨설팅비용 탭
|
|
*/
|
|
public function consultingTab(Request $request): View
|
|
{
|
|
return view('finance.settlement.partials.consulting-tab');
|
|
}
|
|
|
|
/**
|
|
* 고객사정산 탭
|
|
*/
|
|
public function customerTab(Request $request): View
|
|
{
|
|
return view('finance.settlement.partials.customer-tab');
|
|
}
|
|
|
|
/**
|
|
* 구독관리 탭
|
|
*/
|
|
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'
|
|
));
|
|
}
|
|
|
|
/**
|
|
* 통합 통계 데이터
|
|
*/
|
|
private function getSummaryStats(): array
|
|
{
|
|
$now = now();
|
|
|
|
// 미지급 수당 (pending + approved)
|
|
$unpaidAmount = SalesCommission::whereIn('status', [
|
|
SalesCommission::STATUS_PENDING,
|
|
SalesCommission::STATUS_APPROVED,
|
|
])->selectRaw('SUM(partner_commission + manager_commission + COALESCE(referrer_commission, 0)) as total')
|
|
->value('total') ?? 0;
|
|
|
|
// 승인 대기 건수
|
|
$pendingCount = SalesCommission::where('status', SalesCommission::STATUS_PENDING)->count();
|
|
|
|
// 이번달 지급예정
|
|
$thisMonthScheduled = SalesCommission::whereIn('status', [
|
|
SalesCommission::STATUS_PENDING,
|
|
SalesCommission::STATUS_APPROVED,
|
|
])
|
|
->whereYear('scheduled_payment_date', $now->year)
|
|
->whereMonth('scheduled_payment_date', $now->month)
|
|
->selectRaw('SUM(partner_commission + manager_commission + COALESCE(referrer_commission, 0)) as total')
|
|
->value('total') ?? 0;
|
|
|
|
// 누적 지급완료
|
|
$totalPaid = SalesCommission::where('status', SalesCommission::STATUS_PAID)
|
|
->selectRaw('SUM(partner_commission + manager_commission + COALESCE(referrer_commission, 0)) as total')
|
|
->value('total') ?? 0;
|
|
|
|
return [
|
|
'unpaid_amount' => $unpaidAmount,
|
|
'pending_count' => $pendingCount,
|
|
'this_month_scheduled' => $thisMonthScheduled,
|
|
'total_paid' => $totalPaid,
|
|
];
|
|
}
|
|
}
|