2026-01-27 18:59:45 +09:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers\Sales;
|
|
|
|
|
|
|
|
|
|
|
|
use App\Http\Controllers\Controller;
|
2026-01-30 17:02:41 +09:00
|
|
|
|
use App\Models\Sales\SalesCommission;
|
2026-01-31 21:28:28 +09:00
|
|
|
|
use App\Models\Sales\SalesContractProduct;
|
2026-01-29 18:14:11 +09:00
|
|
|
|
use App\Models\Sales\SalesPartner;
|
2026-01-29 06:42:32 +09:00
|
|
|
|
use App\Models\Sales\SalesTenantManagement;
|
2026-01-29 06:47:37 +09:00
|
|
|
|
use App\Models\Sales\TenantProspect;
|
2026-01-28 20:15:38 +09:00
|
|
|
|
use App\Models\Tenants\Tenant;
|
2026-01-28 21:45:11 +09:00
|
|
|
|
use App\Models\User;
|
2026-01-29 18:14:11 +09:00
|
|
|
|
use App\Services\SalesCommissionService;
|
2026-01-28 21:45:11 +09:00
|
|
|
|
use Illuminate\Http\JsonResponse;
|
2026-01-27 18:59:45 +09:00
|
|
|
|
use Illuminate\Http\Request;
|
2026-01-31 21:08:40 +09:00
|
|
|
|
use Illuminate\Support\Str;
|
2026-01-27 18:59:45 +09:00
|
|
|
|
use Illuminate\View\View;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 영업관리 대시보드 컨트롤러
|
|
|
|
|
|
*/
|
|
|
|
|
|
class SalesDashboardController extends Controller
|
|
|
|
|
|
{
|
2026-01-29 18:14:11 +09:00
|
|
|
|
public function __construct(
|
|
|
|
|
|
private SalesCommissionService $commissionService
|
|
|
|
|
|
) {}
|
|
|
|
|
|
|
2026-01-27 18:59:45 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 대시보드 화면
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function index(Request $request): View
|
2026-01-27 19:28:48 +09:00
|
|
|
|
{
|
|
|
|
|
|
$data = $this->getDashboardData($request);
|
|
|
|
|
|
|
2026-01-29 18:14:11 +09:00
|
|
|
|
// 영업파트너 수당 정보 추가
|
|
|
|
|
|
$data = array_merge($data, $this->getCommissionData());
|
|
|
|
|
|
|
2026-01-27 19:28:48 +09:00
|
|
|
|
return view('sales.dashboard.index', $data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* HTMX 부분 새로고침용 데이터 반환
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function refresh(Request $request): View
|
|
|
|
|
|
{
|
|
|
|
|
|
$data = $this->getDashboardData($request);
|
|
|
|
|
|
|
|
|
|
|
|
return view('sales.dashboard.partials.data-container', $data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 대시보드 데이터 조회
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function getDashboardData(Request $request): array
|
2026-01-27 18:59:45 +09:00
|
|
|
|
{
|
|
|
|
|
|
// 기간 설정
|
|
|
|
|
|
$period = $request->input('period', 'month'); // month or custom
|
|
|
|
|
|
$year = $request->input('year', now()->year);
|
|
|
|
|
|
$month = $request->input('month', now()->month);
|
|
|
|
|
|
|
2026-01-27 19:06:36 +09:00
|
|
|
|
// 기간 설정 모드일 경우
|
|
|
|
|
|
if ($period === 'custom') {
|
|
|
|
|
|
$startDate = $request->input('start_date', now()->startOfMonth()->format('Y-m-d'));
|
|
|
|
|
|
$endDate = $request->input('end_date', now()->format('Y-m-d'));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$startDate = now()->startOfMonth()->format('Y-m-d');
|
|
|
|
|
|
$endDate = now()->endOfMonth()->format('Y-m-d');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-30 17:02:41 +09:00
|
|
|
|
$currentUserId = auth()->id();
|
|
|
|
|
|
$childrenIds = auth()->user()->children()->pluck('id')->toArray();
|
|
|
|
|
|
$partnerIds = array_merge([$currentUserId], $childrenIds);
|
|
|
|
|
|
|
|
|
|
|
|
// 현재 사용자의 영업파트너 정보 조회
|
|
|
|
|
|
$partner = SalesPartner::where('user_id', $currentUserId)->first();
|
|
|
|
|
|
$partnerId = $partner?->id;
|
|
|
|
|
|
|
|
|
|
|
|
// 나와 관련된 모든 수당 조회 (영업파트너로서 + 매니저로서)
|
|
|
|
|
|
$myCommissionsAsPartner = $partnerId
|
|
|
|
|
|
? SalesCommission::forPartner($partnerId)->get()
|
|
|
|
|
|
: collect();
|
|
|
|
|
|
$myCommissionsAsManager = SalesCommission::forManager($currentUserId)->get();
|
|
|
|
|
|
|
|
|
|
|
|
// 판매자(영업파트너) 수당 계산
|
|
|
|
|
|
$partnerCommissionTotal = $myCommissionsAsPartner->sum('partner_commission');
|
|
|
|
|
|
$partnerCommissionPaid = $myCommissionsAsPartner->where('status', SalesCommission::STATUS_PAID)->sum('partner_commission');
|
|
|
|
|
|
$partnerCommissionPending = $myCommissionsAsPartner->where('status', SalesCommission::STATUS_PENDING)->sum('partner_commission');
|
|
|
|
|
|
$partnerCommissionApproved = $myCommissionsAsPartner->where('status', SalesCommission::STATUS_APPROVED)->sum('partner_commission');
|
|
|
|
|
|
|
|
|
|
|
|
// 매니저 수당 계산
|
|
|
|
|
|
$managerCommissionTotal = $myCommissionsAsManager->sum('manager_commission');
|
|
|
|
|
|
$managerCommissionPaid = $myCommissionsAsManager->where('status', SalesCommission::STATUS_PAID)->sum('manager_commission');
|
|
|
|
|
|
$managerCommissionPending = $myCommissionsAsManager->where('status', SalesCommission::STATUS_PENDING)->sum('manager_commission');
|
|
|
|
|
|
$managerCommissionApproved = $myCommissionsAsManager->where('status', SalesCommission::STATUS_APPROVED)->sum('manager_commission');
|
|
|
|
|
|
|
|
|
|
|
|
// 총 수당 계산 (중복 제거: 동일 commission에서 partner + manager인 경우)
|
|
|
|
|
|
$allCommissionIds = $myCommissionsAsPartner->pluck('id')->merge($myCommissionsAsManager->pluck('id'))->unique();
|
|
|
|
|
|
$totalContracts = $allCommissionIds->count();
|
|
|
|
|
|
|
|
|
|
|
|
// 통계 데이터 (실제 데이터)
|
|
|
|
|
|
$totalMembershipFee = $myCommissionsAsPartner->sum('payment_amount') + $myCommissionsAsManager->sum('payment_amount');
|
|
|
|
|
|
$totalCommission = $partnerCommissionTotal + $managerCommissionTotal;
|
|
|
|
|
|
$paidCommission = $partnerCommissionPaid + $managerCommissionPaid;
|
|
|
|
|
|
$commissionRate = $totalCommission > 0 ? round(($paidCommission / $totalCommission) * 100, 1) : 0;
|
|
|
|
|
|
|
2026-01-27 18:59:45 +09:00
|
|
|
|
$stats = [
|
2026-01-30 17:02:41 +09:00
|
|
|
|
'total_membership_fee' => $totalMembershipFee, // 총 가입비
|
|
|
|
|
|
'total_commission' => $totalCommission, // 총 수당
|
|
|
|
|
|
'commission_rate' => $commissionRate, // 지급 완료 비율
|
|
|
|
|
|
'total_contracts' => $totalContracts, // 전체 건수
|
|
|
|
|
|
'pending_membership_approval' => $myCommissionsAsPartner->where('status', SalesCommission::STATUS_PENDING)->count()
|
|
|
|
|
|
+ $myCommissionsAsManager->where('status', SalesCommission::STATUS_PENDING)->count(),
|
|
|
|
|
|
'pending_payment_approval' => $myCommissionsAsPartner->where('status', SalesCommission::STATUS_APPROVED)->count()
|
|
|
|
|
|
+ $myCommissionsAsManager->where('status', SalesCommission::STATUS_APPROVED)->count(),
|
2026-01-27 18:59:45 +09:00
|
|
|
|
];
|
|
|
|
|
|
|
2026-01-30 17:02:41 +09:00
|
|
|
|
// 역할별 수당 상세 (실제 데이터)
|
2026-01-27 18:59:45 +09:00
|
|
|
|
$commissionByRole = [
|
|
|
|
|
|
[
|
|
|
|
|
|
'name' => '판매자',
|
|
|
|
|
|
'rate' => 20,
|
2026-01-30 17:02:41 +09:00
|
|
|
|
'amount' => $partnerCommissionTotal,
|
|
|
|
|
|
'paid' => $partnerCommissionPaid,
|
|
|
|
|
|
'pending' => $partnerCommissionPending,
|
|
|
|
|
|
'approved' => $partnerCommissionApproved,
|
2026-01-27 18:59:45 +09:00
|
|
|
|
'color' => 'green',
|
|
|
|
|
|
],
|
|
|
|
|
|
[
|
|
|
|
|
|
'name' => '관리자',
|
|
|
|
|
|
'rate' => 5,
|
2026-01-30 17:02:41 +09:00
|
|
|
|
'amount' => $managerCommissionTotal,
|
|
|
|
|
|
'paid' => $managerCommissionPaid,
|
|
|
|
|
|
'pending' => $managerCommissionPending,
|
|
|
|
|
|
'approved' => $managerCommissionApproved,
|
2026-01-27 18:59:45 +09:00
|
|
|
|
'color' => 'blue',
|
|
|
|
|
|
],
|
|
|
|
|
|
[
|
2026-01-30 17:19:01 +09:00
|
|
|
|
'name' => '협업지원금',
|
|
|
|
|
|
'rate' => null, // 메뉴당 2,000원
|
|
|
|
|
|
'amount' => null, // 가입비 완납 시 계산
|
|
|
|
|
|
'color' => 'purple',
|
2026-01-27 18:59:45 +09:00
|
|
|
|
],
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2026-01-31 21:28:28 +09:00
|
|
|
|
// === 인계(handover) 완료된 가망고객의 수당 계산 ===
|
|
|
|
|
|
// 내가 등록한 가망고객 중 인계 완료된 것들의 계약 금액 조회
|
|
|
|
|
|
$handoverProspectIds = TenantProspect::whereIn('registered_by', $partnerIds)
|
|
|
|
|
|
->pluck('id')
|
|
|
|
|
|
->toArray();
|
|
|
|
|
|
|
|
|
|
|
|
// 인계 완료된 가망고객의 management_id 조회
|
2026-01-31 21:29:37 +09:00
|
|
|
|
$handoverManagements = SalesTenantManagement::whereIn('tenant_prospect_id', $handoverProspectIds)
|
2026-01-31 21:28:28 +09:00
|
|
|
|
->where('hq_status', SalesTenantManagement::HQ_STATUS_HANDOVER)
|
|
|
|
|
|
->get();
|
|
|
|
|
|
|
|
|
|
|
|
// 인계 완료된 계약의 가입비 합계
|
|
|
|
|
|
$handoverManagementIds = $handoverManagements->pluck('id')->toArray();
|
|
|
|
|
|
$handoverTotalRegFee = SalesContractProduct::whereIn('management_id', $handoverManagementIds)
|
|
|
|
|
|
->sum('registration_fee');
|
|
|
|
|
|
|
|
|
|
|
|
// 수당 계산: 가입비 × 50% × 20% = 가입비 × 10%
|
|
|
|
|
|
$handoverPartnerCommission = (int)($handoverTotalRegFee * 0.10);
|
|
|
|
|
|
|
|
|
|
|
|
// 내가 매니저로 지정된 인계 완료 건의 수당 계산
|
|
|
|
|
|
$managedHandoverManagements = SalesTenantManagement::where('manager_user_id', $currentUserId)
|
|
|
|
|
|
->where('hq_status', SalesTenantManagement::HQ_STATUS_HANDOVER)
|
|
|
|
|
|
->get();
|
|
|
|
|
|
$managedHandoverManagementIds = $managedHandoverManagements->pluck('id')->toArray();
|
|
|
|
|
|
$managedHandoverTotalRegFee = SalesContractProduct::whereIn('management_id', $managedHandoverManagementIds)
|
|
|
|
|
|
->sum('registration_fee');
|
|
|
|
|
|
|
|
|
|
|
|
// 매니저 수당: 가입비 × 50% × 5% = 가입비 × 2.5%
|
|
|
|
|
|
$handoverManagerCommission = (int)($managedHandoverTotalRegFee * 0.025);
|
|
|
|
|
|
|
|
|
|
|
|
// 기존 수당에 인계 완료 수당 추가
|
|
|
|
|
|
$partnerCommissionTotal += $handoverPartnerCommission;
|
|
|
|
|
|
$managerCommissionTotal += $handoverManagerCommission;
|
|
|
|
|
|
$totalMembershipFee += $handoverTotalRegFee;
|
|
|
|
|
|
$totalCommission = $partnerCommissionTotal + $managerCommissionTotal;
|
|
|
|
|
|
|
|
|
|
|
|
// 역할별 수당 업데이트
|
|
|
|
|
|
$commissionByRole[0]['amount'] = $partnerCommissionTotal;
|
|
|
|
|
|
$commissionByRole[1]['amount'] = $managerCommissionTotal;
|
|
|
|
|
|
|
2026-01-30 17:02:41 +09:00
|
|
|
|
// 총 가입비 대비 수당 비율
|
|
|
|
|
|
$totalCommissionRatio = $totalMembershipFee > 0 ? round(($totalCommission / $totalMembershipFee) * 100, 1) : 0;
|
2026-01-30 16:32:11 +09:00
|
|
|
|
|
2026-01-30 16:38:16 +09:00
|
|
|
|
// 1) 내가 등록한 가망고객에서 전환된 tenant_id (20% 수당)
|
|
|
|
|
|
$registeredTenantIds = TenantProspect::whereNotNull('tenant_id')
|
2026-01-29 06:47:37 +09:00
|
|
|
|
->where('status', TenantProspect::STATUS_CONVERTED)
|
2026-01-30 16:32:11 +09:00
|
|
|
|
->whereIn('registered_by', $partnerIds)
|
2026-01-29 06:47:37 +09:00
|
|
|
|
->pluck('tenant_id')
|
|
|
|
|
|
->toArray();
|
|
|
|
|
|
|
2026-01-30 16:38:16 +09:00
|
|
|
|
// 2) 내가 매니저로 지정된 tenant_id (5% 수당)
|
|
|
|
|
|
$managedTenantIds = SalesTenantManagement::where('manager_user_id', $currentUserId)
|
2026-01-31 21:28:28 +09:00
|
|
|
|
->whereNotNull('tenant_id')
|
2026-01-30 16:38:16 +09:00
|
|
|
|
->pluck('tenant_id')
|
|
|
|
|
|
->toArray();
|
|
|
|
|
|
|
|
|
|
|
|
// 두 목록 합치기 (중복 제거)
|
|
|
|
|
|
$convertedTenantIds = array_unique(array_merge($registeredTenantIds, $managedTenantIds));
|
|
|
|
|
|
|
2026-01-31 21:28:28 +09:00
|
|
|
|
// 3) 인계 완료된 가망고객 ID 조회
|
2026-01-31 21:29:37 +09:00
|
|
|
|
$handoverCompletedProspectIds = SalesTenantManagement::whereNotNull('tenant_prospect_id')
|
2026-01-31 21:28:28 +09:00
|
|
|
|
->where('hq_status', SalesTenantManagement::HQ_STATUS_HANDOVER)
|
2026-01-31 21:29:37 +09:00
|
|
|
|
->pluck('tenant_prospect_id')
|
2026-01-31 21:28:28 +09:00
|
|
|
|
->toArray();
|
|
|
|
|
|
|
2026-02-02 09:45:18 +09:00
|
|
|
|
// 4) 내가 직접 등록한 가망고객 (진행중 - 인계 완료되지 않은 것)
|
|
|
|
|
|
// 하위 파트너가 등록한 것은 "유치 파트너 현황" 탭에서 표시
|
|
|
|
|
|
$prospects = TenantProspect::where('registered_by', $currentUserId)
|
2026-01-31 19:24:31 +09:00
|
|
|
|
->whereIn('status', [TenantProspect::STATUS_ACTIVE, TenantProspect::STATUS_EXPIRED])
|
2026-01-31 21:28:28 +09:00
|
|
|
|
->whereNotIn('id', $handoverCompletedProspectIds)
|
2026-01-31 19:24:31 +09:00
|
|
|
|
->orderBy('created_at', 'desc')
|
|
|
|
|
|
->get();
|
|
|
|
|
|
|
2026-02-02 09:45:18 +09:00
|
|
|
|
// 5) 내가 직접 등록하고 인계 완료된 가망고객 (히스토리)
|
|
|
|
|
|
$handoverProspects = TenantProspect::where('registered_by', $currentUserId)
|
2026-01-31 21:32:49 +09:00
|
|
|
|
->whereIn('id', $handoverCompletedProspectIds)
|
|
|
|
|
|
->orderBy('created_at', 'desc')
|
|
|
|
|
|
->get();
|
|
|
|
|
|
|
2026-01-31 21:28:28 +09:00
|
|
|
|
// 인계 완료된 가망고객 수
|
|
|
|
|
|
$handoverProspectCount = count($handoverManagementIds);
|
|
|
|
|
|
|
2026-01-30 17:02:41 +09:00
|
|
|
|
// 수익 및 테넌트 관리 통계 (실제 데이터)
|
|
|
|
|
|
$tenantStats = [
|
2026-01-31 21:28:28 +09:00
|
|
|
|
'total_tenants' => count($convertedTenantIds) + $handoverProspectCount, // 관리 테넌트 + 인계완료
|
|
|
|
|
|
'total_prospects' => $prospects->count(), // 진행중 가망고객
|
|
|
|
|
|
'total_membership_revenue' => $totalMembershipFee, // 총 가입비 실적
|
|
|
|
|
|
'total_commission_accumulated' => $totalCommission, // 누적 수당
|
|
|
|
|
|
'confirmed_commission' => $paidCommission, // 확정(지급완료) 수당
|
2026-01-30 17:02:41 +09:00
|
|
|
|
];
|
|
|
|
|
|
|
2026-01-31 21:28:28 +09:00
|
|
|
|
// 통계 업데이트
|
|
|
|
|
|
$stats['total_membership_fee'] = $totalMembershipFee;
|
|
|
|
|
|
$stats['total_commission'] = $totalCommission;
|
|
|
|
|
|
|
2026-01-29 07:51:41 +09:00
|
|
|
|
// 전환된 테넌트만 조회 (최신순, 페이지네이션)
|
2026-01-29 06:47:37 +09:00
|
|
|
|
$tenants = Tenant::whereIn('id', $convertedTenantIds)
|
2026-01-28 20:15:38 +09:00
|
|
|
|
->orderBy('created_at', 'desc')
|
2026-01-29 07:51:41 +09:00
|
|
|
|
->paginate(10)
|
|
|
|
|
|
->withQueryString();
|
2026-01-28 20:15:38 +09:00
|
|
|
|
|
2026-01-29 06:42:32 +09:00
|
|
|
|
// 각 테넌트의 영업 관리 정보 로드
|
|
|
|
|
|
$tenantIds = $tenants->pluck('id')->toArray();
|
|
|
|
|
|
$managements = SalesTenantManagement::whereIn('tenant_id', $tenantIds)
|
|
|
|
|
|
->with('manager')
|
|
|
|
|
|
->get()
|
|
|
|
|
|
->keyBy('tenant_id');
|
|
|
|
|
|
|
2026-01-29 07:57:30 +09:00
|
|
|
|
// 내가 유치한 영업파트너 목록 (드롭다운용)
|
|
|
|
|
|
$allManagers = auth()->user()->children()
|
|
|
|
|
|
->where('is_active', true)
|
|
|
|
|
|
->get(['id', 'name', 'email']);
|
2026-01-29 07:05:45 +09:00
|
|
|
|
|
2026-01-27 19:28:48 +09:00
|
|
|
|
return compact(
|
2026-01-27 18:59:45 +09:00
|
|
|
|
'stats',
|
|
|
|
|
|
'commissionByRole',
|
|
|
|
|
|
'totalCommissionRatio',
|
2026-01-27 19:14:29 +09:00
|
|
|
|
'tenantStats',
|
2026-01-28 20:15:38 +09:00
|
|
|
|
'tenants',
|
2026-01-31 19:24:31 +09:00
|
|
|
|
'prospects',
|
2026-01-31 21:32:49 +09:00
|
|
|
|
'handoverProspects',
|
2026-01-29 06:42:32 +09:00
|
|
|
|
'managements',
|
2026-01-29 07:05:45 +09:00
|
|
|
|
'allManagers',
|
2026-01-27 18:59:45 +09:00
|
|
|
|
'period',
|
|
|
|
|
|
'year',
|
2026-01-27 19:06:36 +09:00
|
|
|
|
'month',
|
|
|
|
|
|
'startDate',
|
|
|
|
|
|
'endDate'
|
2026-01-27 19:28:48 +09:00
|
|
|
|
);
|
2026-01-27 18:59:45 +09:00
|
|
|
|
}
|
2026-01-28 21:45:11 +09:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 매니저 지정 변경
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function assignManager(int $tenantId, Request $request): JsonResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
$request->validate([
|
|
|
|
|
|
'manager_id' => 'required|integer',
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
$tenant = Tenant::findOrFail($tenantId);
|
|
|
|
|
|
$managerId = $request->input('manager_id');
|
|
|
|
|
|
|
2026-01-29 06:42:32 +09:00
|
|
|
|
// 테넌트 영업 관리 정보 조회 또는 생성
|
|
|
|
|
|
$management = SalesTenantManagement::findOrCreateByTenant($tenantId);
|
2026-01-28 21:45:11 +09:00
|
|
|
|
|
|
|
|
|
|
if ($managerId === 0) {
|
|
|
|
|
|
// 본인으로 설정 (현재 로그인 사용자)
|
|
|
|
|
|
$manager = auth()->user();
|
2026-01-29 06:42:32 +09:00
|
|
|
|
$management->update([
|
|
|
|
|
|
'manager_user_id' => $manager->id,
|
|
|
|
|
|
]);
|
2026-01-28 21:45:11 +09:00
|
|
|
|
} else {
|
|
|
|
|
|
// 특정 매니저 지정
|
|
|
|
|
|
$manager = User::find($managerId);
|
|
|
|
|
|
if (!$manager) {
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
|
'success' => false,
|
|
|
|
|
|
'message' => '매니저를 찾을 수 없습니다.',
|
|
|
|
|
|
], 404);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-29 06:42:32 +09:00
|
|
|
|
$management->update([
|
|
|
|
|
|
'manager_user_id' => $manager->id,
|
|
|
|
|
|
]);
|
2026-01-28 21:45:11 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
|
'success' => true,
|
|
|
|
|
|
'manager' => [
|
|
|
|
|
|
'id' => $manager->id,
|
|
|
|
|
|
'name' => $manager->name,
|
|
|
|
|
|
],
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-29 13:02:52 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 테넌트 리스트 부분 새로고침 (HTMX)
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function refreshTenantList(Request $request): View
|
|
|
|
|
|
{
|
2026-01-30 16:38:16 +09:00
|
|
|
|
// 테넌트 목록 (나와 연결된 계약만)
|
2026-01-30 16:32:11 +09:00
|
|
|
|
$currentUserId = auth()->id();
|
|
|
|
|
|
$childrenIds = auth()->user()->children()->pluck('id')->toArray();
|
|
|
|
|
|
$partnerIds = array_merge([$currentUserId], $childrenIds);
|
|
|
|
|
|
|
2026-01-30 16:38:16 +09:00
|
|
|
|
// 1) 내가 등록한 가망고객에서 전환된 tenant_id (20% 수당)
|
|
|
|
|
|
$registeredTenantIds = TenantProspect::whereNotNull('tenant_id')
|
2026-01-29 13:02:52 +09:00
|
|
|
|
->where('status', TenantProspect::STATUS_CONVERTED)
|
2026-01-30 16:32:11 +09:00
|
|
|
|
->whereIn('registered_by', $partnerIds)
|
2026-01-29 13:02:52 +09:00
|
|
|
|
->pluck('tenant_id')
|
|
|
|
|
|
->toArray();
|
|
|
|
|
|
|
2026-01-30 16:38:16 +09:00
|
|
|
|
// 2) 내가 매니저로 지정된 tenant_id (5% 수당)
|
|
|
|
|
|
$managedTenantIds = SalesTenantManagement::where('manager_user_id', $currentUserId)
|
|
|
|
|
|
->pluck('tenant_id')
|
|
|
|
|
|
->toArray();
|
|
|
|
|
|
|
|
|
|
|
|
// 두 목록 합치기 (중복 제거)
|
|
|
|
|
|
$convertedTenantIds = array_unique(array_merge($registeredTenantIds, $managedTenantIds));
|
|
|
|
|
|
|
2026-02-02 09:45:18 +09:00
|
|
|
|
// 3) 내가 직접 등록한 가망고객 (아직 전환되지 않은 것 - active 상태)
|
|
|
|
|
|
$prospects = TenantProspect::where('registered_by', $currentUserId)
|
2026-01-31 20:24:26 +09:00
|
|
|
|
->whereIn('status', [TenantProspect::STATUS_ACTIVE, TenantProspect::STATUS_EXPIRED])
|
|
|
|
|
|
->orderBy('created_at', 'desc')
|
|
|
|
|
|
->get();
|
|
|
|
|
|
|
2026-01-29 13:02:52 +09:00
|
|
|
|
// 전환된 테넌트만 조회 (최신순, 페이지네이션)
|
|
|
|
|
|
$tenants = Tenant::whereIn('id', $convertedTenantIds)
|
|
|
|
|
|
->orderBy('created_at', 'desc')
|
|
|
|
|
|
->paginate(10)
|
|
|
|
|
|
->withQueryString();
|
|
|
|
|
|
|
|
|
|
|
|
// 각 테넌트의 영업 관리 정보 로드
|
|
|
|
|
|
$tenantIds = $tenants->pluck('id')->toArray();
|
|
|
|
|
|
$managements = SalesTenantManagement::whereIn('tenant_id', $tenantIds)
|
|
|
|
|
|
->with('manager')
|
|
|
|
|
|
->get()
|
|
|
|
|
|
->keyBy('tenant_id');
|
|
|
|
|
|
|
|
|
|
|
|
// 내가 유치한 영업파트너 목록 (드롭다운용)
|
|
|
|
|
|
$allManagers = auth()->user()->children()
|
|
|
|
|
|
->where('is_active', true)
|
|
|
|
|
|
->get(['id', 'name', 'email']);
|
|
|
|
|
|
|
|
|
|
|
|
return view('sales.dashboard.partials.tenant-list', compact(
|
|
|
|
|
|
'tenants',
|
2026-01-31 20:24:26 +09:00
|
|
|
|
'prospects',
|
2026-01-29 13:02:52 +09:00
|
|
|
|
'managements',
|
|
|
|
|
|
'allManagers'
|
|
|
|
|
|
));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-28 21:45:11 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 매니저 목록 조회 (드롭다운용)
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getManagers(Request $request): JsonResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
// HQ 테넌트의 사용자 중 매니저 역할이 있는 사용자 조회
|
|
|
|
|
|
$managers = User::whereHas('tenants', function ($query) {
|
|
|
|
|
|
$query->where('tenant_type', 'HQ');
|
|
|
|
|
|
})->get(['id', 'name', 'email']);
|
|
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
|
'success' => true,
|
|
|
|
|
|
'managers' => $managers,
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
2026-01-29 18:14:11 +09:00
|
|
|
|
|
2026-01-31 16:15:50 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 유치 파트너 활동 현황 (HTMX 탭 로드)
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function partnerActivity(Request $request): View
|
|
|
|
|
|
{
|
|
|
|
|
|
$data = $this->getPartnerActivityData();
|
|
|
|
|
|
|
|
|
|
|
|
return view('sales.dashboard.partials.partner-activity', $data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 유치 파트너 활동 데이터 조회
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function getPartnerActivityData(): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$currentUser = auth()->user();
|
|
|
|
|
|
$currentUserId = $currentUser->id;
|
|
|
|
|
|
|
|
|
|
|
|
// 직접 유치한 하위 파트너 목록 (parent_id가 현재 사용자인 사용자들)
|
|
|
|
|
|
$recruitedPartners = User::where('parent_id', $currentUserId)
|
|
|
|
|
|
->where('is_active', true)
|
|
|
|
|
|
->with(['userRoles.role'])
|
|
|
|
|
|
->get();
|
|
|
|
|
|
|
|
|
|
|
|
$partnerIds = $recruitedPartners->pluck('id')->toArray();
|
|
|
|
|
|
|
|
|
|
|
|
// 요약 통계 계산
|
|
|
|
|
|
$summaryStats = $this->calculatePartnerSummaryStats($partnerIds, $currentUserId);
|
|
|
|
|
|
|
|
|
|
|
|
// 파트너별 상세 활동 데이터
|
|
|
|
|
|
$partnerActivities = $this->getPartnerActivitiesDetail($recruitedPartners, $currentUserId);
|
|
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
|
'summaryStats' => $summaryStats,
|
|
|
|
|
|
'partnerActivities' => $partnerActivities,
|
|
|
|
|
|
'recruitedPartners' => $recruitedPartners,
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 유치 파트너 요약 통계 계산
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function calculatePartnerSummaryStats(array $partnerIds, int $currentUserId): array
|
|
|
|
|
|
{
|
|
|
|
|
|
// 유치 파트너 수
|
|
|
|
|
|
$partnerCount = count($partnerIds);
|
|
|
|
|
|
|
|
|
|
|
|
// 하위 파트너들이 등록한 총 영업권(명함) 수
|
|
|
|
|
|
$totalProspects = TenantProspect::whereIn('registered_by', $partnerIds)->count();
|
|
|
|
|
|
|
|
|
|
|
|
// 하위 파트너들의 계약 성사 건수
|
|
|
|
|
|
$totalConversions = TenantProspect::whereIn('registered_by', $partnerIds)
|
|
|
|
|
|
->where('status', TenantProspect::STATUS_CONVERTED)
|
|
|
|
|
|
->count();
|
|
|
|
|
|
|
2026-02-02 09:49:28 +09:00
|
|
|
|
// 확정 수당 (SalesCommission에서)
|
|
|
|
|
|
$confirmedCommission = SalesCommission::where('manager_user_id', $currentUserId)
|
2026-01-31 16:15:50 +09:00
|
|
|
|
->whereHas('partner', function ($query) use ($partnerIds) {
|
|
|
|
|
|
$query->whereIn('user_id', $partnerIds);
|
|
|
|
|
|
})
|
|
|
|
|
|
->sum('manager_commission');
|
|
|
|
|
|
|
2026-02-02 09:49:28 +09:00
|
|
|
|
// 예상 수당: 하위 파트너들이 등록한 가망고객의 가입비 × 5%
|
|
|
|
|
|
$prospectIds = TenantProspect::whereIn('registered_by', $partnerIds)->pluck('id')->toArray();
|
|
|
|
|
|
$managementIds = SalesTenantManagement::whereIn('tenant_prospect_id', $prospectIds)->pluck('id')->toArray();
|
|
|
|
|
|
$totalRegistrationFee = SalesContractProduct::whereIn('management_id', $managementIds)->sum('registration_fee');
|
|
|
|
|
|
$expectedFromFee = (int)($totalRegistrationFee * 0.05);
|
|
|
|
|
|
|
|
|
|
|
|
// 최종 예상 수당 (확정 + 예상 중 큰 값)
|
|
|
|
|
|
$expectedCommission = max($confirmedCommission, $expectedFromFee);
|
|
|
|
|
|
|
2026-01-31 16:15:50 +09:00
|
|
|
|
return [
|
|
|
|
|
|
'partner_count' => $partnerCount,
|
|
|
|
|
|
'total_prospects' => $totalProspects,
|
|
|
|
|
|
'total_conversions' => $totalConversions,
|
|
|
|
|
|
'expected_commission' => $expectedCommission,
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 파트너별 상세 활동 데이터
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function getPartnerActivitiesDetail($recruitedPartners, int $currentUserId): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$activities = [];
|
|
|
|
|
|
|
|
|
|
|
|
foreach ($recruitedPartners as $partner) {
|
|
|
|
|
|
// 파트너의 영업파트너 정보
|
|
|
|
|
|
$salesPartner = SalesPartner::where('user_id', $partner->id)->first();
|
|
|
|
|
|
|
|
|
|
|
|
// 파트너가 등록한 영업권 수
|
|
|
|
|
|
$prospectCount = TenantProspect::where('registered_by', $partner->id)->count();
|
|
|
|
|
|
|
|
|
|
|
|
// 진행 중인 영업권 (active 상태)
|
|
|
|
|
|
$activeProspects = TenantProspect::where('registered_by', $partner->id)
|
|
|
|
|
|
->where('status', TenantProspect::STATUS_ACTIVE)
|
|
|
|
|
|
->count();
|
|
|
|
|
|
|
|
|
|
|
|
// 계약 성사 건수
|
|
|
|
|
|
$conversions = TenantProspect::where('registered_by', $partner->id)
|
|
|
|
|
|
->where('status', TenantProspect::STATUS_CONVERTED)
|
|
|
|
|
|
->count();
|
|
|
|
|
|
|
2026-02-02 09:49:28 +09:00
|
|
|
|
// 이 파트너로 인한 나의 매니저 수당 (확정 수당)
|
|
|
|
|
|
$confirmedCommission = 0;
|
2026-01-31 16:15:50 +09:00
|
|
|
|
if ($salesPartner) {
|
2026-02-02 09:49:28 +09:00
|
|
|
|
$confirmedCommission = SalesCommission::where('manager_user_id', $currentUserId)
|
2026-01-31 16:15:50 +09:00
|
|
|
|
->where('partner_id', $salesPartner->id)
|
|
|
|
|
|
->sum('manager_commission');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 09:49:28 +09:00
|
|
|
|
// 예상 수당 계산: 파트너가 등록한 가망고객의 가입비 × 5%
|
|
|
|
|
|
$prospectIds = TenantProspect::where('registered_by', $partner->id)->pluck('id')->toArray();
|
|
|
|
|
|
$managementIds = SalesTenantManagement::whereIn('tenant_prospect_id', $prospectIds)->pluck('id')->toArray();
|
|
|
|
|
|
$totalRegistrationFee = SalesContractProduct::whereIn('management_id', $managementIds)->sum('registration_fee');
|
|
|
|
|
|
$expectedCommission = (int)($totalRegistrationFee * 0.05); // 5% 매니저 수당
|
|
|
|
|
|
|
|
|
|
|
|
// 최종 매니저 수당 (확정 + 예상 중 큰 값, 또는 합산)
|
|
|
|
|
|
$managerCommission = max($confirmedCommission, $expectedCommission);
|
|
|
|
|
|
$hasRegistrationFee = $totalRegistrationFee > 0;
|
|
|
|
|
|
|
2026-01-31 16:15:50 +09:00
|
|
|
|
// 최근 활동 내역 (최근 전환된 테넌트 5개)
|
|
|
|
|
|
$recentTenants = TenantProspect::where('registered_by', $partner->id)
|
|
|
|
|
|
->where('status', TenantProspect::STATUS_CONVERTED)
|
|
|
|
|
|
->with(['tenant'])
|
|
|
|
|
|
->orderBy('converted_at', 'desc')
|
|
|
|
|
|
->limit(5)
|
|
|
|
|
|
->get();
|
|
|
|
|
|
|
2026-01-31 21:41:42 +09:00
|
|
|
|
// 파트너의 모든 가망고객 (진행률 조회용)
|
|
|
|
|
|
$allProspects = TenantProspect::where('registered_by', $partner->id)
|
|
|
|
|
|
->whereIn('status', [TenantProspect::STATUS_ACTIVE, TenantProspect::STATUS_EXPIRED])
|
|
|
|
|
|
->orderBy('created_at', 'desc')
|
|
|
|
|
|
->get();
|
|
|
|
|
|
|
2026-01-31 16:15:50 +09:00
|
|
|
|
// 활동 상태 판단
|
|
|
|
|
|
$lastActivity = TenantProspect::where('registered_by', $partner->id)
|
|
|
|
|
|
->orderBy('updated_at', 'desc')
|
|
|
|
|
|
->first();
|
|
|
|
|
|
$status = 'inactive';
|
|
|
|
|
|
if ($lastActivity) {
|
|
|
|
|
|
$daysSinceActivity = now()->diffInDays($lastActivity->updated_at);
|
|
|
|
|
|
if ($daysSinceActivity <= 7) {
|
|
|
|
|
|
$status = 'active';
|
|
|
|
|
|
} elseif ($daysSinceActivity <= 30) {
|
|
|
|
|
|
$status = 'moderate';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 역할 정보
|
|
|
|
|
|
$roles = $partner->userRoles->pluck('role.name')->filter()->toArray();
|
|
|
|
|
|
$roleLabel = !empty($roles) ? implode(', ', $roles) : '영업';
|
|
|
|
|
|
|
|
|
|
|
|
$activities[] = [
|
|
|
|
|
|
'partner' => $partner,
|
|
|
|
|
|
'role_label' => $roleLabel,
|
|
|
|
|
|
'prospect_count' => $prospectCount,
|
|
|
|
|
|
'active_prospects' => $activeProspects,
|
|
|
|
|
|
'conversions' => $conversions,
|
|
|
|
|
|
'manager_commission' => $managerCommission,
|
2026-02-02 09:49:28 +09:00
|
|
|
|
'has_registration_fee' => $hasRegistrationFee,
|
2026-01-31 16:15:50 +09:00
|
|
|
|
'status' => $status,
|
|
|
|
|
|
'recent_tenants' => $recentTenants,
|
2026-01-31 21:41:42 +09:00
|
|
|
|
'all_prospects' => $allProspects,
|
2026-01-31 16:15:50 +09:00
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $activities;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-29 18:14:11 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 영업파트너 수당 정보 조회
|
|
|
|
|
|
*/
|
|
|
|
|
|
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');
|
|
|
|
|
|
}
|
2026-01-31 21:08:40 +09:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 영업파트너 가이드북 도움말 모달
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function helpGuide(): View
|
|
|
|
|
|
{
|
2026-01-31 21:22:14 +09:00
|
|
|
|
// 가이드북 마크다운 파일 읽기 (resources/markdown 폴더)
|
|
|
|
|
|
$guidePath = resource_path('markdown/영업파트너가이드북.md');
|
2026-01-31 21:08:40 +09:00
|
|
|
|
|
|
|
|
|
|
if (file_exists($guidePath)) {
|
|
|
|
|
|
$markdown = file_get_contents($guidePath);
|
|
|
|
|
|
$htmlContent = Str::markdown($markdown);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$htmlContent = '<p class="text-gray-500">가이드북을 찾을 수 없습니다.</p>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return view('sales.dashboard.partials.help-modal', compact('htmlContent'));
|
|
|
|
|
|
}
|
2026-01-27 18:59:45 +09:00
|
|
|
|
}
|