기관에 박혀 있던 담당자 3종(신청기관/문정원/변호사) 컬럼을 contact 테이블로 옮기고,
기관은 담당자 id만 배정으로 참조하게 했다. 신청기관 담당자 배정 여부가 채널 생성
가능(READY) 판정 기준이 되도록 상태 로직도 함께 바꿨다.
- V4 마이그레이션: contact 테이블 생성 + 기존 임베디드 컬럼 백필 후 제거
- kr.itn.itnhub.contact 패키지: 담당자 CRUD API (GET/POST/PUT/DELETE /api/contacts)
- PUT /api/orgs/{id}/assignments가 기존 /contact 엔드포인트를 대체 (구분 불일치 400)
- SeedService: 시트의 담당자명은 신청기관 담당자가 아직 없을 때만 새로 만들어 연결
- ChannelProvisionService 게이트를 hasApplicantAssigned()로 교체
- findChannelIdByDisplayName이 /users/me/teams/{id}/channels(토큰 사용자가 가입한
채널만) 대신 /teams/{id}/channels를 페이지네이션(page/per_page=200, 상한 100페이지)
하며 표시명을 찾도록 변경. 레거시 채널에 토큰 사용자가 가입하지 않은 경우 생기던
중복 채널 생성 위험을 제거한다(system-admin PAT 필요, README에 명시).
- 존재하지 않는 기관 id에 대한 404를 폭넓은 IllegalArgumentException 대신 전용
OrgNotFoundException으로 좁혀서 매핑.
- 검증 실패(@Valid) 응답을 원시 필드명이 담긴 JSON 대신 한글 라벨 메시지로 변환.
- SeedController 업로드 스트림 읽기 실패를 SeedParseException(400)으로 통일.
Co-Authored-By: Claude Opus 4.8 (1M context)
- GlobalExceptionHandler(@RestControllerAdvice) 추가: IllegalArgumentException은
404, SeedParseException은 400으로 매핑하고 예외 메시지를 ApiError로 그대로
돌려준다. Exception 전체를 잡는 catch-all은 두지 않아 예상 밖의 진짜 버그는
여전히 500으로 드러나게 한다.
- SeedController 업로드에 확장자(.xlsx/.xlsm) 검사를 추가해 임의의 바이트가
Apache POI로 그대로 전달되기 전에 SeedParseException으로 걸러낸다.
- POST /api/orgs/{id}/channels의 실제 혼합 성공/실패 결과가 200으로 내려가는
경로를 컨트롤러 경계에서 검증하는 테스트를 추가하고, 담당자 정보 누락
테스트의 law 단정을 보강했다.
같은 시트에 동일 기관이 두 번 들어오면 DB는 upsert로 한 행에 합치지만
집계는 둘 다 created로 세어 report가 실제 결과와 어긋났다. 처리 완료된
키를 그 자리에서 seen 집합에 반영해 두 번째 등장부터는 updated로 잡히게
했다.
또한 seed()가 엑셀 파싱까지 @Transactional 안에서 수행해 파싱 중에도
DB 커넥션을 점유하고 있었다. 파싱은 트랜잭션 밖으로 빼고, DB 조회+upsert
구간만 TransactionTemplate으로 감싸 커넥션 점유 구간을 최소화했다.
(같은 빈 self-invocation으로 @Transactional을 추가하면 프록시를 우회해
트랜잭션이 걸리지 않으므로 이 방식을 택함.)
중복 기관 업로드 시나리오에 대한 테스트를 추가했다.
- 연번1 등 NUMERIC 셀을 (long) 캐스팅해 절삭하던 것을 Math.round로 교체.
부동소수점 오차(예: 6.999999999999998)가 실제 값보다 한 자리 작은
기관번호("006")로 읽혀 채널명이 잘못 생성되는 문제를 막는다.
- FORMULA 셀은 default 분기로 빠져 항상 빈 문자열로 읽히던 것을 고쳐,
캐시된 수식 결과 타입(getCachedFormulaResultType)에 따라 STRING/NUMERIC
처리 로직을 재사용하도록 cellValue(Cell, CellType) 재귀 구조로 정리.
- 두 결함을 각각 재현하는 테스트 추가(부동소수점 절삭, 수식 셀 캐시값 읽기).
Co-Authored-By: Claude Opus 4.8 (1M context)