package com.zy.acs.manager.core.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.zy.acs.common.enums.AgvStatusType; import com.zy.acs.manager.manager.entity.Agv; import com.zy.acs.manager.manager.entity.AgvDetail; import com.zy.acs.manager.manager.entity.Guarantee; import com.zy.acs.manager.manager.entity.Task; import com.zy.acs.manager.manager.enums.StatusType; import com.zy.acs.manager.manager.enums.TaskStsType; import com.zy.acs.manager.manager.enums.TaskTypeType; import com.zy.acs.manager.manager.service.AgvDetailService; import com.zy.acs.manager.manager.service.AgvService; import com.zy.acs.manager.manager.service.TaskService; import lombok.AllArgsConstructor; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.Optional; @Slf4j @Service public class GuaranteeRuntimeService { private final AgvService agvService; private final AgvDetailService agvDetailService; private final TaskService taskService; private final MainLockWrapService mainLockWrapService; public GuaranteeRuntimeService(AgvService agvService, AgvDetailService agvDetailService, TaskService taskService, MainLockWrapService mainLockWrapService) { this.agvService = agvService; this.agvDetailService = agvDetailService; this.taskService = taskService; this.mainLockWrapService = mainLockWrapService; } public void prepare(Guarantee plan, LocalDateTime targetTime) { CapacitySnapshot snapshot = evaluate(plan); if (snapshot.availableCount >= plan.getRequiredCount()) { log.debug("Guarantee[{}] already has {} available vehicles for {}", plan.getName(), snapshot.availableCount, targetTime); return; } int shortage = plan.getRequiredCount() - snapshot.availableCount; log.info("Guarantee[{}] shortage {} vehicles for {} (minSoc={}%), scheduling charge.", plan.getName(), shortage, targetTime, plan.getMinSoc()); dispatchChargeTasks(snapshot, shortage); } public void lock(Guarantee plan, LocalDateTime targetTime) { log.info("Guarantee[{}] entering lock stage for {}", plan.getName(), targetTime); // TODO persist lock state / inform dispatcher once integration is ready } public void checkWindow(Guarantee plan, LocalDateTime targetTime) { CapacitySnapshot snapshot = evaluate(plan); if (snapshot.availableCount < plan.getRequiredCount()) { int shortage = plan.getRequiredCount() - snapshot.availableCount; log.warn("Guarantee[{}] window shortage {} vehicles for {}. Trigger quick recharge.", plan.getName(), shortage, targetTime); dispatchChargeTasks(snapshot, shortage); } } public void finish(Guarantee plan, LocalDateTime targetTime) { log.info("Guarantee[{}] finished window for {}", plan.getName(), targetTime); // TODO release any lock/flag once detail implementation is defined } private CapacitySnapshot evaluate(Guarantee plan) { int minSoc = Optional.ofNullable(plan.getMinSoc()).orElse(50); List scopedAgvs = findScopedAgvs(plan); int available = 0; List candidates = new ArrayList<>(); for (Agv agv : scopedAgvs) { AgvDetail detail = agvDetailService.selectByAgvId(agv.getId()); if (detail == null || detail.getSoc() == null) { continue; } if (detail.getSoc() >= minSoc && isAvailable(agv, detail)) { available++; } else { candidates.add(new ChargeCandidate(agv, detail)); } } candidates.sort(Comparator.comparingInt(o -> Optional.ofNullable(o.detail.getSoc()).orElse(0))); return new CapacitySnapshot(available, candidates); } private boolean isAvailable(Agv agv, AgvDetail detail) { if (!Objects.equals(agv.getStatus(), StatusType.ENABLE.val)) { return false; } if (detail.getAgvStatus() != null && detail.getAgvStatus().equals(AgvStatusType.CHARGE)) { return false; } boolean busy = taskService.count(new LambdaQueryWrapper() .eq(Task::getAgvId, agv.getId()) .in(Task::getTaskSts, TaskStsType.WAITING.val(), TaskStsType.ASSIGN.val(), TaskStsType.PROGRESS.val())) > 0; return !busy; } private void dispatchChargeTasks(CapacitySnapshot snapshot, int shortage) { if (shortage <= 0) { return; } int scheduled = 0; for (ChargeCandidate candidate : snapshot.candidates) { if (scheduled >= shortage) { break; } log.info("Scheduling AGV [{}] for charging to support guarantee", candidate.agv.getName()); mainLockWrapService.buildMinorTask(candidate.agv.getId(), TaskTypeType.TO_CHARGE, null, null); scheduled++; } } private List findScopedAgvs(Guarantee plan) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper() .eq(Agv::getStatus, StatusType.ENABLE.val); if ("MODEL".equalsIgnoreCase(plan.getScopeType()) && plan.getScopeValue() != null) { try { wrapper.eq(Agv::getAgvModel, Long.valueOf(plan.getScopeValue())); } catch (NumberFormatException ignore) { log.warn("Guarantee[{}] invalid scopeValue {}", plan.getName(), plan.getScopeValue()); } } return agvService.list(wrapper); } @Data private static class CapacitySnapshot { private final int availableCount; private final List candidates; } @Data @AllArgsConstructor private static class ChargeCandidate { private Agv agv; private AgvDetail detail; } }