diff --git a/src/main/java/com/factoreal/backend/controller/SystemLogController.java b/src/main/java/com/factoreal/backend/controller/SystemLogController.java new file mode 100644 index 00000000..dfeb3839 --- /dev/null +++ b/src/main/java/com/factoreal/backend/controller/SystemLogController.java @@ -0,0 +1,31 @@ +package com.factoreal.backend.controller; + +import com.factoreal.backend.dto.abnormalLog.AbnormalPagingDto; +import com.factoreal.backend.dto.abnormalLog.SystemLogResponseDto; +import com.factoreal.backend.service.AbnormalLogService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "시스템 로그 API", description = "공간별 시스템 로그 조회 API") +@RestController +@RequestMapping("/api/system-logs") +@RequiredArgsConstructor +public class SystemLogController { + private final AbnormalLogService abnormalLogService; + + @Operation(summary = "공간별 시스템 로그 조회", description = "특정 공간(zone)의 시스템 로그를 페이징 처리하여 조회합니다.") + @GetMapping("/zone/{zoneId}") + public ResponseEntity> getSystemLogsByZone( + @Parameter(description = "조회할 공간 ID", required = true) + @PathVariable String zoneId, + @Parameter(description = "페이징 정보 (page: 페이지 번호, size: 페이지 크기)") + @ModelAttribute AbnormalPagingDto pagingDto) { + Page logs = abnormalLogService.findSystemLogsByZoneId(zoneId, pagingDto); + return ResponseEntity.ok(logs); + } +} \ No newline at end of file diff --git a/src/main/java/com/factoreal/backend/controller/WorkerController.java b/src/main/java/com/factoreal/backend/controller/WorkerController.java new file mode 100644 index 00000000..e4efd46f --- /dev/null +++ b/src/main/java/com/factoreal/backend/controller/WorkerController.java @@ -0,0 +1,48 @@ +package com.factoreal.backend.controller; + +import com.factoreal.backend.dto.WorkerDto; +import com.factoreal.backend.dto.ZoneManagerResponseDto; +import com.factoreal.backend.service.WorkerService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/workers") +@RequiredArgsConstructor +@Slf4j +@Tag(name = "작업자 API", description = "작업자 조회 API") +public class WorkerController { + private final WorkerService workerService; + + @Operation(summary = "전체 작업자 목록 조회", description = "전체 작업자 목록을 조회합니다.") + @GetMapping + public ResponseEntity> getAllWorkers() { + log.info("전체 작업자 목록 조회 요청"); + List workers = workerService.getAllWorkers(); + return ResponseEntity.ok(workers); + } + + @Operation(summary = "공간별 작업자 목록 조회", description = "공간 ID를 기반으로 현재 해당 공간에 들어가있는 작업자 리스트를 조회합니다.") + @GetMapping("/zone/{zoneId}") + public ResponseEntity> getWorkersByZoneId(@PathVariable String zoneId) { + log.info("공간 ID: {}의 작업자 목록 조회 요청", zoneId); + List zoneWorkers = workerService.getWorkersByZoneId(zoneId); + return ResponseEntity.ok(zoneWorkers); + } + + @Operation(summary = "공간 담당자 정보 조회", + description = "공간 ID를 기반으로 해당 공간의 담당자와 현재 위치 정보를 조회합니다.") + @GetMapping("/zone/{zoneId}/manager") + public ResponseEntity getZoneManager(@PathVariable String zoneId) { + log.info("공간 ID: {}의 담당자 정보 조회 요청", zoneId); + ZoneManagerResponseDto manager = workerService.getZoneManagerWithLocation(zoneId); + return ResponseEntity.ok(manager); + } +} \ No newline at end of file diff --git a/src/main/java/com/factoreal/backend/controller/WorkerLocationController.java b/src/main/java/com/factoreal/backend/controller/WorkerLocationController.java new file mode 100644 index 00000000..74bb5a8b --- /dev/null +++ b/src/main/java/com/factoreal/backend/controller/WorkerLocationController.java @@ -0,0 +1,36 @@ +package com.factoreal.backend.controller; + +import com.factoreal.backend.dto.WorkerLocationRequest; +import com.factoreal.backend.service.WorkerLocationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.time.LocalDateTime; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@Tag(name = "작업자 위치 API", description = "작업자의 실시간 위치 정보를 관리하는 API") +@RestController +@RequestMapping("/api/worker-locations") +@RequiredArgsConstructor +// 웨어러블 디바이스에서 받아오는 데이터를 업데이트하는 컨트롤러 +public class WorkerLocationController { + + private final WorkerLocationService workerLocationService; + + @Operation(summary = "작업자 위치 업데이트", description = "웨어러블 디바이스로부터 받은 작업자의 위치 정보를 업데이트합니다.") + @PostMapping("/update") + public ResponseEntity updateWorkerLocation(@RequestBody WorkerLocationRequest request) { + log.info("작업자 위치 업데이트 요청: {}", request); + workerLocationService.updateWorkerLocation( + request.getWorkerId(), + request.getZoneId(), + request.getTimestamp() != null ? request.getTimestamp() : LocalDateTime.now() + ); + return ResponseEntity.ok().build(); // 200 OK 응답 + } +} \ No newline at end of file diff --git a/src/main/java/com/factoreal/backend/dto/WorkerDto.java b/src/main/java/com/factoreal/backend/dto/WorkerDto.java new file mode 100644 index 00000000..b79b04b7 --- /dev/null +++ b/src/main/java/com/factoreal/backend/dto/WorkerDto.java @@ -0,0 +1,30 @@ +package com.factoreal.backend.dto; + +import com.factoreal.backend.entity.Worker; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class WorkerDto { + private String workerId; + private String name; + private String phoneNumber; + private String email; + private Boolean isManager; // 관리자 여부 + + // Entity -> DTO 변환 + public static WorkerDto fromEntity(Worker worker, Boolean isManager) { + return WorkerDto.builder() + .workerId(worker.getWorkerId()) + .name(worker.getName()) + .phoneNumber(worker.getPhoneNumber()) + .email(worker.getEmail()) + .isManager(isManager) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/factoreal/backend/dto/WorkerLocationRequest.java b/src/main/java/com/factoreal/backend/dto/WorkerLocationRequest.java new file mode 100644 index 00000000..7530a47e --- /dev/null +++ b/src/main/java/com/factoreal/backend/dto/WorkerLocationRequest.java @@ -0,0 +1,17 @@ +package com.factoreal.backend.dto; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Getter +@Setter +@ToString +// Wearable 장치에서 받아오는 데이터 by 우영. 추후 논의 예정 +public class WorkerLocationRequest { + private String workerId; + private String zoneId; + private LocalDateTime timestamp; // 장치에서 받아오는 데이터 +} \ No newline at end of file diff --git a/src/main/java/com/factoreal/backend/dto/ZoneManagerResponseDto.java b/src/main/java/com/factoreal/backend/dto/ZoneManagerResponseDto.java new file mode 100644 index 00000000..476c5883 --- /dev/null +++ b/src/main/java/com/factoreal/backend/dto/ZoneManagerResponseDto.java @@ -0,0 +1,33 @@ +package com.factoreal.backend.dto; + +import com.factoreal.backend.entity.Worker; +import com.factoreal.backend.entity.Zone; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +// 공간 담당자 정보 조회 시 사용되는 DTO (BE -> FE) +public class ZoneManagerResponseDto { + private String workerId; // 작업자 ID + private String name; // 작업자 이름 + private String phoneNumber; // 연락처 + private String email; // 이메일 + private String currentZoneId; // 현재 위치한 공간 ID + private String currentZoneName; // 현재 위치한 공간 이름 + + public static ZoneManagerResponseDto fromEntity(Worker worker, Zone currentZone) { + return ZoneManagerResponseDto.builder() + .workerId(worker.getWorkerId()) + .name(worker.getName()) + .phoneNumber(worker.getPhoneNumber()) + .email(worker.getEmail()) + .currentZoneId(currentZone != null ? currentZone.getZoneId() : null) + .currentZoneName(currentZone != null ? currentZone.getZoneName() : null) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/factoreal/backend/dto/abnormalLog/AbnormalPagingDto.java b/src/main/java/com/factoreal/backend/dto/abnormalLog/AbnormalPagingDto.java index 79d459a5..7c90ea2a 100644 --- a/src/main/java/com/factoreal/backend/dto/abnormalLog/AbnormalPagingDto.java +++ b/src/main/java/com/factoreal/backend/dto/abnormalLog/AbnormalPagingDto.java @@ -1,9 +1,14 @@ package com.factoreal.backend.dto.abnormalLog; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data +@Schema(description = "페이징 정보 DTO") public class AbnormalPagingDto { + @Schema(description = "페이지 번호 (0부터 시작)", example = "0") private int page; + + @Schema(description = "페이지 크기", example = "10") private int size; } diff --git a/src/main/java/com/factoreal/backend/dto/abnormalLog/SystemLogResponseDto.java b/src/main/java/com/factoreal/backend/dto/abnormalLog/SystemLogResponseDto.java new file mode 100644 index 00000000..c6fe9ea6 --- /dev/null +++ b/src/main/java/com/factoreal/backend/dto/abnormalLog/SystemLogResponseDto.java @@ -0,0 +1,75 @@ +package com.factoreal.backend.dto.abnormalLog; + +import com.factoreal.backend.entity.AbnormalLog; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SystemLogResponseDto { + private String zoneId; + private String targetType; + private String sensorType; + private int dangerLevel; + private double value; + private LocalDateTime timestamp; + private String abnormalType; + private String targetId; + + public static SystemLogResponseDto fromEntity(AbnormalLog abnormalLog) { + return SystemLogResponseDto.builder() + .zoneId(abnormalLog.getZone().getZoneId()) + .targetType(convertLogTypeToKorean(abnormalLog.getTargetType())) + .sensorType(abnormalLog.getTargetType().toString()) + .dangerLevel(calculateDangerLevel(abnormalLog.getAbnormalType())) + .value(abnormalLog.getAbnVal()) + .timestamp(abnormalLog.getDetectedAt()) + .abnormalType(abnormalLog.getAbnormalType()) + .targetId(abnormalLog.getTargetId()) + .build(); + } + + private static String convertLogTypeToKorean(LogType logType) { + return switch (logType) { + case Sensor -> "환경"; + case Worker -> "작업자"; + case Equip -> "설비"; + }; + } + + private static int calculateDangerLevel(String abnormalType) { + if (abnormalType.contains("위험")) return 2; + if (abnormalType.contains("주의")) return 1; + return 0; + } +} + +/** + * { + "content": [ + { + "zoneId": "zone123", + "targetType": "환경", // 또는 "작업자", "설비" + "sensorType": "TEMPERATURE", + "dangerLevel": 2, + "value": 35.5, + "timestamp": "2024-03-20T14:30:00", + "abnormalType": "온도 위험", + "targetId": "sensor456" + } + // ... 더 많은 로그 + ], + "pageable": { + "pageNumber": 0, + "pageSize": 10 + }, + "totalElements": 50, + "totalPages": 5 +} + */ \ No newline at end of file diff --git a/src/main/java/com/factoreal/backend/entity/AbnormalLog.java b/src/main/java/com/factoreal/backend/entity/AbnormalLog.java index 8681652e..d3f53ae9 100644 --- a/src/main/java/com/factoreal/backend/entity/AbnormalLog.java +++ b/src/main/java/com/factoreal/backend/entity/AbnormalLog.java @@ -30,7 +30,8 @@ public class AbnormalLog { private String targetId; // 고유 ID : 센서ID, WorkerID, EquipID @Column(name = "abnormal_type", length = 100) - private String abnormalType; // 이상 유형 : (예: 심박수 이상, 온도 초과, 진동 이상 등) + private String abnormalType; // 이상 유형 : (예: 심박수 위험, 온도 초과 위험, 진동 주의 등) + // 이상은 위험과 주의로 구분이 애매하므로 명확한 표현 필요 @Column(name = "abn_val") private Double abnVal; // 이상치 값 diff --git a/src/main/java/com/factoreal/backend/kafka/KafkaConsumerD.java b/src/main/java/com/factoreal/backend/kafka/KafkaConsumerD.java index e8defda5..e79d016d 100644 --- a/src/main/java/com/factoreal/backend/kafka/KafkaConsumerD.java +++ b/src/main/java/com/factoreal/backend/kafka/KafkaConsumerD.java @@ -71,7 +71,7 @@ public void consume(String message) { SensorKafkaDto dto = objectMapper.readValue(message, SensorKafkaDto.class); // 시스템 로그 (위험도 변화 감지 -> 비동기 전송) - sendSystemLog(dto); + // sendSystemLog(dto); // 공간 센서일 때만 히트맵용 웹소켓 전송 if (dto.getEquipId() != null && dto.getZoneId() != null && dto.getEquipId().equals(dto.getZoneId())) { @@ -164,6 +164,7 @@ public void startAlarm(SensorKafkaDto sensorData, AbnormalLog abnormalLog, RiskL // 공간(zone)별 위험도 변경 시 시스템 로그 전송 @Async + @Deprecated public void sendSystemLog(SensorKafkaDto dto) { String zoneId = dto.getZoneId(); int newLevel = getDangerLevel(dto.getSensorType(), dto.getVal()); diff --git a/src/main/java/com/factoreal/backend/repository/AbnLogRepository.java b/src/main/java/com/factoreal/backend/repository/AbnLogRepository.java index cfc7afbd..b066b882 100644 --- a/src/main/java/com/factoreal/backend/repository/AbnLogRepository.java +++ b/src/main/java/com/factoreal/backend/repository/AbnLogRepository.java @@ -12,4 +12,7 @@ public interface AbnLogRepository extends JpaRepository { long countByIsReadFalse(); // 읽지 않은 로그의 개수 반환 Page findAllByIsReadIsFalse(Pageable pageable); + + // zoneId로 페이징 처리된 로그 조회 + Page findByZone_ZoneIdOrderByDetectedAtDesc(String zoneId, Pageable pageable); } \ No newline at end of file diff --git a/src/main/java/com/factoreal/backend/repository/WorkerZoneRepository.java b/src/main/java/com/factoreal/backend/repository/WorkerZoneRepository.java new file mode 100644 index 00000000..ab3dd503 --- /dev/null +++ b/src/main/java/com/factoreal/backend/repository/WorkerZoneRepository.java @@ -0,0 +1,17 @@ +package com.factoreal.backend.repository; + +import com.factoreal.backend.entity.WorkerZone; +import com.factoreal.backend.entity.WorkerZoneId; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface WorkerZoneRepository extends JpaRepository { + + // 특정 zone_id에 속한 작업자 목록 조회 + List findByZoneZoneId(String zoneId); + + // 특정 zone_id의 담당자 조회 (manageYn = true) + Optional findByZoneZoneIdAndManageYnIsTrue(String zoneId); +} \ No newline at end of file diff --git a/src/main/java/com/factoreal/backend/repository/ZoneHistRepository.java b/src/main/java/com/factoreal/backend/repository/ZoneHistRepository.java new file mode 100644 index 00000000..ba438d4b --- /dev/null +++ b/src/main/java/com/factoreal/backend/repository/ZoneHistRepository.java @@ -0,0 +1,14 @@ +package com.factoreal.backend.repository; + +import com.factoreal.backend.entity.ZoneHist; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ZoneHistRepository extends JpaRepository { + // 특정 공간에 현재 있는 작업자들 조회 (existFlag = 1) + List findByZone_ZoneIdAndExistFlag(String zoneId, Integer existFlag); + + // 특정 작업자의 현재 위치 조회 (existFlag = 1) + ZoneHist findByWorker_WorkerIdAndExistFlag(String workerId, Integer existFlag); +} \ No newline at end of file diff --git a/src/main/java/com/factoreal/backend/service/AbnormalLogService.java b/src/main/java/com/factoreal/backend/service/AbnormalLogService.java index d832a110..6e8ca23e 100644 --- a/src/main/java/com/factoreal/backend/service/AbnormalLogService.java +++ b/src/main/java/com/factoreal/backend/service/AbnormalLogService.java @@ -3,6 +3,7 @@ import com.factoreal.backend.dto.abnormalLog.AbnormalLogDto; import com.factoreal.backend.dto.abnormalLog.AbnormalPagingDto; import com.factoreal.backend.dto.abnormalLog.LogType; +import com.factoreal.backend.dto.abnormalLog.SystemLogResponseDto; import com.factoreal.backend.dto.SensorKafkaDto; import com.factoreal.backend.entity.AbnormalLog; import com.factoreal.backend.entity.Zone; @@ -23,6 +24,8 @@ import org.springframework.web.server.ResponseStatusException; import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; @Service @Slf4j @@ -159,6 +162,16 @@ public Long readRequired(){ return count; } + @Transactional(readOnly = true) + public Page findSystemLogsByZoneId(String zoneId, AbnormalPagingDto pagingDto) { + log.info("공간 ID: {}의 시스템 로그 조회", zoneId); + Pageable pageable = getPageable(pagingDto); + + // zoneId로 직접 필터링된 로그 조회 + Page logs = abnLogRepository.findByZone_ZoneIdOrderByDetectedAtDesc(zoneId, pageable); + return logs.map(SystemLogResponseDto::fromEntity); + } + private Pageable getPageable(AbnormalPagingDto abnormalPagingDto){ return PageRequest.of( abnormalPagingDto.getPage(), diff --git a/src/main/java/com/factoreal/backend/service/WorkerLocationService.java b/src/main/java/com/factoreal/backend/service/WorkerLocationService.java new file mode 100644 index 00000000..b647e731 --- /dev/null +++ b/src/main/java/com/factoreal/backend/service/WorkerLocationService.java @@ -0,0 +1,80 @@ +package com.factoreal.backend.service; + +import com.factoreal.backend.entity.Worker; +import com.factoreal.backend.entity.Zone; +import com.factoreal.backend.entity.ZoneHist; +import com.factoreal.backend.repository.WorkerRepository; +import com.factoreal.backend.repository.ZoneHistRepository; +import com.factoreal.backend.repository.ZoneRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class WorkerLocationService { + + private final ZoneHistRepository zoneHistRepository; + private final WorkerRepository workerRepository; + private final ZoneRepository zoneRepository; + + /** + * 작업자의 위치 변경을 처리 + */ + @Transactional + public void updateWorkerLocation(String workerId, String zoneId, LocalDateTime timestamp) { + Worker worker = workerRepository.findById(workerId) + .orElseThrow(() -> new IllegalArgumentException("작업자를 찾을 수 없습니다: " + workerId)); + + Zone zone = zoneRepository.findById(zoneId) + .orElseThrow(() -> new IllegalArgumentException("공간을 찾을 수 없습니다: " + zoneId)); + + // 1. workerId 기반 작업자의 이전 위치가 있으면, 새로운 기록 생성 전 해당 작업자 위치 기록에 endTime 찍어주기 + ZoneHist currentLocation = zoneHistRepository.findByWorker_WorkerIdAndExistFlag(workerId, 1); + if (currentLocation != null) { + currentLocation.setEndTime(timestamp); // 다음 공간의 입장 시간으로 update + currentLocation.setExistFlag(0); + zoneHistRepository.save(currentLocation); + } + + // 2. 새로운 위치 기록 생성 + ZoneHist newLocation = ZoneHist.builder() + .worker(worker) + .zone(zone) + .startTime(timestamp) + .endTime(null) + .existFlag(1) + .build(); + + zoneHistRepository.save(newLocation); + + /** + * currentLocation이 있으면 (이전 위치가 있으면) -> 그 공간의 ID를 출력 + * currentLocation이 없으면 (최초 입장이면) -> "없음" 출력 + */ + log.info("작업자 {} 위치 변경: {} -> {}", workerId, + currentLocation != null ? currentLocation.getZone().getZoneId() : "없음", + zoneId); + } + + /** + * 특정 공간에 현재 들어가있는 작업자 리스트 조회 + */ + @Transactional(readOnly = true) + public List getCurrentWorkersByZoneId(String zoneId) { + return zoneHistRepository.findByZone_ZoneIdAndExistFlag(zoneId, 1); // 해당 공간의 existFlag가 1인 모든 작업자 리스트 + } + + /** + * 특정 작업자의 현재 위치 조회 + */ + @Transactional(readOnly = true) + public ZoneHist getCurrentWorkerLocation(String workerId) { + return zoneHistRepository.findByWorker_WorkerIdAndExistFlag(workerId, 1); + } +} \ No newline at end of file diff --git a/src/main/java/com/factoreal/backend/service/WorkerService.java b/src/main/java/com/factoreal/backend/service/WorkerService.java new file mode 100644 index 00000000..27b49682 --- /dev/null +++ b/src/main/java/com/factoreal/backend/service/WorkerService.java @@ -0,0 +1,89 @@ +package com.factoreal.backend.service; + +import com.factoreal.backend.dto.WorkerDto; +import com.factoreal.backend.dto.ZoneManagerResponseDto; +import com.factoreal.backend.entity.Worker; +import com.factoreal.backend.entity.WorkerZone; +import com.factoreal.backend.entity.Zone; +import com.factoreal.backend.entity.ZoneHist; +import com.factoreal.backend.repository.WorkerRepository; +import com.factoreal.backend.repository.WorkerZoneRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class WorkerService { + private final WorkerRepository workerRepository; + private final WorkerLocationService workerLocationService; + private final WorkerZoneRepository workerZoneRepository; + + @Transactional(readOnly = true) + public List getAllWorkers() { + log.info("전체 작업자 목록 조회"); + List workers = workerRepository.findAll(); + return workers.stream() + .map(worker -> WorkerDto.fromEntity(worker, false)) + .collect(Collectors.toList()); + } + + /** + * 특정 공간에 현재 들어가있는 작업자 목록 조회 + */ + @Transactional(readOnly = true) + public List getWorkersByZoneId(String zoneId) { + log.info("공간 ID: {}의 현재 작업자 목록 조회", zoneId); + + // 현재 해당 공간에 있는 작업자 이력 조회 (existFlag = 1) + List currentWorkers = workerLocationService.getCurrentWorkersByZoneId(zoneId); + + // ZoneHist에서 Worker 정보만 추출하여 DTO로 변환 + return currentWorkers.stream() + .map(zoneHist -> WorkerDto.fromEntity(zoneHist.getWorker(), false)) + .collect(Collectors.toList()); + } + + /** + * 특정 공간의 담당자와 현재 위치 정보 조회 + */ + @Transactional(readOnly = true) + public ZoneManagerResponseDto getZoneManagerWithLocation(String zoneId) { + log.info("공간 ID: {}의 담당자 정보 조회", zoneId); + + // 1. 해당 공간의 담당자 조회 (manageYn = true) + WorkerZone zoneManager = workerZoneRepository.findByZoneZoneIdAndManageYnIsTrue(zoneId) + .orElseThrow(() -> new IllegalArgumentException("해당 공간의 담당자를 찾을 수 없습니다: " + zoneId)); + + Worker manager = zoneManager.getWorker(); + + // 2. 담당자의 현재 위치 조회 (existFlag = 1) + ZoneHist currentLocation = workerLocationService.getCurrentWorkerLocation(manager.getWorkerId()); + + // 3. 현재 위치한 공간 정보 (없을 수 있음) + Zone currentZone = currentLocation != null ? currentLocation.getZone() : null; + + // 4. DTO 변환 및 반환 + return ZoneManagerResponseDto.fromEntity(manager, currentZone); + } +} + +// TODO. 수정되어야 할 로직. 현재는 WorkerZone 테이블에서 공간id로 필터링 되는 모든 작업자를 끌고왔는데, +// 사실 현재 그 공간에서 실제로 작업하고 있는, 즉 들어 +// public List getWorkersByZoneId(String zoneId) { +// log.info("공간 ID: {}의 작업자 목록 조회", zoneId); +// List workerZones = workerZoneRepository.findByZoneZoneId(zoneId); + +// return workerZones.stream() +// .map(workerZone -> { +// Worker worker = workerZone.getWorker(); +// Boolean isManager = workerZone.getManageYn(); +// return WorkerDto.fromEntity(worker, isManager); +// }) +// .collect(Collectors.toList()); +// } \ No newline at end of file diff --git a/src/test/java/com/factoreal/backend/service/WorkerServiceTest.java b/src/test/java/com/factoreal/backend/service/WorkerServiceTest.java new file mode 100644 index 00000000..5e527e98 --- /dev/null +++ b/src/test/java/com/factoreal/backend/service/WorkerServiceTest.java @@ -0,0 +1,117 @@ +package com.factoreal.backend.service; + +import com.factoreal.backend.dto.WorkerDto; +import com.factoreal.backend.entity.Worker; +import com.factoreal.backend.entity.Zone; +import com.factoreal.backend.entity.ZoneHist; +import com.factoreal.backend.repository.WorkerRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.when; + +public class WorkerServiceTest { + + @Mock + private WorkerRepository workerRepository; + + @Mock + private WorkerLocationService workerLocationService; + + @InjectMocks + private WorkerService workerService; + + private Worker worker1; + private Worker worker2; + private Zone zone1; + private ZoneHist zoneHist1; + + @BeforeEach + public void setup() { + MockitoAnnotations.openMocks(this); + + // 테스트용 작업자 데이터 생성 + worker1 = Worker.builder() + .workerId("20240101-1234") + .name("홍길동") + .phoneNumber("01012345678") + .email("hong@example.com") + .build(); + + worker2 = Worker.builder() + .workerId("20240102-5678") + .name("김철수") + .phoneNumber("01087654321") + .email("kim@example.com") + .build(); + + // 테스트용 공간 데이터 생성 + zone1 = Zone.builder() + .zoneId("zone1") + .zoneName("테스트 공간") + .build(); + + // 테스트용 ZoneHist 데이터 생성 + zoneHist1 = ZoneHist.builder() + .id(1L) + .worker(worker1) + .zone(zone1) + .startTime(LocalDateTime.now()) + .endTime(null) + .existFlag(1) + .build(); + } + + @Test + public void testGetAllWorkers() { + // Mock 설정 + when(workerRepository.findAll()).thenReturn(Arrays.asList(worker1, worker2)); + + // 서비스 메소드 호출 + List result = workerService.getAllWorkers(); + + // 결과 검증 + assertNotNull(result); + assertEquals(2, result.size()); + + // 첫 번째 작업자 정보 확인 + assertEquals("20240101-1234", result.get(0).getWorkerId()); + assertEquals("홍길동", result.get(0).getName()); + assertEquals("01012345678", result.get(0).getPhoneNumber()); + assertEquals("hong@example.com", result.get(0).getEmail()); + assertEquals(false, result.get(0).getIsManager()); // 기본값은 false + + // 두 번째 작업자 정보 확인 + assertEquals("20240102-5678", result.get(1).getWorkerId()); + assertEquals("김철수", result.get(1).getName()); + assertEquals("01087654321", result.get(1).getPhoneNumber()); + assertEquals("kim@example.com", result.get(1).getEmail()); + assertEquals(false, result.get(1).getIsManager()); // 기본값은 false + } + + @Test + void testGetWorkersByZoneId() { + // Mock 설정 + when(workerLocationService.getCurrentWorkersByZoneId("zone1")).thenReturn(Arrays.asList(zoneHist1)); + + // 테스트 실행 + List workers = workerService.getWorkersByZoneId("zone1"); + + // 검증 + assertEquals(1, workers.size()); + assertEquals("20240101-1234", workers.get(0).getWorkerId()); + assertEquals("홍길동", workers.get(0).getName()); + assertEquals("01012345678", workers.get(0).getPhoneNumber()); + assertEquals("hong@example.com", workers.get(0).getEmail()); + assertEquals(false, workers.get(0).getIsManager()); // 관리자 여부는 더 이상 ZoneHist에서 확인할 수 없음 + } +}