{"id":"cmq4r7ygh000xo601eopamcsq","docId":"cmq4p3x7o006fpl01m3vltljr","version":1,"title":"Phase E 데이터 마이그 운영가이드","content":"# Phase E — 데이터 마이그 운영 가이드\n\n작성: 2026-06-06 KST · 좁힘 ETL (협력사 + 계약 + 지점)\n\n## 0. 사전 조건\n\n1. 신모델 DB (irosafe_shred) 가 V1~V6 Flyway migration 완료 상태.\n2. legacy ILSA DB 에 read-only 계정 발급 — `SELECT` 권한만.\n3. 두 DB 간 네트워크 연결 가능 (또는 ETL 서버에서 양쪽 reachable).\n\n## 1. 실행 모드\n\n| 모드 | 목적 | 결과 |\n|---|---|---|\n| `dry-run` | 검증. 전체 row 처리하지만 트랜잭션 rollback. | 로그에 count + 매핑 실패 출력 |\n| `apply` | 실제 commit. 1회 실행. | `INSERT IGNORE` 로 idempotent 보장 |\n\n## 2. 실행 명령\n\n```bash\n# dry-run\njava -jar shred-backend.jar \\\n  --spring.profiles.active=migrate \\\n  --migration.legacy.url='jdbc:mariadb://system1472.sldb.iwinv.net:3306/ILSA_SHREDDING?useSSL=false' \\\n  --migration.legacy.user=READONLY \\\n  --migration.legacy.password='***' \\\n  --migration.mode=dry-run\n\n# apply\njava -jar shred-backend.jar \\\n  --spring.profiles.active=migrate \\\n  --migration.legacy.url='jdbc:mariadb://...' \\\n  --migration.legacy.user=READONLY \\\n  --migration.legacy.password='***' \\\n  --migration.mode=apply\n```\n\n## 3. 마이그 대상 (좁힘)\n\n1. **협력사** — `SSF_SUBCONTRACTOR_MANAGEMENT` → `subcontractor`\n   - 자연키: `code = SUBCONTRACTOR_ID`, `business_number = normalized BUSINESS_NUMBER`\n   - `legacy_subcontractor_seq = SUBCONTRACTOR_SEQ` 보존\n   - 상태 매핑: A/ACTIVE → ACTIVE, S → SUSPENDED, C → CLOSED\n2. **계약** — `SSF_CONTRACT_MANAGEMENT` → `contract`\n   - 트랙 결정: `IS_ANNUAL='Y'` → RECURRING, else ONE_CALL\n   - 이름 prefix: `[정기] ` / `[원콜] `\n   - `legacy_contract_id = CONTRACT_ID`\n   - 증명서 매핑: C1210000~4 → YEARLY/ONE_TIME/COWAY/AH/HDD\n3. **지점** — `SSF_BRANCH_OFFICE_MANAGEMENT` → `branch_office`\n   - `contract_id` FK: legacy CONTRACT_ID → 신규 contract.id 조회\n   - `subcontractor_id` FK: legacy SUBCONTRACTOR_ID → 신규 subcontractor.id 조회\n   - `legacy_branch_office_id = BRANCH_OFFICE_ID`\n\n## 4. 검증 SQL (apply 후)\n\n```sql\n-- count 비교\nSELECT 'legacy.subcontractor' src, COUNT(*) cnt FROM legacy_link.SSF_SUBCONTRACTOR_MANAGEMENT\nUNION SELECT 'new.subcontractor', COUNT(*) FROM irosafe_shred.subcontractor\nUNION SELECT 'legacy.contract', COUNT(*) FROM legacy_link.SSF_CONTRACT_MANAGEMENT\nUNION SELECT 'new.contract', COUNT(*) FROM irosafe_shred.contract\nUNION SELECT 'legacy.branch', COUNT(*) FROM legacy_link.SSF_BRANCH_OFFICE_MANAGEMENT\nUNION SELECT 'new.branch', COUNT(*) FROM irosafe_shred.branch_office;\n\n-- 누락 detect — 신모델에 옮겨지지 않은 leg row\nSELECT l.SUBCONTRACTOR_ID, l.SUBCONTRACTOR_NAME\nFROM legacy_link.SSF_SUBCONTRACTOR_MANAGEMENT l\nLEFT JOIN irosafe_shred.subcontractor n\n  ON n.legacy_subcontractor_seq = l.SUBCONTRACTOR_SEQ\nWHERE n.id IS NULL;\n\n-- 계약 ↔ 지점 FK 매칭률\nSELECT\n  COUNT(*) AS total_branches,\n  SUM(CASE WHEN contract_id IS NOT NULL THEN 1 ELSE 0 END) AS branches_with_contract,\n  SUM(CASE WHEN subcontractor_id IS NOT NULL THEN 1 ELSE 0 END) AS branches_with_partner\nFROM irosafe_shred.branch_office;\n```\n\n## 5. 매핑 실패 처리\n\n- legacy row 의 `CONTRACT_ID` 가 신규 contract 에 없는 경우 → `branch_office.contract_id = NULL`\n- `SUBCONTRACTOR_ID` 매칭 실패 → `branch_office.subcontractor_id = NULL`\n- 운영자 staging 검토 (TODO: migration_legacy_ref 테이블 — Phase E2)\n\n## 6. 롤백 시나리오\n\napply 후 문제 발견 시:\n1. Flyway 로 새 migration V99__rollback_phaseE.sql 추가\n2. `DELETE FROM contract WHERE legacy_contract_id IS NOT NULL`\n3. 동일 패턴으로 branch_office, subcontractor\n4. 또는 신모델 DB 전체 drop & V1~V6 재적용 후 다시 apply\n\n## 7. MVP 외 (Phase E2 — 7월)\n\n- 사용자(member), 콘솔/바코드, 증명서, 작업 이력 풀 마이그\n- migration_legacy_ref 운영자 staging 검토 테이블\n- 잔여 컬럼 (created_by, updated_by) 사용자 매핑 후 backfill\n","sourceHash":null,"archivedAt":"2026-06-08T14:12:16+09:00","archivedBy":"sync"}