|
|
|
|
@ -1,7 +1,6 @@
|
|
|
|
|
package cn.iocoder.yudao.module.mes.service.task;
|
|
|
|
|
|
|
|
|
|
import cn.hutool.core.collection.CollUtil;
|
|
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
|
|
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
|
|
|
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
|
|
|
|
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
|
|
|
|
@ -15,16 +14,18 @@ import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductMapper;
|
|
|
|
|
import cn.iocoder.yudao.module.erp.dal.mysql.productdevicerel.ProductDeviceRelMapper;
|
|
|
|
|
import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
|
|
|
|
|
import cn.iocoder.yudao.module.erp.service.product.ErpProductUnitService;
|
|
|
|
|
import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO;
|
|
|
|
|
import cn.iocoder.yudao.module.iot.dal.mysql.device.DeviceMapper;
|
|
|
|
|
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.calholiday.CalHolidayDO;
|
|
|
|
|
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.baogongrecord.BaogongRecordMapper;
|
|
|
|
|
import cn.iocoder.yudao.module.mes.dal.mysql.calholiday.CalHolidayMapper;
|
|
|
|
|
import cn.iocoder.yudao.module.mes.dal.mysql.deviceledger.DeviceLedgerMapper;
|
|
|
|
|
import cn.iocoder.yudao.module.mes.dal.mysql.plan.PlanMapper;
|
|
|
|
|
@ -33,6 +34,7 @@ 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.enums.BaogongTrendTypeEnum;
|
|
|
|
|
import cn.iocoder.yudao.module.iot.service.device.TDengineService;
|
|
|
|
|
import cn.iocoder.yudao.module.mes.service.deviceledger.DeviceLedgerService;
|
|
|
|
|
import cn.iocoder.yudao.module.mes.strategy.task.ScheduleSortStrategyFactory;
|
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
|
|
@ -84,6 +86,10 @@ public class TaskServiceImpl implements TaskService {
|
|
|
|
|
@Resource
|
|
|
|
|
private DeviceLedgerMapper deviceLedgerMapper;
|
|
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
@Lazy
|
|
|
|
|
private DeviceMapper deviceMapper;
|
|
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
private ProductDeviceRelMapper productDeviceRelMapper;
|
|
|
|
|
|
|
|
|
|
@ -93,6 +99,13 @@ public class TaskServiceImpl implements TaskService {
|
|
|
|
|
@Resource
|
|
|
|
|
private CalHolidayMapper calHolidayMapper;
|
|
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
private BaogongRecordMapper baogongRecordMapper;
|
|
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
private TDengineService tdengineService;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
private AutoCodeUtil autoCodeUtil;
|
|
|
|
|
@Resource
|
|
|
|
|
@ -450,7 +463,6 @@ public class TaskServiceImpl implements TaskService {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
|
|
public List<TaskOneClickScheduleRespVO> oneClickSchedule(TaskOneClickScheduleReqVO reqVO) {
|
|
|
|
|
if (reqVO == null || CollUtil.isEmpty(reqVO.getCreateReqVO())) {
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
@ -515,6 +527,8 @@ public class TaskServiceImpl implements TaskService {
|
|
|
|
|
throw exception(UNSUPPORTED_CAPACITY_TYPE, reqVO.getCapacityType());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CapacityContext capacityContext = buildCapacityContext(sortedPlans, skipHoliday, capacityType);
|
|
|
|
|
|
|
|
|
|
DateTimeFormatter DATETIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
|
|
DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
|
|
|
|
|
|
|
|
|
@ -538,7 +552,7 @@ public class TaskServiceImpl implements TaskService {
|
|
|
|
|
List<DeviceCandidate> candidates = new ArrayList<>();
|
|
|
|
|
for (ProductRelationRespVO rel : deviceRels) {
|
|
|
|
|
DeviceLedgerDO device = deviceLedgerMapper.selectById(rel.getId());
|
|
|
|
|
Integer dailyCapacity = capacityType.getCapacity(device); // 每日产能
|
|
|
|
|
Integer dailyCapacity = resolveCapacityValue(capacityType, device, item.getProductId(), rel.getId(), capacityContext);
|
|
|
|
|
if (dailyCapacity == null || dailyCapacity <= 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
@ -554,7 +568,8 @@ public class TaskServiceImpl implements TaskService {
|
|
|
|
|
c.setDeviceName(rel.getName());
|
|
|
|
|
c.setCapacityValue(dailyCapacity);
|
|
|
|
|
c.setNextAvailableTime(nextAvailable);
|
|
|
|
|
candidates.add(c); }
|
|
|
|
|
candidates.add(c);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (CollUtil.isEmpty(candidates)) {
|
|
|
|
|
throw exception(SCHEDULE_PRODUCT_DEVICE_UNAVAILABLE, item.getProductId());
|
|
|
|
|
@ -592,6 +607,7 @@ public class TaskServiceImpl implements TaskService {
|
|
|
|
|
vo.setDeviceId(chosen.getDeviceId());
|
|
|
|
|
vo.setDeviceName(chosen.getDeviceName());
|
|
|
|
|
vo.setRatedCapacity(chosen.getCapacityValue());
|
|
|
|
|
vo.setCapacityType(reqVO.getCapacityType());
|
|
|
|
|
vo.setPlans(new ArrayList<>());
|
|
|
|
|
return vo;
|
|
|
|
|
});
|
|
|
|
|
@ -633,7 +649,8 @@ public class TaskServiceImpl implements TaskService {
|
|
|
|
|
TaskOneClickScheduleRespVO vo = new TaskOneClickScheduleRespVO();
|
|
|
|
|
vo.setDeviceId(deviceId);
|
|
|
|
|
vo.setDeviceName(device == null ? null : device.getDeviceName());
|
|
|
|
|
vo.setRatedCapacity(capacityType.getCapacity(device)); // device为空时注意判空
|
|
|
|
|
// vo.setRatedCapacity(resolveCapacityValue(capacityType, device, null, deviceId, capacityContext));
|
|
|
|
|
// vo.setCapacityType(reqVO.getCapacityType());
|
|
|
|
|
vo.setPlans(new ArrayList<>());
|
|
|
|
|
return vo;
|
|
|
|
|
});
|
|
|
|
|
@ -854,6 +871,157 @@ public class TaskServiceImpl implements TaskService {
|
|
|
|
|
return d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Integer resolveCapacityValue(CapacityTypeEnum capacityType, DeviceLedgerDO device,
|
|
|
|
|
Long productId, Long deviceId, CapacityContext capacityContext) {
|
|
|
|
|
if (capacityType == CapacityTypeEnum.RATED) {
|
|
|
|
|
return device == null ? null : capacityType.getCapacity(device);
|
|
|
|
|
}
|
|
|
|
|
if (capacityContext == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
if (capacityType == CapacityTypeEnum.DAILY_AVERAGE) {
|
|
|
|
|
if (productId == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
Integer value = capacityContext.getProductDailyAverageCapacity(productId);
|
|
|
|
|
if (value == null || value <= 0) {
|
|
|
|
|
ErpProductDO product = capacityContext.getProductCache().get(productId);
|
|
|
|
|
throw exception(SCHEDULE_PRODUCT_DAILY_AVERAGE_ZERO, productId,
|
|
|
|
|
product == null ? null : product.getBarCode(),
|
|
|
|
|
product == null ? null : product.getName());
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
if (capacityType == CapacityTypeEnum.DATA_COLLECTION) {
|
|
|
|
|
if (deviceId == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
Integer value = capacityContext.getDeviceCollectionAverageCapacity(deviceId);
|
|
|
|
|
if (value == null || value <= 0) {
|
|
|
|
|
DeviceDO deviceInfo = capacityContext.getDeviceCache().get(deviceId);
|
|
|
|
|
throw exception(SCHEDULE_DEVICE_COLLECTION_CAPACITY_ZERO, deviceId,
|
|
|
|
|
deviceInfo == null ? null : deviceInfo.getDeviceName());
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
return device == null ? null : capacityType.getCapacity(device);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private CapacityContext buildCapacityContext(List<PlanSaveReqVO> sortedPlans, boolean skipHoliday,
|
|
|
|
|
CapacityTypeEnum capacityType) {
|
|
|
|
|
CapacityContext context = new CapacityContext();
|
|
|
|
|
if (CollUtil.isEmpty(sortedPlans)) {
|
|
|
|
|
return context;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LocalDate endDate = LocalDate.now();
|
|
|
|
|
LocalDate startDate = endDate.minusMonths(6).plusDays(1);
|
|
|
|
|
List<LocalDate> statDays = listStatDays(startDate, endDate, skipHoliday);
|
|
|
|
|
context.setStatDays(statDays);
|
|
|
|
|
context.setStatDayCount(statDays.size());
|
|
|
|
|
if (CollUtil.isEmpty(statDays)) {
|
|
|
|
|
return context;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LocalDateTime beginTime = statDays.get(0).atStartOfDay();
|
|
|
|
|
LocalDateTime endTime = statDays.get(statDays.size() - 1).plusDays(1).atStartOfDay();
|
|
|
|
|
|
|
|
|
|
if (capacityType == CapacityTypeEnum.DAILY_AVERAGE) {
|
|
|
|
|
Set<Long> productIds = sortedPlans.stream().map(PlanSaveReqVO::getProductId)
|
|
|
|
|
.filter(Objects::nonNull).collect(Collectors.toSet());
|
|
|
|
|
if (CollUtil.isNotEmpty(productIds)) {
|
|
|
|
|
context.setProductCache(productIds.stream().collect(Collectors.toMap(
|
|
|
|
|
id -> id,
|
|
|
|
|
erpProductMapper::selectById,
|
|
|
|
|
(a, b) -> a
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
context.setProductDailyAverageMap(queryProductDailyAverageMap(productIds, statDays, beginTime, endTime));
|
|
|
|
|
} else if (capacityType == CapacityTypeEnum.DATA_COLLECTION) {
|
|
|
|
|
|
|
|
|
|
Set<Long> deviceIds = deviceMapper.selectList(new LambdaQueryWrapper<DeviceDO>()
|
|
|
|
|
.select(DeviceDO::getId))
|
|
|
|
|
.stream()
|
|
|
|
|
.map(DeviceDO::getId)
|
|
|
|
|
.filter(Objects::nonNull)
|
|
|
|
|
.collect(Collectors.toSet());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (CollUtil.isNotEmpty(deviceIds)) {
|
|
|
|
|
context.setDeviceCache(deviceMapper.selectBatchIds(deviceIds).stream()
|
|
|
|
|
.filter(Objects::nonNull)
|
|
|
|
|
.collect(Collectors.toMap(
|
|
|
|
|
DeviceDO::getId,
|
|
|
|
|
device -> device,
|
|
|
|
|
(a, b) -> a
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
context.setDeviceCollectionAverageMap(queryDeviceCollectionAverageMap(deviceIds, statDays, beginTime, endTime));
|
|
|
|
|
}
|
|
|
|
|
return context;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Map<Long, Integer> queryProductDailyAverageMap(Set<Long> productIds, List<LocalDate> statDays,
|
|
|
|
|
LocalDateTime beginTime, LocalDateTime endTime) {
|
|
|
|
|
Map<Long, Integer> result = new HashMap<>();
|
|
|
|
|
if (CollUtil.isEmpty(productIds) || CollUtil.isEmpty(statDays)) {
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
Set<LocalDate> validDays = new HashSet<>(statDays);
|
|
|
|
|
List<Map<String, Object>> rows = baogongRecordMapper.selectProductDailyReportTotals(productIds, beginTime, endTime);
|
|
|
|
|
Map<Long, BigDecimal> productTotalMap = new HashMap<>();
|
|
|
|
|
for (Map<String, Object> row : rows) {
|
|
|
|
|
Long productId = row.get("productId") == null ? null : Long.valueOf(String.valueOf(row.get("productId")));
|
|
|
|
|
LocalDate day = row.get("day") == null ? null : LocalDate.parse(String.valueOf(row.get("day")));
|
|
|
|
|
if (productId == null || day == null || !validDays.contains(day)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
BigDecimal totalNum = new BigDecimal(String.valueOf(row.get("totalNum")));
|
|
|
|
|
productTotalMap.merge(productId, totalNum, BigDecimal::add);
|
|
|
|
|
}
|
|
|
|
|
BigDecimal divisor = BigDecimal.valueOf(statDays.size());
|
|
|
|
|
for (Long productId : productIds) {
|
|
|
|
|
BigDecimal total = productTotalMap.getOrDefault(productId, BigDecimal.ZERO);
|
|
|
|
|
result.put(productId, total.divide(divisor, 0, RoundingMode.HALF_UP).intValue());
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Map<Long, Integer> queryDeviceCollectionAverageMap(Set<Long> deviceIds, List<LocalDate> statDays,
|
|
|
|
|
LocalDateTime beginTime, LocalDateTime endTime) {
|
|
|
|
|
Map<Long, Integer> result = new HashMap<>();
|
|
|
|
|
if (CollUtil.isEmpty(deviceIds) || CollUtil.isEmpty(statDays)) {
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
Set<LocalDate> validDays = new HashSet<>(statDays);
|
|
|
|
|
Map<Long, Map<LocalDate, Double>> rawMap = tdengineService.queryDailyLatestCapacityValues(deviceIds, beginTime, endTime);
|
|
|
|
|
BigDecimal divisor = BigDecimal.valueOf(statDays.size());
|
|
|
|
|
for (Long deviceId : deviceIds) {
|
|
|
|
|
Map<LocalDate, Double> dayMap = rawMap.getOrDefault(deviceId, Collections.emptyMap());
|
|
|
|
|
BigDecimal total = BigDecimal.ZERO;
|
|
|
|
|
for (Map.Entry<LocalDate, Double> entry : dayMap.entrySet()) {
|
|
|
|
|
if (entry.getKey() == null || entry.getValue() == null || !validDays.contains(entry.getKey())) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
total = total.add(BigDecimal.valueOf(entry.getValue()));
|
|
|
|
|
}
|
|
|
|
|
result.put(deviceId, total.divide(divisor, 0, RoundingMode.HALF_UP).intValue());
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<LocalDate> listStatDays(LocalDate startDate, LocalDate endDate, boolean skipHoliday) {
|
|
|
|
|
List<LocalDate> days = new ArrayList<>();
|
|
|
|
|
LocalDate cursor = startDate;
|
|
|
|
|
while (!cursor.isAfter(endDate)) {
|
|
|
|
|
if (!skipHoliday || isWorkingDay(cursor)) {
|
|
|
|
|
days.add(cursor);
|
|
|
|
|
}
|
|
|
|
|
cursor = cursor.plusDays(1);
|
|
|
|
|
}
|
|
|
|
|
return days;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Set<Long> queryDeviceIdsFromCurrentMonth() {
|
|
|
|
|
LocalDateTime monthStart = LocalDate.now().withDayOfMonth(1).atStartOfDay();
|
|
|
|
|
List<PlanDO> plans = planMapper.selectList(new LambdaQueryWrapper<PlanDO>()
|
|
|
|
|
|