2026-01-26 11:09:42 +09:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers\Sales;
|
|
|
|
|
|
|
|
|
|
use App\Http\Controllers\Controller;
|
2026-01-27 20:06:51 +09:00
|
|
|
use App\Models\Sales\SalesManagerDocument;
|
|
|
|
|
use App\Models\User;
|
|
|
|
|
use App\Services\Sales\SalesManagerService;
|
2026-01-26 11:09:42 +09:00
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
use Illuminate\Http\Response;
|
|
|
|
|
use Illuminate\View\View;
|
|
|
|
|
|
|
|
|
|
/**
|
2026-01-27 21:20:33 +09:00
|
|
|
* 영업파트너 관리 컨트롤러
|
2026-01-26 11:09:42 +09:00
|
|
|
*/
|
|
|
|
|
class SalesManagerController extends Controller
|
|
|
|
|
{
|
2026-01-27 20:06:51 +09:00
|
|
|
public function __construct(
|
|
|
|
|
private SalesManagerService $service
|
|
|
|
|
) {}
|
|
|
|
|
|
2026-01-26 11:09:42 +09:00
|
|
|
/**
|
|
|
|
|
* 목록 페이지
|
|
|
|
|
*/
|
|
|
|
|
public function index(Request $request): View|Response
|
|
|
|
|
{
|
|
|
|
|
if ($request->header('HX-Request')) {
|
|
|
|
|
return response('', 200)->header('HX-Redirect', route('sales.managers.index'));
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-27 20:06:51 +09:00
|
|
|
$filters = [
|
|
|
|
|
'search' => $request->get('search'),
|
|
|
|
|
'role' => $request->get('role'),
|
|
|
|
|
'approval_status' => $request->get('approval_status'),
|
2026-01-26 11:09:42 +09:00
|
|
|
];
|
|
|
|
|
|
2026-01-27 21:20:33 +09:00
|
|
|
$partners = $this->service->getSalesPartners($filters)->paginate(20);
|
2026-01-27 20:06:51 +09:00
|
|
|
$stats = $this->service->getStats();
|
|
|
|
|
|
2026-01-27 21:20:33 +09:00
|
|
|
return view('sales.managers.index', compact('partners', 'stats'));
|
2026-01-26 11:09:42 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 등록 폼
|
|
|
|
|
*/
|
|
|
|
|
public function create(): View
|
|
|
|
|
{
|
2026-01-27 21:20:33 +09:00
|
|
|
// 영업 역할 목록
|
|
|
|
|
$roles = $this->service->getSalesRoles();
|
2026-01-27 20:06:51 +09:00
|
|
|
|
|
|
|
|
// 문서 타입 목록
|
|
|
|
|
$documentTypes = SalesManagerDocument::DOCUMENT_TYPES;
|
|
|
|
|
|
2026-01-27 21:28:59 +09:00
|
|
|
return view('sales.managers.create', compact('roles', 'documentTypes'));
|
2026-01-26 11:09:42 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 등록 처리
|
|
|
|
|
*/
|
|
|
|
|
public function store(Request $request)
|
|
|
|
|
{
|
|
|
|
|
$validated = $request->validate([
|
2026-01-27 20:06:51 +09:00
|
|
|
'user_id' => 'nullable|string|max:50|unique:users,user_id',
|
2026-01-26 11:09:42 +09:00
|
|
|
'name' => 'required|string|max:100',
|
2026-01-27 20:06:51 +09:00
|
|
|
'email' => 'required|email|max:255|unique:users,email',
|
2026-01-26 11:09:42 +09:00
|
|
|
'phone' => 'nullable|string|max:20',
|
2026-01-27 20:06:51 +09:00
|
|
|
'password' => 'required|string|min:4|confirmed',
|
|
|
|
|
'role_ids' => 'required|array|min:1',
|
|
|
|
|
'role_ids.*' => 'exists:roles,id',
|
|
|
|
|
'documents' => 'nullable|array',
|
2026-01-27 21:20:33 +09:00
|
|
|
'documents.*.file' => 'nullable|file|max:10240',
|
2026-01-27 20:06:51 +09:00
|
|
|
'documents.*.document_type' => 'nullable|string',
|
|
|
|
|
'documents.*.description' => 'nullable|string|max:500',
|
2026-01-26 11:09:42 +09:00
|
|
|
]);
|
|
|
|
|
|
2026-01-29 08:09:28 +09:00
|
|
|
// 등록자를 추천인(parent)으로 자동 설정
|
|
|
|
|
// 본사 관리자가 등록해도 해당 관리자가 추천인이 됨
|
|
|
|
|
$validated['parent_id'] = auth()->id();
|
2026-01-27 21:28:59 +09:00
|
|
|
|
2026-01-27 20:06:51 +09:00
|
|
|
// 문서 배열 구성
|
|
|
|
|
$documents = [];
|
|
|
|
|
if ($request->hasFile('documents')) {
|
|
|
|
|
foreach ($request->file('documents') as $index => $file) {
|
|
|
|
|
$documents[] = [
|
|
|
|
|
'file' => $file,
|
|
|
|
|
'document_type' => $request->input("documents.{$index}.document_type", 'other'),
|
|
|
|
|
'description' => $request->input("documents.{$index}.description"),
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-27 21:20:33 +09:00
|
|
|
$this->service->createSalesPartner($validated, $documents);
|
2026-01-26 11:09:42 +09:00
|
|
|
|
|
|
|
|
return redirect()->route('sales.managers.index')
|
2026-01-27 21:20:33 +09:00
|
|
|
->with('success', '영업파트너 등록 신청이 완료되었습니다. 본사 승인 후 활성화됩니다.');
|
2026-01-26 11:09:42 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 상세 페이지
|
|
|
|
|
*/
|
|
|
|
|
public function show(int $id): View
|
|
|
|
|
{
|
2026-01-27 21:20:33 +09:00
|
|
|
$partner = User::with(['parent', 'children', 'userRoles.role', 'salesDocuments', 'approver'])
|
2026-01-26 11:09:42 +09:00
|
|
|
->findOrFail($id);
|
|
|
|
|
|
2026-01-27 21:20:33 +09:00
|
|
|
// 계층 레벨
|
|
|
|
|
$level = $this->service->getPartnerLevel($partner);
|
|
|
|
|
|
|
|
|
|
// 하위 파트너
|
|
|
|
|
$children = User::where('parent_id', $partner->id)
|
|
|
|
|
->with('userRoles.role')
|
|
|
|
|
->get();
|
|
|
|
|
|
|
|
|
|
// 역할 위임 가능한 하위 파트너
|
|
|
|
|
$delegationCandidates = $this->service->getDelegationCandidates($partner);
|
|
|
|
|
|
|
|
|
|
return view('sales.managers.show', compact('partner', 'level', 'children', 'delegationCandidates'));
|
2026-01-26 11:09:42 +09:00
|
|
|
}
|
|
|
|
|
|
2026-01-29 21:27:05 +09:00
|
|
|
/**
|
|
|
|
|
* 상세 모달용
|
|
|
|
|
*/
|
|
|
|
|
public function modalShow(int $id): View
|
|
|
|
|
{
|
|
|
|
|
$partner = User::with(['parent', 'children', 'userRoles.role', 'salesDocuments', 'approver'])
|
|
|
|
|
->findOrFail($id);
|
|
|
|
|
|
|
|
|
|
$level = $this->service->getPartnerLevel($partner);
|
|
|
|
|
|
|
|
|
|
$children = User::where('parent_id', $partner->id)
|
|
|
|
|
->with('userRoles.role')
|
|
|
|
|
->get();
|
|
|
|
|
|
|
|
|
|
return view('sales.managers.partials.show-modal', compact('partner', 'level', 'children'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 수정 모달용
|
|
|
|
|
*/
|
|
|
|
|
public function modalEdit(int $id): View
|
|
|
|
|
{
|
|
|
|
|
$partner = User::with(['userRoles.role', 'salesDocuments', 'parent'])->findOrFail($id);
|
|
|
|
|
$roles = $this->service->getSalesRoles();
|
|
|
|
|
$currentRoleIds = $partner->userRoles->pluck('role_id')->toArray();
|
|
|
|
|
$documentTypes = SalesManagerDocument::DOCUMENT_TYPES;
|
|
|
|
|
|
|
|
|
|
return view('sales.managers.partials.edit-modal', compact(
|
|
|
|
|
'partner', 'roles', 'currentRoleIds', 'documentTypes'
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-26 11:09:42 +09:00
|
|
|
/**
|
|
|
|
|
* 수정 폼
|
|
|
|
|
*/
|
|
|
|
|
public function edit(int $id): View
|
|
|
|
|
{
|
2026-01-27 21:28:59 +09:00
|
|
|
$partner = User::with(['userRoles.role', 'salesDocuments', 'parent'])->findOrFail($id);
|
2026-01-27 20:06:51 +09:00
|
|
|
|
2026-01-27 21:20:33 +09:00
|
|
|
// 영업 역할 목록
|
|
|
|
|
$roles = $this->service->getSalesRoles();
|
2026-01-26 11:09:42 +09:00
|
|
|
|
2026-01-27 20:06:51 +09:00
|
|
|
// 현재 역할 ID 목록
|
2026-01-27 21:20:33 +09:00
|
|
|
$currentRoleIds = $partner->userRoles->pluck('role_id')->toArray();
|
2026-01-27 20:06:51 +09:00
|
|
|
|
|
|
|
|
// 문서 타입 목록
|
|
|
|
|
$documentTypes = SalesManagerDocument::DOCUMENT_TYPES;
|
|
|
|
|
|
|
|
|
|
return view('sales.managers.edit', compact(
|
2026-01-27 21:28:59 +09:00
|
|
|
'partner', 'roles', 'currentRoleIds', 'documentTypes'
|
2026-01-27 20:06:51 +09:00
|
|
|
));
|
2026-01-26 11:09:42 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 수정 처리
|
|
|
|
|
*/
|
|
|
|
|
public function update(Request $request, int $id)
|
|
|
|
|
{
|
2026-01-27 21:20:33 +09:00
|
|
|
$partner = User::findOrFail($id);
|
2026-01-26 11:09:42 +09:00
|
|
|
|
|
|
|
|
$validated = $request->validate([
|
|
|
|
|
'name' => 'required|string|max:100',
|
2026-01-27 20:06:51 +09:00
|
|
|
'email' => 'required|email|max:255|unique:users,email,' . $id,
|
2026-01-26 11:09:42 +09:00
|
|
|
'phone' => 'nullable|string|max:20',
|
2026-01-27 20:06:51 +09:00
|
|
|
'password' => 'nullable|string|min:4|confirmed',
|
|
|
|
|
'role_ids' => 'required|array|min:1',
|
|
|
|
|
'role_ids.*' => 'exists:roles,id',
|
|
|
|
|
'documents' => 'nullable|array',
|
|
|
|
|
'documents.*.file' => 'nullable|file|max:10240',
|
|
|
|
|
'documents.*.document_type' => 'nullable|string',
|
|
|
|
|
'documents.*.description' => 'nullable|string|max:500',
|
2026-01-26 11:09:42 +09:00
|
|
|
]);
|
|
|
|
|
|
2026-01-27 20:06:51 +09:00
|
|
|
// 문서 배열 구성
|
|
|
|
|
$documents = [];
|
|
|
|
|
if ($request->hasFile('documents')) {
|
|
|
|
|
foreach ($request->file('documents') as $index => $file) {
|
|
|
|
|
$documents[] = [
|
|
|
|
|
'file' => $file,
|
|
|
|
|
'document_type' => $request->input("documents.{$index}.document_type", 'other'),
|
|
|
|
|
'description' => $request->input("documents.{$index}.description"),
|
|
|
|
|
];
|
|
|
|
|
}
|
2026-01-26 11:09:42 +09:00
|
|
|
}
|
|
|
|
|
|
2026-01-27 21:20:33 +09:00
|
|
|
$this->service->updateSalesPartner($partner, $validated, $documents);
|
2026-01-26 11:09:42 +09:00
|
|
|
|
|
|
|
|
return redirect()->route('sales.managers.index')
|
2026-01-27 21:20:33 +09:00
|
|
|
->with('success', '영업파트너 정보가 수정되었습니다.');
|
2026-01-26 11:09:42 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 삭제 처리 (비활성화)
|
|
|
|
|
*/
|
|
|
|
|
public function destroy(int $id)
|
|
|
|
|
{
|
2026-01-27 21:20:33 +09:00
|
|
|
$partner = User::findOrFail($id);
|
|
|
|
|
$partner->update(['is_active' => false]);
|
2026-01-26 11:09:42 +09:00
|
|
|
|
|
|
|
|
return redirect()->route('sales.managers.index')
|
2026-01-27 21:20:33 +09:00
|
|
|
->with('success', '영업파트너가 비활성화되었습니다.');
|
2026-01-26 11:09:42 +09:00
|
|
|
}
|
2026-01-27 20:06:51 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 승인 처리
|
|
|
|
|
*/
|
|
|
|
|
public function approve(int $id)
|
|
|
|
|
{
|
2026-01-27 21:20:33 +09:00
|
|
|
$partner = User::findOrFail($id);
|
|
|
|
|
$this->service->approve($partner, auth()->id());
|
2026-01-27 20:06:51 +09:00
|
|
|
|
|
|
|
|
return redirect()->back()
|
2026-01-27 21:20:33 +09:00
|
|
|
->with('success', '영업파트너가 승인되었습니다.');
|
2026-01-27 20:06:51 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 반려 처리
|
|
|
|
|
*/
|
|
|
|
|
public function reject(Request $request, int $id)
|
|
|
|
|
{
|
|
|
|
|
$validated = $request->validate([
|
|
|
|
|
'rejection_reason' => 'required|string|max:1000',
|
|
|
|
|
]);
|
|
|
|
|
|
2026-01-27 21:20:33 +09:00
|
|
|
$partner = User::findOrFail($id);
|
|
|
|
|
$this->service->reject($partner, auth()->id(), $validated['rejection_reason']);
|
|
|
|
|
|
|
|
|
|
return redirect()->back()
|
|
|
|
|
->with('success', '영업파트너가 반려되었습니다.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 역할 위임 처리
|
|
|
|
|
*/
|
|
|
|
|
public function delegateRole(Request $request, int $id)
|
|
|
|
|
{
|
|
|
|
|
$validated = $request->validate([
|
|
|
|
|
'to_user_id' => 'required|exists:users,id',
|
|
|
|
|
'role_name' => 'required|string|in:manager,recruiter',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$fromUser = User::findOrFail($id);
|
|
|
|
|
$toUser = User::findOrFail($validated['to_user_id']);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$this->service->delegateRole($fromUser, $toUser, $validated['role_name']);
|
|
|
|
|
|
|
|
|
|
$roleLabel = $validated['role_name'] === 'manager' ? '매니저' : '유치담당';
|
|
|
|
|
return redirect()->back()
|
|
|
|
|
->with('success', "{$roleLabel} 역할이 {$toUser->name}님에게 위임되었습니다.");
|
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
|
|
|
|
return redirect()->back()
|
|
|
|
|
->with('error', $e->getMessage());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 역할 부여
|
|
|
|
|
*/
|
|
|
|
|
public function assignRole(Request $request, int $id)
|
|
|
|
|
{
|
|
|
|
|
$validated = $request->validate([
|
|
|
|
|
'role_name' => 'required|string|in:sales,manager,recruiter',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$partner = User::findOrFail($id);
|
|
|
|
|
$this->service->assignRole($partner, $validated['role_name']);
|
|
|
|
|
|
|
|
|
|
$roleLabels = ['sales' => '영업', 'manager' => '매니저', 'recruiter' => '유치담당'];
|
|
|
|
|
return redirect()->back()
|
|
|
|
|
->with('success', "{$roleLabels[$validated['role_name']]} 역할이 부여되었습니다.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 역할 제거
|
|
|
|
|
*/
|
|
|
|
|
public function removeRole(Request $request, int $id)
|
|
|
|
|
{
|
|
|
|
|
$validated = $request->validate([
|
|
|
|
|
'role_name' => 'required|string|in:sales,manager,recruiter',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$partner = User::findOrFail($id);
|
|
|
|
|
$this->service->removeRole($partner, $validated['role_name']);
|
2026-01-27 20:06:51 +09:00
|
|
|
|
2026-01-27 21:20:33 +09:00
|
|
|
$roleLabels = ['sales' => '영업', 'manager' => '매니저', 'recruiter' => '유치담당'];
|
2026-01-27 20:06:51 +09:00
|
|
|
return redirect()->back()
|
2026-01-27 21:20:33 +09:00
|
|
|
->with('success', "{$roleLabels[$validated['role_name']]} 역할이 제거되었습니다.");
|
2026-01-27 20:06:51 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 서류 다운로드
|
|
|
|
|
*/
|
|
|
|
|
public function downloadDocument(int $id, int $documentId)
|
|
|
|
|
{
|
|
|
|
|
$document = SalesManagerDocument::where('user_id', $id)
|
|
|
|
|
->findOrFail($documentId);
|
|
|
|
|
|
|
|
|
|
return $document->download();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 서류 삭제
|
|
|
|
|
*/
|
|
|
|
|
public function deleteDocument(int $id, int $documentId)
|
|
|
|
|
{
|
|
|
|
|
$document = SalesManagerDocument::where('user_id', $id)
|
|
|
|
|
->findOrFail($documentId);
|
|
|
|
|
|
|
|
|
|
$this->service->deleteDocument($document);
|
|
|
|
|
|
|
|
|
|
return redirect()->back()
|
|
|
|
|
->with('success', '서류가 삭제되었습니다.');
|
|
|
|
|
}
|
2026-01-26 11:09:42 +09:00
|
|
|
}
|