[Schedule]
- Created schedule worker. (schedules are only in sys-rest-api) [HcmClient] - Added get system token to call hcm-rest-api
This commit is contained in:
parent
923b6d5aca
commit
6a7882a028
|
|
@ -14,6 +14,7 @@ import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
import com.goi.erp.token.PermissionAuthenticationToken;
|
import com.goi.erp.token.PermissionAuthenticationToken;
|
||||||
|
import com.goi.erp.token.SystemTokenProvider;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -24,54 +25,41 @@ import java.util.UUID;
|
||||||
public class HcmEmployeeClient {
|
public class HcmEmployeeClient {
|
||||||
|
|
||||||
private final RestTemplate restTemplate;
|
private final RestTemplate restTemplate;
|
||||||
|
private final SystemTokenProvider systemTokenProvider;
|
||||||
|
|
||||||
@Value("${hcm.api.base-url}")
|
@Value("${hcm.api.base-url}")
|
||||||
private String hcmBaseUrl;
|
private String hcmBaseUrl;
|
||||||
|
|
||||||
public Long getEmpIdFromExternalId(String externalSource, String externalId) {
|
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 {
|
try {
|
||||||
// set token in header
|
HttpEntity<Void> entity = new HttpEntity<>(systemHeaders());
|
||||||
String jwt = getCurrentJwt();
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.set("Authorization", "Bearer " + jwt);
|
|
||||||
HttpEntity<Void> entity = new HttpEntity<>(headers);
|
|
||||||
|
|
||||||
// GET
|
|
||||||
ResponseEntity<Map<String, Object>> response =
|
ResponseEntity<Map<String, Object>> response =
|
||||||
restTemplate.exchange(
|
restTemplate.exchange(
|
||||||
url,
|
url,
|
||||||
HttpMethod.GET,
|
HttpMethod.GET,
|
||||||
entity,
|
entity,
|
||||||
new ParameterizedTypeReference<Map<String, Object>>() {}
|
new ParameterizedTypeReference<>() {}
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, Object> body = response.getBody();
|
Map<String, Object> body = response.getBody();
|
||||||
//System.out.println("RESPONSE ➜ " + body);
|
if (body != null && body.get("eexEmpId") instanceof Number n) {
|
||||||
|
return n.longValue();
|
||||||
if (body != null && body.get("eexEmpId") != null) {
|
|
||||||
|
|
||||||
Object raw = body.get("eexEmpId");
|
|
||||||
|
|
||||||
if (raw instanceof Number) {
|
|
||||||
return ((Number) raw).longValue(); // 🔥 모든 숫자를 Long 변환
|
|
||||||
}
|
|
||||||
|
|
||||||
// 예상 밖 타입일 경우
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(
|
log.error("[HCM][GET] externalId lookup error", e);
|
||||||
"[EXTERNAL][GET] externalId lookup error: {}",
|
|
||||||
e.getMessage()
|
|
||||||
);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Long getEmpIdFromUuid(UUID uuid) {
|
public Long getEmpIdFromUuid(UUID uuid) {
|
||||||
|
|
||||||
String url = hcmBaseUrl + "/employee/" + uuid;
|
String url = hcmBaseUrl + "/employee/" + uuid;
|
||||||
|
|
@ -125,6 +113,12 @@ public class HcmEmployeeClient {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private HttpHeaders systemHeaders() {
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setBearerAuth(systemTokenProvider.getToken());
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ public class SecurityConfig {
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
||||||
.requestMatchers("/ext/**").permitAll()
|
.requestMatchers("/ext/**").permitAll()
|
||||||
|
.requestMatchers("/scheduler/**").permitAll()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
) // 요청 권한 설정
|
) // 요청 권한 설정
|
||||||
.addFilterBefore(new CorsFilter(corsConfigurationSource()), UsernamePasswordAuthenticationFilter.class) // JWT 필터 전에 CorsFilter 등록
|
.addFilterBefore(new CorsFilter(corsConfigurationSource()), UsernamePasswordAuthenticationFilter.class) // JWT 필터 전에 CorsFilter 등록
|
||||||
|
|
|
||||||
|
|
@ -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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -11,7 +11,6 @@ import lombok.NoArgsConstructor;
|
||||||
@Builder
|
@Builder
|
||||||
public class ExtIngestResult {
|
public class ExtIngestResult {
|
||||||
private String source;
|
private String source;
|
||||||
private String recordType;
|
|
||||||
private int received;
|
private int received;
|
||||||
private int inserted;
|
private int inserted;
|
||||||
private int updated;
|
private int updated;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import java.util.List;
|
||||||
@Builder
|
@Builder
|
||||||
public class ExtSamsaraInspectionIngestCommand {
|
public class ExtSamsaraInspectionIngestCommand {
|
||||||
private String source;
|
private String source;
|
||||||
private String recordType;
|
|
||||||
private LocalDateTime fetchedAt;
|
private LocalDateTime fetchedAt;
|
||||||
private List<ExtSamsaraInspectionRecordDto> records;
|
private List<ExtSamsaraInspectionRecordDto> records;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,9 +49,6 @@ public class ExtSamsaraRawInspection {
|
||||||
@Column(name = "esri_source", nullable = false, length = 20)
|
@Column(name = "esri_source", nullable = false, length = 20)
|
||||||
private String esriSource; // SAMSARA
|
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)
|
@Column(name = "esri_external_id", nullable = false, length = 50)
|
||||||
private String esriExternalId; // inspection id
|
private String esriExternalId; // inspection id
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,6 @@ public class ExtSamsaraInspectionIngestService {
|
||||||
|
|
||||||
return ExtIngestResult.builder()
|
return ExtIngestResult.builder()
|
||||||
.source(command.getSource())
|
.source(command.getSource())
|
||||||
.recordType(command.getRecordType())
|
|
||||||
.received(command.getRecords().size())
|
.received(command.getRecords().size())
|
||||||
.inserted(inserted)
|
.inserted(inserted)
|
||||||
.updated(updated)
|
.updated(updated)
|
||||||
|
|
@ -90,7 +89,6 @@ public class ExtSamsaraInspectionIngestService {
|
||||||
|
|
||||||
ExtSamsaraRawInspection entity = ExtSamsaraRawInspection.builder()
|
ExtSamsaraRawInspection entity = ExtSamsaraRawInspection.builder()
|
||||||
.esriSource(command.getSource())
|
.esriSource(command.getSource())
|
||||||
.esriRecordType(command.getRecordType())
|
|
||||||
.esriExternalId(record.getExternalId())
|
.esriExternalId(record.getExternalId())
|
||||||
.esriVehicleExtId(record.getVehicleExternalId())
|
.esriVehicleExtId(record.getVehicleExternalId())
|
||||||
.esriDriverExtId(record.getDriverExternalId())
|
.esriDriverExtId(record.getDriverExternalId())
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.goi.erp.service;
|
package com.goi.erp.service;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.goi.erp.dto.ProcessResultDto;
|
||||||
import com.goi.erp.dto.VehicleInspectionDefectDto;
|
import com.goi.erp.dto.VehicleInspectionDefectDto;
|
||||||
import com.goi.erp.dto.VehicleInspectionDto;
|
import com.goi.erp.dto.VehicleInspectionDto;
|
||||||
import com.goi.erp.entity.ExtSamsaraRawInspection;
|
import com.goi.erp.entity.ExtSamsaraRawInspection;
|
||||||
|
|
@ -11,6 +12,7 @@ import jakarta.transaction.Transactional;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -25,43 +27,61 @@ public class ExtSamsaraInspectionProcessor {
|
||||||
private final ExtSamsaraRawInspectionRepository rawRepo;
|
private final ExtSamsaraRawInspectionRepository rawRepo;
|
||||||
private final VehicleInspectionService vehicleInspectionService;
|
private final VehicleInspectionService vehicleInspectionService;
|
||||||
|
|
||||||
@Transactional
|
public ProcessResultDto processUnprocessed() {
|
||||||
public void processUnprocessed() {
|
|
||||||
|
|
||||||
List<ExtSamsaraRawInspection> raws = rawRepo.findByEsriProcessedFalse();
|
|
||||||
|
|
||||||
if (raws.isEmpty()) {
|
|
||||||
log.info("[DVIR_PROCESSOR] no unprocessed raw inspections");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// log 용
|
|
||||||
int processed = 0;
|
int processed = 0;
|
||||||
int failed = 0;
|
int failed = 0;
|
||||||
|
|
||||||
|
List<ExtSamsaraRawInspection> 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) {
|
for (ExtSamsaraRawInspection raw : raws) {
|
||||||
try {
|
try {
|
||||||
processSingle(raw);
|
processSingleTx(raw);
|
||||||
processed++;
|
processed++;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
failed++;
|
failed++;
|
||||||
log.error(
|
// markFailed(raw, e);
|
||||||
"[DVIR_PROCESSOR] failed rawId={} externalId={} msg={}",
|
|
||||||
raw.getEsriId(),
|
|
||||||
raw.getEsriExternalId(),
|
|
||||||
e.getMessage(),
|
|
||||||
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) {
|
private void processSingle(ExtSamsaraRawInspection raw) {
|
||||||
|
|
||||||
JsonNode payload = raw.getEsriPayload();
|
JsonNode payload = raw.getEsriPayload();
|
||||||
|
|
||||||
// Inspection
|
|
||||||
VehicleInspectionDto dto = new VehicleInspectionDto();
|
VehicleInspectionDto dto = new VehicleInspectionDto();
|
||||||
dto.setSource("SAMSARA");
|
dto.setSource("SAMSARA");
|
||||||
dto.setSourceId(Long.valueOf(raw.getEsriExternalId()));
|
dto.setSourceId(Long.valueOf(raw.getEsriExternalId()));
|
||||||
|
|
@ -80,14 +100,7 @@ public class ExtSamsaraInspectionProcessor {
|
||||||
dto.setResult(payload.path("safetyStatus").asText(null));
|
dto.setResult(payload.path("safetyStatus").asText(null));
|
||||||
dto.setOdometer(payload.path("odometerMeters").asLong(0L));
|
dto.setOdometer(payload.path("odometerMeters").asLong(0L));
|
||||||
|
|
||||||
log.info(
|
// defects
|
||||||
"[DVIR] rawId={} odometerMeters node={} value={}",
|
|
||||||
raw.getEsriExternalId(),
|
|
||||||
payload.get("odometerMeters"),
|
|
||||||
payload.path("odometerMeters").asLong(-1)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Defects
|
|
||||||
JsonNode defectsNode = payload.path("vehicleDefects");
|
JsonNode defectsNode = payload.path("vehicleDefects");
|
||||||
List<VehicleInspectionDefectDto> defectDtos = new ArrayList<>();
|
List<VehicleInspectionDefectDto> defectDtos = new ArrayList<>();
|
||||||
|
|
||||||
|
|
@ -98,33 +111,22 @@ public class ExtSamsaraInspectionProcessor {
|
||||||
defectDto.setDefectType(d.path("defectType").asText(null));
|
defectDto.setDefectType(d.path("defectType").asText(null));
|
||||||
defectDto.setComment(d.path("comment").asText(null));
|
defectDto.setComment(d.path("comment").asText(null));
|
||||||
defectDto.setIsResolved(d.path("isResolved").asBoolean(false));
|
defectDto.setIsResolved(d.path("isResolved").asBoolean(false));
|
||||||
|
|
||||||
JsonNode resolvedBy = d.path("resolvedBy");
|
JsonNode resolvedBy = d.path("resolvedBy");
|
||||||
if (!resolvedBy.isMissingNode()) {
|
if (!resolvedBy.isMissingNode()) {
|
||||||
defectDto.setResolvedByExternalId(resolvedBy.path("id").asText(null));
|
defectDto.setResolvedByExternalId(resolvedBy.path("id").asText(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
defectDto.setResolvedAt(DateTimeUtil.parse(d.path("resolvedAtTime")));
|
defectDto.setResolvedAt(DateTimeUtil.parse(d.path("resolvedAtTime")));
|
||||||
defectDto.setCreatedAt(DateTimeUtil.parse(d.path("createdAtTime")));
|
defectDto.setCreatedAt(DateTimeUtil.parse(d.path("createdAtTime")));
|
||||||
defectDtos.add(defectDto);
|
defectDtos.add(defectDto);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dto.setDefects(defectDtos);
|
dto.setDefects(defectDtos);
|
||||||
dto.setHasDefect(!defectDtos.isEmpty());
|
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);
|
vehicleInspectionService.saveInspection(dto);
|
||||||
|
|
||||||
// raw 처리 완료
|
|
||||||
raw.setEsriProcessed(true);
|
|
||||||
raw.setEsriProcessedAt(LocalDateTime.now());
|
|
||||||
rawRepo.save(raw);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
package com.goi.erp.service;
|
package com.goi.erp.service;
|
||||||
|
|
||||||
import com.goi.erp.client.SamsaraVehicleStatGpsClient;
|
import com.goi.erp.client.SamsaraVehicleStatGpsClient;
|
||||||
import com.goi.erp.client.SysConfigClient;
|
|
||||||
import com.goi.erp.common.util.GeoUtil;
|
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.dto.VehicleStatGpsResponseDto;
|
||||||
import com.goi.erp.entity.VehicleDispatch;
|
import com.goi.erp.entity.VehicleDispatch;
|
||||||
import com.goi.erp.repository.VehicleDispatchRepository;
|
import com.goi.erp.repository.VehicleDispatchRepository;
|
||||||
|
|
@ -35,22 +36,38 @@ public class VehicleDispatchAutoCloseService {
|
||||||
private final VehicleDispatchRepository vehicleDispatchRepository;
|
private final VehicleDispatchRepository vehicleDispatchRepository;
|
||||||
private final VehicleExternalMapService vehicleExternalMapService;
|
private final VehicleExternalMapService vehicleExternalMapService;
|
||||||
private final SamsaraVehicleStatGpsClient vehicleStatClient;
|
private final SamsaraVehicleStatGpsClient vehicleStatClient;
|
||||||
private final SysConfigClient sysConfigClient;
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void processAutoClose(LocalDate dispatchDate) {
|
public ProcessResultDto processAutoClose(ScheduleWorkerRequestDto request) {
|
||||||
// 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");
|
|
||||||
|
|
||||||
// 0. auto open check 먼저
|
int processed = 0;
|
||||||
processAutoOpen(dispatchDate, homeLat, homeLon, radiusMeters);
|
int failed = 0;
|
||||||
|
|
||||||
// 1. 대상 vehicle_dispatch 조회 (C 제외)
|
// config
|
||||||
|
Map<String, Map<String, String>> cfg = request.getConfig();
|
||||||
|
|
||||||
|
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<VehicleDispatch> targets = vehicleDispatchRepository.findAllNotClosed(dispatchDate);
|
List<VehicleDispatch> 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 수집
|
// 2. external vehicleIds 수집
|
||||||
List<Long> vehicleIds = targets.stream()
|
List<Long> vehicleIds = targets.stream()
|
||||||
|
|
@ -58,8 +75,10 @@ public class VehicleDispatchAutoCloseService {
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.distinct()
|
.distinct()
|
||||||
.toList();
|
.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 조회
|
// 3. internal vehicleId -> samsara externalId 매핑 bulk 조회
|
||||||
Map<Long, String> vehicleIdToExternalId = vehicleExternalMapService.findExternalIdsByVehicleIds("SAMSARA", vehicleIds);
|
Map<Long, String> vehicleIdToExternalId = vehicleExternalMapService.findExternalIdsByVehicleIds("SAMSARA", vehicleIds);
|
||||||
|
|
@ -68,11 +87,17 @@ public class VehicleDispatchAutoCloseService {
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.distinct()
|
.distinct()
|
||||||
.toList();
|
.toList();
|
||||||
if (externalIds.isEmpty()) return;
|
if (externalIds.isEmpty()) {
|
||||||
|
log.info("[AUTO-CLOSE] no external vehicles to process");
|
||||||
|
return new ProcessResultDto(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
// 4. call
|
// 4. call
|
||||||
List<VehicleStatGpsResponseDto> stats = vehicleStatClient.fetchVehicleStats(externalIds);
|
List<VehicleStatGpsResponseDto> 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
|
// 5. response
|
||||||
Map<String, VehicleStatGpsResponseDto> statMap = stats.stream()
|
Map<String, VehicleStatGpsResponseDto> statMap = stats.stream()
|
||||||
|
|
@ -82,63 +107,116 @@ public class VehicleDispatchAutoCloseService {
|
||||||
(a, b) -> a
|
(a, b) -> a
|
||||||
));
|
));
|
||||||
|
|
||||||
// 6. dispatch별 처리
|
|
||||||
for (VehicleDispatch dispatch : targets) {
|
for (VehicleDispatch dispatch : targets) {
|
||||||
|
try {
|
||||||
Long internalVehicleId = dispatch.getVedVehId();
|
boolean changed = processSingleDispatch(
|
||||||
if (internalVehicleId == null) continue;
|
dispatch,
|
||||||
|
vehicleIdToExternalId,
|
||||||
String externalId = vehicleIdToExternalId.get(internalVehicleId);
|
statMap,
|
||||||
if (externalId == null) continue;
|
homeLat,
|
||||||
|
homeLon,
|
||||||
VehicleStatGpsResponseDto stat = statMap.get(externalId);
|
radiusMeters,
|
||||||
if (stat == null) continue;
|
pausedCloseMinutes
|
||||||
|
);
|
||||||
// 관측값 계산
|
if (changed) {
|
||||||
boolean engineOn = Boolean.TRUE.equals(stat.getVesEngineOn());
|
processed++;
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
} 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<Long, String> vehicleIdToExternalId,
|
||||||
|
Map<String, VehicleStatGpsResponseDto> 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(
|
private void processAutoOpen(
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,9 @@ application:
|
||||||
expiration: 86400000 # a day
|
expiration: 86400000 # a day
|
||||||
refresh-token:
|
refresh-token:
|
||||||
expiration: 604800000 # 7 days
|
expiration: 604800000 # 7 days
|
||||||
|
system:
|
||||||
|
client-id: opr-rest-api
|
||||||
|
client-secret: ${SYSTEM_CLIENT_SECRET}
|
||||||
pagination:
|
pagination:
|
||||||
default-page: 0
|
default-page: 0
|
||||||
default-size: 20
|
default-size: 20
|
||||||
|
|
@ -33,6 +36,9 @@ server:
|
||||||
# ================================
|
# ================================
|
||||||
# ADD THIS
|
# ADD THIS
|
||||||
# ================================
|
# ================================
|
||||||
|
auth:
|
||||||
|
api:
|
||||||
|
base-url: http://localhost:8080/auth-service
|
||||||
hcm:
|
hcm:
|
||||||
api:
|
api:
|
||||||
base-url: http://localhost:8081/hcm-rest-api
|
base-url: http://localhost:8081/hcm-rest-api
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue