Parcourir la source

新增硬度检测菜单,新增剩余批次码预警功能

dkk il y a 3 mois
Parent
commit
f669229258
20 fichiers modifiés avec 1060 ajouts et 72 suppressions
  1. 14 0
      src/main/java/com/jeesite/modules/mes/dao/MesHardnessConfigDao.java
  2. 14 0
      src/main/java/com/jeesite/modules/mes/dao/MesHardnessDao.java
  3. 9 0
      src/main/java/com/jeesite/modules/mes/dao/MesMoldMaintenancePlanDao.java
  4. 181 0
      src/main/java/com/jeesite/modules/mes/entity/MesHardness.java
  5. 51 0
      src/main/java/com/jeesite/modules/mes/entity/MesHardnessConfig.java
  6. 12 1
      src/main/java/com/jeesite/modules/mes/entity/MesMoldMaintenancePlan.java
  7. 2 1
      src/main/java/com/jeesite/modules/mes/entity/MesQuality.java
  8. 45 0
      src/main/java/com/jeesite/modules/mes/service/MesHardnessConfigService.java
  9. 113 0
      src/main/java/com/jeesite/modules/mes/service/MesHardnessService.java
  10. 69 58
      src/main/java/com/jeesite/modules/mes/service/MesMoldMaintenancePlanService.java
  11. 1 1
      src/main/java/com/jeesite/modules/mes/service/MesQualityService.java
  12. 136 0
      src/main/java/com/jeesite/modules/mes/web/MesHardnessController.java
  13. 135 0
      src/main/java/com/jeesite/modules/scheduling/MesMoldMaintenancePlanScheduling.java
  14. 5 0
      src/main/resources/mappings/modules/mes/MesHardnessConfigDao.xml
  15. 5 0
      src/main/resources/mappings/modules/mes/MesHardnessDao.xml
  16. 8 9
      src/main/resources/mappings/modules/mes/MesMoldMaintenancePlanDao.xml
  17. 80 0
      src/main/resources/views/modules/mes/mesHardnessInfo.html
  18. 178 0
      src/main/resources/views/modules/mes/mesHardnessList.html
  19. 1 1
      src/main/resources/views/modules/mes/mesMoldMaintenancePlanList.html
  20. 1 1
      src/test/java/com/jeesite/test/TencentSmsUtilTest.java

+ 14 - 0
src/main/java/com/jeesite/modules/mes/dao/MesHardnessConfigDao.java

@@ -0,0 +1,14 @@
+package com.jeesite.modules.mes.dao;
+
+import com.jeesite.common.dao.CrudDao;
+import com.jeesite.common.mybatis.annotation.MyBatisDao;
+import com.jeesite.modules.mes.entity.MesHardnessConfig;
+
+/**
+ * 硬度检测标准值配置DAO接口
+ * @author hzd
+ * @version 2026-07-14
+ */
+@MyBatisDao
+public interface MesHardnessConfigDao extends CrudDao<MesHardnessConfig> {
+}

+ 14 - 0
src/main/java/com/jeesite/modules/mes/dao/MesHardnessDao.java

@@ -0,0 +1,14 @@
+package com.jeesite.modules.mes.dao;
+
+import com.jeesite.common.dao.CrudDao;
+import com.jeesite.common.mybatis.annotation.MyBatisDao;
+import com.jeesite.modules.mes.entity.MesHardness;
+
+/**
+ * 硬度检测DAO接口
+ * @author hzd
+ * @version 2026-07-14
+ */
+@MyBatisDao
+public interface MesHardnessDao extends CrudDao<MesHardness> {
+}

+ 9 - 0
src/main/java/com/jeesite/modules/mes/dao/MesMoldMaintenancePlanDao.java

@@ -12,4 +12,13 @@ import com.jeesite.modules.mes.entity.MesMoldMaintenancePlan;
 @MyBatisDao
 public interface MesMoldMaintenancePlanDao extends CrudDao<MesMoldMaintenancePlan> {
 
+	/**
+	 * 更新剩余批次生产数和批次完成数
+	 * @param id 维保计划ID
+	 * @param remainBatchProductionNum 计算后的剩余批次生产数
+	 * @param batchCompleteNum 批次完成数
+	 */
+	void updateRemainBatchProductionNum(@org.apache.ibatis.annotations.Param("id") String id,
+			@org.apache.ibatis.annotations.Param("remainBatchProductionNum") Integer remainBatchProductionNum,
+			@org.apache.ibatis.annotations.Param("batchCompleteNum") Integer batchCompleteNum);
 }

+ 181 - 0
src/main/java/com/jeesite/modules/mes/entity/MesHardness.java

@@ -0,0 +1,181 @@
+package com.jeesite.modules.mes.entity;
+
+import com.jeesite.common.entity.DataEntity;
+import com.jeesite.common.mybatis.annotation.Column;
+import com.jeesite.common.mybatis.annotation.Table;
+import com.jeesite.common.mybatis.mapper.query.QueryType;
+import com.jeesite.common.utils.excel.annotation.ExcelField;
+import com.jeesite.common.utils.excel.annotation.ExcelFields;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.util.Date;
+
+/**
+ * 硬度检测Entity
+ * @author hzd
+ * @version 2026-07-14
+ */
+@Table(name="mes_hardness", alias="a", label="硬度检测", columns={
+		@Column(name="id", attrName="id", label="id", isPK=true),
+		@Column(name="sn", attrName="sn", label="工件码"),
+		@Column(name="standard_value", attrName="standardValue", label="标准值"),
+		@Column(name="actual_value1", attrName="actualValue1", label="实测值1"),
+		@Column(name="actual_value2", attrName="actualValue2", label="实测值2"),
+		@Column(name="actual_value3", attrName="actualValue3", label="实测值3"),
+		@Column(name="avg_value", attrName="avgValue", label="平均值"),
+		@Column(name="result", attrName="result", label="判定结果"),
+		@Column(name="type", attrName="type", label="类型"),
+		@Column(name="unit", attrName="unit", label="测量单位"),
+		@Column(name="measure_date", attrName="measureDate", label="测量日期"),
+		@Column(name="remark", attrName="remark", label="备注"),
+		@Column(name="create_by", attrName="createBy", label="创建人", isUpdate=false, isQuery=false),
+		@Column(name="create_date", attrName="createDate", label="创建日期", isUpdate=false, isQuery=false, isUpdateForce=true),
+		@Column(name="update_by", attrName="updateBy", label="更新人", isQuery=false),
+		@Column(name="update_date", attrName="updateDate", label="更新日期", isUpdateForce=true),
+	}, orderBy="a.measure_date DESC"
+)
+public class MesHardness extends DataEntity<MesHardness> {
+
+	private static final long serialVersionUID = 1L;
+	private String sn;			// 工件码
+	private String standardValue;	// 标准值
+	private Integer actualValue1;	// 实测值1
+	private Integer actualValue2;	// 实测值2
+	private Integer actualValue3;	// 实测值3
+	private Integer avgValue;		// 平均值
+	private String result;			// 判定结果
+	private String type;			// 类型
+	private String unit;			// 测量单位
+	private Date measureDate;		// 测量日期
+	private String remark;			// 备注
+
+	@ExcelFields({
+			@ExcelField(title="工件码", attrName="sn", align= ExcelField.Align.CENTER, sort=10),
+			@ExcelField(title="标准值", attrName="standardValue", align= ExcelField.Align.CENTER, sort=20),
+			@ExcelField(title="实测值1", attrName="actualValue1", align= ExcelField.Align.CENTER, sort=30),
+			@ExcelField(title="实测值2", attrName="actualValue2", align= ExcelField.Align.CENTER, sort=40),
+			@ExcelField(title="实测值3", attrName="actualValue3", align= ExcelField.Align.CENTER, sort=50),
+			@ExcelField(title="平均值", attrName="avgValue", align= ExcelField.Align.CENTER, sort=60),
+			@ExcelField(title="判定结果", attrName="result", align= ExcelField.Align.CENTER, sort=70),
+			@ExcelField(title="类型", attrName="type", align= ExcelField.Align.CENTER, sort=80),
+			@ExcelField(title="测量单位", attrName="unit", align= ExcelField.Align.CENTER, sort=90),
+			@ExcelField(title="备注", attrName="remark", align= ExcelField.Align.CENTER, sort=100),
+			@ExcelField(title="测量日期", attrName="measureDate", align= ExcelField.Align.CENTER, sort=110, dataFormat="yyyy-MM-dd"),
+	})
+
+	public MesHardness() {
+		this(null);
+	}
+
+	public MesHardness(String id){
+		super(id);
+	}
+
+	@NotBlank(message="工件码不能为空")
+	@Size(min=0, max=100, message="工件码长度不能超过 100 个字符")
+	public String getSn() {
+		return sn;
+	}
+
+	public void setSn(String sn) {
+		this.sn = sn;
+	}
+
+	public String getStandardValue() {
+		return standardValue;
+	}
+
+	public void setStandardValue(String standardValue) {
+		this.standardValue = standardValue;
+	}
+
+	public Integer getActualValue1() {
+		return actualValue1;
+	}
+
+	public void setActualValue1(Integer actualValue1) {
+		this.actualValue1 = actualValue1;
+	}
+
+	public Integer getActualValue2() {
+		return actualValue2;
+	}
+
+	public void setActualValue2(Integer actualValue2) {
+		this.actualValue2 = actualValue2;
+	}
+
+	public Integer getActualValue3() {
+		return actualValue3;
+	}
+
+	public void setActualValue3(Integer actualValue3) {
+		this.actualValue3 = actualValue3;
+	}
+
+	public Integer getAvgValue() {
+		return avgValue;
+	}
+
+	public void setAvgValue(Integer avgValue) {
+		this.avgValue = avgValue;
+	}
+
+	public String getResult() {
+		return result;
+	}
+
+	public void setResult(String result) {
+		this.result = result;
+	}
+
+	public String getType() {
+		return type;
+	}
+
+	public void setType(String type) {
+		this.type = type;
+	}
+
+	public String getUnit() {
+		return unit;
+	}
+
+	public void setUnit(String unit) {
+		this.unit = unit;
+	}
+
+	public Date getMeasureDate() {
+		return measureDate;
+	}
+
+	public void setMeasureDate(Date measureDate) {
+		this.measureDate = measureDate;
+	}
+
+	@Size(min=0, max=255, message="备注长度不能超过 255 个字符")
+	public String getRemark() {
+		return remark;
+	}
+
+	public void setRemark(String remark) {
+		this.remark = remark;
+	}
+
+	public Date getMeasureDate_gte() {
+		return sqlMap.getWhere().getValue("measure_date", QueryType.GTE);
+	}
+
+	public void setMeasureDate_gte(Date measureDate) {
+		sqlMap.getWhere().and("measure_date", QueryType.GTE, measureDate);
+	}
+
+	public Date getMeasureDate_lte() {
+		return sqlMap.getWhere().getValue("measure_date", QueryType.LTE);
+	}
+
+	public void setMeasureDate_lte(Date measureDate) {
+		sqlMap.getWhere().and("measure_date", QueryType.LTE, measureDate);
+	}
+}

+ 51 - 0
src/main/java/com/jeesite/modules/mes/entity/MesHardnessConfig.java

@@ -0,0 +1,51 @@
+package com.jeesite.modules.mes.entity;
+
+import com.jeesite.common.entity.DataEntity;
+import com.jeesite.common.mybatis.annotation.Column;
+import com.jeesite.common.mybatis.annotation.Table;
+
+/**
+ * 硬度检测标准值配置Entity
+ * @author hzd
+ * @version 2026-07-14
+ */
+@Table(name="mes_hardness_config", alias="a", label="硬度检测标准值配置", columns={
+		@Column(name="id", attrName="id", label="id", isPK=true),
+		@Column(name="standard_op", attrName="standardOp", label="比较运算符"),
+		@Column(name="standard_value", attrName="standardValue", label="标准值"),
+		@Column(name="create_by", attrName="createBy", label="创建人", isUpdate=false, isQuery=false),
+		@Column(name="create_date", attrName="createDate", label="创建日期", isUpdate=false, isQuery=false, isUpdateForce=true),
+		@Column(name="update_by", attrName="updateBy", label="更新人", isQuery=false),
+		@Column(name="update_date", attrName="updateDate", label="更新日期", isUpdateForce=true),
+	}, orderBy="a.update_date DESC"
+)
+public class MesHardnessConfig extends DataEntity<MesHardnessConfig> {
+
+	private static final long serialVersionUID = 1L;
+	private String standardOp;		// 比较运算符(>, <, =)
+	private String standardValue;		// 标准值数值
+
+	public MesHardnessConfig() {
+		this(null);
+	}
+
+	public MesHardnessConfig(String id){
+		super(id);
+	}
+
+	public String getStandardOp() {
+		return standardOp;
+	}
+
+	public void setStandardOp(String standardOp) {
+		this.standardOp = standardOp;
+	}
+
+	public String getStandardValue() {
+		return standardValue;
+	}
+
+	public void setStandardValue(String standardValue) {
+		this.standardValue = standardValue;
+	}
+}

+ 12 - 1
src/main/java/com/jeesite/modules/mes/entity/MesMoldMaintenancePlan.java

@@ -26,6 +26,8 @@ import com.jeesite.common.mybatis.annotation.Table;
 		@Column(name="last_maintenance_content", attrName="lastMaintenanceContent", label="上次维保内容"),
 		@Column(name="remain_batch_production_num", attrName="remainBatchProductionNum", label="剩余批次生产数"),
 		@Column(name="last_maintenance_finish_qty", attrName="lastMaintenanceFinishQty", label="上次保养生产完成数"),
+		@Column(name="initial_batch_production_num", attrName="initialBatchProductionNum", label="初始批次生产数"),
+		@Column(name="batch_complete_num", attrName="batchCompleteNum", label="批次完成数"),
 		@Column(name="last_maintenance_date", attrName="lastMaintenanceDate", label="上次维保时间", isQuery=true),
 		@Column(name="create_by", attrName="createBy", label="create_by", isUpdate=false, isQuery=false),
 		@Column(name="create_date", attrName="createDate", label="create_date", isUpdate=false, isQuery=false, isUpdateForce=true),
@@ -47,7 +49,8 @@ public class MesMoldMaintenancePlan extends DataEntity<MesMoldMaintenancePlan> {
 	private String lastMaintenanceContent;	// 上次维保内容
 	private Integer remainBatchProductionNum;	// 剩余批次生产数
 	private Integer lastMaintenanceFinishQty;	// 上次保养生产完成数
-	private Integer batchCompleteNum;		// 批次完成数(实时计算,不持久化)
+	private Integer initialBatchProductionNum;	// 初始批次生产数
+	private Integer batchCompleteNum;		// 批次完成数
 	private Date lastMaintenanceDate;		// 上次维保时间
 
 	public MesMoldMaintenancePlan() {
@@ -152,6 +155,14 @@ public class MesMoldMaintenancePlan extends DataEntity<MesMoldMaintenancePlan> {
 		this.lastMaintenanceFinishQty = lastMaintenanceFinishQty;
 	}
 
+	public Integer getInitialBatchProductionNum() {
+		return initialBatchProductionNum;
+	}
+
+	public void setInitialBatchProductionNum(Integer initialBatchProductionNum) {
+		this.initialBatchProductionNum = initialBatchProductionNum;
+	}
+
 	public Integer getBatchCompleteNum() {
 		return batchCompleteNum;
 	}

+ 2 - 1
src/main/java/com/jeesite/modules/mes/entity/MesQuality.java

@@ -24,6 +24,7 @@ import java.util.List;
 		@Column(name="oprno", attrName="oprno", label="工位"),
 		@Column(name="type", attrName="type", label="类型"),
 		@Column(name="result", attrName="result", label="检查结果"),
+		@Column(name="standard_value", attrName="standardValue", label="标准值"),
 		@Column(name="line_sn", attrName="lineSn", label="产线编号"),
 		@Column(name="items", attrName="items", label="问题清单"),
 		@Column(name="remark", attrName="remark", label="备注"),
@@ -105,7 +106,7 @@ public class MesQuality extends DataEntity<MesQuality> {
 	public void setResult(String result) {
 		this.result = result;
 	}
-	
+
 	@Size(min=0, max=3000, message="问题清单长度不能超过 3000 个字符")
 	public String getItems() {
 		return items;

+ 45 - 0
src/main/java/com/jeesite/modules/mes/service/MesHardnessConfigService.java

@@ -0,0 +1,45 @@
+package com.jeesite.modules.mes.service;
+
+import com.jeesite.common.service.CrudService;
+import com.jeesite.modules.mes.dao.MesHardnessConfigDao;
+import com.jeesite.modules.mes.entity.MesHardnessConfig;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+/**
+ * 硬度检测标准值配置Service
+ * @author hzd
+ * @version 2026-07-14
+ */
+@Service
+public class MesHardnessConfigService extends CrudService<MesHardnessConfigDao, MesHardnessConfig> {
+
+	/**
+	 * 获取当前配置(只保留一条记录)
+	 */
+	public MesHardnessConfig getConfig() {
+		List<MesHardnessConfig> list = findList(new MesHardnessConfig());
+		if (list != null && !list.isEmpty()) {
+			return list.get(0);
+		}
+		return null;
+	}
+
+	/**
+	 * 保存配置(存在则更新,不存在则新增)
+	 */
+	@Override
+	@Transactional
+	public void save(MesHardnessConfig config) {
+		MesHardnessConfig existing = getConfig();
+		if (existing != null) {
+			config.setId(existing.getId());
+			config.setIsNewRecord(false);
+		} else {
+			config.setIsNewRecord(true);
+		}
+		super.save(config);
+	}
+}

+ 113 - 0
src/main/java/com/jeesite/modules/mes/service/MesHardnessService.java

@@ -0,0 +1,113 @@
+package com.jeesite.modules.mes.service;
+
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.lang.StringUtils;
+import com.jeesite.common.service.CrudService;
+import com.jeesite.modules.mes.dao.MesHardnessDao;
+import com.jeesite.modules.mes.entity.MesHardness;
+import com.jeesite.modules.mes.entity.MesHardnessConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 硬度检测Service
+ * @author hzd
+ * @version 2026-07-14
+ */
+@Service
+public class MesHardnessService extends CrudService<MesHardnessDao, MesHardness> {
+
+	@Autowired
+	private MesHardnessConfigService mesHardnessConfigService;
+
+	@Override
+	public MesHardness get(MesHardness mesHardness) {
+		return super.get(mesHardness);
+	}
+
+	@Override
+	public Page<MesHardness> findPage(MesHardness mesHardness) {
+		return super.findPage(mesHardness);
+	}
+
+	@Override
+	public List<MesHardness> findList(MesHardness mesHardness) {
+		return super.findList(mesHardness);
+	}
+
+	/**
+	 * PDA端新增硬度检测记录
+	 * @param sn       工件码(必填)
+	 * @param value1   实测值1(必填)
+	 * @param value2   实测值2(必填)
+	 * @param value3   实测值3(必填)
+	 * @param remark   备注(可空)
+	 */
+	@Transactional
+	public MesHardness saveFromPda(String sn, Integer value1, Integer value2, Integer value3, String remark) {
+		// 获取标准值配置
+		MesHardnessConfig config = mesHardnessConfigService.getConfig();
+		String standardOp = config != null ? config.getStandardOp() : null;
+		String standardNum = config != null ? config.getStandardValue() : null;
+
+		// 组合标准值字符串,如 ">50"
+		String standardValueStr = "";
+		double standardNumVal = 0;
+		if (StringUtils.isNotEmpty(standardOp) && StringUtils.isNotEmpty(standardNum)) {
+			standardValueStr = standardOp + standardNum;
+			standardNumVal = Double.parseDouble(standardNum);
+		}
+
+		// 计算平均值(取整)
+		int avg = Math.round((value1 + value2 + value3) / 3.0f);
+
+		// 逐一比较三个实测值与标准值,全部合格才为"合格"
+		boolean allPass = true;
+		if (StringUtils.isNotEmpty(standardOp) && standardNumVal > 0) {
+			allPass = isPass(value1, standardOp, standardNumVal)
+					&& isPass(value2, standardOp, standardNumVal)
+					&& isPass(value3, standardOp, standardNumVal);
+		}
+		String result = allPass ? "合格" : "不合格";
+
+		// 构建实体
+		MesHardness hardness = new MesHardness();
+		hardness.setIsNewRecord(true);
+		hardness.setSn(sn);
+		hardness.setStandardValue(standardValueStr);
+		hardness.setActualValue1(value1);
+		hardness.setActualValue2(value2);
+		hardness.setActualValue3(value3);
+		hardness.setAvgValue(avg);
+		hardness.setResult(result);
+		hardness.setType("T5后硬度检测");
+		hardness.setUnit("HB");
+		hardness.setMeasureDate(new Date());
+		hardness.setRemark(remark);
+
+		super.save(hardness);
+		return hardness;
+	}
+
+	/**
+	 * 判断单个实测值是否合格
+	 */
+	private boolean isPass(int actual, String op, double standard) {
+		switch (op) {
+			case ">": return actual > standard;
+			case "<": return actual < standard;
+			case "=": return actual == standard;
+			default:  return false;
+		}
+	}
+
+	@Override
+	@Transactional
+	public void delete(MesHardness mesHardness) {
+		super.delete(mesHardness);
+	}
+}

+ 69 - 58
src/main/java/com/jeesite/modules/mes/service/MesMoldMaintenancePlanService.java

@@ -1,10 +1,9 @@
 package com.jeesite.modules.mes.service;
 
-import java.util.ArrayList;
 import java.util.Date;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -15,6 +14,7 @@ import com.jeesite.modules.mes.entity.MesMoldMaintenanceItem;
 import com.jeesite.modules.mes.entity.MesMoldMaintenancePlan;
 import com.jeesite.modules.mes.dao.MesMoldMaintenanceItemDao;
 import com.jeesite.modules.mes.dao.MesMoldMaintenancePlanDao;
+import com.jeesite.modules.mes.util.TencentSmsUtil;
 import com.jeesite.modules.tianHong.dao.RptProductionSummaryDao;
 
 /**
@@ -25,6 +25,14 @@ import com.jeesite.modules.tianHong.dao.RptProductionSummaryDao;
 @Service
 public class MesMoldMaintenancePlanService extends CrudService<MesMoldMaintenancePlanDao, MesMoldMaintenancePlan> {
 
+	private static final Logger log = LoggerFactory.getLogger(MesMoldMaintenancePlanService.class);
+
+	// TODO: 请在此处填写接收预警短信的手机号(多个用英文逗号分隔)
+	private static final String SMS_PHONE_NUMBERS = "17203891886";
+
+	// 短信发送开关:true允许发送,发送成功后置为false,执行保养后置为true
+	private static volatile boolean smsAlertEnabled = true;
+
 	@Autowired
 	private MesMoldMaintenanceItemDao mesMoldMaintenanceItemDao;
 
@@ -41,63 +49,11 @@ public class MesMoldMaintenancePlanService extends CrudService<MesMoldMaintenanc
 
 	/**
 	 * 查询分页数据
-	 * 查询后实时计算批次完成数和剩余批次生产数:
-	 * 批次完成数 = bzh库FinishQTY - 上次保养生产完成数
-	 * 剩余批次生产数 = 数据库中的remainBatchProductionNum - 批次完成数
+	 * 剩余批次生产数由定时任务实时计算并更新到数据库,此处直接返回数据库值
 	 */
 	@Override
 	public Page<MesMoldMaintenancePlan> findPage(MesMoldMaintenancePlan mesMoldMaintenancePlan) {
-		Page<MesMoldMaintenancePlan> page = super.findPage(mesMoldMaintenancePlan);
-		List<MesMoldMaintenancePlan> list = page.getList();
-		if (list != null && !list.isEmpty()) {
-			// 收集所有非空moldCode
-			List<String> moldCodes = new ArrayList<>();
-			for (MesMoldMaintenancePlan plan : list) {
-				if (plan.getMoldCode() != null && !plan.getMoldCode().isEmpty()) {
-					moldCodes.add(plan.getMoldCode());
-				}
-			}
-			// 批量查询bzh库FinishQTY
-			Map<String, Integer> finishQtyMap = new HashMap<>();
-			if (!moldCodes.isEmpty()) {
-				List<Map<String, Object>> finishQtyList = rptProductionSummaryDao.getFinishQtyByEqpIds(moldCodes);
-				if (finishQtyList != null) {
-					for (Map<String, Object> item : finishQtyList) {
-						String eqpId = (String) item.get("eqpId");
-						Integer finishQty = ((Number) item.get("finishQty")).intValue();
-						finishQtyMap.put(eqpId, finishQty);
-					}
-				}
-			}
-			// 计算批次完成数和剩余批次生产数
-			for (MesMoldMaintenancePlan plan : list) {
-				Integer finishQty = finishQtyMap.get(plan.getMoldCode());
-				if (finishQty == null) {
-					finishQty = 0;
-				}
-				Integer lastFinishQty = plan.getLastMaintenanceFinishQty();
-				if (lastFinishQty == null) {
-					lastFinishQty = 0;
-				}
-				// 批次完成数 = FinishQTY - 上次保养生产完成数
-				int batchCompleteNum = finishQty - lastFinishQty;
-				if (batchCompleteNum < 0) {
-					batchCompleteNum = 0;
-				}
-				plan.setBatchCompleteNum(batchCompleteNum);
-				// 剩余批次生产数 = 当前值 - 批次完成数
-				Integer remainNum = plan.getRemainBatchProductionNum();
-				if (remainNum == null) {
-					remainNum = 0;
-				}
-				int displayRemainNum = remainNum - batchCompleteNum;
-				if (displayRemainNum < 0) {
-					displayRemainNum = 0;
-				}
-				plan.setRemainBatchProductionNum(displayRemainNum);
-			}
-		}
-		return page;
+		return super.findPage(mesMoldMaintenancePlan);
 	}
 
 	/**
@@ -128,7 +84,7 @@ public class MesMoldMaintenancePlanService extends CrudService<MesMoldMaintenanc
 
 	/**
 	 * 执行保养:根据选中的保养项更新维保计划
-	 * 保养时记录bzh库当前FinishQTY作为上次保养生产完成数
+	 * 保养时记录bzh库当前FinishQTY作为上次保养生产完成数,保存初始批次生产数,并重新开启短信预警
 	 */
 	@Transactional
 	public void doUpkeep(MesMoldMaintenancePlan mesMoldMaintenancePlan, String itemId) {
@@ -138,6 +94,8 @@ public class MesMoldMaintenancePlanService extends CrudService<MesMoldMaintenanc
 		if (item != null) {
 			mesMoldMaintenancePlan.setMaintenanceItemId(item.getId());
 			mesMoldMaintenancePlan.setRemainBatchProductionNum(item.getBatchProductionNum());
+			// 保存初始批次生产数,供定时任务计算使用
+			mesMoldMaintenancePlan.setInitialBatchProductionNum(item.getBatchProductionNum());
 			mesMoldMaintenancePlan.setLastMaintenanceLevel(item.getMaintenanceLevel());
 			mesMoldMaintenancePlan.setLastMaintenanceContent(item.getMaintenanceContent());
 			mesMoldMaintenancePlan.setLastMaintenanceDate(new Date());
@@ -149,6 +107,59 @@ public class MesMoldMaintenancePlanService extends CrudService<MesMoldMaintenanc
 			mesMoldMaintenancePlan.setLastMaintenanceFinishQty(finishQty);
 		}
 		super.save(mesMoldMaintenancePlan);
+		// 执行保养后重新开启短信预警
+		smsAlertEnabled = true;
+	}
+
+	/**
+	 * 更新剩余批次生产数和批次完成数(供定时任务调用)
+	 * 使用 REQUIRES_NEW 传播:每次更新在独立新事务中提交,避免加入外层只读事务导致长事务持锁
+	 * @param id 维保计划ID
+	 * @param remainBatchProductionNum 计算后的剩余批次生产数
+	 * @param batchCompleteNum 批次完成数
+	 */
+	@Transactional(propagation = org.springframework.transaction.annotation.Propagation.REQUIRES_NEW)
+	public void updateRemainBatchProductionNum(String id, Integer remainBatchProductionNum, Integer batchCompleteNum) {
+		dao.updateRemainBatchProductionNum(id, remainBatchProductionNum, batchCompleteNum);
+	}
+
+	/**
+	 * 发送剩余批次生产数预警短信
+	 * 仅当 smsAlertEnabled 为 true 时才发送,发送成功后置为 false
+	 * 短信发送失败不影响业务,仅记录日志
+	 */
+	public void sendBatchAlertSms(MesMoldMaintenancePlan plan) {
+		if (!smsAlertEnabled) {
+			log.debug("短信预警已关闭,跳过发送。模具号={}, 剩余批次生产数={}",
+					plan.getMoldCode(), plan.getRemainBatchProductionNum());
+			return;
+		}
+		if (SMS_PHONE_NUMBERS == null || SMS_PHONE_NUMBERS.trim().isEmpty()) {
+			log.warn("未配置预警短信接收手机号,跳过发送。模具号={}, 剩余批次生产数={}",
+					plan.getMoldCode(), plan.getRemainBatchProductionNum());
+			return;
+		}
+		String[] phones = SMS_PHONE_NUMBERS.split(",");
+		boolean allSuccess = true;
+		for (String phone : phones) {
+			String trimmedPhone = phone.trim();
+			if (trimmedPhone.isEmpty()) {
+				continue;
+			}
+			try {
+				TencentSmsUtil.sendSms(trimmedPhone);
+				log.info("模具维保预警短信发送成功: 模具号={}, 剩余批次生产数={}, 手机号={}",
+						plan.getMoldCode(), plan.getRemainBatchProductionNum(), trimmedPhone);
+			} catch (Exception e) {
+				allSuccess = false;
+				log.error("模具维保预警短信发送失败: 模具号={}, 剩余批次生产数={}, 手机号={}",
+						plan.getMoldCode(), plan.getRemainBatchProductionNum(), trimmedPhone, e);
+			}
+		}
+		// 所有短信发送成功后,关闭短信预警,直到下次保养才重新开启
+		if (allSuccess) {
+			smsAlertEnabled = false;
+		}
 	}
 
 }

+ 1 - 1
src/main/java/com/jeesite/modules/mes/service/MesQualityService.java

@@ -17,7 +17,7 @@ import java.util.List;
  */
 @Service
 public class MesQualityService extends CrudService<MesQualityDao, MesQuality> {
-	
+
 	/**
 	 * 获取单条数据
 	 * @param mesQuality

+ 136 - 0
src/main/java/com/jeesite/modules/mes/web/MesHardnessController.java

@@ -0,0 +1,136 @@
+package com.jeesite.modules.mes.web;
+
+import com.jeesite.common.config.Global;
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.lang.DateUtils;
+import com.jeesite.common.utils.excel.ExcelExport;
+import com.jeesite.common.web.BaseController;
+import com.jeesite.modules.mes.entity.MesHardness;
+import com.jeesite.modules.mes.entity.MesHardnessConfig;
+import com.jeesite.modules.mes.service.MesHardnessConfigService;
+import com.jeesite.modules.mes.service.MesHardnessService;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 硬度检测Controller
+ * @author hzd
+ * @version 2026-07-14
+ */
+@Controller
+@RequestMapping(value = "${adminPath}/mes/mesHardness")
+public class MesHardnessController extends BaseController {
+
+	@Autowired
+	private MesHardnessService mesHardnessService;
+
+	@Autowired
+	private MesHardnessConfigService mesHardnessConfigService;
+
+	@ModelAttribute
+	public MesHardness get(String id, boolean isNewRecord) {
+		return mesHardnessService.get(id, isNewRecord);
+	}
+
+	/**
+	 * 查询列表页面
+	 */
+	@RequiresPermissions("mes:mesHardness:view")
+	@RequestMapping(value = {"list", ""})
+	public String list(MesHardness mesHardness, Model model) {
+		model.addAttribute("mesHardness", mesHardness);
+		return "modules/mes/mesHardnessList";
+	}
+
+	/**
+	 * 查询列表数据
+	 */
+	@RequiresPermissions("mes:mesHardness:view")
+	@RequestMapping(value = "listData")
+	@ResponseBody
+	public Page<MesHardness> listData(MesHardness mesHardness, HttpServletRequest request, HttpServletResponse response) {
+		mesHardness.setPage(new Page<>(request, response));
+		Page<MesHardness> page = mesHardnessService.findPage(mesHardness);
+		return page;
+	}
+
+	/**
+	 * 查看详情
+	 */
+	@RequiresPermissions("mes:mesHardness:view")
+	@RequestMapping(value = "detail")
+	public String detail(MesHardness mesHardness, Model model) {
+		model.addAttribute("mesHardness", mesHardness);
+		return "modules/mes/mesHardnessInfo";
+	}
+
+	/**
+	 * 导出数据
+	 */
+	@RequiresPermissions("mes:mesHardness:view")
+	@RequestMapping(value = "exportData")
+	public void exportData(MesHardness mesHardness, HttpServletResponse response) {
+		List<MesHardness> list = mesHardnessService.findList(mesHardness);
+		String fileName = "硬度检测" + DateUtils.getDate("yyyyMMddHHmmss") + ".xlsx";
+		try(ExcelExport ee = new ExcelExport("硬度检测", MesHardness.class)){
+			ee.setDataList(list).write(response, fileName);
+		}
+	}
+
+	// ==================== PDA 接口 ====================
+
+	/**
+	 * PDA端新增硬度检测记录
+	 * @param sn     工件码(必填)
+	 * @param value1 实测值1(必填)
+	 * @param value2 实测值2(必填)
+	 * @param value3 实测值3(必填)
+	 * @param remark 备注(可空)
+	 */
+	@RequestMapping(value = "pdaSave")
+	@ResponseBody
+	public String pdaSave(String sn, Integer value1, Integer value2, Integer value3, String remark) {
+		if (sn == null || sn.trim().isEmpty()) {
+			return renderResult(Global.FALSE, "工件码不能为空");
+		}
+		if (value1 == null || value2 == null || value3 == null) {
+			return renderResult(Global.FALSE, "三个实测值均不能为空");
+		}
+		MesHardness hardness = mesHardnessService.saveFromPda(sn.trim(), value1, value2, value3, remark);
+		return renderResult(Global.TRUE, "保存成功", hardness);
+	}
+
+	// ==================== 标准值配置接口 ====================
+
+	/**
+	 * 获取标准值配置
+	 */
+	@RequiresPermissions("mes:mesHardness:view")
+	@RequestMapping(value = "getConfig")
+	@ResponseBody
+	public MesHardnessConfig getConfig() {
+		return mesHardnessConfigService.getConfig();
+	}
+
+	/**
+	 * 保存标准值配置
+	 */
+	@RequiresPermissions("mes:mesHardness:edit")
+	@PostMapping(value = "saveConfig")
+	@ResponseBody
+	public String saveConfig(MesHardnessConfig config) {
+		mesHardnessConfigService.save(config);
+		return renderResult(Global.TRUE, "保存标准值设置成功!");
+	}
+
+}

+ 135 - 0
src/main/java/com/jeesite/modules/scheduling/MesMoldMaintenancePlanScheduling.java

@@ -0,0 +1,135 @@
+package com.jeesite.modules.scheduling;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.sql.DataSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+
+import com.jeesite.modules.mes.entity.MesMoldMaintenancePlan;
+import com.jeesite.modules.mes.service.MesMoldMaintenancePlanService;
+import com.jeesite.modules.tianHong.dao.RptProductionSummaryDao;
+
+/**
+ * 模具维保计划定时任务
+ * 每5秒计算一次剩余批次生产数:
+ *   批次完成数 = bzh库FinishQTY - 上次保养生产完成数
+ *   剩余批次生产数 = 初始批次生产数 - 批次完成数
+ * 计算结果更新到数据库的 remain_batch_production_num 字段
+ * 当剩余批次生产数小于10时,发送预警短信(受布尔静态变量控制)
+ *
+ * 更新方式:使用原始JDBC连接直接执行SQL,显式设置autocommit=true,
+ * 完全绕过Spring事务代理、MyBatis拦截器和JeeSite自定义拦截器,
+ * 每条UPDATE立即提交,绝不持有行锁,避免事务泄漏导致锁等待超时。
+ * @author mes
+ */
+@Configuration
+@EnableScheduling
+public class MesMoldMaintenancePlanScheduling {
+
+	private static final Logger log = LoggerFactory.getLogger(MesMoldMaintenancePlanScheduling.class);
+
+	// 剩余批次生产数预警阈值
+	private static final int BATCH_ALERT_THRESHOLD = 10;
+
+	private static final String UPDATE_SQL =
+			"UPDATE mes_mold_maintenance_plan SET remain_batch_production_num = ?, batch_complete_num = ?, update_date = NOW() WHERE id = ?";
+
+	@Autowired
+	private RptProductionSummaryDao rptProductionSummaryDao;
+
+	@Autowired
+	private MesMoldMaintenancePlanService mesMoldMaintenancePlanService;
+
+	@Autowired
+	private DataSource dataSource;
+
+	/**
+	 * 每5秒执行一次:计算剩余批次生产数并更新数据库
+	 */
+	@Scheduled(fixedRate = 5000)
+	public void calculateRemainBatchProductionNum() {
+		try {
+			// 查询所有维保计划
+			List<MesMoldMaintenancePlan> list = mesMoldMaintenancePlanService.findList(new MesMoldMaintenancePlan());
+			if (list == null || list.isEmpty()) {
+				return;
+			}
+			// 收集所有非空moldCode
+			List<String> moldCodes = new ArrayList<>();
+			for (MesMoldMaintenancePlan plan : list) {
+				if (plan.getMoldCode() != null && !plan.getMoldCode().isEmpty()) {
+					moldCodes.add(plan.getMoldCode());
+				}
+			}
+			// 批量查询bzh库FinishQTY
+			Map<String, Integer> finishQtyMap = new HashMap<>();
+			if (!moldCodes.isEmpty()) {
+				List<Map<String, Object>> finishQtyList = rptProductionSummaryDao.getFinishQtyByEqpIds(moldCodes);
+				if (finishQtyList != null) {
+					for (Map<String, Object> item : finishQtyList) {
+						String eqpId = (String) item.get("eqpId");
+						Integer finishQty = ((Number) item.get("finishQty")).intValue();
+						finishQtyMap.put(eqpId, finishQty);
+					}
+				}
+			}
+			// 逐条计算并更新
+			for (MesMoldMaintenancePlan plan : list) {
+				Integer finishQty = finishQtyMap.get(plan.getMoldCode());
+				if (finishQty == null) {
+					finishQty = 0;
+				}
+				Integer lastFinishQty = plan.getLastMaintenanceFinishQty();
+				if (lastFinishQty == null) {
+					lastFinishQty = 0;
+				}
+				Integer initialNum = plan.getInitialBatchProductionNum();
+				if (initialNum == null) {
+					initialNum = 0;
+				}
+				// 批次完成数 = FinishQTY - 上次保养生产完成数
+				int batchCompleteNum = finishQty - lastFinishQty;
+				if (batchCompleteNum < 0) {
+					batchCompleteNum = 0;
+				}
+				// 剩余批次生产数 = 初始批次生产数 - 批次完成数
+				int remainNum = initialNum - batchCompleteNum;
+				if (remainNum < 0) {
+					remainNum = 0;
+				}
+				try {
+					// 使用原始JDBC连接执行UPDATE,显式autocommit,绕过所有Spring/MyBatis事务管理
+					// 每次UPDATE立即提交,绝不会产生事务泄漏
+					try (Connection conn = dataSource.getConnection()) {
+						conn.setAutoCommit(true);
+						try (PreparedStatement ps = conn.prepareStatement(UPDATE_SQL)) {
+							ps.setInt(1, remainNum);
+							ps.setInt(2, batchCompleteNum);
+							ps.setString(3, plan.getId());
+							ps.executeUpdate();
+						}
+					}
+				} catch (Exception ex) {
+					log.warn("更新维保计划剩余批次生产数失败,跳过该记录。id={}, 模具号={}",
+							plan.getId(), plan.getMoldCode(), ex);
+					continue;
+				}
+				// 剩余批次生产数小于阈值时发送预警短信
+				if (remainNum < BATCH_ALERT_THRESHOLD) {
+					mesMoldMaintenancePlanService.sendBatchAlertSms(plan);
+				}
+			}
+		} catch (Exception e) {
+			log.error("定时计算剩余批次生产数失败", e);
+		}
+	}
+}

+ 5 - 0
src/main/resources/mappings/modules/mes/MesHardnessConfigDao.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.jeesite.modules.mes.dao.MesHardnessConfigDao">
+
+</mapper>

+ 5 - 0
src/main/resources/mappings/modules/mes/MesHardnessDao.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.jeesite.modules.mes.dao.MesHardnessDao">
+
+</mapper>

+ 8 - 9
src/main/resources/mappings/modules/mes/MesMoldMaintenancePlanDao.xml

@@ -2,14 +2,13 @@
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.jeesite.modules.mes.dao.MesMoldMaintenancePlanDao">
 
-	<!-- 查询数据
-	<select id="findList" resultType="MesMoldMaintenancePlan">
-		SELECT ${sqlMap.column.toSql()}
-		FROM ${sqlMap.table.toSql()}
-		<where>
-			${sqlMap.where.toSql()}
-		</where>
-		ORDER BY ${sqlMap.order.toSql()}
-	</select> -->
+	<!-- 更新剩余批次生产数和批次完成数 -->
+	<update id="updateRemainBatchProductionNum">
+		UPDATE mes_mold_maintenance_plan
+		SET remain_batch_production_num = #{remainBatchProductionNum},
+			batch_complete_num = #{batchCompleteNum},
+			update_date = NOW()
+		WHERE id = #{id}
+	</update>
 
 </mapper>

+ 80 - 0
src/main/resources/views/modules/mes/mesHardnessInfo.html

@@ -0,0 +1,80 @@
+<% layout('/layouts/default.html', {title: '硬度检测详情', libs: ['dataGrid']}){ %>
+<div class="main-content">
+	<div class="box box-main">
+		<div class="box-header with-border">
+			<div class="box-title">
+				<i class="fa icon-note"></i> ${text('硬度检测详情')}
+			</div>
+			<div class="box-tools pull-right">
+				<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
+			</div>
+		</div>
+		<div class="box-body">
+			<div class="form-unit">${text('基本信息')}</div>
+			<div class="row">
+				<div class="col-xs-12">
+					<table class="table table-bordered">
+						<tr>
+							<th class="mpc-header" style="width:120px;">工件码</th>
+							<td>${mesHardness.sn}</td>
+						</tr>
+						<tr>
+							<th class="mpc-header">标准值</th>
+							<td>${mesHardness.standardValue}</td>
+						</tr>
+						<tr>
+							<th class="mpc-header">实测值1</th>
+							<td>${mesHardness.actualValue1}</td>
+						</tr>
+						<tr>
+							<th class="mpc-header">实测值2</th>
+							<td>${mesHardness.actualValue2}</td>
+						</tr>
+						<tr>
+							<th class="mpc-header">实测值3</th>
+							<td>${mesHardness.actualValue3}</td>
+						</tr>
+						<tr>
+							<th class="mpc-header">平均值</th>
+							<td>${mesHardness.avgValue}</td>
+						</tr>
+						<tr>
+							<th class="mpc-header">判定结果</th>
+							<td>
+								<% if('合格' == mesHardness.result){ %>
+									<span style="color:green;font-weight:bold;">合格</span>
+								<% } else { %>
+									<span style="color:red;font-weight:bold;">不合格</span>
+								<% } %>
+							</td>
+						</tr>
+						<tr>
+							<th class="mpc-header">类型</th>
+							<td>${mesHardness.type}</td>
+						</tr>
+						<tr>
+							<th class="mpc-header">测量单位</th>
+							<td>${mesHardness.unit}</td>
+						</tr>
+						<tr>
+							<th class="mpc-header">备注</th>
+							<td>${mesHardness.remark}</td>
+						</tr>
+						<tr>
+							<th class="mpc-header">测量日期</th>
+							<td>${@DateUtils.formatDate(mesHardness.measureDate)}</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+		</div>
+		<div class="box-footer">
+			<div class="row">
+				<div class="col-sm-offset-2 col-sm-10">
+					<button type="button" class="btn btn-sm btn-default" id="btnCancel" onclick="js.closeCurrentTabPage()"><i class="fa fa-reply-all"></i> ${text('关 闭')}</button>
+				</div>
+			</div>
+		</div>
+	</div>
+</div>
+<% } %>

+ 178 - 0
src/main/resources/views/modules/mes/mesHardnessList.html

@@ -0,0 +1,178 @@
+<% layout('/layouts/default.html', {title: '硬度检测管理', libs: ['dataGrid']}){ %>
+<div class="main-content">
+	<div class="box box-main">
+		<div class="box-header">
+			<div class="box-title">
+				<i class="fa icon-notebook"></i> ${text('硬度检测管理')}
+			</div>
+			<div class="box-tools pull-right">
+				<a href="#" class="btn btn-default" id="btnSearch" title="${text('查询')}"><i class="fa fa-filter"></i> ${text('查询')}</a>
+				<a href="#" class="btn btn-default" id="btnExport"><i class="glyphicon glyphicon-export"></i> 导出</a>
+				<% if(hasPermi('mes:mesHardness:edit')){ %>
+				<a href="#" class="btn btn-default" id="btnStandardConfig" title="${text('标准值设置')}"><i class="fa fa-cog"></i> ${text('标准值设置')}</a>
+				<% } %>
+				<a href="#" class="btn btn-default" id="btnSetting" title="${text('设置')}"><i class="fa fa-navicon"></i></a>
+			</div>
+		</div>
+		<div class="box-body">
+			<#form:form id="searchForm" model="${mesHardness}" action="${ctx}/mes/mesHardness/listData" method="post" class="form-inline hide"
+					data-page-no="${parameter.pageNo}" data-page-size="${parameter.pageSize}" data-order-by="${parameter.orderBy}">
+				<div class="form-group">
+					<label class="control-label">${text('工件码')}:</label>
+					<div class="control-inline">
+						<#form:input path="sn" maxlength="100" class="form-control width-260"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('判定结果')}:</label>
+					<div class="control-inline width-120">
+						<#form:select path="result" dictType="mes_hardness_result" blankOption="true" class="form-control"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('测量日期')}:</label>
+					<div class="control-inline">
+						<#form:input path="measureDate_gte" readonly="true" maxlength="20" class="form-control laydate width-datetime"
+							dataFormat="date" data-type="date" data-format="yyyy-MM-dd" data-done="measureDate_lte.click()"/>
+						&nbsp;-&nbsp;
+						<#form:input path="measureDate_lte" readonly="true" maxlength="20" class="form-control laydate width-datetime"
+							dataFormat="date" data-type="date" data-format="yyyy-MM-dd"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<button type="submit" class="btn btn-primary btn-sm">${text('查询')}</button>
+					<button type="reset" class="btn btn-default btn-sm">${text('重置')}</button>
+				</div>
+			</#form:form>
+			<table id="dataGrid"></table>
+			<div id="dataGridPage"></div>
+		</div>
+	</div>
+</div>
+
+<!-- 标准值设置弹框 -->
+<div class="modal fade" id="configModal" tabindex="-1" role="dialog">
+	<div class="modal-dialog" role="document">
+		<div class="modal-content">
+			<div class="modal-header">
+				<button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
+				<h4 class="modal-title">${text('标准值设置')}</h4>
+			</div>
+			<div class="modal-body">
+				<div class="form-horizontal">
+					<div class="form-group">
+						<label class="control-label col-sm-4">${text('比较方式')}:</label>
+						<div class="col-sm-8">
+							<select id="standardOp" class="form-control">
+								<option value=">">大于</option>
+								<option value="<">小于</option>
+								<option value="=">等于</option>
+							</select>
+						</div>
+					</div>
+					<div class="form-group">
+						<label class="control-label col-sm-4">${text('标准值')}:</label>
+						<div class="col-sm-8">
+							<input type="number" id="standardValueInput" class="form-control" placeholder="${text('请输入数值')}"/>
+						</div>
+					</div>
+					<div class="form-group">
+						<div class="col-sm-offset-4 col-sm-8">
+							<p class="text-muted"><small>设置后,PDA新增硬度检测记录时将以此标准值判定合格与否。</small></p>
+						</div>
+					</div>
+				</div>
+			</div>
+			<div class="modal-footer">
+				<button type="button" class="btn btn-default" data-dismiss="modal">${text('取消')}</button>
+				<button type="button" class="btn btn-primary" id="btnSaveConfig">${text('保存')}</button>
+			</div>
+		</div>
+	</div>
+</div>
+<% } %>
+<script>
+$('#dataGrid').dataGrid({
+	searchForm: $("#searchForm"),
+	columnModel: [
+		{header:'${text("工件码")}', name:'sn', index:'a.sn', width:150, align:"center"},
+		{header:'${text("标准值")}', name:'standardValue', index:'a.standard_value', width:100, align:"center", formatter: function(val){
+			if(val && val.length > 0){
+				var op = val.substring(0, 1);
+				var num = val.substring(1);
+				var opMap = {'>': '大于', '<': '小于', '=': '等于'};
+				return (opMap[op] || op) + num;
+			}
+			return '';
+		}},
+		{header:'${text("实测值1")}', name:'actualValue1', index:'a.actual_value1', width:80, align:"center"},
+		{header:'${text("实测值2")}', name:'actualValue2', index:'a.actual_value2', width:80, align:"center"},
+		{header:'${text("实测值3")}', name:'actualValue3', index:'a.actual_value3', width:80, align:"center"},
+		{header:'${text("平均值")}', name:'avgValue', index:'a.avg_value', width:80, align:"center"},
+		{header:'${text("判定结果")}', name:'result', index:'a.result', width:100, align:"center", formatter: function(val){
+			if(val == '合格'){
+				return '<span style="color:green;font-weight:bold;">合格</span>';
+			} else if(val == '不合格'){
+				return '<span style="color:red;font-weight:bold;">不合格</span>';
+			}
+			return val;
+		}},
+		{header:'${text("类型")}', name:'type', index:'a.type', width:120, align:"center"},
+		{header:'${text("测量单位")}', name:'unit', index:'a.unit', width:80, align:"center"},
+		{header:'${text("备注")}', name:'remark', index:'a.remark', width:150, align:"left"},
+		{header:'${text("测量日期")}', name:'measureDate', index:'a.measure_date', width:120, align:"center", formatter: function(val){
+			return val ? val.substring(0, 10) : '';
+		}},
+		{header:'${text("操作")}', name:'actions', width:80, align:"center", formatter: function(val, obj, row, act){
+			return '<a href="${ctx}/mes/mesHardness/detail?id='+row.id+'" class="btnList" title="${text("详情")}"><i class="fa fa-fw fa-eye"></i></a>';
+		}}
+	],
+	ajaxSuccess: function(data){
+	}
+});
+
+$('#btnExport').click(function(){
+	js.ajaxSubmitForm($('#searchForm'), {
+		url:'${ctx}/mes/mesHardness/exportData',
+		downloadFile:true
+	});
+});
+
+// 标准值设置
+$('#btnStandardConfig').click(function(){
+	$.get('${ctx}/mes/mesHardness/getConfig', function(data){
+		if(data && data.standardOp){
+			$('#standardOp').val(data.standardOp);
+			$('#standardValueInput').val(data.standardValue);
+		} else {
+			$('#standardOp').val('>');
+			$('#standardValueInput').val('');
+		}
+		$('#configModal').modal('show');
+	});
+});
+
+$('#btnSaveConfig').click(function(){
+	var op = $('#standardOp').val();
+	var val = $('#standardValueInput').val();
+	if(!val){
+		js.showMessage('请输入标准值数值');
+		return;
+	}
+	$.ajax({
+		url: '${ctx}/mes/mesHardness/saveConfig',
+		type: 'post',
+		data: { standardOp: op, standardValue: val },
+		dataType: 'json',
+		success: function(data){
+			js.showMessage(data.message);
+			if(data.result == Global.TRUE){
+				$('#configModal').modal('hide');
+			}
+		},
+		error: function(){
+			js.showMessage('保存失败,请检查网络或联系管理员');
+		}
+	});
+});
+</script>

+ 1 - 1
src/main/resources/views/modules/mes/mesMoldMaintenancePlanList.html

@@ -73,7 +73,7 @@ $('#dataGrid').dataGrid({
 		{header:'${text("上次维保等级")}', name:'lastMaintenanceLevel', index:'a.last_maintenance_level', width:120, align:"center"},
 		{header:'${text("上次维保内容")}', name:'lastMaintenanceContent', index:'a.last_maintenance_content', width:250, align:"left"},
 		{header:'${text("上次保养生产完成数")}', name:'lastMaintenanceFinishQty', index:'a.last_maintenance_finish_qty', width:140, align:"center"},
-		{header:'${text("批次完成数")}', name:'batchCompleteNum', width:120, align:"center"},
+		{header:'${text("批次完成数")}', name:'batchCompleteNum', index:'a.batch_complete_num', width:120, align:"center"},
 		{header:'${text("剩余批次生产数")}', name:'remainBatchProductionNum', index:'a.remain_batch_production_num', width:120, align:"center"},
 		{header:'${text("上次维保时间")}', name:'lastMaintenanceDate', index:'a.last_maintenance_date', width:150, align:"center"},
 		{header:'${text("操作")}', name:'actions', width:160, formatter: function(val, obj, row, act){

+ 1 - 1
src/test/java/com/jeesite/test/TencentSmsUtilTest.java

@@ -26,7 +26,7 @@ public class TencentSmsUtilTest extends BaseSpringContextTests {
 	public void testSendSms() throws TencentCloudSDKException {
 		String phone = System.getenv("TENCENT_SMS_TEST_PHONE");
 		if (StringUtils.isBlank(phone)) {
-			phone = "19863431582";
+			phone = "17203891886";
 		}
 
 		SendSmsResponse resp = TencentSmsUtil.sendSms(phone);