feat:단체(Corporate) 파트너 UI 접근 제한 구현

- User 모델에 isGroupPartner() 헬퍼 추가
- 대시보드에서 단체 파트너는 판매자 카드만 표시 (관리자/협업지원금 카드 제외)
- 유치 파트너 현황 탭 단체 파트너에게 숨김
- 파트너 등록 create/store 접근 차단 (403)
- 파트너 목록에서 등록 버튼 숨김
- SidebarMenuService에 hide_for_group_partner 옵션 기반 메뉴 필터링 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
김보곤
2026-02-19 08:00:58 +09:00
parent 28c6c40f54
commit 81bcd617fe
7 changed files with 42 additions and 7 deletions

View File

@@ -139,7 +139,10 @@ private function getDashboardData(Request $request): array
'approved' => $partnerCommissionApproved,
'color' => 'green',
],
[
];
if (!$isGroupPartner) {
$commissionByRole[] = [
'name' => '관리자',
'rate' => null, // 1개월 구독료 (퍼센트가 아닌 고정 금액)
'rate_label' => '1개월 구독료',
@@ -148,8 +151,8 @@ private function getDashboardData(Request $request): array
'pending' => $managerCommissionPending,
'approved' => $managerCommissionApproved,
'color' => 'blue',
],
[
];
$commissionByRole[] = [
'name' => '협업지원금',
'rate' => 3,
'amount' => $referrerCommissionTotal,
@@ -157,8 +160,8 @@ private function getDashboardData(Request $request): array
'pending' => $referrerCommissionPending,
'approved' => $referrerCommissionApproved,
'color' => 'purple',
],
];
];
}
// === 인계(handover) 완료된 가망고객의 수당 계산 ===
// 내가 등록한 가망고객 중 인계 완료된 것들의 계약 금액 조회
@@ -199,7 +202,9 @@ private function getDashboardData(Request $request): array
// 역할별 수당 업데이트 (실제 지급된 수당 기준)
// 참고: 예상 수당은 나중에 $totalExpectedCommission으로 별도 계산됨
$commissionByRole[0]['amount'] = $partnerCommissionTotal;
$commissionByRole[1]['amount'] = $managerCommissionTotal;
if (!$isGroupPartner) {
$commissionByRole[1]['amount'] = $managerCommissionTotal;
}
// 총 개발비 대비 수당 비율
$totalCommissionRatio = $totalMembershipFee > 0 ? round(($totalCommission / $totalMembershipFee) * 100, 1) : 0;
@@ -345,6 +350,7 @@ private function getDashboardData(Request $request): array
'allManagers',
'managerOnlyProspects',
'commissionSummary',
'isGroupPartner',
'period',
'year',
'month',

View File

@@ -47,6 +47,10 @@ public function index(Request $request): View|Response
*/
public function create(): View
{
if (auth()->user()->isGroupPartner()) {
abort(403, '단체 파트너는 하위 파트너를 등록할 수 없습니다.');
}
// 영업 역할 목록
$roles = $this->service->getSalesRoles();
@@ -61,6 +65,10 @@ public function create(): View
*/
public function store(Request $request)
{
if (auth()->user()->isGroupPartner()) {
abort(403, '단체 파트너는 하위 파트너를 등록할 수 없습니다.');
}
$validated = $request->validate([
'user_id' => 'nullable|string|max:50|unique:users,user_id',
'name' => 'required|string|max:100',

View File

@@ -104,6 +104,14 @@ public function salesPartner(): HasOne
return $this->hasOne(\App\Models\Sales\SalesPartner::class, 'user_id');
}
/**
* 단체(corporate) 파트너 여부 확인
*/
public function isGroupPartner(): bool
{
return $this->salesPartner && $this->salesPartner->isGroup();
}
/**
* 영업파트너 첨부 서류
*/

View File

@@ -50,6 +50,11 @@ public function getUserMenuTree(?User $user = null): Collection
return false;
}
// 단체 파트너에게 숨길 메뉴 체크
if ($menu->getOption('hide_for_group_partner') && $user?->salesPartner?->isGroup()) {
return false;
}
// 부서 권한 체크: 허용된 메뉴 ID만 표시
return in_array($menu->id, $permittedMenuIds);
});

View File

@@ -64,6 +64,7 @@ class="whitespace-nowrap py-3 px-1 border-b-2 font-medium text-sm transition-col
</svg>
활동
</button>
@unless($isGroupPartner ?? false)
<button type="button"
@click="activeTab = 'partner-activity'"
hx-get="{{ route('sales.salesmanagement.dashboard.partner-activity') }}"
@@ -79,6 +80,7 @@ class="whitespace-nowrap py-3 px-1 border-b-2 font-medium text-sm transition-col
</svg>
유치 파트너 현황
</button>
@endunless
</div>
<!-- 새로고침 버튼 -->
@@ -97,6 +99,7 @@ class="refresh-btn inline-flex items-center gap-1.5 px-3 py-1.5 text-sm text-gra
</svg>
<span class="hidden sm:inline">새로고침</span>
</button>
@unless($isGroupPartner ?? false)
<button type="button"
x-show="activeTab === 'partner-activity'"
x-cloak
@@ -113,6 +116,7 @@ class="refresh-btn inline-flex items-center gap-1.5 px-3 py-1.5 text-sm text-gra
</svg>
<span class="hidden sm:inline">새로고침</span>
</button>
@endunless
</nav>
</div>
@@ -123,6 +127,7 @@ class="refresh-btn inline-flex items-center gap-1.5 px-3 py-1.5 text-sm text-gra
</div>
</div>
@unless($isGroupPartner ?? false)
<!-- 콘텐츠: 유치 파트너 현황 -->
<div x-show="activeTab === 'partner-activity'" x-cloak x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100">
<div id="partner-activity-content" class="space-y-6">
@@ -136,6 +141,7 @@ class="refresh-btn inline-flex items-center gap-1.5 px-3 py-1.5 text-sm text-gra
</div>
</div>
</div>
@endunless
</div>
{{-- 시나리오 모달용 포털 --}}

View File

@@ -7,7 +7,7 @@
<h2 class="text-lg font-bold text-gray-800">역할별 수당 상세</h2>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
<div class="grid grid-cols-1 {{ count($commissionByRole) === 1 ? 'md:grid-cols-1 max-w-md' : 'md:grid-cols-3' }} gap-4 mb-4">
@foreach($commissionByRole as $role)
<div class="rounded-xl p-4 border
@if($role['color'] === 'green') bg-green-50 border-green-200

View File

@@ -10,6 +10,7 @@
<h1 class="text-2xl font-bold text-gray-800">영업파트너 관리</h1>
<p class="text-sm text-gray-500 mt-1">영업파트너의 등록, 승인, 역할을 관리합니다</p>
</div>
@unless(auth()->user()->isGroupPartner())
<a href="{{ route('sales.managers.create') }}"
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition text-center w-full sm:w-auto flex items-center justify-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -17,6 +18,7 @@ class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition
</svg>
파트너 등록
</a>
@endunless
</div>
<!-- 통계 카드 -->