docs: [plan] 사용자-사원 삭제 동기화 계획 문서 추가
- Phase 1~4 영향도 분석 및 실행 순서 포함 - 전체 Phase 완료 상태
This commit is contained in:
236
dev/dev_plans/user-employee-sync-plan.md
Normal file
236
dev/dev_plans/user-employee-sync-plan.md
Normal file
@@ -0,0 +1,236 @@
|
||||
# 사용자-사원 삭제 동기화 계획
|
||||
|
||||
> **작성일**: 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 | `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 수정
|
||||
|
||||
```
|
||||
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 스킬로 생성되었습니다.*
|
||||
Reference in New Issue
Block a user