opr-rest-api/src/main/java/com/goi/erp/service/VehicleInspectionService.java

239 lines
8.7 KiB
Java

package com.goi.erp.service;
import com.goi.erp.dto.VehicleInspectionDefectDto;
import com.goi.erp.dto.VehicleInspectionDto;
import com.goi.erp.entity.VehicleDispatch;
import com.goi.erp.entity.VehicleInspection;
import com.goi.erp.entity.VehicleInspectionDefect;
import com.goi.erp.repository.VehicleDispatchRepository;
import com.goi.erp.repository.VehicleInspectionDefectRepository;
import com.goi.erp.repository.VehicleInspectionRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
@Slf4j
@Service
@RequiredArgsConstructor
public class VehicleInspectionService {
private final VehicleInspectionRepository inspectionRepository;
private final VehicleInspectionDefectRepository defectRepository;
private final VehicleDispatchRepository dispatchRepository;
private final HcmEmployeeClient hcmEmployeeClient;
private final VehicleExternalMapService vehicleExternalMapService;
private final VehicleDispatchService vehicleDispatchService;
// private final VehicleService vehicleService;
/**
* Create or Update Vehicle Inspection (idempotent)
* -> defects upsert
* -> evaluate dispatch close by inspection
* -> create dispatch (preTrip) if needed
*/
@Transactional
public VehicleInspection saveInspection(VehicleInspectionDto dto) {
VehicleInspection inspection = inspectionRepository
.findByVeiSourceAndVeiSourceId(dto.getSource(), dto.getSourceId())
.orElseGet(VehicleInspection::new);
// ===== ID resolve =====
Long driverId = resolveDriverId(dto);
Long subDriverId = resolveSubDriverId(dto);
Long vehicleId = resolveVehicleId(dto);
log.info(
"[INSPECTION] start source={} sourceId={} type={} vehId={} driverId={}",
dto.getSource(),
dto.getSourceId(),
dto.getInspectionType(),
vehicleId,
driverId
);
// ===== Inspection =====
inspection.setVeiUuid(UUID.randomUUID());
inspection.setVeiVehId(vehicleId);
inspection.setVeiDriverId(driverId);
inspection.setVeiSubDriverId(subDriverId);
inspection.setVeiInspectionDate(dto.getInspectionDate());
inspection.setVeiInspectionType(dto.getInspectionType());
inspection.setVeiStartAt(dto.getStartAt());
inspection.setVeiEndAt(dto.getEndAt());
inspection.setVeiResult(dto.getResult());
inspection.setVeiOdometer(dto.getOdometer());
inspection.setVeiHasDefect(dto.getHasDefect());
inspection.setVeiSource(dto.getSource());
inspection.setVeiSourceId(dto.getSourceId());
inspection = inspectionRepository.save(inspection);
// ===== Defects =====
if (dto.getDefects() != null) {
saveDefects(inspection.getVeiId(), dto.getDefects());
}
// ===== Dispatch close evaluation (Rule 1~3) =====
// - postTrip이면 여기서 closeDispatch 호출됨
// - preTrip new driver/vehicle 조건도 여기서 기존 open close됨
vehicleDispatchService.evaluateDispatchCloseByInspection(inspection);
// ===== Dispatch create (preTrip) =====
// - 이미 open dispatch가 있으면 생성하지 않음
// - odometerStart = inspection.odometer (가능하면)
if (shouldCreateDispatch(inspection)) {
// open 여부
boolean hasOpenDispatch = dispatchRepository.findOpenDispatchByVehId(inspection.getVeiVehId()).isPresent();
if (!hasOpenDispatch) {
BigDecimal odometerStart =
(inspection.getVeiOdometer() != null)
? BigDecimal.valueOf(inspection.getVeiOdometer())
: null;
VehicleDispatch dispatch =
vehicleDispatchService.createFromPreTripInspection(
inspection.getVeiVehId(),
inspection.getVeiDriverId(),
inspection.getVeiSubDriverId(),
inspection.getVeiInspectionDate(),
inspection.getVeiStartAt(),
odometerStart,
"AUTO"
);
// inspection에 dispatch 연결
inspection.setVeiDispatchId(dispatch.getVedId());
inspection = inspectionRepository.save(inspection);
} else {
log.info(
"[DISPATCH][CREATE] skipped. open dispatch already exists. vehId={}",
inspection.getVeiVehId()
);
}
} else {
log.debug(
"[DISPATCH][CREATE] skip. reason=shouldCreateDispatch=false inspectionId={}",
inspection.getVeiId()
);
}
return inspection;
}
/**
* defects 저장 (externalDefectId 기준 upsert)
* - externalDefectId가 null이면 "수기"로 간주 → 그냥 매번 INSERT 하면 중복 가능성이 커짐
* => 수기는 (veiId + defectType + comment + createdAt) 조합으로 찾아
*/
private void saveDefects(Long veiId, List<VehicleInspectionDefectDto> defects) {
// 없으면
if (defects == null || defects.isEmpty()) return;
// defect 별
for (VehicleInspectionDefectDto d : defects) {
VehicleInspectionDefect defect;
// 외부 defect id가 있으면 그걸로 idempotent
if (d.getVidExternalDefectId() != null && !d.getVidExternalDefectId().isBlank()) {
defect = defectRepository
.findByVidVeiIdAndVidExternalDefectId(veiId, d.getVidExternalDefectId())
.orElseGet(VehicleInspectionDefect::new);
} else {
// 수기 defect: 최소한의 중복 방지 (원하면 repository 메서드로 더 정교하게)
defect = new VehicleInspectionDefect();
}
defect.setVidVeiId(veiId);
defect.setVidExternalDefectId(d.getVidExternalDefectId());
defect.setVidDefectType(d.getDefectType());
defect.setVidComment(d.getComment());
defect.setVidIsResolved(d.getIsResolved());
defect.setVidCreatedAt(d.getCreatedAt());
defect.setVidResolvedAt(d.getResolvedAt());
defect.setVidResolvedBy(resolveEmpId(d));
defectRepository.save(defect);
}
}
private Long resolveDriverId(VehicleInspectionDto dto) {
if (dto.getDriverId() != null) return dto.getDriverId();
if (dto.getExternalDriverId() != null) {
return hcmEmployeeClient.getEmpIdFromExternalId(
"SAMSARA",
dto.getExternalDriverId()
);
}
if (dto.getDriverUuid() != null) {
return hcmEmployeeClient.getEmpIdFromUuid(dto.getDriverUuid());
}
return null;
}
private Long resolveSubDriverId(VehicleInspectionDto dto) {
if (dto.getSubDriverId() != null) return dto.getSubDriverId();
if (dto.getExternalSubDriverId() != null) {
return hcmEmployeeClient.getEmpIdFromExternalId(
"SAMSARA",
dto.getExternalSubDriverId()
);
}
if (dto.getSubDriverUuid() != null) {
return hcmEmployeeClient.getEmpIdFromUuid(dto.getSubDriverUuid());
}
return null;
}
private Long resolveVehicleId(VehicleInspectionDto dto) {
if (dto.getVehId() != null) return dto.getVehId();
if (dto.getExternalVehicleId() != null) {
return vehicleExternalMapService
.findMapping("SAMSARA", dto.getExternalVehicleId())
.getVexVehicleId();
}
return null;
}
private Long resolveEmpId(VehicleInspectionDefectDto dto) {
if (dto.getResolvedBy() != null) return dto.getResolvedBy();
if (dto.getResolvedByExternalId() != null) {
return hcmEmployeeClient.getEmpIdFromExternalId(
"SAMSARA",
dto.getResolvedByExternalId()
);
}
return null;
}
private boolean shouldCreateDispatch(VehicleInspection inspection) {
return "preTrip".equalsIgnoreCase(inspection.getVeiInspectionType())
&& inspection.getVeiDispatchId() == null
&& inspection.getVeiVehId() != null
&& inspection.getVeiDriverId() != null;
}
}