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'); // 1차/2차 수당 상세 계산 $firstCommissionDetails = $this->calculateStageCommission($commissions, 'first'); $secondCommissionDetails = $this->calculateStageCommission($commissions, 'second'); 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(), // 1차 수당 상세 'first_commission' => $firstCommissionDetails, // 2차 수당 상세 'second_commission' => $secondCommissionDetails, // 총 수당 금액 (1차 + 2차) 'total_commission' => $commissions->sum('partner_commission'), ]; } /** * 단계별 수당 계산 (1차/2차) * 파트너 수당의 50%씩 1차/2차로 분할 */ private function calculateStageCommission($commissions, string $stage): array { $paymentAtField = $stage === 'first' ? 'first_payment_at' : 'second_payment_at'; $paidAtField = $stage === 'first' ? 'first_partner_paid_at' : 'second_partner_paid_at'; $total = 0; $pending = 0; // 납입 대기 (입금 전) $scheduled = 0; // 지급예정 (입금 완료, 수당 미지급) $paid = 0; // 지급완료 foreach ($commissions as $commission) { // 파트너 수당의 50%가 각 단계별 금액 $stageAmount = $commission->partner_commission / 2; $total += $stageAmount; $paymentAt = $commission->{$paymentAtField}; $paidAt = $commission->{$paidAtField}; if ($paidAt) { // 지급완료 $paid += $stageAmount; } elseif ($paymentAt) { // 납입완료, 수당 지급예정 $scheduled += $stageAmount; } else { // 납입 대기 $pending += $stageAmount; } } return [ 'total' => $total, 'pending' => $pending, // 납입 대기 'scheduled' => $scheduled, // 지급예정 'paid' => $paid, // 지급완료 ]; } /** * 매니저 수당 요약 */ 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, ]); } }