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 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; } }