refactor: 쿼리와 자바에 박혀 있던 처리완료 문자열 제거
SQL 5곳(ProcessItemMapper 4 + DashboardMapper 1)과 자바 화이트리스트 1곳에 '처리완료'가 남아 있었다. 쿼리는 코드표를 참조할 방법이 없어 서비스가 값을 읽어 파라미터로 넘긴다. 처리상태 검증도 코드표를 따른다. 상태를 하나 더 만들어도 자바를 안 고쳐도 된다. 지정이 없을 때는 빈 문자열이 아니라 매칭되지 않는 값을 넘긴다. 빈 문자열을 주면 처리상태가 비어 있는 행이 전부 완료로 잡힌다. 파라미터화가 형식만 바뀐 게 아님을 확인하는 테스트를 넣었다. 코드표에서 done 속성을 '미처리' 쪽으로 옮기면 진행률과 상태 필터가 실제로 뒤집힌다 - 예전 구조에서는 코드표를 바꿔도 아무 일도 일어나지 않았다. 이로써 조사에서 세었던 13곳이 모두 정리됐다. 백엔드 266건 통과. Co-Authored-By: Claude Opus 5 (1M context)
@0f508acacd8ecd5a7d5ebc936e9082cf4098e191
--- src/main/java/kr/itn/itnhub/dashboard/DashboardMapper.java
+++ src/main/java/kr/itn/itnhub/dashboard/DashboardMapper.java
... | ... | @@ -1,12 +1,18 @@ |
| 1 | 1 |
package kr.itn.itnhub.dashboard; |
| 2 | 2 |
|
| 3 | 3 |
import org.apache.ibatis.annotations.Mapper; |
| 4 |
+import org.apache.ibatis.annotations.Param; |
|
| 4 | 5 |
|
| 5 | 6 |
import java.util.List; |
| 6 | 7 |
|
| 7 | 8 |
@Mapper |
| 8 | 9 |
public interface DashboardMapper {
|
| 9 | 10 |
|
| 10 |
- /** 기관 전체를 연번순으로, 권리확인/권리처리 집계와 단계·최근변동 시각까지 붙여 한 번에 읽는다. */ |
|
| 11 |
- List<DashboardOrgRow> findOrgRows(); |
|
| 11 |
+ /** |
|
| 12 |
+ * 기관 전체를 연번순으로, 권리확인/권리처리 집계와 단계·최근변동 시각까지 붙여 한 번에 읽는다. |
|
| 13 |
+ * |
|
| 14 |
+ * @param doneStatus 권리처리 완료로 셀 상태값. 코드표에서 와야 하는데 SQL이 코드표를 |
|
| 15 |
+ * 참조할 방법이 없어 서비스가 읽어 넘긴다. |
|
| 16 |
+ */ |
|
| 17 |
+ List<DashboardOrgRow> findOrgRows(@Param("doneStatus") String doneStatus);
|
|
| 12 | 18 |
} |
--- src/main/java/kr/itn/itnhub/dashboard/DashboardService.java
+++ src/main/java/kr/itn/itnhub/dashboard/DashboardService.java
... | ... | @@ -42,10 +42,22 @@ |
| 42 | 42 |
} |
| 43 | 43 |
|
| 44 | 44 |
public DashboardResponse dashboard() {
|
| 45 |
- List<DashboardOrgRow> rows = mapper.findOrgRows(); |
|
| 45 |
+ List<DashboardOrgRow> rows = mapper.findOrgRows(doneStatus()); |
|
| 46 | 46 |
return new DashboardResponse(summarize(rows), rows); |
| 47 | 47 |
} |
| 48 | 48 |
|
| 49 |
+ /** |
|
| 50 |
+ * 권리처리 완료로 셀 상태값. 지정이 없으면 어떤 행도 완료로 세지 않도록 매칭되지 않는 |
|
| 51 |
+ * 값을 준다 - 빈 문자열을 주면 처리상태가 비어 있는 행이 전부 완료로 잡힌다. |
|
| 52 |
+ */ |
|
| 53 |
+ private String doneStatus() {
|
|
| 54 |
+ return codeService.group(kr.itn.itnhub.code.Codes.PROCESS_STATUS).stream() |
|
| 55 |
+ .filter(code -> code.flag(kr.itn.itnhub.code.Codes.ATTR_DONE)) |
|
| 56 |
+ .map(kr.itn.itnhub.code.Code::code) |
|
| 57 |
+ .findFirst() |
|
| 58 |
+ .orElse(" ");
|
|
| 59 |
+ } |
|
| 60 |
+ |
|
| 49 | 61 |
DashboardSummary summarize(List<DashboardOrgRow> rows) {
|
| 50 | 62 |
int docSubmittedFrom = thresholdOf(KPI_DOC_SUBMITTED); |
| 51 | 63 |
int completedFrom = thresholdOf(KPI_COMPLETED); |
--- src/main/java/kr/itn/itnhub/process/ProcessItemMapper.java
+++ src/main/java/kr/itn/itnhub/process/ProcessItemMapper.java
... | ... | @@ -5,6 +5,13 @@ |
| 5 | 5 |
|
| 6 | 6 |
import java.util.List; |
| 7 | 7 |
|
| 8 |
+/** |
|
| 9 |
+ * 권리처리 매퍼. |
|
| 10 |
+ * |
|
| 11 |
+ * <p>어느 값이 "처리완료"인지는 쿼리에 박지 않고 {@code doneStatus} 파라미터로 받는다.
|
|
| 12 |
+ * 코드값은 코드표에서 오는데 SQL 안에는 코드표를 참조할 방법이 마땅치 않아, 서비스가 |
|
| 13 |
+ * 읽어 넘긴다. 이 값이 틀리면 진행률과 처리일시가 조용히 어긋나므로 항상 함께 넘겨야 한다.</p> |
|
| 14 |
+ */ |
|
| 8 | 15 |
@Mapper |
| 9 | 16 |
public interface ProcessItemMapper {
|
| 10 | 17 |
|
... | ... | @@ -15,19 +22,23 @@ |
| 15 | 22 |
List<ProcessItem> findPage(@Param("orgId") Long orgId, @Param("keyword") String keyword,
|
| 16 | 23 |
@Param("site") String site, @Param("board") String board,
|
| 17 | 24 |
@Param("title") String title,
|
| 18 |
- @Param("status") String status, @Param("offset") int offset,
|
|
| 25 |
+ @Param("status") String status,
|
|
| 26 |
+ @Param("doneStatus") String doneStatus,
|
|
| 27 |
+ @Param("offset") int offset,
|
|
| 19 | 28 |
@Param("limit") int limit);
|
| 20 | 29 |
|
| 21 | 30 |
/** keyword/status로 필터링된 건수 - 필터별 페이지 경계 계산용(현재 응답의 total과는 별개). */ |
| 22 | 31 |
int countByOrg(@Param("orgId") Long orgId, @Param("keyword") String keyword,
|
| 23 | 32 |
@Param("site") String site, @Param("board") String board,
|
| 24 |
- @Param("title") String title, @Param("status") String status);
|
|
| 33 |
+ @Param("title") String title, @Param("status") String status,
|
|
| 34 |
+ @Param("doneStatus") String doneStatus);
|
|
| 25 | 35 |
|
| 26 | 36 |
/** 여러 건에 같은 값을 한 번에 반영한다(일괄 등록). null 항목은 기존 값을 유지한다. */ |
| 27 | 37 |
int updateProcessingBulk(@Param("orgId") Long orgId, @Param("ids") List<Long> ids,
|
| 28 | 38 |
@Param("processStatus") String processStatus,
|
| 29 | 39 |
@Param("judgedKoglType") String judgedKoglType,
|
| 30 |
- @Param("finalOpinion") String finalOpinion);
|
|
| 40 |
+ @Param("finalOpinion") String finalOpinion,
|
|
| 41 |
+ @Param("doneStatus") String doneStatus);
|
|
| 31 | 42 |
|
| 32 | 43 |
/** 일괄 삭제. */ |
| 33 | 44 |
int deleteByIds(@Param("orgId") Long orgId, @Param("ids") List<Long> ids);
|
... | ... | @@ -54,7 +65,8 @@ |
| 54 | 65 |
@Param("judgedAiType") String judgedAiType,
|
| 55 | 66 |
@Param("finalOpinion") String finalOpinion,
|
| 56 | 67 |
@Param("judgmentBasis") String judgmentBasis,
|
| 57 |
- @Param("processStatus") String processStatus);
|
|
| 68 |
+ @Param("processStatus") String processStatus,
|
|
| 69 |
+ @Param("doneStatus") String doneStatus);
|
|
| 58 | 70 |
|
| 59 | 71 |
int deleteById(@Param("orgId") Long orgId, @Param("id") Long id);
|
| 60 | 72 |
|
... | ... | @@ -62,5 +74,5 @@ |
| 62 | 74 |
int deleteByOrg(@Param("orgId") Long orgId);
|
| 63 | 75 |
|
| 64 | 76 |
/** 검색어/상태 필터와 무관한 기관 전체 진행률 - 목록 헤더와 기관 상세 통계 카드가 함께 쓴다. */ |
| 65 |
- ProcessStats stats(@Param("orgId") Long orgId);
|
|
| 77 |
+ ProcessStats stats(@Param("orgId") Long orgId, @Param("doneStatus") String doneStatus);
|
|
| 66 | 78 |
} |
--- src/main/java/kr/itn/itnhub/process/ProcessService.java
+++ src/main/java/kr/itn/itnhub/process/ProcessService.java
... | ... | @@ -1,208 +0,0 @@ |
| 1 | -package kr.itn.itnhub.process; | |
| 2 | - | |
| 3 | -import kr.itn.itnhub.org.OrgNotFoundException; | |
| 4 | -import kr.itn.itnhub.org.OrganizationMapper; | |
| 5 | -import org.apache.poi.ss.usermodel.Cell; | |
| 6 | -import org.apache.poi.ss.usermodel.Row; | |
| 7 | -import org.apache.poi.ss.usermodel.Sheet; | |
| 8 | -import org.apache.poi.xssf.usermodel.XSSFWorkbook; | |
| 9 | -import org.springframework.stereotype.Service; | |
| 10 | -import org.springframework.transaction.annotation.Transactional; | |
| 11 | - | |
| 12 | -import java.io.ByteArrayOutputStream; | |
| 13 | -import java.io.IOException; | |
| 14 | -import java.io.UncheckedIOException; | |
| 15 | -import java.util.List; | |
| 16 | -import java.util.Set; | |
| 17 | - | |
| 18 | -/** 기관관리 상세의 권리처리 탭이 쓰는 목록/상세/수정/삭제/다운로드 서비스. */ | |
| 19 | -@Service | |
| 20 | -public class ProcessService { | |
| 21 | - | |
| 22 | - private static final Set<String> VALID_PROCESS_STATUSES = Set.of("미처리", "처리완료"); | |
| 23 | - | |
| 24 | - /** | |
| 25 | - * 1행 그룹 제목(A=게시물 정보(조사원), M=권리확인(변호사), U=권리처리(변호사)) + 2행 컬럼 헤더. | |
| 26 | - * U열(수정 링크)과 Y열은 원본 시트의 건너뛰는 열이라 빈 문자열로 남겨둔다. | |
| 27 | - */ | |
| 28 | - private static final String[] COLUMN_HEADERS = { | |
| 29 | - "순번", "사이트명", "범주", "게시판명", "게시물제목", "URL주소", "설명", | |
| 30 | - "첨부파일여부", "기존 공공누리유형(+AI)", "계약서 유무", "제작일", "공표일", | |
| 31 | - "권리확인(대분류)", "권리확인(세부)", "처리 구분", "공공누리 유형", "AI유형", "의견", "비고", "기존 증빙자료", | |
| 32 | - "", "공공누리유형(판정)", "AI유형(판정)", "최종의견", "", "판단근거", "처리상태" | |
| 33 | - }; | |
| 34 | - | |
| 35 | - private final ProcessItemMapper mapper; | |
| 36 | - private final OrganizationMapper orgMapper; | |
| 37 | - | |
| 38 | - public ProcessService(ProcessItemMapper mapper, OrganizationMapper orgMapper) { | |
| 39 | - this.mapper = mapper; | |
| 40 | - this.orgMapper = orgMapper; | |
| 41 | - } | |
| 42 | - | |
| 43 | - public ProcessPage list(Long orgId, String keyword, String site, String board, String title, | |
| 44 | - String status, int page, int size) { | |
| 45 | - requireOrg(orgId); | |
| 46 | - String kw = normalizeKeyword(keyword); | |
| 47 | - String siteFilter = normalizeKeyword(site); | |
| 48 | - String boardFilter = normalizeKeyword(board); | |
| 49 | - String titleFilter = normalizeKeyword(title); | |
| 50 | - String st = normalizeStatus(status); | |
| 51 | - int safePage = Math.max(page, 0); | |
| 52 | - int safeSize = size <= 0 ? 30 : size; | |
| 53 | - int offset = safePage * safeSize; | |
| 54 | - | |
| 55 | - List<ProcessItem> items = | |
| 56 | - mapper.findPage(orgId, kw, siteFilter, boardFilter, titleFilter, st, offset, safeSize); | |
| 57 | - // total/done은 검색어·상태 필터와 무관한 기관 전체 진행률이다(헤더/통계 카드용 - ProcessPage 참고). | |
| 58 | - ProcessStats stats = mapper.stats(orgId); | |
| 59 | - return new ProcessPage(items, stats.total(), stats.done(), safePage, safeSize); | |
| 60 | - } | |
| 61 | - | |
| 62 | - /** 목록에서 고른 여러 건에 같은 값을 한 번에 반영한다. 화면에서 비워 둔 항목은 유지된다. */ | |
| 63 | - @Transactional | |
| 64 | - public int updateBulk(Long orgId, BulkProcessingRequest request) { | |
| 65 | - requireOrg(orgId); | |
| 66 | - if (request.ids() == null || request.ids().isEmpty()) { | |
| 67 | - return 0; | |
| 68 | - } | |
| 69 | - return mapper.updateProcessingBulk(orgId, request.ids(), | |
| 70 | - blankToNull(request.processStatus()), | |
| 71 | - blankToNull(request.judgedKoglType()), | |
| 72 | - blankToNull(request.finalOpinion())); | |
| 73 | - } | |
| 74 | - | |
| 75 | - /** 목록에서 고른 여러 건을 지운다. */ | |
| 76 | - @Transactional | |
| 77 | - public int deleteBulk(Long orgId, List<Long> ids) { | |
| 78 | - requireOrg(orgId); | |
| 79 | - if (ids == null || ids.isEmpty()) { | |
| 80 | - return 0; | |
| 81 | - } | |
| 82 | - return mapper.deleteByIds(orgId, ids); | |
| 83 | - } | |
| 84 | - | |
| 85 | - private String blankToNull(String value) { | |
| 86 | - return value == null || value.isBlank() ? null : value; | |
| 87 | - } | |
| 88 | - | |
| 89 | - public ProcessItem get(Long orgId, Long itemId) { | |
| 90 | - requireOrg(orgId); | |
| 91 | - return requireItem(orgId, itemId); | |
| 92 | - } | |
| 93 | - | |
| 94 | - @Transactional | |
| 95 | - public ProcessItem updateProcessing(Long orgId, Long itemId, ProcessingRequest request) { | |
| 96 | - requireOrg(orgId); | |
| 97 | - validateProcessStatus(request.processStatus()); | |
| 98 | - int updated = mapper.updateProcessing(orgId, itemId, | |
| 99 | - request.contractDocs(), request.judgedKoglType(), request.judgedAiType(), | |
| 100 | - request.finalOpinion(), request.judgmentBasis(), request.processStatus()); | |
| 101 | - if (updated == 0) { | |
| 102 | - throw new ProcessNotFoundException("게시물을 찾을 수 없습니다: " + itemId); | |
| 103 | - } | |
| 104 | - return mapper.findById(orgId, itemId); | |
| 105 | - } | |
| 106 | - | |
| 107 | - @Transactional | |
| 108 | - public void delete(Long orgId, Long itemId) { | |
| 109 | - requireOrg(orgId); | |
| 110 | - int deleted = mapper.deleteById(orgId, itemId); | |
| 111 | - if (deleted == 0) { | |
| 112 | - throw new ProcessNotFoundException("게시물을 찾을 수 없습니다: " + itemId); | |
| 113 | - } | |
| 114 | - } | |
| 115 | - | |
| 116 | - /** 원본 파일과 같은 헤더 배치(1행 그룹제목, 2행 컬럼헤더, 3행부터 데이터)로 xlsx를 만든다. */ | |
| 117 | - public byte[] downloadWorkbook(Long orgId) { | |
| 118 | - requireOrg(orgId); | |
| 119 | - List<ProcessItem> items = mapper.findAllByOrg(orgId); | |
| 120 | - | |
| 121 | - try (XSSFWorkbook wb = new XSSFWorkbook(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { | |
| 122 | - Sheet sheet = wb.createSheet(ProcessParser.PRIMARY_SHEET_NAME); | |
| 123 | - | |
| 124 | - Row groupRow = sheet.createRow(0); | |
| 125 | - groupRow.createCell(0).setCellValue("게시물 정보(조사원)"); | |
| 126 | - groupRow.createCell(12).setCellValue("권리확인(변호사)"); | |
| 127 | - groupRow.createCell(20).setCellValue("권리처리(변호사)"); | |
| 128 | - | |
| 129 | - Row headerRow = sheet.createRow(1); | |
| 130 | - for (int i = 0; i < COLUMN_HEADERS.length; i++) { | |
| 131 | - headerRow.createCell(i).setCellValue(COLUMN_HEADERS[i]); | |
| 132 | - } | |
| 133 | - | |
| 134 | - int rowNum = 2; | |
| 135 | - for (ProcessItem item : items) { | |
| 136 | - Row row = sheet.createRow(rowNum++); | |
| 137 | - setCell(row, 0, String.valueOf(item.getSeq())); | |
| 138 | - setCell(row, 1, item.getSiteName()); | |
| 139 | - setCell(row, 2, item.getCategory()); | |
| 140 | - setCell(row, 3, item.getBoardName()); | |
| 141 | - setCell(row, 4, item.getPostTitle()); | |
| 142 | - setCell(row, 5, item.getUrl()); | |
| 143 | - setCell(row, 6, item.getDescription()); | |
| 144 | - setCell(row, 7, item.getHasAttachment()); | |
| 145 | - setCell(row, 8, item.getPriorKoglType()); | |
| 146 | - setCell(row, 9, item.getContractDocs()); | |
| 147 | - setCell(row, 10, item.getProducedDate()); | |
| 148 | - setCell(row, 11, item.getPublishedDate()); | |
| 149 | - setCell(row, 12, item.getReviewMajor()); | |
| 150 | - setCell(row, 13, item.getReviewMinor()); | |
| 151 | - setCell(row, 14, item.getReviewResult()); | |
| 152 | - setCell(row, 15, item.getReviewKoglType()); | |
| 153 | - setCell(row, 16, item.getReviewAiType()); | |
| 154 | - setCell(row, 17, item.getReviewOpinion()); | |
| 155 | - setCell(row, 18, item.getReviewNote()); | |
| 156 | - setCell(row, 19, item.getPriorEvidence()); | |
| 157 | - // 20 = U, 수정 링크 열, 건너뜀 | |
| 158 | - setCell(row, 21, item.getJudgedKoglType()); | |
| 159 | - setCell(row, 22, item.getJudgedAiType()); | |
| 160 | - setCell(row, 23, item.getFinalOpinion()); | |
| 161 | - // 24 = Y, 빈 열 | |
| 162 | - setCell(row, 25, item.getJudgmentBasis()); | |
| 163 | - setCell(row, 26, item.getProcessStatus()); | |
| 164 | - } | |
| 165 | - | |
| 166 | - wb.write(out); | |
| 167 | - return out.toByteArray(); | |
| 168 | - } catch (IOException e) { | |
| 169 | - throw new UncheckedIOException("권리처리 엑셀을 만들지 못했습니다.", e); | |
| 170 | - } | |
| 171 | - } | |
| 172 | - | |
| 173 | - private void setCell(Row row, int columnIndex, String value) { | |
| 174 | - if (value == null) { | |
| 175 | - return; | |
| 176 | - } | |
| 177 | - Cell cell = row.createCell(columnIndex); | |
| 178 | - cell.setCellValue(value); | |
| 179 | - } | |
| 180 | - | |
| 181 | - private void validateProcessStatus(String processStatus) { | |
| 182 | - if (processStatus != null && !VALID_PROCESS_STATUSES.contains(processStatus)) { | |
| 183 | - throw new InvalidProcessStatusException("처리상태는 미처리 또는 처리완료여야 합니다."); | |
| 184 | - } | |
| 185 | - } | |
| 186 | - | |
| 187 | - private String normalizeKeyword(String keyword) { | |
| 188 | - return (keyword == null || keyword.isBlank()) ? null : keyword.trim(); | |
| 189 | - } | |
| 190 | - | |
| 191 | - private String normalizeStatus(String status) { | |
| 192 | - return (status == null || status.isBlank()) ? null : status.trim(); | |
| 193 | - } | |
| 194 | - | |
| 195 | - private void requireOrg(Long orgId) { | |
| 196 | - if (orgMapper.findById(orgId) == null) { | |
| 197 | - throw new OrgNotFoundException("기관을 찾을 수 없습니다: " + orgId); | |
| 198 | - } | |
| 199 | - } | |
| 200 | - | |
| 201 | - private ProcessItem requireItem(Long orgId, Long itemId) { | |
| 202 | - ProcessItem item = mapper.findById(orgId, itemId); | |
| 203 | - if (item == null) { | |
| 204 | - throw new ProcessNotFoundException("게시물을 찾을 수 없습니다: " + itemId); | |
| 205 | - } | |
| 206 | - return item; | |
| 207 | - } | |
| 208 | -} | |
| +Binary file is not shown |
--- src/main/resources/mapper/DashboardMapper.xml
+++ src/main/resources/mapper/DashboardMapper.xml
... | ... | @@ -1,4 +1,4 @@ |
| 1 |
-<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 2 | 2 |
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" |
| 3 | 3 |
"https://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
| 4 | 4 |
<mapper namespace="kr.itn.itnhub.dashboard.DashboardMapper"> |
... | ... | @@ -75,7 +75,7 @@ |
| 75 | 75 |
left join ( |
| 76 | 76 |
select org_id, |
| 77 | 77 |
count(*) as total, |
| 78 |
- count(*) filter (where process_status = '처리완료') as done, |
|
| 78 |
+ count(*) filter (where process_status = #{doneStatus}) as done,
|
|
| 79 | 79 |
max(updated_at) as last_at |
| 80 | 80 |
from process_item group by org_id |
| 81 | 81 |
) pr on pr.org_id = o.id |
--- src/main/resources/mapper/ProcessItemMapper.xml
+++ src/main/resources/mapper/ProcessItemMapper.xml
... | ... | @@ -1,4 +1,4 @@ |
| 1 |
-<?xml version="1.0" encoding="UTF-8"?> |
|
| 1 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
| 2 | 2 |
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" |
| 3 | 3 |
"https://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
| 4 | 4 |
<mapper namespace="kr.itn.itnhub.process.ProcessItemMapper"> |
... | ... | @@ -39,10 +39,10 @@ |
| 39 | 39 |
<!-- status: null=전체, "DONE"=처리완료, "PENDING"=그 외(null/빈/미처리 등 처리완료가 아닌 전부). --> |
| 40 | 40 |
<sql id="statusFilter"> |
| 41 | 41 |
<if test="status != null and status == 'DONE'"> |
| 42 |
- and process_status = '처리완료' |
|
| 42 |
+ and process_status = #{doneStatus}
|
|
| 43 | 43 |
</if> |
| 44 | 44 |
<if test="status != null and status == 'PENDING'"> |
| 45 |
- and (process_status is null or process_status = '' or process_status <> '처리완료') |
|
| 45 |
+ and (process_status is null or process_status = '' or process_status <> #{doneStatus})
|
|
| 46 | 46 |
</if> |
| 47 | 47 |
</sql> |
| 48 | 48 |
|
... | ... | @@ -145,7 +145,7 @@ |
| 145 | 145 |
process_status = #{processStatus},
|
| 146 | 146 |
web_edited_at = now(), |
| 147 | 147 |
processed_at = case |
| 148 |
- when #{processStatus} = '처리완료' and process_item.processed_at is null then now()
|
|
| 148 |
+ when #{processStatus} = #{doneStatus} and process_item.processed_at is null then now()
|
|
| 149 | 149 |
else process_item.processed_at |
| 150 | 150 |
end, |
| 151 | 151 |
updated_at = now() |
... | ... | @@ -173,7 +173,7 @@ |
| 173 | 173 |
process_status = coalesce(#{processStatus}, process_status),
|
| 174 | 174 |
judged_kogl_type = coalesce(#{judgedKoglType}, judged_kogl_type),
|
| 175 | 175 |
final_opinion = coalesce(#{finalOpinion}, final_opinion),
|
| 176 |
- processed_at = case when #{processStatus} = '처리완료' then now() else processed_at end,
|
|
| 176 |
+ processed_at = case when #{processStatus} = #{doneStatus} then now() else processed_at end,
|
|
| 177 | 177 |
web_edited_at = now(), |
| 178 | 178 |
updated_at = now() |
| 179 | 179 |
where org_id = #{orgId}
|
... | ... | @@ -191,7 +191,7 @@ |
| 191 | 191 |
<select id="stats" resultMap="statsResultMap"> |
| 192 | 192 |
select |
| 193 | 193 |
count(*) as total, |
| 194 |
- count(*) filter (where process_status = '처리완료') as done |
|
| 194 |
+ count(*) filter (where process_status = #{doneStatus}) as done
|
|
| 195 | 195 |
from process_item |
| 196 | 196 |
where org_id = #{orgId}
|
| 197 | 197 |
</select> |
+++ src/test/java/kr/itn/itnhub/process/ProcessDoneStatusTest.java
... | ... | @@ -0,0 +1,110 @@ |
| 1 | +package kr.itn.itnhub.process; | |
| 2 | + | |
| 3 | +import kr.itn.itnhub.AbstractDbTest; | |
| 4 | +import kr.itn.itnhub.code.CodeService; | |
| 5 | +import kr.itn.itnhub.org.Organization; | |
| 6 | +import kr.itn.itnhub.org.OrganizationMapper; | |
| 7 | +import org.junit.jupiter.api.AfterEach; | |
| 8 | +import org.junit.jupiter.api.BeforeEach; | |
| 9 | +import org.junit.jupiter.api.Test; | |
| 10 | +import org.springframework.beans.factory.annotation.Autowired; | |
| 11 | +import org.springframework.jdbc.core.JdbcTemplate; | |
| 12 | + | |
| 13 | +import static org.assertj.core.api.Assertions.assertThat; | |
| 14 | + | |
| 15 | +/** | |
| 16 | + * "어느 값이 처리완료인가"가 코드표에서 온다는 것을 확인한다. | |
| 17 | + * | |
| 18 | + * <p>쿼리에 '처리완료'가 박혀 있던 시절에는 코드표를 바꿔도 집계가 그대로였다. 이 테스트는 | |
| 19 | + * 코드표의 done 속성을 옮기면 진행률도 따라 움직이는지를 본다 - 파라미터화가 형식만 바뀐 게 | |
| 20 | + * 아니라 실제로 동작하는지를 증명하는 자리다.</p> | |
| 21 | + */ | |
| 22 | +class ProcessDoneStatusTest extends AbstractDbTest { | |
| 23 | + | |
| 24 | + @Autowired | |
| 25 | + ProcessService service; | |
| 26 | + | |
| 27 | + @Autowired | |
| 28 | + OrganizationMapper orgMapper; | |
| 29 | + | |
| 30 | + @Autowired | |
| 31 | + CodeService codeService; | |
| 32 | + | |
| 33 | + @Autowired | |
| 34 | + JdbcTemplate jdbc; | |
| 35 | + | |
| 36 | + private Long orgId; | |
| 37 | + | |
| 38 | + @BeforeEach | |
| 39 | + void setUp() { | |
| 40 | + jdbc.update("delete from process_item"); | |
| 41 | + jdbc.update("delete from organization where org_no = '910'"); | |
| 42 | + | |
| 43 | + Organization org = new Organization(); | |
| 44 | + org.setOrgNo("910"); | |
| 45 | + org.setOrgName("처리상태테스트기관"); | |
| 46 | + org.setChannelSlug("910"); | |
| 47 | + orgMapper.upsertBySeed(org); | |
| 48 | + orgId = orgMapper.findByOrgNoAndOrgName("910", "처리상태테스트기관").getId(); | |
| 49 | + | |
| 50 | + insert(1, "처리완료"); | |
| 51 | + insert(2, "처리완료"); | |
| 52 | + insert(3, "미처리"); | |
| 53 | + } | |
| 54 | + | |
| 55 | + @AfterEach | |
| 56 | + void restore() { | |
| 57 | + jdbc.update("update code set attrs = jsonb_set(attrs, '{done}', 'true') " | |
| 58 | + + "where group_id = 'PROCESS_STATUS' and code = '처리완료'"); | |
| 59 | + jdbc.update("update code set attrs = jsonb_set(attrs, '{done}', 'false') " | |
| 60 | + + "where group_id = 'PROCESS_STATUS' and code = '미처리'"); | |
| 61 | + codeService.reload(); | |
| 62 | + | |
| 63 | + jdbc.update("delete from process_item"); | |
| 64 | + jdbc.update("delete from organization where org_no = '910'"); | |
| 65 | + } | |
| 66 | + | |
| 67 | + private void insert(int seq, String status) { | |
| 68 | + jdbc.update("insert into process_item (org_id, seq, process_status) values (?, ?, ?)", | |
| 69 | + orgId, seq, status); | |
| 70 | + } | |
| 71 | + | |
| 72 | + @Test | |
| 73 | + void 기본_설정에서는_처리완료가_완료로_집계된다() { | |
| 74 | + ProcessPage page = service.list(orgId, null, null, null, null, null, 0, 30); | |
| 75 | + | |
| 76 | + assertThat(page.total()).isEqualTo(3); | |
| 77 | + assertThat(page.done()).isEqualTo(2); | |
| 78 | + } | |
| 79 | + | |
| 80 | + @Test | |
| 81 | + void 코드표에서_done을_옮기면_집계도_따라_움직인다() { | |
| 82 | + // '미처리'를 완료로, '처리완료'를 미완료로 뒤집는다. 쿼리에 값이 박혀 있었다면 | |
| 83 | + // 아무 변화도 없었을 것이다. | |
| 84 | + jdbc.update("update code set attrs = jsonb_set(attrs, '{done}', 'false') " | |
| 85 | + + "where group_id = 'PROCESS_STATUS' and code = '처리완료'"); | |
| 86 | + jdbc.update("update code set attrs = jsonb_set(attrs, '{done}', 'true') " | |
| 87 | + + "where group_id = 'PROCESS_STATUS' and code = '미처리'"); | |
| 88 | + codeService.reload(); | |
| 89 | + | |
| 90 | + ProcessPage page = service.list(orgId, null, null, null, null, null, 0, 30); | |
| 91 | + | |
| 92 | + assertThat(page.total()).isEqualTo(3); | |
| 93 | + assertThat(page.done()).isEqualTo(1); | |
| 94 | + } | |
| 95 | + | |
| 96 | + @Test | |
| 97 | + void 상태_필터도_코드표를_따른다() { | |
| 98 | + assertThat(service.list(orgId, null, null, null, null, "DONE", 0, 30).items()).hasSize(2); | |
| 99 | + assertThat(service.list(orgId, null, null, null, null, "PENDING", 0, 30).items()).hasSize(1); | |
| 100 | + | |
| 101 | + jdbc.update("update code set attrs = jsonb_set(attrs, '{done}', 'false') " | |
| 102 | + + "where group_id = 'PROCESS_STATUS' and code = '처리완료'"); | |
| 103 | + jdbc.update("update code set attrs = jsonb_set(attrs, '{done}', 'true') " | |
| 104 | + + "where group_id = 'PROCESS_STATUS' and code = '미처리'"); | |
| 105 | + codeService.reload(); | |
| 106 | + | |
| 107 | + assertThat(service.list(orgId, null, null, null, null, "DONE", 0, 30).items()).hasSize(1); | |
| 108 | + assertThat(service.list(orgId, null, null, null, null, "PENDING", 0, 30).items()).hasSize(2); | |
| 109 | + } | |
| 110 | +} |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?