Files
sam-docs/dev/dev_plans/user-employee-sync-plan.md
권혁성 4e8e6b8423 docs: [plan] 사용자-사원 삭제 동기화 계획 문서 추가
- Phase 1~4 영향도 분석 및 실행 순서 포함
- 전체 Phase 완료 상태
2026-03-13 00:30:36 +09:00

9.0 KiB

사용자-사원 삭제 동기화 계획

작성일: 2026-03-12 목적: MNG 사용자 관리 ↔ React 사원관리 간 삭제 동기화 수정 (멀티테넌트 고려) 상태: 🔄 진행중


📍 현재 진행 상태

항목 내용
마지막 완료 작업 Phase 4 데이터 정리 완료
다음 작업 검증 및 커밋
진행률 4/4 (100%)
마지막 업데이트 2026-03-13 01:10

1. 개요

1.1 배경

MNG 사용자 관리(/users)와 React 사원관리(/hr/employee-management)가 동일한 DB 테이블을 공유하지만, 삭제 시 동기화가 불완전하여 다음 문제 발생:

  1. MNG 영구 삭제 시 orphan 발생: users hard delete → tenant_user_profiles 잔존 → React에 유령 사원 노출
  2. React 퇴직 처리 시 계정 미차단: employee_status='resigned'만 변경 → 해당 테넌트에 여전히 로그인 가능
  3. 멀티테넌트 미고려: 사용자가 여러 테넌트 소속 가능 → 특정 테넌트 퇴직이 전체 시스템에 영향 주면 안 됨

1.2 테이블 관계

users (전역 계정)
  ├── user_tenants (테넌트별 소속) ← is_active로 테넌트 접근 차단
  └── tenant_user_profiles (테넌트별 사원 정보) ← employee_status로 HR 상태 관리

1.3 차단 레벨 정리

레벨 테이블.필드 범위 용도
전체 시스템 users.is_active 모든 테넌트 계정 완전 정지
특정 테넌트 user_tenants.is_active 해당 테넌트만 퇴직자 접근 차단
HR 상태 tenant_user_profiles.employee_status 해당 테넌트 재직/휴직/퇴직 표시

1.4 성공 기준

  • MNG 영구 삭제 시 orphan tenant_user_profiles 발생하지 않음
  • React 퇴직 처리 시 해당 테넌트 접근 불가 (다른 테넌트는 정상)
  • 인증 시 user_tenants.is_active 체크하여 비활성 테넌트 차단
  • 기존 orphan 데이터 정리 완료

1.5 변경 승인 정책

분류 예시 승인
즉시 가능 기존 메서드에 DELETE 쿼리 추가 불필요
⚠️ 컨펌 필요 인증 로직 변경 (미들웨어) 필수
🔴 금지 users 테이블 구조 변경 별도 협의

2. 대상 범위

Phase 1: MNG forceDelete 수정 (영향: mng) — 위험도 🟢

# 작업 항목 상태 파일
1.1 forceDeleteUser()tenant_user_profiles DELETE 추가 mng/app/Services/UserService.php

영향도 분석:

  • 기존 DB::transaction() 내부에 1줄 추가 → 실패 시 자동 롤백
  • bulkForceDelete()는 내부에서 forceDeleteUser() 루프 호출 → 자동 적용
  • deleteUser() (소프트 삭제)는 수정 불필요 — 복구 가능성 유지
  • 부작용 없음

Phase 2: React 퇴직 처리 수정 (영향: api) — 위험도 🟢

# 작업 항목 상태 파일
2.1 destroy()에서 user_tenants.is_active = false 추가 api/app/Services/EmployeeService.php
2.2 bulkDelete()에도 동일 적용 (user_id 추출 필요) api/app/Services/EmployeeService.php
2.3 update()에서 employee_status 변경 시 is_active 동기화 api/app/Services/EmployeeService.php

영향도 분석:

  • destroy(): 이미 $tenantId, $profile->user_id 확보됨 → 바로 사용 가능
  • bulkDelete(): mass update 방식이라 user_id 목록 추출 후 user_tenants 업데이트 필요
  • getUserInfoForLogin()이 이미 is_active = 1 필터링 → Phase 2 적용 즉시 로그인 시 해당 테넌트 미노출
  • ⚠️ 복직 로직이 있다면 is_active = true 동기화 필요 (확인 필요)
  • 다른 테넌트 영향 없음 (WHERE에 tenant_id 포함)

Phase 3: 테넌트 전환 차단 수정 (영향: api) — 위험도 🟡 ⚠️ 컨펌 필요

# 작업 항목 상태 파일
3.1 SwitchTenantRequestuser_tenants.is_active 검증 추가 (방안 A 채택) api/app/Http/Requests/User/SwitchTenantRequest.php
3.2 i18n 에러 메시지 추가 (tenant_access_denied) api/lang/{ko,en}/error.php

영향도 분석:

  • getUserInfoForLogin(): 이미 is_active = 1 필터링 (수정 불필요)
  • switchMyTenant(): is_active 체크 없음 → API 직접 호출로 비활성 테넌트 전환 가능 (보안 Gap)
  • SwitchTenantRequest: exists:tenants,id만 검증 → user_tenants.is_active 미검증
  • 수정 방안 A (권장): SwitchTenantRequest에서 Rule::exists('user_tenants') + is_active = 1 검증
  • 수정 방안 B: switchMyTenant() 내부에서 is_active 체크 후 예외 throw
  • 정상 사용자 영향 없음 (UI에서 활성 테넌트만 노출)

Phase 4: 기존 데이터 정리 (영향: DB) — 위험도 🟢

# 작업 항목 상태 대상
4.1 개발서버 orphan profiles 11건 삭제 개발 DB (sam)
4.2 로컬 데이터 정합성 확인 로컬 DB (정리 완료)
4.3 운영 DB orphan 7건 삭제 운영 DB (sam-prod)

3. 작업 절차

Phase 1: MNG forceDelete 수정

Step 1: UserService::forceDeleteUser() 분석
├── 현재 트랜잭션 내 삭제 순서 확인
└── tenant_user_profiles DELETE 삽입 위치 결정

Step 2: tenant_user_profiles DELETE 추가
└── $user->forceDelete() 직전에 추가:
    DB::table('tenant_user_profiles')
      ->where('user_id', $user->id)
      ->delete();

Phase 2: React 퇴직 처리 수정

Step 1: EmployeeService::destroy() 수정
├── employee_status = 'resigned' 후
└── user_tenants.is_active = false (해당 테넌트만)
    DB::table('user_tenants')
      ->where('user_id', $profile->user_id)
      ->where('tenant_id', $tenantId)
      ->update(['is_active' => false]);

Step 2: EmployeeService::bulkDelete() 동일 적용

Step 3: 복직 처리 확인
└── 상태를 active로 되돌릴 때 user_tenants.is_active = true도 함께

Phase 3: 인증 미들웨어 수정

Step 1: 현재 인증 흐름 분석
├── 로그인 시 테넌트 선택 로직 확인
└── 테넌트 전환 시 체크 로직 확인

Step 2: user_tenants.is_active 체크 추가
├── 테넌트 접근 시 is_active = false면 거부
└── 에러 메시지: "해당 테넌트에 대한 접근 권한이 없습니다"

4. 의존성 및 실행 순서

4.1 의존성 맵

Phase 1 (MNG forceDelete) ─── 독립 실행 가능 (mng 저장소)
Phase 2 (React 퇴직처리) ──┐
                            ├── Phase 3 적용 시 완전한 차단 (api 저장소)
Phase 3 (switchMyTenant) ──┘
Phase 4 (데이터 정리) ─── Phase 1 이후 실행 권장 (DB)

4.2 권장 실행 순서

순서 Phase 이유
1 Phase 1 + Phase 2 병렬 서로 다른 저장소(mng/api), 독립적
2 Phase 3 Phase 2의 is_active = false 설정을 전환 시 차단 (⚠️ 컨펌 필요)
3 Phase 4 Phase 1 적용 후 orphan 재발 방지 확인 → 기존 orphan 정리

5. 상세 작업 내용

각 Phase 진행 후 이 섹션에 상세 내용 추가


6. 컨펌 대기 목록

# 항목 변경 내용 영향 범위 상태
1 Phase 3 switchMyTenant 테넌트 전환 시 is_active 체크 전체 테넌트 전환 흐름 ⚠️ 컨펌 필요
2 Phase 3 수정 방안 선택 A: SwitchTenantRequest / B: switchMyTenant() 내부 인증 흐름 ⚠️ 선택 필요
3 Phase 2.3 복직 로직 is_active = true 복원 동기화 퇴직→복직 흐름 ⚠️ 확인 필요

7. 변경 이력

날짜 항목 변경 내용 파일 승인
2026-03-12 - 문서 초안 작성 - -
2026-03-13 영향도 분석 4개 Phase 코드 분석 + 위험도 평가 + 실행 순서 결정 - -

8. 참고 문서

  • docs/system/database/tenants.md — 테넌트/사용자 DB 구조
  • docs/rules/employee-api.md — 사원관리 API 규칙
  • docs/system/security-policy.md — 보안 아키텍처 (인증 레이어)
  • docs/dev/standards/quality-checklist.md — 품질 체크리스트

9. 검증 결과

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

9.1 테스트 케이스

시나리오 예상 결과 실제 결과 상태
MNG에서 사용자 영구 삭제 tenant_user_profiles도 삭제됨
React에서 퇴직 처리 user_tenants.is_active = false
퇴직자가 해당 테넌트 접근 시도 접근 거부
퇴직자가 다른 테넌트 접근 정상 접근
복직 처리 후 테넌트 접근 정상 접근

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