From f83d2a1333995b5a470f939a89b2a694ea34ee68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Sat, 31 Jan 2026 20:15:03 +0900 Subject: [PATCH] =?UTF-8?q?feat:=EA=B0=9C=EB=B0=9C=20=EC=8A=B9=EC=9D=B8=20?= =?UTF-8?q?=EB=A9=94=EB=89=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 영업관리 하위에 "개발 승인" 메뉴 추가 - 영업/매니저 100% 완료 고객의 개발 진행 상태 관리 - 3분할 레이아웃: 승인대기 / 개발진행중 / 완료 - 7단계 진행 상태: 대기→검토→기획안작성→개발코드작성→개발테스트→개발완료→통합테스트→인계 - 승인/반려/상태변경 기능 구현 - 통계 카드 및 상세 모달 지원 Co-Authored-By: Claude --- .../SalesDevelopmentApprovalController.php | 193 ++++++++++++++ .../Sales/SalesDevelopmentApprovalService.php | 217 +++++++++++++++ .../seeders/DevelopmentApprovalMenuSeeder.php | 81 ++++++ .../sales/development/approvals.blade.php | 247 ++++++++++++++++++ .../partials/completed-list.blade.php | 94 +++++++ .../partials/detail-modal.blade.php | 161 ++++++++++++ .../partials/pending-list.blade.php | 92 +++++++ .../partials/progress-list.blade.php | 106 ++++++++ .../partials/reject-modal.blade.php | 32 +++ routes/web.php | 14 + 10 files changed, 1237 insertions(+) create mode 100644 app/Http/Controllers/Sales/SalesDevelopmentApprovalController.php create mode 100644 app/Services/Sales/SalesDevelopmentApprovalService.php create mode 100644 database/seeders/DevelopmentApprovalMenuSeeder.php create mode 100644 resources/views/sales/development/approvals.blade.php create mode 100644 resources/views/sales/development/partials/completed-list.blade.php create mode 100644 resources/views/sales/development/partials/detail-modal.blade.php create mode 100644 resources/views/sales/development/partials/pending-list.blade.php create mode 100644 resources/views/sales/development/partials/progress-list.blade.php create mode 100644 resources/views/sales/development/partials/reject-modal.blade.php diff --git a/app/Http/Controllers/Sales/SalesDevelopmentApprovalController.php b/app/Http/Controllers/Sales/SalesDevelopmentApprovalController.php new file mode 100644 index 00000000..821bfa21 --- /dev/null +++ b/app/Http/Controllers/Sales/SalesDevelopmentApprovalController.php @@ -0,0 +1,193 @@ +user()->isAdmin()) { + abort(403, '접근 권한이 없습니다.'); + } + + if ($request->header('HX-Request')) { + return response('', 200)->header('HX-Redirect', route('sales.development.approvals.index')); + } + + $search = $request->get('search'); + + // 통계 + $stats = $this->service->getStats(); + + // 3개 목록 + $pendingItems = $this->service->getPendingApprovals($search); + $progressItems = $this->service->getInProgressItems($search); + $completedItems = $this->service->getCompletedItems($search); + + // 본사 진행 상태 정보 (뷰에서 사용) + $hqStatuses = SalesTenantManagement::$hqStatusLabels; + $hqStatusOrder = SalesTenantManagement::$hqStatusOrder; + + return view('sales.development.approvals', compact( + 'stats', + 'pendingItems', + 'progressItems', + 'completedItems', + 'hqStatuses', + 'hqStatusOrder' + )); + } + + /** + * 승인 처리 + */ + public function approve(Request $request, int $id) + { + // 권한 체크 + if (!auth()->user()->isAdmin()) { + abort(403, '접근 권한이 없습니다.'); + } + + try { + $management = $this->service->approve($id); + $tenantName = $management->tenant?->company_name ?? '알 수 없음'; + + if ($request->header('HX-Request')) { + return response()->json([ + 'success' => true, + 'message' => "{$tenantName}의 개발이 승인되었습니다.", + ]); + } + + return redirect()->route('sales.development.approvals.index') + ->with('success', "{$tenantName}의 개발이 승인되었습니다."); + } catch (\InvalidArgumentException $e) { + if ($request->header('HX-Request')) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + + return redirect()->back()->with('error', $e->getMessage()); + } + } + + /** + * 반려 처리 + */ + public function reject(Request $request, int $id) + { + // 권한 체크 + if (!auth()->user()->isAdmin()) { + abort(403, '접근 권한이 없습니다.'); + } + + $validated = $request->validate([ + 'rejection_reason' => 'required|string|max:1000', + ]); + + try { + $management = $this->service->reject($id, $validated['rejection_reason']); + $tenantName = $management->tenant?->company_name ?? '알 수 없음'; + + if ($request->header('HX-Request')) { + return response()->json([ + 'success' => true, + 'message' => "{$tenantName}이(가) 반려되었습니다.", + ]); + } + + return redirect()->route('sales.development.approvals.index') + ->with('success', "{$tenantName}이(가) 반려되었습니다."); + } catch (\InvalidArgumentException $e) { + if ($request->header('HX-Request')) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + + return redirect()->back()->with('error', $e->getMessage()); + } + } + + /** + * 상태 변경 + */ + public function updateStatus(Request $request, int $id) + { + // 권한 체크 + if (!auth()->user()->isAdmin()) { + abort(403, '접근 권한이 없습니다.'); + } + + $validated = $request->validate([ + 'status' => 'required|string', + ]); + + try { + $management = $this->service->updateHqStatus($id, $validated['status']); + $tenantName = $management->tenant?->company_name ?? '알 수 없음'; + $statusLabel = SalesTenantManagement::$hqStatusLabels[$validated['status']] ?? $validated['status']; + + if ($request->header('HX-Request')) { + return response()->json([ + 'success' => true, + 'message' => "{$tenantName}의 상태가 '{$statusLabel}'(으)로 변경되었습니다.", + ]); + } + + return redirect()->route('sales.development.approvals.index') + ->with('success', "{$tenantName}의 상태가 '{$statusLabel}'(으)로 변경되었습니다."); + } catch (\InvalidArgumentException $e) { + if ($request->header('HX-Request')) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + + return redirect()->back()->with('error', $e->getMessage()); + } + } + + /** + * 상세 정보 모달 + */ + public function detail(int $id): View + { + // 권한 체크 + if (!auth()->user()->isAdmin()) { + abort(403, '접근 권한이 없습니다.'); + } + + $management = $this->service->getDetail($id); + $hqStatuses = SalesTenantManagement::$hqStatusLabels; + $hqStatusOrder = SalesTenantManagement::$hqStatusOrder; + + return view('sales.development.partials.detail-modal', compact( + 'management', + 'hqStatuses', + 'hqStatusOrder' + )); + } +} diff --git a/app/Services/Sales/SalesDevelopmentApprovalService.php b/app/Services/Sales/SalesDevelopmentApprovalService.php new file mode 100644 index 00000000..b597e1bf --- /dev/null +++ b/app/Services/Sales/SalesDevelopmentApprovalService.php @@ -0,0 +1,217 @@ +where('sales_progress', 100) + ->where('manager_progress', 100) + ->where('hq_status', SalesTenantManagement::HQ_STATUS_PENDING) + ->count(); + + // 개발 진행 중 (review ~ int_test) + $progressStatuses = [ + SalesTenantManagement::HQ_STATUS_REVIEW, + SalesTenantManagement::HQ_STATUS_PLANNING, + SalesTenantManagement::HQ_STATUS_CODING, + SalesTenantManagement::HQ_STATUS_DEV_TEST, + SalesTenantManagement::HQ_STATUS_DEV_DONE, + SalesTenantManagement::HQ_STATUS_INT_TEST, + ]; + $inProgressCount = SalesTenantManagement::query() + ->where('sales_progress', 100) + ->where('manager_progress', 100) + ->whereIn('hq_status', $progressStatuses) + ->count(); + + // 오늘 완료 (handover 상태이고 오늘 업데이트된) + $todayCompletedCount = SalesTenantManagement::query() + ->where('hq_status', SalesTenantManagement::HQ_STATUS_HANDOVER) + ->whereDate('updated_at', today()) + ->count(); + + // 총 완료 + $totalCompletedCount = SalesTenantManagement::query() + ->where('hq_status', SalesTenantManagement::HQ_STATUS_HANDOVER) + ->count(); + + return [ + 'pending' => $pendingCount, + 'in_progress' => $inProgressCount, + 'today_completed' => $todayCompletedCount, + 'total_completed' => $totalCompletedCount, + ]; + } + + /** + * 승인 대기 목록 조회 + */ + public function getPendingApprovals(?string $search = null, int $perPage = 10): LengthAwarePaginator + { + $query = SalesTenantManagement::query() + ->with(['tenant', 'salesPartner', 'manager']) + ->where('sales_progress', 100) + ->where('manager_progress', 100) + ->where('hq_status', SalesTenantManagement::HQ_STATUS_PENDING); + + // 검색 + if ($search) { + $query->whereHas('tenant', function ($q) use ($search) { + $q->where('company_name', 'like', "%{$search}%") + ->orWhere('business_number', 'like', "%{$search}%"); + }); + } + + return $query->latest('updated_at')->paginate($perPage, ['*'], 'pending_page'); + } + + /** + * 개발 진행 중 목록 조회 + */ + public function getInProgressItems(?string $search = null, int $perPage = 10): LengthAwarePaginator + { + $progressStatuses = [ + SalesTenantManagement::HQ_STATUS_REVIEW, + SalesTenantManagement::HQ_STATUS_PLANNING, + SalesTenantManagement::HQ_STATUS_CODING, + SalesTenantManagement::HQ_STATUS_DEV_TEST, + SalesTenantManagement::HQ_STATUS_DEV_DONE, + SalesTenantManagement::HQ_STATUS_INT_TEST, + ]; + + $query = SalesTenantManagement::query() + ->with(['tenant', 'salesPartner', 'manager']) + ->where('sales_progress', 100) + ->where('manager_progress', 100) + ->whereIn('hq_status', $progressStatuses); + + // 검색 + if ($search) { + $query->whereHas('tenant', function ($q) use ($search) { + $q->where('company_name', 'like', "%{$search}%") + ->orWhere('business_number', 'like', "%{$search}%"); + }); + } + + return $query->latest('updated_at')->paginate($perPage, ['*'], 'progress_page'); + } + + /** + * 완료 목록 조회 + */ + public function getCompletedItems(?string $search = null, int $perPage = 10): LengthAwarePaginator + { + $query = SalesTenantManagement::query() + ->with(['tenant', 'salesPartner', 'manager']) + ->where('hq_status', SalesTenantManagement::HQ_STATUS_HANDOVER); + + // 검색 + if ($search) { + $query->whereHas('tenant', function ($q) use ($search) { + $q->where('company_name', 'like', "%{$search}%") + ->orWhere('business_number', 'like', "%{$search}%"); + }); + } + + return $query->latest('updated_at')->paginate($perPage, ['*'], 'completed_page'); + } + + /** + * 개발 승인 처리 (pending → review) + */ + public function approve(int $id): SalesTenantManagement + { + $management = SalesTenantManagement::findOrFail($id); + + // 승인 조건 확인 + if ($management->sales_progress < 100 || $management->manager_progress < 100) { + throw new \InvalidArgumentException('영업/매니저 진행률이 100%가 아닙니다.'); + } + + if ($management->hq_status !== SalesTenantManagement::HQ_STATUS_PENDING) { + throw new \InvalidArgumentException('이미 승인된 항목입니다.'); + } + + $management->update([ + 'hq_status' => SalesTenantManagement::HQ_STATUS_REVIEW, + ]); + + return $management->fresh(); + } + + /** + * 반려 처리 + */ + public function reject(int $id, string $reason): SalesTenantManagement + { + $management = SalesTenantManagement::findOrFail($id); + + if ($management->hq_status !== SalesTenantManagement::HQ_STATUS_PENDING) { + throw new \InvalidArgumentException('승인 대기 상태가 아닙니다.'); + } + + // notes 필드에 반려 사유 추가 + $currentNotes = $management->notes ?? ''; + $rejectionNote = '[반려 ' . now()->format('Y-m-d H:i') . '] ' . $reason; + $newNotes = $currentNotes ? $currentNotes . "\n" . $rejectionNote : $rejectionNote; + + $management->update([ + 'notes' => $newNotes, + ]); + + return $management->fresh(); + } + + /** + * 본사 진행 상태 업데이트 + */ + public function updateHqStatus(int $id, string $status): SalesTenantManagement + { + $management = SalesTenantManagement::findOrFail($id); + + // 유효한 상태인지 확인 + if (!array_key_exists($status, SalesTenantManagement::$hqStatusLabels)) { + throw new \InvalidArgumentException('유효하지 않은 상태입니다.'); + } + + // pending 상태에서는 review로만 변경 가능 (승인 처리) + if ($management->hq_status === SalesTenantManagement::HQ_STATUS_PENDING && $status !== SalesTenantManagement::HQ_STATUS_REVIEW) { + throw new \InvalidArgumentException('승인 대기 상태에서는 검토 상태로만 변경 가능합니다. 먼저 승인 처리를 해주세요.'); + } + + $management->update([ + 'hq_status' => $status, + ]); + + return $management->fresh(); + } + + /** + * 상세 정보 조회 + */ + public function getDetail(int $id): SalesTenantManagement + { + return SalesTenantManagement::with([ + 'tenant', + 'salesPartner', + 'manager', + 'contractProducts.product', + ])->findOrFail($id); + } +} diff --git a/database/seeders/DevelopmentApprovalMenuSeeder.php b/database/seeders/DevelopmentApprovalMenuSeeder.php new file mode 100644 index 00000000..5343cc63 --- /dev/null +++ b/database/seeders/DevelopmentApprovalMenuSeeder.php @@ -0,0 +1,81 @@ + 영업파트너 승인 아래에 "개발 승인" 메뉴 추가 + */ +class DevelopmentApprovalMenuSeeder extends Seeder +{ + public function run(): void + { + $tenantId = 1; + + // 영업관리 메뉴 ID 찾기 + $salesParentId = Menu::where('tenant_id', $tenantId) + ->where('name', '영업관리') + ->whereNull('parent_id') + ->value('id'); + + if (!$salesParentId) { + $this->command->error('영업관리 메뉴를 찾을 수 없습니다.'); + return; + } + + // 기존 개발 승인 메뉴 확인 + $existingMenu = Menu::where('tenant_id', $tenantId) + ->where('name', '개발 승인') + ->where('parent_id', $salesParentId) + ->first(); + + if ($existingMenu) { + $this->command->info('개발 승인 메뉴가 이미 존재합니다.'); + return; + } + + // 영업파트너 승인 메뉴의 sort_order 확인 + $approvalMenu = Menu::where('tenant_id', $tenantId) + ->where('name', '영업파트너 승인') + ->where('parent_id', $salesParentId) + ->first(); + + $sortOrder = 6; // 기본값 + if ($approvalMenu) { + $sortOrder = $approvalMenu->sort_order + 1; + + // 기존 메뉴들의 sort_order를 뒤로 밀기 + Menu::where('tenant_id', $tenantId) + ->where('parent_id', $salesParentId) + ->where('sort_order', '>=', $sortOrder) + ->increment('sort_order'); + } + + // 개발 승인 메뉴 생성 + Menu::create([ + 'tenant_id' => $tenantId, + 'parent_id' => $salesParentId, + 'name' => '개발 승인', + 'url' => '/sales/development/approvals', + 'icon' => 'code', + 'sort_order' => $sortOrder, + 'is_active' => true, + ]); + + $this->command->info("개발 승인 메뉴 생성 완료 (sort_order: {$sortOrder})"); + + // 결과 출력 + $this->command->info(''); + $this->command->info('=== 영업관리 하위 메뉴 ==='); + $children = Menu::where('parent_id', $salesParentId) + ->orderBy('sort_order') + ->get(['name', 'url', 'sort_order']); + + foreach ($children as $child) { + $this->command->info("{$child->sort_order}. {$child->name} ({$child->url})"); + } + } +} diff --git a/resources/views/sales/development/approvals.blade.php b/resources/views/sales/development/approvals.blade.php new file mode 100644 index 00000000..43de16e2 --- /dev/null +++ b/resources/views/sales/development/approvals.blade.php @@ -0,0 +1,247 @@ +@extends('layouts.app') + +@section('title', '개발 승인') + +@section('content') +
+ +
+
+

개발 승인

+

영업 완료된 고객의 개발 진행 상태를 관리합니다

+
+
+ + +
+
+
승인 대기
+
{{ number_format($stats['pending']) }}건
+
+
+
개발 진행중
+
{{ number_format($stats['in_progress']) }}건
+
+
+
오늘 완료
+
{{ number_format($stats['today_completed']) }}건
+
+
+
총 완료
+
{{ number_format($stats['total_completed']) }}건
+
+
+ + +
+
+
+ +
+ +
+
+ + +
+ + @include('sales.development.partials.pending-list') + + + @include('sales.development.partials.progress-list') + + + @include('sales.development.partials.completed-list') +
+
+ + +@include('sales.development.partials.reject-modal') + + + +@endsection + +@push('scripts') + +@endpush diff --git a/resources/views/sales/development/partials/completed-list.blade.php b/resources/views/sales/development/partials/completed-list.blade.php new file mode 100644 index 00000000..7de90bac --- /dev/null +++ b/resources/views/sales/development/partials/completed-list.blade.php @@ -0,0 +1,94 @@ +{{-- 완료 목록 --}} +
+
+ + + + 완료 + {{ $completedItems->total() }}건 +
+
+
+ @forelse($completedItems as $item) + @php + $tenant = $item->tenant; + $companyName = $tenant?->company_name ?? '알 수 없음'; + @endphp +
+
+
+
{{ $companyName }}
+
{{ $tenant?->business_number ?? '-' }}
+
+
+ + {{-- 완료 정보 --}} +
+
+ + + + + 인계 완료 + +
+
+ {{ $item->updated_at->format('Y-m-d') }} +
+
+ + {{-- 7단계 프로그레스 바 (완료 상태) --}} +
+
+ @foreach($hqStatuses as $statusKey => $statusLabel) + @if($statusKey !== 'pending') +
+
+
+ {{ $statusLabel }} +
+
+ @endif + @endforeach +
+
+ + {{-- 담당자 정보 --}} +
+ + + + + 영업: {{ $item->salesPartner?->user?->name ?? '-' }} + + | + + 매니저: {{ $item->manager?->name ?? '-' }} + +
+ + {{-- 상세 버튼 --}} +
+ +
+
+ @empty +
+ + + + 완료된 항목이 없습니다. +
+ @endforelse +
+
+ @if($completedItems->hasPages()) +
+ {{ $completedItems->withQueryString()->links() }} +
+ @endif +
diff --git a/resources/views/sales/development/partials/detail-modal.blade.php b/resources/views/sales/development/partials/detail-modal.blade.php new file mode 100644 index 00000000..02cba335 --- /dev/null +++ b/resources/views/sales/development/partials/detail-modal.blade.php @@ -0,0 +1,161 @@ +{{-- 상세 정보 모달 --}} +@php + $tenant = $management->tenant; + $companyName = $tenant?->company_name ?? '알 수 없음'; + $currentHqStep = $hqStatusOrder[$management->hq_status ?? 'pending'] ?? 0; +@endphp + +
+ {{-- 헤더 --}} +
+
+

{{ $companyName }}

+

{{ $tenant?->business_number ?? '-' }}

+
+ +
+ + {{-- 현재 상태 --}} +
+

개발 진행 상태

+
+ {{-- 7단계 프로그레스 바 --}} +
+
+ @foreach($hqStatuses as $statusKey => $statusLabel) + @php + $stepNum = $hqStatusOrder[$statusKey]; + $isCompleted = $stepNum < $currentHqStep; + $isCurrent = $stepNum === $currentHqStep; + @endphp +
+
+
+ {{ $statusLabel }} +
+
+ @endforeach +
+
+ 대기 + 인계 +
+
+
+ + {{ $management->hq_status_label }} + +
+
+
+ + {{-- 진행률 정보 --}} +
+
+
영업 진행률
+
+
+
+
+ {{ $management->sales_progress }}% +
+
+ 담당: {{ $management->salesPartner?->user?->name ?? '-' }} +
+
+
+
매니저 진행률
+
+
+
+
+ {{ $management->manager_progress }}% +
+
+ 담당: {{ $management->manager?->name ?? '-' }} +
+
+
+ + {{-- 테넌트 정보 --}} +
+

고객 정보

+
+
+
+
업체명
+
{{ $tenant?->company_name ?? '-' }}
+
+
+
사업자번호
+
{{ $tenant?->business_number ?? '-' }}
+
+
+
대표자
+
{{ $tenant?->representative_name ?? '-' }}
+
+
+
연락처
+
{{ $tenant?->phone ?? '-' }}
+
+
+
주소
+
{{ $tenant?->address ?? '-' }}
+
+
+
+
+ + {{-- 계약 상품 정보 --}} + @if($management->contractProducts->count() > 0) +
+

계약 상품

+
+ + + + + + + + + + @foreach($management->contractProducts as $cp) + + + + + + @endforeach + +
상품명가입비월 구독료
{{ $cp->product?->name ?? '-' }}{{ number_format($cp->registration_fee ?? 0) }}원{{ number_format($cp->subscription_fee ?? 0) }}원
+
+
+ @endif + + {{-- 메모 --}} + @if($management->notes) +
+

메모

+
+

{{ $management->notes }}

+
+
+ @endif + + {{-- 닫기 버튼 --}} +
+ +
+
diff --git a/resources/views/sales/development/partials/pending-list.blade.php b/resources/views/sales/development/partials/pending-list.blade.php new file mode 100644 index 00000000..29c6ab3c --- /dev/null +++ b/resources/views/sales/development/partials/pending-list.blade.php @@ -0,0 +1,92 @@ +{{-- 승인 대기 목록 --}} +
+
+ + + + 승인 대기 + {{ $pendingItems->total() }}건 +
+
+
+ @forelse($pendingItems as $item) + @php + $tenant = $item->tenant; + $companyName = $tenant?->company_name ?? '알 수 없음'; + @endphp +
+
+
+
{{ $companyName }}
+
{{ $tenant?->business_number ?? '-' }}
+
+
{{ $item->updated_at->format('m/d') }}
+
+ + {{-- 진행률 표시 --}} +
+
+ 영업 +
+
+
+ {{ $item->sales_progress }}% +
+
+ 매니 +
+
+
+ {{ $item->manager_progress }}% +
+
+ + {{-- 담당자 정보 --}} +
+ + + + + 영업: {{ $item->salesPartner?->user?->name ?? '-' }} + + | + + 매니저: {{ $item->manager?->name ?? '-' }} + +
+ + {{-- 버튼 --}} +
+ + + +
+
+ @empty +
+ + + + 승인 대기 중인 항목이 없습니다. +
+ @endforelse +
+
+ @if($pendingItems->hasPages()) +
+ {{ $pendingItems->withQueryString()->links() }} +
+ @endif +
diff --git a/resources/views/sales/development/partials/progress-list.blade.php b/resources/views/sales/development/partials/progress-list.blade.php new file mode 100644 index 00000000..a8de2507 --- /dev/null +++ b/resources/views/sales/development/partials/progress-list.blade.php @@ -0,0 +1,106 @@ +{{-- 개발 진행 중 목록 --}} +
+
+ + + + 개발 진행중 + {{ $progressItems->total() }}건 +
+
+
+ @forelse($progressItems as $item) + @php + $tenant = $item->tenant; + $companyName = $tenant?->company_name ?? '알 수 없음'; + $currentHqStep = $hqStatusOrder[$item->hq_status ?? 'pending'] ?? 0; + @endphp +
+
+
+
{{ $companyName }}
+
{{ $tenant?->business_number ?? '-' }}
+
+
{{ $item->updated_at->format('m/d') }}
+
+ + {{-- 7단계 프로그레스 바 --}} +
+
+ @foreach($hqStatuses as $statusKey => $statusLabel) + @if($statusKey !== 'pending') + @php + $stepNum = $hqStatusOrder[$statusKey]; + $isCompleted = $stepNum < $currentHqStep; + $isCurrent = $stepNum === $currentHqStep; + @endphp +
+
+
+ {{ $statusLabel }} +
+
+ @endif + @endforeach +
+
{{ $item->hq_status_label }}
+
+ + {{-- 상태 변경 드롭다운 --}} +
+ + +
+ + {{-- 담당자 정보 --}} +
+ + + + + 영업: {{ $item->salesPartner?->user?->name ?? '-' }} + + | + + 매니저: {{ $item->manager?->name ?? '-' }} + +
+ + {{-- 상세 버튼 --}} +
+ +
+
+ @empty +
+ + + + 개발 진행 중인 항목이 없습니다. +
+ @endforelse +
+
+ @if($progressItems->hasPages()) +
+ {{ $progressItems->withQueryString()->links() }} +
+ @endif +
diff --git a/resources/views/sales/development/partials/reject-modal.blade.php b/resources/views/sales/development/partials/reject-modal.blade.php new file mode 100644 index 00000000..bc00c64d --- /dev/null +++ b/resources/views/sales/development/partials/reject-modal.blade.php @@ -0,0 +1,32 @@ +{{-- 반려 사유 모달 --}} + diff --git a/routes/web.php b/routes/web.php index c6f3d3c2..9e943993 100644 --- a/routes/web.php +++ b/routes/web.php @@ -887,6 +887,20 @@ Route::post('managers/approvals/{id}/approve', [\App\Http\Controllers\Sales\SalesManagerController::class, 'approveFromList'])->name('managers.approvals.approve'); Route::post('managers/approvals/{id}/reject', [\App\Http\Controllers\Sales\SalesManagerController::class, 'rejectFromList'])->name('managers.approvals.reject'); + // 개발 승인 관리 (본사 관리자 전용) + Route::prefix('development')->name('development.')->group(function () { + Route::get('/approvals', [\App\Http\Controllers\Sales\SalesDevelopmentApprovalController::class, 'index']) + ->name('approvals.index'); + Route::post('/approvals/{id}/approve', [\App\Http\Controllers\Sales\SalesDevelopmentApprovalController::class, 'approve']) + ->name('approvals.approve'); + Route::post('/approvals/{id}/reject', [\App\Http\Controllers\Sales\SalesDevelopmentApprovalController::class, 'reject']) + ->name('approvals.reject'); + Route::post('/approvals/{id}/status', [\App\Http\Controllers\Sales\SalesDevelopmentApprovalController::class, 'updateStatus']) + ->name('approvals.status'); + Route::get('/approvals/{id}/detail', [\App\Http\Controllers\Sales\SalesDevelopmentApprovalController::class, 'detail']) + ->name('approvals.detail'); + }); + // 영업 담당자 관리 Route::resource('managers', \App\Http\Controllers\Sales\SalesManagerController::class); Route::get('managers/{id}/modal-show', [\App\Http\Controllers\Sales\SalesManagerController::class, 'modalShow'])->name('managers.modal-show');