File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
package kr.itn.itnhub.review;
import kr.itn.itnhub.org.OrgNotFoundException;
import kr.itn.itnhub.org.OrganizationMapper;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.List;
/** 기관관리 상세의 권리확인 탭이 쓰는 목록/상세/수정/삭제/다운로드 서비스. */
@Service
public class ReviewService {
/** 1행 그룹 제목 + 2행 컬럼 헤더. W열은 원본 시트의 빈 열이라 빈 문자열로 남겨둔다. */
private static final String[] COLUMN_HEADERS = {
"순번", "사이트명", "범주", "게시판 경로", "게시판명", "게시물제목", "URL주소",
"게시글 등록", "제작일", "공표일", "첨부파일여부", "기존 공공누리부착", "기존 공공누리유형", "AI유형",
"비고(조사원)", "개방가능여부", "권리확인(대분류)", "권리확인(세부)", "처리결과",
"공공누리유형(판정)", "AI유형(판정)", "의견", "", "비고(변호사)", "권리처리필요"
};
private final ReviewItemMapper mapper;
private final OrganizationMapper orgMapper;
public ReviewService(ReviewItemMapper mapper, OrganizationMapper orgMapper) {
this.mapper = mapper;
this.orgMapper = orgMapper;
}
public ReviewPage list(Long orgId, String keyword, String site, String board, String title,
int page, int size) {
requireOrg(orgId);
String kw = normalizeKeyword(keyword);
String siteFilter = normalizeKeyword(site);
String boardFilter = normalizeKeyword(board);
String titleFilter = normalizeKeyword(title);
int safePage = Math.max(page, 0);
int safeSize = size <= 0 ? 30 : size;
int offset = safePage * safeSize;
List<ReviewItem> items =
mapper.findPage(orgId, kw, siteFilter, boardFilter, titleFilter, offset, safeSize);
int total = mapper.countByOrg(orgId, kw, siteFilter, boardFilter, titleFilter);
int done = mapper.countDone(orgId);
return new ReviewPage(items, total, done, safePage, safeSize);
}
/**
* 목록에서 고른 여러 건에 같은 값을 한 번에 반영한다. 화면에서 비워 둔 항목은 null로 와서
* 기존 값이 유지된다 - "체크한 것 전부 0유형, 의견은 ooo" 식의 일괄 등록 요구를 위한 것이다.
*/
@Transactional
public int updateBulk(Long orgId, BulkJudgmentRequest request) {
requireOrg(orgId);
if (request.ids() == null || request.ids().isEmpty()) {
return 0;
}
return mapper.updateJudgmentBulk(orgId, request.ids(),
blankToNull(request.reviewResult()),
blankToNull(request.judgedKoglType()),
blankToNull(request.opinion()));
}
/** 목록에서 고른 여러 건을 지운다. */
@Transactional
public int deleteBulk(Long orgId, List<Long> ids) {
requireOrg(orgId);
if (ids == null || ids.isEmpty()) {
return 0;
}
return mapper.deleteByIds(orgId, ids);
}
private String blankToNull(String value) {
return value == null || value.isBlank() ? null : value;
}
public ReviewItem get(Long orgId, Long itemId) {
requireOrg(orgId);
return requireItem(orgId, itemId);
}
@Transactional
public ReviewItem updateJudgment(Long orgId, Long itemId, JudgmentRequest request) {
requireOrg(orgId);
int updated = mapper.updateJudgment(orgId, itemId,
request.hasAttachment(), request.koglAttached(), request.koglType(), request.aiType(),
request.openable(), request.reviewMajor(), request.reviewMinor(), request.reviewResult(),
request.judgedKoglType(), request.judgedAiType(), request.opinion(), request.lawyerNote(),
request.needsProcessing());
if (updated == 0) {
throw new ReviewNotFoundException("게시물을 찾을 수 없습니다: " + itemId);
}
return mapper.findById(orgId, itemId);
}
@Transactional
public void delete(Long orgId, Long itemId) {
requireOrg(orgId);
int deleted = mapper.deleteById(orgId, itemId);
if (deleted == 0) {
throw new ReviewNotFoundException("게시물을 찾을 수 없습니다: " + itemId);
}
}
/** 원본 파일과 같은 헤더 배치(1행 그룹제목, 2행 컬럼헤더, 3행부터 데이터)로 xlsx를 만든다. */
public byte[] downloadWorkbook(Long orgId) {
requireOrg(orgId);
List<ReviewItem> items = mapper.findAllByOrg(orgId);
try (XSSFWorkbook wb = new XSSFWorkbook(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
Sheet sheet = wb.createSheet(ReviewParser.PRIMARY_SHEET_NAME);
Row groupRow = sheet.createRow(0);
groupRow.createCell(0).setCellValue("게시물 정보(조사원)");
groupRow.createCell(15).setCellValue("권리확인(변호사)");
Row headerRow = sheet.createRow(1);
for (int i = 0; i < COLUMN_HEADERS.length; i++) {
headerRow.createCell(i).setCellValue(COLUMN_HEADERS[i]);
}
int rowNum = 2;
for (ReviewItem item : items) {
Row row = sheet.createRow(rowNum++);
setCell(row, 0, String.valueOf(item.getSeq()));
setCell(row, 1, item.getSiteName());
setCell(row, 2, item.getCategory());
setCell(row, 3, item.getBoardPath());
setCell(row, 4, item.getBoardName());
setCell(row, 5, item.getPostTitle());
setCell(row, 6, item.getUrl());
setCell(row, 7, item.getPostRegistered());
setCell(row, 8, item.getProducedDate());
setCell(row, 9, item.getPublishedDate());
setCell(row, 10, item.getHasAttachment());
setCell(row, 11, item.getKoglAttached());
setCell(row, 12, item.getKoglType());
setCell(row, 13, item.getAiType());
setCell(row, 14, item.getSurveyorNote());
setCell(row, 15, item.getOpenable());
setCell(row, 16, item.getReviewMajor());
setCell(row, 17, item.getReviewMinor());
setCell(row, 18, item.getReviewResult());
setCell(row, 19, item.getJudgedKoglType());
setCell(row, 20, item.getJudgedAiType());
setCell(row, 21, item.getOpinion());
// 22 = W, 빈 열
setCell(row, 23, item.getLawyerNote());
setCell(row, 24, item.getNeedsProcessing());
}
wb.write(out);
return out.toByteArray();
} catch (IOException e) {
throw new UncheckedIOException("권리확인 엑셀을 만들지 못했습니다.", e);
}
}
private void setCell(Row row, int columnIndex, String value) {
if (value == null) {
return;
}
Cell cell = row.createCell(columnIndex);
cell.setCellValue(value);
}
private String normalizeKeyword(String keyword) {
return (keyword == null || keyword.isBlank()) ? null : keyword.trim();
}
private void requireOrg(Long orgId) {
if (orgMapper.findById(orgId) == null) {
throw new OrgNotFoundException("기관을 찾을 수 없습니다: " + orgId);
}
}
private ReviewItem requireItem(Long orgId, Long itemId) {
ReviewItem item = mapper.findById(orgId, itemId);
if (item == null) {
throw new ReviewNotFoundException("게시물을 찾을 수 없습니다: " + itemId);
}
return item;
}
}