Files
sam-docs/dev/dev_plans/tenant-id-compliance-plan.md
권혁성 db63fcff85 refactor: [docs] 팀별 폴더 구조 재편 (공유/개발/프론트/기획)
- 개발팀 전용 폴더 dev/ 생성 (standards, guides, quickstart, changes, deploys, data, history, dev_plans 이동)
- 프론트엔드 전용 폴더 frontend/ 생성 (api/ → frontend/api-specs/)
- 기획팀 폴더 requests/ 생성
- plans/ → dev/dev_plans/ 이름 변경
- README.md 신규 (사람용 안내), INDEX.md 재작성 (Claude Code용)
- resources.md 신규 (노션 링크용, assets/brochure 이관 예정)
- CURRENT_WORKS.md 삭제, TODO.md → dev/ 이동
- 전체 참조 경로 업데이트

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:46:03 +09:00

18 KiB

tenant_id 준수 분석 및 분리 방안

작성일: 2026-01-29 목적: API 전체 모델에서 tenant_id 스코핑 미적용 현황을 분석하고, BelongsToTenant trait 적용 방안 수립 기준 문서: docs/specs/database-schema.md, docs/architecture/system-overview.md 상태: 🔄 분석 완료 → 실행 대기


📍 현재 진행 상태

항목 내용
마지막 완료 작업 전체 모델 분석 완료
다음 작업 사용자 검토 후 Phase 1 실행
진행률 0/4 Phase (0%)
마지막 업데이트 2026-01-29

1. 개요

1.1 배경

SAM API는 멀티테넌트 아키텍처를 사용하며, BelongsToTenant trait를 통해 자동 tenant_id 스코핑을 적용합니다. 그러나 일부 모델에서 trait가 누락되어 있어, 테넌트 간 데이터 격리가 보장되지 않을 수 있습니다.

분석 결과 요약:

  • 전체 모델: 167개
  • BelongsToTenant 적용: 103개 (61.7%)
  • 미적용: 63개 (37.7%)
    • 의도적 글로벌: 18개
    • 부모 종속 (FK 격리): 13개
    • BelongsToTenant 추가 필요: 27개
    • 검토 후 결정: 5개

1.2 기준 원칙

┌─────────────────────────────────────────────────────────────────┐
│  🎯 핵심 원칙                                                    │
├─────────────────────────────────────────────────────────────────┤
│  1. tenant_id 컬럼이 있는 모델은 BelongsToTenant 적용 필수       │
│  2. 부모-자식 관계에서 자식은 부모의 FK로 격리 가능하면 면제      │
│  3. 시스템 전역 데이터(User, Tenant, ApiKey 등)는 글로벌 유지     │
│  4. Boards 영역은 시스템/테넌트 혼용이므로 커스텀 스코프 유지      │
└─────────────────────────────────────────────────────────────────┘

1.3 변경 승인 정책

분류 예시 승인
즉시 가능 BelongsToTenant trait 추가 (기존 동작 유지) 불필요
⚠️ 컨펌 필요 Boards 영역 스코핑 방식 변경, 쿼리 로직 수정 필수
🔴 금지 테이블 구조 변경, tenant_id 컬럼 추가/삭제 별도 협의

1.4 준수 규칙

  • docs/specs/database-schema.md - 테이블 구조
  • docs/architecture/system-overview.md - 시스템 아키텍처
  • docs/standards/quality-checklist.md - 품질 체크리스트

2. 분석 결과 상세

2.1 의도적 글로벌 (BelongsToTenant 불필요) - 18개

시스템 전역 데이터로, tenant_id 스코핑이 필요하지 않습니다.

# 모델 테이블 tenant_id 사유
1 ApiKey api_keys 시스템 API 키
2 ApiRequestLog api_request_logs 시스템 감시 로그
3 AuditLog audit_logs 전체 감사 로그 (자체 tenant_id 필터링)
4 FcmSendLog fcm_send_logs 푸시 발송 로그 (수동 필터링)
5 LoginToken login_tokens MNG→API 인증 토큰
6 SiteAdmin site_admins 시스템 관리자
7 Tenant tenants 테넌트 마스터 (자기 자신)
8 Plan plans 구독 플랜 정의
9 Subscription subscriptions 구독 (tenant_id로 연결)
10 Payment payments 결제 (tenant_id로 연결)
11 User users 글로벌 사용자 계정
12 UserTenant user_tenants 사용자-테넌트 매핑 (피벗)
13 UserRole user_roles 사용자-역할 매핑 (피벗)
14 GlobalMenu global_menus 시스템 전역 메뉴
15 Tag tags 시스템 공용 태그
16 SystemFieldDefinition system_field_definitions 시스템 필드 정의
17 KdPriceTable kd_price_tables 경동 전용 단가표 (레거시)
18 TenantScope - - 스코프 정의 클래스 (모델 아님)

2.2 부모 종속 (FK 격리 - BelongsToTenant 면제) - 13개

부모 모델에 BelongsToTenant가 적용되어 있고, FK를 통해 자동 격리되는 자식 모델입니다.

# 모델 테이블 부모 모델 FK
1 PostCustomFieldValue post_custom_field_values Post post_id
2 DocumentData document_data Document document_id
3 DocumentApproval document_approvals Document document_id
4 DocumentAttachment document_attachments Document document_id
5 RoleMenuPermission role_menu_permissions Role role_id
6 UserMenuPermission user_menu_permissions - user_id + menu_id
7 NotificationSettingGroupItem notification_setting_group_items NotificationSettingGroup group_id
8 BillInstallment bill_installments Bill bill_id
9 ApprovalStep approval_steps Approval approval_id
10 OrderItemComponent order_item_components OrderItem order_item_id
11 MaterialInspectionItem inspection_items MaterialInspection inspection_id
12 QuoteFormulaItem quote_formula_items QuoteFormula formula_id
13 QuoteFormulaRange quote_formula_ranges QuoteFormula formula_id

주의: 이 모델들은 직접 쿼리할 때 부모를 통한 접근이 필수입니다. 직접 ::all() 등으로 접근하면 테넌트 격리가 안됩니다.

2.3 🔴 BelongsToTenant 추가 필요 - 27개

tenant_id 컬럼이 있지만 BelongsToTenant trait가 없는 모델입니다. 추가해야 합니다.

# 모델 테이블 영역 우선순위
Design 영역 (4개)
1 DesignModel models 설계 높음
2 ModelVersion model_versions 설계 높음
3 BomTemplate bom_templates 설계 높음
4 BomTemplateItem bom_template_items 설계 높음
Orders 영역 (2개)
5 OrderHistory order_histories 수주 높음
6 OrderVersion order_versions 수주 높음
Materials 영역 (3개)
7 MaterialReceipt material_receipts 자재 높음
8 MaterialInspection inspections 자재 높음
9 MaterialInspectionItem inspection_items 자재 중간
Tenants 설정 영역 (7개)
10 TenantOptionGroup tenant_option_groups 설정 높음
11 TenantOptionValue tenant_option_values 설정 높음
12 TenantFieldSetting tenant_field_settings 설정 높음
13 TenantUserProfile tenant_user_profiles 설정 중간
14 SettingFieldDef setting_field_defs 설정 중간
15 TenantStatField tenant_stat_fields 설정 중간
16 BarobillSetting barobill_settings 설정 낮음
BadDebts 영역 (2개)
17 BadDebtDocument bad_debt_documents 미수금 중간
18 BadDebtMemo bad_debt_memos 미수금 중간
Permissions 영역 (2개)
19 Permission permissions 권한 높음
20 PermissionOverride permission_overrides 권한 높음
기타 영역 (8개)
21 MainRequest main_requests 요청 높음
22 MainRequestFlow main_request_flows 요청 높음
23 MainRequestEstimate main_request_estimates 견적 높음
24 Schedule schedules 일정 중간
25 ProcessItem process_items 공정 중간
26 ProcessClassificationRule process_classification_rules 공정 중간
27 ItemDetail item_details 품목 중간

2.4 ⚠️ 검토 후 결정 필요 - 5개

커스텀 스코핑을 사용하거나, 특수한 비즈니스 로직이 있는 모델입니다.

# 모델 현재 방식 검토 사항
1 Board scopeAccessible() 커스텀 tenant_id nullable; 시스템 게시판(tenant_id=null)과 테넌트 게시판 혼용
2 Post 수동 where 조건 Board의 tenant_id 정책을 따름
3 BoardComment 수동 where 조건 Post 종속
4 BoardSetting 수동 where 조건 Board 종속
5 CompanyRequest created_tenant_id tenant_id 대신 created_tenant_id 사용

Board 영역 결론: 시스템 게시판(tenant_id=null)을 지원해야 하므로, BelongsToTenant의 글로벌 스코프 대신 현재 커스텀 스코프(scopeAccessible) 유지가 적합합니다.

2.5 특수 케이스

모델 설명
Part tenant_id 있지만 BelongsToTenant 미적용. Product와 유사 역할이나 별도 테이블
Lot / LotSale tenant_id 있지만 미적용. 품질관리 영역
CalculationConfig tenant_id 있지만 미적용. 계산 설정
QuoteFormulaMapping tenant_id 있지만 미적용. 견적 수식 매핑

3. 작업 절차

3.1 단계별 절차

Phase 1: 고우선순위 모델 적용 (15개)
├── Design 영역: DesignModel, ModelVersion, BomTemplate, BomTemplateItem
├── Orders 영역: OrderHistory, OrderVersion
├── Materials 영역: MaterialReceipt, MaterialInspection
├── Permissions 영역: Permission, PermissionOverride
├── MainRequest 영역: MainRequest, MainRequestFlow, MainRequestEstimate
├── Tenants 설정: TenantOptionGroup, TenantOptionValue, TenantFieldSetting
└── 검증: 기존 서비스 로직에 중복 where 조건 확인 및 정리

Phase 2: 중간 우선순위 모델 적용 (10개)
├── Tenants 설정: TenantUserProfile, SettingFieldDef, TenantStatField
├── BadDebts: BadDebtDocument, BadDebtMemo
├── 기타: Schedule, ProcessItem, ProcessClassificationRule, ItemDetail
├── Materials: MaterialInspectionItem
└── 검증: 서비스 로직 정리

Phase 3: 특수 케이스 처리 (4개)
├── Part, Lot, LotSale, CalculationConfig, QuoteFormulaMapping
└── 각 모델별 비즈니스 로직 확인 후 적용

Phase 4: 서비스 레이어 정리
├── BelongsToTenant 적용 후 불필요한 수동 where('tenant_id', ...) 제거
├── 직접 쿼리하는 부모 종속 모델에 대한 접근 패턴 검토
└── 전체 통합 테스트

4. 상세 작업 내용

4.1 Phase 1: 고우선순위 (15개)

각 모델에 use BelongsToTenant; 추가. 작업 패턴:

// Before
namespace App\Models\Design;

use App\Traits\ModelTrait;
use Illuminate\Database\Eloquent\Model;

class DesignModel extends Model
{
    use ModelTrait;
    // ...
}

// After
namespace App\Models\Design;

use App\Traits\BelongsToTenant;
use App\Traits\ModelTrait;
use Illuminate\Database\Eloquent\Model;

class DesignModel extends Model
{
    use BelongsToTenant, ModelTrait;
    // ...
}

Phase 1 대상 모델:

# 파일 경로 상태
1.1 app/Models/Design/DesignModel.php
1.2 app/Models/Design/ModelVersion.php
1.3 app/Models/Design/BomTemplate.php
1.4 app/Models/Design/BomTemplateItem.php
1.5 app/Models/Orders/OrderHistory.php
1.6 app/Models/Orders/OrderVersion.php
1.7 app/Models/Materials/MaterialReceipt.php
1.8 app/Models/Materials/MaterialInspection.php
1.9 app/Models/Permissions/Permission.php
1.10 app/Models/Permissions/PermissionOverride.php
1.11 app/Models/MainRequest.php
1.12 app/Models/MainRequestFlow.php
1.13 app/Models/Estimates/MainRequestEstimate.php
1.14 app/Models/Tenants/TenantOptionGroup.php
1.15 app/Models/Tenants/TenantOptionValue.php
1.16 app/Models/Tenants/TenantFieldSetting.php

4.2 Phase 2: 중간 우선순위 (10개)

# 파일 경로 상태
2.1 app/Models/Tenants/TenantUserProfile.php
2.2 app/Models/Tenants/SettingFieldDef.php
2.3 app/Models/Tenants/TenantStatField.php
2.4 app/Models/BadDebts/BadDebtDocument.php
2.5 app/Models/BadDebts/BadDebtMemo.php
2.6 app/Models/Tenants/Schedule.php
2.7 app/Models/ProcessItem.php
2.8 app/Models/ProcessClassificationRule.php
2.9 app/Models/Items/ItemDetail.php
2.10 app/Models/Materials/MaterialInspectionItem.php

4.3 Phase 3: 특수 케이스 (5개)

# 파일 경로 검토 사항 상태
3.1 app/Models/Products/Part.php Product와 역할 중복 여부
3.2 app/Models/Qualitys/Lot.php 품질관리 독립 쿼리 패턴
3.3 app/Models/Qualitys/LotSale.php Lot 종속 여부
3.4 app/Models/Calculation/CalculationConfig.php 테넌트별 설정 확인
3.5 app/Models/Quote/QuoteFormulaMapping.php 공용 vs 테넌트별

4.4 Phase 4: 서비스 레이어 정리

BelongsToTenant 적용 후, 서비스에서 수동으로 ->where('tenant_id', $tenantId) 하던 코드를 정리합니다.

확인 대상 서비스:

  • app/Services/Design/ - DesignModelService, BomTemplateService 등
  • app/Services/OrderService.php - OrderHistory, OrderVersion 관련
  • app/Services/Materials/ - MaterialReceiptService 등
  • app/Services/MainRequestService.php
  • 기타 관련 서비스

정리 패턴:

// Before (수동 스코핑)
$models = DesignModel::where('tenant_id', $this->tenantId())->get();

// After (BelongsToTenant 자동 스코핑)
$models = DesignModel::all(); // 글로벌 스코프가 자동 적용

5. 컨펌 대기 목록

# 항목 변경 내용 영향 범위 상태
1 Boards 영역 현재 커스텀 스코프 유지 vs BelongsToTenant 전환 Board, Post, BoardComment, BoardSetting ⚠️ 확인 필요
2 Permission 영역 Spatie Permission 패키지와의 호환성 Permission, PermissionOverride ⚠️ 확인 필요
3 Schedule 모델 tenant_id nullable - 글로벌 일정 지원 유지 여부 Schedule ⚠️ 확인 필요
4 CompanyRequest created_tenant_id → tenant_id 통일 여부 CompanyRequest ⚠️ 확인 필요

6. 변경 이력

날짜 항목 변경 내용 파일 승인
2026-01-29 분석 전체 모델 tenant_id 준수 분석 완료 167개 모델 분석 -
2026-01-29 문서 계획 문서 초안 작성 docs/dev_plans/tenant-id-compliance-plan.md -

7. 참고 문서

  • DB 스키마: docs/specs/database-schema.md
  • 시스템 아키텍처: docs/architecture/system-overview.md
  • API 규칙: docs/standards/api-rules.md
  • 품질 체크리스트: docs/standards/quality-checklist.md
  • BelongsToTenant trait: api/app/Traits/BelongsToTenant.php
  • TenantScope: api/app/Models/Scopes/TenantScope.php

8. 리스크 및 주의사항

8.1 BelongsToTenant 적용 시 부작용

리스크 설명 대응
중복 스코핑 서비스에서 이미 where('tenant_id', ...) 하고 있으면 중복 Phase 4에서 수동 where 제거
withoutGlobalScope 필요 관리자 기능에서 전체 데이터 조회 시 withoutGlobalScopes() 명시
테스트 실패 tenant_id 컨텍스트 없는 테스트 테스트에서 tenant context 설정
마이그레이션 영향 seeder/migration에서 직접 insert tenant context 없이 실행 가능한지 확인

8.2 Permission 모델 특수 사항

Permission은 Spatie Permission 패키지를 확장하므로, BelongsToTenant 적용 시 패키지 내부 쿼리와 충돌 가능성이 있습니다. 적용 전 테스트가 필수입니다.

8.3 Schedule 모델 특수 사항

Schedule의 tenant_id는 nullable입니다. BelongsToTenant 적용 시 tenant_id=null인 글로벌 일정이 조회되지 않을 수 있습니다. TenantScope에서 nullable 처리가 필요할 수 있습니다.


9. 검증 결과

작업 완료 후 이 섹션에 검증 결과 추가

9.1 성공 기준

기준 측정 방법 달성
Phase 1 모델 전체 BelongsToTenant 적용 코드 확인
Phase 2 모델 전체 BelongsToTenant 적용 코드 확인
서비스 레이어 중복 where 제거 grep 검색
기존 API 기능 정상 동작 Swagger 테스트
Pint 포맷팅 통과 ./vendor/bin/pint --test

10. 자기완결성 점검 결과

10.1 체크리스트 검증

# 검증 항목 상태 비고
1 작업 목적이 명확한가? tenant_id 미적용 모델에 BelongsToTenant 추가
2 성공 기준이 정의되어 있는가? 섹션 9.1 참조
3 작업 범위가 구체적인가? 27개 모델 명시, 5개 검토 대상 분리
4 의존성이 명시되어 있는가? Spatie Permission, Boards 커스텀 스코프
5 참고 파일 경로가 정확한가? 전체 모델 파일 경로 명시
6 단계별 절차가 실행 가능한가? Phase 1~4 구체적 작업 항목
7 검증 방법이 명시되어 있는가? 성공 기준 및 테스트 방법
8 모호한 표현이 없는가? 구체적 모델명, 파일 경로, 수치 사용

10.2 새 세션 시뮬레이션 테스트

질문 답변 가능 참조 섹션
Q1. 이 작업의 목적은 무엇인가? 1.1 배경
Q2. 어디서부터 시작해야 하는가? 4.1 Phase 1
Q3. 어떤 파일을 수정해야 하는가? 2.3 수정 필요 목록 + 4.1~4.3 파일 경로
Q4. 작업 완료 확인 방법은? 9.1 성공 기준
Q5. 막혔을 때 참고 문서는? 7. 참고 문서

결과: 5/5 통과 → 자기완결성 확보


이 문서는 /sc:plan 스킬로 생성되었습니다.