package kr.itn.itnhub.review; 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 ReviewImportService { private final ReviewParser parser; private final ReviewItemMapper mapper; private final OrganizationMapper orgMapper; private final TransactionTemplate transactionTemplate; public ReviewImportService(ReviewParser parser, ReviewItemMapper mapper, OrganizationMapper orgMapper, PlatformTransactionManager transactionManager) { this.parser = parser; this.mapper = mapper; this.orgMapper = orgMapper; this.transactionTemplate = new TransactionTemplate(transactionManager); } /** * 엑셀 파싱은 DB와 무관하므로 트랜잭션 밖에서 수행한다(SeedService와 동일한 이유). */ public ReviewImportReport importFile(Long orgId, InputStream xlsx) { if (orgMapper.findById(orgId) == null) { throw new OrgNotFoundException("기관을 찾을 수 없습니다: " + orgId); } List rows = parser.parse(xlsx); return transactionTemplate.execute(status -> importRows(orgId, rows)); } private ReviewImportReport importRows(Long orgId, List rows) { // 이미 DB에 있는 순번 + 이번 파일에서 먼저 처리된 순번을 모두 "이미 존재함"으로 취급한다 // (SeedService.seedRows와 동일한 관례 - 같은 순번이 파일에 중복돼 있어도 두 번째는 갱신으로 집계). Set seen = new HashSet<>(mapper.findSeqsByOrg(orgId)); int created = 0; int updated = 0; for (ReviewRow row : rows) { if (seen.add(row.seq())) { created++; } else { updated++; } mapper.upsertFromFile(toReviewItem(orgId, row)); } return new ReviewImportReport(created, updated, rows.size()); } private ReviewItem toReviewItem(Long orgId, ReviewRow row) { ReviewItem item = new ReviewItem(); item.setOrgId(orgId); item.setSeq(row.seq()); item.setSiteName(row.siteName()); item.setCategory(row.category()); item.setBoardPath(row.boardPath()); item.setBoardName(row.boardName()); item.setPostTitle(row.postTitle()); item.setUrl(row.url()); item.setPostRegistered(row.postRegistered()); item.setProducedDate(row.producedDate()); item.setPublishedDate(row.publishedDate()); item.setHasAttachment(row.hasAttachment()); item.setKoglAttached(row.koglAttached()); item.setKoglType(row.koglType()); item.setAiType(row.aiType()); item.setSurveyorNote(row.surveyorNote()); item.setOpenable(row.openable()); item.setReviewMajor(row.reviewMajor()); item.setReviewMinor(row.reviewMinor()); item.setReviewResult(row.reviewResult()); item.setJudgedKoglType(row.judgedKoglType()); item.setJudgedAiType(row.judgedAiType()); item.setOpinion(row.opinion()); item.setLawyerNote(row.lawyerNote()); item.setNeedsProcessing(row.needsProcessing()); return item; } }