From 88cf9a44872be805db8d9b075beff104e82fb756 Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 5 Jan 2026 10:37:17 +0900 Subject: [PATCH 01/14] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=EB=A7=81=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=EA=B4=80=EB=A6=AC=20=EB=AA=A9=EB=A1=9D=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kamco/cd/kamcoback/label/LabelWorkerApiController.java | 7 ++++--- .../Inference/InferenceResultRepositoryImpl.java | 6 ++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java b/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java index 17d8844e..d07083e1 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java @@ -45,9 +45,10 @@ public class LabelWorkerApiController { }) @GetMapping("/label-work-mng-list") public ApiResponseDto> labelWorkMngList( - @Schema(description = "변화탐지년도", example = "2024") Integer detectYyyy, - @Schema(description = "시작일", example = "20260101") String strtDttm, - @Schema(description = "종료일", example = "20261201") String endDttm, + @Parameter(description = "변화탐지년도", example = "2024") @RequestParam(required = false) + Integer detectYyyy, + @Parameter(description = "시작일", example = "20260101") @RequestParam String strtDttm, + @Parameter(description = "종료일", example = "20261201") @RequestParam String endDttm, @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") int page, @Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20") diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java index 1876e53c..8e1804b9 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java @@ -39,13 +39,15 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC compare_yyyy, target_yyyy, stage, - anal_title + anal_title, + detecting_cnt ) SELECT r.input1 AS compare_yyyy, r.input2 AS target_yyyy, r.stage, - CONCAT(r.stage ,'_', r.input1 ,'_', r.input2) AS anal_title + CONCAT(r.stage ,'_', r.input1 ,'_', r.input2) AS anal_title, + count(*) FROM inference_results r GROUP BY r.stage, r.input1, r.input2; """; From 67eb99e243d74684d5ff0adaeb902f369415dbeb Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Mon, 5 Jan 2026 11:41:50 +0900 Subject: [PATCH 02/14] =?UTF-8?q?=EC=9E=91=EC=97=85=ED=98=84=ED=99=A9?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelAllocateApiController.java | 18 ++++++---- .../kamcoback/label/dto/WorkerStatsDto.java | 12 +++++++ .../label/service/LabelAllocateService.java | 34 +++++++++++++----- .../core/LabelAllocateCoreService.java | 5 ++- .../label/LabelAllocateRepositoryCustom.java | 2 +- .../label/LabelAllocateRepositoryImpl.java | 36 ++++++++++--------- 6 files changed, 71 insertions(+), 36 deletions(-) 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 6869b636..7870a7fc 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -63,7 +63,7 @@ public class LabelAllocateApiController { }) @GetMapping("/admin/workers") public ApiResponseDto getWorkerStatistics( - @Parameter(description = "분석 ID (필수)", required = true, example = "3") @RequestParam + @Parameter(description = "분석 ID (선택)", example = "3") @RequestParam(required = false) Long analUid, @Parameter( description = "작업자 유형 (선택) - 미입력 시 LABELER로 조회", @@ -74,11 +74,9 @@ public class LabelAllocateApiController { defaultValue = "LABELER")) @RequestParam(required = false) String type, - @Parameter(description = "작업자 이름 검색 (부분 일치)", example = "김라벨") @RequestParam(required = false) - String searchName, - @Parameter(description = "작업자 사번 검색 (부분 일치)", example = "1234567") + @Parameter(description = "검색어 (작업자 이름 또는 사번으로 검색, 부분 일치)", example = "김라벨") @RequestParam(required = false) - String searchEmployeeNo, + String search, @Parameter( description = "정렬 조건 (선택) - 미입력 시 이름 오름차순", example = "REMAINING_DESC", @@ -92,14 +90,20 @@ public class LabelAllocateApiController { }, defaultValue = "NAME_ASC")) @RequestParam(required = false) - String sort) { + String sort, + @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") + @RequestParam(defaultValue = "0") + Integer page, + @Parameter(description = "페이지 크기", example = "20") + @RequestParam(defaultValue = "20") + Integer size) { // type이 null이면 기본값으로 LABELER 설정 String workerType = (type == null || type.isEmpty()) ? RoleType.LABELER.name() : type; return ApiResponseDto.ok( labelAllocateService.getWorkerStatistics( - analUid, workerType, searchName, searchEmployeeNo, sort)); + analUid, workerType, search, sort, page, size)); } @Operation(summary = "라벨링작업 관리 > 작업 배정", description = "라벨링작업 관리 > 작업 배정") diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java index efca81b2..f879f0ee 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java @@ -113,5 +113,17 @@ public class WorkerStatsDto { @Schema(description = "작업자 목록") private List workers; + + @Schema(description = "현재 페이지 번호 (0부터 시작)") + private Integer currentPage; + + @Schema(description = "페이지 크기") + private Integer pageSize; + + @Schema(description = "전체 데이터 수") + private Long totalElements; + + @Schema(description = "전체 페이지 수") + private Integer totalPages; } } 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 c919a5eb..1954a294 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 @@ -107,21 +107,23 @@ public class LabelAllocateService { } /** - * 작업자 목록 및 3일치 통계 조회 + * 작업자 통계 조회 * * @param analUid 분석 ID * @param workerType 작업자 유형 (LABELER/INSPECTOR) - * @param searchName 이름 검색 - * @param searchEmployeeNo 사번 검색 + * @param search 검색어 (이름 또는 사번) * @param sortType 정렬 조건 + * @param page 페이지 번호 (0부터 시작) + * @param size 페이지 크기 * @return 작업자 목록 및 통계 */ public WorkerListResponse getWorkerStatistics( Long analUid, String workerType, - String searchName, - String searchEmployeeNo, - String sortType) { + String search, + String sortType, + Integer page, + Integer size) { // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); @@ -129,7 +131,7 @@ public class LabelAllocateService { // 작업자 통계 조회 List workers = labelAllocateCoreService.findWorkerStatistics( - analUid, workerType, searchName, searchEmployeeNo, sortType); + analUid, workerType, search, sortType); // 각 작업자별 3일치 처리량 조회 LocalDate today = LocalDate.now(); @@ -162,7 +164,23 @@ public class LabelAllocateService { } } - return WorkerListResponse.builder().progressInfo(progressInfo).workers(workers).build(); + // 페이징 처리 + long totalElements = workers.size(); + int totalPages = (int) Math.ceil((double) totalElements / size); + int fromIndex = page * size; + int toIndex = Math.min(fromIndex + size, workers.size()); + + List pagedWorkers = + (fromIndex < workers.size()) ? workers.subList(fromIndex, toIndex) : List.of(); + + return WorkerListResponse.builder() + .progressInfo(progressInfo) + .workers(pagedWorkers) + .currentPage(page) + .pageSize(size) + .totalElements(totalElements) + .totalPages(totalPages) + .build(); } public InferenceDetail findInferenceDetail( 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 3e16eae5..c7469db6 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 @@ -57,11 +57,10 @@ public class LabelAllocateCoreService { public List findWorkerStatistics( Long analUid, String workerType, - String searchName, - String searchEmployeeNo, + String search, String sortType) { return labelAllocateRepository.findWorkerStatistics( - analUid, workerType, searchName, searchEmployeeNo, sortType); + analUid, workerType, search, sortType); } public WorkProgressInfo findWorkProgressInfo(Long analUid) { 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 22988c98..8fb3d96a 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 @@ -34,7 +34,7 @@ public interface LabelAllocateRepositoryCustom { // 작업자 통계 조회 List findWorkerStatistics( - Long analUid, String workerType, String searchName, String searchEmployeeNo, String sortType); + Long analUid, String workerType, String search, String sortType); // 작업 진행 현황 조회 WorkProgressInfo findWorkProgressInfo(Long analUid); 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 9e52da12..9f92dd3f 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 @@ -206,8 +206,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto public List findWorkerStatistics( Long analUid, String workerType, - String searchName, - String searchEmployeeNo, + String search, String sortType) { // 작업자 유형에 따른 필드 선택 @@ -221,14 +220,11 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto ? labelingAssignmentEntity.inspectorUid.isNotNull() : labelingAssignmentEntity.workerUid.isNotNull(); - // 검색 조건 + // 검색 조건 (이름 또는 사번으로 검색) BooleanExpression searchCondition = null; - if (searchName != null && !searchName.isEmpty()) { - searchCondition = memberEntity.name.contains(searchName); - } - if (searchEmployeeNo != null && !searchEmployeeNo.isEmpty()) { - BooleanExpression empCondition = memberEntity.employeeNo.contains(searchEmployeeNo); - searchCondition = searchCondition == null ? empCondition : searchCondition.and(empCondition); + if (search != null && !search.isEmpty()) { + searchCondition = memberEntity.name.contains(search) + .or(memberEntity.employeeNo.contains(search)); } // 완료, 스킵, 남은 작업 계산 @@ -258,6 +254,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .sum(); // 기본 통계 조회 쿼리 + BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; + var baseQuery = queryFactory .select( @@ -274,7 +272,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto "REVIEWER".equals(workerType) ? memberEntity.employeeNo.eq(labelingAssignmentEntity.inspectorUid) : memberEntity.employeeNo.eq(labelingAssignmentEntity.workerUid)) - .where(labelingAssignmentEntity.analUid.eq(analUid), workerCondition, searchCondition) + .where(analUidCondition, workerCondition, searchCondition) .groupBy(workerIdField, memberEntity.name); // 정렬 조건 적용 @@ -321,12 +319,14 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public WorkProgressInfo findWorkProgressInfo(Long analUid) { + BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; + // 전체 배정 건수 Long totalAssigned = queryFactory .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) - .where(labelingAssignmentEntity.analUid.eq(analUid)) + .where(analUidCondition) .fetchOne(); // 완료 + 스킵 건수 @@ -335,7 +335,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) .where( - labelingAssignmentEntity.analUid.eq(analUid), + analUidCondition, labelingAssignmentEntity.workState.in("DONE", "SKIP")) .fetchOne(); @@ -345,7 +345,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .select(labelingAssignmentEntity.workerUid.countDistinct()) .from(labelingAssignmentEntity) .where( - labelingAssignmentEntity.analUid.eq(analUid), + analUidCondition, labelingAssignmentEntity.workerUid.isNotNull()) .fetchOne(); @@ -355,7 +355,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) .where( - labelingAssignmentEntity.analUid.eq(analUid), + analUidCondition, labelingAssignmentEntity.workerUid.isNotNull(), labelingAssignmentEntity.workState.notIn("DONE", "SKIP")) .fetchOne(); @@ -366,7 +366,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .select(labelingAssignmentEntity.inspectorUid.countDistinct()) .from(labelingAssignmentEntity) .where( - labelingAssignmentEntity.analUid.eq(analUid), + analUidCondition, labelingAssignmentEntity.inspectorUid.isNotNull()) .fetchOne(); @@ -376,7 +376,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) .where( - labelingAssignmentEntity.analUid.eq(analUid), + analUidCondition, labelingAssignmentEntity.inspectorUid.isNotNull(), labelingAssignmentEntity.workState.notIn("DONE")) .fetchOne(); @@ -416,12 +416,14 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto ? labelingAssignmentEntity.inspectorUid.eq(workerId) : labelingAssignmentEntity.workerUid.eq(workerId); + BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; + Long count = queryFactory .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) .where( - labelingAssignmentEntity.analUid.eq(analUid), + analUidCondition, workerCondition, labelingAssignmentEntity.workState.in( LabelState.DONE.getId(), LabelState.SKIP.getId()), From b4ae6e148dcbf54e75ab13d9acc41480480349b4 Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Mon, 5 Jan 2026 11:46:21 +0900 Subject: [PATCH 03/14] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=ED=95=A0=EB=8B=B9=20?= =?UTF-8?q?=EA=B2=80=EC=88=98=EC=9E=90=20insert=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelAllocateApiController.java | 32 ++--- .../kamcoback/label/dto/LabelAllocateDto.java | 31 +--- .../label/dto/LabelInspectorDto.java | 22 +++ .../label/service/LabelAllocateService.java | 62 ++++---- .../core/LabelAllocateCoreService.java | 36 +++-- .../entity/LabelingInspectorEntity.java | 33 +++++ .../label/LabelAllocateRepositoryCustom.java | 23 ++- .../label/LabelAllocateRepositoryImpl.java | 135 ++++++++---------- 8 files changed, 190 insertions(+), 184 deletions(-) create mode 100644 src/main/java/com/kamco/cd/kamcoback/label/dto/LabelInspectorDto.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingInspectorEntity.java 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 6869b636..3f2133f7 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -122,8 +122,6 @@ public class LabelAllocateApiController { return ApiResponseDto.okObject( labelAllocateService.allocateAsc( - dto.getLabelerAutoType(), - dto.getInspectorAutoType(), dto.getStage(), dto.getLabelers(), dto.getInspectors(), @@ -140,13 +138,13 @@ public class LabelAllocateApiController { }) @GetMapping("/stage-detail") public ApiResponseDto findInferenceDetail( - @Parameter(description = "비교년도", required = true, example = "2022") @RequestParam - Integer compareYyyy, - @Parameter(description = "기준년도", required = true, example = "2024") @RequestParam - Integer targetYyyy, - @Parameter(description = "회차", required = true, example = "4") @RequestParam Integer stage) { - return ApiResponseDto.ok( - labelAllocateService.findInferenceDetail(compareYyyy, targetYyyy, stage)); + @Parameter( + description = "회차 마스터 key", + required = true, + example = "8584e8d4-53b3-4582-bde2-28a81495a626") + @RequestParam + String uuid) { + return ApiResponseDto.ok(labelAllocateService.findInferenceDetail(uuid)); } @Operation( @@ -154,14 +152,14 @@ public class LabelAllocateApiController { description = "작업현황 관리 > 라벨러 상세 정보, 작업이관 팝업 내 라벨러 상세 정보 동일") @GetMapping("/labeler-detail") public ApiResponseDto findLabelerDetail( - @RequestParam(defaultValue = "01022223333") String userId, - @Parameter(description = "비교년도", required = true, example = "2022") @RequestParam - Integer compareYyyy, - @Parameter(description = "기준년도", required = true, example = "2024") @RequestParam - Integer targetYyyy, - @Parameter(description = "회차", required = true, example = "4") @RequestParam Integer stage) { - return ApiResponseDto.ok( - labelAllocateService.findLabelerDetail(userId, compareYyyy, targetYyyy, stage)); + @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)); } @Operation(summary = "작업현황 관리 > 상세 > 작업 이관", description = "작업현황 관리 > 상세 > 작업 이관") 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 c0dd75a6..f5a060a8 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 @@ -87,22 +87,13 @@ public class LabelAllocateDto { @AllArgsConstructor public static class AllocateDto { - // @Schema(description = "분석 ID", example = "3") - // private Long analUid; - @Schema(description = "비교년도", example = "2022", required = true) private Integer compareYyyy; @Schema(description = "기준년도", example = "2024", required = true) private Integer targetYyyy; - @Schema(description = "라벨러 자동/수동여부(AUTO/MANUAL)", example = "AUTO") - private String labelerAutoType; - - @Schema(description = "검수자 자동/수동여부(AUTO/MANUAL)", example = "AUTO") - private String inspectorAutoType; - - @Schema(description = "회차", example = "4") + @Schema(description = "회차", example = "4", required = true) private Integer stage; @Schema( @@ -130,22 +121,12 @@ public class LabelAllocateDto { description = "검수자 할당 목록", example = """ - [ - { - "inspectorUid": "K20251216001", - "userCount": 1000 - }, - { - "inspectorUid": "01022225555", - "userCount": 340 - }, - { - "inspectorUid": "K20251212001", - "userCount": 500 - } + ["K20251216001", + "01022225555", + "K20251212001" ] """) - private List inspectors; + private List inspectors; } @Getter @@ -273,6 +254,6 @@ public class LabelAllocateDto { public static class AllocateInfoDto { private Long geoUid; - private String mapSheetNum; + private Long mapSheetNum; } } diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelInspectorDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelInspectorDto.java new file mode 100644 index 00000000..89fd8ef7 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelInspectorDto.java @@ -0,0 +1,22 @@ +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 LabelInspectorDto { + + @Getter + @Setter + @AllArgsConstructor + public static class Basic { + + private UUID operatorUid; + private Long analUid; + private String inspectorUid; + private ZonedDateTime createdDttm; + private ZonedDateTime updatedDttm; + } +} 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 c919a5eb..8a994a1f 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,7 +6,6 @@ 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.TargetInspector; 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,7 +15,6 @@ import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService; import java.time.LocalDate; import java.util.List; import java.util.Objects; -import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -38,19 +36,15 @@ public class LabelAllocateService { /** * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직 * - * @param labelerAutoType 라벨러 자동/수동 배정 타입 - * @param inspectorAutoType 검수자 자동/수동 배정 타입 * @param stage 회차 * @param targetUsers 라벨러 목록 * @param targetInspectors 검수자 목록 */ @Transactional public ApiResponseDto.ResponseObj allocateAsc( - String labelerAutoType, - String inspectorAutoType, Integer stage, List targetUsers, - List targetInspectors, + List targetInspectors, Integer compareYyyy, Integer targetYyyy) { Long lastId = null; @@ -70,35 +64,45 @@ public class LabelAllocateService { List allIds = labelAllocateCoreService.fetchNextIds(lastId, chargeCnt, compareYyyy, targetYyyy, stage); + + // MapSheetAnalInferenceEntity analUid 가져오기 + Long analUid = + labelAllocateCoreService.findMapSheetAnalInferenceUid(compareYyyy, targetYyyy, stage); + int index = 0; for (TargetUser target : targetUsers) { int end = index + target.getDemand(); List sub = allIds.subList(index, end); - labelAllocateCoreService.assignOwner(sub, target.getUserId(), compareYyyy, targetYyyy, stage); + labelAllocateCoreService.assignOwner(sub, target.getUserId(), analUid); index = end; } // 검수자에게 userCount명 만큼 할당 - List list = - labelAllocateCoreService.findAssignedLabelerList(compareYyyy, targetYyyy, stage); - int from = 0; + List list = labelAllocateCoreService.findAssignedLabelerList(analUid); - for (TargetInspector inspector : targetInspectors) { - int to = Math.min(from + inspector.getUserCount(), list.size()); - - if (from >= to) { - break; - } - - List assignmentUids = - list.subList(from, to).stream().map(LabelAllocateDto.Basic::getAssignmentUid).toList(); - - labelAllocateCoreService.assignInspectorBulk(assignmentUids, inspector.getInspectorUid()); - - from = to; + for (String inspector : targetInspectors) { + labelAllocateCoreService.insertInspector(analUid, inspector); } + // int from = 0; + // for (TargetInspector inspector : targetInspectors) { + // int to = Math.min(from + inspector.getUserCount(), list.size()); + // + // if (from >= to) { + // break; + // } + // + // List assignmentUids = + // list.subList(from, + // to).stream().map(LabelAllocateDto.Basic::getAssignmentUid).toList(); + // + // labelAllocateCoreService.assignInspectorBulk(assignmentUids, + // inspector.getInspectorUid()); + // + // from = to; + // } + return new ApiResponseDto.ResponseObj(ApiResponseCode.OK, "배정이 완료되었습니다."); } @@ -165,9 +169,8 @@ public class LabelAllocateService { return WorkerListResponse.builder().progressInfo(progressInfo).workers(workers).build(); } - public InferenceDetail findInferenceDetail( - Integer compareYyyy, Integer targetYyyy, Integer stage) { - return labelAllocateCoreService.findInferenceDetail(compareYyyy, targetYyyy, stage); + public InferenceDetail findInferenceDetail(String uuid) { + return labelAllocateCoreService.findInferenceDetail(uuid); } public ApiResponseDto.ResponseObj allocateMove( @@ -199,8 +202,7 @@ public class LabelAllocateService { return new ApiResponseDto.ResponseObj(ApiResponseCode.OK, "이관을 완료하였습니다."); } - public LabelerDetail findLabelerDetail( - String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { - return labelAllocateCoreService.findLabelerDetail(userId, compareYyyy, targetYyyy, stage); + public LabelerDetail findLabelerDetail(String userId, String uuid) { + return labelAllocateCoreService.findLabelerDetail(userId, uuid); } } 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 3e16eae5..5fd49cf5 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 @@ -26,18 +26,12 @@ public class LabelAllocateCoreService { return labelAllocateRepository.fetchNextIds(lastId, batchSize, compareYyyy, targetYyyy, stage); } - public void assignOwner( - List ids, - String userId, - Integer compareYyyy, - Integer targetYyyy, - Integer stage) { - labelAllocateRepository.assignOwner(ids, userId, compareYyyy, targetYyyy, stage); + public void assignOwner(List ids, String userId, Long analUid) { + labelAllocateRepository.assignOwner(ids, userId, analUid); } - public List findAssignedLabelerList( - Integer compareYyyy, Integer targetYyyy, Integer stage) { - return labelAllocateRepository.findAssignedLabelerList(compareYyyy, targetYyyy, stage).stream() + public List findAssignedLabelerList(Long analUid) { + return labelAllocateRepository.findAssignedLabelerList(analUid).stream() .map(LabelingAssignmentEntity::toDto) .toList(); } @@ -77,13 +71,8 @@ public class LabelAllocateCoreService { labelAllocateRepository.assignInspectorBulk(assignmentUids, inspectorUid); } - public InferenceDetail findInferenceDetail( - Integer compareYyyy, Integer targetYyyy, Integer stage) { - return labelAllocateRepository.findInferenceDetail(compareYyyy, targetYyyy, stage); - } - - public Long findLabelUnCompleteCnt(Long analUid) { - return labelAllocateRepository.findLabelUnCompleteCnt(analUid); + public InferenceDetail findInferenceDetail(String uuid) { + return labelAllocateRepository.findInferenceDetail(uuid); } public List fetchNextMoveIds( @@ -96,8 +85,15 @@ public class LabelAllocateCoreService { labelAllocateRepository.assignOwnerMove(sub, userId); } - public LabelerDetail findLabelerDetail( - String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { - return labelAllocateRepository.findLabelerDetail(userId, compareYyyy, targetYyyy, stage); + public LabelerDetail findLabelerDetail(String userId, String uuid) { + return labelAllocateRepository.findLabelerDetail(userId, uuid); + } + + public Long findMapSheetAnalInferenceUid(Integer compareYyyy, Integer targetYyyy, Integer stage) { + return labelAllocateRepository.findMapSheetAnalInferenceUid(compareYyyy, targetYyyy, stage); + } + + public void insertInspector(Long analUid, String inspector) { + labelAllocateRepository.insertInspector(analUid, inspector); } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingInspectorEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingInspectorEntity.java new file mode 100644 index 00000000..5da0a685 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingInspectorEntity.java @@ -0,0 +1,33 @@ +package com.kamco.cd.kamcoback.postgres.entity; + +import com.kamco.cd.kamcoback.label.dto.LabelInspectorDto; +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_inspector") +public class LabelingInspectorEntity extends CommonDateEntity { + + @Id + @Column(name = "operator_uid") + private UUID operatorUid; + + @Column(name = "anal_uid") + private Long analUid; + + @Column(name = "inspector_uid") + private String inspectorUid; + + public LabelInspectorDto.Basic toDto() { + return new LabelInspectorDto.Basic( + this.operatorUid, + this.analUid, + this.inspectorUid, + super.getCreatedDate(), + super.getModifiedDate()); + } +} 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 22988c98..72a1534e 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 @@ -16,15 +16,9 @@ public interface LabelAllocateRepositoryCustom { List fetchNextIds( Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); - void assignOwner( - List ids, - String userId, - Integer compareYyyy, - Integer targetYyyy, - Integer stage); + void assignOwner(List ids, String userId, Long analUid); - List findAssignedLabelerList( - Integer compareYyyy, Integer targetYyyy, Integer stage); + List findAssignedLabelerList(Long analUid); Long findLabelUnAssignedCnt(Integer stage, Integer compareYyyy, Integer targetYyyy); @@ -44,15 +38,16 @@ public interface LabelAllocateRepositoryCustom { void assignInspectorBulk(List assignmentUids, String inspectorUid); - InferenceDetail findInferenceDetail(Integer compareYyyy, Integer targetYyyy, Integer stage); + InferenceDetail findInferenceDetail(String uuid); - public List fetchNextMoveIds( + List fetchNextMoveIds( Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); - Long findLabelUnCompleteCnt(Long analUid); - void assignOwnerMove(List sub, String userId); - LabelerDetail findLabelerDetail( - String userId, Integer compareYyyy, Integer targetYyyy, Integer stage); + LabelerDetail findLabelerDetail(String userId, String uuid); + + Long findMapSheetAnalInferenceUid(Integer compareYyyy, Integer targetYyyy, Integer stage); + + void insertInspector(Long analUid, String inspector); } 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 9e52da12..37f54a38 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 @@ -1,9 +1,10 @@ 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.QMapSheetAnalDataInferenceEntity.mapSheetAnalDataInferenceEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QLabelingInspectorEntity.labelingInspectorEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalEntity.mapSheetAnalEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMemberEntity.memberEntity; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; @@ -16,8 +17,7 @@ import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkProgressInfo; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity; -import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceEntity; -import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalEntity; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalInferenceEntity; import com.kamco.cd.kamcoback.postgres.entity.QMemberEntity; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; @@ -73,27 +73,17 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto } @Override - public void assignOwner( - List ids, - String userId, - Integer compareYyyy, - Integer targetYyyy, - Integer stage) { + public void assignOwner(List ids, String userId, Long analUid) { // analUid로 분석 정보 조회 - MapSheetAnalDataInferenceEntity analEntity = + MapSheetAnalInferenceEntity analEntity = queryFactory - .selectFrom(mapSheetAnalDataInferenceEntity) - .where( - mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy), - mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy), - mapSheetAnalDataInferenceEntity.stage.eq(stage)) - .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc()) - .limit(1) + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.id.eq(analUid)) .fetchOne(); if (Objects.isNull(analEntity)) { - throw new EntityNotFoundException("MapSheetAnalEntity not found for analUid: "); + throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: "); } // data_geom 테이블에 label state 를 ASSIGNED 로 update @@ -125,7 +115,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .setParameter(3, userId) .setParameter(4, LabelState.ASSIGNED.getId()) .setParameter(5, info.getMapSheetNum()) - .setParameter(6, analEntity.getAnalUid()) + .setParameter(6, analEntity.getId()) .executeUpdate(); } @@ -134,28 +124,22 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto } @Override - public List findAssignedLabelerList( - Integer compareYyyy, Integer targetYyyy, Integer stage) { + public List findAssignedLabelerList(Long analUid) { // analUid로 분석 정보 조회 - MapSheetAnalDataInferenceEntity analEntity = + MapSheetAnalInferenceEntity analEntity = queryFactory - .selectFrom(mapSheetAnalDataInferenceEntity) - .where( - mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy), - mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy), - mapSheetAnalDataInferenceEntity.stage.eq(stage)) - .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc()) - .limit(1) + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.id.eq(analUid)) .fetchOne(); if (Objects.isNull(analEntity)) { - throw new EntityNotFoundException("MapSheetAnalEntity not found for analUid: "); + throw new EntityNotFoundException("mapSheetAnalInferenceEntity not found for analUid: "); } return queryFactory .selectFrom(labelingAssignmentEntity) .where( - labelingAssignmentEntity.analUid.eq(analEntity.getAnalUid()), + labelingAssignmentEntity.analUid.eq(analEntity.getId()), labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId()), labelingAssignmentEntity.inspectorUid.isNull()) .orderBy(labelingAssignmentEntity.workerUid.asc()) @@ -443,20 +427,18 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto } @Override - public InferenceDetail findInferenceDetail( - Integer compareYyyy, Integer targetYyyy, Integer stage) { + public InferenceDetail findInferenceDetail(String uuid) { // analUid로 분석 정보 조회 - MapSheetAnalDataInferenceEntity analEntity = + MapSheetAnalInferenceEntity analEntity = queryFactory - .selectFrom(mapSheetAnalDataInferenceEntity) - .where( - mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy), - mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy), - mapSheetAnalDataInferenceEntity.stage.eq(stage)) - .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc()) - .limit(1) + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) .fetchOne(); + if (Objects.isNull(analEntity)) { + throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: "); + } + return queryFactory .select( Projections.constructor( @@ -470,7 +452,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .from(mapSheetAnalEntity) .innerJoin(labelingAssignmentEntity) .on(mapSheetAnalEntity.id.eq(labelingAssignmentEntity.analUid)) - .where(mapSheetAnalEntity.id.eq(analEntity.getAnalUid())) + .where(mapSheetAnalEntity.id.eq(analEntity.getId())) .groupBy( mapSheetAnalEntity.analTitle, mapSheetAnalEntity.gukyuinApplyDttm, @@ -498,30 +480,6 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .fetch(); } - @Override - public Long findLabelUnCompleteCnt(Long analUid) { - MapSheetAnalEntity entity = - queryFactory - .selectFrom(mapSheetAnalEntity) - .where(mapSheetAnalEntity.id.eq(analUid)) - .fetchOne(); - - if (Objects.isNull(entity)) { - throw new EntityNotFoundException(); - } - - return queryFactory - .select(mapSheetAnalDataInferenceGeomEntity.geoUid.count()) - .from(mapSheetAnalDataInferenceGeomEntity) - .where( - mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(entity.getCompareYyyy()), - mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(entity.getTargetYyyy()), - mapSheetAnalDataInferenceGeomEntity.stage.eq(4), // TODO: 회차 컬럼을 가져와야 할 듯? - mapSheetAnalDataInferenceGeomEntity.labelState.in( - LabelState.ASSIGNED.getId(), LabelState.SKIP.getId())) - .fetchOne(); - } - @Transactional @Override public void assignOwnerMove(List sub, String userId) { @@ -535,8 +493,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto } @Override - public LabelerDetail findLabelerDetail( - String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { + public LabelerDetail findLabelerDetail(String userId, String uuid) { NumberExpression assignedCnt = new CaseBuilder() .when(labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId())) @@ -570,19 +527,14 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto completeCnt)); // analUid로 분석 정보 조회 - MapSheetAnalDataInferenceEntity analEntity = + MapSheetAnalInferenceEntity analEntity = queryFactory - .selectFrom(mapSheetAnalDataInferenceEntity) - .where( - mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy), - mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy), - mapSheetAnalDataInferenceEntity.stage.eq(stage)) - .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc()) - .limit(1) + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) .fetchOne(); if (Objects.isNull(analEntity)) { - throw new EntityNotFoundException("MapSheetAnalEntity not found for analUid: "); + throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: "); } QMemberEntity worker = QMemberEntity.memberEntity; @@ -606,11 +558,38 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .innerJoin(labelingAssignmentEntity) .on( worker.employeeNo.eq(labelingAssignmentEntity.workerUid), - labelingAssignmentEntity.analUid.eq(analEntity.getAnalUid())) + 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 + public Long findMapSheetAnalInferenceUid(Integer compareYyyy, Integer targetYyyy, Integer stage) { + return queryFactory + .select(mapSheetAnalInferenceEntity.id) + .from(mapSheetAnalInferenceEntity) + .where( + mapSheetAnalInferenceEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalInferenceEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalInferenceEntity.stage.eq(stage)) + .fetchOne(); + } + + @Override + public void insertInspector(Long analUid, String inspector) { + queryFactory + .insert(labelingInspectorEntity) + .columns( + labelingInspectorEntity.operatorUid, + labelingInspectorEntity.analUid, + labelingInspectorEntity.inspectorUid) + .values(UUID.randomUUID(), analUid, inspector) + .execute(); + + em.flush(); + em.clear(); + } } From 96f5a87b1a7173288f9625c18b8e393c7a9d7e6e Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Mon, 5 Jan 2026 12:33:48 +0900 Subject: [PATCH 04/14] =?UTF-8?q?=EC=9E=91=EC=97=85=ED=98=84=ED=99=A9?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=EA=B0=92=20=EC=B6=94=EA=B0=80=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelAllocateApiController.java | 2 + .../kamcoback/label/dto/WorkerStatsDto.java | 87 +++++++++++++++++-- .../label/LabelAllocateRepositoryImpl.java | 6 ++ 3 files changed, 86 insertions(+), 9 deletions(-) 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 1395f42b..4e0f9373 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -85,6 +85,8 @@ public class LabelAllocateApiController { allowableValues = { "REMAINING_DESC", "REMAINING_ASC", + "COMPLETED_DESC", + "COMPLETED_ASC", "NAME_ASC", "NAME_DESC" }, diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java index f879f0ee..e95dd346 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java @@ -10,6 +10,27 @@ import lombok.Setter; public class WorkerStatsDto { + @Getter + @Setter + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "프로젝트 기본 정보 (상단 표시용)") + public static class ProjectInfo { + + @Schema(description = "변화탐지년도 (예: 2026-2025)") + private String detectionYear; + + @Schema(description = "회차 (예: 8)") + private String round; + + @Schema(description = "국유인 반영일 (예: 2026-03-31)") + private String reflectionDate; + + @Schema(description = "작업 시작일 (예: 2026-04-06)") + private String startDate; + } + @Getter @Setter @Builder @@ -75,29 +96,74 @@ public class WorkerStatsDto { @Schema(description = "작업 진행 현황 정보") public static class WorkProgressInfo { + // === 라벨링 관련 === @Schema(description = "라벨링 진행률 (완료건+스킵건)/배정건") private Double labelingProgressRate; - @Schema(description = "작업 상태 (진행중/종료)") - private String workStatus; + @Schema(description = "라벨링 작업 상태 (진행중/완료)") + private String labelingStatus; - @Schema(description = "진행률 수치 (완료+스킵)") - private Long completedCount; + @Schema(description = "라벨링 전체 배정 건수") + private Long labelingTotalCount; - @Schema(description = "전체 배정 건수") - private Long totalAssignedCount; + @Schema(description = "라벨링 완료 건수 (LABEL_FIN + TEST_ING + DONE)") + private Long labelingCompletedCount; + + @Schema(description = "라벨링 스킵 건수 (SKIP)") + private Long labelingSkipCount; + + @Schema(description = "라벨링 남은 작업 건수") + private Long labelingRemainingCount; @Schema(description = "투입된 라벨러 수") private Long labelerCount; - @Schema(description = "남은 라벨링 작업 데이터 수") - private Long remainingLabelCount; + // === 검수(Inspection) 관련 (신규 추가) === + @Schema(description = "검수 진행률 (완료건/대상건)") + private Double inspectionProgressRate; + + @Schema(description = "검수 작업 상태 (진행중/완료)") + private String inspectionStatus; + + @Schema(description = "검수 전체 대상 건수") + private Long inspectionTotalCount; + + @Schema(description = "검수 완료 건수 (DONE)") + private Long inspectionCompletedCount; + + @Schema(description = "검수 제외 건수 (라벨링 스킵과 동일)") + private Long inspectionSkipCount; + + @Schema(description = "검수 남은 작업 건수") + private Long inspectionRemainingCount; @Schema(description = "투입된 검수자 수") private Long inspectorCount; - @Schema(description = "남은 검수 작업 데이터 수") + // === 레거시 호환 필드 (Deprecated) === + @Deprecated + @Schema(description = "[Deprecated] labelingProgressRate 사용 권장") + private Double progressRate; + + @Deprecated + @Schema(description = "[Deprecated] labelingTotalCount 사용 권장") + private Long totalAssignedCount; + + @Deprecated + @Schema(description = "[Deprecated] labelingCompletedCount 사용 권장") + private Long completedCount; + + @Deprecated + @Schema(description = "[Deprecated] labelingRemainingCount 사용 권장") + private Long remainingLabelCount; + + @Deprecated + @Schema(description = "[Deprecated] inspectionRemainingCount 사용 권장") private Long remainingInspectCount; + + @Deprecated + @Schema(description = "[Deprecated] labelingStatus/inspectionStatus 사용 권장") + private String workStatus; } @Getter @@ -108,6 +174,9 @@ public class WorkerStatsDto { @Schema(description = "작업자 목록 응답 (작업 정보 포함)") public static class WorkerListResponse { + @Schema(description = "프로젝트 기본 정보 (상단 표시용)") + private ProjectInfo projectInfo; + @Schema(description = "작업 진행 현황 정보") private WorkProgressInfo progressInfo; 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 6d973dad..d6ceeb0f 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 @@ -268,6 +268,12 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto case "REMAINING_ASC": baseQuery.orderBy(remainingSum.asc()); break; + case "COMPLETED_DESC": + baseQuery.orderBy(completedSum.desc()); + break; + case "COMPLETED_ASC": + baseQuery.orderBy(completedSum.asc()); + break; case "NAME_ASC": baseQuery.orderBy(memberEntity.name.asc()); break; From 040aabe95e252f25d11b693555e4cf1df89f8218 Mon Sep 17 00:00:00 2001 From: Moon Date: Mon, 5 Jan 2026 12:41:07 +0900 Subject: [PATCH 05/14] =?UTF-8?q?=EC=9E=91=EC=97=85=ED=98=84=ED=99=A9=20DT?= =?UTF-8?q?O=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kamcoback/label/dto/WorkerStatsDto.java | 40 ++++++------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java index efca81b2..6df63cf0 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java @@ -31,42 +31,28 @@ public class WorkerStatsDto { private Long totalAssigned; @Schema(description = "완료 건수") - private Long completed; + private Long doneCnt; @Schema(description = "스킵 건수") - private Long skipped; + private Long skipCnt; @Schema(description = "남은 작업 건수") - private Long remaining; - - @Schema(description = "최근 3일간 처리 이력") - private DailyHistory history; - - @Schema(description = "작업 정체 여부 (3일간 실적이 저조하면 true)") - private Boolean isStagnated; - } - - @Getter - @Setter - @Builder - @NoArgsConstructor - @AllArgsConstructor - @Schema(description = "최근 3일간 일일 처리 이력") - public static class DailyHistory { - - @Schema(description = "1일 전 (어제) 처리량") - private Long day1Ago; - - @Schema(description = "2일 전 처리량") - private Long day2Ago; + private Long remainingCnt; @Schema(description = "3일 전 처리량") - private Long day3Ago; + private Long day3AgoDoneCnt; - @Schema(description = "3일 평균 처리량") - private Long average; + @Schema(description = "2일 전 처리량") + private Long day2AgoDoneCnt; + + @Schema(description = "1일 전 처리량") + private Long day1AgoDoneCnt; + + //@Schema(description = "작업 정체 여부 (3일간 실적이 저조하면 true)") + //private Boolean isStagnated; } + @Getter @Setter @Builder From 06d4b597cb32fa8bfd9bef28378f38aae7d01d16 Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Mon, 5 Jan 2026 12:42:10 +0900 Subject: [PATCH 06/14] =?UTF-8?q?=EC=9E=91=EC=97=85=ED=98=84=ED=99=A9?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20Data=20accept=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/service/LabelAllocateService.java | 4 + .../core/LabelAllocateCoreService.java | 5 + .../label/LabelAllocateRepositoryCustom.java | 4 + .../label/LabelAllocateRepositoryImpl.java | 159 ++++++++++++++---- 4 files changed, 140 insertions(+), 32 deletions(-) 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 7da6156c..ae0ada1d 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 @@ -129,6 +129,9 @@ public class LabelAllocateService { Integer page, Integer size) { + // 프로젝트 정보 조회 (analUid가 있을 때만) + var projectInfo = labelAllocateCoreService.findProjectInfo(analUid); + // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); @@ -178,6 +181,7 @@ public class LabelAllocateService { (fromIndex < workers.size()) ? workers.subList(fromIndex, toIndex) : List.of(); return WorkerListResponse.builder() + .projectInfo(projectInfo) .progressInfo(progressInfo) .workers(pagedWorkers) .currentPage(page) 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 d71471c0..a16252fa 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 @@ -5,6 +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.LabelerDetail; 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; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity; @@ -48,6 +49,10 @@ public class LabelAllocateCoreService { return labelAllocateRepository.availUserList(role); } + public ProjectInfo findProjectInfo(Long analUid) { + return labelAllocateRepository.findProjectInfo(analUid); + } + public List findWorkerStatistics( Long analUid, String workerType, 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 a127e142..739c30c2 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 @@ -4,6 +4,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.LabelerDetail; 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; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity; @@ -26,6 +27,9 @@ public interface LabelAllocateRepositoryCustom { List availUserList(String role); + // 프로젝트 정보 조회 + ProjectInfo findProjectInfo(Long analUid); + // 작업자 통계 조회 List findWorkerStatistics( Long analUid, String workerType, String search, String sortType); 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 d6ceeb0f..141bace8 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 @@ -14,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.LabelerDetail; 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; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity; @@ -34,9 +35,12 @@ import java.time.LocalDate; import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Objects; import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Repository; @@ -319,17 +323,28 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .where(analUidCondition) .fetchOne(); - // 완료 + 스킵 건수 - Long completedCount = + // === 라벨링 통계 === + // 라벨링 완료: LABEL_FIN, TEST_ING, DONE (검수 포함) + Long labelingCompleted = queryFactory .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) .where( analUidCondition, - labelingAssignmentEntity.workState.in("DONE", "SKIP")) + labelingAssignmentEntity.workState.in("LABEL_FIN", "TEST_ING", "DONE")) .fetchOne(); - // 투입된 라벨러 수 (고유한 worker_uid 수) + // 스킵 건수 + Long skipCount = + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + analUidCondition, + labelingAssignmentEntity.workState.eq("SKIP")) + .fetchOne(); + + // 투입된 라벨러 수 Long labelerCount = queryFactory .select(labelingAssignmentEntity.workerUid.countDistinct()) @@ -339,18 +354,18 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto labelingAssignmentEntity.workerUid.isNotNull()) .fetchOne(); - // 남은 라벨링 작업 데이터 수 - Long remainingLabelCount = + // === 검수 통계 === + // 검수 완료: DONE만 + Long inspectionCompleted = queryFactory .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) .where( analUidCondition, - labelingAssignmentEntity.workerUid.isNotNull(), - labelingAssignmentEntity.workState.notIn("DONE", "SKIP")) + labelingAssignmentEntity.workState.eq("DONE")) .fetchOne(); - // 투입된 검수자 수 (고유한 inspector_uid 수) + // 투입된 검수자 수 Long inspectorCount = queryFactory .select(labelingAssignmentEntity.inspectorUid.countDistinct()) @@ -360,36 +375,47 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto labelingAssignmentEntity.inspectorUid.isNotNull()) .fetchOne(); - // 남은 검수 작업 데이터 수 - Long remainingInspectCount = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.inspectorUid.isNotNull(), - labelingAssignmentEntity.workState.notIn("DONE")) - .fetchOne(); + // 남은 작업 건수 계산 + long total = totalAssigned != null ? totalAssigned : 0L; + long labelCompleted = labelingCompleted != null ? labelingCompleted : 0L; + long inspectCompleted = inspectionCompleted != null ? inspectionCompleted : 0L; + long skipped = skipCount != null ? skipCount : 0L; + + long labelingRemaining = total - labelCompleted - skipped; + long inspectionRemaining = total - inspectCompleted - skipped; // 진행률 계산 - double progressRate = 0.0; - if (totalAssigned != null && totalAssigned > 0) { - progressRate = - (completedCount != null ? completedCount.doubleValue() : 0.0) / totalAssigned * 100; - } + double labelingRate = total > 0 ? (double) labelCompleted / total * 100 : 0.0; + double inspectionRate = total > 0 ? (double) inspectCompleted / total * 100 : 0.0; - // 작업 상태 판단 (간단하게 진행률 100%면 종료, 아니면 진행중) - String workStatus = (progressRate >= 100.0) ? "종료" : "진행중"; + // 상태 판단 + String labelingStatus = labelingRemaining > 0 ? "진행중" : "완료"; + String inspectionStatus = inspectionRemaining > 0 ? "진행중" : "완료"; return WorkProgressInfo.builder() - .labelingProgressRate(progressRate) - .workStatus(workStatus) - .completedCount(completedCount != null ? completedCount : 0L) - .totalAssignedCount(totalAssigned != null ? totalAssigned : 0L) + // 라벨링 + .labelingProgressRate(labelingRate) + .labelingStatus(labelingStatus) + .labelingTotalCount(total) + .labelingCompletedCount(labelCompleted) + .labelingSkipCount(skipped) + .labelingRemainingCount(labelingRemaining) .labelerCount(labelerCount != null ? labelerCount : 0L) - .remainingLabelCount(remainingLabelCount != null ? remainingLabelCount : 0L) + // 검수 + .inspectionProgressRate(inspectionRate) + .inspectionStatus(inspectionStatus) + .inspectionTotalCount(total) + .inspectionCompletedCount(inspectCompleted) + .inspectionSkipCount(skipped) + .inspectionRemainingCount(inspectionRemaining) .inspectorCount(inspectorCount != null ? inspectorCount : 0L) - .remainingInspectCount(remainingInspectCount != null ? remainingInspectCount : 0L) + // 레거시 호환 필드 (Deprecated) + .progressRate(labelingRate) + .totalAssignedCount(total) + .completedCount(labelCompleted) + .remainingLabelCount(labelingRemaining) + .remainingInspectCount(inspectionRemaining) + .workStatus(labelingStatus) .build(); } @@ -600,4 +626,73 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto em.flush(); em.clear(); } + + @Override + public ProjectInfo findProjectInfo(Long analUid) { + if (analUid == null) { + return null; + } + + var result = queryFactory + .select( + mapSheetAnalEntity.compareYyyy, + mapSheetAnalEntity.targetYyyy, + mapSheetAnalEntity.analTitle, + mapSheetAnalEntity.gukyuinApplyDttm, + mapSheetAnalEntity.analStrtDttm + ) + .from(mapSheetAnalEntity) + .where(mapSheetAnalEntity.id.eq(analUid)) + .fetchOne(); + + if (result == null) { + return null; + } + + Integer compareYyyy = result.get(mapSheetAnalEntity.compareYyyy); + Integer targetYyyy = result.get(mapSheetAnalEntity.targetYyyy); + String analTitle = result.get(mapSheetAnalEntity.analTitle); + ZonedDateTime gukyuinApplyDttm = result.get(mapSheetAnalEntity.gukyuinApplyDttm); + ZonedDateTime analStrtDttm = result.get(mapSheetAnalEntity.analStrtDttm); + + // 변화탐지년도 생성 + String detectionYear = (compareYyyy != null && targetYyyy != null) + ? compareYyyy + "-" + targetYyyy + : null; + + // 회차 추출 (예: "8회차" → "8") + String round = extractRoundFromTitle(analTitle); + + return ProjectInfo.builder() + .detectionYear(detectionYear) + .round(round) + .reflectionDate(formatDate(gukyuinApplyDttm)) + .startDate(formatDate(analStrtDttm)) + .build(); + } + + /** + * 제목에서 회차 숫자 추출 + * 예: "8회차", "제8회차" → "8" + */ + private String extractRoundFromTitle(String title) { + if (title == null || title.isEmpty()) { + return null; + } + + Pattern pattern = Pattern.compile("(\\d+)회차"); + Matcher matcher = pattern.matcher(title); + + return matcher.find() ? matcher.group(1) : null; + } + + /** + * ZonedDateTime을 "yyyy-MM-dd" 형식으로 변환 + */ + private String formatDate(ZonedDateTime dateTime) { + if (dateTime == null) { + return null; + } + return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + } } From ba0e408c2b7e1d1fe4feaecb893374999c234020 Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Mon, 5 Jan 2026 12:45:50 +0900 Subject: [PATCH 07/14] Build Error Fix --- .../kamcoback/label/dto/WorkerStatsDto.java | 54 +++++++++++++++---- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java index 581aac7e..3501da8a 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java @@ -52,25 +52,59 @@ public class WorkerStatsDto { private Long totalAssigned; @Schema(description = "완료 건수") - private Long doneCnt; + private Long completed; @Schema(description = "스킵 건수") - private Long skipCnt; + private Long skipped; @Schema(description = "남은 작업 건수") - private Long remainingCnt; + private Long remaining; - @Schema(description = "3일 전 처리량") - private Long day3AgoDoneCnt; + @Schema(description = "최근 3일간 처리 이력") + private DailyHistory history; + + @Schema(description = "작업 정체 여부 (3일간 실적이 저조하면 true)") + private Boolean isStagnated; + + // 레거시 필드 (기존 호환성 유지) + @Deprecated + private Long doneCnt; // completed로 대체 + + @Deprecated + private Long skipCnt; // skipped로 대체 + + @Deprecated + private Long remainingCnt; // remaining으로 대체 + + @Deprecated + private Long day3AgoDoneCnt; // history.day3Ago로 대체 + + @Deprecated + private Long day2AgoDoneCnt; // history.day2Ago로 대체 + + @Deprecated + private Long day1AgoDoneCnt; // history.day1Ago로 대체 + } + + @Getter + @Setter + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "최근 3일간 일일 처리 이력") + public static class DailyHistory { + + @Schema(description = "1일 전 (어제) 처리량") + private Long day1Ago; @Schema(description = "2일 전 처리량") - private Long day2AgoDoneCnt; + private Long day2Ago; - @Schema(description = "1일 전 처리량") - private Long day1AgoDoneCnt; + @Schema(description = "3일 전 처리량") + private Long day3Ago; - //@Schema(description = "작업 정체 여부 (3일간 실적이 저조하면 true)") - //private Boolean isStagnated; + @Schema(description = "3일 평균 처리량") + private Long average; } From 877de7f17e176353fbc149fc1f7cb5df3579ea4f Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Mon, 5 Jan 2026 12:59:21 +0900 Subject: [PATCH 08/14] =?UTF-8?q?=EC=A3=BC=EC=84=9D=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cd/kamcoback/label/LabelAllocateApiController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 4e0f9373..cb723a69 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -63,8 +63,8 @@ public class LabelAllocateApiController { }) @GetMapping("/admin/workers") public ApiResponseDto getWorkerStatistics( - @Parameter(description = "분석 ID (선택)", example = "3") @RequestParam(required = false) - Long analUid, + // @Parameter(description = "분석 ID (선택)", example = "3") @RequestParam(required = false) + // Long analUid, @Parameter( description = "작업자 유형 (선택) - 미입력 시 LABELER로 조회", example = "LABELER", @@ -105,7 +105,7 @@ public class LabelAllocateApiController { return ApiResponseDto.ok( labelAllocateService.getWorkerStatistics( - analUid, workerType, search, sort, page, size)); + null, workerType, search, sort, page, size)); } @Operation(summary = "라벨링작업 관리 > 작업 배정", description = "라벨링작업 관리 > 작업 배정") From f5fcc2a9e3b4caaa11f452b358ccedf985aba155 Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Mon, 5 Jan 2026 13:25:38 +0900 Subject: [PATCH 09/14] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=EB=9F=AC,=EA=B2=80?= =?UTF-8?q?=EC=88=98=EC=9E=90=20=EC=83=81=EC=84=B8=20=EC=A0=95=EB=B3=B4,?= =?UTF-8?q?=20=EC=9D=BC=EC=9E=90=EB=B3=84=20=EB=AA=A9=EB=A1=9D=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelAllocateApiController.java | 284 +++--- .../kamcoback/label/dto/LabelAllocateDto.java | 66 +- .../label/service/LabelAllocateService.java | 120 ++- .../core/LabelAllocateCoreService.java | 37 +- .../entity/LabelingAssignmentEntity.java | 36 +- .../label/LabelAllocateRepositoryCustom.java | 15 +- .../label/LabelAllocateRepositoryImpl.java | 911 ++++++++++++------ 7 files changed, 948 insertions(+), 521 deletions(-) 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 1395f42b..b235ccd9 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -5,6 +5,7 @@ import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; 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.WorkerStatsDto.WorkerListResponse; import com.kamco.cd.kamcoback.label.service.LabelAllocateService; import io.swagger.v3.oas.annotations.Operation; @@ -18,6 +19,7 @@ import jakarta.validation.Valid; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -36,168 +38,204 @@ public class LabelAllocateApiController { @Operation(summary = "배정 가능한 사용자 목록 조회", description = "라벨링 작업 배정을 위한 활성 상태의 사용자 목록을 조회합니다.") @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "조회 성공"), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음"), - @ApiResponse(responseCode = "500", description = "서버 오류") - }) + value = { + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) @GetMapping("/avail-user") public ApiResponseDto> availUserList( - @Parameter( - description = "사용자 역할", - example = "LABELER", - schema = @Schema(allowableValues = {"LABELER", "REVIEWER"})) - @RequestParam - String role) { + @Parameter( + description = "사용자 역할", + example = "LABELER", + schema = @Schema(allowableValues = {"LABELER", "REVIEWER"})) + @RequestParam + String role) { return ApiResponseDto.ok(labelAllocateService.availUserList(role)); } @Operation( - summary = "작업현황 관리 (라벨링, 검수 진행률 요약정보, 작업자 목록)", - description = "작업현황 관리 (라벨링, 검수 진행률 요약정보, 작업자 목록)") + summary = "작업현황 관리 (라벨링, 검수 진행률 요약정보, 작업자 목록)", + description = "작업현황 관리 (라벨링, 검수 진행률 요약정보, 작업자 목록)") @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "조회 성공"), - @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), - @ApiResponse(responseCode = "500", description = "서버 오류") - }) + value = { + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) @GetMapping("/admin/workers") public ApiResponseDto getWorkerStatistics( - @Parameter(description = "분석 ID (선택)", example = "3") @RequestParam(required = false) - Long analUid, - @Parameter( - description = "작업자 유형 (선택) - 미입력 시 LABELER로 조회", - example = "LABELER", - schema = - @Schema( - allowableValues = {"LABELER", "REVIEWER"}, - defaultValue = "LABELER")) - @RequestParam(required = false) - String type, - @Parameter(description = "검색어 (작업자 이름 또는 사번으로 검색, 부분 일치)", example = "김라벨") - @RequestParam(required = false) - String search, - @Parameter( - description = "정렬 조건 (선택) - 미입력 시 이름 오름차순", - example = "REMAINING_DESC", - schema = - @Schema( - allowableValues = { - "REMAINING_DESC", - "REMAINING_ASC", - "NAME_ASC", - "NAME_DESC" - }, - defaultValue = "NAME_ASC")) - @RequestParam(required = false) - String sort, - @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") - @RequestParam(defaultValue = "0") - Integer page, - @Parameter(description = "페이지 크기", example = "20") - @RequestParam(defaultValue = "20") - Integer size) { + @Parameter(description = "분석 ID (선택)", example = "3") @RequestParam(required = false) + Long analUid, + @Parameter( + description = "작업자 유형 (선택) - 미입력 시 LABELER로 조회", + example = "LABELER", + schema = + @Schema( + allowableValues = {"LABELER", "REVIEWER"}, + defaultValue = "LABELER")) + @RequestParam(required = false) + String type, + @Parameter(description = "검색어 (작업자 이름 또는 사번으로 검색, 부분 일치)", example = "김라벨") + @RequestParam(required = false) + String search, + @Parameter( + description = "정렬 조건 (선택) - 미입력 시 이름 오름차순", + example = "REMAINING_DESC", + schema = + @Schema( + allowableValues = { + "REMAINING_DESC", + "REMAINING_ASC", + "NAME_ASC", + "NAME_DESC" + }, + defaultValue = "NAME_ASC")) + @RequestParam(required = false) + String sort, + @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") + @RequestParam(defaultValue = "0") + Integer page, + @Parameter(description = "페이지 크기", example = "20") + @RequestParam(defaultValue = "20") + Integer size) { // type이 null이면 기본값으로 LABELER 설정 String workerType = (type == null || type.isEmpty()) ? RoleType.LABELER.name() : type; return ApiResponseDto.ok( - labelAllocateService.getWorkerStatistics( - analUid, workerType, search, sort, page, size)); + labelAllocateService.getWorkerStatistics( + analUid, workerType, search, sort, page, size)); } @Operation(summary = "라벨링작업 관리 > 작업 배정", description = "라벨링작업 관리 > 작업 배정") @ApiResponses( - value = { - @ApiResponse( - responseCode = "201", - description = "등록 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Long.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) + value = { + @ApiResponse( + responseCode = "201", + description = "등록 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) @PostMapping("/allocate") public ApiResponseDto labelAllocate( - @RequestBody @Valid LabelAllocateDto.AllocateDto dto) { + @RequestBody @Valid LabelAllocateDto.AllocateDto dto) { return ApiResponseDto.okObject( - labelAllocateService.allocateAsc( - dto.getStage(), - dto.getLabelers(), - dto.getInspectors(), - dto.getCompareYyyy(), - dto.getTargetYyyy())); + labelAllocateService.allocateAsc( + dto.getStage(), + dto.getLabelers(), + dto.getInspectors(), + dto.getCompareYyyy(), + dto.getTargetYyyy())); } @Operation(summary = "작업현황 관리 > 변화탐지 회차 정보", description = "작업현황 관리 > 변화탐지 회차 정보") @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "조회 성공"), - @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), - @ApiResponse(responseCode = "500", description = "서버 오류") - }) + value = { + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) @GetMapping("/stage-detail") public ApiResponseDto findInferenceDetail( - @Parameter( - description = "회차 마스터 key", - required = true, - example = "8584e8d4-53b3-4582-bde2-28a81495a626") - @RequestParam - String uuid) { + @Parameter( + description = "회차 마스터 key", + required = true, + example = "8584e8d4-53b3-4582-bde2-28a81495a626") + @RequestParam + String uuid) { return ApiResponseDto.ok(labelAllocateService.findInferenceDetail(uuid)); } @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 = "작업현황 관리 > 상세 > 작업 이관") @ApiResponses( - value = { - @ApiResponse( - responseCode = "201", - description = "등록 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Long.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) + value = { + @ApiResponse( + responseCode = "201", + description = "등록 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) @PostMapping("/allocate-move") public ApiResponseDto labelAllocateMove( - @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "라벨링 이관", - required = true, - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = LabelAllocateDto.AllocateMoveDto.class))) - @RequestBody - LabelAllocateDto.AllocateMoveDto dto) { + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "라벨링 이관", + required = true, + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = LabelAllocateDto.AllocateMoveDto.class))) + @RequestBody + LabelAllocateDto.AllocateMoveDto dto) { return ApiResponseDto.okObject( - labelAllocateService.allocateMove( - dto.getAutoType(), - dto.getStage(), - dto.getLabelers(), - dto.getCompareYyyy(), - dto.getTargetYyyy())); + labelAllocateService.allocateMove( + dto.getAutoType(), + dto.getStage(), + dto.getLabelers(), + 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 7da6156c..057506e8 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; @@ -36,22 +38,22 @@ public class LabelAllocateService { /** * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직 * - * @param stage 회차 - * @param targetUsers 라벨러 목록 + * @param stage 회차 + * @param targetUsers 라벨러 목록 * @param targetInspectors 검수자 목록 */ @Transactional public ApiResponseDto.ResponseObj allocateAsc( - Integer stage, - List targetUsers, - List targetInspectors, - Integer compareYyyy, - Integer targetYyyy) { + Integer stage, + List targetUsers, + List targetInspectors, + Integer compareYyyy, + Integer targetYyyy) { Long lastId = null; // geom 잔여건수 조회 Long chargeCnt = - labelAllocateCoreService.findLabelUnAssignedCnt(stage, compareYyyy, targetYyyy); + labelAllocateCoreService.findLabelUnAssignedCnt(stage, compareYyyy, targetYyyy); if (chargeCnt <= 0) { return new ApiResponseDto.ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 배정완료된 회차 입니다."); } @@ -59,15 +61,15 @@ public class LabelAllocateService { Long totalDemand = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); if (!Objects.equals(chargeCnt, totalDemand)) { return new ApiResponseDto.ResponseObj( - ApiResponseCode.BAD_REQUEST, "총 잔여건수와 요청 값의 합계가 맞지 않습니다."); + ApiResponseCode.BAD_REQUEST, "총 잔여건수와 요청 값의 합계가 맞지 않습니다."); } List allIds = - labelAllocateCoreService.fetchNextIds(lastId, chargeCnt, compareYyyy, targetYyyy, stage); + labelAllocateCoreService.fetchNextIds(lastId, chargeCnt, compareYyyy, targetYyyy, stage); // MapSheetAnalInferenceEntity analUid 가져오기 Long analUid = - labelAllocateCoreService.findMapSheetAnalInferenceUid(compareYyyy, targetYyyy, stage); + labelAllocateCoreService.findMapSheetAnalInferenceUid(compareYyyy, targetYyyy, stage); int index = 0; for (TargetUser target : targetUsers) { @@ -113,52 +115,52 @@ public class LabelAllocateService { /** * 작업자 통계 조회 * - * @param analUid 분석 ID + * @param analUid 분석 ID * @param workerType 작업자 유형 (LABELER/INSPECTOR) - * @param search 검색어 (이름 또는 사번) - * @param sortType 정렬 조건 - * @param page 페이지 번호 (0부터 시작) - * @param size 페이지 크기 + * @param search 검색어 (이름 또는 사번) + * @param sortType 정렬 조건 + * @param page 페이지 번호 (0부터 시작) + * @param size 페이지 크기 * @return 작업자 목록 및 통계 */ public WorkerListResponse getWorkerStatistics( - Long analUid, - String workerType, - String search, - String sortType, - Integer page, - Integer size) { + Long analUid, + String workerType, + String search, + String sortType, + Integer page, + Integer size) { // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); // 작업자 통계 조회 List workers = - labelAllocateCoreService.findWorkerStatistics( - analUid, workerType, search, sortType); + labelAllocateCoreService.findWorkerStatistics( + analUid, workerType, search, sortType); // 각 작업자별 3일치 처리량 조회 LocalDate today = LocalDate.now(); for (WorkerStatistics worker : workers) { Long day1Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(1), analUid); + labelAllocateCoreService.findDailyProcessedCount( + worker.getWorkerId(), workerType, today.minusDays(1), analUid); Long day2Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(2), analUid); + labelAllocateCoreService.findDailyProcessedCount( + worker.getWorkerId(), workerType, today.minusDays(2), analUid); Long day3Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(3), analUid); + labelAllocateCoreService.findDailyProcessedCount( + worker.getWorkerId(), workerType, today.minusDays(3), analUid); long average = (day1Count + day2Count + day3Count) / 3; DailyHistory history = - DailyHistory.builder() - .day1Ago(day1Count) - .day2Ago(day2Count) - .day3Ago(day3Count) - .average(average) - .build(); + DailyHistory.builder() + .day1Ago(day1Count) + .day2Ago(day2Count) + .day3Ago(day3Count) + .average(average) + .build(); worker.setHistory(history); @@ -175,16 +177,16 @@ public class LabelAllocateService { int toIndex = Math.min(fromIndex + size, workers.size()); List pagedWorkers = - (fromIndex < workers.size()) ? workers.subList(fromIndex, toIndex) : List.of(); + (fromIndex < workers.size()) ? workers.subList(fromIndex, toIndex) : List.of(); return WorkerListResponse.builder() - .progressInfo(progressInfo) - .workers(pagedWorkers) - .currentPage(page) - .pageSize(size) - .totalElements(totalElements) - .totalPages(totalPages) - .build(); + .progressInfo(progressInfo) + .workers(pagedWorkers) + .currentPage(page) + .pageSize(size) + .totalElements(totalElements) + .totalPages(totalPages) + .build(); } public InferenceDetail findInferenceDetail(String uuid) { @@ -192,11 +194,11 @@ public class LabelAllocateService { } public ApiResponseDto.ResponseObj allocateMove( - String autoType, - Integer stage, - List targetUsers, - Integer compareYyyy, - Integer targetYyyy) { + String autoType, + Integer stage, + List targetUsers, + Integer compareYyyy, + Integer targetYyyy) { Long lastId = null; Long chargeCnt = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); @@ -206,8 +208,8 @@ public class LabelAllocateService { } List allIds = - labelAllocateCoreService.fetchNextMoveIds( - lastId, chargeCnt, compareYyyy, targetYyyy, stage); + labelAllocateCoreService.fetchNextMoveIds( + lastId, chargeCnt, compareYyyy, targetYyyy, stage); int index = 0; for (TargetUser target : targetUsers) { int end = index + target.getDemand(); @@ -220,7 +222,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 d71471c0..fb7301e0 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.WorkProgressInfo; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity; @@ -13,6 +15,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 @@ -22,7 +25,7 @@ public class LabelAllocateCoreService { private final LabelAllocateRepository labelAllocateRepository; public List fetchNextIds( - Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { + Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { return labelAllocateRepository.fetchNextIds(lastId, batchSize, compareYyyy, targetYyyy, stage); } @@ -32,8 +35,8 @@ public class LabelAllocateCoreService { public List findAssignedLabelerList(Long analUid) { return labelAllocateRepository.findAssignedLabelerList(analUid).stream() - .map(LabelingAssignmentEntity::toDto) - .toList(); + .map(LabelingAssignmentEntity::toDto) + .toList(); } public Long findLabelUnAssignedCnt(Integer stage, Integer compareYyyy, Integer targetYyyy) { @@ -49,12 +52,12 @@ public class LabelAllocateCoreService { } public List findWorkerStatistics( - Long analUid, - String workerType, - String search, - String sortType) { + Long analUid, + String workerType, + String search, + String sortType) { return labelAllocateRepository.findWorkerStatistics( - analUid, workerType, search, sortType); + analUid, workerType, search, sortType); } public WorkProgressInfo findWorkProgressInfo(Long analUid) { @@ -62,7 +65,7 @@ public class LabelAllocateCoreService { } public Long findDailyProcessedCount( - String workerId, String workerType, LocalDate date, Long analUid) { + String workerId, String workerType, LocalDate date, Long analUid) { return labelAllocateRepository.findDailyProcessedCount(workerId, workerType, date, analUid); } @@ -75,9 +78,9 @@ public class LabelAllocateCoreService { } public List fetchNextMoveIds( - Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { + Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { return labelAllocateRepository.fetchNextMoveIds( - lastId, batchSize, compareYyyy, targetYyyy, stage); + lastId, batchSize, compareYyyy, targetYyyy, stage); } public void assignOwnerMove(List sub, String userId) { @@ -95,4 +98,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 a127e142..9177bceb 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.WorkProgressInfo; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; @@ -10,11 +12,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); @@ -28,7 +31,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); @@ -41,7 +44,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); @@ -50,4 +53,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 6d973dad..c7173589 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,12 +13,15 @@ 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.LabelAllocateDto.searchReq; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkProgressInfo; 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; @@ -39,6 +42,9 @@ import java.util.Objects; import java.util.UUID; 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 @@ -48,28 +54,29 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto private final JPAQueryFactory queryFactory; - @PersistenceContext private EntityManager em; + @PersistenceContext + private EntityManager em; @Override public List fetchNextIds( - Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { + Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { return queryFactory - .select( - Projections.constructor( - AllocateInfoDto.class, - mapSheetAnalDataInferenceGeomEntity.geoUid, - mapSheetAnalDataInferenceGeomEntity.mapSheetNum)) - .from(mapSheetAnalDataInferenceGeomEntity) - .where( - lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), - mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(compareYyyy), - mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(targetYyyy), - mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), - mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) - .orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()) - .limit(batchSize) - .fetch(); + .select( + Projections.constructor( + AllocateInfoDto.class, + mapSheetAnalDataInferenceGeomEntity.geoUid, + mapSheetAnalDataInferenceGeomEntity.mapSheetNum)) + .from(mapSheetAnalDataInferenceGeomEntity) + .where( + lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), + mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), + mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) + .orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()) + .limit(batchSize) + .fetch(); } @Override @@ -77,10 +84,10 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // analUid로 분석 정보 조회 MapSheetAnalInferenceEntity analEntity = - queryFactory - .selectFrom(mapSheetAnalInferenceEntity) - .where(mapSheetAnalInferenceEntity.id.eq(analUid)) - .fetchOne(); + queryFactory + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.id.eq(analUid)) + .fetchOne(); if (Objects.isNull(analEntity)) { throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: "); @@ -88,20 +95,20 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // data_geom 테이블에 label state 를 ASSIGNED 로 update List geoUidList = - ids.stream().map(AllocateInfoDto::getGeoUid).filter(Objects::nonNull).toList(); + ids.stream().map(AllocateInfoDto::getGeoUid).filter(Objects::nonNull).toList(); queryFactory - .update(mapSheetAnalDataInferenceGeomEntity) - .set(mapSheetAnalDataInferenceGeomEntity.labelState, LabelState.ASSIGNED.getId()) - .set(mapSheetAnalDataInferenceGeomEntity.labelStateDttm, ZonedDateTime.now()) - .set(mapSheetAnalDataInferenceGeomEntity.testState, InspectState.UNCONFIRM.getId()) - .set(mapSheetAnalDataInferenceGeomEntity.testStateDttm, ZonedDateTime.now()) - .where(mapSheetAnalDataInferenceGeomEntity.geoUid.in(geoUidList)) - .execute(); + .update(mapSheetAnalDataInferenceGeomEntity) + .set(mapSheetAnalDataInferenceGeomEntity.labelState, LabelState.ASSIGNED.getId()) + .set(mapSheetAnalDataInferenceGeomEntity.labelStateDttm, ZonedDateTime.now()) + .set(mapSheetAnalDataInferenceGeomEntity.testState, InspectState.UNCONFIRM.getId()) + .set(mapSheetAnalDataInferenceGeomEntity.testStateDttm, ZonedDateTime.now()) + .where(mapSheetAnalDataInferenceGeomEntity.geoUid.in(geoUidList)) + .execute(); // 라벨러 할당 테이블에 insert String sql = - """ + """ insert into tb_labeling_assignment (assignment_uid, inference_geom_uid, worker_uid, work_state, assign_group_id, anal_uid) @@ -110,13 +117,13 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto for (AllocateInfoDto info : ids) { em.createNativeQuery(sql) - .setParameter(1, UUID.randomUUID()) - .setParameter(2, info.getGeoUid()) - .setParameter(3, userId) - .setParameter(4, LabelState.ASSIGNED.getId()) - .setParameter(5, info.getMapSheetNum()) - .setParameter(6, analEntity.getId()) - .executeUpdate(); + .setParameter(1, UUID.randomUUID()) + .setParameter(2, info.getGeoUid()) + .setParameter(3, userId) + .setParameter(4, LabelState.ASSIGNED.getId()) + .setParameter(5, info.getMapSheetNum()) + .setParameter(6, analEntity.getId()) + .executeUpdate(); } em.flush(); @@ -127,137 +134,137 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto public List findAssignedLabelerList(Long analUid) { // analUid로 분석 정보 조회 MapSheetAnalInferenceEntity analEntity = - queryFactory - .selectFrom(mapSheetAnalInferenceEntity) - .where(mapSheetAnalInferenceEntity.id.eq(analUid)) - .fetchOne(); + queryFactory + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.id.eq(analUid)) + .fetchOne(); if (Objects.isNull(analEntity)) { throw new EntityNotFoundException("mapSheetAnalInferenceEntity not found for analUid: "); } return queryFactory - .selectFrom(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analEntity.getId()), - labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId()), - labelingAssignmentEntity.inspectorUid.isNull()) - .orderBy(labelingAssignmentEntity.workerUid.asc()) - .fetch(); + .selectFrom(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analEntity.getId()), + labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId()), + labelingAssignmentEntity.inspectorUid.isNull()) + .orderBy(labelingAssignmentEntity.workerUid.asc()) + .fetch(); } @Override public Long findLabelUnAssignedCnt(Integer stage, Integer compareYyyy, Integer targetYyyy) { return queryFactory - .select(mapSheetAnalDataInferenceGeomEntity.geoUid.count()) - .from(mapSheetAnalDataInferenceGeomEntity) - .where( - mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(compareYyyy), - mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(targetYyyy), - mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), - mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) - .fetchOne(); + .select(mapSheetAnalDataInferenceGeomEntity.geoUid.count()) + .from(mapSheetAnalDataInferenceGeomEntity) + .where( + mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), + mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) + .fetchOne(); } @Override public void assignInspector(UUID assignmentUid, String inspectorUid) { queryFactory - .update(labelingAssignmentEntity) - .set(labelingAssignmentEntity.inspectorUid, inspectorUid) - .where(labelingAssignmentEntity.assignmentUid.eq(assignmentUid)) - .execute(); + .update(labelingAssignmentEntity) + .set(labelingAssignmentEntity.inspectorUid, inspectorUid) + .where(labelingAssignmentEntity.assignmentUid.eq(assignmentUid)) + .execute(); } @Override public List availUserList(String role) { return queryFactory - .select( - Projections.constructor( - LabelAllocateDto.UserList.class, - memberEntity.userRole, - memberEntity.employeeNo, - memberEntity.name)) - .from(memberEntity) - .where( - memberEntity.userRole.eq(role), - memberEntity.status.eq(com.kamco.cd.kamcoback.common.enums.StatusType.ACTIVE.getId())) - .orderBy(memberEntity.name.asc()) - .fetch(); + .select( + Projections.constructor( + LabelAllocateDto.UserList.class, + memberEntity.userRole, + memberEntity.employeeNo, + memberEntity.name)) + .from(memberEntity) + .where( + memberEntity.userRole.eq(role), + memberEntity.status.eq(com.kamco.cd.kamcoback.common.enums.StatusType.ACTIVE.getId())) + .orderBy(memberEntity.name.asc()) + .fetch(); } @Override public List findWorkerStatistics( - Long analUid, - String workerType, - String search, - String sortType) { + Long analUid, + String workerType, + String search, + String sortType) { // 작업자 유형에 따른 필드 선택 StringExpression workerIdField = - "REVIEWER".equals(workerType) - ? labelingAssignmentEntity.inspectorUid - : labelingAssignmentEntity.workerUid; + "REVIEWER".equals(workerType) + ? labelingAssignmentEntity.inspectorUid + : labelingAssignmentEntity.workerUid; BooleanExpression workerCondition = - "REVIEWER".equals(workerType) - ? labelingAssignmentEntity.inspectorUid.isNotNull() - : labelingAssignmentEntity.workerUid.isNotNull(); + "REVIEWER".equals(workerType) + ? labelingAssignmentEntity.inspectorUid.isNotNull() + : labelingAssignmentEntity.workerUid.isNotNull(); // 검색 조건 (이름 또는 사번으로 검색) BooleanExpression searchCondition = null; if (search != null && !search.isEmpty()) { searchCondition = memberEntity.name.contains(search) - .or(memberEntity.employeeNo.contains(search)); + .or(memberEntity.employeeNo.contains(search)); } // 완료, 스킵, 남은 작업 계산 NumberExpression completedSum = - new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq("DONE")) - .then(1L) - .otherwise(0L) - .sum(); + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("DONE")) + .then(1L) + .otherwise(0L) + .sum(); NumberExpression skippedSum = - new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq("SKIP")) - .then(1L) - .otherwise(0L) - .sum(); + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("SKIP")) + .then(1L) + .otherwise(0L) + .sum(); NumberExpression remainingSum = - new CaseBuilder() - .when( - labelingAssignmentEntity - .workState - .notIn("DONE", "SKIP") - .and(labelingAssignmentEntity.workState.isNotNull())) - .then(1L) - .otherwise(0L) - .sum(); + new CaseBuilder() + .when( + labelingAssignmentEntity + .workState + .notIn("DONE", "SKIP") + .and(labelingAssignmentEntity.workState.isNotNull())) + .then(1L) + .otherwise(0L) + .sum(); // 기본 통계 조회 쿼리 BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; var baseQuery = - queryFactory - .select( - workerIdField, - memberEntity.name, - workerIdField.count(), - completedSum, - skippedSum, - remainingSum, - labelingAssignmentEntity.stagnationYn.max()) - .from(labelingAssignmentEntity) - .leftJoin(memberEntity) - .on( - "REVIEWER".equals(workerType) - ? memberEntity.employeeNo.eq(labelingAssignmentEntity.inspectorUid) - : memberEntity.employeeNo.eq(labelingAssignmentEntity.workerUid)) - .where(analUidCondition, workerCondition, searchCondition) - .groupBy(workerIdField, memberEntity.name); + queryFactory + .select( + workerIdField, + memberEntity.name, + workerIdField.count(), + completedSum, + skippedSum, + remainingSum, + labelingAssignmentEntity.stagnationYn.max()) + .from(labelingAssignmentEntity) + .leftJoin(memberEntity) + .on( + "REVIEWER".equals(workerType) + ? memberEntity.employeeNo.eq(labelingAssignmentEntity.inspectorUid) + : memberEntity.employeeNo.eq(labelingAssignmentEntity.workerUid)) + .where(analUidCondition, workerCondition, searchCondition) + .groupBy(workerIdField, memberEntity.name); // 정렬 조건 적용 if (sortType != null) { @@ -283,22 +290,22 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // 결과를 DTO로 변환 return baseQuery.fetch().stream() - .map( - tuple -> { - Character maxStagnationYn = tuple.get(labelingAssignmentEntity.stagnationYn.max()); - return WorkerStatistics.builder() - .workerId(tuple.get(workerIdField)) - .workerName(tuple.get(memberEntity.name)) - .workerType(workerType) - .totalAssigned(tuple.get(workerIdField.count())) - .completed(tuple.get(completedSum)) - .skipped(tuple.get(skippedSum)) - .remaining(tuple.get(remainingSum)) - .history(null) // 3일 이력은 Service에서 채움 - .isStagnated(maxStagnationYn != null && maxStagnationYn == 'Y') - .build(); - }) - .toList(); + .map( + tuple -> { + Character maxStagnationYn = tuple.get(labelingAssignmentEntity.stagnationYn.max()); + return WorkerStatistics.builder() + .workerId(tuple.get(workerIdField)) + .workerName(tuple.get(memberEntity.name)) + .workerType(workerType) + .totalAssigned(tuple.get(workerIdField.count())) + .completed(tuple.get(completedSum)) + .skipped(tuple.get(skippedSum)) + .remaining(tuple.get(remainingSum)) + .history(null) // 3일 이력은 Service에서 채움 + .isStagnated(maxStagnationYn != null && maxStagnationYn == 'Y') + .build(); + }) + .toList(); } @Override @@ -307,112 +314,112 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // 전체 배정 건수 Long totalAssigned = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where(analUidCondition) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where(analUidCondition) + .fetchOne(); // 완료 + 스킵 건수 Long completedCount = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.workState.in("DONE", "SKIP")) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + analUidCondition, + labelingAssignmentEntity.workState.in("DONE", "SKIP")) + .fetchOne(); // 투입된 라벨러 수 (고유한 worker_uid 수) Long labelerCount = - queryFactory - .select(labelingAssignmentEntity.workerUid.countDistinct()) - .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.workerUid.isNotNull()) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.workerUid.countDistinct()) + .from(labelingAssignmentEntity) + .where( + analUidCondition, + labelingAssignmentEntity.workerUid.isNotNull()) + .fetchOne(); // 남은 라벨링 작업 데이터 수 Long remainingLabelCount = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.workerUid.isNotNull(), - labelingAssignmentEntity.workState.notIn("DONE", "SKIP")) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + analUidCondition, + labelingAssignmentEntity.workerUid.isNotNull(), + labelingAssignmentEntity.workState.notIn("DONE", "SKIP")) + .fetchOne(); // 투입된 검수자 수 (고유한 inspector_uid 수) Long inspectorCount = - queryFactory - .select(labelingAssignmentEntity.inspectorUid.countDistinct()) - .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.inspectorUid.isNotNull()) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.inspectorUid.countDistinct()) + .from(labelingAssignmentEntity) + .where( + analUidCondition, + labelingAssignmentEntity.inspectorUid.isNotNull()) + .fetchOne(); // 남은 검수 작업 데이터 수 Long remainingInspectCount = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.inspectorUid.isNotNull(), - labelingAssignmentEntity.workState.notIn("DONE")) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + analUidCondition, + labelingAssignmentEntity.inspectorUid.isNotNull(), + labelingAssignmentEntity.workState.notIn("DONE")) + .fetchOne(); // 진행률 계산 double progressRate = 0.0; if (totalAssigned != null && totalAssigned > 0) { progressRate = - (completedCount != null ? completedCount.doubleValue() : 0.0) / totalAssigned * 100; + (completedCount != null ? completedCount.doubleValue() : 0.0) / totalAssigned * 100; } // 작업 상태 판단 (간단하게 진행률 100%면 종료, 아니면 진행중) String workStatus = (progressRate >= 100.0) ? "종료" : "진행중"; return WorkProgressInfo.builder() - .labelingProgressRate(progressRate) - .workStatus(workStatus) - .completedCount(completedCount != null ? completedCount : 0L) - .totalAssignedCount(totalAssigned != null ? totalAssigned : 0L) - .labelerCount(labelerCount != null ? labelerCount : 0L) - .remainingLabelCount(remainingLabelCount != null ? remainingLabelCount : 0L) - .inspectorCount(inspectorCount != null ? inspectorCount : 0L) - .remainingInspectCount(remainingInspectCount != null ? remainingInspectCount : 0L) - .build(); + .labelingProgressRate(progressRate) + .workStatus(workStatus) + .completedCount(completedCount != null ? completedCount : 0L) + .totalAssignedCount(totalAssigned != null ? totalAssigned : 0L) + .labelerCount(labelerCount != null ? labelerCount : 0L) + .remainingLabelCount(remainingLabelCount != null ? remainingLabelCount : 0L) + .inspectorCount(inspectorCount != null ? inspectorCount : 0L) + .remainingInspectCount(remainingInspectCount != null ? remainingInspectCount : 0L) + .build(); } @Override public Long findDailyProcessedCount( - String workerId, String workerType, LocalDate date, Long analUid) { + String workerId, String workerType, LocalDate date, Long analUid) { // 해당 날짜의 시작과 끝 시간 ZonedDateTime startOfDay = date.atStartOfDay(ZoneId.systemDefault()); ZonedDateTime endOfDay = date.atTime(LocalTime.MAX).atZone(ZoneId.systemDefault()); BooleanExpression workerCondition = - "REVIEWER".equals(workerType) - ? labelingAssignmentEntity.inspectorUid.eq(workerId) - : labelingAssignmentEntity.workerUid.eq(workerId); + "REVIEWER".equals(workerType) + ? labelingAssignmentEntity.inspectorUid.eq(workerId) + : labelingAssignmentEntity.workerUid.eq(workerId); BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; Long count = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - analUidCondition, - workerCondition, - labelingAssignmentEntity.workState.in( - LabelState.DONE.getId(), LabelState.SKIP.getId()), - labelingAssignmentEntity.modifiedDate.between(startOfDay, endOfDay)) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + analUidCondition, + workerCondition, + labelingAssignmentEntity.workState.in( + LabelState.DONE.getId(), LabelState.SKIP.getId()), + labelingAssignmentEntity.modifiedDate.between(startOfDay, endOfDay)) + .fetchOne(); return count != null ? count : 0L; } @@ -420,10 +427,10 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public void assignInspectorBulk(List assignmentUids, String inspectorUid) { queryFactory - .update(labelingAssignmentEntity) - .set(labelingAssignmentEntity.inspectorUid, inspectorUid) - .where(labelingAssignmentEntity.assignmentUid.in(assignmentUids)) - .execute(); + .update(labelingAssignmentEntity) + .set(labelingAssignmentEntity.inspectorUid, inspectorUid) + .where(labelingAssignmentEntity.assignmentUid.in(assignmentUids)) + .execute(); em.clear(); } @@ -432,64 +439,64 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto public InferenceDetail findInferenceDetail(String uuid) { // analUid로 분석 정보 조회 MapSheetAnalInferenceEntity analEntity = - queryFactory - .selectFrom(mapSheetAnalInferenceEntity) - .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) - .fetchOne(); + queryFactory + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) + .fetchOne(); if (Objects.isNull(analEntity)) { throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: "); } return queryFactory - .select( - Projections.constructor( - InferenceDetail.class, - mapSheetAnalEntity.analTitle, - Expressions.numberTemplate(Integer.class, "{0}", 4), - mapSheetAnalEntity.gukyuinApplyDttm, - mapSheetAnalEntity.detectingCnt, - labelingAssignmentEntity.workerUid.countDistinct(), - labelingAssignmentEntity.inspectorUid.countDistinct())) - .from(mapSheetAnalEntity) - .innerJoin(labelingAssignmentEntity) - .on(mapSheetAnalEntity.id.eq(labelingAssignmentEntity.analUid)) - .where(mapSheetAnalEntity.id.eq(analEntity.getId())) - .groupBy( - mapSheetAnalEntity.analTitle, - mapSheetAnalEntity.gukyuinApplyDttm, - mapSheetAnalEntity.detectingCnt) - .fetchOne(); + .select( + Projections.constructor( + InferenceDetail.class, + mapSheetAnalEntity.analTitle, + Expressions.numberTemplate(Integer.class, "{0}", 4), + mapSheetAnalEntity.gukyuinApplyDttm, + mapSheetAnalEntity.detectingCnt, + labelingAssignmentEntity.workerUid.countDistinct(), + labelingAssignmentEntity.inspectorUid.countDistinct())) + .from(mapSheetAnalEntity) + .innerJoin(labelingAssignmentEntity) + .on(mapSheetAnalEntity.id.eq(labelingAssignmentEntity.analUid)) + .where(mapSheetAnalEntity.id.eq(analEntity.getId())) + .groupBy( + mapSheetAnalEntity.analTitle, + mapSheetAnalEntity.gukyuinApplyDttm, + mapSheetAnalEntity.detectingCnt) + .fetchOne(); } @Override public List fetchNextMoveIds( - Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { + Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { return queryFactory - .select(mapSheetAnalDataInferenceGeomEntity.geoUid) - .from(mapSheetAnalDataInferenceGeomEntity) - .where( - // mapSheetAnalDataGeomEntity.pnu.isNotNull(), //TODO: Mockup 진행 이후 확인하기 - lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), - mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(compareYyyy), - mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(targetYyyy), - mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), - mapSheetAnalDataInferenceGeomEntity.labelState.in( - LabelState.ASSIGNED.getId(), LabelState.SKIP.getId())) - .orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()) - .limit(batchSize) - .fetch(); + .select(mapSheetAnalDataInferenceGeomEntity.geoUid) + .from(mapSheetAnalDataInferenceGeomEntity) + .where( + // mapSheetAnalDataGeomEntity.pnu.isNotNull(), //TODO: Mockup 진행 이후 확인하기 + lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), + mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), + mapSheetAnalDataInferenceGeomEntity.labelState.in( + LabelState.ASSIGNED.getId(), LabelState.SKIP.getId())) + .orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()) + .limit(batchSize) + .fetch(); } @Transactional @Override public void assignOwnerMove(List sub, String userId) { queryFactory - .update(labelingAssignmentEntity) - .set(labelingAssignmentEntity.workerUid, userId) - .where(labelingAssignmentEntity.inferenceGeomUid.in(sub)) - .execute(); + .update(labelingAssignmentEntity) + .set(labelingAssignmentEntity.workerUid, userId) + .where(labelingAssignmentEntity.inferenceGeomUid.in(sub)) + .execute(); em.clear(); } @@ -497,43 +504,43 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public LabelerDetail findLabelerDetail(String userId, String uuid) { NumberExpression assignedCnt = - new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId())) - .then(1L) - .otherwise((Long) null) - .count(); + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId())) + .then(1L) + .otherwise((Long) null) + .count(); NumberExpression skipCnt = - new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq(LabelState.SKIP.getId())) - .then(1L) - .otherwise((Long) null) - .count(); + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq(LabelState.SKIP.getId())) + .then(1L) + .otherwise((Long) null) + .count(); NumberExpression completeCnt = - new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq(LabelState.COMPLETE.getId())) - .then(1L) - .otherwise((Long) null) - .count(); + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq(LabelState.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)); + 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(); + queryFactory + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) + .fetchOne(); if (Objects.isNull(analEntity)) { throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: "); @@ -543,55 +550,339 @@ 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 public Long findMapSheetAnalInferenceUid(Integer compareYyyy, Integer targetYyyy, Integer stage) { return queryFactory - .select(mapSheetAnalInferenceEntity.id) - .from(mapSheetAnalInferenceEntity) - .where( - mapSheetAnalInferenceEntity.compareYyyy.eq(compareYyyy), - mapSheetAnalInferenceEntity.targetYyyy.eq(targetYyyy), - mapSheetAnalInferenceEntity.stage.eq(stage)) - .fetchOne(); + .select(mapSheetAnalInferenceEntity.id) + .from(mapSheetAnalInferenceEntity) + .where( + mapSheetAnalInferenceEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalInferenceEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalInferenceEntity.stage.eq(stage)) + .fetchOne(); } @Override public void insertInspector(Long analUid, String inspector) { queryFactory - .insert(labelingInspectorEntity) - .columns( - labelingInspectorEntity.operatorUid, - labelingInspectorEntity.analUid, - labelingInspectorEntity.inspectorUid) - .values(UUID.randomUUID(), analUid, inspector) - .execute(); + .insert(labelingInspectorEntity) + .columns( + labelingInspectorEntity.operatorUid, + labelingInspectorEntity.analUid, + labelingInspectorEntity.inspectorUid) + .values(UUID.randomUUID(), analUid, inspector) + .execute(); em.flush(); em.clear(); } + + @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(); + } } From 03d8114897fdee4dfa45dd8323f69537d7bac22d Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 5 Jan 2026 13:36:07 +0900 Subject: [PATCH 10/14] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=EB=A7=81=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=EA=B4=80=EB=A6=AC=20=EB=AA=A9=EB=A1=9D=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelAllocateApiController.java | 9 +- .../label/LabelWorkerApiController.java | 53 +++++- .../cd/kamcoback/label/dto/LabelWorkDto.java | 40 ++++- .../kamcoback/label/dto/WorkerStatsDto.java | 19 +- .../label/service/LabelAllocateService.java | 10 +- .../label/service/LabelWorkService.java | 38 +++- .../core/LabelAllocateCoreService.java | 8 +- .../postgres/core/LabelWorkCoreService.java | 37 ++++ .../InferenceResultRepositoryImpl.java | 167 +++++++++++------- .../label/LabelAllocateRepositoryImpl.java | 70 +++----- .../label/LabelWorkRepositoryCustom.java | 8 + .../label/LabelWorkRepositoryImpl.java | 142 ++++++++++++--- 12 files changed, 430 insertions(+), 171 deletions(-) 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 cb723a69..e23a4104 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -93,19 +93,16 @@ public class LabelAllocateApiController { defaultValue = "NAME_ASC")) @RequestParam(required = false) String sort, - @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") - @RequestParam(defaultValue = "0") + @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") Integer page, - @Parameter(description = "페이지 크기", example = "20") - @RequestParam(defaultValue = "20") + @Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20") Integer size) { // type이 null이면 기본값으로 LABELER 설정 String workerType = (type == null || type.isEmpty()) ? RoleType.LABELER.name() : type; return ApiResponseDto.ok( - labelAllocateService.getWorkerStatistics( - null, workerType, search, sort, page, size)); + labelAllocateService.getWorkerStatistics(null, workerType, search, sort, page, size)); } @Operation(summary = "라벨링작업 관리 > 작업 배정", description = "라벨링작업 관리 > 작업 배정") diff --git a/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java b/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java index d07083e1..eca1c7f0 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java @@ -1,9 +1,10 @@ package com.kamco.cd.kamcoback.label; -import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.ChangeDetectYear; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngDetail; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngSearchReq; import com.kamco.cd.kamcoback.label.service.LabelWorkService; import io.swagger.v3.oas.annotations.Operation; @@ -13,10 +14,13 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import java.util.UUID; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -30,6 +34,24 @@ public class LabelWorkerApiController { private final LabelWorkService labelWorkService; + @Operation(summary = "변화탐지 년도 셀렉트박스 조회", description = "라벨링작업 관리 > 목록 조회 변화탐지 년도 셀렉트박스 조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = ChangeDetectYear.class))), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/change-detect-year") + public ApiResponseDto> getChangeDetectYear() { + return ApiResponseDto.ok(labelWorkService.getChangeDetectYear()); + } + @Operation(summary = "라벨링작업 관리 > 목록 조회", description = "라벨링작업 관리 > 목록 조회") @ApiResponses( value = { @@ -39,26 +61,45 @@ public class LabelWorkerApiController { content = @Content( mediaType = "application/json", - schema = @Schema(implementation = CommonCodeDto.Basic.class))), + schema = @Schema(implementation = Page.class))), @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) }) @GetMapping("/label-work-mng-list") public ApiResponseDto> labelWorkMngList( - @Parameter(description = "변화탐지년도", example = "2024") @RequestParam(required = false) - Integer detectYyyy, - @Parameter(description = "시작일", example = "20260101") @RequestParam String strtDttm, + @Parameter(description = "변화탐지년도", example = "2022-2024") @RequestParam(required = false) + String detectYear, + @Parameter(description = "시작일", example = "20220101") @RequestParam String strtDttm, @Parameter(description = "종료일", example = "20261201") @RequestParam String endDttm, @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") int page, @Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20") int size) { LabelWorkDto.LabelWorkMngSearchReq searchReq = new LabelWorkMngSearchReq(); - searchReq.setDetectYyyy(detectYyyy); + searchReq.setDetectYear(detectYear); searchReq.setStrtDttm(strtDttm); searchReq.setEndDttm(endDttm); searchReq.setPage(page); searchReq.setSize(size); return ApiResponseDto.ok(labelWorkService.labelWorkMngList(searchReq)); } + + @Operation(summary = "라벨링작업 관리 > 작업 배정 정보조회", description = "작업 배정 정보조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = LabelWorkMngDetail.class))), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/label-work-mng-detail/{uuid}") + public ApiResponseDto labelWorkMngDetail( + @Parameter(description = "uuid") @PathVariable UUID uuid) { + return ApiResponseDto.ok(labelWorkService.findLabelWorkMngDetail(uuid)); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelWorkDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelWorkDto.java index 024f89b5..55272f9f 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelWorkDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelWorkDto.java @@ -1,10 +1,12 @@ package com.kamco.cd.kamcoback.label.dto; +import com.fasterxml.jackson.annotation.JsonProperty; import com.kamco.cd.kamcoback.common.utils.enums.Enums; import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelMngState; import io.swagger.v3.oas.annotations.media.Schema; import java.time.ZonedDateTime; +import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -14,6 +16,15 @@ import org.springframework.data.domain.Pageable; public class LabelWorkDto { + @Getter + @Setter + @AllArgsConstructor + public static class ChangeDetectYear { + + private String code; + private String name; + } + @Schema(name = "LabelWorkMng", description = "라벨작업관리") @Getter @Setter @@ -21,8 +32,9 @@ public class LabelWorkDto { @AllArgsConstructor public static class LabelWorkMng { - private int compareYyyy; - private int targetYyyy; + private UUID uuid; + private Integer compareYyyy; + private Integer targetYyyy; private int stage; @JsonFormatDttm private ZonedDateTime createdDttm; private Long detectionTotCnt; @@ -32,6 +44,14 @@ public class LabelWorkDto { private Long labelCompleteTotCnt; @JsonFormatDttm private ZonedDateTime labelStartDttm; + @JsonProperty("detectYear") + public String getDetectYear() { + if (compareYyyy == null || targetYyyy == null) { + return null; + } + return compareYyyy + "-" + targetYyyy; + } + public String getLabelState() { String mngState = "PENDING"; @@ -65,6 +85,20 @@ public class LabelWorkDto { } } + @Getter + @Setter + @AllArgsConstructor + @NoArgsConstructor + public static class LabelWorkMngDetail { + + private String detectionYear; + private Integer stage; + @JsonFormatDttm private ZonedDateTime createdDttm; + private Long labelTotCnt; + private Long labeler; + private Long reviewer; + } + @Schema(name = "LabelWorkMngSearchReq", description = "라벨작업관리 검색 요청") @Getter @Setter @@ -80,7 +114,7 @@ public class LabelWorkDto { private int size = 20; @Schema(description = "변화탐지년도", example = "2024") - private Integer detectYyyy; + private String detectYear; @Schema(description = "시작일", example = "20260101") private String strtDttm; diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java index 3501da8a..435c2060 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java @@ -67,23 +67,17 @@ public class WorkerStatsDto { private Boolean isStagnated; // 레거시 필드 (기존 호환성 유지) - @Deprecated - private Long doneCnt; // completed로 대체 + @Deprecated private Long doneCnt; // completed로 대체 - @Deprecated - private Long skipCnt; // skipped로 대체 + @Deprecated private Long skipCnt; // skipped로 대체 - @Deprecated - private Long remainingCnt; // remaining으로 대체 + @Deprecated private Long remainingCnt; // remaining으로 대체 - @Deprecated - private Long day3AgoDoneCnt; // history.day3Ago로 대체 + @Deprecated private Long day3AgoDoneCnt; // history.day3Ago로 대체 - @Deprecated - private Long day2AgoDoneCnt; // history.day2Ago로 대체 + @Deprecated private Long day2AgoDoneCnt; // history.day2Ago로 대체 - @Deprecated - private Long day1AgoDoneCnt; // history.day1Ago로 대체 + @Deprecated private Long day1AgoDoneCnt; // history.day1Ago로 대체 } @Getter @@ -107,7 +101,6 @@ public class WorkerStatsDto { private Long average; } - @Getter @Setter @Builder 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 ae0ada1d..bf551dfb 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 @@ -122,12 +122,7 @@ public class LabelAllocateService { * @return 작업자 목록 및 통계 */ public WorkerListResponse getWorkerStatistics( - Long analUid, - String workerType, - String search, - String sortType, - Integer page, - Integer size) { + Long analUid, String workerType, String search, String sortType, Integer page, Integer size) { // 프로젝트 정보 조회 (analUid가 있을 때만) var projectInfo = labelAllocateCoreService.findProjectInfo(analUid); @@ -137,8 +132,7 @@ public class LabelAllocateService { // 작업자 통계 조회 List workers = - labelAllocateCoreService.findWorkerStatistics( - analUid, workerType, search, sortType); + labelAllocateCoreService.findWorkerStatistics(analUid, workerType, search, sortType); // 각 작업자별 3일치 처리량 조회 LocalDate today = LocalDate.now(); diff --git a/src/main/java/com/kamco/cd/kamcoback/label/service/LabelWorkService.java b/src/main/java/com/kamco/cd/kamcoback/label/service/LabelWorkService.java index 3a8d6553..c597c5db 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/service/LabelWorkService.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/service/LabelWorkService.java @@ -1,24 +1,52 @@ package com.kamco.cd.kamcoback.label.service; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.ChangeDetectYear; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngDetail; import com.kamco.cd.kamcoback.postgres.core.LabelWorkCoreService; +import java.util.List; +import java.util.UUID; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Slf4j @Service +@RequiredArgsConstructor +@Transactional(rollbackFor = Exception.class) public class LabelWorkService { private final LabelWorkCoreService labelWorkCoreService; - public LabelWorkService(LabelWorkCoreService labelWorkCoreService) { - this.labelWorkCoreService = labelWorkCoreService; - } - + /** + * 라벨링작업 관리 목록조회 + * + * @param searchReq + * @return + */ public Page labelWorkMngList(LabelWorkDto.LabelWorkMngSearchReq searchReq) { - return labelWorkCoreService.labelWorkMngList(searchReq); } + + /** + * 작업배정 정보 조회 + * + * @param uuid + * @return + */ + public LabelWorkMngDetail findLabelWorkMngDetail(UUID uuid) { + return labelWorkCoreService.findLabelWorkMngDetail(uuid); + } + + /** + * 변화탐지 셀렉트박스 조회 + * + * @return + */ + public List getChangeDetectYear() { + return labelWorkCoreService.getChangeDetectYear(); + } } 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 a16252fa..2ad6ba67 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 @@ -54,12 +54,8 @@ public class LabelAllocateCoreService { } public List findWorkerStatistics( - Long analUid, - String workerType, - String search, - String sortType) { - return labelAllocateRepository.findWorkerStatistics( - analUid, workerType, search, sortType); + Long analUid, String workerType, String search, String sortType) { + return labelAllocateRepository.findWorkerStatistics(analUid, workerType, search, sortType); } public WorkProgressInfo findWorkProgressInfo(Long analUid) { diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelWorkCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelWorkCoreService.java index a1be92a5..cc3c3754 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelWorkCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelWorkCoreService.java @@ -1,8 +1,13 @@ package com.kamco.cd.kamcoback.postgres.core; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.ChangeDetectYear; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngDetail; import com.kamco.cd.kamcoback.postgres.repository.label.LabelWorkRepository; +import com.kamco.cd.kamcoback.postgres.repository.members.MembersRepository; +import java.util.List; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; @@ -12,8 +17,40 @@ import org.springframework.stereotype.Service; public class LabelWorkCoreService { private final LabelWorkRepository labelWorkRepository; + private final MembersRepository membersRepository; + /** + * 변화탐지 년도 셀렉트박스 조회 + * + * @return + */ + public List getChangeDetectYear() { + return labelWorkRepository.findChangeDetectYearList().stream() + .map( + e -> + new ChangeDetectYear( + e.getCompareYyyy() + "-" + e.getTargetYyyy(), + e.getCompareYyyy() + "-" + e.getTargetYyyy())) + .toList(); + } + + /** + * 라벨링작업 관리 목록 조회 + * + * @param searchReq + * @return + */ public Page labelWorkMngList(LabelWorkDto.LabelWorkMngSearchReq searchReq) { return labelWorkRepository.labelWorkMngList(searchReq); } + + /** + * 작업배정 정보 조회 + * + * @param uuid + * @return + */ + public LabelWorkMngDetail findLabelWorkMngDetail(UUID uuid) { + return labelWorkRepository.findLabelWorkMngDetail(uuid); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java index 8e1804b9..37f0c8a6 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java @@ -36,20 +36,29 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC String sql = """ INSERT INTO tb_map_sheet_anal_inference ( - compare_yyyy, - target_yyyy, - stage, - anal_title, - detecting_cnt - ) - SELECT - r.input1 AS compare_yyyy, - r.input2 AS target_yyyy, - r.stage, - CONCAT(r.stage ,'_', r.input1 ,'_', r.input2) AS anal_title, - count(*) - FROM inference_results r - GROUP BY r.stage, r.input1, r.input2; + stage, + compare_yyyy, + target_yyyy, + anal_title, + detecting_cnt, + created_dttm, + updated_dttm + ) + SELECT + r.stage, + r.input1 AS compare_yyyy, + r.input2 AS target_yyyy, + CONCAT(r.stage, '_', r.input1, '_', r.input2) AS anal_title, + COUNT(*) AS detecting_cnt, + now(), + now() + FROM inference_results r + GROUP BY r.stage, r.input1, r.input2 + ON CONFLICT (stage, compare_yyyy, target_yyyy) + DO UPDATE SET + detecting_cnt = EXCLUDED.detecting_cnt, + anal_title = EXCLUDED.anal_title, + updated_dttm = now() """; return em.createNativeQuery(sql).executeUpdate(); @@ -69,30 +78,42 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC String sql = """ INSERT INTO tb_map_sheet_anal_data_inference ( + anal_uid, stage, compare_yyyy, target_yyyy, map_sheet_num, - created_dttm, - updated_dttm, + detecting_cnt, file_created_yn, - detecting_cnt + created_dttm, + updated_dttm ) SELECT + ai.id AS anal_uid, r.stage, r.input1 AS compare_yyyy, r.input2 AS target_yyyy, r.map_id AS map_sheet_num, - now() AS created_dttm, - now() AS updated_dttm, - false AS file_created_yn, - count(*) AS detecting_cnt + COUNT(*) AS detecting_cnt, + false AS file_created_yn, + now(), + now() FROM inference_results r - GROUP BY r.stage, r.input1, r.input2, r.map_id + JOIN tb_map_sheet_anal_inference ai + ON ai.stage = r.stage + AND ai.compare_yyyy = r.input1 + AND ai.target_yyyy = r.input2 + GROUP BY + ai.id, + r.stage, + r.input1, + r.input2, + r.map_id ON CONFLICT (stage, compare_yyyy, target_yyyy, map_sheet_num) DO UPDATE SET - updated_dttm = now(), - detecting_cnt = EXCLUDED.detecting_cnt + anal_uid = EXCLUDED.anal_uid, + detecting_cnt = EXCLUDED.detecting_cnt, + updated_dttm = now() """; return em.createNativeQuery(sql).executeUpdate(); @@ -111,46 +132,70 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC String sql = """ - INSERT INTO tb_map_sheet_anal_data_inference_geom ( - uuid, stage, cd_prob, compare_yyyy, target_yyyy, map_sheet_num, - class_before_cd, class_before_prob, class_after_cd, class_after_prob, - geom, area, data_uid, created_dttm, updated_dttm, - file_created_yn + INSERT INTO tb_map_sheet_anal_data_inference_geom ( + uuid, + stage, + cd_prob, + compare_yyyy, + target_yyyy, + map_sheet_num, + class_before_cd, + class_before_prob, + class_after_cd, + class_after_prob, + geom, + area, + data_uid, + file_created_yn, + created_dttm, + updated_dttm ) SELECT - x.uuid, x.stage, x.cd_prob, x.compare_yyyy, x.target_yyyy, x.map_sheet_num, - x.class_before_cd, x.class_before_prob, x.class_after_cd, x.class_after_prob, - x.geom, x.area, x.data_uid, x.created_dttm, x.updated_dttm, - false AS file_created_yn + x.uuid, + x.stage, + x.cd_prob, + x.compare_yyyy, + x.target_yyyy, + x.map_sheet_num, + x.class_before_cd, + x.class_before_prob, + x.class_after_cd, + x.class_after_prob, + x.geom, + x.area, + x.data_uid, + false, + x.created_dttm, + x.updated_dttm FROM ( - SELECT DISTINCT ON (r.uuid) - r.uuid, - r.stage, - r.cd_prob, - r.input1 AS compare_yyyy, - r.input2 AS target_yyyy, - r.map_id AS map_sheet_num, - r.before_class AS class_before_cd, - r.before_probability AS class_before_prob, - r.after_class AS class_after_cd, - r.after_probability AS class_after_prob, - CASE - WHEN r.geometry IS NULL THEN NULL - WHEN left(r.geometry, 2) = '01' - THEN ST_SetSRID(ST_GeomFromWKB(decode(r.geometry, 'hex')), 5186) - ELSE ST_GeomFromText(r.geometry, 5186) - END AS geom, - r.area, - di.data_uid, - r.created_dttm, - r.updated_dttm - FROM inference_results r - JOIN tb_map_sheet_anal_data_inference di - ON di.stage = r.stage - AND di.compare_yyyy = r.input1 - AND di.target_yyyy = r.input2 - AND di.map_sheet_num = r.map_id - ORDER BY r.uuid, r.updated_dttm DESC NULLS LAST, r.uid DESC + SELECT DISTINCT ON (r.uuid) + r.uuid, + r.stage, + r.cd_prob, + r.input1 AS compare_yyyy, + r.input2 AS target_yyyy, + r.map_id AS map_sheet_num, + r.before_class AS class_before_cd, + r.before_probability AS class_before_prob, + r.after_class AS class_after_cd, + r.after_probability AS class_after_prob, + CASE + WHEN r.geometry IS NULL THEN NULL + WHEN LEFT(r.geometry, 2) = '01' + THEN ST_SetSRID(ST_GeomFromWKB(decode(r.geometry, 'hex')), 5186) + ELSE ST_GeomFromText(r.geometry, 5186) + END AS geom, + r.area, + di.data_uid, + r.created_dttm, + r.updated_dttm + FROM inference_results r + JOIN tb_map_sheet_anal_data_inference di + ON di.stage = r.stage + AND di.compare_yyyy = r.input1 + AND di.target_yyyy = r.input2 + AND di.map_sheet_num = r.map_id + ORDER BY r.uuid, r.updated_dttm DESC NULLS LAST, r.uid DESC ) x ON CONFLICT (uuid) DO UPDATE SET 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 141bace8..b08617b8 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 @@ -192,10 +192,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public List findWorkerStatistics( - Long analUid, - String workerType, - String search, - String sortType) { + Long analUid, String workerType, String search, String sortType) { // 작업자 유형에 따른 필드 선택 StringExpression workerIdField = @@ -211,8 +208,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // 검색 조건 (이름 또는 사번으로 검색) BooleanExpression searchCondition = null; if (search != null && !search.isEmpty()) { - searchCondition = memberEntity.name.contains(search) - .or(memberEntity.employeeNo.contains(search)); + searchCondition = + memberEntity.name.contains(search).or(memberEntity.employeeNo.contains(search)); } // 완료, 스킵, 남은 작업 계산 @@ -242,7 +239,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .sum(); // 기본 통계 조회 쿼리 - BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; + BooleanExpression analUidCondition = + analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; var baseQuery = queryFactory @@ -313,7 +311,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public WorkProgressInfo findWorkProgressInfo(Long analUid) { - BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; + BooleanExpression analUidCondition = + analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; // 전체 배정 건수 Long totalAssigned = @@ -339,9 +338,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto queryFactory .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.workState.eq("SKIP")) + .where(analUidCondition, labelingAssignmentEntity.workState.eq("SKIP")) .fetchOne(); // 투입된 라벨러 수 @@ -349,9 +346,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto queryFactory .select(labelingAssignmentEntity.workerUid.countDistinct()) .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.workerUid.isNotNull()) + .where(analUidCondition, labelingAssignmentEntity.workerUid.isNotNull()) .fetchOne(); // === 검수 통계 === @@ -360,9 +355,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto queryFactory .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.workState.eq("DONE")) + .where(analUidCondition, labelingAssignmentEntity.workState.eq("DONE")) .fetchOne(); // 투입된 검수자 수 @@ -370,9 +363,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto queryFactory .select(labelingAssignmentEntity.inspectorUid.countDistinct()) .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.inspectorUid.isNotNull()) + .where(analUidCondition, labelingAssignmentEntity.inspectorUid.isNotNull()) .fetchOne(); // 남은 작업 건수 계산 @@ -432,7 +423,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto ? labelingAssignmentEntity.inspectorUid.eq(workerId) : labelingAssignmentEntity.workerUid.eq(workerId); - BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; + BooleanExpression analUidCondition = + analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; Long count = queryFactory @@ -633,17 +625,17 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto return null; } - var result = queryFactory - .select( - mapSheetAnalEntity.compareYyyy, - mapSheetAnalEntity.targetYyyy, - mapSheetAnalEntity.analTitle, - mapSheetAnalEntity.gukyuinApplyDttm, - mapSheetAnalEntity.analStrtDttm - ) - .from(mapSheetAnalEntity) - .where(mapSheetAnalEntity.id.eq(analUid)) - .fetchOne(); + var result = + queryFactory + .select( + mapSheetAnalEntity.compareYyyy, + mapSheetAnalEntity.targetYyyy, + mapSheetAnalEntity.analTitle, + mapSheetAnalEntity.gukyuinApplyDttm, + mapSheetAnalEntity.analStrtDttm) + .from(mapSheetAnalEntity) + .where(mapSheetAnalEntity.id.eq(analUid)) + .fetchOne(); if (result == null) { return null; @@ -656,9 +648,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto ZonedDateTime analStrtDttm = result.get(mapSheetAnalEntity.analStrtDttm); // 변화탐지년도 생성 - String detectionYear = (compareYyyy != null && targetYyyy != null) - ? compareYyyy + "-" + targetYyyy - : null; + String detectionYear = + (compareYyyy != null && targetYyyy != null) ? compareYyyy + "-" + targetYyyy : null; // 회차 추출 (예: "8회차" → "8") String round = extractRoundFromTitle(analTitle); @@ -671,10 +662,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .build(); } - /** - * 제목에서 회차 숫자 추출 - * 예: "8회차", "제8회차" → "8" - */ + /** 제목에서 회차 숫자 추출 예: "8회차", "제8회차" → "8" */ private String extractRoundFromTitle(String title) { if (title == null || title.isEmpty()) { return null; @@ -686,9 +674,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto return matcher.find() ? matcher.group(1) : null; } - /** - * ZonedDateTime을 "yyyy-MM-dd" 형식으로 변환 - */ + /** ZonedDateTime을 "yyyy-MM-dd" 형식으로 변환 */ private String formatDate(ZonedDateTime dateTime) { if (dateTime == null) { return null; diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryCustom.java index 6a27f5d0..9c0a55cb 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryCustom.java @@ -2,9 +2,17 @@ package com.kamco.cd.kamcoback.postgres.repository.label; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngDetail; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalInferenceEntity; +import java.util.List; +import java.util.UUID; import org.springframework.data.domain.Page; public interface LabelWorkRepositoryCustom { + List findChangeDetectYearList(); + public Page labelWorkMngList(LabelWorkDto.LabelWorkMngSearchReq searchReq); + + LabelWorkMngDetail findLabelWorkMngDetail(UUID uuid); } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java index e17da42c..761d9f12 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java @@ -1,22 +1,29 @@ 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.QMapSheetAnalDataInferenceEntity.mapSheetAnalDataInferenceEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngDetail; import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataGeomEntity; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalInferenceEntity; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.CaseBuilder; import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.NumberExpression; import com.querydsl.core.types.dsl.StringExpression; +import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; -import java.time.format.DateTimeFormatter; import java.util.List; +import java.util.UUID; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; @@ -39,20 +46,59 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport this.queryFactory = queryFactory; } + /** + * 변화탐지 년도 셀렉트박스 조회 + * + * @return + */ + @Override + public List findChangeDetectYearList() { + return queryFactory + .selectFrom(mapSheetAnalInferenceEntity) + .where( + mapSheetAnalInferenceEntity.id.in( + JPAExpressions.select(mapSheetAnalInferenceEntity.id.min()) + .from(mapSheetAnalInferenceEntity) + .groupBy( + mapSheetAnalInferenceEntity.compareYyyy, + mapSheetAnalInferenceEntity.targetYyyy))) + .orderBy( + mapSheetAnalInferenceEntity.compareYyyy.asc(), + mapSheetAnalInferenceEntity.targetYyyy.asc()) + .fetch(); + } + + /** + * 라벨링 작업관리 목록 조회 + * + * @param searchReq + * @return + */ @Override public Page labelWorkMngList(LabelWorkDto.LabelWorkMngSearchReq searchReq) { Pageable pageable = PageRequest.of(searchReq.getPage(), searchReq.getSize()); BooleanBuilder whereBuilder = new BooleanBuilder(); + BooleanBuilder whereSubDataBuilder = new BooleanBuilder(); BooleanBuilder whereSubBuilder = new BooleanBuilder(); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); + if (StringUtils.isNotBlank(searchReq.getDetectYear())) { + String[] years = searchReq.getDetectYear().split("-"); - if (searchReq.getDetectYyyy() != null) { - whereBuilder.and(mapSheetAnalDataInferenceEntity.targetYyyy.eq(searchReq.getDetectYyyy())); + if (years.length == 2) { + Integer compareYear = Integer.valueOf(years[0]); + Integer targetYear = Integer.valueOf(years[1]); + + whereBuilder.and( + mapSheetAnalDataInferenceEntity + .compareYyyy + .eq(compareYear) + .and(mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYear))); + } } - // mapSheetAnalDataInferenceGeomEntity.dataUid.eq(mapSheetAnalDataInferenceEntity.id) + whereSubDataBuilder.and( + mapSheetAnalInferenceEntity.id.eq(mapSheetAnalDataInferenceEntity.analUid)); whereSubBuilder.and( mapSheetAnalDataInferenceGeomEntity.dataUid.eq(mapSheetAnalDataInferenceEntity.id)); @@ -61,15 +107,10 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport && !searchReq.getStrtDttm().isEmpty() && searchReq.getEndDttm() != null && !searchReq.getEndDttm().isEmpty()) { - - // whereSubBuilder.and(mapSheetAnalDataInferenceGeomEntity.labelStateDttm.isNotNull()); whereSubBuilder.and( Expressions.stringTemplate( "to_char({0}, 'YYYYMMDD')", mapSheetAnalDataInferenceGeomEntity.labelStateDttm) .between(searchReq.getStrtDttm(), searchReq.getEndDttm())); - - // whereBuilder.and(mapSheetAnalDataInferenceGeomEntity.labelStateDttm.min().isNotNull()); - } List foundContent = @@ -77,9 +118,10 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport .select( Projections.constructor( LabelWorkMng.class, - mapSheetAnalDataInferenceEntity.compareYyyy, - mapSheetAnalDataInferenceEntity.targetYyyy, - mapSheetAnalDataInferenceEntity.stage, + mapSheetAnalInferenceEntity.uuid, + mapSheetAnalInferenceEntity.compareYyyy, + mapSheetAnalInferenceEntity.targetYyyy, + mapSheetAnalInferenceEntity.stage, mapSheetAnalDataInferenceEntity.createdDttm.min(), mapSheetAnalDataInferenceGeomEntity.dataUid.count(), mapSheetAnalDataInferenceGeomEntity.dataUid.count(), @@ -99,17 +141,21 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport .otherwise(0L) .sum(), mapSheetAnalDataInferenceGeomEntity.labelStateDttm.min())) - .from(mapSheetAnalDataInferenceEntity) + .from(mapSheetAnalInferenceEntity) + .innerJoin(mapSheetAnalDataInferenceEntity) + .on(whereSubDataBuilder) .innerJoin(mapSheetAnalDataInferenceGeomEntity) .on(whereSubBuilder) .where(whereBuilder) .groupBy( - mapSheetAnalDataInferenceEntity.compareYyyy, - mapSheetAnalDataInferenceEntity.targetYyyy, - mapSheetAnalDataInferenceEntity.stage) + mapSheetAnalInferenceEntity.uuid, + mapSheetAnalInferenceEntity.compareYyyy, + mapSheetAnalInferenceEntity.targetYyyy, + mapSheetAnalInferenceEntity.stage) .orderBy( - mapSheetAnalDataInferenceEntity.targetYyyy.desc(), - mapSheetAnalDataInferenceEntity.stage.desc()) + mapSheetAnalInferenceEntity.targetYyyy.desc(), + mapSheetAnalInferenceEntity.compareYyyy.desc(), + mapSheetAnalInferenceEntity.stage.desc()) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .fetch(); @@ -131,8 +177,62 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport */ - Long countQuery = foundContent.stream().count(); + Long total = + queryFactory + .select(mapSheetAnalInferenceEntity.uuid.countDistinct()) + .from(mapSheetAnalInferenceEntity) + .innerJoin(mapSheetAnalDataInferenceEntity) + .on(whereSubDataBuilder) + .innerJoin(mapSheetAnalDataInferenceGeomEntity) + .on(whereSubBuilder) + .where(whereBuilder) + .fetchOne(); - return new PageImpl<>(foundContent, pageable, countQuery); + return new PageImpl<>(foundContent, pageable, total); + } + + /** + * 작업배정 상세조회 + * + * @param uuid + * @return + */ + @Override + public LabelWorkMngDetail findLabelWorkMngDetail(UUID uuid) { + + NumberExpression labelTotCnt = mapSheetAnalDataInferenceGeomEntity.geoUid.count(); + NumberExpression labelerCnt = labelingAssignmentEntity.workerUid.count(); + NumberExpression reviewerCnt = labelingAssignmentEntity.inspectorUid.count(); + + return queryFactory + .select( + Projections.constructor( + LabelWorkMngDetail.class, + mapSheetAnalInferenceEntity + .compareYyyy + .stringValue() + .concat("-") + .concat(mapSheetAnalInferenceEntity.targetYyyy.stringValue()), + mapSheetAnalInferenceEntity.stage, + mapSheetAnalInferenceEntity.createdDttm, + labelTotCnt, + labelerCnt, + reviewerCnt)) + .from(mapSheetAnalInferenceEntity) + .leftJoin(mapSheetAnalDataInferenceEntity) + .on(mapSheetAnalInferenceEntity.id.eq(mapSheetAnalDataInferenceEntity.analUid)) + .leftJoin(mapSheetAnalDataInferenceGeomEntity) + .on(mapSheetAnalDataInferenceEntity.id.eq(mapSheetAnalDataInferenceGeomEntity.dataUid)) + .leftJoin(labelingAssignmentEntity) + .on( + mapSheetAnalDataInferenceGeomEntity.geoUid.eq( + labelingAssignmentEntity.inferenceGeomUid)) + .where(mapSheetAnalInferenceEntity.uuid.eq(uuid)) + .groupBy( + mapSheetAnalInferenceEntity.compareYyyy, + mapSheetAnalInferenceEntity.targetYyyy, + mapSheetAnalInferenceEntity.stage, + mapSheetAnalInferenceEntity.createdDttm) + .fetchOne(); } } From 8c149605e5556add606169ae3f90306357922cae Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Mon, 5 Jan 2026 13:36:15 +0900 Subject: [PATCH 11/14] spotless --- .../label/LabelAllocateApiController.java | 82 ++- .../kamcoback/label/dto/LabelAllocateDto.java | 21 +- .../kamcoback/label/dto/WorkerStatsDto.java | 19 +- .../label/service/LabelAllocateService.java | 13 +- .../core/LabelAllocateCoreService.java | 14 +- .../entity/LabelingAssignmentEntity.java | 29 +- .../label/LabelAllocateRepositoryCustom.java | 12 +- .../label/LabelAllocateRepositoryImpl.java | 476 ++++++++---------- 8 files changed, 295 insertions(+), 371 deletions(-) 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 7f12077e..2dfaf04c 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -5,6 +5,7 @@ import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; 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.WorkerStatsDto.WorkerListResponse; import com.kamco.cd.kamcoback.label.service.LabelAllocateService; import io.swagger.v3.oas.annotations.Operation; @@ -18,6 +19,7 @@ import jakarta.validation.Valid; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -93,19 +95,16 @@ public class LabelAllocateApiController { defaultValue = "NAME_ASC")) @RequestParam(required = false) String sort, - @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") - @RequestParam(defaultValue = "0") + @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") Integer page, - @Parameter(description = "페이지 크기", example = "20") - @RequestParam(defaultValue = "20") + @Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20") Integer size) { // type이 null이면 기본값으로 LABELER 설정 String workerType = (type == null || type.isEmpty()) ? RoleType.LABELER.name() : type; return ApiResponseDto.ok( - labelAllocateService.getWorkerStatistics( - null, workerType, search, sort, page, size)); + labelAllocateService.getWorkerStatistics(null, workerType, search, sort, page, size)); } @Operation(summary = "라벨링작업 관리 > 작업 배정", description = "라벨링작업 관리 > 작업 배정") @@ -154,24 +153,23 @@ public class LabelAllocateApiController { } @Operation( - summary = "작업현황 관리 > 라벨러/검수자 상세 정보, 작업이관 팝업 내 라벨러 상세 정보 동일", - description = "작업현황 관리 > 라벨러/검수자 상세 정보, 작업이관 팝업 내 라벨러 상세 정보 동일") + 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 - ) { + @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)); } @@ -211,30 +209,26 @@ public class LabelAllocateApiController { } @Operation( - summary = "라벨링작업 관리 > 상세 > 라벨러/검수자 일별 작업량 목록", - description = "라벨링작업 관리 > 상세 > 라벨러/검수자 일별 작업량 목록") + 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 - ) { + @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 e931ce40..75381dd8 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 @@ -101,9 +101,9 @@ public class LabelAllocateDto { private Integer stage; @Schema( - description = "라벨러 할당 목록", - example = - """ + description = "라벨러 할당 목록", + example = + """ [ { "userId": "123456", @@ -122,9 +122,9 @@ public class LabelAllocateDto { private List labelers; @Schema( - description = "검수자 할당 목록", - example = - """ + description = "검수자 할당 목록", + example = + """ ["K20251216001", "01022225555", "K20251212001" @@ -232,9 +232,9 @@ public class LabelAllocateDto { private Integer stage; @Schema( - description = "라벨러 할당 목록", - example = - """ + description = "라벨러 할당 목록", + example = + """ [ { "userId": "123456", @@ -295,11 +295,10 @@ public class LabelAllocateDto { String[] sortParams = sort.split(","); String property = sortParams[0]; Sort.Direction direction = - sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC; + 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/dto/WorkerStatsDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java index 3501da8a..435c2060 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java @@ -67,23 +67,17 @@ public class WorkerStatsDto { private Boolean isStagnated; // 레거시 필드 (기존 호환성 유지) - @Deprecated - private Long doneCnt; // completed로 대체 + @Deprecated private Long doneCnt; // completed로 대체 - @Deprecated - private Long skipCnt; // skipped로 대체 + @Deprecated private Long skipCnt; // skipped로 대체 - @Deprecated - private Long remainingCnt; // remaining으로 대체 + @Deprecated private Long remainingCnt; // remaining으로 대체 - @Deprecated - private Long day3AgoDoneCnt; // history.day3Ago로 대체 + @Deprecated private Long day3AgoDoneCnt; // history.day3Ago로 대체 - @Deprecated - private Long day2AgoDoneCnt; // history.day2Ago로 대체 + @Deprecated private Long day2AgoDoneCnt; // history.day2Ago로 대체 - @Deprecated - private Long day1AgoDoneCnt; // history.day1Ago로 대체 + @Deprecated private Long day1AgoDoneCnt; // history.day1Ago로 대체 } @Getter @@ -107,7 +101,6 @@ public class WorkerStatsDto { private Long average; } - @Getter @Setter @Builder 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 ab5a33ac..eb41c6b3 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 @@ -124,12 +124,7 @@ public class LabelAllocateService { * @return 작업자 목록 및 통계 */ public WorkerListResponse getWorkerStatistics( - Long analUid, - String workerType, - String search, - String sortType, - Integer page, - Integer size) { + Long analUid, String workerType, String search, String sortType, Integer page, Integer size) { // 프로젝트 정보 조회 (analUid가 있을 때만) var projectInfo = labelAllocateCoreService.findProjectInfo(analUid); @@ -139,8 +134,7 @@ public class LabelAllocateService { // 작업자 통계 조회 List workers = - labelAllocateCoreService.findWorkerStatistics( - analUid, workerType, search, sortType); + labelAllocateCoreService.findWorkerStatistics(analUid, workerType, search, sortType); // 각 작업자별 3일치 처리량 조회 LocalDate today = LocalDate.now(); @@ -234,7 +228,8 @@ public class LabelAllocateService { } } - public Page findDaliyList(LabelAllocateDto.searchReq searchReq, String uuid, String userId, String type) { + public Page findDaliyList( + LabelAllocateDto.searchReq searchReq, String uuid, String userId, String type) { if (type.equals("LABELER")) { return labelAllocateCoreService.findLabelerDailyStat(searchReq, uuid, userId); } else { 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 2716e8c9..4e8cacfd 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 @@ -57,12 +57,8 @@ public class LabelAllocateCoreService { } public List findWorkerStatistics( - Long analUid, - String workerType, - String search, - String sortType) { - return labelAllocateRepository.findWorkerStatistics( - analUid, workerType, search, sortType); + Long analUid, String workerType, String search, String sortType) { + return labelAllocateRepository.findWorkerStatistics(analUid, workerType, search, sortType); } public WorkProgressInfo findWorkProgressInfo(Long analUid) { @@ -104,11 +100,13 @@ public class LabelAllocateCoreService { labelAllocateRepository.insertInspector(analUid, inspector); } - public Page findLabelerDailyStat(searchReq searchReq, String uuid, String userId) { + public Page findLabelerDailyStat( + searchReq searchReq, String uuid, String userId) { return labelAllocateRepository.findLabelerDailyStat(searchReq, uuid, userId); } - public Page findInspectorDailyStat(searchReq searchReq, String uuid, String userId) { + public Page findInspectorDailyStat( + searchReq searchReq, String uuid, String userId) { return labelAllocateRepository.findInspectorDailyStat(searchReq, uuid, userId); } 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 88b5a15b..ea8e6893 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 @@ -52,20 +52,19 @@ public class LabelingAssignmentEntity extends CommonDateEntity { 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.inspectState, - this.workStatDttm, - this.inspectStatDttm - ); + 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 a4e7807f..659f03bd 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 @@ -18,7 +18,7 @@ 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); @@ -35,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); @@ -48,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); @@ -58,9 +58,11 @@ public interface LabelAllocateRepositoryCustom { void insertInspector(Long analUid, String inspector); - Page findLabelerDailyStat(LabelAllocateDto.searchReq searchReq, String uuid, String userId); + Page findLabelerDailyStat( + LabelAllocateDto.searchReq searchReq, String uuid, String userId); - Page findInspectorDailyStat(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 dd4ae190..4fee9c19 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 @@ -197,10 +197,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public List findWorkerStatistics( - Long analUid, - String workerType, - String search, - String sortType) { + Long analUid, String workerType, String search, String sortType) { // 작업자 유형에 따른 필드 선택 StringExpression workerIdField = @@ -216,8 +213,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // 검색 조건 (이름 또는 사번으로 검색) BooleanExpression searchCondition = null; if (search != null && !search.isEmpty()) { - searchCondition = memberEntity.name.contains(search) - .or(memberEntity.employeeNo.contains(search)); + searchCondition = + memberEntity.name.contains(search).or(memberEntity.employeeNo.contains(search)); } // 완료, 스킵, 남은 작업 계산 @@ -247,7 +244,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .sum(); // 기본 통계 조회 쿼리 - BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; + BooleanExpression analUidCondition = + analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; var baseQuery = queryFactory @@ -318,7 +316,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public WorkProgressInfo findWorkProgressInfo(Long analUid) { - BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; + BooleanExpression analUidCondition = + analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; // 전체 배정 건수 Long totalAssigned = @@ -344,9 +343,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto queryFactory .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.workState.eq("SKIP")) + .where(analUidCondition, labelingAssignmentEntity.workState.eq("SKIP")) .fetchOne(); // 투입된 라벨러 수 @@ -354,9 +351,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto queryFactory .select(labelingAssignmentEntity.workerUid.countDistinct()) .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.workerUid.isNotNull()) + .where(analUidCondition, labelingAssignmentEntity.workerUid.isNotNull()) .fetchOne(); // === 검수 통계 === @@ -365,9 +360,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto queryFactory .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.workState.eq("DONE")) + .where(analUidCondition, labelingAssignmentEntity.workState.eq("DONE")) .fetchOne(); // 투입된 검수자 수 @@ -375,9 +368,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto queryFactory .select(labelingAssignmentEntity.inspectorUid.countDistinct()) .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.inspectorUid.isNotNull()) + .where(analUidCondition, labelingAssignmentEntity.inspectorUid.isNotNull()) .fetchOne(); // 남은 작업 건수 계산 @@ -437,7 +428,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto ? labelingAssignmentEntity.inspectorUid.eq(workerId) : labelingAssignmentEntity.workerUid.eq(workerId); - BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; + BooleanExpression analUidCondition = + analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; Long count = queryFactory @@ -580,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.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(); + .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 @@ -638,17 +630,17 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto return null; } - var result = queryFactory - .select( - mapSheetAnalEntity.compareYyyy, - mapSheetAnalEntity.targetYyyy, - mapSheetAnalEntity.analTitle, - mapSheetAnalEntity.gukyuinApplyDttm, - mapSheetAnalEntity.analStrtDttm - ) - .from(mapSheetAnalEntity) - .where(mapSheetAnalEntity.id.eq(analUid)) - .fetchOne(); + var result = + queryFactory + .select( + mapSheetAnalEntity.compareYyyy, + mapSheetAnalEntity.targetYyyy, + mapSheetAnalEntity.analTitle, + mapSheetAnalEntity.gukyuinApplyDttm, + mapSheetAnalEntity.analStrtDttm) + .from(mapSheetAnalEntity) + .where(mapSheetAnalEntity.id.eq(analUid)) + .fetchOne(); if (result == null) { return null; @@ -661,9 +653,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto ZonedDateTime analStrtDttm = result.get(mapSheetAnalEntity.analStrtDttm); // 변화탐지년도 생성 - String detectionYear = (compareYyyy != null && targetYyyy != null) - ? compareYyyy + "-" + targetYyyy - : null; + String detectionYear = + (compareYyyy != null && targetYyyy != null) ? compareYyyy + "-" + targetYyyy : null; // 회차 추출 (예: "8회차" → "8") String round = extractRoundFromTitle(analTitle); @@ -676,10 +667,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .build(); } - /** - * 제목에서 회차 숫자 추출 - * 예: "8회차", "제8회차" → "8" - */ + /** 제목에서 회차 숫자 추출 예: "8회차", "제8회차" → "8" */ private String extractRoundFromTitle(String title) { if (title == null || title.isEmpty()) { return null; @@ -691,9 +679,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto return matcher.find() ? matcher.group(1) : null; } - /** - * ZonedDateTime을 "yyyy-MM-dd" 형식으로 변환 - */ + /** ZonedDateTime을 "yyyy-MM-dd" 형식으로 변환 */ private String formatDate(ZonedDateTime dateTime) { if (dateTime == null) { return null; @@ -702,211 +688,169 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto } @Override - public Page findLabelerDailyStat(LabelAllocateDto.searchReq searchReq, String uuid, String userId) { + public Page findLabelerDailyStat( + LabelAllocateDto.searchReq searchReq, String uuid, String userId) { // 날짜 포맷 Expression workDate = - Expressions.stringTemplate( - "TO_CHAR({0}, 'YYYY-MM-DD')", - labelingAssignmentEntity.workStatDttm - ); + Expressions.stringTemplate( + "TO_CHAR({0}, 'YYYY-MM-DD')", labelingAssignmentEntity.workStatDttm); // 날짜별 전체 건수 - Expression dailyTotalCnt = - Expressions.numberTemplate( - Long.class, - "COUNT(*)" - ); + Expression dailyTotalCnt = Expressions.numberTemplate(Long.class, "COUNT(*)"); // ⭐ 전체 기간 총 건수 (윈도우 함수) - Expression totalCnt = - Expressions.numberTemplate( - Long.class, - "SUM(COUNT(*)) OVER ()" - ); + Expression totalCnt = Expressions.numberTemplate(Long.class, "SUM(COUNT(*)) OVER ()"); // 상태별 카운트 (Postgres FILTER 사용) Expression assignedCnt = - Expressions.numberTemplate( - Long.class, - "COUNT(*) FILTER (WHERE {0} = 'ASSIGNED')", - labelingAssignmentEntity.workState - ); + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'ASSIGNED')", + labelingAssignmentEntity.workState); Expression skipCnt = - Expressions.numberTemplate( - Long.class, - "COUNT(*) FILTER (WHERE {0} = 'SKIP')", - labelingAssignmentEntity.workState - ); + Expressions.numberTemplate( + Long.class, "COUNT(*) FILTER (WHERE {0} = 'SKIP')", labelingAssignmentEntity.workState); Expression completeCnt = - Expressions.numberTemplate( - Long.class, - "COUNT(*) FILTER (WHERE {0} = 'COMPLETE')", - labelingAssignmentEntity.workState - ); + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'COMPLETE')", + labelingAssignmentEntity.workState); Expression remainCnt = - Expressions.numberTemplate( - Long.class, - "({0} - {1} - {2})", - totalCnt, - skipCnt, - completeCnt - ); + Expressions.numberTemplate(Long.class, "({0} - {1} - {2})", totalCnt, skipCnt, completeCnt); // analUid로 분석 정보 조회 MapSheetAnalInferenceEntity analEntity = - queryFactory - .selectFrom(mapSheetAnalInferenceEntity) - .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) - .fetchOne(); + 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(); + 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(); + 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) { + public Page findInspectorDailyStat( + searchReq searchReq, String uuid, String userId) { // 날짜 포맷 Expression workDate = - Expressions.stringTemplate( - "TO_CHAR({0}, 'YYYY-MM-DD')", - labelingAssignmentEntity.inspectStatDttm - ); + Expressions.stringTemplate( + "TO_CHAR({0}, 'YYYY-MM-DD')", labelingAssignmentEntity.inspectStatDttm); // 날짜별 전체 건수 - Expression dailyTotalCnt = - Expressions.numberTemplate( - Long.class, - "COUNT(*)" - ); + Expression dailyTotalCnt = Expressions.numberTemplate(Long.class, "COUNT(*)"); // ⭐ 전체 기간 총 건수 (윈도우 함수) - Expression totalCnt = - Expressions.numberTemplate( - Long.class, - "SUM(COUNT(*)) OVER ()" - ); + Expression totalCnt = Expressions.numberTemplate(Long.class, "SUM(COUNT(*)) OVER ()"); // 상태별 카운트 (Postgres FILTER 사용) Expression assignedCnt = - Expressions.numberTemplate( - Long.class, - "COUNT(*) FILTER (WHERE {0} = 'UNCONFIRM')", - labelingAssignmentEntity.inspectState - ); + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'UNCONFIRM')", + labelingAssignmentEntity.inspectState); Expression skipCnt = - Expressions.numberTemplate( - Long.class, - "COUNT(*) FILTER (WHERE {0} = 'EXCEPT')", - labelingAssignmentEntity.inspectState - ); + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'EXCEPT')", + labelingAssignmentEntity.inspectState); Expression completeCnt = - Expressions.numberTemplate( - Long.class, - "COUNT(*) FILTER (WHERE {0} = 'COMPLETE')", - labelingAssignmentEntity.inspectState - ); + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'COMPLETE')", + labelingAssignmentEntity.inspectState); Expression remainCnt = - Expressions.numberTemplate( - Long.class, - "({0} - {1} - {2})", - totalCnt, - skipCnt, - completeCnt - ); + Expressions.numberTemplate(Long.class, "({0} - {1} - {2})", totalCnt, skipCnt, completeCnt); // analUid로 분석 정보 조회 MapSheetAnalInferenceEntity analEntity = - queryFactory - .selectFrom(mapSheetAnalInferenceEntity) - .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) - .fetchOne(); + 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(); + 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(); + 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); } @@ -914,43 +858,43 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @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(); + 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(); + 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(); + 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)); + 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(); + queryFactory + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) + .fetchOne(); if (Objects.isNull(analEntity)) { throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: "); @@ -960,28 +904,28 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto 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(); + .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(); } } From 462cdc0241a03667e65e6e9a52274e395e67190b Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Mon, 5 Jan 2026 13:37:30 +0900 Subject: [PATCH 12/14] worklist delete --- .../label/LabelAllocateApiController.java | 11 +--- .../kamcoback/label/dto/WorkerStatsDto.java | 15 +---- .../label/service/LabelAllocateService.java | 61 +------------------ 3 files changed, 4 insertions(+), 83 deletions(-) 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 cb723a69..e78b9919 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -92,20 +92,13 @@ public class LabelAllocateApiController { }, defaultValue = "NAME_ASC")) @RequestParam(required = false) - String sort, - @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") - @RequestParam(defaultValue = "0") - Integer page, - @Parameter(description = "페이지 크기", example = "20") - @RequestParam(defaultValue = "20") - Integer size) { + String sort) { // type이 null이면 기본값으로 LABELER 설정 String workerType = (type == null || type.isEmpty()) ? RoleType.LABELER.name() : type; return ApiResponseDto.ok( - labelAllocateService.getWorkerStatistics( - null, workerType, search, sort, page, size)); + labelAllocateService.getWorkerStatistics(null, workerType, search, sort)); } @Operation(summary = "라벨링작업 관리 > 작업 배정", description = "라벨링작업 관리 > 작업 배정") diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java index 3501da8a..1bcdc1af 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java @@ -200,19 +200,6 @@ public class WorkerStatsDto { @Schema(description = "작업 진행 현황 정보") private WorkProgressInfo progressInfo; - @Schema(description = "작업자 목록") - private List workers; - - @Schema(description = "현재 페이지 번호 (0부터 시작)") - private Integer currentPage; - - @Schema(description = "페이지 크기") - private Integer pageSize; - - @Schema(description = "전체 데이터 수") - private Long totalElements; - - @Schema(description = "전체 페이지 수") - private Integer totalPages; + // workers 필드는 제거되었습니다 (프로젝트 정보와 진행현황만 반환) } } 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 ae0ada1d..40bfa6ca 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 @@ -8,11 +8,8 @@ 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.TargetUser; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; -import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.DailyHistory; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse; -import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService; -import java.time.LocalDate; import java.util.List; import java.util.Objects; import lombok.extern.slf4j.Slf4j; @@ -24,8 +21,6 @@ import org.springframework.transaction.annotation.Transactional; @Transactional public class LabelAllocateService { - private static final int STAGNATION_THRESHOLD = 10; // 정체 판단 기준 (3일 평균 처리량) - private static final int BATCH_SIZE = 100; // 배정 배치 크기 private final LabelAllocateCoreService labelAllocateCoreService; @@ -117,17 +112,13 @@ public class LabelAllocateService { * @param workerType 작업자 유형 (LABELER/INSPECTOR) * @param search 검색어 (이름 또는 사번) * @param sortType 정렬 조건 - * @param page 페이지 번호 (0부터 시작) - * @param size 페이지 크기 * @return 작업자 목록 및 통계 */ public WorkerListResponse getWorkerStatistics( Long analUid, String workerType, String search, - String sortType, - Integer page, - Integer size) { + String sortType) { // 프로젝트 정보 조회 (analUid가 있을 때만) var projectInfo = labelAllocateCoreService.findProjectInfo(analUid); @@ -135,59 +126,9 @@ public class LabelAllocateService { // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); - // 작업자 통계 조회 - List workers = - labelAllocateCoreService.findWorkerStatistics( - analUid, workerType, search, sortType); - - // 각 작업자별 3일치 처리량 조회 - LocalDate today = LocalDate.now(); - for (WorkerStatistics worker : workers) { - Long day1Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(1), analUid); - Long day2Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(2), analUid); - Long day3Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(3), analUid); - - long average = (day1Count + day2Count + day3Count) / 3; - - DailyHistory history = - DailyHistory.builder() - .day1Ago(day1Count) - .day2Ago(day2Count) - .day3Ago(day3Count) - .average(average) - .build(); - - worker.setHistory(history); - - // 정체 여부 판단 (3일 평균이 STAGNATION_THRESHOLD 미만일 때) - if (average < STAGNATION_THRESHOLD) { - worker.setIsStagnated(true); - } - } - - // 페이징 처리 - long totalElements = workers.size(); - int totalPages = (int) Math.ceil((double) totalElements / size); - int fromIndex = page * size; - int toIndex = Math.min(fromIndex + size, workers.size()); - - List pagedWorkers = - (fromIndex < workers.size()) ? workers.subList(fromIndex, toIndex) : List.of(); - return WorkerListResponse.builder() .projectInfo(projectInfo) .progressInfo(progressInfo) - .workers(pagedWorkers) - .currentPage(page) - .pageSize(size) - .totalElements(totalElements) - .totalPages(totalPages) .build(); } From 78c8cb4a75d6e186b0f8fc978ffe792a6b0ebfbf Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Mon, 5 Jan 2026 13:41:32 +0900 Subject: [PATCH 13/14] build error fix --- .../label/LabelAllocateApiController.java | 11 +--- .../label/service/LabelAllocateService.java | 62 +------------------ .../label/LabelAllocateRepositoryImpl.java | 2 +- 3 files changed, 4 insertions(+), 71 deletions(-) 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 cb723a69..e78b9919 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -92,20 +92,13 @@ public class LabelAllocateApiController { }, defaultValue = "NAME_ASC")) @RequestParam(required = false) - String sort, - @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") - @RequestParam(defaultValue = "0") - Integer page, - @Parameter(description = "페이지 크기", example = "20") - @RequestParam(defaultValue = "20") - Integer size) { + String sort) { // type이 null이면 기본값으로 LABELER 설정 String workerType = (type == null || type.isEmpty()) ? RoleType.LABELER.name() : type; return ApiResponseDto.ok( - labelAllocateService.getWorkerStatistics( - null, workerType, search, sort, page, size)); + labelAllocateService.getWorkerStatistics(null, workerType, search, sort)); } @Operation(summary = "라벨링작업 관리 > 작업 배정", description = "라벨링작업 관리 > 작업 배정") 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 ae0ada1d..5a31c4af 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 @@ -8,11 +8,8 @@ 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.TargetUser; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; -import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.DailyHistory; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse; -import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService; -import java.time.LocalDate; import java.util.List; import java.util.Objects; import lombok.extern.slf4j.Slf4j; @@ -24,9 +21,6 @@ import org.springframework.transaction.annotation.Transactional; @Transactional public class LabelAllocateService { - private static final int STAGNATION_THRESHOLD = 10; // 정체 판단 기준 (3일 평균 처리량) - private static final int BATCH_SIZE = 100; // 배정 배치 크기 - private final LabelAllocateCoreService labelAllocateCoreService; public LabelAllocateService(LabelAllocateCoreService labelAllocateCoreService) { @@ -117,17 +111,13 @@ public class LabelAllocateService { * @param workerType 작업자 유형 (LABELER/INSPECTOR) * @param search 검색어 (이름 또는 사번) * @param sortType 정렬 조건 - * @param page 페이지 번호 (0부터 시작) - * @param size 페이지 크기 * @return 작업자 목록 및 통계 */ public WorkerListResponse getWorkerStatistics( Long analUid, String workerType, String search, - String sortType, - Integer page, - Integer size) { + String sortType) { // 프로젝트 정보 조회 (analUid가 있을 때만) var projectInfo = labelAllocateCoreService.findProjectInfo(analUid); @@ -135,59 +125,9 @@ public class LabelAllocateService { // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); - // 작업자 통계 조회 - List workers = - labelAllocateCoreService.findWorkerStatistics( - analUid, workerType, search, sortType); - - // 각 작업자별 3일치 처리량 조회 - LocalDate today = LocalDate.now(); - for (WorkerStatistics worker : workers) { - Long day1Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(1), analUid); - Long day2Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(2), analUid); - Long day3Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(3), analUid); - - long average = (day1Count + day2Count + day3Count) / 3; - - DailyHistory history = - DailyHistory.builder() - .day1Ago(day1Count) - .day2Ago(day2Count) - .day3Ago(day3Count) - .average(average) - .build(); - - worker.setHistory(history); - - // 정체 여부 판단 (3일 평균이 STAGNATION_THRESHOLD 미만일 때) - if (average < STAGNATION_THRESHOLD) { - worker.setIsStagnated(true); - } - } - - // 페이징 처리 - long totalElements = workers.size(); - int totalPages = (int) Math.ceil((double) totalElements / size); - int fromIndex = page * size; - int toIndex = Math.min(fromIndex + size, workers.size()); - - List pagedWorkers = - (fromIndex < workers.size()) ? workers.subList(fromIndex, toIndex) : List.of(); - return WorkerListResponse.builder() .projectInfo(projectInfo) .progressInfo(progressInfo) - .workers(pagedWorkers) - .currentPage(page) - .pageSize(size) - .totalElements(totalElements) - .totalPages(totalPages) .build(); } 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 4fee9c19..1abb30f3 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 @@ -772,7 +772,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public Page findInspectorDailyStat( - searchReq searchReq, String uuid, String userId) { + LabelAllocateDto.searchReq searchReq, String uuid, String userId) { // 날짜 포맷 Expression workDate = Expressions.stringTemplate( From a15b319bc2ffcd3051da88a872814d39ad0fd483 Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Mon, 5 Jan 2026 13:45:39 +0900 Subject: [PATCH 14/14] spotless --- .../kamco/cd/kamcoback/label/dto/WorkerStatsDto.java | 1 - .../kamcoback/label/service/LabelAllocateService.java | 10 ++-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java index 040f12d2..5b4dab5c 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java @@ -1,7 +1,6 @@ package com.kamco.cd.kamcoback.label.dto; import io.swagger.v3.oas.annotations.media.Schema; -import java.util.List; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; 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 5a31c4af..f11bb7de 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 @@ -114,10 +114,7 @@ public class LabelAllocateService { * @return 작업자 목록 및 통계 */ public WorkerListResponse getWorkerStatistics( - Long analUid, - String workerType, - String search, - String sortType) { + Long analUid, String workerType, String search, String sortType) { // 프로젝트 정보 조회 (analUid가 있을 때만) var projectInfo = labelAllocateCoreService.findProjectInfo(analUid); @@ -125,10 +122,7 @@ public class LabelAllocateService { // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); - return WorkerListResponse.builder() - .projectInfo(projectInfo) - .progressInfo(progressInfo) - .build(); + return WorkerListResponse.builder().projectInfo(projectInfo).progressInfo(progressInfo).build(); } public InferenceDetail findInferenceDetail(String uuid) {