#
vincentlu
昨天 e643e4c346976a7a7543364b324d5c5cee982aad
zy-acs-manager/src/main/java/com/zy/acs/manager/core/service/GuaranteeRuntimeService.java
@@ -1,32 +1,155 @@
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;
/**
 * Runtime guarantee orchestration entry.
 */
public interface GuaranteeRuntimeService {
@Slf4j
@Service
public class GuaranteeRuntimeService {
    /**
     * Lead time entrance: make sure enough vehicles can reach the target window.
     */
    void prepare(Guarantee plan, LocalDateTime targetTime);
    private final AgvService agvService;
    private final AgvDetailService agvDetailService;
    private final TaskService taskService;
    private final MainLockWrapService mainLockWrapService;
    /**
     * Lock stage: restrict non-essential dispatching for reserve vehicles.
     */
    void lock(Guarantee plan, LocalDateTime targetTime);
    public GuaranteeRuntimeService(AgvService agvService,
                                   AgvDetailService agvDetailService,
                                   TaskService taskService,
                                   MainLockWrapService mainLockWrapService) {
        this.agvService = agvService;
        this.agvDetailService = agvDetailService;
        this.taskService = taskService;
        this.mainLockWrapService = mainLockWrapService;
    }
    /**
     * Executed repeatedly during the guarantee window to keep SLA.
     */
    void checkWindow(Guarantee plan, LocalDateTime targetTime);
    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);
    }
    /**
     * Called once after the window ends to release state/records.
     */
    void finish(Guarantee plan, LocalDateTime targetTime);
    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<Agv> scopedAgvs = findScopedAgvs(plan);
        int available = 0;
        List<ChargeCandidate> 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<Task>()
                .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<Agv> findScopedAgvs(Guarantee plan) {
        LambdaQueryWrapper<Agv> wrapper = new LambdaQueryWrapper<Agv>()
                .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<ChargeCandidate> candidates;
    }
    @Data
    @AllArgsConstructor
    private static class ChargeCandidate {
        private Agv agv;
        private AgvDetail detail;
    }
}