package kr.itn.itnhub.process; import kr.itn.itnhub.org.OrgNotFoundException; 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 ProcessImportService { private final ProcessParser parser; private final ProcessItemMapper mapper; private final OrganizationMapper orgMapper; private final kr.itn.itnhub.stage.StageService stageService; private final kr.itn.itnhub.code.CodeService codeService; private final TransactionTemplate transactionTemplate; public ProcessImportService(ProcessParser parser, ProcessItemMapper mapper, OrganizationMapper orgMapper, kr.itn.itnhub.stage.StageService stageService, kr.itn.itnhub.code.CodeService codeService, PlatformTransactionManager transactionManager) { this.parser = parser; this.mapper = mapper; this.orgMapper = orgMapper; this.stageService = stageService; this.codeService = codeService; this.transactionTemplate = new TransactionTemplate(transactionManager); } /** 공공누리 유형의 옛 표기를 표준 표기로 맞춘다(제4유형 → 4유형). */ private String canonicalKogl(String value) { return codeService.canonical(kr.itn.itnhub.code.Codes.KOGL_TYPE, value); } /** * 엑셀 파싱은 DB와 무관하므로 트랜잭션 밖에서 수행한다(ReviewImportService와 동일한 이유). */ public ProcessImportReport importFile(Long orgId, InputStream xlsx) { if (orgMapper.findById(orgId) == null) { throw new OrgNotFoundException("기관을 찾을 수 없습니다: " + orgId); } List rows = parser.parse(xlsx); ProcessImportReport report = transactionTemplate.execute(status -> importRows(orgId, rows)); // 요구사항 [18]: 권리처리 자료가 올라오면 ⑧법률검토(권리처리)로 저절로 넘어간다. // 이미 그보다 앞서 있으면 아무 일도 하지 않는다. if (report != null && report.total() > 0) { stageService.autoAdvance(orgId, kr.itn.itnhub.stage.AutoStage.PROCESS_STARTED, kr.itn.itnhub.stage.AutoStage.REASON_PROCESS_STARTED); } return report; } private ProcessImportReport importRows(Long orgId, List rows) { // 이미 DB에 있는 순번 + 이번 파일에서 먼저 처리된 순번을 모두 "이미 존재함"으로 취급한다 // (ReviewImportService.importRows와 동일한 관례). Set seen = new HashSet<>(mapper.findSeqsByOrg(orgId)); int created = 0; int updated = 0; for (ProcessRow row : rows) { if (seen.add(row.seq())) { created++; } else { updated++; } mapper.upsertFromFile(toProcessItem(orgId, row)); } return new ProcessImportReport(created, updated, rows.size()); } private ProcessItem toProcessItem(Long orgId, ProcessRow row) { ProcessItem item = new ProcessItem(); item.setOrgId(orgId); item.setSeq(row.seq()); item.setSiteName(row.siteName()); item.setCategory(row.category()); item.setBoardName(row.boardName()); item.setPostTitle(row.postTitle()); item.setUrl(row.url()); item.setDescription(row.description()); item.setHasAttachment(row.hasAttachment()); // 옛 표기(제4유형)로 적혀 온 값은 표준 표기(4유형)로 맞춰 넣는다. item.setPriorKoglType(canonicalKogl(row.priorKoglType())); item.setContractDocs(row.contractDocs()); item.setProducedDate(row.producedDate()); item.setPublishedDate(row.publishedDate()); item.setReviewMajor(row.reviewMajor()); item.setReviewMinor(row.reviewMinor()); item.setReviewResult(row.reviewResult()); item.setReviewKoglType(canonicalKogl(row.reviewKoglType())); item.setReviewAiType(row.reviewAiType()); item.setReviewOpinion(row.reviewOpinion()); item.setReviewNote(row.reviewNote()); item.setPriorEvidence(row.priorEvidence()); item.setJudgedKoglType(canonicalKogl(row.judgedKoglType())); item.setJudgedAiType(row.judgedAiType()); item.setFinalOpinion(row.finalOpinion()); item.setJudgmentBasis(row.judgmentBasis()); item.setProcessStatus(row.processStatus()); return item; } }