diff --git a/src/main/java/com/goi/erp/client/HcmEmployeeClient.java b/src/main/java/com/goi/erp/client/HcmEmployeeClient.java index ac36c98..d6d078a 100644 --- a/src/main/java/com/goi/erp/client/HcmEmployeeClient.java +++ b/src/main/java/com/goi/erp/client/HcmEmployeeClient.java @@ -14,6 +14,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import com.goi.erp.token.PermissionAuthenticationToken; +import com.goi.erp.token.SystemTokenProvider; import java.util.Map; import java.util.UUID; @@ -24,53 +25,40 @@ import java.util.UUID; public class HcmEmployeeClient { private final RestTemplate restTemplate; + private final SystemTokenProvider systemTokenProvider; + @Value("${hcm.api.base-url}") private String hcmBaseUrl; public Long getEmpIdFromExternalId(String externalSource, String externalId) { - String url = hcmBaseUrl + "/employee/external" + "?solutionType=" + externalSource + "&externalId=" + externalId; + String url = hcmBaseUrl + "/employee/external" + + "?solutionType=" + externalSource + + "&externalId=" + externalId; try { - // set token in header - String jwt = getCurrentJwt(); - HttpHeaders headers = new HttpHeaders(); - headers.set("Authorization", "Bearer " + jwt); - HttpEntity entity = new HttpEntity<>(headers); - - // GET + HttpEntity entity = new HttpEntity<>(systemHeaders()); + ResponseEntity> response = restTemplate.exchange( url, HttpMethod.GET, entity, - new ParameterizedTypeReference>() {} + new ParameterizedTypeReference<>() {} ); - Map body = response.getBody(); - //System.out.println("RESPONSE ➜ " + body); - - if (body != null && body.get("eexEmpId") != null) { - - Object raw = body.get("eexEmpId"); - - if (raw instanceof Number) { - return ((Number) raw).longValue(); // πŸ”₯ λͺ¨λ“  숫자λ₯Ό Long λ³€ν™˜ - } - - // μ˜ˆμƒ λ°– νƒ€μž…μΌ 경우 + Map body = response.getBody(); + if (body != null && body.get("eexEmpId") instanceof Number n) { + return n.longValue(); } - return null; } catch (Exception e) { - log.error( - "[EXTERNAL][GET] externalId lookup error: {}", - e.getMessage() - ); + log.error("[HCM][GET] externalId lookup error", e); return null; } } + public Long getEmpIdFromUuid(UUID uuid) { @@ -124,6 +112,12 @@ public class HcmEmployeeClient { } return null; } + + private HttpHeaders systemHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(systemTokenProvider.getToken()); + return headers; + } } diff --git a/src/main/java/com/goi/erp/config/SecurityConfig.java b/src/main/java/com/goi/erp/config/SecurityConfig.java index 8790862..b98d26d 100644 --- a/src/main/java/com/goi/erp/config/SecurityConfig.java +++ b/src/main/java/com/goi/erp/config/SecurityConfig.java @@ -33,6 +33,7 @@ public class SecurityConfig { .authorizeHttpRequests(auth -> auth .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() .requestMatchers("/ext/**").permitAll() + .requestMatchers("/scheduler/**").permitAll() .anyRequest().authenticated() ) // μš”μ²­ κΆŒν•œ μ„€μ • .addFilterBefore(new CorsFilter(corsConfigurationSource()), UsernamePasswordAuthenticationFilter.class) // JWT ν•„ν„° 전에 CorsFilter 등둝 diff --git a/src/main/java/com/goi/erp/controller/TestController.java b/src/main/java/com/goi/erp/controller/TestController.java deleted file mode 100644 index 93c109b..0000000 --- a/src/main/java/com/goi/erp/controller/TestController.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.goi.erp.controller; - -import com.goi.erp.service.ExtSamsaraInspectionProcessor; -import com.goi.erp.service.VehicleDispatchAutoCloseService; - -import lombok.RequiredArgsConstructor; - -import java.time.LocalDate; - -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/test") -@RequiredArgsConstructor -public class TestController { - - private final ExtSamsaraInspectionProcessor processor; - private final VehicleDispatchAutoCloseService autoCloseService; - - @PostMapping("/process-samsara-inspection") - public String process() { - processor.processUnprocessed(); - return "OK"; - } - - /** - * μ°¨λŸ‰ λ°°μ°¨ μžλ™ μ’…λ£Œ ν…ŒμŠ€νŠΈ (였늘 λ‚ μ§œ κΈ°μ€€) - */ - @PostMapping("/auto-close-dispatch") - public String autoCloseDispatch() { - - autoCloseService.processAutoClose(LocalDate.now()); - - return "AUTO CLOSE OK"; - } -} diff --git a/src/main/java/com/goi/erp/dto/ExtIngestResult.java b/src/main/java/com/goi/erp/dto/ExtIngestResult.java index 15568df..b969e31 100644 --- a/src/main/java/com/goi/erp/dto/ExtIngestResult.java +++ b/src/main/java/com/goi/erp/dto/ExtIngestResult.java @@ -11,7 +11,6 @@ import lombok.NoArgsConstructor; @Builder public class ExtIngestResult { private String source; - private String recordType; private int received; private int inserted; private int updated; diff --git a/src/main/java/com/goi/erp/dto/ExtSamsaraInspectionIngestCommand.java b/src/main/java/com/goi/erp/dto/ExtSamsaraInspectionIngestCommand.java index 1c0fb90..1675b01 100644 --- a/src/main/java/com/goi/erp/dto/ExtSamsaraInspectionIngestCommand.java +++ b/src/main/java/com/goi/erp/dto/ExtSamsaraInspectionIngestCommand.java @@ -14,7 +14,6 @@ import java.util.List; @Builder public class ExtSamsaraInspectionIngestCommand { private String source; - private String recordType; private LocalDateTime fetchedAt; private List records; } diff --git a/src/main/java/com/goi/erp/entity/ExtSamsaraRawInspection.java b/src/main/java/com/goi/erp/entity/ExtSamsaraRawInspection.java index 4d77c7c..ae4884f 100644 --- a/src/main/java/com/goi/erp/entity/ExtSamsaraRawInspection.java +++ b/src/main/java/com/goi/erp/entity/ExtSamsaraRawInspection.java @@ -49,9 +49,6 @@ public class ExtSamsaraRawInspection { @Column(name = "esri_source", nullable = false, length = 20) private String esriSource; // SAMSARA - @Column(name = "esri_record_type", nullable = false, length = 20) - private String esriRecordType; // DVIR - @Column(name = "esri_external_id", nullable = false, length = 50) private String esriExternalId; // inspection id diff --git a/src/main/java/com/goi/erp/service/ExtSamsaraInspectionIngestService.java b/src/main/java/com/goi/erp/service/ExtSamsaraInspectionIngestService.java index 4d13745..27bfb8e 100644 --- a/src/main/java/com/goi/erp/service/ExtSamsaraInspectionIngestService.java +++ b/src/main/java/com/goi/erp/service/ExtSamsaraInspectionIngestService.java @@ -50,7 +50,6 @@ public class ExtSamsaraInspectionIngestService { return ExtIngestResult.builder() .source(command.getSource()) - .recordType(command.getRecordType()) .received(command.getRecords().size()) .inserted(inserted) .updated(updated) @@ -90,7 +89,6 @@ public class ExtSamsaraInspectionIngestService { ExtSamsaraRawInspection entity = ExtSamsaraRawInspection.builder() .esriSource(command.getSource()) - .esriRecordType(command.getRecordType()) .esriExternalId(record.getExternalId()) .esriVehicleExtId(record.getVehicleExternalId()) .esriDriverExtId(record.getDriverExternalId()) diff --git a/src/main/java/com/goi/erp/service/ExtSamsaraInspectionProcessor.java b/src/main/java/com/goi/erp/service/ExtSamsaraInspectionProcessor.java index 85f7aa8..561f05a 100644 --- a/src/main/java/com/goi/erp/service/ExtSamsaraInspectionProcessor.java +++ b/src/main/java/com/goi/erp/service/ExtSamsaraInspectionProcessor.java @@ -1,6 +1,7 @@ package com.goi.erp.service; import com.fasterxml.jackson.databind.JsonNode; +import com.goi.erp.dto.ProcessResultDto; import com.goi.erp.dto.VehicleInspectionDefectDto; import com.goi.erp.dto.VehicleInspectionDto; import com.goi.erp.entity.ExtSamsaraRawInspection; @@ -11,6 +12,7 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -25,43 +27,61 @@ public class ExtSamsaraInspectionProcessor { private final ExtSamsaraRawInspectionRepository rawRepo; private final VehicleInspectionService vehicleInspectionService; - @Transactional - public void processUnprocessed() { + public ProcessResultDto processUnprocessed() { - List raws = rawRepo.findByEsriProcessedFalse(); - - if (raws.isEmpty()) { - log.info("[DVIR_PROCESSOR] no unprocessed raw inspections"); - return; - } - // log 용 int processed = 0; int failed = 0; + List raws = rawRepo.findByEsriProcessedFalseAndEsriFetchedDate(LocalDate.now()); + + if (raws.isEmpty()) { + log.info("[INSPECTION_PROCESSOR] no unprocessed raw inspections"); + return new ProcessResultDto(0, 0); + } + for (ExtSamsaraRawInspection raw : raws) { try { - processSingle(raw); + processSingleTx(raw); processed++; } catch (Exception e) { failed++; - log.error( - "[DVIR_PROCESSOR] failed rawId={} externalId={} msg={}", - raw.getEsriId(), - raw.getEsriExternalId(), - e.getMessage(), - e - ); + // markFailed(raw, e); } } - log.info("[DVIR_PROCESSOR] done processed={} failed={}", processed, failed); + log.info("[INSPECTION_PROCESSOR] done processed={} failed={}", processed, failed); + return new ProcessResultDto(processed, failed); } + /** + * raw 1건 λ‹¨μœ„ νŠΈλžœμž­μ…˜ + */ + @Transactional + protected void processSingleTx(ExtSamsaraRawInspection raw) { + processSingle(raw); + raw.setEsriProcessed(true); + raw.setEsriProcessedAt(LocalDateTime.now()); + rawRepo.save(raw); + } + +// private void markFailed(ExtSamsaraRawInspection raw, Exception e) { +// log.error( +// "[INSPECTION_PROCESSOR] failed rawId={} externalId={} msg={}", +// raw.getEsriId(), +// raw.getEsriExternalId(), +// e.getMessage(), +// e +// ); +// +// raw.setEsriProcessError(e.getMessage()); +// raw.setEsriProcessFailedAt(LocalDateTime.now()); +// rawRepo.save(raw); +// } + private void processSingle(ExtSamsaraRawInspection raw) { JsonNode payload = raw.getEsriPayload(); - // Inspection VehicleInspectionDto dto = new VehicleInspectionDto(); dto.setSource("SAMSARA"); dto.setSourceId(Long.valueOf(raw.getEsriExternalId())); @@ -79,18 +99,11 @@ public class ExtSamsaraInspectionProcessor { dto.setEndAt(raw.getEsriEndTime()); dto.setResult(payload.path("safetyStatus").asText(null)); dto.setOdometer(payload.path("odometerMeters").asLong(0L)); - - log.info( - "[DVIR] rawId={} odometerMeters node={} value={}", - raw.getEsriExternalId(), - payload.get("odometerMeters"), - payload.path("odometerMeters").asLong(-1) - ); - // Defects + // defects JsonNode defectsNode = payload.path("vehicleDefects"); List defectDtos = new ArrayList<>(); - + if (defectsNode.isArray()) { for (JsonNode d : defectsNode) { VehicleInspectionDefectDto defectDto = new VehicleInspectionDefectDto(); @@ -98,33 +111,22 @@ public class ExtSamsaraInspectionProcessor { defectDto.setDefectType(d.path("defectType").asText(null)); defectDto.setComment(d.path("comment").asText(null)); defectDto.setIsResolved(d.path("isResolved").asBoolean(false)); + JsonNode resolvedBy = d.path("resolvedBy"); if (!resolvedBy.isMissingNode()) { defectDto.setResolvedByExternalId(resolvedBy.path("id").asText(null)); } + defectDto.setResolvedAt(DateTimeUtil.parse(d.path("resolvedAtTime"))); defectDto.setCreatedAt(DateTimeUtil.parse(d.path("createdAtTime"))); defectDtos.add(defectDto); } } + dto.setDefects(defectDtos); dto.setHasDefect(!defectDtos.isEmpty()); - - log.info( - "[DVIR DTO] sourceId={} result={} odometer={} hasDefect={} defects={}", - dto.getSourceId(), - dto.getResult(), - dto.getOdometer(), - dto.getHasDefect(), - dto.getDefects() != null ? dto.getDefects().size() : null - ); - // inspection + defect μ €μž₯은 Service μ—μ„œ vehicleInspectionService.saveInspection(dto); - - // raw 처리 μ™„λ£Œ - raw.setEsriProcessed(true); - raw.setEsriProcessedAt(LocalDateTime.now()); - rawRepo.save(raw); } } + diff --git a/src/main/java/com/goi/erp/service/VehicleDispatchAutoCloseService.java b/src/main/java/com/goi/erp/service/VehicleDispatchAutoCloseService.java index 0f9f593..cb3b99e 100644 --- a/src/main/java/com/goi/erp/service/VehicleDispatchAutoCloseService.java +++ b/src/main/java/com/goi/erp/service/VehicleDispatchAutoCloseService.java @@ -1,8 +1,9 @@ package com.goi.erp.service; import com.goi.erp.client.SamsaraVehicleStatGpsClient; -import com.goi.erp.client.SysConfigClient; import com.goi.erp.common.util.GeoUtil; +import com.goi.erp.dto.ProcessResultDto; +import com.goi.erp.dto.ScheduleWorkerRequestDto; import com.goi.erp.dto.VehicleStatGpsResponseDto; import com.goi.erp.entity.VehicleDispatch; import com.goi.erp.repository.VehicleDispatchRepository; @@ -35,22 +36,38 @@ public class VehicleDispatchAutoCloseService { private final VehicleDispatchRepository vehicleDispatchRepository; private final VehicleExternalMapService vehicleExternalMapService; private final SamsaraVehicleStatGpsClient vehicleStatClient; - private final SysConfigClient sysConfigClient; @Transactional - public void processAutoClose(LocalDate dispatchDate) { - // config - BigDecimal homeLat = sysConfigClient.getDecimal("SYS", "HOME_LATITUDE"); - BigDecimal homeLon = sysConfigClient.getDecimal("SYS", "HOME_LONGITUDE"); - Integer radiusMeters = sysConfigClient.getInt("OPR", "HOME_RADIUS_METERS"); - Integer pausedMinutes = sysConfigClient.getInt("OPR", "PAUSED_CLOSE_MINUTES"); + public ProcessResultDto processAutoClose(ScheduleWorkerRequestDto request) { + + int processed = 0; + int failed = 0; + + // config + Map> cfg = request.getConfig(); - // 0. auto open check λ¨Όμ € - processAutoOpen(dispatchDate, homeLat, homeLon, radiusMeters); - - // 1. λŒ€μƒ vehicle_dispatch 쑰회 (C μ œμ™Έ) + BigDecimal homeLat = new BigDecimal(cfg.get("SYS").get("HOME_LATITUDE")); + BigDecimal homeLon = new BigDecimal(cfg.get("SYS").get("HOME_LONGITUDE")); + Integer radiusMeters = Integer.valueOf(cfg.get("OPR").get("HOME_RADIUS_METERS")); + Integer pausedCloseMinutes = Integer.valueOf(cfg.get("OPR").getOrDefault("PAUSED_CLOSE_MINUTES", "30")); + + LocalDate dispatchDate = request.getFrom().toLocalDate(); + + // 0. auto open + try { + processAutoOpen(dispatchDate, homeLat, homeLon, radiusMeters); + } catch (Exception e) { + log.error("[AUTO-CLOSE] auto-open failed", e); + failed++; + } + + // 1. λŒ€μƒ 쑰회 List targets = vehicleDispatchRepository.findAllNotClosed(dispatchDate); - if (targets.isEmpty()) return; + + if (targets.isEmpty()) { + log.info("[AUTO-CLOSE] no dispatch to process"); + return new ProcessResultDto(0, 0); + } // 2. external vehicleIds μˆ˜μ§‘ List vehicleIds = targets.stream() @@ -58,8 +75,10 @@ public class VehicleDispatchAutoCloseService { .filter(Objects::nonNull) .distinct() .toList(); - if (vehicleIds.isEmpty()) return; - + if (vehicleIds.isEmpty()) { + log.info("[AUTO-CLOSE] no vehicles to process"); + return new ProcessResultDto(0, 0); + } // 3. internal vehicleId -> samsara externalId λ§€ν•‘ bulk 쑰회 Map vehicleIdToExternalId = vehicleExternalMapService.findExternalIdsByVehicleIds("SAMSARA", vehicleIds); @@ -68,11 +87,17 @@ public class VehicleDispatchAutoCloseService { .filter(Objects::nonNull) .distinct() .toList(); - if (externalIds.isEmpty()) return; + if (externalIds.isEmpty()) { + log.info("[AUTO-CLOSE] no external vehicles to process"); + return new ProcessResultDto(0, 0); + } // 4. call List stats = vehicleStatClient.fetchVehicleStats(externalIds); - if (stats.isEmpty()) return; + if (stats.isEmpty()) { + log.info("[AUTO-CLOSE] no stats to process"); + return new ProcessResultDto(0, 0); + } // 5. response Map statMap = stats.stream() @@ -81,64 +106,117 @@ public class VehicleDispatchAutoCloseService { s -> s, (a, b) -> a )); - - // 6. dispatch별 처리 + + for (VehicleDispatch dispatch : targets) { - - Long internalVehicleId = dispatch.getVedVehId(); - if (internalVehicleId == null) continue; - - String externalId = vehicleIdToExternalId.get(internalVehicleId); - if (externalId == null) continue; - - VehicleStatGpsResponseDto stat = statMap.get(externalId); - if (stat == null) continue; - - // κ΄€μΈ‘κ°’ 계산 - boolean engineOn = Boolean.TRUE.equals(stat.getVesEngineOn()); - - double distanceMeters = GeoUtil.distanceMeters( - homeLat.doubleValue(), - homeLon.doubleValue(), - stat.getVesLatitude().doubleValue(), - stat.getVesLongitude().doubleValue() - ); - - boolean isAtHome = distanceMeters <= radiusMeters; - boolean isStoppedAtHome = !engineOn && isAtHome; - - // μƒνƒœ λ³€κ²½ - String status = dispatch.getVedStatus(); - - // P β†’ O (λ‹€μ‹œ 좜발) - if ("P".equals(status) && !isStoppedAtHome) { - transitionToOnRoute(dispatch); - continue; - } - - // O -> P (도착 ν›„ μ •μ°¨) - if ("O".equals(status) && isStoppedAtHome) { - transitionToPaused(dispatch); - continue; - } - - // P -> C (μž₯μ‹œκ°„ μ •μ°¨) - if ("P".equals(status) && isStoppedAtHome) { - - if (dispatch.getVedPausedAt() == null) { - continue; - } - - long pausedMin = Duration.between( - dispatch.getVedPausedAt(), - LocalDateTime.now() - ).toMinutes(); - - if (pausedMin >= pausedMinutes) { - transitionToClosed(dispatch, "AUTO_CLOSE_HOME_IDLE"); + try { + boolean changed = processSingleDispatch( + dispatch, + vehicleIdToExternalId, + statMap, + homeLat, + homeLon, + radiusMeters, + pausedCloseMinutes + ); + if (changed) { + processed++; } + } catch (Exception e) { + failed++; + log.error( + "[AUTO-CLOSE] failed dispatchId={} msg={}", + dispatch.getVedId(), + e.getMessage(), + e + ); } } + + log.info("[AUTO-CLOSE] done processed={} failed={}", processed, failed); + return new ProcessResultDto(processed, failed); + } + + + private boolean processSingleDispatch( + VehicleDispatch dispatch, + Map vehicleIdToExternalId, + Map statMap, + BigDecimal homeLat, + BigDecimal homeLon, + Integer radiusMeters, + Integer pausedCloseMinutes + ) { + Long internalVehicleId = dispatch.getVedVehId(); + if (internalVehicleId == null) { + return false; + } + + // internal β†’ external vehicleId + String externalId = vehicleIdToExternalId.get(internalVehicleId); + if (externalId == null) { + return false; + } + + // μ™ΈλΆ€ GPS μƒνƒœ + VehicleStatGpsResponseDto stat = statMap.get(externalId); + if (stat == null) { + return false; + } + + boolean engineOn = Boolean.TRUE.equals(stat.getVesEngineOn()); + + double distanceMeters = GeoUtil.distanceMeters( + homeLat.doubleValue(), + homeLon.doubleValue(), + stat.getVesLatitude().doubleValue(), + stat.getVesLongitude().doubleValue() + ); + + boolean isAtHome = distanceMeters <= radiusMeters; + boolean isStoppedAtHome = !engineOn && isAtHome; + + String status = dispatch.getVedStatus(); + + /* + * μƒνƒœ 전이 κ·œμΉ™ + * + * O β†’ P : HOME κ·Όμ ‘ + engine OFF + * P β†’ O : HOME μ΄νƒˆ or engine ON + * P β†’ C : HOME κ·Όμ ‘ + engine OFF + paused_at β‰₯ NλΆ„ + */ + + // P β†’ O (λ‹€μ‹œ 좜발) + if ("P".equals(status) && !isStoppedAtHome) { + transitionToOnRoute(dispatch); + return true; + } + + // O β†’ P (도착 ν›„ μ •μ°¨) + if ("O".equals(status) && isStoppedAtHome) { + transitionToPaused(dispatch); + return true; + } + + // P β†’ C (μž₯μ‹œκ°„ μ •μ°¨) + if ("P".equals(status) && isStoppedAtHome) { + + if (dispatch.getVedPausedAt() == null) { + return false; + } + + long pausedMinutes = Duration.between( + dispatch.getVedPausedAt(), + LocalDateTime.now() + ).toMinutes(); + + if (pausedMinutes >= pausedCloseMinutes) { + transitionToClosed(dispatch, "AUTO_CLOSE_HOME_IDLE"); + return true; + } + } + + return false; } private void processAutoOpen( diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 09d4e36..6d3bb35 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -22,6 +22,9 @@ application: expiration: 86400000 # a day refresh-token: expiration: 604800000 # 7 days + system: + client-id: opr-rest-api + client-secret: ${SYSTEM_CLIENT_SECRET} pagination: default-page: 0 default-size: 20 @@ -33,6 +36,9 @@ server: # ================================ # ADD THIS # ================================ +auth: + api: + base-url: http://localhost:8080/auth-service hcm: api: base-url: http://localhost:8081/hcm-rest-api