From 3ec5e2f7bc81b1471e0f66ccc09ce30d9534919b Mon Sep 17 00:00:00 2001 From: "zangch@mesnac.com" Date: Fri, 6 Mar 2026 13:22:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(pda):=20=E6=96=B0=E5=A2=9EPDA=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E5=8F=82=E6=95=B0=E5=80=BC=E7=BB=B4=E6=8A=A4=E4=B8=8E?= =?UTF-8?q?=E7=89=A9=E6=96=99=E5=B7=A5=E5=8D=95=E6=9F=A5=E8=AF=A2=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现upsertTodayParamValue方法支持PDA参数值新增不覆盖当天记录 - 添加BaseMaterialInfo查询关键字字段及PDA物料模糊查询接口 - 新增PDA按物料关键字查询工单列表功能及订单状态数量维护接口 - 在DmsMobileController中增加PDA订单维护、设备运行时间更新等API - 添加设备参数值的运行时间与当日产量更新功能及数值校验逻辑 - 完善异常处理机制,统一系统异常日志记录与业务异常转换 --- .../aucma/base/domain/BaseMaterialInfo.java | 11 + .../base/mapper/BaseMaterialinfoMapper.java | 8 + .../base/mapper/BaseOrderInfoMapper.java | 8 + .../service/IBaseDeviceParamValService.java | 8 + .../service/IBaseMaterialInfoService.java | 8 + .../base/service/IBaseOrderInfoService.java | 16 ++ .../impl/BaseDeviceParamValServiceImpl.java | 36 ++- .../impl/BaseMaterialInfoServiceImpl.java | 9 + .../impl/BaseOrderInfoServiceImpl.java | 91 +++++++- .../mapper/base/BaseMaterialinfoMapper.xml | 21 +- .../mapper/base/BaseOrderInfoMapper.xml | 19 +- .../dms/controller/DmsMobileController.java | 214 +++++++++++++++++- 12 files changed, 438 insertions(+), 11 deletions(-) diff --git a/aucma-base/src/main/java/com/aucma/base/domain/BaseMaterialInfo.java b/aucma-base/src/main/java/com/aucma/base/domain/BaseMaterialInfo.java index 984b890..847142c 100644 --- a/aucma-base/src/main/java/com/aucma/base/domain/BaseMaterialInfo.java +++ b/aucma-base/src/main/java/com/aucma/base/domain/BaseMaterialInfo.java @@ -111,6 +111,9 @@ public class BaseMaterialInfo extends BaseEntity @Excel(name = "商品名称") private String productName; + /** 查询关键字(非持久化) */ + private String keyword; + public String getProductCode() { return productCode; } @@ -127,6 +130,14 @@ public class BaseMaterialInfo extends BaseEntity this.productName = productName; } + public String getKeyword() { + return keyword; + } + + public void setKeyword(String keyword) { + this.keyword = keyword; + } + public String getDISPO() { return DISPO; } diff --git a/aucma-base/src/main/java/com/aucma/base/mapper/BaseMaterialinfoMapper.java b/aucma-base/src/main/java/com/aucma/base/mapper/BaseMaterialinfoMapper.java index 30939d6..0110c39 100644 --- a/aucma-base/src/main/java/com/aucma/base/mapper/BaseMaterialinfoMapper.java +++ b/aucma-base/src/main/java/com/aucma/base/mapper/BaseMaterialinfoMapper.java @@ -27,6 +27,14 @@ public interface BaseMaterialinfoMapper */ public List selectBaseMaterialInfoList(BaseMaterialInfo baseMaterialInfo); + /** + * PDA按关键字模糊查询物料 + * + * @param keyword 物料关键字 + * @return 物料集合 + */ + public List selectPdaMaterialListByKeyword(String keyword); + /** * 新增物料信息 * diff --git a/aucma-base/src/main/java/com/aucma/base/mapper/BaseOrderInfoMapper.java b/aucma-base/src/main/java/com/aucma/base/mapper/BaseOrderInfoMapper.java index 445e2d5..08a577e 100644 --- a/aucma-base/src/main/java/com/aucma/base/mapper/BaseOrderInfoMapper.java +++ b/aucma-base/src/main/java/com/aucma/base/mapper/BaseOrderInfoMapper.java @@ -36,6 +36,14 @@ public interface BaseOrderInfoMapper { */ public List selectAllOrderInfoList(BaseOrderInfo baseOrderInfo); + /** + * PDA按物料编码集合查询工单 + * + * @param materialCodes 物料编码集合 + * @return 工单集合 + */ + public List selectPdaOrderListByMaterialCodes(@Param("materialCodes") List materialCodes); + /** * 新增工单信息 * diff --git a/aucma-base/src/main/java/com/aucma/base/service/IBaseDeviceParamValService.java b/aucma-base/src/main/java/com/aucma/base/service/IBaseDeviceParamValService.java index 4cd98dd..0c4dccc 100644 --- a/aucma-base/src/main/java/com/aucma/base/service/IBaseDeviceParamValService.java +++ b/aucma-base/src/main/java/com/aucma/base/service/IBaseDeviceParamValService.java @@ -111,4 +111,12 @@ public interface IBaseDeviceParamValService * @return SPC分析结果 */ public Map getSPCData(String deviceCode, String paramCode, String startTime, String endTime); + + /** + * 按设备与参数更新当天数据,不存在则新增 + * + * @param baseDeviceParamVal 设备参数值 + * @return 结果 + */ + public int upsertTodayParamValue(BaseDeviceParamVal baseDeviceParamVal); } diff --git a/aucma-base/src/main/java/com/aucma/base/service/IBaseMaterialInfoService.java b/aucma-base/src/main/java/com/aucma/base/service/IBaseMaterialInfoService.java index 1e6356c..7f66c8d 100644 --- a/aucma-base/src/main/java/com/aucma/base/service/IBaseMaterialInfoService.java +++ b/aucma-base/src/main/java/com/aucma/base/service/IBaseMaterialInfoService.java @@ -27,6 +27,14 @@ public interface IBaseMaterialInfoService */ public List selectBaseMaterialInfoList(BaseMaterialInfo baseMaterialInfo); + /** + * PDA按关键字模糊查询物料 + * + * @param keyword 物料关键字 + * @return 物料集合 + */ + public List selectPdaMaterialListByKeyword(String keyword); + /** * 新增物料信息 * diff --git a/aucma-base/src/main/java/com/aucma/base/service/IBaseOrderInfoService.java b/aucma-base/src/main/java/com/aucma/base/service/IBaseOrderInfoService.java index d224f65..b5428c6 100644 --- a/aucma-base/src/main/java/com/aucma/base/service/IBaseOrderInfoService.java +++ b/aucma-base/src/main/java/com/aucma/base/service/IBaseOrderInfoService.java @@ -35,6 +35,14 @@ public interface IBaseOrderInfoService { */ public List selectAllOrderInfoList(BaseOrderInfo baseOrderInfo); + /** + * PDA按物料关键字查询工单列表 + * + * @param materialKeyword 物料关键字 + * @return 工单集合 + */ + public List selectPdaOrderListByMaterialKeyword(String materialKeyword); + /** * 新增工单信息 * @@ -147,4 +155,12 @@ public interface IBaseOrderInfoService { * @return 结果 */ public int batchCompleteProduction(Long[] objIds, Long completeQty, Long defectQty); + + /** + * PDA维护工单状态与数量 + * + * @param baseOrderInfo 工单维护参数 + * @return 结果 + */ + public int updatePdaOrderExecution(BaseOrderInfo baseOrderInfo); } diff --git a/aucma-base/src/main/java/com/aucma/base/service/impl/BaseDeviceParamValServiceImpl.java b/aucma-base/src/main/java/com/aucma/base/service/impl/BaseDeviceParamValServiceImpl.java index c918579..9d2aa72 100644 --- a/aucma-base/src/main/java/com/aucma/base/service/impl/BaseDeviceParamValServiceImpl.java +++ b/aucma-base/src/main/java/com/aucma/base/service/impl/BaseDeviceParamValServiceImpl.java @@ -59,7 +59,8 @@ public class BaseDeviceParamValServiceImpl implements IBaseDeviceParamValService public List selectLatestBaseDeviceParamValList(BaseDeviceParamVal baseDeviceParamVal) { try { - if (baseDeviceParamVal == null) { + // 入参为空直接返回,调用侧可据此判定为无效请求 + if (baseDeviceParamVal == null) { baseDeviceParamVal = new BaseDeviceParamVal(); } // 防止全表扫描:若未指定时间范围,默认查询最近24小时 @@ -86,6 +87,7 @@ public class BaseDeviceParamValServiceImpl implements IBaseDeviceParamValService @Override public int insertBaseDeviceParamVal(BaseDeviceParamVal baseDeviceParamVal) { + // 当天无记录时再插入,兼容首次上报场景 return baseDeviceParamValMapper.insertBaseDeviceParamVal(baseDeviceParamVal); } @@ -207,7 +209,8 @@ public class BaseDeviceParamValServiceImpl implements IBaseDeviceParamValService @Override public List selectTraceList(String deviceCode, String paramCode, String startTime, String endTime) { try { - Map params = new HashMap<>(); + // mapper层使用Map入参,便于只携带本次upsert所需字段 + Map params = new HashMap<>(); params.put("deviceCode", deviceCode); params.put("paramCode", paramCode); params.put("startTime", startTime); @@ -235,7 +238,8 @@ public class BaseDeviceParamValServiceImpl implements IBaseDeviceParamValService result.put("paramName", paramName != null ? paramName : paramCode); // 查询参数历史值 - Map params = new HashMap<>(); + // mapper层使用Map入参,便于只携带本次upsert所需字段 + Map params = new HashMap<>(); params.put("deviceCode", deviceCode); params.put("paramCode", paramCode); params.put("startTime", startTime); @@ -285,6 +289,29 @@ public class BaseDeviceParamValServiceImpl implements IBaseDeviceParamValService } } + + /** + * 按设备与参数直接新增一条数据(不覆盖当天历史记录) + */ + @Override + public int upsertTodayParamValue(BaseDeviceParamVal baseDeviceParamVal) + { + // 入参为空直接返回,调用侧可据此判定为无效请求 + if (baseDeviceParamVal == null) { + return 0; + } + // 若调用方未传时间,统一回填服务端当前时间,避免空时间导致SQL匹配偏差 + Date now = new Date(); + if (baseDeviceParamVal.getCollectTime() == null) { + baseDeviceParamVal.setCollectTime(now); + } + if (baseDeviceParamVal.getRecordTime() == null) { + baseDeviceParamVal.setRecordTime(now); + } + + // Why:PDA上报需要沉淀完整过程数据,后续分析依赖时间序列,不能覆盖当天已有记录 + return baseDeviceParamValMapper.insertBaseDeviceParamVal(baseDeviceParamVal); + } private double calculateMean(List values) { if (values == null || values.isEmpty()) return 0.0; double sum = 0.0; @@ -312,3 +339,6 @@ public class BaseDeviceParamValServiceImpl implements IBaseDeviceParamValService return Math.min(cpupper, cplower); } } + + + diff --git a/aucma-base/src/main/java/com/aucma/base/service/impl/BaseMaterialInfoServiceImpl.java b/aucma-base/src/main/java/com/aucma/base/service/impl/BaseMaterialInfoServiceImpl.java index e045064..7a5860a 100644 --- a/aucma-base/src/main/java/com/aucma/base/service/impl/BaseMaterialInfoServiceImpl.java +++ b/aucma-base/src/main/java/com/aucma/base/service/impl/BaseMaterialInfoServiceImpl.java @@ -46,6 +46,15 @@ public class BaseMaterialInfoServiceImpl implements IBaseMaterialInfoService return baseMaterialInfoMapper.selectBaseMaterialInfoList(baseMaterialInfo); } + /** + * PDA按关键字模糊查询物料 + */ + @Override + public List selectPdaMaterialListByKeyword(String keyword) + { + return baseMaterialInfoMapper.selectPdaMaterialListByKeyword(keyword); + } + /** * 新增物料信息 * diff --git a/aucma-base/src/main/java/com/aucma/base/service/impl/BaseOrderInfoServiceImpl.java b/aucma-base/src/main/java/com/aucma/base/service/impl/BaseOrderInfoServiceImpl.java index 97c05bf..439fd7f 100644 --- a/aucma-base/src/main/java/com/aucma/base/service/impl/BaseOrderInfoServiceImpl.java +++ b/aucma-base/src/main/java/com/aucma/base/service/impl/BaseOrderInfoServiceImpl.java @@ -1,15 +1,20 @@ package com.aucma.base.service.impl; +import java.util.ArrayList; import java.util.Date; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; +import com.aucma.base.domain.BaseMaterialInfo; +import com.aucma.base.domain.BaseOrderInfo; +import com.aucma.base.mapper.BaseOrderInfoMapper; +import com.aucma.base.service.IBaseMaterialInfoService; +import com.aucma.base.service.IBaseOrderInfoService; import com.aucma.common.utils.DateUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.aucma.base.mapper.BaseOrderInfoMapper; -import com.aucma.base.domain.BaseOrderInfo; -import com.aucma.base.service.IBaseOrderInfoService; /** * 工单信息Service业务层处理 @@ -22,6 +27,9 @@ public class BaseOrderInfoServiceImpl implements IBaseOrderInfoService { @Autowired private BaseOrderInfoMapper baseOrderInfoMapper; + @Autowired + private IBaseMaterialInfoService baseMaterialInfoService; + /** * 查询工单信息 * @@ -55,6 +63,30 @@ public class BaseOrderInfoServiceImpl implements IBaseOrderInfoService { return baseOrderInfoMapper.selectAllOrderInfoList(baseOrderInfo); } + /** + * PDA按物料关键字查询工单列表 + */ + @Override + public List selectPdaOrderListByMaterialKeyword(String materialKeyword) { + // 第一步:先在物料主数据中做模糊匹配,保证PDA搜索结果受主数据启停与编码口径约束 + List materials = baseMaterialInfoService.selectPdaMaterialListByKeyword(materialKeyword); + if (materials == null || materials.isEmpty()) { + return new ArrayList<>(); + } + Set materialCodes = new LinkedHashSet<>(); + for (BaseMaterialInfo material : materials) { + // 用LinkedHashSet去重并保序,避免同物料重复命中导致订单列表重复 + if (material.getMaterialCode() != null && !material.getMaterialCode().trim().isEmpty()) { + materialCodes.add(material.getMaterialCode().trim()); + } + } + if (materialCodes.isEmpty()) { + return new ArrayList<>(); + } + // 第二步:按物料编码集合回查订单,减少在订单表直接做模糊搜索带来的性能与误匹配风险 + return baseOrderInfoMapper.selectPdaOrderListByMaterialCodes(new ArrayList<>(materialCodes)); + } + /** * 新增工单信息 * @@ -254,4 +286,57 @@ public class BaseOrderInfoServiceImpl implements IBaseOrderInfoService { Date now = DateUtils.getNowDate(); return baseOrderInfoMapper.batchCompleteProduction(objIds, completeQty, defectQty, now); } + + /** + * PDA维护工单状态与数量 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int updatePdaOrderExecution(BaseOrderInfo baseOrderInfo) { + if (baseOrderInfo == null) { + return 0; + } + BaseOrderInfo order = null; + // 优先按主键定位,避免工单编号存在历史重复或外部系统口径不一致时误更新 + if (baseOrderInfo.getObjId() != null) { + order = baseOrderInfoMapper.selectBaseOrderInfoByObjId(baseOrderInfo.getObjId()); + } + // 仅在没有主键时再退化为按工单编号查找,兼容PDA轻量传参 + if (order == null && baseOrderInfo.getOrderCode() != null && !baseOrderInfo.getOrderCode().trim().isEmpty()) { + order = selectBaseOrderInfoByOrderCode(baseOrderInfo.getOrderCode().trim()); + } + if (order == null) { + return 0; + } + + Date now = DateUtils.getNowDate(); + // Why:该标识用于区分SAP/计划系统同步数据与PDA人工干预数据,方便后续追溯 + order.setManualUpdateFlag("1"); + order.setUpdatedTime(now); + + // 执行状态允许人工修正,但仍受控制器的枚举校验约束 + if (baseOrderInfo.getExecutionStatus() != null && !baseOrderInfo.getExecutionStatus().trim().isEmpty()) { + order.setExecutionStatus(baseOrderInfo.getExecutionStatus().trim()); + } + if (baseOrderInfo.getActualCompleteQty() != null) { + // 列表页展示兼容completeAmount,因此这里同步两套字段,避免前后端显示不一致 + order.setActualCompleteQty(baseOrderInfo.getActualCompleteQty()); + order.setCompleteAmount(baseOrderInfo.getActualCompleteQty()); + } + if (baseOrderInfo.getActualDefectQty() != null) { + order.setActualDefectQty(baseOrderInfo.getActualDefectQty()); + } + + // 人工将工单切到运行中时,如果系统尚未记录开工时间,则补记当前时间 + if ("RUNNING".equals(order.getExecutionStatus()) && order.getStartTime() == null) { + order.setStartTime(now); + } + if ("COMPLETED".equals(order.getExecutionStatus())) { + // Why:PDA显式将工单维护为已完成时,需要同步完工时间与完成日期,保证执行页和报表口径一致 + order.setFinishTime(now); + order.setCompleteDate(now); + } + + return baseOrderInfoMapper.updateBaseOrderInfo(order); + } } diff --git a/aucma-base/src/main/resources/mapper/base/BaseMaterialinfoMapper.xml b/aucma-base/src/main/resources/mapper/base/BaseMaterialinfoMapper.xml index a88c18c..7fa29df 100644 --- a/aucma-base/src/main/resources/mapper/base/BaseMaterialinfoMapper.xml +++ b/aucma-base/src/main/resources/mapper/base/BaseMaterialinfoMapper.xml @@ -84,6 +84,25 @@ where ml.obj_id = #{objId} + + SELECT seq_base_materialinfo.NEXTVAL as objId FROM DUAL @@ -171,4 +190,4 @@ #{objId} - \ No newline at end of file + diff --git a/aucma-base/src/main/resources/mapper/base/BaseOrderInfoMapper.xml b/aucma-base/src/main/resources/mapper/base/BaseOrderInfoMapper.xml index e17b0eb..fc6aac5 100644 --- a/aucma-base/src/main/resources/mapper/base/BaseOrderInfoMapper.xml +++ b/aucma-base/src/main/resources/mapper/base/BaseOrderInfoMapper.xml @@ -137,6 +137,23 @@ order by oi.material_name, order_code desc + + SELECT seq_base_orderinfo.NEXTVAL as objId FROM DUAL @@ -294,4 +311,4 @@ and execution_status = 'RUNNING' - \ No newline at end of file + diff --git a/aucma-dms/src/main/java/com/aucma/dms/controller/DmsMobileController.java b/aucma-dms/src/main/java/com/aucma/dms/controller/DmsMobileController.java index 9236e24..8f21f75 100644 --- a/aucma-dms/src/main/java/com/aucma/dms/controller/DmsMobileController.java +++ b/aucma-dms/src/main/java/com/aucma/dms/controller/DmsMobileController.java @@ -10,8 +10,10 @@ import com.aucma.dms.domain.dto.DmsBaseDeviceLedgerDTO; import com.aucma.dms.domain.vo.DmsBillsFaultInstanceScanVo; import com.aucma.dms.domain.vo.DmsBillsInspectInstanceScanVo; import com.aucma.base.domain.BaseDeviceLedger; +import com.aucma.base.domain.BaseOrderInfo; import com.aucma.base.service.IBaseDeviceLedgerService; import com.aucma.base.domain.BaseDeviceParamVal; +import com.aucma.base.service.IBaseOrderInfoService; import com.aucma.base.service.IBaseDeviceParamValService; import com.aucma.dms.service.*; import org.slf4j.Logger; @@ -75,6 +77,17 @@ public class DmsMobileController extends BaseController { @Autowired private IBaseDeviceParamValService baseDeviceParamValService; + @Autowired + private IBaseOrderInfoService baseOrderInfoService; + + private static final String PARAM_CODE_PDA_CUSTOM = "9999"; + private static final String PARAM_NAME_ACTUAL_RUNTIME = "机台状态-实际生产时间"; + private static final String PARAM_NAME_DAILY_OUTPUT = "机台状态-实际产出数量"; + private static final String ORDER_EXECUTION_PENDING = "PENDING"; + private static final String ORDER_EXECUTION_RUNNING = "RUNNING"; + private static final String ORDER_EXECUTION_PAUSED = "PAUSED"; + private static final String ORDER_EXECUTION_COMPLETED = "COMPLETED"; + // 停机原因服务 @Autowired private IDmsBaseShutReasonService dmsBaseShutReasonService; @@ -105,6 +118,7 @@ public class DmsMobileController extends BaseController { DmsBaseDeviceLedger result = convertToDeviceLedger(list.get(0)); return success(convertDeviceLedgerToDto(result)); } catch (Exception e) { + // 系统异常统一落日志并转业务异常,避免将内部堆栈直接暴露给调用方 log.error("PDA查询设备信息失败 | deviceCode={}, 异常信息: {}", deviceCode, e.getMessage(), e); throw new ServiceException("获取设备信息失败: " + e.getMessage()); } @@ -133,6 +147,7 @@ public class DmsMobileController extends BaseController { } return success(resultList); } catch (Exception e) { + // 系统异常统一落日志并转业务异常,避免将内部堆栈直接暴露给调用方 log.error("PDA模糊查询设备失败 | deviceName={}, 异常信息: {}", deviceName, e.getMessage(), e); throw new ServiceException("查询设备失败: " + e.getMessage()); } @@ -237,6 +252,80 @@ public class DmsMobileController extends BaseController { return dms; } + /** + * PDA-按物料关键字查询关联工单 + */ + @GetMapping("/order/listByMaterial") + public AjaxResult listOrdersByMaterial(String keyword) { + // 物料关键字是PDA唯一输入入口,空值时直接拒绝,避免放大后端模糊查询范围 + if (keyword == null || keyword.trim().isEmpty()) { + throw new ServiceException("物料关键字不能为空"); + } + try { + // Why:先按物料主数据检索,再按物料编码反查订单,能保证PDA搜索口径与主数据一致 + List orders = baseOrderInfoService.selectPdaOrderListByMaterialKeyword(keyword.trim()); + return success(orders); + } catch (Exception e) { + log.error("PDA按物料查询工单失败 | keyword={}, 异常信息: {}", keyword, e.getMessage(), e); + throw new ServiceException("查询工单失败: " + e.getMessage()); + } + } + + /** + * PDA-维护订单状态与数量 + */ + @PostMapping("/order/updateStatusAndQty") + @Transactional(rollbackFor = Exception.class) + public AjaxResult updateOrderStatusAndQty(@RequestBody BaseOrderInfo baseOrderInfo) { + // 请求体为空时没有任何维护目标,直接拦截比进入service后再判空更清晰 + if (baseOrderInfo == null) { + throw new ServiceException("请求体不能为空"); + } + // PDA下拉选中订单后至少应能定位到一条工单,兼容前端传objId或orderCode两种方式 + if (baseOrderInfo.getObjId() == null + && (baseOrderInfo.getOrderCode() == null || baseOrderInfo.getOrderCode().trim().isEmpty())) { + throw new ServiceException("objId和orderCode不能同时为空"); + } + // 状态和数量都在控制器前置校验,避免脏数据落到service或数据库层 + validateOrderExecutionStatus(baseOrderInfo.getExecutionStatus()); + validateOrderQty(baseOrderInfo.getActualCompleteQty(), "完工数量"); + validateOrderQty(baseOrderInfo.getActualDefectQty(), "不良数量"); + + try { + // Why:PDA订单维护属于人工修正场景,统一走service封装,避免控制器直接拼装状态流转细节 + int rows = baseOrderInfoService.updatePdaOrderExecution(baseOrderInfo); + return rows > 0 ? success(rows) : error("工单不存在或更新失败"); + } catch (ServiceException e) { + throw e; + } catch (Exception e) { + log.error("PDA维护订单失败 | objId={}, orderCode={}, executionStatus={}, actualCompleteQty={}, 异常信息: {}", + baseOrderInfo.getObjId(), baseOrderInfo.getOrderCode(), baseOrderInfo.getExecutionStatus(), + baseOrderInfo.getActualCompleteQty(), e.getMessage(), e); + throw new ServiceException("维护订单失败: " + e.getMessage()); + } + } + + private void validateOrderExecutionStatus(String executionStatus) { + // PDA当前仅允许维护执行态,保持与execution.vue和现有执行页状态枚举一致 + if (executionStatus == null || executionStatus.trim().isEmpty()) { + throw new ServiceException("执行状态不能为空"); + } + String normalized = executionStatus.trim(); + if (!ORDER_EXECUTION_PENDING.equals(normalized) + && !ORDER_EXECUTION_RUNNING.equals(normalized) + && !ORDER_EXECUTION_PAUSED.equals(normalized) + && !ORDER_EXECUTION_COMPLETED.equals(normalized)) { + throw new ServiceException("执行状态不合法"); + } + } + + private void validateOrderQty(Long qty, String fieldName) { + // 数量允许为空,表示本次只改状态;但一旦传值就必须满足非负约束 + if (qty != null && qty < 0) { + throw new ServiceException(fieldName + "不能小于0"); + } + } + // ========== PDA 停机相关 ========== @@ -315,6 +404,7 @@ public class DmsMobileController extends BaseController { return success(rows); } catch (Exception e) { + // 系统异常统一落日志并转业务异常,避免将内部堆栈直接暴露给调用方 log.error("PDA新增停机记录失败 | deviceCode={}, shutReason={}, deviceId={}, 异常信息: {}", shutDown.getDeviceCode(), shutDown.getShutReason(), shutDown.getDeviceId(), e.getMessage(), e); throw new ServiceException("新增停机记录失败: " + e.getMessage()); @@ -331,6 +421,7 @@ public class DmsMobileController extends BaseController { List list = dmsRecordShutDownService.selectPdaShutDownList(); return success(list); } catch (Exception e) { + // 系统异常统一落日志并转业务异常,避免将内部堆栈直接暴露给调用方 log.error("PDA获取待处理停机记录失败 | 异常信息: {}", e.getMessage(), e); throw new ServiceException("获取停机记录失败: " + e.getMessage()); } @@ -395,6 +486,7 @@ public class DmsMobileController extends BaseController { return rows > 0 ? success(origin.getRecordShutDownId()) : error("更新停机记录失败"); } catch (Exception e) { + // 系统异常统一落日志并转业务异常,避免将内部堆栈直接暴露给调用方 log.error("PDA完成停机记录失败 | recordShutDownId={}, shutReasonId={}, 异常信息: {}", shutDown.getRecordShutDownId(), shutDown.getShutReasonId(), e.getMessage(), e); throw new ServiceException("完成停机失败: " + e.getMessage()); @@ -429,9 +521,9 @@ public class DmsMobileController extends BaseController { List records = new ArrayList<>(); String runVal = running ? "True" : "False"; String pauseVal = running ? "False" : "True"; - records.add(buildParamVal("9999", deviceCode, resolvedDeviceId, "机台状态-三色灯机器运行", runVal, now)); - records.add(buildParamVal("9999", deviceCode, resolvedDeviceId, "机台状态-三色灯机器暂停", pauseVal, now)); - records.add(buildParamVal("9999", deviceCode, resolvedDeviceId, "机台状态-三色灯机器报警", "False", now)); + records.add(buildParamVal(PARAM_CODE_PDA_CUSTOM, deviceCode, resolvedDeviceId, "机台状态-三色灯机器运行", runVal, now)); + records.add(buildParamVal(PARAM_CODE_PDA_CUSTOM, deviceCode, resolvedDeviceId, "机台状态-三色灯机器暂停", pauseVal, now)); + records.add(buildParamVal(PARAM_CODE_PDA_CUSTOM, deviceCode, resolvedDeviceId, "机台状态-三色灯机器报警", "False", now)); for (BaseDeviceParamVal rec : records) { baseDeviceParamValService.insertBaseDeviceParamVal(rec); @@ -451,6 +543,120 @@ public class DmsMobileController extends BaseController { return v; } + /** + * PDA-选择设备后更新运行时间(写入参数:机台状态-实际生产时间) + */ + @PostMapping("/device/updateRunTime") + @Transactional(rollbackFor = Exception.class) + public AjaxResult updateDeviceRunTime(@RequestBody BaseDeviceParamVal baseDeviceParamVal) { + return updatePdaDeviceParamValue(baseDeviceParamVal, PARAM_NAME_ACTUAL_RUNTIME, false, "运行时间"); + } + + /** + * PDA-选择设备后更新当日产量(写入参数:机台状态-实际产出数量) + */ + @PostMapping("/device/updateDailyOutput") + @Transactional(rollbackFor = Exception.class) + public AjaxResult updateDeviceDailyOutput(@RequestBody BaseDeviceParamVal baseDeviceParamVal) { + return updatePdaDeviceParamValue(baseDeviceParamVal, PARAM_NAME_DAILY_OUTPUT, true, "当日产量"); + } + + private AjaxResult updatePdaDeviceParamValue(BaseDeviceParamVal baseDeviceParamVal, String paramName, + boolean integerRequired, String bizName) { + // 请求体为空时直接拒绝,避免后续空指针并明确返回可读错误 + if (baseDeviceParamVal == null) { + throw new ServiceException("请求体不能为空"); + } + // PDA上报的业务值统一从paramValue读取,后续会按业务类型做数值校验 + String rawValue = baseDeviceParamVal.getParamValue(); + if (rawValue == null || rawValue.trim().isEmpty()) { + throw new ServiceException(bizName + "不能为空"); + } + // 运行时间允许小数;当日产量要求整数,规则由integerRequired控制 + validateNumericParamValue(rawValue.trim(), integerRequired, bizName); + + try { + // 兼容PDA只传deviceId或只传deviceCode的场景,统一补齐设备主数据标识 + fillDeviceIdentity(baseDeviceParamVal); + Date now = new Date(); + // 参数名由后端强制指定,避免前端透传导致写错业务口径 + baseDeviceParamVal.setParamName(paramName); + baseDeviceParamVal.setParamValue(rawValue.trim()); + if (baseDeviceParamVal.getParamCode() == null || baseDeviceParamVal.getParamCode().trim().isEmpty()) { + // Why:该参数值用于PDA手工更新场景,统一标识便于后续数据追溯与运维排查 + baseDeviceParamVal.setParamCode(PARAM_CODE_PDA_CUSTOM); + } + // 采集时间/记录时间均取服务端当前时间,确保同一时区口径一致 + baseDeviceParamVal.setCollectTime(now); + baseDeviceParamVal.setRecordTime(now); + + // 直接插入一条新参数记录,保留完整时间序列,避免覆盖历史值 + int rows = baseDeviceParamValService.upsertTodayParamValue(baseDeviceParamVal); + return rows > 0 ? success(rows) : error("更新" + bizName + "失败"); + } catch (ServiceException e) { + // 业务异常原样抛出,保留明确可读的提示信息 + throw e; + } catch (Exception e) { + // 系统异常统一落日志并转业务异常,避免将内部堆栈直接暴露给调用方 + log.error("PDA更新设备{}失败 | deviceCode={}, deviceId={}, paramName={}, paramValue={}, 异常信息: {}", + bizName, baseDeviceParamVal.getDeviceCode(), baseDeviceParamVal.getDeviceId(), + paramName, baseDeviceParamVal.getParamValue(), e.getMessage(), e); + throw new ServiceException("更新" + bizName + "失败: " + e.getMessage()); + } + } + + private void fillDeviceIdentity(BaseDeviceParamVal baseDeviceParamVal) { + String deviceCode = baseDeviceParamVal.getDeviceCode(); + Long deviceId = baseDeviceParamVal.getDeviceId(); + boolean hasCode = deviceCode != null && !deviceCode.trim().isEmpty(); + + // 未传deviceCode时,必须依赖deviceId反查设备编码;否则无法确定落库维度 + if (!hasCode) { + // 两个标识都没有时无法定位设备,直接拒绝请求 + if (deviceId == null) { + throw new ServiceException("deviceCode和deviceId不能同时为空"); + } + BaseDeviceLedger query = new BaseDeviceLedger(); + query.setObjId(deviceId); + query.setIsFlag(1L); + List list = baseDeviceLedgerService.selectBaseDeviceLedgerList(query); + if (CollectionUtils.isEmpty(list)) { + throw new ServiceException("设备不存在或已停用"); + } + baseDeviceParamVal.setDeviceCode(list.get(0).getDeviceCode()); + return; + } + + // 已传deviceCode时统一去空白,并在缺少deviceId时补齐主键 + baseDeviceParamVal.setDeviceCode(deviceCode.trim()); + if (deviceId == null) { + BaseDeviceLedger query = new BaseDeviceLedger(); + query.setDeviceCode(baseDeviceParamVal.getDeviceCode()); + query.setIsFlag(1L); + List list = baseDeviceLedgerService.selectBaseDeviceLedgerList(query); + if (CollectionUtils.isEmpty(list)) { + throw new ServiceException("设备不存在或已停用"); + } + baseDeviceParamVal.setDeviceId(list.get(0).getObjId()); + } + } + + private void validateNumericParamValue(String value, boolean integerRequired, String bizName) { + try { + BigDecimal decimal = new BigDecimal(value); + // 业务口径下运行时间/产量都不允许负值 + if (decimal.compareTo(BigDecimal.ZERO) < 0) { + throw new ServiceException(bizName + "不能小于0"); + } + // 当日产量必须是整数,避免出现1.2这类无意义产量值 + if (integerRequired && decimal.stripTrailingZeros().scale() > 0) { + throw new ServiceException(bizName + "必须为整数"); + } + } catch (NumberFormatException ex) { + throw new ServiceException(bizName + "必须为数字"); + } + } + /** * PDA-报修 *

@@ -849,3 +1055,5 @@ public class DmsMobileController extends BaseController { return success(dmsBillsLubeInstanceService.completeLube(dmsBillsLubeDetail)); } } + +