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 eca1c7f0..fa7cf915 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java @@ -6,6 +6,9 @@ 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.dto.LabelWorkDto.WorkerState; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.WorkerStateSearchReq; +import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.label.service.LabelWorkService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -102,4 +105,34 @@ public class LabelWorkerApiController { @Parameter(description = "uuid") @PathVariable UUID uuid) { return ApiResponseDto.ok(labelWorkService.findLabelWorkMngDetail(uuid)); } + + @Operation(summary = "작업현황 관리 > 현황 목록 조회", description = "작업현황 관리 > 현황 목록 조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommonCodeDto.Basic.class))), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/work-state-list") + public ApiResponseDto> findWorkStateList( + @Parameter(description = "유형", example = "LABELER") @RequestParam(required = false) String userRole, + @Parameter(description = "검색어", example = "20261201") @RequestParam(required = false) String searchVal, + @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") + int page, + @Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20") + int size) { + + LabelWorkDto.WorkerStateSearchReq searchReq = new WorkerStateSearchReq(); + searchReq.setUserRole(userRole); + searchReq.setSearchVal(searchVal); + searchReq.setPage(page); + searchReq.setSize(size); + return ApiResponseDto.ok(labelWorkService.findlabelWorkStateList(searchReq)); + } } 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 55272f9f..d895ad1b 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 @@ -4,10 +4,12 @@ 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 com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.DailyHistory; import io.swagger.v3.oas.annotations.media.Schema; import java.time.ZonedDateTime; import java.util.UUID; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -127,4 +129,86 @@ public class LabelWorkDto { return PageRequest.of(page, size); } } + + + @Getter + @Setter + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "작업자 통계 응답") + public static class WorkerState { + + @Schema(description = "작업자 유형 (LABELER/INSPECTOR)") + private String userRole; + + @Schema(description = "작업자 ID (사번)") + private String name; + + @Schema(description = "작업자 이름") + private String userId; + + @Schema(description = "배정개수") + private Long assignedCnt; + + @Schema(description = "완료개수") + private Long doneCnt; + + @Schema(description = "Skip개수") + private Long skipCnt; + + @Schema(description = "Skip개수") + private Long day3AgoDoneCnt; + + @Schema(description = "Skip개수") + private Long day2AgoDoneCnt; + + @Schema(description = "Skip개수") + private Long day1AgoDoneCnt; + + public Long getremindCnt() { + return this.assignedCnt - this.doneCnt; + } + + public double getDoneRate() { + if (this.doneCnt == null || this.assignedCnt == 0) { + return 0.0; + } + return (double) this.doneCnt / this.assignedCnt * 100.0; + } + + public String getUserRoleName() { + if (this.userRole.equals("LABELER")) { + return "라벨러"; + } + return "검수자"; + } + + } + + @Schema(name = "WorkerStateSearchReq", description = "라벨작업관리 검색 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class WorkerStateSearchReq { + + // 페이징 파라미터 + @Schema(description = "페이지 번호 (0부터 시작) ", example = "0") + private int page = 0; + + @Schema(description = "페이지 크기", example = "20") + private int size = 20; + + @Schema(description = "유형", example = "LABELER") + private String userRole; + + @Schema(description = "종료일", example = "20261201") + private String searchVal; + + public Pageable toPageable() { + + 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 1ea0f5b9..6681d1e4 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,15 +6,16 @@ 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; 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; -import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -23,6 +24,9 @@ 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) { @@ -113,10 +117,17 @@ 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) { + Long analUid, + String workerType, + String search, + String sortType, + Integer page, + Integer size) { // 프로젝트 정보 조회 (analUid가 있을 때만) var projectInfo = labelAllocateCoreService.findProjectInfo(analUid); @@ -124,7 +135,51 @@ public class LabelAllocateService { // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); - return WorkerListResponse.builder().projectInfo(projectInfo).progressInfo(progressInfo).build(); + + + // 작업자 통계 조회 + 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; + + + + + } + + // 페이징 처리 + 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(); } public InferenceDetail findInferenceDetail(String uuid) { @@ -160,20 +215,7 @@ public class LabelAllocateService { return new ApiResponseDto.ResponseObj(ApiResponseCode.OK, "이관을 완료하였습니다."); } - 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); - } + public LabelerDetail findLabelerDetail(String userId, String uuid) { + return labelAllocateCoreService.findLabelerDetail(userId, uuid); } } 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 c597c5db..3e336a38 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 @@ -3,6 +3,8 @@ 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.WorkerState; +import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngDetail; import com.kamco.cd.kamcoback.postgres.core.LabelWorkCoreService; import java.util.List; @@ -49,4 +51,10 @@ public class LabelWorkService { public List getChangeDetectYear() { return labelWorkCoreService.getChangeDetectYear(); } + + + public Page findlabelWorkStateList(LabelWorkDto.WorkerStateSearchReq searchReq) { + + return labelWorkCoreService.findlabelWorkStateList(searchReq); + } } 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 cc3c3754..ffec3b34 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,13 +1,10 @@ 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.label.dto.LabelWorkDto.WorkerState; +import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; 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; @@ -17,7 +14,6 @@ import org.springframework.stereotype.Service; public class LabelWorkCoreService { private final LabelWorkRepository labelWorkRepository; - private final MembersRepository membersRepository; /** * 변화탐지 년도 셀렉트박스 조회 @@ -44,6 +40,11 @@ public class LabelWorkCoreService { return labelWorkRepository.labelWorkMngList(searchReq); } + public Page findlabelWorkStateList(LabelWorkDto.WorkerStateSearchReq searchReq) + { + return labelWorkRepository.findlabelWorkStateList(searchReq); + }; + /** * 작업배정 정보 조회 * 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 9c0a55cb..4db287c3 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,6 +2,8 @@ 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.WorkerState; +import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngDetail; import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalInferenceEntity; import java.util.List; @@ -15,4 +17,7 @@ public interface LabelWorkRepositoryCustom { public Page labelWorkMngList(LabelWorkDto.LabelWorkMngSearchReq searchReq); LabelWorkMngDetail findLabelWorkMngDetail(UUID uuid); + + Page findlabelWorkStateList(LabelWorkDto.WorkerStateSearchReq searchReq); + } 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 55d5586f..0b56ace1 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,13 +1,21 @@ package com.kamco.cd.kamcoback.postgres.repository.label; import static com.kamco.cd.kamcoback.postgres.entity.QLabelingAssignmentEntity.labelingAssignmentEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QLabelingAssignmentEntity.labelingAssignmentEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QMapInkx5kEntity.mapInkx5kEntity; 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 static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngEntity.mapSheetMngEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngHstEntity.mapSheetMngHstEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QMemberEntity.memberEntity; 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.label.dto.LabelWorkDto.WorkerState; +import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; +import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto; import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataGeomEntity; import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalInferenceEntity; import com.querydsl.core.BooleanBuilder; @@ -20,6 +28,8 @@ import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.List; import java.util.UUID; import lombok.extern.slf4j.Slf4j; @@ -185,6 +195,129 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport */ + 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, total); + } + + + @Override + public Page findlabelWorkStateList(LabelWorkDto.WorkerStateSearchReq searchReq){ + Pageable pageable = PageRequest.of(searchReq.getPage(), searchReq.getSize()); + BooleanBuilder whereBuilder = new BooleanBuilder(); + BooleanBuilder whereSubBuilder = new BooleanBuilder(); + + LocalDate threeDaysAgo = LocalDate.now().minusDays(3); + String s3 = threeDaysAgo.format(DateTimeFormatter.ofPattern("YYYY-MM-DD")); + + LocalDate twoDaysAgo = LocalDate.now().minusDays(2); + String s2 = twoDaysAgo.format(DateTimeFormatter.ofPattern("YYYY-MM-DD")); + + LocalDate oneDaysAgo = LocalDate.now().minusDays(1); + String s1 = oneDaysAgo.format(DateTimeFormatter.ofPattern("YYYY-MM-DD")); + + if (searchReq.getUserRole() != null && ! searchReq.getUserRole().isEmpty()) { + whereSubBuilder.and(memberEntity.userRole.eq(searchReq.getUserRole())); + } + + if (searchReq.getSearchVal() != null && ! searchReq.getSearchVal().isEmpty()) { + whereSubBuilder.and( + Expressions.stringTemplate( + "{0}",memberEntity.userId) + .likeIgnoreCase("%" + searchReq.getSearchVal() + "%") + .or( + Expressions.stringTemplate( + "{0}",memberEntity.name) + .likeIgnoreCase("%" + searchReq.getSearchVal() + "%") + ) + ); + } + + whereSubBuilder.and( + labelingAssignmentEntity.workerUid.eq(memberEntity.userId)); + + List foundContent = + queryFactory + .select( + Projections.constructor( + WorkerState.class, + memberEntity.userRole, + memberEntity.name, + memberEntity.userId, + labelingAssignmentEntity.workerUid.count().as("assignedCnt"), + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("DONE")) + .then(1L) + .otherwise(0L) + .sum() + .as("doneCnt"), + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("SKIP")) + .then(1L) + .otherwise(0L) + .sum() + .as("skipCnt"), + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("DONE") + .and( + Expressions.stringTemplate( + "to_char({0}, 'YYYY-MM-DD')", labelingAssignmentEntity.modifiedDate).eq(s3) ) + ) + .then(1L) + .otherwise(0L) + .sum() + .as("day3AgoDoneCnt"), + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("DONE") + .and( + Expressions.stringTemplate( + "to_char({0}, 'YYYY-MM-DD')", labelingAssignmentEntity.modifiedDate).eq(s2) ) + ) + .then(1L) + .otherwise(0L) + .sum() + .as("day2AgoDoneCnt"), + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("DONE") + .and( + Expressions.stringTemplate( + "to_char({0}, 'YYYY-MM-DD')", labelingAssignmentEntity.modifiedDate).eq(s1) ) + ) + .then(1L) + .otherwise(0L) + .sum() + .as("day1AgoDoneCnt") + )) + .from(labelingAssignmentEntity) + .innerJoin(memberEntity) + .on(whereSubBuilder) + .where(whereBuilder) + .groupBy( + memberEntity.userRole, + memberEntity.name, + memberEntity.userId) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + /* + Long countQuery = + queryFactory + .select(labelingAssignmentEntity.workerUid.count()) + .from(labelingAssignmentEntity) + .where(whereBuilder) + .fetchOne(); + */ + Long total = queryFactory .select(mapSheetAnalInferenceEntity.uuid.countDistinct()) @@ -243,4 +376,6 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport mapSheetAnalInferenceEntity.createdDttm) .fetchOne(); } + + }