408 lines
15 KiB
PHP
408 lines
15 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Sales;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Sales\SalesCommission;
|
|
use App\Models\Sales\SalesScenarioChecklist;
|
|
use App\Models\Sales\SalesTenantManagement;
|
|
use App\Models\Sales\TenantProspect;
|
|
use App\Models\User;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\Response;
|
|
use Illuminate\View\View;
|
|
|
|
/**
|
|
* 관리자용 전체 영업파트너 고객 관리 컨트롤러
|
|
* 관리자/슈퍼관리자만 접근 가능
|
|
*/
|
|
class AdminProspectController extends Controller
|
|
{
|
|
/**
|
|
* 관리자 권한 체크
|
|
*/
|
|
private function checkAdminAccess(): void
|
|
{
|
|
if (!auth()->user()->isAdmin() && !auth()->user()->isSuperAdmin()) {
|
|
abort(403, '관리자만 접근할 수 있습니다.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 전체 고객 목록 페이지
|
|
*/
|
|
public function index(Request $request): View|Response
|
|
{
|
|
$this->checkAdminAccess();
|
|
if ($request->header('HX-Request')) {
|
|
return response('', 200)->header('HX-Redirect', route('sales.admin-prospects.index'));
|
|
}
|
|
|
|
$data = $this->getIndexData($request);
|
|
|
|
return view('sales.admin-prospects.index', $data);
|
|
}
|
|
|
|
/**
|
|
* 고객 상세 모달
|
|
*/
|
|
public function modalShow(int $id): View
|
|
{
|
|
$this->checkAdminAccess();
|
|
|
|
$prospect = TenantProspect::with(['registeredBy', 'tenant'])->findOrFail($id);
|
|
|
|
// 진행률
|
|
$progress = SalesScenarioChecklist::getProspectProgress($prospect->id);
|
|
$prospect->sales_progress = $progress['sales']['percentage'];
|
|
$prospect->manager_progress = $progress['manager']['percentage'];
|
|
|
|
// management 정보
|
|
$management = SalesTenantManagement::findOrCreateByProspect($prospect->id);
|
|
|
|
return view('sales.admin-prospects.partials.show-modal', compact('prospect', 'management', 'progress'));
|
|
}
|
|
|
|
/**
|
|
* 콘텐츠 새로고침 (HTMX)
|
|
*/
|
|
public function refresh(Request $request): View
|
|
{
|
|
$this->checkAdminAccess();
|
|
|
|
$data = $this->getIndexData($request);
|
|
|
|
return view('sales.admin-prospects.partials.content', $data);
|
|
}
|
|
|
|
/**
|
|
* index 데이터 조회 (공통)
|
|
*/
|
|
private function getIndexData(Request $request): array
|
|
{
|
|
// 영업 역할을 가진 사용자 목록 (영업파트너)
|
|
$salesPartners = User::whereHas('userRoles', function ($q) {
|
|
$q->whereHas('role', function ($rq) {
|
|
$rq->whereIn('name', ['sales', 'manager']);
|
|
});
|
|
})->orderBy('name')->get();
|
|
|
|
// 필터
|
|
$filters = [
|
|
'search' => $request->get('search'),
|
|
'status' => $request->get('status'),
|
|
'registered_by' => $request->get('registered_by'),
|
|
];
|
|
|
|
// 쿼리 빌드
|
|
$query = TenantProspect::with(['registeredBy', 'tenant']);
|
|
|
|
// 검색
|
|
if (!empty($filters['search'])) {
|
|
$search = $filters['search'];
|
|
$query->where(function ($q) use ($search) {
|
|
$q->where('company_name', 'like', "%{$search}%")
|
|
->orWhere('business_number', 'like', "%{$search}%")
|
|
->orWhere('ceo_name', 'like', "%{$search}%")
|
|
->orWhere('contact_phone', 'like', "%{$search}%");
|
|
});
|
|
}
|
|
|
|
// 상태 필터 (progress_complete는 계산값 기반이므로 별도 처리)
|
|
$isProgressCompleteFilter = ($filters['status'] === 'progress_complete');
|
|
if (!empty($filters['status']) && !$isProgressCompleteFilter) {
|
|
$query->where('status', $filters['status']);
|
|
}
|
|
|
|
// 영업파트너 필터
|
|
if (!empty($filters['registered_by'])) {
|
|
$query->where('registered_by', $filters['registered_by']);
|
|
}
|
|
|
|
// progress_complete 필터: 전체 조회 후 PHP에서 필터링
|
|
if ($isProgressCompleteFilter) {
|
|
$allProspects = $query->orderByDesc('created_at')->get();
|
|
|
|
// 진행률 계산 및 부가정보 세팅
|
|
foreach ($allProspects as $prospect) {
|
|
$progress = SalesScenarioChecklist::getProspectProgress($prospect->id);
|
|
$prospect->sales_progress = $progress['sales']['percentage'];
|
|
$prospect->manager_progress = $progress['manager']['percentage'];
|
|
|
|
if ($progress['sales']['percentage'] === 100 && $progress['manager']['percentage'] === 100) {
|
|
SalesScenarioChecklist::checkAndConvertProspectStatus($prospect->id);
|
|
$prospect->refresh();
|
|
}
|
|
|
|
$management = SalesTenantManagement::where('tenant_prospect_id', $prospect->id)->first();
|
|
$prospect->hq_status = $management?->hq_status ?? 'pending';
|
|
$prospect->hq_status_label = $management?->hq_status_label ?? '대기';
|
|
$prospect->manager_user = $management?->manager;
|
|
|
|
if ($management) {
|
|
$commission = SalesCommission::where('management_id', $management->id)->first();
|
|
$prospect->commission = $commission;
|
|
} else {
|
|
$prospect->commission = null;
|
|
}
|
|
}
|
|
|
|
// 두 시나리오 모두 100%인 것만 필터링
|
|
$filtered = $allProspects->filter(function ($prospect) {
|
|
return $prospect->sales_progress === 100 && $prospect->manager_progress === 100;
|
|
});
|
|
|
|
// 수동 페이지네이션
|
|
$page = request()->get('page', 1);
|
|
$perPage = 20;
|
|
$prospects = new \Illuminate\Pagination\LengthAwarePaginator(
|
|
$filtered->forPage($page, $perPage)->values(),
|
|
$filtered->count(),
|
|
$perPage,
|
|
$page,
|
|
['path' => request()->url(), 'query' => request()->query()]
|
|
);
|
|
} else {
|
|
$prospects = $query->orderByDesc('created_at')->paginate(20);
|
|
|
|
// 각 가망고객의 진행률 계산 및 상태 자동 전환
|
|
foreach ($prospects as $prospect) {
|
|
$progress = SalesScenarioChecklist::getProspectProgress($prospect->id);
|
|
$prospect->sales_progress = $progress['sales']['percentage'];
|
|
$prospect->manager_progress = $progress['manager']['percentage'];
|
|
|
|
// 진행률 100% 시 상태 자동 전환 체크
|
|
if ($progress['sales']['percentage'] === 100 && $progress['manager']['percentage'] === 100) {
|
|
SalesScenarioChecklist::checkAndConvertProspectStatus($prospect->id);
|
|
$prospect->refresh();
|
|
}
|
|
|
|
// management 정보
|
|
$management = SalesTenantManagement::where('tenant_prospect_id', $prospect->id)->first();
|
|
$prospect->hq_status = $management?->hq_status ?? 'pending';
|
|
$prospect->hq_status_label = $management?->hq_status_label ?? '대기';
|
|
$prospect->manager_user = $management?->manager;
|
|
|
|
// 수당 정보 (management가 있는 경우)
|
|
if ($management) {
|
|
$commission = SalesCommission::where('management_id', $management->id)->first();
|
|
$prospect->commission = $commission;
|
|
} else {
|
|
$prospect->commission = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 진행완료 건수 계산 (전체 prospect 중 두 시나리오 모두 100%인 건수)
|
|
$progressCompleteCount = 0;
|
|
$allForStats = TenantProspect::all();
|
|
foreach ($allForStats as $p) {
|
|
$prog = SalesScenarioChecklist::getProspectProgress($p->id);
|
|
if ($prog['sales']['percentage'] === 100 && $prog['manager']['percentage'] === 100) {
|
|
$progressCompleteCount++;
|
|
}
|
|
}
|
|
|
|
// 전체 통계
|
|
$stats = [
|
|
'total' => TenantProspect::count(),
|
|
'active' => TenantProspect::where('status', TenantProspect::STATUS_ACTIVE)->count(),
|
|
'expired' => TenantProspect::where('status', TenantProspect::STATUS_EXPIRED)->count(),
|
|
'converted' => TenantProspect::where('status', TenantProspect::STATUS_CONVERTED)->count(),
|
|
'progress_complete' => $progressCompleteCount,
|
|
];
|
|
|
|
// 영업파트너별 통계
|
|
$partnerStats = TenantProspect::selectRaw('registered_by, COUNT(*) as total')
|
|
->groupBy('registered_by')
|
|
->with('registeredBy')
|
|
->get()
|
|
->map(function ($item) {
|
|
return [
|
|
'user' => $item->registeredBy,
|
|
'total' => $item->total,
|
|
];
|
|
});
|
|
|
|
$isSuperAdmin = auth()->user()->isSuperAdmin();
|
|
|
|
return compact('prospects', 'stats', 'salesPartners', 'partnerStats', 'filters', 'isSuperAdmin');
|
|
}
|
|
|
|
/**
|
|
* 개발 진행 상태 변경
|
|
*/
|
|
public function updateHqStatus(int $id, Request $request)
|
|
{
|
|
$this->checkAdminAccess();
|
|
|
|
$request->validate([
|
|
'hq_status' => 'required|in:' . implode(',', array_keys(SalesTenantManagement::$hqStatusLabels)),
|
|
]);
|
|
|
|
$prospect = TenantProspect::findOrFail($id);
|
|
$management = SalesTenantManagement::findOrCreateByProspect($prospect->id);
|
|
|
|
$management->update([
|
|
'hq_status' => $request->input('hq_status'),
|
|
]);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'hq_status' => $management->hq_status,
|
|
'hq_status_label' => $management->hq_status_label,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 수당 날짜 기록/수정
|
|
*/
|
|
public function updateCommissionDate(int $id, Request $request)
|
|
{
|
|
$this->checkAdminAccess();
|
|
|
|
$request->validate([
|
|
'field' => 'required|in:first_payment_at,first_partner_paid_at,second_payment_at,second_partner_paid_at,first_subscription_at,manager_paid_at',
|
|
'date' => 'nullable|date',
|
|
]);
|
|
|
|
$prospect = TenantProspect::findOrFail($id);
|
|
$management = SalesTenantManagement::findOrCreateByProspect($prospect->id);
|
|
|
|
// Commission 레코드 조회 또는 생성
|
|
$commission = SalesCommission::firstOrCreate(
|
|
['management_id' => $management->id],
|
|
[
|
|
'tenant_id' => $prospect->tenant_id ?? 1,
|
|
'payment_type' => 'deposit',
|
|
'payment_amount' => 0,
|
|
'payment_date' => now(),
|
|
'base_amount' => 0,
|
|
'partner_rate' => 0,
|
|
'manager_rate' => 0,
|
|
'partner_commission' => 0,
|
|
'manager_commission' => 0,
|
|
'scheduled_payment_date' => now()->addMonth()->day(10),
|
|
'status' => SalesCommission::STATUS_PENDING,
|
|
'partner_id' => $management->sales_partner_id ?? 0,
|
|
'manager_user_id' => $management->manager_user_id,
|
|
]
|
|
);
|
|
|
|
$field = $request->input('field');
|
|
$date = $request->input('date') ?: now()->format('Y-m-d');
|
|
|
|
// 수당지급일 필드는 개발상태가 '인계'일 때만 저장 가능
|
|
$paidFields = ['first_partner_paid_at', 'second_partner_paid_at', 'manager_paid_at'];
|
|
if (in_array($field, $paidFields) && $management->hq_status !== 'handover') {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => '개발상태가 인계일 때만 수당이 지급됩니다.',
|
|
], 422);
|
|
}
|
|
|
|
$updateData = [$field => $date];
|
|
|
|
// 납입일 입력 시 수당지급일 자동 계산 (익월 10일) - 인계 상태일 때만
|
|
$autoFields = [
|
|
'first_payment_at' => 'first_partner_paid_at',
|
|
'second_payment_at' => 'second_partner_paid_at',
|
|
];
|
|
|
|
$autoField = null;
|
|
$autoDate = null;
|
|
if (isset($autoFields[$field]) && $management->hq_status === 'handover') {
|
|
$autoField = $autoFields[$field];
|
|
$autoDate = \Carbon\Carbon::parse($date)->addMonth()->day(10)->format('Y-m-d');
|
|
$updateData[$autoField] = $autoDate;
|
|
}
|
|
|
|
$commission->update($updateData);
|
|
|
|
$response = [
|
|
'success' => true,
|
|
'field' => $field,
|
|
'date' => $commission->$field?->format('Y-m-d'),
|
|
'date_display' => $commission->$field?->format('m/d'),
|
|
];
|
|
|
|
if ($autoField) {
|
|
$response['auto_field'] = $autoField;
|
|
$response['auto_date'] = $autoDate;
|
|
}
|
|
|
|
return response()->json($response);
|
|
}
|
|
|
|
/**
|
|
* 가망고객 삭제 (슈퍼관리자 전용)
|
|
*/
|
|
public function destroy(int $id)
|
|
{
|
|
$prospect = TenantProspect::findOrFail($id);
|
|
|
|
// 연관 데이터 삭제
|
|
$management = SalesTenantManagement::where('tenant_prospect_id', $prospect->id)->first();
|
|
if ($management) {
|
|
SalesCommission::where('management_id', $management->id)->delete();
|
|
$management->delete();
|
|
}
|
|
SalesScenarioChecklist::where('tenant_prospect_id', $prospect->id)->delete();
|
|
|
|
$prospect->delete();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => "'{$prospect->company_name}' 가망고객이 삭제되었습니다.",
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 수당 날짜 삭제 (초기화)
|
|
*/
|
|
public function clearCommissionDate(int $id, Request $request)
|
|
{
|
|
$this->checkAdminAccess();
|
|
|
|
$request->validate([
|
|
'field' => 'required|in:first_payment_at,first_partner_paid_at,second_payment_at,second_partner_paid_at,first_subscription_at,manager_paid_at',
|
|
]);
|
|
|
|
$prospect = TenantProspect::findOrFail($id);
|
|
$management = SalesTenantManagement::where('tenant_prospect_id', $prospect->id)->first();
|
|
|
|
if (!$management) {
|
|
return response()->json(['success' => false, 'message' => '관리 정보가 없습니다.']);
|
|
}
|
|
|
|
$commission = SalesCommission::where('management_id', $management->id)->first();
|
|
|
|
if (!$commission) {
|
|
return response()->json(['success' => false, 'message' => '수당 정보가 없습니다.']);
|
|
}
|
|
|
|
$field = $request->input('field');
|
|
$updateData = [$field => null];
|
|
|
|
// 납입일 삭제 시 수당지급일도 함께 초기화
|
|
$autoFields = [
|
|
'first_payment_at' => 'first_partner_paid_at',
|
|
'second_payment_at' => 'second_partner_paid_at',
|
|
];
|
|
|
|
$autoField = null;
|
|
if (isset($autoFields[$field])) {
|
|
$autoField = $autoFields[$field];
|
|
$updateData[$autoField] = null;
|
|
}
|
|
|
|
$commission->update($updateData);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'field' => $field,
|
|
'auto_field' => $autoField,
|
|
]);
|
|
}
|
|
}
|