feat(pda): 新增PDA设备参数值维护与物料工单查询功能

- 实现upsertTodayParamValue方法支持PDA参数值新增不覆盖当天记录
- 添加BaseMaterialInfo查询关键字字段及PDA物料模糊查询接口
- 新增PDA按物料关键字查询工单列表功能及订单状态数量维护接口
- 在DmsMobileController中增加PDA订单维护、设备运行时间更新等API
- 添加设备参数值的运行时间与当日产量更新功能及数值校验逻辑
- 完善异常处理机制,统一系统异常日志记录与业务异常转换
master
zangch@mesnac.com 3 days ago
parent e1502c59a1
commit 3ec5e2f7bc

@ -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;
}

@ -27,6 +27,14 @@ public interface BaseMaterialinfoMapper
*/
public List<BaseMaterialInfo> selectBaseMaterialInfoList(BaseMaterialInfo baseMaterialInfo);
/**
* PDA
*
* @param keyword
* @return
*/
public List<BaseMaterialInfo> selectPdaMaterialListByKeyword(String keyword);
/**
*
*

@ -36,6 +36,14 @@ public interface BaseOrderInfoMapper {
*/
public List<BaseOrderInfo> selectAllOrderInfoList(BaseOrderInfo baseOrderInfo);
/**
* PDA
*
* @param materialCodes
* @return
*/
public List<BaseOrderInfo> selectPdaOrderListByMaterialCodes(@Param("materialCodes") List<String> materialCodes);
/**
*
*

@ -111,4 +111,12 @@ public interface IBaseDeviceParamValService
* @return SPC
*/
public Map<String, Object> getSPCData(String deviceCode, String paramCode, String startTime, String endTime);
/**
*
*
* @param baseDeviceParamVal
* @return
*/
public int upsertTodayParamValue(BaseDeviceParamVal baseDeviceParamVal);
}

@ -27,6 +27,14 @@ public interface IBaseMaterialInfoService
*/
public List<BaseMaterialInfo> selectBaseMaterialInfoList(BaseMaterialInfo baseMaterialInfo);
/**
* PDA
*
* @param keyword
* @return
*/
public List<BaseMaterialInfo> selectPdaMaterialListByKeyword(String keyword);
/**
*
*

@ -35,6 +35,14 @@ public interface IBaseOrderInfoService {
*/
public List<BaseOrderInfo> selectAllOrderInfoList(BaseOrderInfo baseOrderInfo);
/**
* PDA
*
* @param materialKeyword
* @return
*/
public List<BaseOrderInfo> 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);
}

@ -59,7 +59,8 @@ public class BaseDeviceParamValServiceImpl implements IBaseDeviceParamValService
public List<BaseDeviceParamVal> 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<BaseDeviceParamVal> selectTraceList(String deviceCode, String paramCode, String startTime, String endTime) {
try {
Map<String, Object> params = new HashMap<>();
// mapper层使用Map入参便于只携带本次upsert所需字段
Map<String, Object> 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<String, Object> params = new HashMap<>();
// mapper层使用Map入参便于只携带本次upsert所需字段
Map<String, Object> 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);
}
// WhyPDA上报需要沉淀完整过程数据后续分析依赖时间序列不能覆盖当天已有记录
return baseDeviceParamValMapper.insertBaseDeviceParamVal(baseDeviceParamVal);
}
private double calculateMean(List<Double> 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);
}
}

@ -46,6 +46,15 @@ public class BaseMaterialInfoServiceImpl implements IBaseMaterialInfoService
return baseMaterialInfoMapper.selectBaseMaterialInfoList(baseMaterialInfo);
}
/**
* PDA
*/
@Override
public List<BaseMaterialInfo> selectPdaMaterialListByKeyword(String keyword)
{
return baseMaterialInfoMapper.selectPdaMaterialListByKeyword(keyword);
}
/**
*
*

@ -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<BaseOrderInfo> selectPdaOrderListByMaterialKeyword(String materialKeyword) {
// 第一步先在物料主数据中做模糊匹配保证PDA搜索结果受主数据启停与编码口径约束
List<BaseMaterialInfo> materials = baseMaterialInfoService.selectPdaMaterialListByKeyword(materialKeyword);
if (materials == null || materials.isEmpty()) {
return new ArrayList<>();
}
Set<String> 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())) {
// WhyPDA显式将工单维护为已完成时需要同步完工时间与完成日期保证执行页和报表口径一致
order.setFinishTime(now);
order.setCompleteDate(now);
}
return baseOrderInfoMapper.updateBaseOrderInfo(order);
}
}

@ -84,6 +84,25 @@
where ml.obj_id = #{objId}
</select>
<select id="selectPdaMaterialListByKeyword" parameterType="String" resultMap="BaseMaterialInfoResult">
SELECT *
FROM (
<include refid="selectBaseMaterialInfoVo"/>
<!-- 只返回启用物料避免PDA选择到已停用主数据 -->
WHERE ml.is_flag = 1
AND (
<!-- 编码走不区分大小写匹配兼容PDA扫码枪/人工输入大小写不统一 -->
UPPER(ml.material_code) LIKE '%' || UPPER(#{keyword}) || '%'
<!-- 名称维持原始中文模糊匹配,兼容现场按商品名/型号搜索 -->
OR ml.material_name LIKE '%' || #{keyword} || '%'
)
<!-- 最近同步的物料优先展示,减少现场误选旧版本主数据的概率 -->
ORDER BY ml.increment_date DESC, ml.material_code
)
<!-- PDA下拉列表做上限保护防止一次检出过多数据影响手持端体验 -->
WHERE ROWNUM &lt;= 30
</select>
<insert id="insertBaseMaterialInfo" parameterType="BaseMaterialInfo">
<selectKey keyProperty="objId" resultType="long" order="BEFORE">
SELECT seq_base_materialinfo.NEXTVAL as objId FROM DUAL
@ -171,4 +190,4 @@
#{objId}
</foreach>
</delete>
</mapper>
</mapper>

@ -137,6 +137,23 @@
order by oi.material_name, order_code desc
</select>
<select id="selectPdaOrderListByMaterialCodes" resultMap="BaseOrderInfoResult">
<include refid="selectBaseOrderInfoVo"/>
<where>
<!-- PDA只维护有效工单避免停用/脏数据进入下拉框 -->
AND NVL(oi.is_flag, 1) = 1
<if test="materialCodes != null and materialCodes.size() > 0">
<!-- 这里按物料编码精确匹配,模糊搜索已在物料主数据层完成 -->
AND oi.material_code IN
<foreach collection="materialCodes" item="materialCode" open="(" separator="," close=")">
#{materialCode}
</foreach>
</if>
</where>
<!-- 先按物料名称再按工单倒序便于PDA下拉框更快定位最近工单 -->
order by oi.material_name, oi.order_code desc
</select>
<insert id="insertBaseOrderInfo" parameterType="BaseOrderInfo">
<selectKey keyProperty="objId" resultType="long" order="BEFORE">
SELECT seq_base_orderinfo.NEXTVAL as objId FROM DUAL
@ -294,4 +311,4 @@
</foreach>
and execution_status = 'RUNNING'
</update>
</mapper>
</mapper>

@ -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<BaseOrderInfo> 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 {
// WhyPDA订单维护属于人工修正场景统一走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<DmsRecordShutDown> 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<BaseDeviceParamVal> 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<BaseDeviceLedger> 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<BaseDeviceLedger> 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-
* <p>
@ -849,3 +1055,5 @@ public class DmsMobileController extends BaseController {
return success(dmsBillsLubeInstanceService.completeLube(dmsBillsLubeDetail));
}
}

Loading…
Cancel
Save