|
|
|
|
@ -6,28 +6,44 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
|
|
|
|
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
|
|
|
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
|
|
|
|
import cn.iocoder.yudao.module.erp.controller.admin.autocode.util.AutoCodeUtil;
|
|
|
|
|
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ProductRelationRespVO;
|
|
|
|
|
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
|
|
|
|
|
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductUnitDO;
|
|
|
|
|
import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductMapper;
|
|
|
|
|
import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
|
|
|
|
|
import cn.iocoder.yudao.module.erp.service.product.ErpProductUnitService;
|
|
|
|
|
import cn.iocoder.yudao.module.mes.controller.admin.deviceledger.enums.CapacityTypeEnum;
|
|
|
|
|
import cn.iocoder.yudao.module.mes.controller.admin.plan.vo.PlanSaveReqVO;
|
|
|
|
|
import cn.iocoder.yudao.module.mes.controller.admin.plan.vo.PlanStatusEnum;
|
|
|
|
|
import cn.iocoder.yudao.module.mes.controller.admin.task.vo.*;
|
|
|
|
|
import cn.iocoder.yudao.module.mes.dal.dataobject.deviceledger.DeviceLedgerDO;
|
|
|
|
|
import cn.iocoder.yudao.module.mes.dal.dataobject.plan.PlanDO;
|
|
|
|
|
import cn.iocoder.yudao.module.mes.dal.dataobject.task.TaskDO;
|
|
|
|
|
import cn.iocoder.yudao.module.mes.dal.dataobject.task.TaskDetailDO;
|
|
|
|
|
import cn.iocoder.yudao.module.mes.dal.dataobject.task.ViewTaskProductSummary;
|
|
|
|
|
import cn.iocoder.yudao.module.mes.dal.mysql.deviceledger.DeviceLedgerMapper;
|
|
|
|
|
import cn.iocoder.yudao.module.mes.dal.mysql.plan.PlanMapper;
|
|
|
|
|
import cn.iocoder.yudao.module.mes.dal.mysql.task.TaskDetailMapper;
|
|
|
|
|
import cn.iocoder.yudao.module.mes.dal.mysql.task.TaskMapper;
|
|
|
|
|
import cn.iocoder.yudao.module.mes.dal.mysql.task.ViewTaskProductSummaryMapper;
|
|
|
|
|
import cn.iocoder.yudao.module.mes.dal.redis.no.MesNoRedisDAO;
|
|
|
|
|
import cn.iocoder.yudao.module.mes.strategy.task.ScheduleSortStrategyFactory;
|
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
|
|
import org.springframework.context.annotation.Lazy;
|
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
import org.springframework.validation.annotation.Validated;
|
|
|
|
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
|
|
|
import java.time.LocalDate;
|
|
|
|
|
import java.time.LocalDateTime;
|
|
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
|
|
import java.time.temporal.ChronoUnit;
|
|
|
|
|
import java.util.*;
|
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
|
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
|
|
|
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
|
|
|
|
@ -40,6 +56,7 @@ import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
|
|
|
|
|
*/
|
|
|
|
|
@Service
|
|
|
|
|
@Validated
|
|
|
|
|
@Slf4j
|
|
|
|
|
public class TaskServiceImpl implements TaskService {
|
|
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
@ -49,8 +66,17 @@ public class TaskServiceImpl implements TaskService {
|
|
|
|
|
@Resource
|
|
|
|
|
private MesNoRedisDAO noRedisDAO;
|
|
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
private ErpProductMapper erpProductMapper;
|
|
|
|
|
@Resource
|
|
|
|
|
@Lazy
|
|
|
|
|
private DeviceLedgerMapper deviceLedgerMapper;
|
|
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
private AutoCodeUtil autoCodeUtil;
|
|
|
|
|
@Resource
|
|
|
|
|
private ScheduleSortStrategyFactory scheduleSortStrategyFactory;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
|
|
@ -239,6 +265,9 @@ public class TaskServiceImpl implements TaskService {
|
|
|
|
|
for (TaskDetailRespVO respVO : resList) {
|
|
|
|
|
Long number = planMapper.selectSumTaskDetail(respVO.getId(),null);
|
|
|
|
|
respVO.setPlanNumber(number);
|
|
|
|
|
TaskDO taskDO = taskMapper.selectOne(Wrappers.<TaskDO>lambdaQuery().eq(TaskDO::getId, respVO.getTaskId()));
|
|
|
|
|
respVO.setTaskCode(taskDO !=null && StringUtils.isNotBlank(taskDO.getCode()) ? taskDO.getCode() : "");
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return resList;
|
|
|
|
|
@ -333,4 +362,308 @@ public class TaskServiceImpl implements TaskService {
|
|
|
|
|
}
|
|
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
|
|
public List<TaskOneClickScheduleRespVO> oneClickSchedule(TaskOneClickScheduleReqVO reqVO) {
|
|
|
|
|
if (reqVO == null || CollUtil.isEmpty(reqVO.getCreateReqVO())) {
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
}
|
|
|
|
|
Map<Long, TaskDO> taskCache = new HashMap<>();
|
|
|
|
|
Map<Long, ErpProductDO> productCache = new HashMap<>();
|
|
|
|
|
|
|
|
|
|
// 1) 先按规则排序
|
|
|
|
|
List<PlanSaveReqVO> sortedPlans = new ArrayList<>(reqVO.getCreateReqVO());
|
|
|
|
|
// 排序前:若明细交期为空,则回填为订单交期
|
|
|
|
|
sortedPlans.forEach(plan -> {
|
|
|
|
|
if (plan.getOrderDetailDeliveryDate() == null) {
|
|
|
|
|
plan.setOrderDetailDeliveryDate(plan.getDeliveryDate());
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
scheduleSortStrategyFactory.get(reqVO.getSortRule()).sort(sortedPlans);
|
|
|
|
|
for (PlanSaveReqVO p : sortedPlans) {
|
|
|
|
|
log.info("sorted: productCode={}, deliveryDate={}, orderDetailDeliveryDate={}, priority={}, workOrderCode={}, detailId={}, planNumber={}",
|
|
|
|
|
p.getProductCode(),
|
|
|
|
|
p.getDeliveryDate(),
|
|
|
|
|
p.getOrderDetailDeliveryDate(),
|
|
|
|
|
p.getOrderPriority(),
|
|
|
|
|
p.getWorkOrderCode(),
|
|
|
|
|
p.getOrderDetailId(),
|
|
|
|
|
p.getPlanNumber());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2) 返回结果:按设备分组
|
|
|
|
|
Map<Long, TaskOneClickScheduleRespVO> deviceResultMap = new LinkedHashMap<>();
|
|
|
|
|
|
|
|
|
|
// 3) 设备下次可开工时间(内存态,边排边更新)
|
|
|
|
|
Map<Long, LocalDateTime> deviceNextAvailableTime = new HashMap<>();
|
|
|
|
|
|
|
|
|
|
CapacityTypeEnum capacityType = CapacityTypeEnum.of(reqVO.getCapacityType());
|
|
|
|
|
if (capacityType == null) {
|
|
|
|
|
throw exception(UNSUPPORTED_CAPACITY_TYPE, reqVO.getCapacityType());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DateTimeFormatter DATETIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
|
|
DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (PlanSaveReqVO item : sortedPlans) {
|
|
|
|
|
if (item.getProductId() == null) {
|
|
|
|
|
throw exception(SCHEDULE_PRODUCT_ID_EMPTY);
|
|
|
|
|
}
|
|
|
|
|
if (item.getPlanNumber() == null || item.getPlanNumber() <= 0) {
|
|
|
|
|
throw exception(SCHEDULE_PLAN_NUMBER_INVALID);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4) 找产品关联设备(单设备直接用;多设备按设备可用时间最早优先,再按id升序兜底)
|
|
|
|
|
List<ProductRelationRespVO> deviceRels = erpProductMapper.selectDevicesByProductId(item.getProductId());
|
|
|
|
|
if (CollUtil.isEmpty(deviceRels)) {
|
|
|
|
|
throw exception(SCHEDULE_PRODUCT_DEVICE_NOT_FOUND, item.getProductId());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 构建设备候选(含选中口径的产能 + nextAvailable)
|
|
|
|
|
List<DeviceCandidate> candidates = new ArrayList<>();
|
|
|
|
|
for (ProductRelationRespVO rel : deviceRels) {
|
|
|
|
|
if (rel == null || rel.getId() == null) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
Long deviceId = rel.getId();
|
|
|
|
|
DeviceLedgerDO device = deviceLedgerMapper.selectById(deviceId);
|
|
|
|
|
if (device == null) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Integer capacityValue = capacityType.getCapacity(device);
|
|
|
|
|
if (capacityValue == null || capacityValue <= 0) {
|
|
|
|
|
continue; // 当前口径产能无效则跳过
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LocalDateTime nextTime = deviceNextAvailableTime.get(deviceId);
|
|
|
|
|
if (nextTime == null) {
|
|
|
|
|
nextTime = queryDeviceNextAvailableTime(deviceId);
|
|
|
|
|
deviceNextAvailableTime.put(deviceId, nextTime);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
candidates.add(new DeviceCandidate(deviceId, device.getDeviceName(), capacityValue, nextTime));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (CollUtil.isEmpty(candidates)) {
|
|
|
|
|
throw exception(SCHEDULE_PRODUCT_DEVICE_UNAVAILABLE, item.getProductId());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5) 选设备:优先空闲/最早可开工(nextAvailable最小),并列按deviceId升序
|
|
|
|
|
DeviceCandidate chosen = candidates.stream()
|
|
|
|
|
.sorted(Comparator
|
|
|
|
|
.comparing(DeviceCandidate::getNextAvailableTime)
|
|
|
|
|
.thenComparing(DeviceCandidate::getDeviceId, Comparator.reverseOrder()))
|
|
|
|
|
.findFirst()
|
|
|
|
|
.orElseThrow(() -> exception(SCHEDULE_PRODUCT_DEVICE_UNAVAILABLE, item.getProductId()));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 6) 按 数量/额定产能 计算天数
|
|
|
|
|
int days = (int) Math.ceil((double) item.getPlanNumber() / chosen.getCapacityValue());
|
|
|
|
|
if (days <= 0) {
|
|
|
|
|
days = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 7) 计算开始/结束时间(算法生成)
|
|
|
|
|
LocalDate startDate = chosen.getNextAvailableTime().toLocalDate();
|
|
|
|
|
LocalDateTime planStartTime = startDate.atStartOfDay();
|
|
|
|
|
LocalDateTime planEndTime = startDate.plusDays(days - 1).atTime(23, 59, 59);
|
|
|
|
|
|
|
|
|
|
// 7.1) 计算最晚开工时间(优先 orderDetailDeliveryDate,缺失则降级 deliveryDate)
|
|
|
|
|
LocalDate dueDate;
|
|
|
|
|
if (item.getOrderDetailDeliveryDate() != null) {
|
|
|
|
|
dueDate = item.getOrderDetailDeliveryDate().toLocalDate();
|
|
|
|
|
} else if (item.getDeliveryDate() != null) {
|
|
|
|
|
dueDate = item.getDeliveryDate().toLocalDate();; // 如果你的 deliveryDate 是 LocalDateTime,这里改成 toLocalDate()
|
|
|
|
|
} else {
|
|
|
|
|
throw exception(SCHEDULE_DELIVERY_DATE_EMPTY, item.getTaskDetailId());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 天数A = (截止日期 - 计划开始日期) - scheduleDays
|
|
|
|
|
long availableDays = ChronoUnit.DAYS.between(startDate, dueDate) + 1;
|
|
|
|
|
// 最晚可向后平移天数
|
|
|
|
|
long dayA = availableDays - days;
|
|
|
|
|
LocalDateTime latestStartTime = dayA <= 0
|
|
|
|
|
? planStartTime
|
|
|
|
|
: planStartTime.plusDays(dayA);
|
|
|
|
|
|
|
|
|
|
// 8) 更新该设备下次可开工时间(下一天00:00:00)
|
|
|
|
|
LocalDateTime nextAvailable = planEndTime.plusSeconds(1);
|
|
|
|
|
deviceNextAvailableTime.put(chosen.getDeviceId(), nextAvailable);
|
|
|
|
|
|
|
|
|
|
// 9) 组装返回(按设备分组)
|
|
|
|
|
TaskOneClickScheduleRespVO deviceResp = deviceResultMap.computeIfAbsent(chosen.getDeviceId(), k -> {
|
|
|
|
|
TaskOneClickScheduleRespVO vo = new TaskOneClickScheduleRespVO();
|
|
|
|
|
vo.setDeviceId(chosen.getDeviceId());
|
|
|
|
|
vo.setDeviceName(chosen.getDeviceName());
|
|
|
|
|
vo.setRatedCapacity(chosen.getCapacityValue());
|
|
|
|
|
vo.setPlans(new ArrayList<>());
|
|
|
|
|
return vo;
|
|
|
|
|
});
|
|
|
|
|
TaskDO taskDO = getTaskCached(item.getTaskId(), taskCache);
|
|
|
|
|
ErpProductDO productDO = getProductCached(item.getProductId(), productCache);
|
|
|
|
|
|
|
|
|
|
TaskOneClickScheduleRespVO.PlanRespItem p = new TaskOneClickScheduleRespVO.PlanRespItem();
|
|
|
|
|
p.setDeviceId(chosen.getDeviceId());
|
|
|
|
|
p.setProductId(item.getProductId());
|
|
|
|
|
p.setTaskId(item.getTaskId());
|
|
|
|
|
p.setTaskDetailId(item.getTaskDetailId());
|
|
|
|
|
p.setPlanNumber(item.getPlanNumber());
|
|
|
|
|
p.setScheduleDays(days);
|
|
|
|
|
// p.setPlanStartTime(planStartTime);
|
|
|
|
|
// p.setPlanEndTime(planEndTime);
|
|
|
|
|
// p.setLatestStartTime(latestStartTime);
|
|
|
|
|
p.setSourceType("CURRENT");
|
|
|
|
|
p.setTaskCode(taskDO == null ? null : taskDO.getCode());
|
|
|
|
|
p.setProductCode(productDO == null ? null : productDO.getBarCode());
|
|
|
|
|
p.setProductName(productDO == null ? null : productDO.getName());
|
|
|
|
|
p.setPlanStartTimeStr(planStartTime.format(DATETIME_FMT));
|
|
|
|
|
p.setPlanEndTimeStr(planEndTime.format(DATETIME_FMT));
|
|
|
|
|
p.setLatestStartTimeStr(latestStartTime.format(DATETIME_FMT));
|
|
|
|
|
|
|
|
|
|
LocalDate deliveryDate = null;
|
|
|
|
|
if (item.getOrderDetailDeliveryDate() != null) {
|
|
|
|
|
deliveryDate = item.getOrderDetailDeliveryDate().toLocalDate();
|
|
|
|
|
} else if (item.getDeliveryDate() != null) {
|
|
|
|
|
deliveryDate = item.getDeliveryDate().toLocalDate();
|
|
|
|
|
} else if (taskDO != null && taskDO.getDeliveryDate() != null) {
|
|
|
|
|
deliveryDate = taskDO.getDeliveryDate().toLocalDate();
|
|
|
|
|
}
|
|
|
|
|
p.setDeliveryDateStr(deliveryDate == null ? null : deliveryDate.format(DATE_FMT));
|
|
|
|
|
|
|
|
|
|
deviceResp.getPlans().add(p);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//查询所有设备,注释即返回符合设备
|
|
|
|
|
Set<Long> historyDeviceIds = queryDeviceIdsFromCurrentMonth();
|
|
|
|
|
for (Long deviceId : historyDeviceIds) {
|
|
|
|
|
deviceResultMap.computeIfAbsent(deviceId, k -> {
|
|
|
|
|
DeviceLedgerDO device = deviceLedgerMapper.selectById(deviceId);
|
|
|
|
|
TaskOneClickScheduleRespVO vo = new TaskOneClickScheduleRespVO();
|
|
|
|
|
vo.setDeviceId(deviceId);
|
|
|
|
|
vo.setDeviceName(device == null ? null : device.getDeviceName());
|
|
|
|
|
vo.setRatedCapacity(capacityType.getCapacity(device)); // device为空时注意判空
|
|
|
|
|
vo.setPlans(new ArrayList<>());
|
|
|
|
|
return vo;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//组装设备历史记录
|
|
|
|
|
for (TaskOneClickScheduleRespVO deviceResp : deviceResultMap.values()) {
|
|
|
|
|
List<PlanDO> histories = queryLatestPlanInCurrentMonth(deviceResp.getDeviceId());
|
|
|
|
|
if (CollUtil.isEmpty(histories)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<TaskOneClickScheduleRespVO.PlanRespItem> historyItems = new ArrayList<>();
|
|
|
|
|
for (PlanDO history : histories) {
|
|
|
|
|
TaskDO taskDO = getTaskCached(history.getTaskId(), taskCache);
|
|
|
|
|
ErpProductDO productDO = getProductCached(history.getProductId(), productCache);
|
|
|
|
|
|
|
|
|
|
TaskOneClickScheduleRespVO.PlanRespItem h = new TaskOneClickScheduleRespVO.PlanRespItem();
|
|
|
|
|
h.setDeviceId(deviceResp.getDeviceId());
|
|
|
|
|
h.setProductId(history.getProductId());
|
|
|
|
|
h.setTaskId(history.getTaskId());
|
|
|
|
|
h.setTaskDetailId(history.getTaskDetailId());
|
|
|
|
|
h.setPlanNumber(history.getPlanNumber());
|
|
|
|
|
// h.setPlanStartTime(history.getPlanStartTime());
|
|
|
|
|
// h.setPlanEndTime(history.getPlanEndTime());
|
|
|
|
|
// h.setLatestStartTime(history.getLatestStartTime());
|
|
|
|
|
h.setSourceType("HISTORY");
|
|
|
|
|
h.setTaskCode(taskDO == null ? null : taskDO.getCode());
|
|
|
|
|
h.setProductCode(productDO == null ? null : productDO.getBarCode());
|
|
|
|
|
h.setProductName(productDO == null ? null : productDO.getName());
|
|
|
|
|
h.setPlanStartTimeStr(history.getPlanStartTime() == null ? null : history.getPlanStartTime().format(DATETIME_FMT));
|
|
|
|
|
h.setPlanEndTimeStr(history.getPlanEndTime() == null ? null : history.getPlanEndTime().format(DATETIME_FMT));
|
|
|
|
|
h.setLatestStartTimeStr(history.getLatestStartTime() == null ? null : history.getLatestStartTime().format(DATETIME_FMT));
|
|
|
|
|
|
|
|
|
|
LocalDate deliveryDate = null;
|
|
|
|
|
if (history.getDeliveryDate() != null) {
|
|
|
|
|
deliveryDate = history.getDeliveryDate().toLocalDate();
|
|
|
|
|
} else if (taskDO != null && taskDO.getDeliveryDate() != null) {
|
|
|
|
|
deliveryDate = taskDO.getDeliveryDate().toLocalDate();
|
|
|
|
|
}
|
|
|
|
|
h.setDeliveryDateStr(deliveryDate == null ? null : deliveryDate.format(DATE_FMT));
|
|
|
|
|
|
|
|
|
|
historyItems.add(h);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 放在最前,前端甘特图先看到历史衔接(按查询排序整体前置)
|
|
|
|
|
deviceResp.getPlans().addAll(0, historyItems);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return new ArrayList<>(deviceResultMap.values());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 初始化设备可开工时间:
|
|
|
|
|
* - 有历史启用计划:取最后结束时间+1秒
|
|
|
|
|
* - 没有历史计划:取当前时间(并对齐到当天开始)
|
|
|
|
|
*/
|
|
|
|
|
private LocalDateTime queryDeviceNextAvailableTime(Long deviceId) {
|
|
|
|
|
PlanDO lastPlan = planMapper.selectOne(new LambdaQueryWrapper<PlanDO>()
|
|
|
|
|
.eq(PlanDO::getDeviceId, deviceId)
|
|
|
|
|
.eq(PlanDO::getIsEnable, true)
|
|
|
|
|
.orderByDesc(PlanDO::getPlanEndTime)
|
|
|
|
|
.last("limit 1"));
|
|
|
|
|
|
|
|
|
|
LocalDate today = LocalDate.now();
|
|
|
|
|
LocalDate historyNextDate;
|
|
|
|
|
if (lastPlan == null || lastPlan.getPlanEndTime() == null) {
|
|
|
|
|
historyNextDate = today;
|
|
|
|
|
} else {
|
|
|
|
|
historyNextDate = lastPlan.getPlanEndTime().toLocalDate().plusDays(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 取 max(今天, 历史下一天)
|
|
|
|
|
LocalDate nextDate = historyNextDate.isBefore(today) ? today : historyNextDate;
|
|
|
|
|
return nextDate.atStartOfDay();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Set<Long> queryDeviceIdsFromCurrentMonth() {
|
|
|
|
|
LocalDateTime monthStart = LocalDate.now().withDayOfMonth(1).atStartOfDay();
|
|
|
|
|
List<PlanDO> plans = planMapper.selectList(new LambdaQueryWrapper<PlanDO>()
|
|
|
|
|
.eq(PlanDO::getIsEnable, true)
|
|
|
|
|
.isNotNull(PlanDO::getDeviceId)
|
|
|
|
|
.ge(PlanDO::getPlanEndTime, monthStart)
|
|
|
|
|
.select(PlanDO::getDeviceId));
|
|
|
|
|
|
|
|
|
|
return plans.stream()
|
|
|
|
|
.map(PlanDO::getDeviceId)
|
|
|
|
|
.collect(Collectors.toSet());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private List<PlanDO> queryLatestPlanInCurrentMonth(Long deviceId) {
|
|
|
|
|
LocalDate now = LocalDate.now();
|
|
|
|
|
LocalDateTime monthStart = now.withDayOfMonth(1).atStartOfDay();
|
|
|
|
|
|
|
|
|
|
return planMapper.selectList(new LambdaQueryWrapper<PlanDO>()
|
|
|
|
|
.eq(PlanDO::getDeviceId, deviceId)
|
|
|
|
|
.eq(PlanDO::getIsEnable, true)
|
|
|
|
|
.ge(PlanDO::getPlanEndTime, monthStart) // 本月及以后(未结束)
|
|
|
|
|
.orderByAsc(PlanDO::getPlanStartTime)
|
|
|
|
|
.orderByAsc(PlanDO::getPlanEndTime));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private TaskDO getTaskCached(Long taskId, Map<Long, TaskDO> cache) {
|
|
|
|
|
if (taskId == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return cache.computeIfAbsent(taskId, taskMapper::selectById);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ErpProductDO getProductCached(Long productId, Map<Long, ErpProductDO> cache) {
|
|
|
|
|
if (productId == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return cache.computeIfAbsent(productId, erpProductMapper::selectById);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|