package kr.itn.itnhub.seed; import kr.itn.itnhub.contact.Contact; import kr.itn.itnhub.contact.ContactMapper; import kr.itn.itnhub.org.Organization; import kr.itn.itnhub.org.OrganizationMapper; import org.springframework.stereotype.Service; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.support.TransactionTemplate; import java.io.InputStream; import java.util.HashSet; import java.util.List; import java.util.Set; @Service public class SeedService { private final SeedParser parser; private final OrganizationMapper mapper; private final ContactMapper contactMapper; private final TransactionTemplate transactionTemplate; public SeedService(SeedParser parser, OrganizationMapper mapper, ContactMapper contactMapper, PlatformTransactionManager transactionManager) { this.parser = parser; this.mapper = mapper; this.contactMapper = contactMapper; this.transactionTemplate = new TransactionTemplate(transactionManager); } /** * 엑셀 파싱은 DB와 무관하므로 트랜잭션 밖에서 수행한다. 커넥션을 잡아두는 구간은 * 기존 행 조회 + upsert 루프뿐이며, {@link TransactionTemplate}으로 그 구간만 감싼다. * (같은 빈의 @Transactional 메서드를 self-invocation으로 호출하면 프록시를 우회해 * 트랜잭션이 아예 적용되지 않으므로 이 방식을 쓴다.) */ public SeedReport seed(InputStream xlsx) { List rows = parser.parse(xlsx); return transactionTemplate.execute(status -> seedRows(rows)); } private SeedReport seedRows(List rows) { // DB에 이미 있는 키와, 이번 업로드에서 먼저 처리된 키를 모두 "이미 존재함"으로 취급한다. // 그래야 한 시트에 같은 기관이 두 번 들어와도 두 번째는 created가 아니라 updated로 집계된다. Set seen = new HashSet<>(); for (Organization existing : mapper.findAll()) { seen.add(key(existing.getOrgNo(), existing.getOrgName())); } int created = 0; int updated = 0; for (SeedRow row : rows) { if (seen.add(key(row.orgNo(), row.orgName()))) { created++; } else { updated++; } mapper.upsertBySeed(toOrganization(row)); linkApplicantContactIfMissing(row); } return new SeedReport(created, updated, rows.size()); } /** * 시트에 담당자 이름이 있고 그 기관이 아직 신청기관 담당자가 배정되지 않았을 때만 * 담당자를 연결한다. 이미 배정돼 있으면(담당자관리 화면에서 사람이 고쳤을 수도 * 있는 값이므로) 절대 건드리지 않는다 - 재시드가 배정을 되돌리거나 중복 생성하면 안 된다. * *

연결할 담당자는 새로 만들기 전에 (구분, 성명, 전화, 이메일)이 같은 기존 담당자가 * 있는지 먼저 찾는다 - 경찰청 계열 27개 하위기관이 같은 담당자를 공유하는 것처럼, * 여러 기관 행이 동일 인물을 가리키는 경우 재시드 때마다 담당자가 중복 생성되면 안 된다.

*/ private void linkApplicantContactIfMissing(SeedRow row) { if (row.managerName() == null) { return; } Organization saved = mapper.findByOrgNoAndOrgName(row.orgNo(), row.orgName()); if (saved.getApplicantContactId() != null) { return; } Contact contact = contactMapper.findMatching( "APPLICANT", row.managerName(), row.managerPhone(), row.managerEmail()); if (contact == null) { contact = new Contact(); contact.setCategory("APPLICANT"); contact.setName(row.managerName()); contact.setAffiliation(row.orgName()); contact.setDeptName(row.deptName()); contact.setTitle(row.managerTitle()); contact.setPhone(row.managerPhone()); contact.setEmail(row.managerEmail()); contactMapper.insert(contact); } mapper.updateAssignments(saved.getId(), contact.getId(), saved.getMjContactId(), saved.getItnContactId(), saved.getLawyerContactId(), saved.getLawyerAssignedDate()); } private String key(String orgNo, String orgName) { return orgNo + " " + orgName; } private Organization toOrganization(SeedRow row) { Organization org = new Organization(); org.setOrgNo(row.orgNo()); org.setOrgName(row.orgName()); org.setChannelSlug(row.channelSlug()); return org; } }