라벨 재할당 로직 수정, 배치로 재배치는 나중에

This commit is contained in:
2026-01-07 14:24:20 +09:00
parent fd249ae2ac
commit c8773dabdd
8 changed files with 243 additions and 75 deletions

View File

@@ -191,12 +191,9 @@ public class LabelAllocateApiController {
@RequestBody @RequestBody
LabelAllocateDto.AllocateMoveDto dto) { LabelAllocateDto.AllocateMoveDto dto) {
int compareYyyy = Integer.parseInt(dto.getYyyy().split("-")[0]);
int targetYyyy = Integer.parseInt(dto.getYyyy().split("-")[1]);
return ApiResponseDto.okObject( return ApiResponseDto.okObject(
labelAllocateService.allocateMove( labelAllocateService.allocateMove(
dto.getStage(), dto.getLabelers(), compareYyyy, targetYyyy, dto.getUserId())); dto.getTotalCnt(), dto.getUuid(), dto.getLabelers(), dto.getUserId()));
} }
@Operation( @Operation(
@@ -232,7 +229,7 @@ public class LabelAllocateApiController {
@ApiResponse(responseCode = "500", description = "서버 오류") @ApiResponse(responseCode = "500", description = "서버 오류")
}) })
@GetMapping("/move-user") @GetMapping("/move-user")
public ApiResponseDto<List<LabelAllocateDto.MoveUserList>> availMoveUserList( public ApiResponseDto<LabelAllocateDto.MoveInfo> availMoveUserList(
@Parameter(description = "해당 사용자 사번", example = "01022223333") @RequestParam String userId, @Parameter(description = "해당 사용자 사번", example = "01022223333") @RequestParam String userId,
@Parameter(description = "회차 마스터 key", example = "f97dc186-e6d3-4645-9737-3173dde8dc64") @Parameter(description = "회차 마스터 key", example = "f97dc186-e6d3-4645-9737-3173dde8dc64")
@RequestParam @RequestParam

View File

@@ -229,34 +229,20 @@ public class LabelAllocateDto {
@AllArgsConstructor @AllArgsConstructor
public static class AllocateMoveDto { public static class AllocateMoveDto {
@Schema(description = "회차", example = "4") @Schema(description = "총 잔여 건수", example = "5061")
private Integer stage; private Integer totalCnt;
@Schema( @Schema(
description = "이관할 라벨러 할당량", description = "이관할 라벨러",
example = example = """
"""
[ [
{ "87654321"
"userId": "123456",
"demand": 10
},
{
"userId": "010222297501",
"demand": 5
}
] ]
""") """)
private List<TargetUser> labelers; private List<String> labelers;
@Schema(description = "비교년도-기준년도", example = "2022-2024") @Schema(description = "회차 마스터 key", example = "f97dc186-e6d3-4645-9737-3173dde8dc64")
private String yyyy; private String uuid;
// @Schema(description = "비교년도", example = "2022")
// private Integer compareYyyy;
//
// @Schema(description = "기준년도", example = "2024")
// private Integer targetYyyy;
@Schema(description = "대상 사번", example = "01022223333") @Schema(description = "대상 사번", example = "01022223333")
private String userId; private String userId;
@@ -269,6 +255,7 @@ public class LabelAllocateDto {
private Long geoUid; private Long geoUid;
private Long mapSheetNum; private Long mapSheetNum;
private Long pnu;
} }
@Getter @Getter
@@ -320,4 +307,13 @@ public class LabelAllocateDto {
private Long remainCnt; private Long remainCnt;
private Double percent; private Double percent;
} }
@Getter
@Setter
@AllArgsConstructor
public static class MoveInfo {
private Long totalCnt;
private List<MoveUserList> moveUserList;
}
} }

View File

@@ -0,0 +1,27 @@
package com.kamco.cd.kamcoback.label.dto;
import java.time.ZonedDateTime;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
public class LabelLabelerDto {
@Getter
@Setter
@AllArgsConstructor
public static class Basic {
private UUID lbUsrUid;
private Long analUid;
private String workerUid;
private Long allocateCnt;
private Boolean deleted;
private Boolean reAllocateYn;
private Long reAllocateCnt;
private String reAllocateWorkerUid;
private ZonedDateTime createdDttm;
private ZonedDateTime updatedDttm;
}
}

View File

@@ -7,12 +7,14 @@ import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.AllocateInfoDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.MoveUserList; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.MoveInfo;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetUser; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetUser;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList;
import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse;
import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService; import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
@@ -73,6 +75,9 @@ public class LabelAllocateService {
labelAllocateCoreService.assignOwner(sub, target.getUserId(), analUid); labelAllocateCoreService.assignOwner(sub, target.getUserId(), analUid);
index = end; index = end;
// 라벨러 유저 테이블에 insert
labelAllocateCoreService.insertLabelerUser(analUid, target.getUserId(), target.getDemand());
} }
// 검수자 할당 테이블에 insert. TODO: 익일 배치로 라벨링 완료된 내역을 검수자에게 할당해야 함 // 검수자 할당 테이블에 insert. TODO: 익일 배치로 라벨링 완료된 내역을 검수자에게 할당해야 함
@@ -116,31 +121,51 @@ public class LabelAllocateService {
} }
public ApiResponseDto.ResponseObj allocateMove( public ApiResponseDto.ResponseObj allocateMove(
Integer stage, Integer totalCnt, String uuid, List<String> targetUsers, String userId) {
List<TargetUser> targetUsers,
Integer compareYyyy,
Integer targetYyyy,
String userId) {
Long lastId = null;
Long chargeCnt = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); Map<String, Integer> result = new LinkedHashMap<>();
int userCount = targetUsers.size();
if (chargeCnt <= 0) { if (userCount <= 0) {
return new ApiResponseDto.ResponseObj(ApiResponseCode.BAD_REQUEST, "이관할 데이터를 입력해주세요."); return new ApiResponseDto.ResponseObj(ApiResponseCode.BAD_REQUEST, "재할당할 라벨러를 선택해주세요.");
} }
List<Long> allIds = int base = totalCnt / userCount;
labelAllocateCoreService.fetchNextMoveIds( int remainder = totalCnt % userCount;
lastId, chargeCnt, compareYyyy, targetYyyy, stage, userId);
int index = 0;
for (TargetUser target : targetUsers) {
int end = index + target.getDemand();
List<Long> sub = allIds.subList(index, end);
labelAllocateCoreService.assignOwnerMove(sub, target.getUserId()); for (int i = 0; i < userCount; i++) {
index = end; int assignCount = base;
// 마지막 사람에게 나머지 몰아주기
if (i == userCount - 1) {
assignCount += remainder;
}
result.put(targetUsers.get(i), assignCount);
// TODO: 재할당 테이블에 update 까지만 하고 나머지는 배치에서 처리하기?
labelAllocateCoreService.assignOwnerReAllocate(
uuid, userId, targetUsers.get(i), (long) assignCount);
} }
// Long lastId = null;
// Long chargeCnt = targetUsers.stream().mapToLong(TargetUser::getDemand).sum();
//
// if (chargeCnt <= 0) {
// return new ApiResponseDto.ResponseObj(ApiResponseCode.BAD_REQUEST, "이관할 데이터를 입력해주세요.");
// }
//
// List<Long> allIds =
// labelAllocateCoreService.fetchNextMoveIds(
// lastId, chargeCnt, compareYyyy, targetYyyy, stage, userId);
// int index = 0;
// for (TargetUser target : targetUsers) {
// int end = index + target.getDemand();
// List<Long> sub = allIds.subList(index, end);
//
// labelAllocateCoreService.assignOwnerMove(sub, target.getUserId());
// index = end;
// }
return new ApiResponseDto.ResponseObj(ApiResponseCode.OK, "이관을 완료하였습니다."); return new ApiResponseDto.ResponseObj(ApiResponseCode.OK, "이관을 완료하였습니다.");
} }
@@ -161,7 +186,7 @@ public class LabelAllocateService {
} }
} }
public List<MoveUserList> moveAvailUserList(String userId, String uuid) { public MoveInfo moveAvailUserList(String userId, String uuid) {
return labelAllocateCoreService.moveAvailUserList(userId, uuid); return labelAllocateCoreService.moveAvailUserList(userId, uuid);
} }
} }

View File

@@ -5,7 +5,7 @@ import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.AllocateInfoDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.MoveUserList; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.MoveInfo;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.searchReq; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.searchReq;
import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.ProjectInfo; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.ProjectInfo;
@@ -128,7 +128,16 @@ public class LabelAllocateCoreService {
return labelAllocateRepository.findInspectorDetail(userId, uuid); return labelAllocateRepository.findInspectorDetail(userId, uuid);
} }
public List<MoveUserList> moveAvailUserList(String userId, String uuid) { public MoveInfo moveAvailUserList(String userId, String uuid) {
return labelAllocateRepository.moveAvailUserList(userId, uuid); return labelAllocateRepository.moveAvailUserList(userId, uuid);
} }
public void insertLabelerUser(Long analUid, String userId, int demand) {
labelAllocateRepository.insertLabelerUser(analUid, userId, demand);
}
public void assignOwnerReAllocate(
String uuid, String userId, String paramUserId, Long assignCount) {
labelAllocateRepository.assignOwnerReAllocate(uuid, userId, paramUserId, assignCount);
}
} }

View File

@@ -0,0 +1,48 @@
package com.kamco.cd.kamcoback.postgres.entity;
import com.kamco.cd.kamcoback.label.dto.LabelLabelerDto;
import com.kamco.cd.kamcoback.postgres.CommonDateEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.UUID;
@Entity
@Table(name = "tb_labeling_labeler")
public class LabelingLabelerEntity extends CommonDateEntity {
@Id
@Column(name = "lb_usr_uid")
private UUID lbUsrUid;
@Column(name = "anal_uid")
private Long analUid;
@Column(name = "worker_uid")
private String workerUid;
private Long allocateCnt;
private Boolean deleted;
private Boolean reAllocateYn;
private Long reAllocateCnt;
private String reAllocateWorkerUid;
public LabelLabelerDto.Basic toDto() {
return new LabelLabelerDto.Basic(
this.lbUsrUid,
this.analUid,
this.workerUid,
this.allocateCnt,
this.deleted,
this.reAllocateYn,
this.reAllocateCnt,
this.reAllocateWorkerUid,
super.getCreatedDate(),
super.getModifiedDate());
}
}

View File

@@ -5,7 +5,7 @@ import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.AllocateInfoDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.MoveUserList; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.MoveInfo;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList;
import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.ProjectInfo; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.ProjectInfo;
import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkProgressInfo; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkProgressInfo;
@@ -78,5 +78,9 @@ public interface LabelAllocateRepositoryCustom {
LabelerDetail findInspectorDetail(String userId, String uuid); LabelerDetail findInspectorDetail(String userId, String uuid);
List<MoveUserList> moveAvailUserList(String userId, String uuid); MoveInfo moveAvailUserList(String userId, String uuid);
void insertLabelerUser(Long analUid, String userId, int demand);
void assignOwnerReAllocate(String uuid, String userId, String paramUserId, Long assignCount);
} }

View File

@@ -2,6 +2,7 @@ package com.kamco.cd.kamcoback.postgres.repository.label;
import static com.kamco.cd.kamcoback.postgres.entity.QLabelingAssignmentEntity.labelingAssignmentEntity; import static com.kamco.cd.kamcoback.postgres.entity.QLabelingAssignmentEntity.labelingAssignmentEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QLabelingInspectorEntity.labelingInspectorEntity; import static com.kamco.cd.kamcoback.postgres.entity.QLabelingInspectorEntity.labelingInspectorEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QLabelingLabelerEntity.labelingLabelerEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMemberEntity.memberEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMemberEntity.memberEntity;
@@ -13,6 +14,7 @@ import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InspectState;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelState; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelState;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.MoveInfo;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.MoveUserList; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.MoveUserList;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList;
import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.ProjectInfo; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.ProjectInfo;
@@ -68,7 +70,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
Projections.constructor( Projections.constructor(
AllocateInfoDto.class, AllocateInfoDto.class,
mapSheetAnalDataInferenceGeomEntity.geoUid, mapSheetAnalDataInferenceGeomEntity.geoUid,
mapSheetAnalDataInferenceGeomEntity.mapSheetNum)) mapSheetAnalDataInferenceGeomEntity.mapSheetNum,
mapSheetAnalDataInferenceGeomEntity.pnu))
.from(mapSheetAnalDataInferenceGeomEntity) .from(mapSheetAnalDataInferenceGeomEntity)
.where( .where(
lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId),
@@ -116,8 +119,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
""" """
insert into tb_labeling_assignment insert into tb_labeling_assignment
(assignment_uid, inference_geom_uid, worker_uid, (assignment_uid, inference_geom_uid, worker_uid,
work_state, assign_group_id, anal_uid) work_state, assign_group_id, anal_uid, pnu)
values (?, ?, ?, ?, ?, ?) values (?, ?, ?, ?, ?, ?, ?)
"""; """;
try (PreparedStatement ps = connection.prepareStatement(sql)) { try (PreparedStatement ps = connection.prepareStatement(sql)) {
@@ -130,6 +133,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
ps.setString(4, LabelState.ASSIGNED.getId()); ps.setString(4, LabelState.ASSIGNED.getId());
ps.setString(5, String.valueOf(info.getMapSheetNum())); ps.setString(5, String.valueOf(info.getMapSheetNum()));
ps.setLong(6, analEntity.getId()); ps.setLong(6, analEntity.getId());
ps.setLong(7, info.getPnu());
ps.addBatch(); ps.addBatch();
batchSize++; batchSize++;
@@ -1038,12 +1042,16 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
} }
@Override @Override
public List<MoveUserList> moveAvailUserList(String userId, String uuid) { public MoveInfo moveAvailUserList(String userId, String uuid) {
NumberExpression<Long> totalCnt = labelingAssignmentEntity.count(); NumberExpression<Long> totalCnt = labelingAssignmentEntity.count();
NumberExpression<Long> completeCnt = NumberExpression<Long> completeCnt =
new CaseBuilder() new CaseBuilder()
.when(labelingAssignmentEntity.workState.eq(LabelState.COMPLETE.getId())) .when(
labelingAssignmentEntity
.workState
.eq(LabelState.COMPLETE.getId())
.or(labelingAssignmentEntity.workState.eq(LabelState.SKIP.getId())))
.then(1L) .then(1L)
.otherwise(0L) .otherwise(0L)
.sum(); .sum();
@@ -1073,24 +1081,78 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: "); throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: ");
} }
return queryFactory Long userChargeCnt =
.select( queryFactory
Projections.constructor( .select(labelingAssignmentEntity.inferenceGeomUid.count())
MoveUserList.class, .from(labelingAssignmentEntity)
memberEntity.userRole, .where(
memberEntity.employeeNo, labelingAssignmentEntity.analUid.eq(analEntity.getId()),
memberEntity.name, labelingAssignmentEntity.workerUid.eq(userId),
remainCnt, labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId()))
percent)) .fetchOne();
.from(labelingAssignmentEntity)
.innerJoin(memberEntity) List<MoveUserList> list =
.on(labelingAssignmentEntity.workerUid.eq(memberEntity.employeeNo)) queryFactory
.select(
Projections.constructor(
MoveUserList.class,
memberEntity.userRole,
memberEntity.employeeNo,
memberEntity.name,
remainCnt,
percent))
.from(labelingLabelerEntity)
.innerJoin(memberEntity)
.on(labelingAssignmentEntity.workerUid.eq(memberEntity.employeeNo))
.where(
labelingAssignmentEntity.analUid.eq(analEntity.getId()),
labelingAssignmentEntity.workerUid.ne(userId))
.groupBy(memberEntity.userRole, memberEntity.employeeNo, memberEntity.name)
.having(
completeCnt
.multiply(2)
.goe(totalCnt)) // 진행률 평균 이상인 것들만 조회 => percent 를 바로 쓰면 having절에 무리가 갈 수 있다고 함
.orderBy(completeCnt.desc()) // TODO: 도엽번호? PNU? 로 정렬하여 보여주기?
.fetch();
return new MoveInfo(userChargeCnt, list);
}
@Override
public void insertLabelerUser(Long analUid, String userId, int demand) {
queryFactory
.insert(labelingLabelerEntity)
.columns(
labelingLabelerEntity.lbUsrUid,
labelingLabelerEntity.analUid,
labelingLabelerEntity.workerUid,
labelingLabelerEntity.allocateCnt)
.values(UUID.randomUUID(), analUid, userId, demand)
.execute();
}
@Override
public void assignOwnerReAllocate(
String uuid, String userId, String paramUserId, Long assignCount) {
// 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: ");
}
queryFactory
.update(labelingLabelerEntity)
.set(labelingLabelerEntity.reAllocateYn, true)
.set(labelingLabelerEntity.reAllocateWorkerUid, userId)
.set(labelingLabelerEntity.reAllocateCnt, assignCount)
.where( .where(
labelingAssignmentEntity.analUid.eq(analEntity.getId()), labelingLabelerEntity.analUid.eq(analEntity.getId()),
labelingAssignmentEntity.workerUid.ne(userId)) labelingLabelerEntity.workerUid.eq(paramUserId))
.groupBy(memberEntity.userRole, memberEntity.employeeNo, memberEntity.name) .execute();
.having(completeCnt.multiply(2).goe(totalCnt)) // 진행률 평균 이상인 것들만 조회 => percent 를 바로 쓰면
.orderBy(completeCnt.desc())
.fetch();
} }
} }