diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java index 1680546d55..e10c3fbf45 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java @@ -20,9 +20,11 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.text.SimpleDateFormat; +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.stream.Collectors; @Service @Slf4j @@ -30,12 +32,13 @@ public class TDengineService { @Resource private JdbcTemplate jdbcTemplate; - @DS("tdengine") - public void testConnection() { - String testSQL = "SELECT SERVER_STATUS()"; - jdbcTemplate.queryForObject(testSQL, Integer.class); - System.out.println("TDengine连接正常"); - } +// @DS("tdengine") +// public void testConnection() { +// String testSQL = "SELECT SERVER_STATUS()"; +// jdbcTemplate.queryForObject(testSQL, Integer.class); +// log.info("TDengine连接正常"); +// +// } @DS("tdengine") public void initDatabaseAndTable(Long id) { @@ -61,10 +64,9 @@ public class TDengineService { tableName, id); jdbcTemplate.execute(createTableSql); - System.out.println("TDengine表创建成功: " + tableName); - } - + log.info("TDengine表创建成功 {}", tableName); + } /** @@ -93,41 +95,9 @@ public class TDengineService { String timestampStr = sdf.format(ts); result.put("timestamp", timestampStr); + // 读取 query_data(二进制字段) byte[] blob = rs.getBytes("query_data"); - if (blob != null) { - String jsonStr = new String(blob, StandardCharsets.UTF_8); - - try { - // 1. 先去除外层的双引号(如果存在) - String trimmed = jsonStr.trim(); - if (trimmed.startsWith("\"") && trimmed.endsWith("\"")) { - trimmed = trimmed.substring(1, trimmed.length() - 1); - } - - // 2. 检查是否是十六进制字符串 - if (isHexString(trimmed)) { - // 3. 十六进制解码 - String decodedJson = hexToString(trimmed); - result.put("queryData", decodedJson); - } else { - // 如果不是十六进制,尝试直接解析 - ObjectMapper objectMapper = new ObjectMapper(); - List> queryData = objectMapper.readValue( - trimmed, - new TypeReference>>() {} - ); - result.put("queryData", queryData); - } - - } catch (Exception e) { - System.err.println("解析JSON失败: " + e.getMessage()); - result.put("queryData", new ArrayList<>()); - result.put("rawData", jsonStr); - } - - } else { - result.put("queryData", new ArrayList<>()); - } + result.put("queryData", decodeQueryData(blob)); result.put("deviceId", id); @@ -136,8 +106,7 @@ public class TDengineService { } }); } catch (Exception e) { - System.out.println("查询设备" + id + "的最新数据时发生异常: " + e.getMessage()); - // 可以考虑记录更详细的日志,或抛出更明确的业务异常 + log.error("查询设备id为 {} 的最新数据时发生异常", id, e); return null; } } @@ -177,7 +146,7 @@ public class TDengineService { ps.setBytes(2, blobData); }) > 0; } catch (Exception e) { - System.out.println("向设备" + id + "插入数据时发生异常: " + e.getMessage()); + log.error("向设备id为 {} 插入数据时发生异常", id, e); return false; } } @@ -200,42 +169,9 @@ public class TDengineService { result.put("timestamp", rs.getTimestamp("ts")); result.put("deviceId", id); + // 读取 query_data(二进制字段) byte[] blob = rs.getBytes("query_data"); - if (blob != null) { - String jsonStr = new String(blob, StandardCharsets.UTF_8); - - try { - // 1. 先去除外层的双引号(如果存在) - String trimmed = jsonStr.trim(); - if (trimmed.startsWith("\"") && trimmed.endsWith("\"")) { - trimmed = trimmed.substring(1, trimmed.length() - 1); - } - - // 2. 检查是否是十六进制字符串 - if (isHexString(trimmed)) { - // 3. 十六进制解码 - String decodedJson = hexToString(trimmed); - - result.put("queryData", decodedJson); - } else { - // 如果不是十六进制,尝试直接解析 - ObjectMapper objectMapper = new ObjectMapper(); - List> queryData = objectMapper.readValue( - trimmed, - new TypeReference>>() {} - ); - result.put("queryData", queryData); - } - - } catch (Exception e) { - System.err.println("解析JSON失败: " + e.getMessage()); - result.put("queryData", new ArrayList<>()); - result.put("rawData", jsonStr); - } - - } else { - result.put("queryData", new ArrayList<>()); - } + result.put("queryData", decodeQueryData(blob)); return result; } @@ -243,8 +179,7 @@ public class TDengineService { } catch (EmptyResultDataAccessException e) { return Collections.singletonList(createEmptyResult(id)); } catch (Exception e) { - System.err.println("查询设备" + id + "的最新数据时发生异常: " + e.getMessage()); - e.printStackTrace(); + log.error("查询设备id为 {} 的最新数据时发生异常", id, e); return Collections.singletonList(createEmptyResult(id)); } } @@ -267,37 +202,11 @@ public class TDengineService { byte[] bytes = Hex.decodeHex(hex); return new String(bytes, StandardCharsets.UTF_8); } catch (DecoderException e) { - throw new RuntimeException("十六进制解码失败: " + e.getMessage(), e); + log.error("十六进制解码失败: {}", e); + return ""; } } - private List> parseJsonData(byte[] blob) { - if (blob == null || blob.length == 0) { - return new ArrayList<>(); - } - try { - String jsonStr = new String(blob, StandardCharsets.UTF_8); - - // 检查是否是有效的JSON - if (jsonStr.trim().startsWith("[") && jsonStr.trim().endsWith("]")) { - ObjectMapper objectMapper = new ObjectMapper(); - return objectMapper.readValue(jsonStr, - new TypeReference>>() {}); - } else { - // 可能是字符串化的JSON,尝试去除引号 - jsonStr = jsonStr.trim(); - if (jsonStr.startsWith("\"") && jsonStr.endsWith("\"")) { - jsonStr = jsonStr.substring(1, jsonStr.length() - 1); - return new ObjectMapper().readValue(jsonStr, - new TypeReference>>() {}); - } - } - } catch (Exception e) { - System.err.println("解析JSON数据失败: " + e.getMessage()); - } - - return new ArrayList<>(); - } private Map createEmptyResult(Long deviceId) { Map result = new HashMap<>(); @@ -343,42 +252,9 @@ public class TDengineService { result.put("timestamp", rs.getTimestamp("ts")); result.put("deviceId", id); + // 读取 query_data(二进制字段) byte[] blob = rs.getBytes("query_data"); - if (blob != null) { - String jsonStr = new String(blob, StandardCharsets.UTF_8); - - try { - // 1. 先去除外层的双引号(如果存在) - String trimmed = jsonStr.trim(); - if (trimmed.startsWith("\"") && trimmed.endsWith("\"")) { - trimmed = trimmed.substring(1, trimmed.length() - 1); - } - - // 2. 检查是否是十六进制字符串 - if (isHexString(trimmed)) { - // 3. 十六进制解码 - String decodedJson = hexToString(trimmed); - - result.put("queryData", decodedJson); - } else { - // 如果不是十六进制,尝试直接解析 - ObjectMapper objectMapper = new ObjectMapper(); - List> queryData = objectMapper.readValue( - trimmed, - new TypeReference>>() {} - ); - result.put("queryData", queryData); - } - - } catch (Exception e) { - System.err.println("解析JSON失败: " + e.getMessage()); - result.put("queryData", new ArrayList<>()); - result.put("rawData", jsonStr); - } - - } else { - result.put("queryData", new ArrayList<>()); - } + result.put("queryData", decodeQueryData(blob)); return result; } @@ -451,31 +327,12 @@ public class TDengineService { // 读取 query_data(二进制字段) byte[] blob = rs.getBytes("query_data"); - if (blob != null) { - // 转为字符串 - String json = new String(blob, StandardCharsets.UTF_8).trim(); - - // 去除 TDengine 中可能存在的外层双引号 - if (json.startsWith("\"") && json.endsWith("\"")) { - json = json.substring(1, json.length() - 1); - } - - // 如果是十六进制字符串,先解码 - if (isHexString(json)) { - json = hexToString(json); - } - - // 统一约定:queryData 始终返回 String,由上层决定是否解析 JSON - result.put("queryData", json); - } else { - // 没有数据时返回空数组字符串 - result.put("queryData", "[]"); - } + result.put("queryData", decodeQueryData(blob)); return result; }); } catch (Exception e) { - log.error("TDengine 查询失败,deviceId={} ,td表不存在", deviceId); + log.error("TDengine 查询失败,deviceId={} ,td表不存在", deviceId,e); return null; } } @@ -557,26 +414,7 @@ public class TDengineService { // 读取 query_data(二进制字段) byte[] blob = rs.getBytes("query_data"); - if (blob != null) { - // 转为字符串 - String json = new String(blob, StandardCharsets.UTF_8).trim(); - - // 去除 TDengine 中可能存在的外层双引号 - if (json.startsWith("\"") && json.endsWith("\"")) { - json = json.substring(1, json.length() - 1); - } - - // 如果是十六进制字符串,先解码 - if (isHexString(json)) { - json = hexToString(json); - } - - // 统一约定:queryData 始终返回 String,由上层决定是否解析 JSON - map.put("queryData", json); - } else { - // 没有数据时返回空数组字符串 - map.put("queryData", "[]"); - } + map.put("queryData", decodeQueryData(blob)); return map; }); @@ -607,29 +445,10 @@ public class TDengineService { map.put("timestamp", rs.getTimestamp("ts")); map.put("deviceId", deviceId); - // 读取 query_data(二进制字段) byte[] blob = rs.getBytes("query_data"); - if (blob != null) { - // 转为字符串 - String json = new String(blob, StandardCharsets.UTF_8).trim(); + map.put("queryData", decodeQueryData(blob)); - // 去除 TDengine 中可能存在的外层双引号 - if (json.startsWith("\"") && json.endsWith("\"")) { - json = json.substring(1, json.length() - 1); - } - - // 如果是十六进制字符串,先解码 - if (isHexString(json)) { - json = hexToString(json); - } - - // 统一约定:queryData 始终返回 String,由上层决定是否解析 JSON - map.put("queryData", json); - } else { - // 没有数据时返回空数组字符串 - map.put("queryData", "[]"); - } return map; }); @@ -659,32 +478,137 @@ public class TDengineService { // 读取 query_data(二进制字段) byte[] blob = rs.getBytes("query_data"); - if (blob != null) { - // 转为字符串 - String json = new String(blob, StandardCharsets.UTF_8).trim(); + map.put("queryData", decodeQueryData(blob)); - // 去除 TDengine 中可能存在的外层双引号 - if (json.startsWith("\"") && json.endsWith("\"")) { - json = json.substring(1, json.length() - 1); - } - // 如果是十六进制字符串,先解码 - if (isHexString(json)) { - json = hexToString(json); - } + return map; + }); + } - // 统一约定:queryData 始终返回 String,由上层决定是否解析 JSON - map.put("queryData", json); - } else { - // 没有数据时返回空数组字符串 - map.put("queryData", "[]"); + + + @DS("tdengine") + public List> queryLastDataByHourBatch( + Set deviceIds, + LocalDateTime startTime, + LocalDateTime endTime) { + + List> result = new ArrayList<>(); + DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + for (Long deviceId : deviceIds) { + String sql = + "SELECT ts, query_data FROM besure.d_" + deviceId + + " WHERE ts >= '" + startTime.format(fmt) + "'" + + " AND ts <= '" + endTime.format(fmt) + "'" + + " ORDER BY ts ASC"; + + jdbcTemplate.query(sql, rs -> { + Map map = new HashMap<>(); + map.put("deviceId", deviceId); + map.put("timestamp", rs.getTimestamp("ts").toLocalDateTime()); + map.put("queryData", decodeQueryData(rs.getBytes("query_data"))); + result.add(map); + }); + } + return result; + } + + + @DS("tdengine") + public List> queryLastDataByDaySafe( + Set deviceIds, + LocalDate startDate, + int days + ) { + List> result = new ArrayList<>(); + + DateTimeFormatter formatter = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + for (int i = 0; i < days; i++) { + + LocalDate day = startDate.plusDays(i); + LocalDateTime dayStart = day.atStartOfDay(); + LocalDateTime dayEnd = day.plusDays(1).atStartOfDay(); + + for (Long deviceId : deviceIds) { + + String tableName = "besure.d_" + deviceId; + + String sql = + "SELECT ts, query_data FROM " + tableName + + " WHERE ts >= '" + dayStart.format(formatter) + "'" + + " AND ts < '" + dayEnd.format(formatter) + "'" + + " ORDER BY ts DESC LIMIT 1"; + + try { + List> rows = jdbcTemplate.query( + sql, + (rs, rowNum) -> { + Map map = new HashMap<>(); + map.put("deviceId", deviceId); + map.put("day", day.toString()); + + Timestamp ts = rs.getTimestamp("ts"); + map.put("timestamp", + ts != null ? ts.toLocalDateTime() : null); + + byte[] blob = rs.getBytes("query_data"); + map.put("queryData", decodeQueryData(blob)); + + return map; + } + ); + + if (!rows.isEmpty()) { + result.add(rows.get(0)); + } + + } catch (Exception e) { + // 表不存在 / 查询失败 → 直接跳过 + log.warn("Skip TDengine table: {}, reason: {}", + tableName, e.getMessage()); + } } + } - return map; - }); + return result; } + + + + + + + + + + + + + + private String decodeQueryData(byte[] blob) { + if (blob == null || blob.length == 0) return "[]"; + + String json = new String(blob, StandardCharsets.UTF_8).trim(); + + // 去掉外层引号 + if (json.startsWith("\"") && json.endsWith("\"")) { + json = json.substring(1, json.length() - 1); + } + + // 如果是十六进制字符串,解码 + if (isHexString(json)) { + json = hexToString(json); + } + + return json; + } + + } \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/EnergyDeviceController.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/EnergyDeviceController.java index 1ef1e1c9eb..bc16c8d9e1 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/EnergyDeviceController.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/EnergyDeviceController.java @@ -166,6 +166,8 @@ public class EnergyDeviceController { @GetMapping("/lastEnergyStatistics") + @Operation(summary = "获得近七小时能耗记录") + @PreAuthorize("@ss.hasPermission('mes:energy-device:query')") public CommonResult> lastEnergyStatistics(@RequestParam("deviceTypeId") Long deviceTypeId, @RequestParam("orgId") Long orgId) { @@ -174,4 +176,14 @@ public class EnergyDeviceController { return success(hourEnergyValueVOS); } + @GetMapping("/latestSevenDaysStatistics") + @Operation(summary = "获得近七天能耗记录") + @PreAuthorize("@ss.hasPermission('mes:energy-device:query')") + public CommonResult> latestSevenDaysStatistics( + @RequestParam Long deviceTypeId, + @RequestParam Long orgId) { + return CommonResult.success(energyDeviceService.latestSevenDaysStatistics(deviceTypeId, orgId)); + } + + } \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/DayEnergyValueVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/DayEnergyValueVO.java new file mode 100644 index 0000000000..d77f521f89 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/DayEnergyValueVO.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class DayEnergyValueVO { + + @Schema(description = "日期(yyyy-MM-dd)") + private String Day; + + @Schema(description = "按规则计算后的值") + private String value; +} diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/HourEnergyValueVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/HourEnergyValueVO.java index c7c0ae429a..5888aaafb6 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/HourEnergyValueVO.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/HourEnergyValueVO.java @@ -7,9 +7,9 @@ import lombok.Data; @Data public class HourEnergyValueVO { - /** 小时(yyyy-MM-dd HH) */ + @Schema(description = "小时(yyyy-MM-dd HH)") private String hour; - /** 按规则计算后的值 */ + @Schema(description = "按规则计算后的值") private String value; } diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/PlanController.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/PlanController.java index 240d9671af..0e4d032676 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/PlanController.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/PlanController.java @@ -10,15 +10,9 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; import cn.iocoder.yudao.module.iot.framework.mqtt.utils.DateUtils; -import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.DeviceTypePieOptionsVO; -import cn.iocoder.yudao.module.mes.controller.admin.itemrequisition.vo.ItemRequisitionRespVO; import cn.iocoder.yudao.module.mes.controller.admin.itemrequisition.vo.ItemRequisitionSaveReqVO; import cn.iocoder.yudao.module.mes.controller.admin.plan.vo.*; -import cn.iocoder.yudao.module.mes.controller.admin.zjproductrecord.vo.ZjProductRecordRespVO; import cn.iocoder.yudao.module.mes.controller.admin.zjproductrecord.vo.ZjProductRecordSaveReqVO; -import cn.iocoder.yudao.module.mes.dal.dataobject.deviceledger.DeviceLedgerDO; -import cn.iocoder.yudao.module.mes.dal.dataobject.dvrepair.DvRepairDO; -import cn.iocoder.yudao.module.mes.dal.dataobject.itemrequisition.ItemRequisitionDO; import cn.iocoder.yudao.module.mes.dal.dataobject.organization.OrganizationDO; import cn.iocoder.yudao.module.mes.dal.dataobject.plan.PlanDO; import cn.iocoder.yudao.module.mes.dal.dataobject.zjproduct.ZjProductDO; @@ -30,12 +24,10 @@ import cn.iocoder.yudao.module.mes.service.organization.OrganizationService; import cn.iocoder.yudao.module.mes.service.plan.PlanService; import cn.iocoder.yudao.module.mes.service.zjproduct.ZjProductService; import cn.iocoder.yudao.module.mes.service.zjproductrecord.ZjProductRecordService; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; -import org.apache.commons.lang3.StringUtils; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -45,9 +37,9 @@ import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import java.io.IOException; import java.math.BigDecimal; -import java.time.DayOfWeek; -import java.time.LocalDate; -import java.time.ZoneId; +import java.math.RoundingMode; +import java.time.*; +import java.time.temporal.TemporalAdjusters; import java.util.*; import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; @@ -374,35 +366,131 @@ public class PlanController { return success(planWeekTrendVO); } - @GetMapping("/getDayCapacity") - @Operation(summary = "综合大屏-日产能达成情况") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - public CommonResult getDayCapacity() { - DayCapacityVO dayCapacityVO = new DayCapacityVO(); - List statusList = new ArrayList<>(Collections.singletonList(PlanStatusEnum.已排产.getValue())); - List planDOList = planService.getPlanByStatus(statusList); - // 排产单数量 - dayCapacityVO.setOrders(planDOList.size()); - // 排产数量 - int plan = 0; - for (PlanDO planDO : planDOList) { - if (planDO.getPlanNumber() != null) { - plan = plan + planDO.getPlanNumber().intValue(); - } + @GetMapping("/getPlanCapacity") + @Operation(summary = "综合大屏-日/月产能达成情况") + @Parameter(name = "type", description = "类型 1-日 2-月", required = true, example = "1024") + public CommonResult getPlanCapacity( + @RequestParam(name = "type", defaultValue = "1") Integer type) { + + PlanCapacityVO vo = new PlanCapacityVO(); + + // 根据 type 计算时间区间 + LocalDate today = LocalDate.now(); + LocalDateTime startTime; + LocalDateTime endTime; + + if (type != null && type == 2) { + // 月 + startTime = today.withDayOfMonth(1).atStartOfDay(); + endTime = today.with(TemporalAdjusters.lastDayOfMonth()) + .atTime(LocalTime.MAX); + } else { + // 日(默认) + startTime = today.atStartOfDay(); + endTime = today.atTime(LocalTime.MAX); } - dayCapacityVO.setPlan(plan); - // 已生产数量 - statusList = Arrays.asList(PlanStatusEnum.已入库.getValue(),PlanStatusEnum.待入库.getValue());; - planDOList = planService.getPlanByStatus(statusList); - int finish = 0; - for (PlanDO planDO : planDOList) { + + // 一次性查询该时间段内所有计划 + List planList = + planService.getPlanCapacity(null, startTime, endTime); + + long orders = 0; + long planTotal = 0; + long finishedTotal = 0; + long totalPass = 0; + long totalFinish = 0; + + + for (PlanDO planDO : planList) { + + Integer status = planDO.getStatus(); + + // 已排产 + if (Objects.equals(status, PlanStatusEnum.已排产.getValue())) { + orders++; + if (planDO.getPlanNumber() != null) { + planTotal += planDO.getPlanNumber(); + } + } + + // 已生产(已入库 + 待入库) + if (Objects.equals(status, PlanStatusEnum.已入库.getValue()) + || Objects.equals(status, PlanStatusEnum.待入库.getValue())) { + if (planDO.getWangongNumber() != null) { + finishedTotal += planDO.getWangongNumber(); + } + } + + // 产率(全部计划) + if (planDO.getPassNumber() != null) { + totalPass += planDO.getPassNumber(); + } if (planDO.getWangongNumber() != null) { - finish = finish + planDO.getWangongNumber().intValue(); + totalFinish += planDO.getWangongNumber(); } } - dayCapacityVO.setPending(finish); - return success(dayCapacityVO); + // 设置返回值 + vo.setOrders((int) orders); + vo.setPlan((int) planTotal); + vo.setPending((int) finishedTotal); + + // 产率(百分比) + double rate = 0D; + if (totalFinish > 0) { + rate = BigDecimal.valueOf(totalPass) + .multiply(BigDecimal.valueOf(100)) + .divide(BigDecimal.valueOf(totalFinish), 2, RoundingMode.HALF_UP) + .doubleValue(); + } + vo.setRate(rate); + + return success(vo); } + + + + @GetMapping("/getLastDaysRate") + @Operation(summary = "综合大屏-前7天每日产率(百分比)") + public CommonResult> getLastDaysRate() { + + Map result = new LinkedHashMap<>(); + + LocalDate today = LocalDate.now(); + + // 遍历前七天 + for (int i = 6; i >= 0; i--) { // 从 6 天前到今天 + LocalDate date = today.minusDays(i); + LocalDateTime startTime = date.atStartOfDay(); + LocalDateTime endTime = date.atTime(LocalTime.MAX); + + // 查询当天的所有计划 + List planList = planService.getPlanCapacity(null, startTime, endTime); + + long totalPass = 0; + long totalFinish = 0; + + for (PlanDO planDO : planList) { + if (planDO.getPassNumber() != null) totalPass += planDO.getPassNumber(); + if (planDO.getWangongNumber() != null) totalFinish += planDO.getWangongNumber(); + } + + double rate = 0D; + if (totalFinish > 0) { + rate = BigDecimal.valueOf(totalPass) + .multiply(BigDecimal.valueOf(100)) + .divide(BigDecimal.valueOf(totalFinish), 2, RoundingMode.HALF_UP) + .doubleValue(); + } + + // key = yyyy-MM-dd + result.put(date.toString(), rate); + } + + return success(result); + } + + + } diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/DayCapacityVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/PlanCapacityVO.java similarity index 68% rename from yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/DayCapacityVO.java rename to yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/PlanCapacityVO.java index fe30575449..e11b45adea 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/DayCapacityVO.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/PlanCapacityVO.java @@ -3,19 +3,17 @@ package cn.iocoder.yudao.module.mes.controller.admin.plan.vo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -import java.util.List; - @Schema(description = "综合大屏 - 周生产趋势 Resp VO") @Data -public class DayCapacityVO { - @Schema(description = "orders") +public class PlanCapacityVO { + @Schema(description = "排产单数量") private Integer orders; - @Schema(description = "plan") + @Schema(description = "已排产") private Integer plan; - @Schema(description = "pending") + @Schema(description = "已生产") private Integer pending; @Schema(description = "rate") diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/plan/PlanMapper.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/plan/PlanMapper.java index 99fa05505c..8d73d309d4 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/plan/PlanMapper.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/plan/PlanMapper.java @@ -13,6 +13,7 @@ import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Update; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -129,4 +130,13 @@ public interface PlanMapper extends BaseMapperX { } return MapUtil.getLong(result.get(0), "sumCount", 0L); } + + + default List getPlanCapacity(List statusList, LocalDateTime startTime, LocalDateTime endTime) { + return selectList(new LambdaQueryWrapperX() + .inIfPresent(PlanDO::getStatus, statusList) + .betweenIfPresent(PlanDO::getStartTime, startTime, endTime) + .orderByDesc(PlanDO::getPriorityNum) + .orderByDesc(PlanDO::getStartTime)); + } } \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/energydevice/EnergyDeviceService.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/energydevice/EnergyDeviceService.java index 2bfd3951a3..a9ffc2d070 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/energydevice/EnergyDeviceService.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/energydevice/EnergyDeviceService.java @@ -2,11 +2,7 @@ package cn.iocoder.yudao.module.mes.service.energydevice; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo.HourEnergyValueVO; -import cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo.EnergyDeviceConsumptionReqVO; -import cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo.EnergyDevicePageReqVO; -import cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo.EnergyDeviceRespVO; -import cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo.EnergyDeviceSaveReqVO; +import cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo.*; import cn.iocoder.yudao.module.mes.dal.dataobject.energydevice.EnergyDeviceCheckRecordDO; import cn.iocoder.yudao.module.mes.dal.dataobject.energydevice.EnergyDeviceDO; @@ -114,4 +110,6 @@ public interface EnergyDeviceService { List lastEnergyStatistics(Long deviceTypeId,Long orgId); + + List latestSevenDaysStatistics(Long deviceTypeId, Long orgId); } \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/energydevice/EnergyDeviceServiceImpl.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/energydevice/EnergyDeviceServiceImpl.java index 63d8bb2061..9157b7ad0b 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/energydevice/EnergyDeviceServiceImpl.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/energydevice/EnergyDeviceServiceImpl.java @@ -29,6 +29,7 @@ import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import java.math.BigDecimal; import java.text.DecimalFormat; +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; @@ -329,31 +330,29 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService { // 4. 时间范围:最近 7 小时 LocalDateTime end = LocalDateTime.now(); LocalDateTime start = end.minusHours(7); - DateTimeFormatter hourFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH"); - // hour -> deviceId -> 数据缓存 - Map> hourCacheMap = new LinkedHashMap<>(); + // 5. 批量查询所有设备数据 + List> allRows = tDengineService.queryLastDataByHourBatch(deviceIds, start, end); - // 5. 查询每个设备的原始数据 - for (Long deviceId : deviceIds) { - List> rawData = tDengineService.queryLastDataByHour(deviceId, start, end); + // 6. 按小时 + 设备构建缓存(每小时最新覆盖) + Map> hourCacheMap = new LinkedHashMap<>(); + for (Map row : allRows) { + LocalDateTime ts = (LocalDateTime) row.get("timestamp"); + if (ts == null) continue; - for (Map row : rawData) { - LocalDateTime ts = (LocalDateTime) row.get("timestamp"); - String hourKey = ts.format(hourFormatter); + String hourKey = ts.format(hourFormatter); + Long deviceId = (Long) row.get("deviceId"); + String queryData = (String) row.get("queryData"); - TimePointCache cache = new TimePointCache(); - cache.setTimestamp(ts); - cache.setQueryData((String) row.get("queryData")); + TimePointCache cache = new TimePointCache(); + cache.setTimestamp(ts); + cache.setQueryData(queryData); - // 每小时最新覆盖之前的 - hourCacheMap.computeIfAbsent(hourKey, k -> new HashMap<>()) - .put(deviceId, cache); - } + hourCacheMap.computeIfAbsent(hourKey, k -> new HashMap<>()).put(deviceId, cache); } - // 6. 生成每小时结果,补齐缺失小时 + // 7. 生成每小时结果,补齐缺失小时 List result = new ArrayList<>(); for (int i = 0; i < 7; i++) { LocalDateTime hourTime = start.plusHours(i); @@ -372,6 +371,95 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService { return result; } + @Override + public List latestSevenDaysStatistics(Long deviceTypeId, Long orgId) { + + // 找最新的能耗设备配置 + EnergyDeviceDO energyDevice = energyDeviceMapper.selectOne( + Wrappers.lambdaQuery() + .eq(EnergyDeviceDO::getDeviceTypeId, deviceTypeId) + .eq(EnergyDeviceDO::getOrgId, orgId) + .orderByDesc(EnergyDeviceDO::getCreateTime) + .last("LIMIT 1") + ); + + if (energyDevice == null || StringUtils.isBlank(energyDevice.getRules())) { + return Collections.emptyList(); + } + + // 解析规则 + List rules = + JsonUtils.parseArray(energyDevice.getRules(), OperationRulesVO.class); + + if (rules.isEmpty()) { + return Collections.emptyList(); + } + + // 收集 deviceId + Set deviceIds = rules.stream() + .map(OperationRulesVO::getDeviceId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + if (deviceIds.isEmpty()) { + return Collections.emptyList(); + } + + // 时间范围:前 7 天 + LocalDate startDate = LocalDate.now().minusDays(7); + int days = 7; + + // 查询 TDengine(方案 B) + List> rows = + tDengineService.queryLastDataByDaySafe(deviceIds, startDate, days); + + // day -> deviceId -> cache + Map> dayCacheMap = new LinkedHashMap<>(); + + for (Map row : rows) { + String day = (String) row.get("day"); + Long deviceId = (Long) row.get("deviceId"); + LocalDateTime ts = (LocalDateTime) row.get("timestamp"); + + if (day == null || deviceId == null || ts == null) { + continue; + } + + TimePointCache cache = new TimePointCache(); + cache.setTimestamp(ts); + cache.setQueryData((String) row.get("queryData")); + + dayCacheMap + .computeIfAbsent(day, k -> new HashMap<>()) + .put(deviceId, cache); + } + + // 生成结果(补齐缺失日期) + List result = new ArrayList<>(); + + for (int i = 0; i < days; i++) { + LocalDate day = startDate.plusDays(i); + String dayKey = day.toString(); + + Map deviceCache = + dayCacheMap.getOrDefault(dayKey, Collections.emptyMap()); + + Double value = calculateByRules(rules, deviceCache); + + DayEnergyValueVO vo = new DayEnergyValueVO(); + vo.setDay(dayKey); + vo.setValue(formatDouble(value)); + + result.add(vo); + } + + return result; + + + + + + } // =================== 核心方法 =================== diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/plan/PlanService.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/plan/PlanService.java index 7d34d46b84..c33eff0fb1 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/plan/PlanService.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/plan/PlanService.java @@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.plan.PlanDO; import cn.iocoder.yudao.module.mes.service.itemrequisition.entity.ItemRequisitionAndStock; import javax.validation.Valid; +import java.time.LocalDateTime; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -104,4 +105,6 @@ public interface PlanService { * @return 生产计划分页 */ PlanRespVO getPlanRespVO(PlanDO planDO); + + List getPlanCapacity(List statusList, LocalDateTime startTime, LocalDateTime endTime); } \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/plan/PlanServiceImpl.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/plan/PlanServiceImpl.java index 62a4e00d54..f91e52abcb 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/plan/PlanServiceImpl.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/plan/PlanServiceImpl.java @@ -228,6 +228,13 @@ public class PlanServiceImpl implements PlanService { return planRespVO; } + @Override + public List getPlanCapacity(List statusList, LocalDateTime startTime, LocalDateTime endTime) { + + return planMapper.getPlanCapacity(statusList,startTime,endTime); + + } + @Override public PageResult getPlanPage(PlanPageReqVO pageReqVO) { PageResult pageResult = planMapper.selectPage(pageReqVO);