From 5d7de6d13b83090746268d765ab46b507ae2054b Mon Sep 17 00:00:00 2001 From: pro Date: Thu, 29 Jan 2026 18:14:11 +0900 Subject: [PATCH] =?UTF-8?q?feat:=EC=98=81=EC=97=85=EC=88=98=EC=88=98?= =?UTF-8?q?=EB=A3=8C=20=EC=A0=95=EC=82=B0=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [모델] - SalesCommission: 영업수수료 정산 모델 - SalesCommissionDetail: 상품별 수당 내역 모델 - SalesTenantManagement: 입금 정보 필드 추가 [서비스/컨트롤러] - SalesCommissionService: 수당 생성, 승인, 지급 처리 로직 - SalesCommissionController: 정산 관리 CRUD [뷰] - 본사 정산 관리 화면 (필터, 통계, 테이블) - 입금 등록 모달 - 상세 보기 모달 - 영업파트너 대시보드 수당 카드 [라우트] - /finance/sales-commissions/* 라우트 추가 - 기존 sales-commission 리다이렉트 호환 [메뉴] - SalesCommissionMenuSeeder: 정산관리 > 영업수수료정산 메뉴 추가 Co-Authored-By: Claude Opus 4.5 --- .../Finance/SalesCommissionController.php | 381 +++++++++++++++ .../Sales/SalesDashboardController.php | 31 ++ app/Models/Sales/SalesCommission.php | 293 ++++++++++++ app/Models/Sales/SalesCommissionDetail.php | 68 +++ app/Models/Sales/SalesTenantManagement.php | 30 ++ app/Services/SalesCommissionService.php | 449 ++++++++++++++++++ .../seeders/SalesCommissionMenuSeeder.php | 101 ++++ .../finance/sales-commission/index.blade.php | 376 +++++++++++++++ .../partials/commission-table.blade.php | 138 ++++++ .../partials/detail-modal.blade.php | 196 ++++++++ .../partials/payment-form.blade.php | 185 ++++++++ .../partials/stats-cards.blade.php | 70 +++ .../partials/data-container.blade.php | 5 + .../partials/my-commission.blade.php | 99 ++++ routes/web.php | 26 +- 15 files changed, 2441 insertions(+), 7 deletions(-) create mode 100644 app/Http/Controllers/Finance/SalesCommissionController.php create mode 100644 app/Models/Sales/SalesCommission.php create mode 100644 app/Models/Sales/SalesCommissionDetail.php create mode 100644 app/Services/SalesCommissionService.php create mode 100644 database/seeders/SalesCommissionMenuSeeder.php create mode 100644 resources/views/finance/sales-commission/index.blade.php create mode 100644 resources/views/finance/sales-commission/partials/commission-table.blade.php create mode 100644 resources/views/finance/sales-commission/partials/detail-modal.blade.php create mode 100644 resources/views/finance/sales-commission/partials/payment-form.blade.php create mode 100644 resources/views/finance/sales-commission/partials/stats-cards.blade.php create mode 100644 resources/views/sales/dashboard/partials/my-commission.blade.php diff --git a/app/Http/Controllers/Finance/SalesCommissionController.php b/app/Http/Controllers/Finance/SalesCommissionController.php new file mode 100644 index 00000000..0aa5a8e0 --- /dev/null +++ b/app/Http/Controllers/Finance/SalesCommissionController.php @@ -0,0 +1,381 @@ +header('HX-Request') && !$request->header('HX-Boosted')) { + return response('', 200)->header('HX-Redirect', route('finance.sales-commissions.index')); + } + + // 필터 파라미터 + $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'), + '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(); + + return view('finance.sales-commission.index', compact( + 'commissions', + 'stats', + 'partners', + 'pendingTenants', + 'year', + 'month', + 'filters' + )); + } + + /** + * 정산 상세 조회 + */ + public function show(int $id): JsonResponse + { + $commission = $this->service->getCommissionById($id); + + if (!$commission) { + return response()->json([ + 'success' => false, + 'message' => '정산 정보를 찾을 수 없습니다.', + ], 404); + } + + return response()->json([ + 'success' => true, + 'data' => $commission, + ]); + } + + /** + * 정산 상세 모달 (HTMX) + */ + public function detail(int $id): View + { + $commission = $this->service->getCommissionById($id); + + return view('finance.sales-commission.partials.detail-modal', compact('commission')); + } + + /** + * 입금 등록 (수당 생성) + */ + public function registerPayment(Request $request): JsonResponse + { + $validated = $request->validate([ + 'management_id' => 'required|integer|exists:sales_tenant_managements,id', + 'payment_type' => 'required|in:deposit,balance', + 'payment_amount' => 'required|numeric|min:0', + 'payment_date' => 'required|date', + ]); + + try { + $commission = $this->service->createCommission( + $validated['management_id'], + $validated['payment_type'], + $validated['payment_amount'], + $validated['payment_date'] + ); + + return response()->json([ + 'success' => true, + 'message' => '입금이 등록되었습니다.', + 'data' => $commission, + ]); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + } + + /** + * 승인 처리 + */ + public function approve(int $id): JsonResponse + { + try { + $commission = $this->service->approve($id, auth()->id()); + + return response()->json([ + 'success' => true, + 'message' => '승인되었습니다.', + 'data' => $commission, + ]); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + } + + /** + * 일괄 승인 + */ + public function bulkApprove(Request $request): JsonResponse + { + $validated = $request->validate([ + 'ids' => 'required|array|min:1', + 'ids.*' => 'integer|exists:sales_commissions,id', + ]); + + try { + $count = $this->service->bulkApprove($validated['ids'], auth()->id()); + + return response()->json([ + 'success' => true, + 'message' => "{$count}건이 승인되었습니다.", + 'count' => $count, + ]); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + } + + /** + * 지급완료 처리 + */ + public function markPaid(int $id, Request $request): JsonResponse + { + $validated = $request->validate([ + 'bank_reference' => 'nullable|string|max:100', + ]); + + try { + $commission = $this->service->markAsPaid($id, $validated['bank_reference'] ?? null); + + return response()->json([ + 'success' => true, + 'message' => '지급완료 처리되었습니다.', + 'data' => $commission, + ]); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + } + + /** + * 일괄 지급완료 + */ + public function bulkMarkPaid(Request $request): JsonResponse + { + $validated = $request->validate([ + 'ids' => 'required|array|min:1', + 'ids.*' => 'integer|exists:sales_commissions,id', + 'bank_reference' => 'nullable|string|max:100', + ]); + + try { + $count = $this->service->bulkMarkAsPaid($validated['ids'], $validated['bank_reference'] ?? null); + + return response()->json([ + 'success' => true, + 'message' => "{$count}건이 지급완료 처리되었습니다.", + 'count' => $count, + ]); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + } + + /** + * 취소 처리 + */ + public function cancel(int $id): JsonResponse + { + try { + $commission = $this->service->cancel($id); + + return response()->json([ + 'success' => true, + 'message' => '취소되었습니다.', + 'data' => $commission, + ]); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + } + + /** + * 정산 테이블 부분 새로고침 (HTMX) + */ + public function table(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'), + 'search' => $request->input('search'), + ]; + + $commissions = $this->service->getCommissions($filters); + + return view('finance.sales-commission.partials.commission-table', compact('commissions')); + } + + /** + * 통계 카드 부분 새로고침 (HTMX) + */ + public function stats(Request $request): View + { + $year = $request->input('year', now()->year); + $month = $request->input('month', now()->month); + + $stats = $this->service->getSettlementStats($year, $month); + + return view('finance.sales-commission.partials.stats-cards', compact('stats', 'year', 'month')); + } + + /** + * 입금 등록 폼 (HTMX 모달) + */ + public function paymentForm(Request $request): View + { + $managementId = $request->input('management_id'); + $management = null; + + if ($managementId) { + $management = SalesTenantManagement::with(['tenant', 'salesPartner.user', 'contractProducts.product']) + ->find($managementId); + } + + // 입금 대기 테넌트 목록 + $pendingTenants = $this->service->getPendingPaymentTenants(); + + return view('finance.sales-commission.partials.payment-form', compact('management', 'pendingTenants')); + } + + /** + * 엑셀 다운로드 + */ + public function export(Request $request) + { + $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'), + ]; + + // 전체 데이터 조회 (페이지네이션 없이) + $commissions = SalesCommission::query() + ->with(['tenant', 'partner.user', 'manager']) + ->when(!empty($filters['status']), fn($q) => $q->where('status', $filters['status'])) + ->when(!empty($filters['payment_type']), fn($q) => $q->where('payment_type', $filters['payment_type'])) + ->when(!empty($filters['partner_id']), fn($q) => $q->where('partner_id', $filters['partner_id'])) + ->forScheduledMonth($year, $month) + ->orderBy('scheduled_payment_date') + ->get(); + + // CSV 생성 + $filename = "sales_commission_{$year}_{$month}.csv"; + + $headers = [ + 'Content-Type' => 'text/csv; charset=UTF-8', + 'Content-Disposition' => "attachment; filename=\"{$filename}\"", + ]; + + $callback = function () use ($commissions) { + $file = fopen('php://output', 'w'); + + // BOM for UTF-8 + fprintf($file, chr(0xEF).chr(0xBB).chr(0xBF)); + + // 헤더 + fputcsv($file, [ + '번호', '테넌트', '입금구분', '입금액', '입금일', + '기준액', '영업파트너', '파트너수당', '매니저', '매니저수당', + '지급예정일', '상태', '실제지급일' + ]); + + // 데이터 + foreach ($commissions as $commission) { + fputcsv($file, [ + $commission->id, + $commission->tenant->name ?? $commission->tenant->company_name, + $commission->payment_type_label, + number_format($commission->payment_amount), + $commission->payment_date->format('Y-m-d'), + number_format($commission->base_amount), + $commission->partner?->user?->name ?? '-', + number_format($commission->partner_commission), + $commission->manager?->name ?? '-', + number_format($commission->manager_commission), + $commission->scheduled_payment_date->format('Y-m-d'), + $commission->status_label, + $commission->actual_payment_date?->format('Y-m-d') ?? '-', + ]); + } + + fclose($file); + }; + + return response()->stream($callback, 200, $headers); + } +} diff --git a/app/Http/Controllers/Sales/SalesDashboardController.php b/app/Http/Controllers/Sales/SalesDashboardController.php index 11ee5b19..fe077e3d 100644 --- a/app/Http/Controllers/Sales/SalesDashboardController.php +++ b/app/Http/Controllers/Sales/SalesDashboardController.php @@ -3,10 +3,12 @@ namespace App\Http\Controllers\Sales; use App\Http\Controllers\Controller; +use App\Models\Sales\SalesPartner; use App\Models\Sales\SalesTenantManagement; use App\Models\Sales\TenantProspect; use App\Models\Tenants\Tenant; use App\Models\User; +use App\Services\SalesCommissionService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\View\View; @@ -16,6 +18,10 @@ */ class SalesDashboardController extends Controller { + public function __construct( + private SalesCommissionService $commissionService + ) {} + /** * 대시보드 화면 */ @@ -23,6 +29,9 @@ public function index(Request $request): View { $data = $this->getDashboardData($request); + // 영업파트너 수당 정보 추가 + $data = array_merge($data, $this->getCommissionData()); + return view('sales.dashboard.index', $data); } @@ -235,4 +244,26 @@ public function getManagers(Request $request): JsonResponse 'managers' => $managers, ]); } + + /** + * 영업파트너 수당 정보 조회 + */ + private function getCommissionData(): array + { + $user = auth()->user(); + $commissionSummary = []; + $recentCommissions = collect(); + + // 현재 사용자가 영업파트너인지 확인 + $partner = SalesPartner::where('user_id', $user->id) + ->where('status', 'active') + ->first(); + + if ($partner) { + $commissionSummary = $this->commissionService->getPartnerCommissionSummary($partner->id); + $recentCommissions = $this->commissionService->getRecentCommissions($partner->id, 5); + } + + return compact('commissionSummary', 'recentCommissions', 'partner'); + } } diff --git a/app/Models/Sales/SalesCommission.php b/app/Models/Sales/SalesCommission.php new file mode 100644 index 00000000..28c1bccb --- /dev/null +++ b/app/Models/Sales/SalesCommission.php @@ -0,0 +1,293 @@ + '대기', + self::STATUS_APPROVED => '승인', + self::STATUS_PAID => '지급완료', + self::STATUS_CANCELLED => '취소', + ]; + + /** + * 입금 구분 라벨 + */ + public static array $paymentTypeLabels = [ + self::PAYMENT_DEPOSIT => '계약금', + self::PAYMENT_BALANCE => '잔금', + ]; + + protected $fillable = [ + 'tenant_id', + 'management_id', + 'payment_type', + 'payment_amount', + 'payment_date', + 'base_amount', + 'partner_rate', + 'manager_rate', + 'partner_commission', + 'manager_commission', + 'scheduled_payment_date', + 'status', + 'actual_payment_date', + 'partner_id', + 'manager_user_id', + 'notes', + 'bank_reference', + 'approved_by', + 'approved_at', + ]; + + protected $casts = [ + 'payment_amount' => 'decimal:2', + 'base_amount' => 'decimal:2', + 'partner_rate' => 'decimal:2', + 'manager_rate' => 'decimal:2', + 'partner_commission' => 'decimal:2', + 'manager_commission' => 'decimal:2', + 'payment_date' => 'date', + 'scheduled_payment_date' => 'date', + 'actual_payment_date' => 'date', + 'approved_at' => 'datetime', + ]; + + /** + * 테넌트 관계 + */ + public function tenant(): BelongsTo + { + return $this->belongsTo(Tenant::class); + } + + /** + * 영업관리 관계 + */ + public function management(): BelongsTo + { + return $this->belongsTo(SalesTenantManagement::class, 'management_id'); + } + + /** + * 영업파트너 관계 + */ + public function partner(): BelongsTo + { + return $this->belongsTo(SalesPartner::class, 'partner_id'); + } + + /** + * 매니저(사용자) 관계 + */ + public function manager(): BelongsTo + { + return $this->belongsTo(User::class, 'manager_user_id'); + } + + /** + * 상세 내역 관계 + */ + public function details(): HasMany + { + return $this->hasMany(SalesCommissionDetail::class, 'commission_id'); + } + + /** + * 승인자 관계 + */ + public function approver(): BelongsTo + { + return $this->belongsTo(User::class, 'approved_by'); + } + + /** + * 상태 라벨 Accessor + */ + public function getStatusLabelAttribute(): string + { + return self::$statusLabels[$this->status] ?? $this->status; + } + + /** + * 입금 구분 라벨 Accessor + */ + public function getPaymentTypeLabelAttribute(): string + { + return self::$paymentTypeLabels[$this->payment_type] ?? $this->payment_type; + } + + /** + * 총 수당액 Accessor + */ + public function getTotalCommissionAttribute(): float + { + return $this->partner_commission + $this->manager_commission; + } + + /** + * 지급예정일 계산 (입금일 익월 10일) + */ + public static function calculateScheduledPaymentDate(Carbon $paymentDate): Carbon + { + return $paymentDate->copy()->addMonth()->day(10); + } + + /** + * 승인 처리 + */ + public function approve(int $approverId): bool + { + if ($this->status !== self::STATUS_PENDING) { + return false; + } + + return $this->update([ + 'status' => self::STATUS_APPROVED, + 'approved_by' => $approverId, + 'approved_at' => now(), + ]); + } + + /** + * 지급완료 처리 + */ + public function markAsPaid(?string $bankReference = null): bool + { + if ($this->status !== self::STATUS_APPROVED) { + return false; + } + + return $this->update([ + 'status' => self::STATUS_PAID, + 'actual_payment_date' => now()->format('Y-m-d'), + 'bank_reference' => $bankReference, + ]); + } + + /** + * 취소 처리 + */ + public function cancel(): bool + { + if ($this->status === self::STATUS_PAID) { + return false; + } + + return $this->update([ + 'status' => self::STATUS_CANCELLED, + ]); + } + + /** + * 대기 상태 스코프 + */ + public function scopePending(Builder $query): Builder + { + return $query->where('status', self::STATUS_PENDING); + } + + /** + * 승인완료 스코프 + */ + public function scopeApproved(Builder $query): Builder + { + return $query->where('status', self::STATUS_APPROVED); + } + + /** + * 지급완료 스코프 + */ + public function scopePaid(Builder $query): Builder + { + return $query->where('status', self::STATUS_PAID); + } + + /** + * 특정 영업파트너 스코프 + */ + public function scopeForPartner(Builder $query, int $partnerId): Builder + { + return $query->where('partner_id', $partnerId); + } + + /** + * 특정 매니저 스코프 + */ + public function scopeForManager(Builder $query, int $managerUserId): Builder + { + return $query->where('manager_user_id', $managerUserId); + } + + /** + * 특정 월 지급예정 스코프 + */ + public function scopeForScheduledMonth(Builder $query, int $year, int $month): Builder + { + return $query->whereYear('scheduled_payment_date', $year) + ->whereMonth('scheduled_payment_date', $month); + } + + /** + * 특정 기간 입금 스코프 + */ + public function scopePaymentDateBetween(Builder $query, string $startDate, string $endDate): Builder + { + return $query->whereBetween('payment_date', [$startDate, $endDate]); + } +} diff --git a/app/Models/Sales/SalesCommissionDetail.php b/app/Models/Sales/SalesCommissionDetail.php new file mode 100644 index 00000000..214a758d --- /dev/null +++ b/app/Models/Sales/SalesCommissionDetail.php @@ -0,0 +1,68 @@ + 'decimal:2', + 'base_amount' => 'decimal:2', + 'partner_rate' => 'decimal:2', + 'manager_rate' => 'decimal:2', + 'partner_commission' => 'decimal:2', + 'manager_commission' => 'decimal:2', + ]; + + /** + * 수수료 정산 관계 + */ + public function commission(): BelongsTo + { + return $this->belongsTo(SalesCommission::class, 'commission_id'); + } + + /** + * 계약 상품 관계 + */ + public function contractProduct(): BelongsTo + { + return $this->belongsTo(SalesContractProduct::class, 'contract_product_id'); + } + + /** + * 총 수당액 Accessor + */ + public function getTotalCommissionAttribute(): float + { + return $this->partner_commission + $this->manager_commission; + } +} diff --git a/app/Models/Sales/SalesTenantManagement.php b/app/Models/Sales/SalesTenantManagement.php index b6db51d5..377c21dd 100644 --- a/app/Models/Sales/SalesTenantManagement.php +++ b/app/Models/Sales/SalesTenantManagement.php @@ -61,6 +61,14 @@ class SalesTenantManagement extends Model 'hq_status', 'incentive_status', 'notes', + // 입금 정보 + 'deposit_amount', + 'deposit_paid_date', + 'deposit_status', + 'balance_amount', + 'balance_paid_date', + 'balance_status', + 'total_registration_fee', ]; protected $casts = [ @@ -76,6 +84,12 @@ class SalesTenantManagement extends Model 'onboarding_completed_at' => 'datetime', 'membership_paid_at' => 'datetime', 'commission_paid_at' => 'datetime', + // 입금 정보 + 'deposit_amount' => 'decimal:2', + 'deposit_paid_date' => 'date', + 'balance_amount' => 'decimal:2', + 'balance_paid_date' => 'date', + 'total_registration_fee' => 'decimal:2', ]; /** @@ -198,6 +212,22 @@ public function consultations(): HasMany return $this->hasMany(SalesConsultation::class, 'tenant_id', 'tenant_id'); } + /** + * 수수료 정산 관계 + */ + public function commissions(): HasMany + { + return $this->hasMany(SalesCommission::class, 'management_id'); + } + + /** + * 계약 상품 관계 + */ + public function contractProducts(): HasMany + { + return $this->hasMany(SalesContractProduct::class, 'management_id'); + } + /** * 테넌트 ID로 조회 또는 생성 */ diff --git a/app/Services/SalesCommissionService.php b/app/Services/SalesCommissionService.php new file mode 100644 index 00000000..596d0e10 --- /dev/null +++ b/app/Services/SalesCommissionService.php @@ -0,0 +1,449 @@ +with(['tenant', 'partner.user', 'manager', 'management']); + + // 상태 필터 + if (!empty($filters['status'])) { + $query->where('status', $filters['status']); + } + + // 입금구분 필터 + if (!empty($filters['payment_type'])) { + $query->where('payment_type', $filters['payment_type']); + } + + // 영업파트너 필터 + if (!empty($filters['partner_id'])) { + $query->where('partner_id', $filters['partner_id']); + } + + // 매니저 필터 + if (!empty($filters['manager_user_id'])) { + $query->where('manager_user_id', $filters['manager_user_id']); + } + + // 지급예정 년/월 필터 + if (!empty($filters['scheduled_year']) && !empty($filters['scheduled_month'])) { + $query->forScheduledMonth((int) $filters['scheduled_year'], (int) $filters['scheduled_month']); + } + + // 입금일 기간 필터 + if (!empty($filters['payment_start_date']) && !empty($filters['payment_end_date'])) { + $query->paymentDateBetween($filters['payment_start_date'], $filters['payment_end_date']); + } + + // 테넌트 검색 + if (!empty($filters['search'])) { + $search = $filters['search']; + $query->whereHas('tenant', function ($q) use ($search) { + $q->where('name', 'like', "%{$search}%") + ->orWhere('company_name', 'like', "%{$search}%"); + }); + } + + return $query + ->orderBy('scheduled_payment_date', 'desc') + ->orderBy('created_at', 'desc') + ->paginate($perPage); + } + + /** + * 정산 상세 조회 + */ + public function getCommissionById(int $id): ?SalesCommission + { + return SalesCommission::with([ + 'tenant', + 'partner.user', + 'manager', + 'management', + 'details.contractProduct.product', + 'approver', + ])->find($id); + } + + // ========================================================================= + // 수당 생성 (입금 시) + // ========================================================================= + + /** + * 입금 등록 및 수당 생성 + */ + public function createCommission(int $managementId, string $paymentType, float $paymentAmount, string $paymentDate): SalesCommission + { + return DB::transaction(function () use ($managementId, $paymentType, $paymentAmount, $paymentDate) { + $management = SalesTenantManagement::with(['salesPartner', 'contractProducts.product']) + ->findOrFail($managementId); + + // 영업파트너 필수 체크 + if (!$management->sales_partner_id) { + throw new \Exception('영업파트너가 지정되지 않았습니다.'); + } + + $partner = $management->salesPartner; + $paymentDateCarbon = Carbon::parse($paymentDate); + + // 계약 상품이 없으면 기본 계산 + $contractProducts = $management->contractProducts; + $totalRegistrationFee = $contractProducts->sum('registration_fee') ?: $paymentAmount * 2; + $baseAmount = $totalRegistrationFee / 2; // 가입비의 50% + + // 수당률 (영업파트너 설정 또는 기본값) + $partnerRate = $partner->commission_rate ?? self::DEFAULT_PARTNER_RATE; + $managerRate = $partner->manager_commission_rate ?? self::DEFAULT_MANAGER_RATE; + + // 수당 계산 + $partnerCommission = $baseAmount * ($partnerRate / 100); + $managerCommission = $management->manager_user_id + ? $baseAmount * ($managerRate / 100) + : 0; + + // 지급예정일 (익월 10일) + $scheduledPaymentDate = SalesCommission::calculateScheduledPaymentDate($paymentDateCarbon); + + // 정산 생성 + $commission = SalesCommission::create([ + 'tenant_id' => $management->tenant_id, + 'management_id' => $managementId, + 'payment_type' => $paymentType, + 'payment_amount' => $paymentAmount, + 'payment_date' => $paymentDate, + 'base_amount' => $baseAmount, + 'partner_rate' => $partnerRate, + 'manager_rate' => $managerRate, + 'partner_commission' => $partnerCommission, + 'manager_commission' => $managerCommission, + 'scheduled_payment_date' => $scheduledPaymentDate, + 'status' => SalesCommission::STATUS_PENDING, + 'partner_id' => $partner->id, + 'manager_user_id' => $management->manager_user_id, + ]); + + // 상품별 상세 내역 생성 + foreach ($contractProducts as $contractProduct) { + $productBaseAmount = ($contractProduct->registration_fee ?? 0) / 2; + $productPartnerRate = $contractProduct->product->partner_commission ?? $partnerRate; + $productManagerRate = $contractProduct->product->manager_commission ?? $managerRate; + + SalesCommissionDetail::create([ + 'commission_id' => $commission->id, + 'contract_product_id' => $contractProduct->id, + 'registration_fee' => $contractProduct->registration_fee ?? 0, + 'base_amount' => $productBaseAmount, + 'partner_rate' => $productPartnerRate, + 'manager_rate' => $productManagerRate, + 'partner_commission' => $productBaseAmount * ($productPartnerRate / 100), + 'manager_commission' => $productBaseAmount * ($productManagerRate / 100), + ]); + } + + // management 입금 정보 업데이트 + $updateData = []; + if ($paymentType === SalesCommission::PAYMENT_DEPOSIT) { + $updateData = [ + 'deposit_amount' => $paymentAmount, + 'deposit_paid_date' => $paymentDate, + 'deposit_status' => 'paid', + ]; + } else { + $updateData = [ + 'balance_amount' => $paymentAmount, + 'balance_paid_date' => $paymentDate, + 'balance_status' => 'paid', + ]; + } + + // 총 가입비 업데이트 + $updateData['total_registration_fee'] = $totalRegistrationFee; + $management->update($updateData); + + return $commission->load(['tenant', 'partner.user', 'manager', 'details']); + }); + } + + // ========================================================================= + // 승인/지급 처리 + // ========================================================================= + + /** + * 승인 처리 + */ + public function approve(int $commissionId, int $approverId): SalesCommission + { + $commission = SalesCommission::findOrFail($commissionId); + + if (!$commission->approve($approverId)) { + throw new \Exception('승인할 수 없는 상태입니다.'); + } + + return $commission->fresh(['tenant', 'partner.user', 'manager']); + } + + /** + * 일괄 승인 + */ + public function bulkApprove(array $ids, int $approverId): int + { + $count = 0; + + DB::transaction(function () use ($ids, $approverId, &$count) { + $commissions = SalesCommission::whereIn('id', $ids) + ->where('status', SalesCommission::STATUS_PENDING) + ->get(); + + foreach ($commissions as $commission) { + if ($commission->approve($approverId)) { + $count++; + } + } + }); + + return $count; + } + + /** + * 지급완료 처리 + */ + public function markAsPaid(int $commissionId, ?string $bankReference = null): SalesCommission + { + $commission = SalesCommission::findOrFail($commissionId); + + if (!$commission->markAsPaid($bankReference)) { + throw new \Exception('지급완료 처리할 수 없는 상태입니다.'); + } + + // 영업파트너 누적 수당 업데이트 + $this->updatePartnerTotalCommission($commission->partner_id); + + return $commission->fresh(['tenant', 'partner.user', 'manager']); + } + + /** + * 일괄 지급완료 + */ + public function bulkMarkAsPaid(array $ids, ?string $bankReference = null): int + { + $count = 0; + $partnerIds = []; + + DB::transaction(function () use ($ids, $bankReference, &$count, &$partnerIds) { + $commissions = SalesCommission::whereIn('id', $ids) + ->where('status', SalesCommission::STATUS_APPROVED) + ->get(); + + foreach ($commissions as $commission) { + if ($commission->markAsPaid($bankReference)) { + $count++; + $partnerIds[] = $commission->partner_id; + } + } + }); + + // 영업파트너 누적 수당 일괄 업데이트 + foreach (array_unique($partnerIds) as $partnerId) { + $this->updatePartnerTotalCommission($partnerId); + } + + return $count; + } + + /** + * 취소 처리 + */ + public function cancel(int $commissionId): SalesCommission + { + $commission = SalesCommission::findOrFail($commissionId); + + if (!$commission->cancel()) { + throw new \Exception('취소할 수 없는 상태입니다.'); + } + + return $commission->fresh(['tenant', 'partner.user', 'manager']); + } + + // ========================================================================= + // 영업파트너/매니저 대시보드용 + // ========================================================================= + + /** + * 영업파트너 수당 요약 + */ + public function getPartnerCommissionSummary(int $partnerId): array + { + $commissions = SalesCommission::forPartner($partnerId)->get(); + + $thisMonth = now()->format('Y-m'); + $thisMonthStart = now()->startOfMonth()->format('Y-m-d'); + $thisMonthEnd = now()->endOfMonth()->format('Y-m-d'); + + return [ + // 이번 달 지급예정 (승인 완료된 건) + 'scheduled_this_month' => $commissions + ->where('status', SalesCommission::STATUS_APPROVED) + ->filter(fn($c) => $c->scheduled_payment_date->format('Y-m') === $thisMonth) + ->sum('partner_commission'), + + // 누적 수령 수당 + 'total_received' => $commissions + ->where('status', SalesCommission::STATUS_PAID) + ->sum('partner_commission'), + + // 대기중 수당 + 'pending_amount' => $commissions + ->where('status', SalesCommission::STATUS_PENDING) + ->sum('partner_commission'), + + // 이번 달 신규 계약 건수 + 'contracts_this_month' => $commissions + ->filter(fn($c) => $c->payment_date >= $thisMonthStart && $c->payment_date <= $thisMonthEnd) + ->count(), + ]; + } + + /** + * 매니저 수당 요약 + */ + public function getManagerCommissionSummary(int $managerUserId): array + { + $commissions = SalesCommission::forManager($managerUserId)->get(); + + $thisMonth = now()->format('Y-m'); + + return [ + // 이번 달 지급예정 (승인 완료된 건) + 'scheduled_this_month' => $commissions + ->where('status', SalesCommission::STATUS_APPROVED) + ->filter(fn($c) => $c->scheduled_payment_date->format('Y-m') === $thisMonth) + ->sum('manager_commission'), + + // 누적 수령 수당 + 'total_received' => $commissions + ->where('status', SalesCommission::STATUS_PAID) + ->sum('manager_commission'), + + // 대기중 수당 + 'pending_amount' => $commissions + ->where('status', SalesCommission::STATUS_PENDING) + ->sum('manager_commission'), + ]; + } + + /** + * 최근 수당 내역 (대시보드용) + */ + public function getRecentCommissions(int $partnerId, int $limit = 5): Collection + { + return SalesCommission::forPartner($partnerId) + ->with(['tenant', 'management']) + ->orderBy('created_at', 'desc') + ->limit($limit) + ->get(); + } + + // ========================================================================= + // 통계 + // ========================================================================= + + /** + * 정산 통계 (본사 대시보드용) + */ + public function getSettlementStats(int $year, int $month): array + { + $commissions = SalesCommission::forScheduledMonth($year, $month)->get(); + + return [ + // 상태별 건수 및 금액 + 'pending' => [ + 'count' => $commissions->where('status', SalesCommission::STATUS_PENDING)->count(), + 'partner_total' => $commissions->where('status', SalesCommission::STATUS_PENDING)->sum('partner_commission'), + 'manager_total' => $commissions->where('status', SalesCommission::STATUS_PENDING)->sum('manager_commission'), + ], + 'approved' => [ + 'count' => $commissions->where('status', SalesCommission::STATUS_APPROVED)->count(), + 'partner_total' => $commissions->where('status', SalesCommission::STATUS_APPROVED)->sum('partner_commission'), + 'manager_total' => $commissions->where('status', SalesCommission::STATUS_APPROVED)->sum('manager_commission'), + ], + 'paid' => [ + 'count' => $commissions->where('status', SalesCommission::STATUS_PAID)->count(), + 'partner_total' => $commissions->where('status', SalesCommission::STATUS_PAID)->sum('partner_commission'), + 'manager_total' => $commissions->where('status', SalesCommission::STATUS_PAID)->sum('manager_commission'), + ], + + // 전체 합계 + 'total' => [ + 'count' => $commissions->count(), + 'base_amount' => $commissions->sum('base_amount'), + 'partner_commission' => $commissions->sum('partner_commission'), + 'manager_commission' => $commissions->sum('manager_commission'), + ], + ]; + } + + /** + * 입금 대기 중인 테넌트 목록 + */ + public function getPendingPaymentTenants(): Collection + { + return SalesTenantManagement::with(['tenant', 'salesPartner.user', 'manager']) + ->contracted() + ->where(function ($query) { + $query->where('deposit_status', 'pending') + ->orWhere('balance_status', 'pending'); + }) + ->orderBy('contracted_at', 'desc') + ->get(); + } + + // ========================================================================= + // 내부 메서드 + // ========================================================================= + + /** + * 영업파트너 누적 수당 업데이트 + */ + private function updatePartnerTotalCommission(int $partnerId): void + { + $totalPaid = SalesCommission::forPartner($partnerId) + ->paid() + ->sum('partner_commission'); + + $contractCount = SalesCommission::forPartner($partnerId) + ->paid() + ->count(); + + SalesPartner::where('id', $partnerId)->update([ + 'total_commission' => $totalPaid, + 'total_contracts' => $contractCount, + ]); + } +} diff --git a/database/seeders/SalesCommissionMenuSeeder.php b/database/seeders/SalesCommissionMenuSeeder.php new file mode 100644 index 00000000..bae6775e --- /dev/null +++ b/database/seeders/SalesCommissionMenuSeeder.php @@ -0,0 +1,101 @@ +where('tenant_id', 1) + ->where('name', '재무관리') + ->first(); + + if (!$financeMenu) { + $this->command->warn('재무관리 메뉴를 찾을 수 없습니다. 먼저 재무관리 메뉴를 생성해주세요.'); + return; + } + + // 정산관리 서브메뉴 찾기 또는 생성 + $settlementMenu = Menu::withoutGlobalScopes() + ->where('tenant_id', 1) + ->where('parent_id', $financeMenu->id) + ->where('name', '정산관리') + ->first(); + + if (!$settlementMenu) { + // 정산관리 메뉴가 없으면 생성 + $maxOrder = Menu::withoutGlobalScopes() + ->where('tenant_id', 1) + ->where('parent_id', $financeMenu->id) + ->max('sort_order') ?? 0; + + $settlementMenu = Menu::create([ + 'tenant_id' => 1, + 'parent_id' => $financeMenu->id, + 'name' => '정산관리', + 'url' => '#', + 'icon' => 'calculator', + 'is_active' => true, + 'hidden' => false, + 'sort_order' => $maxOrder + 1, + ]); + + $this->command->info('정산관리 메뉴를 생성했습니다.'); + } + + // 영업수수료정산 메뉴 추가/업데이트 + $existingMenu = Menu::withoutGlobalScopes() + ->where('tenant_id', 1) + ->where('parent_id', $settlementMenu->id) + ->where('url', '/finance/sales-commissions') + ->first(); + + if ($existingMenu) { + $existingMenu->update([ + 'name' => '영업수수료정산', + 'is_active' => true, + 'hidden' => false, + 'options' => [ + 'route_name' => 'finance.sales-commissions.index', + 'description' => '영업파트너 및 매니저 수당 정산 관리', + ], + ]); + $this->command->info('영업수수료정산 메뉴를 업데이트했습니다.'); + } else { + $maxOrder = Menu::withoutGlobalScopes() + ->where('tenant_id', 1) + ->where('parent_id', $settlementMenu->id) + ->max('sort_order') ?? 0; + + Menu::create([ + 'tenant_id' => 1, + 'parent_id' => $settlementMenu->id, + 'name' => '영업수수료정산', + 'url' => '/finance/sales-commissions', + 'icon' => 'currency-dollar', + 'is_active' => true, + 'hidden' => false, + 'sort_order' => $maxOrder + 1, + 'options' => [ + 'route_name' => 'finance.sales-commissions.index', + 'description' => '영업파트너 및 매니저 수당 정산 관리', + ], + ]); + $this->command->info('영업수수료정산 메뉴를 생성했습니다.'); + } + + $this->command->info('메뉴 시더 완료!'); + } +} diff --git a/resources/views/finance/sales-commission/index.blade.php b/resources/views/finance/sales-commission/index.blade.php new file mode 100644 index 00000000..91faae57 --- /dev/null +++ b/resources/views/finance/sales-commission/index.blade.php @@ -0,0 +1,376 @@ +@extends('layouts.app') + +@section('title', '영업수수료정산') + +@section('content') +
+ {{-- 페이지 헤더 --}} +
+
+

영업수수료정산

+

{{ $year }}년 {{ $month }}월 지급예정

+
+
+ + + + + + 엑셀 다운로드 + +
+
+ + {{-- 통계 카드 --}} +
+ @include('finance.sales-commission.partials.stats-cards', ['stats' => $stats, 'year' => $year, 'month' => $month]) +
+ + {{-- 필터 섹션 --}} +
+
+
+ {{-- 년/월 선택 --}} +
+ + +
+
+ + +
+ + {{-- 상태 필터 --}} +
+ + +
+ + {{-- 입금구분 필터 --}} +
+ + +
+ + {{-- 영업파트너 필터 --}} +
+ + +
+ + {{-- 버튼 --}} +
+ + + 초기화 + +
+
+
+
+ + {{-- 일괄 처리 버튼 --}} + + + {{-- 정산 테이블 --}} +
+ @include('finance.sales-commission.partials.commission-table', ['commissions' => $commissions]) +
+
+ +{{-- 입금 등록 모달 --}} + + +{{-- 상세 모달 --}} + +@endsection + +@push('scripts') + +@endpush diff --git a/resources/views/finance/sales-commission/partials/commission-table.blade.php b/resources/views/finance/sales-commission/partials/commission-table.blade.php new file mode 100644 index 00000000..ea6f0cba --- /dev/null +++ b/resources/views/finance/sales-commission/partials/commission-table.blade.php @@ -0,0 +1,138 @@ +{{-- 정산 테이블 --}} +
+
+ + + + + + + + + + + + + + + + + + + @forelse ($commissions as $commission) + + + + + + + + + + + + + + + @empty + + + + @endforelse + +
+ + 테넌트입금구분입금액입금일영업파트너파트너수당매니저매니저수당지급예정일상태액션
+ @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) }} + + {{ $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 +
+
+ 등록된 정산 내역이 없습니다. +
+
+ + {{-- 페이지네이션 --}} + @if ($commissions->hasPages()) +
+ {{ $commissions->links() }} +
+ @endif +
diff --git a/resources/views/finance/sales-commission/partials/detail-modal.blade.php b/resources/views/finance/sales-commission/partials/detail-modal.blade.php new file mode 100644 index 00000000..4437755e --- /dev/null +++ b/resources/views/finance/sales-commission/partials/detail-modal.blade.php @@ -0,0 +1,196 @@ +{{-- 정산 상세 모달 --}} +
+
+

정산 상세

+ +
+
+ +@if ($commission) +
+ {{-- 기본 정보 --}} +
+
+

테넌트

+

{{ $commission->tenant->name ?? $commission->tenant->company_name ?? '-' }}

+
+
+

상태

+ @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 }} + +
+
+

입금 구분

+ + {{ $commission->payment_type_label }} + +
+
+

입금일

+

{{ $commission->payment_date->format('Y-m-d') }}

+
+
+ + {{-- 금액 정보 --}} +
+

금액 정보

+
+
+ 입금액 + {{ number_format($commission->payment_amount) }}원 +
+
+ 수당 기준액 (가입비 50%) + {{ number_format($commission->base_amount) }}원 +
+
+
+ + {{-- 수당 정보 --}} +
+

수당 정보

+
+
+
+ 영업파트너 + {{ $commission->partner?->user?->name ?? '-' }} +
+
+ {{ $commission->partner_rate }}% + {{ number_format($commission->partner_commission) }}원 +
+
+
+
+ 매니저 + {{ $commission->manager?->name ?? '-' }} +
+
+ {{ $commission->manager_rate }}% + {{ number_format($commission->manager_commission) }}원 +
+
+
+ 총 수당 + {{ number_format($commission->total_commission) }}원 +
+
+
+ + {{-- 지급 일정 --}} +
+
+

지급예정일

+

{{ $commission->scheduled_payment_date->format('Y-m-d') }}

+
+
+

실제지급일

+

{{ $commission->actual_payment_date?->format('Y-m-d') ?? '-' }}

+
+
+ + {{-- 승인 정보 --}} + @if ($commission->approved_at) +
+
+

승인자

+

{{ $commission->approver?->name ?? '-' }}

+
+
+

승인일시

+

{{ $commission->approved_at->format('Y-m-d H:i') }}

+
+
+ @endif + + {{-- 이체 참조번호 --}} + @if ($commission->bank_reference) +
+

이체 참조번호

+

{{ $commission->bank_reference }}

+
+ @endif + + {{-- 메모 --}} + @if ($commission->notes) +
+

메모

+

{{ $commission->notes }}

+
+ @endif + + {{-- 상품별 상세 내역 --}} + @if ($commission->details->count() > 0) +
+

상품별 수당 내역

+
+ + + + + + + + + + + @foreach ($commission->details as $detail) + + + + + + + @endforeach + +
상품가입비파트너수당매니저수당
{{ $detail->contractProduct?->product?->name ?? '-' }}{{ number_format($detail->registration_fee) }}원{{ number_format($detail->partner_commission) }}원{{ number_format($detail->manager_commission) }}원
+
+
+ @endif + + {{-- 액션 버튼 --}} +
+ @if ($commission->status === 'pending') + + + @elseif ($commission->status === 'approved') + + @endif + +
+
+@else +
+ 정산 정보를 찾을 수 없습니다. +
+@endif diff --git a/resources/views/finance/sales-commission/partials/payment-form.blade.php b/resources/views/finance/sales-commission/partials/payment-form.blade.php new file mode 100644 index 00000000..ae0f33f7 --- /dev/null +++ b/resources/views/finance/sales-commission/partials/payment-form.blade.php @@ -0,0 +1,185 @@ +{{-- 입금 등록 폼 --}} +
+ @csrf + + {{-- 테넌트 선택 --}} +
+ + @if ($management) + +
+
{{ $management->tenant->name ?? $management->tenant->company_name }}
+
영업파트너: {{ $management->salesPartner?->user?->name ?? '-' }}
+
+ @else + + @endif +
+ + @if ($management) + {{-- 계약 상품 정보 --}} + @if ($management->contractProducts->count() > 0) +
+ +
+ + + + + + + + + @foreach ($management->contractProducts as $product) + + + + + @endforeach + + + + + + + +
상품명가입비
{{ $product->product?->name ?? '-' }}{{ number_format($product->registration_fee ?? 0) }}원
총 가입비 + {{ number_format($management->contractProducts->sum('registration_fee')) }}원 +
+
+
+ @endif + + {{-- 현재 입금 상태 --}} +
+
+
+ 계약금 +
+ {{ $management->deposit_status === 'paid' ? '입금완료' : '대기' }} + @if ($management->deposit_amount) + ({{ number_format($management->deposit_amount) }}원) + @endif +
+
+
+ 잔금 +
+ {{ $management->balance_status === 'paid' ? '입금완료' : '대기' }} + @if ($management->balance_amount) + ({{ number_format($management->balance_amount) }}원) + @endif +
+
+
+
+ @endif + + {{-- 입금 구분 --}} +
+ +
+ + +
+
+ + {{-- 입금액 --}} +
+ +
+ + +
+

총 가입비의 50%를 입금받습니다.

+
+ + {{-- 입금일 --}} +
+ + +
+ + {{-- 수당 미리보기 --}} + @if ($management && $management->salesPartner) + @php + $totalFee = $management->contractProducts->sum('registration_fee') ?: 0; + $baseAmount = $totalFee / 2; + $partnerRate = $management->salesPartner->commission_rate ?? 20; + $managerRate = $management->salesPartner->manager_commission_rate ?? 5; + $partnerCommission = $baseAmount * ($partnerRate / 100); + $managerCommission = $management->manager_user_id ? $baseAmount * ($managerRate / 100) : 0; + @endphp +
+

수당 미리보기

+
+
+ 기준액 (가입비의 50%) + {{ number_format($baseAmount) }}원 +
+
+ 영업파트너 수당 ({{ $partnerRate }}%) + {{ number_format($partnerCommission) }}원 +
+
+ 매니저 수당 ({{ $managerRate }}%) + {{ number_format($managerCommission) }}원 +
+
+ 총 수당 + {{ number_format($partnerCommission + $managerCommission) }}원 +
+
+
+ @endif + + {{-- 버튼 --}} +
+ + +
+
diff --git a/resources/views/finance/sales-commission/partials/stats-cards.blade.php b/resources/views/finance/sales-commission/partials/stats-cards.blade.php new file mode 100644 index 00000000..7c5c7818 --- /dev/null +++ b/resources/views/finance/sales-commission/partials/stats-cards.blade.php @@ -0,0 +1,70 @@ +{{-- 통계 카드 --}} +
+ {{-- 지급 대기 --}} +
+
+
+

지급 대기

+

{{ number_format($stats['pending']['partner_total'] + $stats['pending']['manager_total']) }}원

+
+
+ + + +
+
+

{{ $stats['pending']['count'] }}건

+
+ + {{-- 승인 완료 --}} +
+
+
+

승인 완료

+

{{ 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']) }}원

+
+
+ + + +
+
+
+ 파트너: {{ number_format($stats['total']['partner_commission']) }}원 + | + 매니저: {{ number_format($stats['total']['manager_commission']) }}원 +
+
+
diff --git a/resources/views/sales/dashboard/partials/data-container.blade.php b/resources/views/sales/dashboard/partials/data-container.blade.php index 31e1135b..19e96714 100644 --- a/resources/views/sales/dashboard/partials/data-container.blade.php +++ b/resources/views/sales/dashboard/partials/data-container.blade.php @@ -1,5 +1,10 @@ {{-- 대시보드 데이터 컨테이너 (HTMX로 새로고침되는 영역) --}} +{{-- 영업파트너 수당 현황 (파트너인 경우에만 표시) --}} +@if (isset($partner) && $partner) + @include('sales.dashboard.partials.my-commission') +@endif + {{-- 전체 누적 실적 --}} @include('sales.dashboard.partials.stats') diff --git a/resources/views/sales/dashboard/partials/my-commission.blade.php b/resources/views/sales/dashboard/partials/my-commission.blade.php new file mode 100644 index 00000000..7c91c245 --- /dev/null +++ b/resources/views/sales/dashboard/partials/my-commission.blade.php @@ -0,0 +1,99 @@ +{{-- 영업파트너 수당 현황 카드 --}} +
+
+
+

내 수당 현황

+ + 전체보기 → + +
+
+ +
+ {{-- 수당 요약 --}} +
+ {{-- 이번 달 지급예정 --}} +
+
이번 달 지급예정
+
+ {{ number_format($commissionSummary['scheduled_this_month'] ?? 0) }}원 +
+
+ + {{-- 누적 수령액 --}} +
+
누적 수령액
+
+ {{ number_format($commissionSummary['total_received'] ?? 0) }}원 +
+
+ + {{-- 대기중 수당 --}} +
+
대기중 수당
+
+ {{ number_format($commissionSummary['pending_amount'] ?? 0) }}원 +
+
+ + {{-- 이번 달 계약 건수 --}} +
+
이번 달 계약
+
+ {{ $commissionSummary['contracts_this_month'] ?? 0 }}건 +
+
+
+ + {{-- 최근 수당 내역 --}} + @if (isset($recentCommissions) && $recentCommissions->count() > 0) +
+

최근 수당 내역

+
+ @foreach ($recentCommissions as $commission) +
+
+ + {{ $commission->payment_type_label }} + +
+
+ {{ $commission->tenant->name ?? $commission->tenant->company_name ?? '-' }} +
+
+ {{ $commission->payment_date->format('Y-m-d') }} +
+
+
+
+
+ +{{ number_format($commission->partner_commission) }}원 +
+ @php + $statusColors = [ + 'pending' => 'text-yellow-600', + 'approved' => 'text-blue-600', + 'paid' => 'text-green-600', + 'cancelled' => 'text-red-600', + ]; + @endphp +
+ {{ $commission->status_label }} +
+
+
+ @endforeach +
+
+ @else +
+ + + +

아직 수당 내역이 없습니다.

+
+ @endif +
+
diff --git a/routes/web.php b/routes/web.php index 55e20518..b8149146 100644 --- a/routes/web.php +++ b/routes/web.php @@ -682,13 +682,25 @@ return view('finance.purchase'); })->name('purchase'); - // 정산관리 - Route::get('/sales-commission', function () { - if (request()->header('HX-Request')) { - return response('', 200)->header('HX-Redirect', route('finance.sales-commission')); - } - return view('finance.sales-commission'); - })->name('sales-commission'); + // 영업수수료정산 (실제 구현) + Route::prefix('sales-commissions')->name('sales-commissions.')->group(function () { + Route::get('/', [\App\Http\Controllers\Finance\SalesCommissionController::class, 'index'])->name('index'); + Route::get('/export', [\App\Http\Controllers\Finance\SalesCommissionController::class, 'export'])->name('export'); + Route::get('/payment-form', [\App\Http\Controllers\Finance\SalesCommissionController::class, 'paymentForm'])->name('payment-form'); + Route::get('/table', [\App\Http\Controllers\Finance\SalesCommissionController::class, 'table'])->name('table'); + Route::get('/stats', [\App\Http\Controllers\Finance\SalesCommissionController::class, 'stats'])->name('stats'); + Route::post('/payment', [\App\Http\Controllers\Finance\SalesCommissionController::class, 'registerPayment'])->name('payment'); + Route::post('/bulk-approve', [\App\Http\Controllers\Finance\SalesCommissionController::class, 'bulkApprove'])->name('bulk-approve'); + Route::post('/bulk-mark-paid', [\App\Http\Controllers\Finance\SalesCommissionController::class, 'bulkMarkPaid'])->name('bulk-mark-paid'); + Route::get('/{id}', [\App\Http\Controllers\Finance\SalesCommissionController::class, 'show'])->name('show'); + Route::get('/{id}/detail', [\App\Http\Controllers\Finance\SalesCommissionController::class, 'detail'])->name('detail'); + Route::post('/{id}/approve', [\App\Http\Controllers\Finance\SalesCommissionController::class, 'approve'])->name('approve'); + Route::post('/{id}/mark-paid', [\App\Http\Controllers\Finance\SalesCommissionController::class, 'markPaid'])->name('mark-paid'); + Route::post('/{id}/cancel', [\App\Http\Controllers\Finance\SalesCommissionController::class, 'cancel'])->name('cancel'); + }); + + // 기존 sales-commission URL 리다이렉트 (호환성) + Route::get('/sales-commission', fn() => redirect()->route('finance.sales-commissions.index'))->name('sales-commission'); Route::get('/consulting-fee', function () { if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('finance.consulting-fee'));