작업현황 관리, 라벨러/검수자 순위추가

This commit is contained in:
DanielLee
2026-01-14 16:19:24 +09:00
parent c650bf15e7
commit 0f636ee7ce
3 changed files with 228 additions and 33 deletions

View File

@@ -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<LabelerDetail> 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<LabelerDetail> 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) {

View File

@@ -68,6 +68,16 @@ public interface LabelAllocateRepositoryCustom {
LabelerDetail findLabelerDetail(String userId, String uuid);
/**
* 순위 계산용 - 특정 회차의 모든 라벨러 통계 조회
*/
List<LabelerDetail> 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<LabelerDetail> findAllInspectorsForRanking(Long analUid);
MoveInfo moveAvailUserList(String userId, String uuid);
void insertLabelerUser(Long analUid, String userId, int demand);

View File

@@ -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<Long> totalCnt = labelingAssignmentEntity.assignmentUid.count();
NumberExpression<Long> assignedCnt =
NumberExpression<Long> 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<Long> completeCnt =
new CaseBuilder()
.when(labelingAssignmentEntity.workState.eq(LabelState.DONE.getId()))
.then(1L)
.otherwise((Long) null)
.count();
NumberExpression<Double> 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<Long> 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<LabelerDetail> findAllLabelersForRanking(Long analUid) {
QMemberEntity worker = QMemberEntity.memberEntity;
NumberExpression<Long> totalCnt = labelingAssignmentEntity.assignmentUid.count();
NumberExpression<Long> completeCnt =
new CaseBuilder()
.when(labelingAssignmentEntity.workState.eq(LabelState.DONE.getId()))
.then(1L)
.otherwise((Long) null)
.count();
NumberExpression<Long> skipCnt =
new CaseBuilder()
.when(labelingAssignmentEntity.workState.eq(LabelState.SKIP.getId()))
.then(1L)
.otherwise((Long) null)
.count();
Expression<Long> 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<LabelerDetail> findAllInspectorsForRanking(Long analUid) {
QMemberEntity inspector = QMemberEntity.memberEntity;
NumberExpression<Long> assignedCnt =
new CaseBuilder()
.when(labelingAssignmentEntity.inspectState.eq(InspectState.UNCONFIRM.getId()))
.then(1L)
.otherwise((Long) null)
.count();
NumberExpression<Long> skipCnt =
new CaseBuilder()
.when(labelingAssignmentEntity.inspectState.eq(InspectState.EXCEPT.getId()))
.then(1L)
.otherwise((Long) null)
.count();
NumberExpression<Long> completeCnt =
new CaseBuilder()
.when(labelingAssignmentEntity.inspectState.eq(InspectState.COMPLETE.getId()))
.then(1L)
.otherwise((Long) null)
.count();
Expression<Long> 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) {