사용자-사원 삭제 동기화 계획
작성일: 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 테이블을 공유하지만, 삭제 시 동기화가 불완전하여 다음 문제 발생:
- MNG 영구 삭제 시 orphan 발생:
users hard delete → tenant_user_profiles 잔존 → React에 유령 사원 노출
- React 퇴직 처리 시 계정 미차단:
employee_status='resigned'만 변경 → 해당 테넌트에 여전히 로그인 가능
- 멀티테넌트 미고려: 사용자가 여러 테넌트 소속 가능 → 특정 테넌트 퇴직이 전체 시스템에 영향 주면 안 됨
1.2 테이블 관계
1.3 차단 레벨 정리
| 레벨 |
테이블.필드 |
범위 |
용도 |
| 전체 시스템 |
users.is_active |
모든 테넌트 |
계정 완전 정지 |
| 특정 테넌트 |
user_tenants.is_active |
해당 테넌트만 |
퇴직자 접근 차단 |
| HR 상태 |
tenant_user_profiles.employee_status |
해당 테넌트 |
재직/휴직/퇴직 표시 |
1.4 성공 기준
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 |
SwitchTenantRequest에 user_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 수정
Phase 2: React 퇴직 처리 수정
Phase 3: 인증 미들웨어 수정
4. 의존성 및 실행 순서
4.1 의존성 맵
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 스킬로 생성되었습니다.