package kr.itn.itnhub.dashboard;
import kr.itn.itnhub.code.CodeService;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 대시보드 화면 하나가 쓰는 서비스. 집계 쿼리는 {@link DashboardMapper#findOrgRows()} 한 번이고,
* 상단 KPI는 그 결과를 Java에서 접어 만든다 - 같은 원본에서 계산해야 타일 숫자와 아래 표/칸반
* 숫자가 어긋나지 않는다(쿼리를 따로 두면 두 결과 사이에 시차가 생긴다).
*
*
어느 단계부터 각 KPI에 세는지는 코드표(STAGE의 kpiFrom 속성)에서 온다. 예전에는 여기
* 상수 세 개로 박혀 있어서, 기준이 바뀔 때마다 배포가 필요했다.
*/
@Service
public class DashboardService {
/** KPI 이름. 코드표의 kpiFrom 값과 맞물린다. */
static final String KPI_DOC_SUBMITTED = "docSubmitted";
static final String KPI_COMPLETED = "completed";
static final String KPI_REPORT = "reportWriting";
private final DashboardMapper mapper;
private final CodeService codeService;
public DashboardService(DashboardMapper mapper, CodeService codeService) {
this.mapper = mapper;
this.codeService = codeService;
}
/**
* 이 KPI에 집계하기 시작하는 단계 번호. 코드표에 지정이 없으면 아무 기관도 세지 않도록
* {@link Integer#MAX_VALUE}를 준다 - 0을 주면 단계가 있는 모든 기관이 잘못 집계된다.
*/
private int thresholdOf(String kpi) {
return codeService.group(kr.itn.itnhub.code.Codes.STAGE).stream()
.filter(code -> kpi.equals(code.attr(kr.itn.itnhub.code.Codes.ATTR_KPI_FROM)))
.mapToInt(code -> Integer.parseInt(code.code()))
.min()
.orElse(Integer.MAX_VALUE);
}
public DashboardResponse dashboard() {
List rows = mapper.findOrgRows(doneStatus());
RightsDashboardStats rights = new RightsDashboardStats(
mapper.reviewResultDistribution(),
mapper.reviewSourceKoglDistribution(),
mapper.reviewJudgedKoglDistribution(),
mapper.processTypeDistribution(),
mapper.processStatusDistribution(),
mapper.countChangedKoglTypes());
return new DashboardResponse(summarize(rows), rows, rights);
}
/**
* 권리처리 완료로 셀 상태값. 지정이 없으면 어떤 행도 완료로 세지 않도록 매칭되지 않는
* 값을 준다 - 빈 문자열을 주면 처리상태가 비어 있는 행이 전부 완료로 잡힌다.
*/
private String doneStatus() {
return codeService.group(kr.itn.itnhub.code.Codes.PROCESS_STATUS).stream()
.filter(code -> code.flag(kr.itn.itnhub.code.Codes.ATTR_DONE))
.map(kr.itn.itnhub.code.Code::code)
.findFirst()
.orElse(" ");
}
DashboardSummary summarize(List rows) {
int docSubmittedFrom = thresholdOf(KPI_DOC_SUBMITTED);
int completedFrom = thresholdOf(KPI_COMPLETED);
int reportFrom = thresholdOf(KPI_REPORT);
int docSubmitted = 0;
int reviewTotal = 0;
int reviewDone = 0;
int processTotal = 0;
int processDone = 0;
int unresolvedRe = 0;
int reportWriting = 0;
int completed = 0;
for (DashboardOrgRow row : rows) {
reviewTotal += row.reviewTotal();
reviewDone += row.reviewDone();
processTotal += row.processTotal();
processDone += row.processDone();
unresolvedRe += row.reCount();
Integer stage = row.stage();
if (stage == null) {
continue;
}
if (stage >= docSubmittedFrom) {
docSubmitted++;
}
if (stage >= completedFrom) {
completed++;
}
if (stage >= reportFrom) {
reportWriting++;
}
}
// 신청 기관 수는 명부에 등록된 기관 전체다 - 명부에 올라온 것 자체가 신청이므로
// 단계값 유무와 무관하다(채널 생성 전 기관도 신청은 한 상태다).
return new DashboardSummary(rows.size(), docSubmitted, reviewTotal, reviewDone,
processTotal, processDone, unresolvedRe, reportWriting, completed);
}
}