diff --git a/app/Http/Controllers/Finance/SettlementController.php b/app/Http/Controllers/Finance/SettlementController.php new file mode 100644 index 00000000..7b6692c3 --- /dev/null +++ b/app/Http/Controllers/Finance/SettlementController.php @@ -0,0 +1,222 @@ +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'); + } + + /** + * 통합 통계 데이터 + */ + 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, + ]; + } +} diff --git a/app/Models/Sales/SalesPartner.php b/app/Models/Sales/SalesPartner.php index 9d853e39..06334b42 100644 --- a/app/Models/Sales/SalesPartner.php +++ b/app/Models/Sales/SalesPartner.php @@ -2,6 +2,7 @@ namespace App\Models\Sales; +use App\Models\Sales\SalesCommission; use App\Models\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -86,6 +87,14 @@ public function tenantManagements(): HasMany return $this->hasMany(SalesTenantManagement::class, 'sales_partner_id'); } + /** + * 수수료 정산 내역 + */ + public function commissions(): HasMany + { + return $this->hasMany(SalesCommission::class, 'partner_id'); + } + /** * 이 단체를 유치한 영업파트너 */ diff --git a/app/Services/SalesCommissionService.php b/app/Services/SalesCommissionService.php index e01806d5..9502de54 100644 --- a/app/Services/SalesCommissionService.php +++ b/app/Services/SalesCommissionService.php @@ -32,7 +32,7 @@ class SalesCommissionService public function getCommissions(array $filters = [], int $perPage = 20): LengthAwarePaginator { $query = SalesCommission::query() - ->with(['tenant', 'partner.user', 'manager', 'management']); + ->with(['tenant', 'partner.user', 'manager', 'management', 'referrerPartner.user']); // 상태 필터 if (!empty($filters['status'])) { @@ -64,6 +64,19 @@ public function getCommissions(array $filters = [], int $perPage = 20): LengthAw $query->paymentDateBetween($filters['payment_start_date'], $filters['payment_end_date']); } + // 수당유형 필터 + if (!empty($filters['commission_type'])) { + $commissionType = $filters['commission_type']; + if ($commissionType === 'partner') { + $query->where('partner_commission', '>', 0); + } elseif ($commissionType === 'manager') { + $query->where('manager_commission', '>', 0); + } elseif ($commissionType === 'referrer') { + $query->whereNotNull('referrer_partner_id') + ->where('referrer_commission', '>', 0); + } + } + // 테넌트 검색 if (!empty($filters['search'])) { $search = $filters['search']; diff --git a/resources/views/finance/settlement/index.blade.php b/resources/views/finance/settlement/index.blade.php new file mode 100644 index 00000000..823362a6 --- /dev/null +++ b/resources/views/finance/settlement/index.blade.php @@ -0,0 +1,372 @@ +@extends('layouts.app') + +@section('title', '정산관리') + +@section('content') +
통합 정산 현황
+{{ $commission->tenant->name ?? $commission->tenant->company_name ?? '-' }}
+{{ $commission->payment_date->format('Y-m-d') }}
+{{ $commission->scheduled_payment_date->format('Y-m-d') }}
+{{ $commission->actual_payment_date?->format('Y-m-d') ?? '-' }}
+{{ $commission->approver?->name ?? '-' }}
+{{ $commission->approved_at->format('Y-m-d H:i') }}
+{{ $commission->bank_reference }}
+{{ $commission->notes }}
+| 상품 | +개발비 | +파트너수당 | +매니저수당 | +
|---|---|---|---|
| {{ $detail->contractProduct?->product?->name ?? '-' }} | +{{ number_format($detail->registration_fee) }}원 | +{{ number_format($detail->partner_commission) }}원 | +{{ number_format($detail->manager_commission) }}원 | +
지급 대기
+{{ number_format($stats['pending']['partner_total'] + $stats['pending']['manager_total']) }}원
+승인 완료
+{{ number_format($stats['approved']['partner_total'] + $stats['approved']['manager_total']) }}원
+{{ $stats['approved']['count'] }}건
+지급 완료
+{{ number_format($stats['paid']['partner_total'] + $stats['paid']['manager_total']) }}원
+{{ $stats['paid']['count'] }}건
+{{ $year }}년 {{ $month }}월 총 수당
+{{ number_format($stats['total']['partner_commission'] + $stats['total']['manager_commission']) }}원
+| + + | +테넌트 | +입금구분 | +입금액 | +입금일 | +영업파트너 | +파트너수당 | +매니저 | +매니저수당 | +유치파트너 | +유치수당 | +지급예정일 | +상태 | +액션 | +
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| + @if (in_array($commission->status, ['pending', 'approved'])) + + @endif + | +
+ {{ $commission->tenant->name ?? $commission->tenant->company_name ?? '-' }}
+ ID: {{ $commission->tenant_id }}
+ |
+ + + {{ $commission->payment_type_label }} + + | ++ {{ number_format($commission->payment_amount) }} + | ++ {{ $commission->payment_date->format('Y-m-d') }} + | +
+ {{ $commission->partner?->user?->name ?? '-' }}
+ {{ $commission->partner_rate }}%
+ |
+ + {{ number_format($commission->partner_commission) }} + | +
+ {{ $commission->manager?->name ?? '-' }}
+ {{ $commission->manager_rate }}%
+ |
+ + {{ number_format($commission->manager_commission) }} + | +
+ @if ($commission->referrer_partner_id)
+ {{ $commission->referrerPartner?->user?->name ?? '-' }}
+ {{ $commission->referrer_rate }}%
+ @else
+ -
+ @endif
+ |
+ + @if ($commission->referrer_commission > 0) + {{ number_format($commission->referrer_commission) }} + @else + - + @endif + | ++ {{ $commission->scheduled_payment_date->format('Y-m-d') }} + | ++ @php + $statusColors = [ + 'pending' => 'bg-yellow-100 text-yellow-800', + 'approved' => 'bg-blue-100 text-blue-800', + 'paid' => 'bg-green-100 text-green-800', + 'cancelled' => 'bg-red-100 text-red-800', + ]; + @endphp + + {{ $commission->status_label }} + + | +
+
+
+ @if ($commission->status === 'pending')
+
+
+ @elseif ($commission->status === 'approved')
+
+ @endif
+
+ |
+
| + 등록된 정산 내역이 없습니다. + | +|||||||||||||
총 시간
+0시간
+총 수수료
+0원
+지급완료
+0원
+지급예정
+0원
+| 날짜 | +컨설턴트 | +고객사 | +서비스 | +시간 | +금액 | +상태 | +관리 | +
|---|---|---|---|---|---|---|---|
| 로딩 중... | |||||||
| 데이터가 없습니다. | |||||||
| + | + | + | + | + | + | + + | ++ + + | +
총 매출
+0원
+정산금액
+0원
+정산완료
+0원
+수수료 합계
+0원
+| 정산월 | +고객사 | +매출액 | +수수료 | +비용 | +정산금액 | +상태 | +관리 | +
|---|---|---|---|---|---|---|---|
| 로딩 중... | |||||||
| 데이터가 없습니다. | |||||||
| + | + | + | + | + | + | + + | ++ + + | +
| 파트너명 | +유형 | +수당률 | +계약건수 | +누적 수당 | +미지급 | +지급완료 | +최근 지급일 | +액션 | +
|---|---|---|---|---|---|---|---|---|
|
+ {{ $partner->user?->name ?? '-' }}
+ {{ $partner->partner_code }}
+ |
+ + @if ($partner->partner_type === 'corporate') + 단체 + @else + 개인 + @endif + | ++ {{ $partner->commission_rate }}% + | ++ {{ number_format($totalCount) }} + | ++ {{ number_format($paidTotal + $unpaidTotal) }}원 + | ++ {{ number_format($unpaidTotal) }}원 + | ++ {{ number_format($paidTotal) }}원 + | ++ {{ $lastPaidDate ? \Carbon\Carbon::parse($lastPaidDate)->format('Y-m-d') : '-' }} + | ++ + 상세보기 + + | +
| + 등록된 파트너가 없습니다. + | +||||||||
활성 구독
+0개
+월 반복 수익(MRR)
+0원
+연 반복 수익(ARR)
+0원
+총 사용자
+0명
+| 고객사 | +플랜 | +월 요금 | +결제주기 | +다음 결제 | +사용자 | +상태 | +관리 | +
|---|---|---|---|---|---|---|---|
| 로딩 중... | |||||||
| 데이터가 없습니다. | |||||||
| + + + | ++ + | ++ | + | + | + | + + | ++ + + | +
미지급 수당
+{{ number_format($summaryStats['unpaid_amount']) }}원
+대기 + 승인 상태
+승인 대기
+{{ $summaryStats['pending_count'] }}건
+승인 처리 필요
+이번달 지급예정
+{{ number_format($summaryStats['this_month_scheduled']) }}원
+{{ now()->format('Y년 n월') }} 예정
+누적 지급완료
+{{ number_format($summaryStats['total_paid']) }}원
+전체 기간 합계
+