Преглед изворни кода

Merge remote-tracking branch 'origin/master'

liuwei пре 2 месеци
родитељ
комит
03abefdcea
21 измењених фајлова са 1366 додато и 69 уклоњено
  1. 15 0
      src/main/java/com/jeesite/modules/mes/dao/MesProductJynyDao.java
  2. 15 0
      src/main/java/com/jeesite/modules/mes/dao/MesScrewDao.java
  3. 90 0
      src/main/java/com/jeesite/modules/mes/entity/MesProductJyny.java
  4. 42 0
      src/main/java/com/jeesite/modules/mes/entity/MesScrew.java
  5. 2 2
      src/main/java/com/jeesite/modules/mes/service/MesProductCcdService.java
  6. 81 0
      src/main/java/com/jeesite/modules/mes/service/MesProductJynyService.java
  7. 81 0
      src/main/java/com/jeesite/modules/mes/service/MesScrewService.java
  8. 50 2
      src/main/java/com/jeesite/modules/mes/web/MesOrderSnController.java
  9. 125 59
      src/main/java/com/jeesite/modules/mes/web/MesProductCncParamController.java
  10. 148 0
      src/main/java/com/jeesite/modules/mes/web/MesProductJynyController.java
  11. 1 1
      src/main/java/com/jeesite/modules/mes/web/MesProductRecordController.java
  12. 91 0
      src/main/java/com/jeesite/modules/mes/web/MesScrewController.java
  13. 291 0
      src/main/java/com/jeesite/modules/scheduling/FswModbusScheduling.java
  14. 8 0
      src/main/resources/config/application.yml
  15. 15 0
      src/main/resources/mappings/modules/mes/MesProductJynyDao.xml
  16. 15 0
      src/main/resources/mappings/modules/mes/MesScrewDao.xml
  17. 5 5
      src/main/resources/views/modules/mes/mesProductCncParamDetail.html
  18. 91 0
      src/main/resources/views/modules/mes/mesProductJynyForm.html
  19. 87 0
      src/main/resources/views/modules/mes/mesProductJynyList.html
  20. 55 0
      src/main/resources/views/modules/mes/mesScrewForm.html
  21. 58 0
      src/main/resources/views/modules/mes/mesScrewList.html

+ 15 - 0
src/main/java/com/jeesite/modules/mes/dao/MesProductJynyDao.java

@@ -0,0 +1,15 @@
+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.MesProductJyny;
+
+/**
+ * 绝缘耐压数据表DAO接口
+ * @author sxl
+ * @version 2026-08-13
+ */
+@MyBatisDao
+public interface MesProductJynyDao extends CrudDao<MesProductJyny> {
+	
+}

+ 15 - 0
src/main/java/com/jeesite/modules/mes/dao/MesScrewDao.java

@@ -0,0 +1,15 @@
+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.MesScrew;
+
+/**
+ * mes_screwDAO接口
+ * @author sxl
+ * @version 2026-08-14
+ */
+@MyBatisDao
+public interface MesScrewDao extends CrudDao<MesScrew> {
+	
+}

+ 90 - 0
src/main/java/com/jeesite/modules/mes/entity/MesProductJyny.java

@@ -0,0 +1,90 @@
+package com.jeesite.modules.mes.entity;
+
+import javax.validation.constraints.Size;
+
+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;
+
+/**
+ * 绝缘耐压数据表Entity
+ * @author sxl
+ * @version 2026-08-13
+ */
+@Table(name="mes_product_jyny", alias="a", label="绝缘耐压数据表信息", columns={
+		@Column(name="id", attrName="id", label="id", isPK=true),
+		@Column(name="sn", attrName="sn", label="工件码"),
+		@Column(name="dz_data", attrName="dzData", label="绝缘电阻"),
+		@Column(name="ld_data", attrName="ldData", label="漏电电流"),
+		@Column(name="create_by", attrName="createBy", label="创建人", isUpdate=false, isQuery=false),
+		@Column(name="result", attrName="result", label="工位结果"),
+		@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="更新时间", isQuery=false, isUpdateForce=true),
+		@Column(name="oprno", attrName="oprno", label="工位号"),
+	}, orderBy="a.update_date DESC"
+)
+public class MesProductJyny extends DataEntity<MesProductJyny> {
+	
+	private static final long serialVersionUID = 1L;
+	private String sn;		// 工件码
+	private String dzData;		// 绝缘电阻
+	private String ldData;		// 漏电电流
+	private String result;		// 工位结果
+	private String oprno;		// 工位号
+
+	public MesProductJyny() {
+		this(null);
+	}
+	
+	public MesProductJyny(String id){
+		super(id);
+	}
+	
+	@Size(min=0, max=64, message="工件码长度不能超过 64 个字符")
+	public String getSn() {
+		return sn;
+	}
+
+	public void setSn(String sn) {
+		this.sn = sn;
+	}
+	
+	@Size(min=0, max=255, message="绝缘电阻长度不能超过 255 个字符")
+	public String getDzData() {
+		return dzData;
+	}
+
+	public void setDzData(String dzData) {
+		this.dzData = dzData;
+	}
+	
+	@Size(min=0, max=255, message="漏电电流长度不能超过 255 个字符")
+	public String getLdData() {
+		return ldData;
+	}
+
+	public void setLdData(String ldData) {
+		this.ldData = ldData;
+	}
+	
+	@Size(min=0, max=64, message="工位结果长度不能超过 64 个字符")
+	public String getResult() {
+		return result;
+	}
+
+	public void setResult(String result) {
+		this.result = result;
+	}
+	
+	@Size(min=0, max=64, message="工位号长度不能超过 64 个字符")
+	public String getOprno() {
+		return oprno;
+	}
+
+	public void setOprno(String oprno) {
+		this.oprno = oprno;
+	}
+	
+}

+ 42 - 0
src/main/java/com/jeesite/modules/mes/entity/MesScrew.java

@@ -0,0 +1,42 @@
+package com.jeesite.modules.mes.entity;
+
+import javax.validation.constraints.Size;
+
+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;
+
+/**
+ * mes_screwEntity
+ * @author sxl
+ * @version 2026-08-14
+ */
+@Table(name="mes_screw", alias="a", label="mes_screw信息", columns={
+		@Column(name="id", attrName="id", label="id", isPK=true),
+		@Column(name="ww", attrName="ww", label="add"),
+	}, orderBy="a.id DESC"
+)
+public class MesScrew extends DataEntity<MesScrew> {
+	
+	private static final long serialVersionUID = 1L;
+	private String ww;		// add
+
+	public MesScrew() {
+		this(null);
+	}
+	
+	public MesScrew(String id){
+		super(id);
+	}
+	
+	@Size(min=0, max=255, message="add长度不能超过 255 个字符")
+	public String getWw() {
+		return ww;
+	}
+
+	public void setWw(String ww) {
+		this.ww = ww;
+	}
+	
+}

+ 2 - 2
src/main/java/com/jeesite/modules/mes/service/MesProductCcdService.java

@@ -183,7 +183,7 @@ public class MesProductCcdService extends CrudService<MesProductCcdDao, MesProdu
 			return CommonUitl.getXml("NG;参数错误4;");
 		}
 
-		String sn = parts[2];	//序号
+		String sn = parts[2];	//序号(条码)
 		String oprno = parts[3]; // OP290B
 		String lineSn = "XT";	//产线编号
 		String title = parts[4];//设备名称
@@ -206,7 +206,7 @@ public class MesProductCcdService extends CrudService<MesProductCcdDao, MesProdu
 			mesProductCcd.setSn(sn);
 			mesProductCcd.setOprno(oprno);
 			mesProductCcd.setLineSn(lineSn);
-			mesProductCcd.setCate("C005电池盒");
+			mesProductCcd.setCate("ADO6箱体");
 			mesProductCcd.setTitle(title);
 			//测试编号--序号
 			mesProductCcd.setTestNum(dtl.split("@")[0]);

+ 81 - 0
src/main/java/com/jeesite/modules/mes/service/MesProductJynyService.java

@@ -0,0 +1,81 @@
+package com.jeesite.modules.mes.service;
+
+import java.util.List;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.service.CrudService;
+import com.jeesite.modules.mes.entity.MesProductJyny;
+import com.jeesite.modules.mes.dao.MesProductJynyDao;
+
+/**
+ * 绝缘耐压数据表Service
+ * @author sxl
+ * @version 2026-08-13
+ */
+@Service
+public class MesProductJynyService extends CrudService<MesProductJynyDao, MesProductJyny> {
+	
+	/**
+	 * 获取单条数据
+	 * @param mesProductJyny
+	 * @return
+	 */
+	@Override
+	public MesProductJyny get(MesProductJyny mesProductJyny) {
+		return super.get(mesProductJyny);
+	}
+	
+	/**
+	 * 查询分页数据
+	 * @param mesProductJyny 查询条件
+	 * @param mesProductJyny.page 分页对象
+	 * @return
+	 */
+	@Override
+	public Page<MesProductJyny> findPage(MesProductJyny mesProductJyny) {
+		return super.findPage(mesProductJyny);
+	}
+	
+	/**
+	 * 查询列表数据
+	 * @param mesProductJyny
+	 * @return
+	 */
+	@Override
+	public List<MesProductJyny> findList(MesProductJyny mesProductJyny) {
+		return super.findList(mesProductJyny);
+	}
+	
+	/**
+	 * 保存数据(插入或更新)
+	 * @param mesProductJyny
+	 */
+	@Override
+	@Transactional
+	public void save(MesProductJyny mesProductJyny) {
+		super.save(mesProductJyny);
+	}
+	
+	/**
+	 * 更新状态
+	 * @param mesProductJyny
+	 */
+	@Override
+	@Transactional
+	public void updateStatus(MesProductJyny mesProductJyny) {
+		super.updateStatus(mesProductJyny);
+	}
+	
+	/**
+	 * 删除数据
+	 * @param mesProductJyny
+	 */
+	@Override
+	@Transactional
+	public void delete(MesProductJyny mesProductJyny) {
+		super.delete(mesProductJyny);
+	}
+	
+}

+ 81 - 0
src/main/java/com/jeesite/modules/mes/service/MesScrewService.java

@@ -0,0 +1,81 @@
+package com.jeesite.modules.mes.service;
+
+import java.util.List;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.service.CrudService;
+import com.jeesite.modules.mes.entity.MesScrew;
+import com.jeesite.modules.mes.dao.MesScrewDao;
+
+/**
+ * mes_screwService
+ * @author sxl
+ * @version 2026-08-14
+ */
+@Service
+public class MesScrewService extends CrudService<MesScrewDao, MesScrew> {
+	
+	/**
+	 * 获取单条数据
+	 * @param mesScrew
+	 * @return
+	 */
+	@Override
+	public MesScrew get(MesScrew mesScrew) {
+		return super.get(mesScrew);
+	}
+	
+	/**
+	 * 查询分页数据
+	 * @param mesScrew 查询条件
+	 * @param mesScrew.page 分页对象
+	 * @return
+	 */
+	@Override
+	public Page<MesScrew> findPage(MesScrew mesScrew) {
+		return super.findPage(mesScrew);
+	}
+	
+	/**
+	 * 查询列表数据
+	 * @param mesScrew
+	 * @return
+	 */
+	@Override
+	public List<MesScrew> findList(MesScrew mesScrew) {
+		return super.findList(mesScrew);
+	}
+	
+	/**
+	 * 保存数据(插入或更新)
+	 * @param mesScrew
+	 */
+	@Override
+	@Transactional
+	public void save(MesScrew mesScrew) {
+		super.save(mesScrew);
+	}
+	
+	/**
+	 * 更新状态
+	 * @param mesScrew
+	 */
+	@Override
+	@Transactional
+	public void updateStatus(MesScrew mesScrew) {
+		super.updateStatus(mesScrew);
+	}
+	
+	/**
+	 * 删除数据
+	 * @param mesScrew
+	 */
+	@Override
+	@Transactional
+	public void delete(MesScrew mesScrew) {
+		super.delete(mesScrew);
+	}
+	
+}

+ 50 - 2
src/main/java/com/jeesite/modules/mes/web/MesOrderSnController.java

@@ -7,12 +7,21 @@ import java.util.Map;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import com.jeesite.common.lang.ObjectUtils;
 import com.jeesite.common.lang.StringUtils;
 import com.jeesite.modules.erp.entity.MesProductOrder;
 import com.jeesite.modules.erp.service.MesProductOrderService;
+import com.jeesite.modules.mes.entity.MesLine;
+import com.jeesite.modules.mes.entity.MesProduct;
 import com.jeesite.modules.mes.resp.CommonResp;
+import com.jeesite.modules.mes.service.MesLineService;
+import com.jeesite.modules.mes.service.MesProductService;
+import com.jeesite.modules.sys.entity.User;
+import com.jeesite.modules.sys.utils.UserUtils;
+import com.jeesite.modules.utils.ErrorMsg;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
 import org.springframework.validation.annotation.Validated;
@@ -40,6 +49,13 @@ public class MesOrderSnController extends BaseController {
 	private MesOrderSnService mesOrderSnService;
 	@Autowired
 	private MesProductOrderService mesProductOrderService;
+	@Autowired
+	private MesProductService mesProductService;
+	@Autowired
+	private MesLineService mesLineService;
+	@Lazy
+	@Autowired
+	private MesProductRecordController mesProductRecordController;
 
 	/**
 	 * 获取数据
@@ -104,7 +120,7 @@ public class MesOrderSnController extends BaseController {
 	}
 
 	/**
-	 * 生产订单绑定工件码
+	 * 生产订单绑定工件码(OP100 工位质量校验通过后方可绑定)
 	 * @param mesOrderSn
 	 * @return
 	 */
@@ -117,8 +133,40 @@ public class MesOrderSnController extends BaseController {
 		if (StringUtils.isBlank(mesOrderSn.getSn())) {
 			return renderResult(Global.FALSE, text("工件码不能为空!"));
 		}
+		String sn = mesOrderSn.getSn();
+
+		// 判定工件是否存在,并解析产线编号(质量校验需要 lineSn)
+		MesProduct mesProductQuery = new MesProduct();
+		mesProductQuery.setSn(sn);
+		List<MesProduct> productList = mesProductService.findList(mesProductQuery);
+		if (productList == null || productList.isEmpty() || ObjectUtils.isEmpty(productList.get(0))) {
+			return renderResult(Global.FALSE, text("工件不存在!"));
+		}
+		MesProduct mesProduct = productList.get(0);
+		MesLine mesLine = new MesLine();
+		mesLine.setProductCate(mesProduct.getCate());
+		mesLine.setStatus("0");
+		MesLine lineInfo = mesLineService.findInfo(mesLine);
+		if (ObjectUtils.isEmpty(lineInfo) || StringUtils.isEmpty(lineInfo.getSn())) {
+			return renderResult(Global.FALSE, text("产线信息不存在!"));
+		}
+		String lineSn = lineInfo.getSn();
+
+		// OP100 质量校验(按子工位 OP100A)
+		String userCode = "";
+		User user = UserUtils.getUser();
+		if (user != null && StringUtils.isNotEmpty(user.getUserCode())) {
+			userCode = user.getUserCode();
+		}
+		String ret = mesProductRecordController.checkQualityCommon("OP100A", sn, lineSn, userCode);
+		String checkret = ret.substring(3, 5);
+		if (!"UD".equals(checkret)) {
+			String lx = ret.substring(0, 3);
+			return renderResult(Global.FALSE, text(ErrorMsg.getErrorMsg(checkret, lx)));
+		}
+
 		MesOrderSn query = new MesOrderSn();
-		query.setSn(mesOrderSn.getSn());
+		query.setSn(sn);
 		List<MesOrderSn> list = mesOrderSnService.findList(query);
 		if(!list.isEmpty()){
 			return renderResult(Global.FALSE, text("该工件已和生产订单绑定!"));

+ 125 - 59
src/main/java/com/jeesite/modules/mes/web/MesProductCncParamController.java

@@ -96,55 +96,83 @@ public class MesProductCncParamController extends BaseController {
 		return "modules/mes/mesProductCncParamDetail";
 	}
 
-	/** CNC参数代码与中文名称映射(展示用主代码 = 实际字段名) */
+	/** CNC参数主代码与中文名称映射(按展示顺序,仅展示这8项) */
 	private static final Map<String, String> CNC_PARAM_LABELS = new LinkedHashMap<>();
-	/** 同一参数的别名(旧字段名 → 主代码),用于取值兼容 */
+	/** 主代码 → 参数地址(展示用) */
+	private static final Map<String, String> CNC_PARAM_ADDRESSES = new LinkedHashMap<>();
+	/** 同一参数的别名(路径/旧短名 → 主代码),用于取值兼容 */
 	private static final Map<String, String> CNC_PARAM_ALIASES = new LinkedHashMap<>();
+	/** 固定显示值的参数 */
+	private static final Map<String, String> CNC_PARAM_FIXED_VALUES = new LinkedHashMap<>();
+	/** 需要按 ON/OFF 展示的布尔类参数 */
+	private static final java.util.Set<String> CNC_PARAM_ON_OFF = new java.util.HashSet<>();
 	static {
-		CNC_PARAM_LABELS.put("spindleActSpeed", "主轴转速");
-		CNC_PARAM_LABELS.put("machineX", "X轴机械坐标");
-		CNC_PARAM_LABELS.put("machineY", "Y轴机械坐标");
-		CNC_PARAM_LABELS.put("machineZ", "Z轴机械坐标");
-		CNC_PARAM_LABELS.put("actPressure", "实际压力值");
-		CNC_PARAM_LABELS.put("aaOffValZ", "Z轴叠加总量");
-		CNC_PARAM_LABELS.put("cutActSpeedX", "X轴进给率实际值");
-		CNC_PARAM_LABELS.put("cutActSpeedY", "Y轴进给率实际值");
-		CNC_PARAM_LABELS.put("cutActSpeedZ", "Z轴进给率实际值");
-		CNC_PARAM_LABELS.put("r210", "参数R210-恒压力开启延时");
-		CNC_PARAM_LABELS.put("r211", "参数R211-设定压力值");
-		CNC_PARAM_LABELS.put("r212", "参数R212-调整下限值");
-		CNC_PARAM_LABELS.put("r213", "参数R213-调整速度");
-		CNC_PARAM_LABELS.put("r214", "参数R214-叠加极限值");
-		CNC_PARAM_LABELS.put("r215", "参数R215-压力波动值");
-		CNC_PARAM_LABELS.put("r216", "参数R216-采集取样次数");
-		CNC_PARAM_LABELS.put("r217", "参数R217-手动调整值");
-		CNC_PARAM_LABELS.put("r218", "参数R218-暂停再启动延时");
-		CNC_PARAM_LABELS.put("r999", "参数R999-投影半径");
-
-		// 旧字段名兼容
-		CNC_PARAM_ALIASES.put("SP_SPEED", "spindleActSpeed");
-		CNC_PARAM_ALIASES.put("X_MCS", "machineX");
-		CNC_PARAM_ALIASES.put("Y_MCS", "machineY");
-		CNC_PARAM_ALIASES.put("Z_MCS", "machineZ");
-		CNC_PARAM_ALIASES.put("Actual_Pressure", "actPressure");
-		CNC_PARAM_ALIASES.put("AA_OFF_VAL_Z", "aaOffValZ");
-		CNC_PARAM_ALIASES.put("X_Feed", "cutActSpeedX");
-		CNC_PARAM_ALIASES.put("Y_Feed", "cutActSpeedY");
-		CNC_PARAM_ALIASES.put("Z_Feed", "cutActSpeedZ");
-		CNC_PARAM_ALIASES.put("R210", "r210");
-		CNC_PARAM_ALIASES.put("R211", "r211");
-		CNC_PARAM_ALIASES.put("R212", "r212");
-		CNC_PARAM_ALIASES.put("R213", "r213");
-		CNC_PARAM_ALIASES.put("R214", "r214");
-		CNC_PARAM_ALIASES.put("R215", "r215");
-		CNC_PARAM_ALIASES.put("R216", "r216");
-		CNC_PARAM_ALIASES.put("R217", "r217");
-		CNC_PARAM_ALIASES.put("R218", "r218");
-		CNC_PARAM_ALIASES.put("R999", "r999");
+		CNC_PARAM_LABELS.put("cmdFeedRateIpo", "设定进给速度");
+		CNC_PARAM_LABELS.put("cmdSpindleSpeed", "设定主轴转速");
+		CNC_PARAM_LABELS.put("hydraulicPressure", "液压压力");
+		CNC_PARAM_LABELS.put("airPressure", "气压压力");
+		CNC_PARAM_LABELS.put("panelFeedRateOvr", "面板进给倍率");
+		CNC_PARAM_LABELS.put("panelSpindleOvr", "面板主轴倍率");
+		CNC_PARAM_LABELS.put("spindleActSpeed", "实际主轴转速");
+		CNC_PARAM_LABELS.put("actFeedRateIpo", "实际进给速度");
+
+		CNC_PARAM_ADDRESSES.put("cmdFeedRateIpo", "cmdFeedRateIpo");
+		CNC_PARAM_ADDRESSES.put("cmdSpindleSpeed", "cmdSpindleSpeed");
+		CNC_PARAM_ADDRESSES.put("hydraulicPressure", "hydraulicPressure");
+		CNC_PARAM_ADDRESSES.put("airPressure", "airPressure");
+		CNC_PARAM_ADDRESSES.put("panelFeedRateOvr", "panelFeedRateOvr");
+		CNC_PARAM_ADDRESSES.put("panelSpindleOvr", "panelSpindleOvr");
+		CNC_PARAM_ADDRESSES.put("spindleActSpeed", "spindleActSpeed");
+		CNC_PARAM_ADDRESSES.put("actFeedRateIpo", "actFeedRateIpo");
+
+		CNC_PARAM_ON_OFF.add("hydraulicPressure");
+		CNC_PARAM_ON_OFF.add("airPressure");
+
+		// 短名 / 旧字段名 / 路径 / OPC NodeId 兼容
+		addAliases("cmdFeedRateIpo",
+				"/Channel/State/cmdFeedRateIpo",
+				"Channel/State/cmdFeedRateIpo", "ns=2;s=/Channel/State/cmdFeedRateIpo");
+		addAliases("cmdSpindleSpeed",
+				"cmdSpeed",
+				"/Channel/Spindle/cmdSpeed", "/Channel/Spindle/cmdSpindleSpeed",
+				"Channel/Spindle/cmdSpeed", "Channel/Spindle/cmdSpindleSpeed",
+				"ns=2;s=/Channel/Spindle/cmdSpeed", "ns=2;s=/Channel/Spindle/cmdSpindleSpeed");
+		addAliases("hydraulicPressure",
+				"/Plc/I6.2", "Plc/I6.2", "I6.2",
+				"ns=2;s=/Plc/I6.2");
+		addAliases("airPressure",
+				"/Plc/I3.5", "Plc/I3.5", "I3.5",
+				"ns=2;s=/Plc/I3.5");
+		addAliases("panelFeedRateOvr",
+				"feedRateOvr",
+				"/Channel/MachineAxis/feedRateOvr", "/Channel/MachineAxis/panelFeedRateOvr",
+				"Channel/MachineAxis/feedRateOvr", "Channel/MachineAxis/panelFeedRateOvr",
+				"ns=2;s=/Channel/MachineAxis/feedRateOvr", "ns=2;s=/Channel/MachineAxis/panelFeedRateOvr");
+		addAliases("panelSpindleOvr",
+				"speedOvr",
+				"/Channel/Spindle/speedOvr", "/Channel/Spindle/panelSpindleOvr",
+				"Channel/Spindle/speedOvr", "Channel/Spindle/panelSpindleOvr",
+				"ns=2;s=/Channel/Spindle/speedOvr", "ns=2;s=/Channel/Spindle/panelSpindleOvr");
+		addAliases("spindleActSpeed",
+				"actSpeed", "SP_SPEED",
+				"/Channel/Spindle/actSpeed", "/Channel/Spindle/spindleActSpeed",
+				"Channel/Spindle/actSpeed", "Channel/Spindle/spindleActSpeed",
+				"ns=2;s=/Channel/Spindle/actSpeed", "ns=2;s=/Channel/Spindle/spindleActSpeed");
+		addAliases("actFeedRateIpo",
+				"/Channel/State/actFeedRateIpo",
+				"Channel/State/actFeedRateIpo", "ns=2;s=/Channel/State/actFeedRateIpo");
+	}
+
+	private static void addAliases(String primaryKey, String... aliases) {
+		for (String alias : aliases) {
+			if (StringUtils.isNotBlank(alias)) {
+				CNC_PARAM_ALIASES.put(alias, primaryKey);
+			}
+		}
 	}
 
 	/**
-	 * 单条记录时,构建参数代码/名称/值列表(含未映射字段)
+	 * 单条记录时,构建参数地址/名称/值列表(仅展示已映射的8项)
 	 */
 	private List<Map<String, Object>> buildParamItems(List<JSONObject> paramRows) {
 		List<Map<String, Object>> items = new ArrayList<>();
@@ -153,32 +181,27 @@ public class MesProductCncParamController extends BaseController {
 		}
 		JSONObject row = paramRows.get(0);
 		for (Map.Entry<String, String> labelEntry : CNC_PARAM_LABELS.entrySet()) {
+			String code = labelEntry.getKey();
 			Map<String, Object> item = new LinkedHashMap<>();
-			item.put("code", labelEntry.getKey());
+			item.put("code", CNC_PARAM_ADDRESSES.getOrDefault(code, code));
 			item.put("label", labelEntry.getValue());
-			Object val = getParamValue(row, labelEntry.getKey());
-			item.put("value", val != null ? val : "-");
-			items.add(item);
-		}
-		for (Map.Entry<String, Object> entry : row.entrySet()) {
-			String key = entry.getKey();
-			if (CNC_PARAM_LABELS.containsKey(key) || CNC_PARAM_ALIASES.containsKey(key)) {
-				continue;
-			}
-			Map<String, Object> item = new LinkedHashMap<>();
-			item.put("code", key);
-			item.put("label", key);
-			item.put("value", entry.getValue());
+			item.put("value", formatParamValue(code, getParamValue(row, code)));
 			items.add(item);
 		}
 		return items;
 	}
 
 	/**
-	 * 按主代码取值,同时兼容别名字段
+	 * 按主代码取值,同时兼容别名字段;固定值参数优先返回固定值
 	 */
 	private Object getParamValue(JSONObject row, String code) {
-		if (row == null || StringUtils.isBlank(code)) {
+		if (StringUtils.isBlank(code)) {
+			return null;
+		}
+		if (CNC_PARAM_FIXED_VALUES.containsKey(code)) {
+			return CNC_PARAM_FIXED_VALUES.get(code);
+		}
+		if (row == null) {
 			return null;
 		}
 		Object val = row.get(code);
@@ -194,6 +217,35 @@ public class MesProductCncParamController extends BaseController {
 	}
 
 	/**
+	 * 格式化展示值:空值用 -;液压/气压转为 ON/OFF
+	 */
+	private Object formatParamValue(String code, Object val) {
+		if (CNC_PARAM_FIXED_VALUES.containsKey(code)) {
+			return CNC_PARAM_FIXED_VALUES.get(code);
+		}
+		if (val == null || "".equals(String.valueOf(val).trim())) {
+			return "-";
+		}
+		if (CNC_PARAM_ON_OFF.contains(code)) {
+			return toOnOff(val);
+		}
+		return val;
+	}
+
+	private String toOnOff(Object val) {
+		String s = String.valueOf(val).trim();
+		if ("1".equals(s) || "true".equalsIgnoreCase(s) || "on".equalsIgnoreCase(s)
+				|| "yes".equalsIgnoreCase(s) || "开".equals(s)) {
+			return "ON";
+		}
+		if ("0".equals(s) || "false".equalsIgnoreCase(s) || "off".equalsIgnoreCase(s)
+				|| "no".equalsIgnoreCase(s) || "关".equals(s)) {
+			return "OFF";
+		}
+		return s;
+	}
+
+	/**
 	 * 将别名字段归一到主代码,避免重复展示
 	 */
 	private void normalizeAliasKeys(List<JSONObject> paramRows) {
@@ -204,11 +256,25 @@ public class MesProductCncParamController extends BaseController {
 			for (Map.Entry<String, String> alias : CNC_PARAM_ALIASES.entrySet()) {
 				String aliasKey = alias.getKey();
 				String primaryKey = alias.getValue();
+				if (aliasKey.equals(primaryKey)) {
+					continue;
+				}
 				if (row.get(primaryKey) == null && row.get(aliasKey) != null) {
 					row.put(primaryKey, row.get(aliasKey));
 				}
 				row.remove(aliasKey);
 			}
+			// 固定值参数写入展示值
+			for (Map.Entry<String, String> fixed : CNC_PARAM_FIXED_VALUES.entrySet()) {
+				row.put(fixed.getKey(), fixed.getValue());
+			}
+			// 布尔类参数统一为 ON/OFF,便于多行表格展示
+			for (String onOffCode : CNC_PARAM_ON_OFF) {
+				Object val = row.get(onOffCode);
+				if (val != null) {
+					row.put(onOffCode, toOnOff(val));
+				}
+			}
 		}
 	}
 

+ 148 - 0
src/main/java/com/jeesite/modules/mes/web/MesProductJynyController.java

@@ -0,0 +1,148 @@
+package com.jeesite.modules.mes.web;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.jeesite.common.lang.ObjectUtils;
+import com.jeesite.common.lang.StringUtils;
+import com.jeesite.modules.mes.entity.MesProductRecord;
+import com.jeesite.modules.mes.service.MesProductRecordService;
+import com.jeesite.modules.sys.entity.User;
+import com.jeesite.modules.sys.utils.UserUtils;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.interceptor.TransactionAspectSupport;
+import org.springframework.ui.Model;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.jeesite.common.config.Global;
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.web.BaseController;
+import com.jeesite.modules.mes.entity.MesProductJyny;
+import com.jeesite.modules.mes.service.MesProductJynyService;
+
+/**
+ * 绝缘耐压数据表Controller
+ * @author sxl
+ * @version 2026-08-13
+ */
+@Controller
+@RequestMapping(value = "${adminPath}/mes/mesProductJyny")
+public class MesProductJynyController extends BaseController {
+
+	@Autowired
+	private MesProductJynyService mesProductJynyService;
+	@Autowired
+	private MesProductRecordService mesProductRecordService;
+
+
+	/**
+	 * 查询列表
+	 */
+	@RequiresPermissions("mes:mesProductJyny:view")
+	@RequestMapping(value = {"list", ""})
+	public String list(MesProductJyny mesProductJyny, Model model) {
+		model.addAttribute("mesProductJyny", mesProductJyny);
+		return "modules/mes/mesProductJynyList";
+	}
+
+	/**
+	 * 查询列表数据
+	 */
+	@RequiresPermissions("mes:mesProductJyny:view")
+	@RequestMapping(value = "listData")
+	@ResponseBody
+	public Page<MesProductJyny> listData(MesProductJyny mesProductJyny, HttpServletRequest request, HttpServletResponse response) {
+		mesProductJyny.setPage(new Page<>(request, response));
+		Page<MesProductJyny> page = mesProductJynyService.findPage(mesProductJyny);
+		return page;
+	}
+
+	/**
+	 * 查看编辑表单
+	 */
+	@RequiresPermissions("mes:mesProductJyny:view")
+	@RequestMapping(value = "form")
+	public String form(MesProductJyny mesProductJyny, Model model) {
+		model.addAttribute("mesProductJyny", mesProductJyny);
+		return "modules/mes/mesProductJynyForm";
+	}
+
+	/**
+	 * 保存数据
+	 */
+	@RequiresPermissions("mes:mesProductJyny:edit")
+	@PostMapping(value = "save")
+	@ResponseBody
+	public String save(@Validated MesProductJyny mesProductJyny) {
+		mesProductJynyService.save(mesProductJyny);
+		return renderResult(Global.TRUE, text("保存绝缘耐压数据表成功!"));
+	}
+
+	/**
+	 * 删除数据
+	 */
+	@RequiresPermissions("mes:mesProductJyny:edit")
+	@RequestMapping(value = "delete")
+	@ResponseBody
+	public String delete(MesProductJyny mesProductJyny) {
+		mesProductJynyService.delete(mesProductJyny);
+		return renderResult(Global.TRUE, text("删除绝缘耐压数据表成功!"));
+	}
+
+	/**
+	 * 接收绝缘耐压数据:先更新工件对应工位结果,再保存数据
+	 */
+	@PostMapping(value = "upload")
+	@ResponseBody
+	@Transactional
+	public String upload(MesProductJyny mesProductJyny) {
+		if (mesProductJyny == null || StringUtils.isBlank(mesProductJyny.getSn())) {
+			return renderResult(Global.FALSE, text("工件码不能为空!"));
+		}
+		if (StringUtils.isBlank(mesProductJyny.getOprno())) {
+			return renderResult(Global.FALSE, text("工位号不能为空!"));
+		}
+		if (StringUtils.isBlank(mesProductJyny.getResult())) {
+			return renderResult(Global.FALSE, text("工位结果不能为空!"));
+		}
+		String result = mesProductJyny.getResult().trim();
+		if (!"OK".equals(result) && !"NG".equals(result)) {
+			return renderResult(Global.FALSE, text("工位结果错误!"));
+		}
+
+		String sn = mesProductJyny.getSn().trim();
+		MesProductRecord record = mesProductRecordService.findBySnCate(sn);
+		if (ObjectUtils.isEmpty(record) || StringUtils.isBlank(record.getLineSn())) {
+			return renderResult(Global.FALSE, text("未找到该工件的生产记录!"));
+		}
+
+		String oprno = mesProductJyny.getOprno().trim();
+		if (oprno.length() == 5) {
+			oprno = oprno + "A";
+		}
+		String userCode = "";
+		User user = UserUtils.getUser();
+		if (user != null && StringUtils.isNotBlank(user.getUserCode())) {
+			userCode = user.getUserCode();
+		}
+
+		try {
+			// 1. 先更新工位结果
+			mesProductRecordService.updateRecord(sn, oprno, "100000", "", result, userCode, record.getLineSn());
+			// 2. 再保存绝缘耐压数据
+			mesProductJynyService.save(mesProductJyny);
+		} catch (Exception e) {
+			TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
+			logger.error("绝缘耐压数据上传失败", e);
+			return renderResult(Global.FALSE, text(StringUtils.isNotBlank(e.getMessage()) ? e.getMessage() : "操作失败!"));
+		}
+		return renderResult(Global.TRUE, text("操作成功!"));
+	}
+
+}

+ 1 - 1
src/main/java/com/jeesite/modules/mes/web/MesProductRecordController.java

@@ -3962,7 +3962,7 @@ public String check(@RequestBody MesElectricTorque mesElectricTorque,HttpServlet
 		// 01;工号;序号(条码);工序名称(工位号);资源名称;设备编码;工治具编码;
 		String commandString = req.getParameter("commandString");
 		logger.info("commandString:"+commandString);
-		mesLogService.apisave2("OP440","XT",commandString,req);
+		mesLogService.apisave2("OP370","XT",commandString,req);
 
 		if (commandString == null || commandString.isEmpty()) {
 			return CommonUitl.getXml("NG;参数错误;");

+ 91 - 0
src/main/java/com/jeesite/modules/mes/web/MesScrewController.java

@@ -0,0 +1,91 @@
+package com.jeesite.modules.mes.web;
+
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+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.validation.annotation.Validated;
+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 com.jeesite.common.config.Global;
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.web.BaseController;
+import com.jeesite.modules.mes.entity.MesScrew;
+import com.jeesite.modules.mes.service.MesScrewService;
+
+/**
+ * mes_screwController
+ * @author sxl
+ * @version 2026-08-14
+ */
+@Controller
+@RequestMapping(value = "${adminPath}/mes/mesScrew")
+public class MesScrewController extends BaseController {
+
+	@Autowired
+	private MesScrewService mesScrewService;
+
+
+
+	/**
+	 * 查询列表
+	 */
+	@RequiresPermissions("mes:mesScrew:view")
+	@RequestMapping(value = {"list", ""})
+	public String list(MesScrew mesScrew, Model model) {
+		model.addAttribute("mesScrew", mesScrew);
+		return "modules/mes/mesScrewList";
+	}
+
+	/**
+	 * 查询列表数据
+	 */
+	@RequiresPermissions("mes:mesScrew:view")
+	@RequestMapping(value = "listData")
+	@ResponseBody
+	public Page<MesScrew> listData(MesScrew mesScrew, HttpServletRequest request, HttpServletResponse response) {
+		mesScrew.setPage(new Page<>(request, response));
+		Page<MesScrew> page = mesScrewService.findPage(mesScrew);
+		return page;
+	}
+
+	/**
+	 * 查看编辑表单
+	 */
+	@RequiresPermissions("mes:mesScrew:view")
+	@RequestMapping(value = "form")
+	public String form(MesScrew mesScrew, Model model) {
+		model.addAttribute("mesScrew", mesScrew);
+		return "modules/mes/mesScrewForm";
+	}
+
+	/**
+	 * 保存数据
+	 */
+	@RequiresPermissions("mes:mesScrew:edit")
+	@PostMapping(value = "save")
+	@ResponseBody
+	public String save(@Validated MesScrew mesScrew) {
+		mesScrewService.save(mesScrew);
+		return renderResult(Global.TRUE, text("保存mes_screw成功!"));
+	}
+
+	/**
+	 * 删除数据
+	 */
+	@RequiresPermissions("mes:mesScrew:edit")
+	@RequestMapping(value = "delete")
+	@ResponseBody
+	public String delete(MesScrew mesScrew) {
+		mesScrewService.delete(mesScrew);
+		return renderResult(Global.TRUE, text("删除mes_screw成功!"));
+	}
+
+}

+ 291 - 0
src/main/java/com/jeesite/modules/scheduling/FswModbusScheduling.java

@@ -0,0 +1,291 @@
+package com.jeesite.modules.scheduling;
+
+import com.github.xingshuangs.iot.protocol.common.buff.EByteBuffFormat;
+import com.github.xingshuangs.iot.protocol.modbus.service.ModbusTcp;
+import com.jeesite.common.config.Global;
+import com.jeesite.common.lang.ObjectUtils;
+import com.jeesite.common.lang.StringUtils;
+import com.jeesite.modules.mes.entity.MesLine;
+import com.jeesite.modules.mes.entity.MesProduct;
+import com.jeesite.modules.mes.entity.MesProductFsw;
+import com.jeesite.modules.mes.service.MesLineService;
+import com.jeesite.modules.mes.service.MesProductFswService;
+import com.jeesite.modules.mes.service.MesProductService;
+import com.jeesite.modules.mes.web.MesProductRecordController;
+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 java.nio.charset.StandardCharsets;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * FSW 设备 Modbus TCP 握手与过程参数采集。
+ * <pre>
+ * 寄存器约定:
+ *   3600  数据采集触发(1=请求采集,MES读完后置0)
+ *   3610  质量校验结果(1=合格,2=不合格)
+ *   3620~3649  工件码(30个字,ASCII拼接)
+ *   3740  进给速度
+ *   3780  进给倍率
+ *   3820  主轴转速
+ *   3860  主轴倍率
+ *   4040  指定焊接点压力
+ *   4590  指定焊接点偏移量
+ *
+ * 配置(application.yml):
+ *   mes.fsw.modbus.enabled   是否启用(默认 false)
+ *   mes.fsw.modbus.host      PLC IP
+ *   mes.fsw.modbus.port      端口,默认 502
+ *   mes.fsw.modbus.unitId    从站号,默认 1
+ *   mes.fsw.modbus.oprno     工位号
+ *   mes.fsw.modbus.lineSn    产线编号(可空,空则按工件类型自动解析)
+ * </pre>
+ */
+@Configuration
+@EnableScheduling
+public class FswModbusScheduling {
+
+	private static final Logger logger = LoggerFactory.getLogger(FswModbusScheduling.class);
+
+	private static final int ADDR_COLLECT_FLAG = 3600;
+	private static final int ADDR_QUALITY_RESULT = 3610;
+	private static final int ADDR_SN_START = 3620;
+	private static final int SN_REGISTER_COUNT = 30;
+
+	private static final int ADDR_FEED_RATE = 3740;
+	private static final int ADDR_FEED_OVERRIDE = 3780;
+	private static final int ADDR_SPINDLE_SPEED = 3820;
+	private static final int ADDR_SPINDLE_OVERRIDE = 3860;
+	private static final int ADDR_WELD_PRESSURE = 4040;
+	private static final int ADDR_WELD_OFFSET = 4590;
+
+	private static final String DEFAULT_HOST = "10.0.20.141";
+	private static final int DEFAULT_PORT = 502;
+	private static final int DEFAULT_UNIT_ID = 1;
+
+	@Autowired
+	private MesProductRecordController mesProductRecordController;
+	@Autowired
+	private MesProductService mesProductService;
+	@Autowired
+	private MesLineService mesLineService;
+	@Autowired
+	private MesProductFswService mesProductFswService;
+
+	private final AtomicBoolean running = new AtomicBoolean(false);
+	private volatile ModbusTcp modbusTcp;
+	private volatile String lastCheckedSn;
+
+	/**
+	 * 每秒执行:工件码质量校验 + 过程参数采集握手
+	 */
+	@Scheduled(fixedRate = 1000)
+	public void collect() {
+		String enabled = Global.getConfig("mes.fsw.modbus.enabled");
+		if (StringUtils.isBlank(enabled) || !"true".equalsIgnoreCase(enabled.trim())) {
+			return;
+		}
+		if (!running.compareAndSet(false, true)) {
+			return;
+		}
+		try {
+			ModbusTcp plc = getClient();
+			handleQualityCheck(plc);
+			handleCollectTrigger(plc);
+		} catch (Exception e) {
+			logger.error("FSW Modbus任务失败:{}", e.getMessage());
+			closeClient();
+		} finally {
+			running.set(false);
+		}
+	}
+
+	/**
+	 * 读取工件码 → 质量校验 → 写回 3610(1合格 / 2不合格)
+	 */
+	private void handleQualityCheck(ModbusTcp plc) {
+		String sn = readWorkpieceCode(plc);
+		if (StringUtils.isBlank(sn)) {
+			lastCheckedSn = null;
+			return;
+		}
+		if (sn.equals(lastCheckedSn)) {
+			return;
+		}
+
+		boolean pass = doQualityCheck(sn);
+		plc.writeInt16(ADDR_QUALITY_RESULT, pass ? (short) 1 : (short) 2);
+		lastCheckedSn = sn;
+		logger.info("FSW质量校验 sn={} result={}", sn, pass ? 1 : 2);
+	}
+
+	/**
+	 * 3600=1 时采集过程参数,完成后置 0
+	 */
+	private void handleCollectTrigger(ModbusTcp plc) {
+		Short flag = plc.readInt16(ADDR_COLLECT_FLAG);
+		if (flag == null || flag != 1) {
+			return;
+		}
+
+		String sn = readWorkpieceCode(plc);
+		float feedRate = plc.readFloat32(ADDR_FEED_RATE, EByteBuffFormat.BA_DC);
+		float feedOverride = plc.readFloat32(ADDR_FEED_OVERRIDE, EByteBuffFormat.BA_DC);
+		float spindleSpeed = plc.readFloat32(ADDR_SPINDLE_SPEED, EByteBuffFormat.BA_DC);
+		float spindleOverride = plc.readFloat32(ADDR_SPINDLE_OVERRIDE, EByteBuffFormat.BA_DC);
+		float weldPressure = plc.readFloat32(ADDR_WELD_PRESSURE, EByteBuffFormat.BA_DC);
+		float weldOffset = plc.readFloat32(ADDR_WELD_OFFSET, EByteBuffFormat.BA_DC);
+
+		logger.info("FSW过程采集 sn={} 进给速度={} 进给倍率={} 主轴转速={} 主轴倍率={} 焊接压力={} 焊接偏移={}",
+				sn, feedRate, feedOverride, spindleSpeed, spindleOverride, weldPressure, weldOffset);
+
+		saveProcessData(sn, feedRate, feedOverride, spindleSpeed, spindleOverride, weldPressure, weldOffset);
+		plc.writeInt16(ADDR_COLLECT_FLAG, (short) 0);
+	}
+
+	private boolean doQualityCheck(String sn) {
+		String oprno = Global.getConfig("mes.fsw.modbus.oprno");
+		if (StringUtils.isBlank(oprno)) {
+			logger.warn("FSW质量校验失败:未配置 mes.fsw.modbus.oprno");
+			return false;
+		}
+		String oldOprno = oprno;
+		if (oldOprno.length() == 5) {
+			oldOprno = oldOprno + "A";
+		}
+
+		String lineSn = Global.getConfig("mes.fsw.modbus.lineSn");
+		if (StringUtils.isBlank(lineSn)) {
+			lineSn = resolveLineSn(sn);
+		}
+		if (StringUtils.isBlank(lineSn)) {
+			logger.warn("FSW质量校验失败:无法解析产线编号 sn={}", sn);
+			return false;
+		}
+
+		try {
+			String ret = mesProductRecordController.checkQualityCommon(oldOprno, sn, lineSn, "");
+			if (StringUtils.isBlank(ret) || ret.length() < 5) {
+				return false;
+			}
+			return "UD".equals(ret.substring(3, 5));
+		} catch (Exception e) {
+			logger.error("FSW质量校验异常 sn={}:{}", sn, e.getMessage());
+			return false;
+		}
+	}
+
+	private String resolveLineSn(String sn) {
+		MesProduct product = mesProductService.findBySn(sn);
+		if (ObjectUtils.isEmpty(product) || StringUtils.isBlank(product.getCate())) {
+			return null;
+		}
+		MesLine query = new MesLine();
+		query.setProductCate(product.getCate());
+		query.setStatus("0");
+		MesLine line = mesLineService.findInfo(query);
+		if (ObjectUtils.isEmpty(line)) {
+			return null;
+		}
+		return line.getSn();
+	}
+
+	private void saveProcessData(String sn, float feedRate, float feedOverride,
+			float spindleSpeed, float spindleOverride, float weldPressure, float weldOffset) {
+		if (StringUtils.isBlank(sn)) {
+			return;
+		}
+		try {
+			String oprno = Global.getConfig("mes.fsw.modbus.oprno");
+			String lineSn = Global.getConfig("mes.fsw.modbus.lineSn");
+			if (StringUtils.isBlank(lineSn)) {
+				lineSn = resolveLineSn(sn);
+			}
+
+			// 进给/主轴倍率、焊接偏移暂无附加字段落库,拼入对应字段便于追溯
+			MesProductFsw entity = new MesProductFsw();
+			entity.setSn(sn);
+			entity.setOprno(oprno);
+			entity.setLineSn(lineSn);
+			entity.setFeedRate(fmt(feedRate) + "/" + fmt(feedOverride));
+			entity.setSpindleSpeed(fmt(spindleSpeed) + "/" + fmt(spindleOverride));
+			entity.setPressure(fmt(weldPressure) + "/" + fmt(weldOffset));
+			mesProductFswService.save(entity);
+		} catch (Exception e) {
+			logger.error("FSW过程参数保存失败 sn={}:{}", sn, e.getMessage());
+		}
+	}
+
+	/**
+	 * 3620 起连续 30 个保持寄存器,按 ASCII 拼接工件码
+	 */
+	private String readWorkpieceCode(ModbusTcp plc) {
+		byte[] bytes = plc.readHoldRegister(ADDR_SN_START, SN_REGISTER_COUNT);
+		if (bytes == null || bytes.length == 0) {
+			return null;
+		}
+		String raw = new String(bytes, StandardCharsets.US_ASCII);
+		StringBuilder sb = new StringBuilder(raw.length());
+		for (int i = 0; i < raw.length(); i++) {
+			char c = raw.charAt(i);
+			if (c == 0 || c == ' ') {
+				continue;
+			}
+			if (c >= 32 && c < 127) {
+				sb.append(c);
+			}
+		}
+		return sb.toString().trim();
+	}
+
+	private ModbusTcp getClient() {
+		ModbusTcp client = modbusTcp;
+		if (client != null) {
+			return client;
+		}
+		synchronized (this) {
+			if (modbusTcp == null) {
+				String host = Global.getConfig("mes.fsw.modbus.host");
+				if (StringUtils.isBlank(host)) {
+					host = DEFAULT_HOST;
+				}
+				int port = parseInt(Global.getConfig("mes.fsw.modbus.port"), DEFAULT_PORT);
+				int unitId = parseInt(Global.getConfig("mes.fsw.modbus.unitId"), DEFAULT_UNIT_ID);
+				modbusTcp = new ModbusTcp(unitId, host, port);
+				logger.info("FSW Modbus已连接 {}:{} unitId={}", host, port, unitId);
+			}
+			return modbusTcp;
+		}
+	}
+
+	private void closeClient() {
+		synchronized (this) {
+			if (modbusTcp != null) {
+				try {
+					modbusTcp.close();
+				} catch (Exception ignore) {
+				}
+				modbusTcp = null;
+			}
+		}
+	}
+
+	private static int parseInt(String val, int defaultVal) {
+		if (StringUtils.isBlank(val)) {
+			return defaultVal;
+		}
+		try {
+			return Integer.parseInt(val.trim());
+		} catch (Exception e) {
+			return defaultVal;
+		}
+	}
+
+	private static String fmt(float val) {
+		return String.format("%.3f", val);
+	}
+}

+ 8 - 0
src/main/resources/config/application.yml

@@ -1089,3 +1089,11 @@ mes:
       url: opc.tcp://10.0.20.141:4840
       username: OpcUaClient
       password: oPCuA_123
+    # FSW Modbus TCP 握手(工件码质量校验 + 过程参数采集)
+    modbus:
+      enabled: false
+      host: 10.0.20.220
+      port: 502
+      unitId: 1
+      oprno:          # 工位号,必填,例如 OP210A
+      lineSn:         # 产线编号,可空(空则按工件类型自动解析)

+ 15 - 0
src/main/resources/mappings/modules/mes/MesProductJynyDao.xml

@@ -0,0 +1,15 @@
+<?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.MesProductJynyDao">
+	
+	<!-- 查询数据
+	<select id="findList" resultType="MesProductJyny">
+		SELECT ${sqlMap.column.toSql()}
+		FROM ${sqlMap.table.toSql()}
+		<where>
+			${sqlMap.where.toSql()}
+		</where>
+		ORDER BY ${sqlMap.order.toSql()}
+	</select> -->
+	
+</mapper>

+ 15 - 0
src/main/resources/mappings/modules/mes/MesScrewDao.xml

@@ -0,0 +1,15 @@
+<?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.MesScrewDao">
+	
+	<!-- 查询数据
+	<select id="findList" resultType="MesScrew">
+		SELECT ${sqlMap.column.toSql()}
+		FROM ${sqlMap.table.toSql()}
+		<where>
+			${sqlMap.where.toSql()}
+		</where>
+		ORDER BY ${sqlMap.order.toSql()}
+	</select> -->
+	
+</mapper>

+ 5 - 5
src/main/resources/views/modules/mes/mesProductCncParamDetail.html

@@ -44,15 +44,15 @@
 				<table class="table table-bordered param-kv">
 					<thead>
 						<tr>
-							<th style="width:180px;">${text('参数代码')}</th>
-							<th style="width:260px;">${text('参数名称')}</th>
+							<th style="width:320px;">${text('参数地址')}</th>
+							<th style="width:180px;">${text('参数名称')}</th>
 							<th>${text('参数值')}</th>
 						</tr>
 					</thead>
 					<tbody>
 						<% for(var item in paramItems){ %>
 						<tr>
-							<td>${item.code}</td>
+							<td style="text-align:left;">${item.code}</td>
 							<td>${item.label}</td>
 							<td>${item.value}</td>
 						</tr>
@@ -66,7 +66,7 @@
 							<tr>
 								<th>${text('序号')}</th>
 								<% for(var code in paramCodes){ %>
-								<th title="${code}">${paramLabels[code]}</th>
+								<th title="${paramLabels[code]}">${paramLabels[code]}</th>
 								<% } %>
 							</tr>
 						</thead>
@@ -75,7 +75,7 @@
 							<tr>
 								<td>${rowLP.index}</td>
 								<% for(var code in paramCodes){ %>
-								<td>${row[code] != null ? row[code] : '-'}</td>
+								<td>${row[code] != null && row[code] != '' ? row[code] : '-'}</td>
 								<% } %>
 							</tr>
 							<% } %>

+ 91 - 0
src/main/resources/views/modules/mes/mesProductJynyForm.html

@@ -0,0 +1,91 @@
+<% layout('/layouts/default.html', {title: '绝缘耐压数据表管理', libs: ['validate']}){ %>
+<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(mesProductJyny.isNewRecord ? '新增绝缘耐压数据表' : '编辑绝缘耐压数据表')}
+			</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>
+		<#form:form id="inputForm" model="${mesProductJyny}" action="${ctx}/mes/mesProductJyny/save" method="post" class="form-horizontal">
+			<div class="box-body">
+				<div class="form-unit">${text('基本信息')}</div>
+				<#form:hidden path="id"/>
+				<div class="row">
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required hide">*</span> ${text('工件码')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="sn" maxlength="64" class="form-control"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required hide">*</span> ${text('绝缘电阻')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="dzData" maxlength="255" class="form-control"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required hide">*</span> ${text('漏电电流')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="ldData" maxlength="255" class="form-control"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required hide">*</span> ${text('工位结果')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="result" maxlength="64" class="form-control"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required hide">*</span> ${text('工位号')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="oprno" maxlength="64" class="form-control"/>
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+			<div class="box-footer">
+				<div class="row">
+					<div class="col-sm-offset-2 col-sm-10">
+						<% if (hasPermi('mes:mesProductJyny:edit')){ %>
+							<button type="submit" class="btn btn-sm btn-primary" id="btnSubmit"><i class="fa fa-check"></i> ${text('保 存')}</button>&nbsp;
+						<% } %>
+						<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>
+		</#form:form>
+	</div>
+</div>
+<% } %>
+<script>
+$("#inputForm").validate({
+	submitHandler: function(form){
+		js.ajaxSubmitForm($(form), function(data){
+			js.showMessage(data.message);
+			if(data.result == Global.TRUE){
+				js.closeCurrentTabPage(function(contentWindow){
+					contentWindow.page();
+				});
+			}
+		}, "json");
+    }
+});
+</script>

+ 87 - 0
src/main/resources/views/modules/mes/mesProductJynyList.html

@@ -0,0 +1,87 @@
+<% 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>
+				<% if(hasPermi('mes:mesProductJyny:edit')){ %>
+					<a href="${ctx}/mes/mesProductJyny/form" class="btn btn-default btnTool" title="${text('新增绝缘耐压数据表')}"><i class="fa fa-plus"></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="${mesProductJyny}" action="${ctx}/mes/mesProductJyny/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="64" class="form-control width-120"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('绝缘电阻')}:</label>
+					<div class="control-inline">
+						<#form:input path="dzData" maxlength="255" class="form-control width-120"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('漏电电流')}:</label>
+					<div class="control-inline">
+						<#form:input path="ldData" maxlength="255" class="form-control width-120"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('工位结果')}:</label>
+					<div class="control-inline">
+						<#form:input path="result" maxlength="64" class="form-control width-120"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('工位号')}:</label>
+					<div class="control-inline">
+						<#form:input path="oprno" maxlength="64" class="form-control width-120"/>
+					</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>
+<% } %>
+<script>
+// 初始化DataGrid对象
+$('#dataGrid').dataGrid({
+	searchForm: $("#searchForm"),
+	columnModel: [
+		{header:'${text("工件码")}', name:'sn', index:'a.sn', width:150, align:"left", frozen:true, formatter: function(val, obj, row, act){
+			return '<a href="${ctx}/mes/mesProductJyny/form?id='+row.id+'" class="btnList" data-title="${text("编辑绝缘耐压数据表")}">'+(val||row.id)+'</a>';
+		}},
+		{header:'${text("绝缘电阻")}', name:'dzData', index:'a.dz_data', width:150, align:"left"},
+		{header:'${text("漏电电流")}', name:'ldData', index:'a.ld_data', width:150, align:"left"},
+		{header:'${text("工位结果")}', name:'result', index:'a.result', width:150, align:"left"},
+		{header:'${text("更新时间")}', name:'updateDate', index:'a.update_date', width:150, align:"center"},
+		{header:'${text("工位号")}', name:'oprno', index:'a.oprno', width:150, align:"left"},
+		{header:'${text("操作")}', name:'actions', width:120, formatter: function(val, obj, row, act){
+			var actions = [];
+			//<% if(hasPermi('mes:mesProductJyny:edit')){ %>
+				actions.push('<a href="${ctx}/mes/mesProductJyny/form?id='+row.id+'" class="btnList" title="${text("编辑绝缘耐压数据表")}"><i class="fa fa-pencil"></i></a>&nbsp;');
+				actions.push('<a href="${ctx}/mes/mesProductJyny/delete?id='+row.id+'" class="btnList" title="${text("删除绝缘耐压数据表")}" data-confirm="${text("确认要删除该绝缘耐压数据表吗?")}"><i class="fa fa-trash-o"></i></a>&nbsp;');
+			//<% } %>
+			return actions.join('');
+		}}
+	],
+	// 加载成功后执行事件
+	ajaxSuccess: function(data){
+		
+	}
+});
+</script>

+ 55 - 0
src/main/resources/views/modules/mes/mesScrewForm.html

@@ -0,0 +1,55 @@
+<% layout('/layouts/default.html', {title: 'mes_screw管理', libs: ['validate']}){ %>
+<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(mesScrew.isNewRecord ? '新增mes_screw' : '编辑mes_screw')}
+			</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>
+		<#form:form id="inputForm" model="${mesScrew}" action="${ctx}/mes/mesScrew/save" method="post" class="form-horizontal">
+			<div class="box-body">
+				<div class="form-unit">${text('基本信息')}</div>
+				<#form:hidden path="id"/>
+				<div class="row">
+					<div class="col-xs-12">
+						<div class="form-group">
+							<label class="control-label col-sm-2" title="">
+								<span class="required hide">*</span> ${text('add')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-4">
+								<#form:input path="ww" maxlength="255" class="form-control"/>
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+			<div class="box-footer">
+				<div class="row">
+					<div class="col-sm-offset-2 col-sm-10">
+						<% if (hasPermi('mes:mesScrew:edit')){ %>
+							<button type="submit" class="btn btn-sm btn-primary" id="btnSubmit"><i class="fa fa-check"></i> ${text('保 存')}</button>&nbsp;
+						<% } %>
+						<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>
+		</#form:form>
+	</div>
+</div>
+<% } %>
+<script>
+$("#inputForm").validate({
+	submitHandler: function(form){
+		js.ajaxSubmitForm($(form), function(data){
+			js.showMessage(data.message);
+			if(data.result == Global.TRUE){
+				js.closeCurrentTabPage(function(contentWindow){
+					contentWindow.page();
+				});
+			}
+		}, "json");
+    }
+});
+</script>

+ 58 - 0
src/main/resources/views/modules/mes/mesScrewList.html

@@ -0,0 +1,58 @@
+<% layout('/layouts/default.html', {title: 'mes_screw管理', 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('mes_screw管理')}
+			</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>
+				<% if(hasPermi('mes:mesScrew:edit')){ %>
+					<a href="${ctx}/mes/mesScrew/form" class="btn btn-default btnTool" title="${text('新增mes_screw')}"><i class="fa fa-plus"></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="${mesScrew}" action="${ctx}/mes/mesScrew/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('add')}:</label>
+					<div class="control-inline">
+						<#form:input path="ww" maxlength="255" class="form-control width-120"/>
+					</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>
+<% } %>
+<script>
+// 初始化DataGrid对象
+$('#dataGrid').dataGrid({
+	searchForm: $("#searchForm"),
+	columnModel: [
+		{header:'${text("add")}', name:'ww', index:'a.ww', width:150, align:"left", frozen:true, formatter: function(val, obj, row, act){
+			return '<a href="${ctx}/mes/mesScrew/form?id='+row.id+'" class="btnList" data-title="${text("编辑mes_screw")}">'+(val||row.id)+'</a>';
+		}},
+		{header:'${text("操作")}', name:'actions', width:120, formatter: function(val, obj, row, act){
+			var actions = [];
+			//<% if(hasPermi('mes:mesScrew:edit')){ %>
+				actions.push('<a href="${ctx}/mes/mesScrew/form?id='+row.id+'" class="btnList" title="${text("编辑mes_screw")}"><i class="fa fa-pencil"></i></a>&nbsp;');
+				actions.push('<a href="${ctx}/mes/mesScrew/delete?id='+row.id+'" class="btnList" title="${text("删除mes_screw")}" data-confirm="${text("确认要删除该mes_screw吗?")}"><i class="fa fa-trash-o"></i></a>&nbsp;');
+			//<% } %>
+			return actions.join('');
+		}}
+	],
+	// 加载成功后执行事件
+	ajaxSuccess: function(data){
+		
+	}
+});
+</script>