From 4e8e6b8423a74a1dc930ccc074471900a8d76ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Fri, 13 Mar 2026 00:30:36 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20[plan]=20=EC=82=AC=EC=9A=A9=EC=9E=90-?= =?UTF-8?q?=EC=82=AC=EC=9B=90=20=EC=82=AD=EC=A0=9C=20=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EA=B3=84=ED=9A=8D=20=EB=AC=B8=EC=84=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Phase 1~4 영향도 분석 및 실행 순서 포함 - 전체 Phase 완료 상태 --- dev/dev_plans/user-employee-sync-plan.md | 236 +++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 dev/dev_plans/user-employee-sync-plan.md diff --git a/dev/dev_plans/user-employee-sync-plan.md b/dev/dev_plans/user-employee-sync-plan.md new file mode 100644 index 0000000..4575972 --- /dev/null +++ b/dev/dev_plans/user-employee-sync-plan.md @@ -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 스킬로 생성되었습니다.* \ No newline at end of file