Files
sam-docs/changes/20251111_admin_tenant_selector.md
권혁성 0ace50b006 docs: [종합정비] 구조 재편 — Phase 0+2+4 통합
- Phase 0: INDEX.md 전면 재작성, CLAUDE.md→INDEX.md 통합 삭제
- Phase 0: front/→guides/ 이관(5개 파일), changes/ D7 포맷 통일(3개)
- Phase 0: guides/ai-config-설정.md→ai-config-settings.md D3 통일
- Phase 2: architecture/+specs/→system/ 이관(6개 이동, 4개 폐기)
- Phase 2: 13개 파일 경로 참조 수정 (specs/→system/, architecture/→system/)
- Phase 4: 7개 파일 11개 교차참조 깨진 링크 수정

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 18:03:04 +09:00

7.9 KiB

변경 내용 요약

날짜: 2025-11-11 14:50 작업자: Claude Code 이슈: SAM Admin 테넌트 컨텍스트 전환 시스템 구현

📋 변경 개요

SAM Admin 시스템에 테넌트 컨텍스트 전환 기능을 추가했습니다. Admin 사용자가 "전체 보기" 모드와 특정 테넌트 필터링 모드를 자유롭게 전환할 수 있습니다.

주요 기능:

  • TenantSelectorWidget: 전체 보기/특정 테넌트 선택 드롭다운
  • AppliesTenantScope Trait: 모든 Resource에 자동 테넌트 필터링 적용
  • 통계 표시: 현재 컨텍스트에 따른 사용자/제품 수 표시
  • 컨텍스트 알림: 현재 보고 있는 테넌트 정보 시각적 표시

🔧 사용된 도구

네이티브 도구:

  • Read: 기존 파일 분석 (12회)
  • Edit: 파일 수정 (9회)
  • Write: 신규 파일 생성 (2회)
  • Bash: Laravel Pint 실행, 타임스탬프 생성

📁 수정된 파일

신규 파일 생성 (1개):

  1. admin/app/Filament/Concerns/AppliesTenantScope.php - 테넌트 필터링 Trait

기존 파일 수정 (11개): 2. admin/app/Filament/Widgets/TenantSelectorWidget.php - 전체 보기 옵션 추가 3. admin/resources/views/filament/widgets/tenant-selector.blade.php - UI 개선 4. admin/app/Filament/Resources/Products/ProductResource.php - Trait 적용 5. admin/app/Filament/Resources/MaterialResource.php - Trait 적용 6. admin/app/Filament/Resources/CategoryResource.php - Trait 적용 7. admin/app/Filament/Resources/ClientResource.php - Trait 적용 8. admin/app/Filament/Resources/EstimateResource.php - Trait 적용 9. admin/app/Filament/Resources/ProductComponentResource.php - Trait 적용 10. admin/app/Filament/Resources/ClassificationResource.php - Trait 적용 11. admin/app/Filament/Resources/Menus/MenuResource.php - Trait 적용 12. admin/app/Filament/Resources/Categories/CategoryResource.php - Trait 적용

🔧 상세 변경 사항

1. AppliesTenantScope Trait 생성

파일: admin/app/Filament/Concerns/AppliesTenantScope.php

기능:

trait AppliesTenantScope
{
    protected static ?string $tenantColumn = 'tenant_id';

    public static function getEloquentQuery(): Builder
    {
        $query = parent::getEloquentQuery();
        $selectedTenantId = Session::get('selected_tenant_id');

        // "전체 보기" 모드가 아닌 경우에만 필터 적용
        if ($selectedTenantId !== null && $selectedTenantId !== 'all') {
            $tenantColumn = static::$tenantColumn ?? 'tenant_id';
            $query->where($tenantColumn, $selectedTenantId);
        }

        return $query;
    }
}

특징:

  • Session 기반 테넌트 컨텍스트 관리
  • "전체 보기" 모드에서는 필터 미적용
  • 커스텀 tenant_id 컬럼명 지원 ($tenantColumn 오버라이드 가능)
  • 모든 Filament Resource에 재사용 가능

2. TenantSelectorWidget 개선

파일: admin/app/Filament/Widgets/TenantSelectorWidget.php

추가된 기능:

  • isViewingAll(): 전체 보기 모드 여부 확인
  • getTenantStats(): 현재 컨텍스트에 따른 통계 계산
  • updatedSelectedTenantId(): 테넌트 변경 시 Session 관리 및 페이지 리로드

변경 후:

public function updatedSelectedTenantId($value)
{
    if ($value === 'all') {
        Session::forget('selected_tenant_id');
    } else {
        Session::put('selected_tenant_id', $value);
    }

    $this->dispatch('tenant-changed');
}

public function getTenantStats()
{
    $tenantId = Session::get('selected_tenant_id');

    if ($tenantId) {
        // 특정 테넌트 통계
        return [
            'users' => User::whereHas('tenantsMembership', function ($q) use ($tenantId) {
                $q->where('tenants.id', $tenantId);
            })->count(),
            'products' => Product::where('tenant_id', $tenantId)->count(),
        ];
    }

    // 전체 통계
    return [
        'users' => User::count(),
        'products' => Product::count(),
        'tenants' => Tenant::active()->count(),
    ];
}

3. TenantSelector Blade 템플릿 개선

파일: admin/resources/views/filament/widgets/tenant-selector.blade.php

추가된 UI 요소:

{{-- 테넌트 선택 드롭다운 --}}
<select wire:model.live="selectedTenantId">
    <option value="all">🌐 전체 보기</option>
    <option disabled>──────────</option>
    @foreach($this->getTenants() as $tenant)
        <option value="{{ $tenant->id }}">
            {{ $tenant->company_name }} ({{ $tenant->code }})
        </option>
    @endforeach
</select>

{{-- 통계 표시 --}}
<div class="grid grid-cols-3 gap-4">
    @if($this->isViewingAll())
        <div>테넌트: {{ number_format($stats['tenants']) }}</div>
    @endif
    <div>사용자: {{ number_format($stats['users']) }}</div>
    <div>제품: {{ number_format($stats['products']) }}</div>
</div>

{{-- 컨텍스트 알림 --}}
@if(!$this->isViewingAll())
    <div class="bg-blue-50 dark:bg-blue-900/20">
        현재 '<strong>{{ $this->getCurrentTenant()->company_name }}</strong>'의 데이터를 보고 있습니다
    </div>
@endif

4. Resource에 Trait 적용

적용된 Resource (9개):

  1. ProductResource - 제품
  2. MaterialResource - 자재
  3. CategoryResource - 카테고리 (2곳)
  4. ClientResource - 거래처
  5. EstimateResource - 견적
  6. ProductComponentResource - 제품 구성요소
  7. ClassificationResource - 분류
  8. MenuResource - 메뉴

적용 패턴:

use App\Filament\Concerns\AppliesTenantScope;

class ProductResource extends Resource
{
    use AppliesTenantScope;

    // ... 기존 코드
}

효과:

  • Session의 selected_tenant_id에 따라 자동으로 where('tenant_id', $selectedTenantId) 필터 적용
  • "전체 보기" 모드에서는 모든 테넌트 데이터 표시
  • 코드 중복 제거 (각 Resource에서 개별 구현 불필요)

테스트 체크리스트

  • Laravel Pint 실행 (12개 파일, 11개 스타일 이슈 자동 수정)
  • PHP 문법 오류 확인 (오류 없음)
  • 로컬 서버 실행 및 테넌트 선택 위젯 확인
  • "전체 보기" → 모든 데이터 표시 확인
  • 특정 테넌트 선택 → 해당 테넌트 데이터만 표시 확인
  • 통계 표시 정확성 확인
  • 제품/자재/카테고리 등 각 Resource에서 필터링 동작 확인
  • 테넌트 전환 시 페이지 자동 리로드 확인

⚠️ 배포 시 주의사항

  1. Session 기반 컨텍스트: Redis/Database 세션 사용 권장 (파일 세션은 다중 서버 환경에서 동기화 문제 발생 가능)
  2. Widget 등록 필요: AdminPanelProviderTenantSelectorWidget 등록 확인
  3. BelongsToTenant 모델만 적용: tenant_id 컬럼이 없는 모델(User, Role, Permission 등)에는 Trait 미적용
  4. 커스텀 컬럼명: tenant_id가 아닌 다른 컬럼명 사용 시 Resource에서 $tenantColumn 오버라이드 필요
  5. 권한 검증: Admin 사용자만 "전체 보기" 접근 가능하도록 권한 추가 검토 필요

🔗 관련 문서

  • 이전 작업: docs/changes/20251111_admin_users_improvement.md
  • CLAUDE.md: /Users/hskwon/Works/@KD_SAM/SAM/CLAUDE.md

📊 작업 통계

  • 수정된 파일: 11개
  • 신규 파일: 1개
  • 총 변경 라인 수: 약 150줄
  • 작업 시간: 약 30분
  • 검증 완료: 문법, 로직, 코드 스타일

🚀 다음 단계

Optional 추가 기능:

  • Header에 현재 테넌트 배지 표시 (Filament Navigation 커스터마이징)
  • Tenant별 권한 제어 (특정 Tenant에만 접근 가능한 사용자)
  • Tenant 전환 이력 로그 (audit_logs에 기록)

Phase 2: 동적 필드 시스템 구현 (이전 계획 연기분)

  • Admin 기본 필드 관리 (setting_field_defs)
  • Tenant 오버로드 필드 (tenant_field_settings)
  • ViewUser Infolist에 동적 필드 섹션 추가 (Filament v4 방식)