diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicepointrules/DevicePointRulesController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicepointrules/DevicePointRulesController.java index 6077d9ebd..e4d82f535 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicepointrules/DevicePointRulesController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicepointrules/DevicePointRulesController.java @@ -80,13 +80,30 @@ public class DevicePointRulesController { public CommonResult> getDevicePointRulesPage(@Valid DevicePointRulesPageReqVO pageReqVO) { PageResult pageResult = devicePointRulesService.getDevicePointRulesPage(pageReqVO); PageResult rulesRespVOPageResult = BeanUtils.toBean(pageResult, DevicePointRulesRespVO.class); - for (DevicePointRulesRespVO devicePointRulesRespVO : rulesRespVOPageResult.getList()) { - if (StringUtils.isNotBlank(devicePointRulesRespVO.getFieldRule())) { - List pointRulesVOList = JSON.parseArray(devicePointRulesRespVO.getFieldRule(), PointRulesRespVO.class); - devicePointRulesRespVO.setPointRulesVOList(pointRulesVOList); - } - - } +// +// for (DevicePointRulesRespVO respVO : rulesRespVOPageResult.getList()) { +// String fieldRule = respVO.getFieldRule(); +// if (StringUtils.isBlank(fieldRule)) { +// continue; +// } +// +// String json = fieldRule.trim(); +// try { +// if (json.startsWith("[")) { +// List pointRulesVOList = JSON.parseArray(json, PointRulesRespVO.class); +// respVO.setPointRulesVOList(pointRulesVOList); +// continue; +// } +// +// if (json.startsWith("{")) { +// respVO.setPointRulesVOList(Collections.emptyList()); +// +// } +// } catch (Exception e) { +// // 防御性处理,避免单条脏数据影响整页 +// respVO.setPointRulesVOList(Collections.emptyList()); +// } +// } return success(rulesRespVOPageResult); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/mqtt/consumer/MqttDataHandler.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/mqtt/consumer/MqttDataHandler.java index 969ce401f..3a1f3295d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/mqtt/consumer/MqttDataHandler.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/mqtt/consumer/MqttDataHandler.java @@ -24,11 +24,14 @@ import cn.iocoder.yudao.module.iot.framework.mqtt.consumer.impl.AsyncService; import cn.iocoder.yudao.module.iot.framework.mqtt.entity.MqttData; import cn.iocoder.yudao.module.iot.framework.mqtt.utils.DateUtils; import cn.iocoder.yudao.module.iot.framework.mqtt.utils.MqttDataUtils; +import cn.iocoder.yudao.module.iot.framework.util.FormulaEvalUtils; import cn.iocoder.yudao.module.iot.service.device.DeviceService; import cn.iocoder.yudao.module.iot.service.device.TDengineService; import cn.iocoder.yudao.module.iot.service.iotorganization.IotOrganizationService; import cn.iocoder.yudao.module.iot.service.mqttrecord.MqttRecordService; import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.core.toolkit.Wrappers; @@ -42,6 +45,7 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import javax.annotation.Resource; +import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -301,21 +305,55 @@ public class MqttDataHandler extends SuperConsumer { // 3. 入库 if (!validDataList.isEmpty()) { + saveToDatabase(deviceId, validDataList, successCount); + } else { + log.warn("设备 {} 未匹配到 MQTT 数据", deviceId); + } - saveToDatabase( - deviceId, - validDataList, - successCount - ); + // 4. 计算并写入产能 + try { + handleCapacityFormula(device, varListMap); + } catch (Exception e) { + log.error("设备 {} 产能计算失败", deviceId, e); + } + } - } else { + // handleCapacityFormula + private void handleCapacityFormula(DeviceDO device, Map varListMap) { + DevicePointRulesDO formulaRule = devicePointRulesMapper.selectOne( + Wrappers.lambdaQuery() + .eq(DevicePointRulesDO::getDeviceId, device.getId()) + .eq(DevicePointRulesDO::getIdentifier, "COUNT") + .orderByDesc(DevicePointRulesDO::getUpdateTime) + .last("limit 1") + ); + if (formulaRule == null || StringUtils.isBlank(formulaRule.getFieldRule())) { + return; + } - log.warn("设备 {} 未匹配到 MQTT 数据", deviceId); + BigDecimal capacity = null; + String rule = formulaRule.getFieldRule().trim(); + if (rule.startsWith("[")) { + JSONArray steps = JSON.parseArray(rule); + capacity = FormulaEvalUtils.evalStepArrayByCode(steps, varListMap); + } else { + // 如需兼容旧 expr 结构可保留 + JSONObject root = JSON.parseObject(rule); + JSONObject expr = root.getJSONObject("expr"); + if (expr != null) { + capacity = FormulaEvalUtils.evalExpr(expr, varListMap); + } } + + if (capacity == null) { + return; + } + tDengineService.insertDeviceCapacityRecord(device.getId(), capacity.doubleValue()); } + private DevicePointRulesDO getDevicePointRules(Long deviceId) { List list = diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/util/FormulaEvalUtils.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/util/FormulaEvalUtils.java new file mode 100644 index 000000000..be3b4c7ff --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/util/FormulaEvalUtils.java @@ -0,0 +1,96 @@ +package cn.iocoder.yudao.module.iot.framework.util; + +// FormulaEvalUtils +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import org.apache.commons.lang3.StringUtils; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Map; + +public class FormulaEvalUtils { + + private FormulaEvalUtils() {} + + public static BigDecimal evalExpr(JSONObject node, Map vars) { + if (node == null) return BigDecimal.ZERO; + + if (node.containsKey("field")) { + Object value = vars.get(node.getString("field")); + return toBigDecimal(value); + } + + if (node.containsKey("value")) { + return toBigDecimal(node.get("value")); + } + + String op = node.getString("op"); + BigDecimal left = evalExpr(node.getJSONObject("left"), vars); + BigDecimal right = evalExpr(node.getJSONObject("right"), vars); + return apply(left, right, op); + } + + // + public static BigDecimal evalStepArrayByCode(JSONArray steps, Map vars) { + if (steps == null || steps.isEmpty()) { + return BigDecimal.ZERO; + } + + BigDecimal result = null; + String pendingBigOp = null; // 上一步的 bigOperator + + for (int i = 0; i < steps.size(); i++) { + JSONObject step = steps.getJSONObject(i); + if (step == null) continue; + + String code = step.getString("code"); + String operator = step.getString("operator"); + Object valueObj = step.get("value"); + + BigDecimal codeValue = readCodeValue(code, vars); + BigDecimal value = toBigDecimal(valueObj); + BigDecimal current = apply(codeValue, value, operator); // A/B/C + + if (result == null) { + result = current; + } else { + result = apply(result, current, pendingBigOp); // 用上一项的 bigOperator + } + + pendingBigOp = step.getString("bigOperator"); // 留给下一轮使用 + } + + return result == null ? BigDecimal.ZERO : result; + } + + private static BigDecimal readCodeValue(String code, Map vars) { + if (StringUtils.isBlank(code) || vars == null) { + return BigDecimal.ZERO; + } + return toBigDecimal(vars.get(code)); + } + + private static BigDecimal toBigDecimal(Object raw) { + if (raw == null || StringUtils.isBlank(String.valueOf(raw))) { + return BigDecimal.ZERO; + } + return new BigDecimal(String.valueOf(raw)); + } + + private static BigDecimal apply(BigDecimal left, BigDecimal right, String op) { + if (StringUtils.isBlank(op)) { + return left; + } + switch (op) { + case "+": return left.add(right); + case "-": return left.subtract(right); + case "*": return left.multiply(right); + case "/": + if (right.compareTo(BigDecimal.ZERO) == 0) return BigDecimal.ZERO; + return left.divide(right, 6, RoundingMode.HALF_UP); + default: throw new IllegalArgumentException("Unsupported op: " + op); + } + } +} + 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 9859d8262..caa958bc9 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 @@ -2516,4 +2516,23 @@ public class TDengineService { return record; } - } \ No newline at end of file + + + // ========================= 产能相关接口 =================== + @DS("tdengine") + public boolean insertDeviceCapacityRecord(Long deviceId, Double capacityValue) { + if (deviceId == null || capacityValue == null) { + return false; + } + try { + String sql = "INSERT INTO iot_device_capacity_record (ts, capacity_value, device_id) VALUES (NOW, " + + capacityValue + ", " + deviceId + ")"; + jdbcTemplate.execute(sql); + return true; + } catch (Exception e) { + log.error("insertDeviceCapacityRecord failed, deviceId={}, capacityValue={}", deviceId, capacityValue, e); + return false; + } + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicepointrules/DevicePointRulesServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicepointrules/DevicePointRulesServiceImpl.java index 6e5f86f6e..fcfe23e23 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicepointrules/DevicePointRulesServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicepointrules/DevicePointRulesServiceImpl.java @@ -46,11 +46,11 @@ public class DevicePointRulesServiceImpl implements DevicePointRulesService { validateDevicePointRulesExists(updateReqVO.getId()); // 更新 DevicePointRulesDO updateObj = BeanUtils.toBean(updateReqVO, DevicePointRulesDO.class); - if (!updateReqVO.getPointRulesVOList().isEmpty()){ - String jsonString = JSON.toJSONString(updateReqVO.getPointRulesVOList()); - updateObj.setFieldRule(jsonString); - - } +// if (!updateReqVO.getPointRulesVOList().isEmpty()){ +// String jsonString = JSON.toJSONString(updateReqVO.getPointRulesVOList()); +// updateObj.setFieldRule(jsonString); +// +// } devicePointRulesMapper.updateById(updateObj); } diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/TaskController.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/TaskController.java index 06127cfc8..03209d756 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/TaskController.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/TaskController.java @@ -113,8 +113,8 @@ public class TaskController { @Operation(summary = "获得生产任务单分页") @PreAuthorize("@ss.hasPermission('mes:task:query')") public CommonResult> getTaskPage(@Valid TaskPageReqVO pageReqVO) { - PageResult pageResult = taskService.getTaskPage(pageReqVO); - return success(BeanUtils.toBean(pageResult, TaskRespVO.class)); + PageResult pageResult = taskService.getTaskPage(pageReqVO); + return success(pageResult); } @GetMapping("/export-excel") @@ -124,10 +124,10 @@ public class TaskController { public void exportTaskExcel(@Valid TaskPageReqVO pageReqVO, HttpServletResponse response) throws IOException { pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); - List list = taskService.getTaskPage(pageReqVO).getList(); + List list = taskService.getTaskPage(pageReqVO).getList(); // 导出 Excel ExcelUtils.write(response, "生产任务单.xls", "数据", TaskRespVO.class, - BeanUtils.toBean(list, TaskRespVO.class)); + list); } // ==================== 子表(生产任务单明细) ==================== diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/vo/TaskRespVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/vo/TaskRespVO.java index d2b67d4bb..466c7ac84 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/vo/TaskRespVO.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/vo/TaskRespVO.java @@ -62,11 +62,21 @@ public class TaskRespVO { @ExcelProperty("创建时间") private LocalDateTime createTime; - @Schema(description = "任务单类型", example = "你猜") + @Schema(description = "任务单类型", example = "") @ExcelProperty("任务单类型") private String taskType; + @Schema(description = "任务总数", example = "") + @ExcelProperty("任务总数") + private Long totalNumber; + @Schema(description = "已计划数量", example = "") + @ExcelProperty("已计划数量") + private Long planNumber; + + @Schema(description = "未计划数量", example = "") + @ExcelProperty("未计划数量") + private Long unPlanNumber; diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/task/TaskService.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/task/TaskService.java index e937fa785..75317bc27 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/task/TaskService.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/task/TaskService.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.mes.controller.admin.task.vo.TaskDetailRespVO; import cn.iocoder.yudao.module.mes.controller.admin.task.vo.TaskPageReqVO; +import cn.iocoder.yudao.module.mes.controller.admin.task.vo.TaskRespVO; import cn.iocoder.yudao.module.mes.controller.admin.task.vo.TaskSaveReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.plan.PlanDO; import cn.iocoder.yudao.module.mes.dal.dataobject.task.TaskDO; @@ -76,7 +77,7 @@ public interface TaskService { * @param pageReqVO 分页查询 * @return 生产任务单分页 */ - PageResult getTaskPage(TaskPageReqVO pageReqVO); + PageResult getTaskPage(TaskPageReqVO pageReqVO); /** * 获得生产任务单分页 diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/task/TaskServiceImpl.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/task/TaskServiceImpl.java index 4c84014a2..9c9e13b31 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/task/TaskServiceImpl.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/task/TaskServiceImpl.java @@ -11,10 +11,7 @@ import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductUnitDO; 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.plan.vo.PlanStatusEnum; -import cn.iocoder.yudao.module.mes.controller.admin.task.vo.TaskDetailRespVO; -import cn.iocoder.yudao.module.mes.controller.admin.task.vo.TaskPageReqVO; -import cn.iocoder.yudao.module.mes.controller.admin.task.vo.TaskSaveReqVO; -import cn.iocoder.yudao.module.mes.controller.admin.task.vo.TaskStatusEnum; +import cn.iocoder.yudao.module.mes.controller.admin.task.vo.*; 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; @@ -133,8 +130,35 @@ public class TaskServiceImpl implements TaskService { } @Override - public PageResult getTaskPage(TaskPageReqVO pageReqVO) { - return taskMapper.selectPage(pageReqVO); + public PageResult getTaskPage(TaskPageReqVO pageReqVO) { + PageResult taskDOPageResult = taskMapper.selectPage(pageReqVO); + PageResult bean = BeanUtils.toBean(taskDOPageResult, TaskRespVO.class); + + if (bean.getList() == null || bean.getList().isEmpty()) { + return bean; + } + + for (TaskRespVO task : bean.getList()) { + List summaryList = summaryMapper.selectListByTaskId(task.getId()); + + long totalNumber = 0L; + long planNumber = 0L; + for (ViewTaskProductSummary summary : summaryList) { + totalNumber += summary.getTotalNumber() == null ? 0L : summary.getTotalNumber(); + planNumber += summary.getPlanNumber() == null ? 0L : summary.getPlanNumber(); + } + + long unplanNumber = totalNumber - planNumber; + if (unplanNumber < 0) { + unplanNumber = 0L; + } + + task.setTotalNumber(totalNumber); + task.setPlanNumber(planNumber); + task.setUnPlanNumber(unplanNumber); + } + + return bean; } @Override diff --git a/yudao-module-mes/yudao-module-mes-biz/src/test/java/cn/iocoder/yudao/module/mes/service/task/TaskServiceImplTest.java b/yudao-module-mes/yudao-module-mes-biz/src/test/java/cn/iocoder/yudao/module/mes/service/task/TaskServiceImplTest.java index 20084eb8e..c423d7545 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/test/java/cn/iocoder/yudao/module/mes/service/task/TaskServiceImplTest.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/test/java/cn/iocoder/yudao/module/mes/service/task/TaskServiceImplTest.java @@ -141,7 +141,7 @@ public class TaskServiceImplTest extends BaseDbUnitTest { reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); // 调用 - PageResult pageResult = taskService.getTaskPage(reqVO); + PageResult pageResult = taskService.getTaskPage(reqVO); // 断言 assertEquals(1, pageResult.getTotal()); assertEquals(1, pageResult.getList().size());