From 0f636ee7ce75d49a43f275feff92617c9c67fe99 Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Wed, 14 Jan 2026 16:19:24 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9E=91=EC=97=85=ED=98=84=ED=99=A9=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC,=20=EB=9D=BC=EB=B2=A8=EB=9F=AC/=EA=B2=80?= =?UTF-8?q?=EC=88=98=EC=9E=90=20=EC=88=9C=EC=9C=84=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/LabelAllocateCoreService.java | 75 +++++++- .../label/LabelAllocateRepositoryCustom.java | 15 ++ .../label/LabelAllocateRepositoryImpl.java | 171 ++++++++++++++---- 3 files changed, 228 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelAllocateCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelAllocateCoreService.java index fd6e985e..56ec4ffd 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelAllocateCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelAllocateCoreService.java @@ -105,7 +105,43 @@ public class LabelAllocateCoreService { } public LabelerDetail findLabelerDetail(String userId, String uuid) { - return labelAllocateRepository.findLabelerDetail(userId, uuid); + // 1. 기본 정보 조회 (순위 0으로 들어옴) + LabelerDetail detail = labelAllocateRepository.findLabelerDetail(userId, uuid); + + if (detail == null) { + return null; + } + + // 2. 해당 회차의 모든 라벨러 정보 조회 (완료 건수 기준 내림차순) + // UUID를 analUid로 변환 + Long analUid = labelAllocateRepository.findAnalUidByUuid(uuid); + if (analUid == null) { + return detail; // analUid 없으면 순위 0으로 반환 + } + + List allLabelers = labelAllocateRepository.findAllLabelersForRanking(analUid); + + // 3. 순위 계산 (DENSE_RANK 로직: 동점자는 같은 순위, 다음 순위는 연속) + Long lastCompleteCnt = null; + int currentRank = 0; + + for (int i = 0; i < allLabelers.size(); i++) { + LabelerDetail labeler = allLabelers.get(i); + + // 완료 건수가 바뀌면 순위 갱신 + if (lastCompleteCnt == null || !lastCompleteCnt.equals(labeler.getCompleteCnt())) { + currentRank = i + 1; + lastCompleteCnt = labeler.getCompleteCnt(); + } + + // 현재 조회한 사용자의 순위 찾기 + if (labeler.getUserId().equals(userId)) { + detail.setRanking(currentRank); + break; + } + } + + return detail; } public Long findMapSheetAnalInferenceUid(Integer compareYyyy, Integer targetYyyy, Integer stage) { @@ -127,7 +163,42 @@ public class LabelAllocateCoreService { } public LabelerDetail findInspectorDetail(String userId, String uuid) { - return labelAllocateRepository.findInspectorDetail(userId, uuid); + // 1. 기본 정보 조회 (순위 0으로 들어옴) + LabelerDetail detail = labelAllocateRepository.findInspectorDetail(userId, uuid); + + if (detail == null) { + return null; + } + + // 2. 해당 회차의 모든 검수자 정보 조회 (완료 건수 기준 내림차순) + Long analUid = labelAllocateRepository.findAnalUidByUuid(uuid); + if (analUid == null) { + return detail; // analUid 없으면 순위 0으로 반환 + } + + List allInspectors = labelAllocateRepository.findAllInspectorsForRanking(analUid); + + // 3. 순위 계산 (DENSE_RANK 로직: 동점자는 같은 순위, 다음 순위는 연속) + Long lastCompleteCnt = null; + int currentRank = 0; + + for (int i = 0; i < allInspectors.size(); i++) { + LabelerDetail inspector = allInspectors.get(i); + + // 완료 건수가 바뀌면 순위 갱신 + if (lastCompleteCnt == null || !lastCompleteCnt.equals(inspector.getCompleteCnt())) { + currentRank = i + 1; + lastCompleteCnt = inspector.getCompleteCnt(); + } + + // 현재 조회한 사용자의 순위 찾기 + if (inspector.getUserId().equals(userId)) { + detail.setRanking(currentRank); + break; + } + } + + return detail; } public MoveInfo moveAvailUserList(String userId, String uuid) { diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryCustom.java index a43df4e1..f1168082 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryCustom.java @@ -68,6 +68,16 @@ public interface LabelAllocateRepositoryCustom { LabelerDetail findLabelerDetail(String userId, String uuid); + /** + * 순위 계산용 - 특정 회차의 모든 라벨러 통계 조회 + */ + List findAllLabelersForRanking(Long analUid); + + /** + * UUID로 analUid 조회 + */ + Long findAnalUidByUuid(String uuid); + Long findMapSheetAnalInferenceUid(Integer compareYyyy, Integer targetYyyy, Integer stage); void insertInspector(Long analUid, String inspector); @@ -80,6 +90,11 @@ public interface LabelAllocateRepositoryCustom { LabelerDetail findInspectorDetail(String userId, String uuid); + /** + * 순위 계산용 - 특정 회차의 모든 검수자 통계 조회 + */ + List findAllInspectorsForRanking(Long analUid); + MoveInfo moveAvailUserList(String userId, String uuid); void insertLabelerUser(Long analUid, String userId, int demand); diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryImpl.java index 97f7e375..19102175 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryImpl.java @@ -777,11 +777,25 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public LabelerDetail findLabelerDetail(String userId, String uuid) { + // analUid로 분석 정보 조회 + MapSheetAnalInferenceEntity analEntity = + queryFactory + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) + .fetchOne(); + + if (Objects.isNull(analEntity)) { + throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for uuid: " + uuid); + } + + QMemberEntity worker = QMemberEntity.memberEntity; + QMemberEntity inspector = new QMemberEntity("inspector"); + NumberExpression totalCnt = labelingAssignmentEntity.assignmentUid.count(); - NumberExpression assignedCnt = + NumberExpression completeCnt = new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId())) + .when(labelingAssignmentEntity.workState.eq(LabelState.DONE.getId())) .then(1L) .otherwise((Long) null) .count(); @@ -793,13 +807,6 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .otherwise((Long) null) .count(); - NumberExpression completeCnt = - new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq(LabelState.DONE.getId())) - .then(1L) - .otherwise((Long) null) - .count(); - NumberExpression percent = new CaseBuilder() .when(completeCnt.eq(0L)) @@ -807,25 +814,10 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .otherwise( Expressions.numberTemplate( Double.class, - "round({0} / {1}, 2)", + "ROUND(({0} * 1.0 / NULLIF({1}, 0)) * 100, 2)", completeCnt, - labelingAssignmentEntity.count())); + totalCnt)); - // analUid로 분석 정보 조회 - MapSheetAnalInferenceEntity analEntity = - queryFactory - .selectFrom(mapSheetAnalInferenceEntity) - .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) - .fetchOne(); - - if (Objects.isNull(analEntity)) { - throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: "); - } - - QMemberEntity worker = QMemberEntity.memberEntity; - QMemberEntity inspector = new QMemberEntity("inspector"); - - // remainCnt Expression remainCnt = Expressions.numberTemplate(Long.class, "({0} - {1} - {2})", totalCnt, skipCnt, completeCnt); @@ -840,7 +832,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto completeCnt, skipCnt, percent, - Expressions.constant(0), // TODO: 순위, 꼭 해야할지? + Expressions.constant(0), // 순위는 Service 레이어에서 계산 labelingAssignmentEntity.workStatDttm.min(), inspector.name.min(), remainCnt)) @@ -856,6 +848,68 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .fetchOne(); } + /** + * 특정 회차의 모든 라벨러 통계 조회 (순위 계산용) + */ + public List findAllLabelersForRanking(Long analUid) { + QMemberEntity worker = QMemberEntity.memberEntity; + + NumberExpression totalCnt = labelingAssignmentEntity.assignmentUid.count(); + + NumberExpression completeCnt = + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq(LabelState.DONE.getId())) + .then(1L) + .otherwise((Long) null) + .count(); + + NumberExpression skipCnt = + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq(LabelState.SKIP.getId())) + .then(1L) + .otherwise((Long) null) + .count(); + + Expression remainCnt = + Expressions.numberTemplate(Long.class, "({0} - {1} - {2})", totalCnt, skipCnt, completeCnt); + + return queryFactory + .select( + Projections.constructor( + LabelerDetail.class, + worker.userRole, + worker.name, + worker.employeeNo, + totalCnt, + completeCnt, + skipCnt, + Expressions.constant(0.0), // percent + Expressions.constant(0), // ranking + labelingAssignmentEntity.workStatDttm.min(), + Expressions.nullExpression(String.class), // ownerName + remainCnt)) + .from(worker) + .innerJoin(labelingAssignmentEntity) + .on( + worker.employeeNo.eq(labelingAssignmentEntity.workerUid), + labelingAssignmentEntity.analUid.eq(analUid)) + .groupBy(worker.userRole, worker.name, worker.employeeNo) + .orderBy(completeCnt.desc()) // 완료 건수 내림차순 + .fetch(); + } + + /** + * UUID로 analUid 조회 + */ + @Override + public Long findAnalUidByUuid(String uuid) { + return queryFactory + .select(mapSheetAnalInferenceEntity.id) + .from(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) + .fetchOne(); + } + @Override public Long findMapSheetAnalInferenceUid(Integer compareYyyy, Integer targetYyyy, Integer stage) { return queryFactory @@ -1301,11 +1355,11 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto inspector.userRole, inspector.name, inspector.employeeNo, - assignedCnt, - skipCnt, - completeCnt, + assignedCnt, // count (총 배정 건수) + completeCnt, // completeCnt (완료 건수) + skipCnt, // skipCnt (스킵 건수) percent, - Expressions.constant(0), // TODO: 순위, 꼭 해야할지? + Expressions.constant(0), // 순위는 Service 레이어에서 계산 labelingAssignmentEntity.inspectStatDttm.min(), worker.name.min(), remainCnt)) @@ -1321,6 +1375,61 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .fetchOne(); } + /** + * 순위 계산용 - 특정 회차의 모든 검수자 통계 조회 + */ + public List findAllInspectorsForRanking(Long analUid) { + QMemberEntity inspector = QMemberEntity.memberEntity; + + NumberExpression assignedCnt = + new CaseBuilder() + .when(labelingAssignmentEntity.inspectState.eq(InspectState.UNCONFIRM.getId())) + .then(1L) + .otherwise((Long) null) + .count(); + + NumberExpression skipCnt = + new CaseBuilder() + .when(labelingAssignmentEntity.inspectState.eq(InspectState.EXCEPT.getId())) + .then(1L) + .otherwise((Long) null) + .count(); + + NumberExpression completeCnt = + new CaseBuilder() + .when(labelingAssignmentEntity.inspectState.eq(InspectState.COMPLETE.getId())) + .then(1L) + .otherwise((Long) null) + .count(); + + Expression remainCnt = + Expressions.numberTemplate(Long.class, "({0} - {1} - {2})", assignedCnt, skipCnt, completeCnt); + + return queryFactory + .select( + Projections.constructor( + LabelerDetail.class, + inspector.userRole, + inspector.name, + inspector.employeeNo, + assignedCnt, // count (총 배정 건수) + completeCnt, // completeCnt (완료 건수) + skipCnt, // skipCnt (스킵 건수) + Expressions.constant(0.0), // percent + Expressions.constant(0), // ranking + labelingAssignmentEntity.inspectStatDttm.min(), + Expressions.nullExpression(String.class), // ownerName + remainCnt)) + .from(inspector) + .innerJoin(labelingAssignmentEntity) + .on( + inspector.employeeNo.eq(labelingAssignmentEntity.inspectorUid), + labelingAssignmentEntity.analUid.eq(analUid)) + .groupBy(inspector.userRole, inspector.name, inspector.employeeNo) + .orderBy(completeCnt.desc()) // 완료 건수 내림차순 + .fetch(); + } + @Override public MoveInfo moveAvailUserList(String userId, String uuid) {