diff --git a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java index e23a4104..cf8ee841 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -151,18 +151,25 @@ public class LabelAllocateApiController { } @Operation( - summary = "작업현황 관리 > 라벨러 상세 정보, 작업이관 팝업 내 라벨러 상세 정보 동일", - description = "작업현황 관리 > 라벨러 상세 정보, 작업이관 팝업 내 라벨러 상세 정보 동일") - @GetMapping("/labeler-detail") - public ApiResponseDto findLabelerDetail( - @RequestParam(defaultValue = "01022223333", required = true) String userId, - @Parameter( - description = "회차 마스터 key", - required = true, - example = "8584e8d4-53b3-4582-bde2-28a81495a626") - @RequestParam - String uuid) { - return ApiResponseDto.ok(labelAllocateService.findLabelerDetail(userId, uuid)); + summary = "작업현황 관리 > 라벨러/검수자 상세 정보, 작업이관 팝업 내 라벨러 상세 정보 동일", + description = "작업현황 관리 > 라벨러/검수자 상세 정보, 작업이관 팝업 내 라벨러 상세 정보 동일") + @GetMapping("/user-detail") + public ApiResponseDto findUserDetail( + @RequestParam(defaultValue = "01022223333", required = true) String userId, + @Parameter( + description = "회차 마스터 key", + required = true, + example = "8584e8d4-53b3-4582-bde2-28a81495a626") + @RequestParam + String uuid, + @Schema( + allowableValues = {"LABELER", "REVIEWER"}, + defaultValue = "LABELER") + @Parameter( + description = "라벨러/검수자(LABELER/REVIEWER)", + required = true) @RequestParam String type + ) { + return ApiResponseDto.ok(labelAllocateService.findUserDetail(userId, uuid, type)); } @Operation(summary = "작업현황 관리 > 상세 > 작업 이관", description = "작업현황 관리 > 상세 > 작업 이관") @@ -199,4 +206,33 @@ public class LabelAllocateApiController { dto.getCompareYyyy(), dto.getTargetYyyy())); } + + @Operation( + summary = "라벨링작업 관리 > 상세 > 라벨러/검수자 일별 작업량 목록", + description = "라벨링작업 관리 > 상세 > 라벨러/검수자 일별 작업량 목록") + @GetMapping("/daily-list") + public ApiResponseDto> findDaliyList( + @RequestParam(defaultValue = "0", required = true) int page, + @RequestParam(defaultValue = "20", required = true) int size, + @Parameter( + description = "회차 마스터 key", + required = true, + example = "8584e8d4-53b3-4582-bde2-28a81495a626") + @RequestParam + String uuid, + @Parameter( + description = "사번", + required = true, + example = "123456") + @RequestParam String userId, + @Schema( + allowableValues = {"LABELER", "REVIEWER"}, + defaultValue = "LABELER") + @Parameter( + description = "라벨러/검수자(LABELER/REVIEWER)", + required = true) @RequestParam String type + ) { + LabelAllocateDto.searchReq searchReq = new LabelAllocateDto.searchReq(page, size, ""); + return ApiResponseDto.ok(labelAllocateService.findDaliyList(searchReq, uuid, userId, type)); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelAllocateDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelAllocateDto.java index f5a060a8..e931ce40 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelAllocateDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelAllocateDto.java @@ -8,7 +8,11 @@ import java.util.List; import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; public class LabelAllocateDto { @@ -97,9 +101,9 @@ public class LabelAllocateDto { private Integer stage; @Schema( - description = "라벨러 할당 목록", - example = - """ + description = "라벨러 할당 목록", + example = + """ [ { "userId": "123456", @@ -118,9 +122,9 @@ public class LabelAllocateDto { private List labelers; @Schema( - description = "검수자 할당 목록", - example = - """ + description = "검수자 할당 목록", + example = + """ ["K20251216001", "01022225555", "K20251212001" @@ -171,6 +175,9 @@ public class LabelAllocateDto { private Long analUid; private ZonedDateTime createdDttm; private ZonedDateTime updatedDttm; + private String inspectState; + private ZonedDateTime workStatDttm; + private ZonedDateTime inspectStatDttm; } @Getter @@ -210,7 +217,7 @@ public class LabelAllocateDto { private Double percent; private Integer ranking; private ZonedDateTime createdDttm; - private String inspectorName; + private String ownerName; } @Getter @@ -225,9 +232,9 @@ public class LabelAllocateDto { private Integer stage; @Schema( - description = "라벨러 할당 목록", - example = - """ + description = "라벨러 할당 목록", + example = + """ [ { "userId": "123456", @@ -256,4 +263,43 @@ public class LabelAllocateDto { private Long geoUid; private Long mapSheetNum; } + + @Getter + @Setter + @AllArgsConstructor + public static class LabelingStatDto { + + private String workDate; + private Long dailyTotalCnt; + private Long totalCnt; + private Long assignedCnt; + private Long skipCnt; + private Long completeCnt; + private Long remainCnt; + } + + @Schema(name = "searchReq", description = "일자별 작업 목록 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class searchReq { + + // 페이징 파라미터 + private int page = 0; + private int size = 20; + private String sort; + + public Pageable toPageable() { + if (sort != null && !sort.isEmpty()) { + String[] sortParams = sort.split(","); + String property = sortParams[0]; + Sort.Direction direction = + sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC; + return PageRequest.of(page, size, Sort.by(direction, property)); + } + return PageRequest.of(page, size); + } + } + } diff --git a/src/main/java/com/kamco/cd/kamcoback/label/service/LabelAllocateService.java b/src/main/java/com/kamco/cd/kamcoback/label/service/LabelAllocateService.java index bf551dfb..963f9237 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/service/LabelAllocateService.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/service/LabelAllocateService.java @@ -6,6 +6,7 @@ import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; 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.LabelerDetail; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto; 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.WorkerStatsDto.DailyHistory; @@ -16,6 +17,7 @@ import java.time.LocalDate; import java.util.List; import java.util.Objects; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -218,7 +220,19 @@ public class LabelAllocateService { return new ApiResponseDto.ResponseObj(ApiResponseCode.OK, "이관을 완료하였습니다."); } - public LabelerDetail findLabelerDetail(String userId, String uuid) { - return labelAllocateCoreService.findLabelerDetail(userId, uuid); + public LabelerDetail findUserDetail(String userId, String uuid, String type) { + if (type.equals("LABELER")) { + return labelAllocateCoreService.findLabelerDetail(userId, uuid); + } else { + return labelAllocateCoreService.findInspectorDetail(userId, uuid); + } + } + + public Page findDaliyList(LabelAllocateDto.searchReq searchReq, String uuid, String userId, String type) { + if (type.equals("LABELER")) { + return labelAllocateCoreService.findLabelerDailyStat(searchReq, uuid, userId); + } else { + return labelAllocateCoreService.findInspectorDailyStat(searchReq, uuid, userId); + } } } 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 2ad6ba67..8866d12e 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 @@ -4,7 +4,9 @@ import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; 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.LabelerDetail; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto; 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.WorkerStatsDto.ProjectInfo; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkProgressInfo; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; @@ -14,6 +16,7 @@ import java.time.LocalDate; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; @Service @@ -96,4 +99,16 @@ public class LabelAllocateCoreService { public void insertInspector(Long analUid, String inspector) { labelAllocateRepository.insertInspector(analUid, inspector); } + + public Page findLabelerDailyStat(searchReq searchReq, String uuid, String userId) { + return labelAllocateRepository.findLabelerDailyStat(searchReq, uuid, userId); + } + + public Page findInspectorDailyStat(searchReq searchReq, String uuid, String userId) { + return labelAllocateRepository.findInspectorDailyStat(searchReq, uuid, userId); + } + + public LabelerDetail findInspectorDetail(String userId, String uuid) { + return labelAllocateRepository.findInspectorDetail(userId, uuid); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingAssignmentEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingAssignmentEntity.java index 22afd4aa..88b5a15b 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingAssignmentEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingAssignmentEntity.java @@ -6,6 +6,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Table; +import java.time.ZonedDateTime; import java.util.UUID; @Entity @@ -40,18 +41,31 @@ public class LabelingAssignmentEntity extends CommonDateEntity { @Column(name = "anal_uid") private Long analUid; + @Column(name = "inspect_state") + private String inspectState; + + @Column(name = "work_stat_dttm") + private ZonedDateTime workStatDttm; + + @Column(name = "inspect_stat_dttm") + private ZonedDateTime inspectStatDttm; + public LabelAllocateDto.Basic toDto() { return new LabelAllocateDto.Basic( - this.assignmentUid, - this.inferenceGeomUid, - this.workerUid, - this.inspectorUid, - this.workState, - this.stagnationYn, - this.assignGroupId, - this.learnGeomUid, - this.analUid, - super.getCreatedDate(), - super.getModifiedDate()); + this.assignmentUid, + this.inferenceGeomUid, + this.workerUid, + this.inspectorUid, + this.workState, + this.stagnationYn, + this.assignGroupId, + this.learnGeomUid, + this.analUid, + super.getCreatedDate(), + super.getModifiedDate(), + this.inspectState, + this.workStatDttm, + this.inspectStatDttm + ); } } 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 739c30c2..a4e7807f 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 @@ -1,8 +1,10 @@ package com.kamco.cd.kamcoback.postgres.repository.label; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; 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.LabelerDetail; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto; 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.WorkProgressInfo; @@ -11,11 +13,12 @@ import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity; import java.time.LocalDate; import java.util.List; import java.util.UUID; +import org.springframework.data.domain.Page; public interface LabelAllocateRepositoryCustom { List fetchNextIds( - Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); + Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); void assignOwner(List ids, String userId, Long analUid); @@ -32,7 +35,7 @@ public interface LabelAllocateRepositoryCustom { // 작업자 통계 조회 List findWorkerStatistics( - Long analUid, String workerType, String search, String sortType); + Long analUid, String workerType, String search, String sortType); // 작업 진행 현황 조회 WorkProgressInfo findWorkProgressInfo(Long analUid); @@ -45,7 +48,7 @@ public interface LabelAllocateRepositoryCustom { InferenceDetail findInferenceDetail(String uuid); List fetchNextMoveIds( - Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); + Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); void assignOwnerMove(List sub, String userId); @@ -54,4 +57,10 @@ public interface LabelAllocateRepositoryCustom { Long findMapSheetAnalInferenceUid(Integer compareYyyy, Integer targetYyyy, Integer stage); void insertInspector(Long analUid, String inspector); + + Page findLabelerDailyStat(LabelAllocateDto.searchReq searchReq, String uuid, String userId); + + Page findInspectorDailyStat(LabelAllocateDto.searchReq searchReq, String uuid, String userId); + + LabelerDetail findInspectorDetail(String userId, String uuid); } 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 b08617b8..41f57d4c 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 @@ -13,6 +13,7 @@ import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail; 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.LabelerDetail; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto; 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.WorkProgressInfo; @@ -20,6 +21,7 @@ import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity; import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalInferenceEntity; import com.kamco.cd.kamcoback.postgres.entity.QMemberEntity; +import com.querydsl.core.types.Expression; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.CaseBuilder; @@ -43,6 +45,9 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Repository; @Slf4j @@ -567,29 +572,29 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto QMemberEntity inspector = new QMemberEntity("inspector"); return queryFactory - .select( - Projections.constructor( - LabelerDetail.class, - worker.userRole, - worker.name, - worker.employeeNo, - assignedCnt, - skipCnt, - completeCnt, - percent, - Expressions.constant(0), // TODO: 순위, 꼭 해야할지? - labelingAssignmentEntity.createdDate.min(), - inspector.name.min())) - .from(worker) - .innerJoin(labelingAssignmentEntity) - .on( - worker.employeeNo.eq(labelingAssignmentEntity.workerUid), - labelingAssignmentEntity.analUid.eq(analEntity.getId())) - .leftJoin(inspector) - .on(labelingAssignmentEntity.inspectorUid.eq(inspector.employeeNo)) - .where(worker.employeeNo.eq(userId)) - .groupBy(worker.userRole, worker.name, worker.employeeNo) - .fetchOne(); + .select( + Projections.constructor( + LabelerDetail.class, + worker.userRole, + worker.name, + worker.employeeNo, + assignedCnt, + skipCnt, + completeCnt, + percent, + Expressions.constant(0), // TODO: 순위, 꼭 해야할지? + labelingAssignmentEntity.workStatDttm.min(), + inspector.name.min())) + .from(worker) + .innerJoin(labelingAssignmentEntity) + .on( + worker.employeeNo.eq(labelingAssignmentEntity.workerUid), + labelingAssignmentEntity.analUid.eq(analEntity.getId())) + .leftJoin(inspector) + .on(labelingAssignmentEntity.inspectorUid.eq(inspector.employeeNo)) + .where(worker.employeeNo.eq(userId)) + .groupBy(worker.userRole, worker.name, worker.employeeNo) + .fetchOne(); } @Override @@ -681,4 +686,288 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto } return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); } + + @Override + public Page findLabelerDailyStat(LabelAllocateDto.searchReq searchReq, String uuid, String userId) { + // 날짜 포맷 + Expression workDate = + Expressions.stringTemplate( + "TO_CHAR({0}, 'YYYY-MM-DD')", + labelingAssignmentEntity.workStatDttm + ); + + // 날짜별 전체 건수 + Expression dailyTotalCnt = + Expressions.numberTemplate( + Long.class, + "COUNT(*)" + ); + + // ⭐ 전체 기간 총 건수 (윈도우 함수) + Expression totalCnt = + Expressions.numberTemplate( + Long.class, + "SUM(COUNT(*)) OVER ()" + ); + + // 상태별 카운트 (Postgres FILTER 사용) + Expression assignedCnt = + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'ASSIGNED')", + labelingAssignmentEntity.workState + ); + + Expression skipCnt = + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'SKIP')", + labelingAssignmentEntity.workState + ); + + Expression completeCnt = + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'COMPLETE')", + labelingAssignmentEntity.workState + ); + + Expression remainCnt = + Expressions.numberTemplate( + Long.class, + "({0} - {1} - {2})", + totalCnt, + skipCnt, + completeCnt + ); + + // 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: "); + } + + Pageable pageable = searchReq.toPageable(); + List foundContent = queryFactory + .select( + Projections.constructor( + LabelingStatDto.class, + workDate, + dailyTotalCnt, + totalCnt, // ⭐ 전체 일자 배정 건수 + assignedCnt, + skipCnt, + completeCnt, + remainCnt + ) + ) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.workerUid.eq(userId), + labelingAssignmentEntity.analUid.eq(analEntity.getId()) + ) + .groupBy(workDate) + .orderBy(labelingAssignmentEntity.workStatDttm.min().asc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + Long countQuery = queryFactory + .select(workDate) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.workerUid.eq(userId), + labelingAssignmentEntity.analUid.eq(analEntity.getId()) + ) + .distinct() + .fetch() + .stream() + .count(); + + return new PageImpl<>(foundContent, pageable, countQuery); + } + + @Override + public Page findInspectorDailyStat(searchReq searchReq, String uuid, String userId) { + // 날짜 포맷 + Expression workDate = + Expressions.stringTemplate( + "TO_CHAR({0}, 'YYYY-MM-DD')", + labelingAssignmentEntity.inspectStatDttm + ); + + // 날짜별 전체 건수 + Expression dailyTotalCnt = + Expressions.numberTemplate( + Long.class, + "COUNT(*)" + ); + + // ⭐ 전체 기간 총 건수 (윈도우 함수) + Expression totalCnt = + Expressions.numberTemplate( + Long.class, + "SUM(COUNT(*)) OVER ()" + ); + + // 상태별 카운트 (Postgres FILTER 사용) + Expression assignedCnt = + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'UNCONFIRM')", + labelingAssignmentEntity.inspectState + ); + + Expression skipCnt = + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'EXCEPT')", + labelingAssignmentEntity.inspectState + ); + + Expression completeCnt = + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'COMPLETE')", + labelingAssignmentEntity.inspectState + ); + + Expression remainCnt = + Expressions.numberTemplate( + Long.class, + "({0} - {1} - {2})", + totalCnt, + skipCnt, + completeCnt + ); + + // 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: "); + } + + Pageable pageable = searchReq.toPageable(); + List foundContent = queryFactory + .select( + Projections.constructor( + LabelingStatDto.class, + workDate, + dailyTotalCnt, + totalCnt, // ⭐ 전체 일자 배정 건수 + assignedCnt, + skipCnt, + completeCnt, + remainCnt + ) + ) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.inspectorUid.eq(userId), + labelingAssignmentEntity.analUid.eq(analEntity.getId()) + ) + .groupBy(workDate) + .orderBy(labelingAssignmentEntity.inspectStatDttm.min().asc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + Long countQuery = queryFactory + .select(workDate) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.inspectorUid.eq(userId), + labelingAssignmentEntity.analUid.eq(analEntity.getId()) + ) + .distinct() + .fetch() + .stream() + .count(); + + return new PageImpl<>(foundContent, pageable, countQuery); + } + + @Override + public LabelerDetail findInspectorDetail(String userId, String uuid) { + 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(); + + NumberExpression percent = + new CaseBuilder() + .when(completeCnt.eq(0L)) + .then(0.0) + .otherwise( + Expressions.numberTemplate( + Double.class, + "round({0} / {1}, 2)", + labelingAssignmentEntity.count(), + completeCnt)); + + // 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 inspector = QMemberEntity.memberEntity; + QMemberEntity worker = new QMemberEntity("worker"); + + return queryFactory + .select( + Projections.constructor( + LabelerDetail.class, + inspector.userRole, + inspector.name, + inspector.employeeNo, + assignedCnt, + skipCnt, + completeCnt, + percent, + Expressions.constant(0), // TODO: 순위, 꼭 해야할지? + labelingAssignmentEntity.inspectStatDttm.min(), + worker.name.min())) + .from(inspector) + .innerJoin(labelingAssignmentEntity) + .on( + inspector.employeeNo.eq(labelingAssignmentEntity.inspectorUid), + labelingAssignmentEntity.analUid.eq(analEntity.getId())) + .leftJoin(worker) + .on(labelingAssignmentEntity.workerUid.eq(worker.employeeNo)) + .where(inspector.employeeNo.eq(userId)) + .groupBy(inspector.userRole, inspector.name, inspector.employeeNo) + .fetchOne(); + } }