瀏覽代碼

OP220-240 QM优化定时任务

sunxianglong 2 月之前
父節點
當前提交
97f2a49f8b
共有 27 個文件被更改,包括 559 次插入 和 275 次删除
  1. 7 2
      src/main/java/com/jeesite/modules/erp/service/MesProductOrderService.java
  2. 3 3
      src/main/java/com/jeesite/modules/mes/entity/MesMaterialBfRecordItems.java
  3. 6 6
      src/main/java/com/jeesite/modules/mes/entity/MesMaterialCheckRecord.java
  4. 3 3
      src/main/java/com/jeesite/modules/mes/entity/MesMaterialCkLog.java
  5. 3 3
      src/main/java/com/jeesite/modules/mes/entity/MesMaterialKcLog.java
  6. 6 6
      src/main/java/com/jeesite/modules/mes/entity/MesMaterialLlRecordItems.java
  7. 9 9
      src/main/java/com/jeesite/modules/mes/entity/MesMaterialRk.java
  8. 3 3
      src/main/java/com/jeesite/modules/mes/entity/MesMaterialRkLog.java
  9. 6 6
      src/main/java/com/jeesite/modules/mes/entity/MesMaterialRkRecordItems.java
  10. 3 3
      src/main/java/com/jeesite/modules/mes/entity/MesMaterialThRecordItems.java
  11. 10 1
      src/main/java/com/jeesite/modules/mes/service/MesMaterialBfRecordService.java
  12. 8 4
      src/main/java/com/jeesite/modules/mes/web/MesMaterialBfRecordController.java
  13. 1 1
      src/main/java/com/jeesite/modules/mes/web/MesMaterialCheckRecordController.java
  14. 43 29
      src/main/java/com/jeesite/modules/mes/web/MesMaterialLlRecordController.java
  15. 7 9
      src/main/java/com/jeesite/modules/mes/web/MesMaterialRkController.java
  16. 33 22
      src/main/java/com/jeesite/modules/mes/web/MesMaterialRkRecordController.java
  17. 2 2
      src/main/java/com/jeesite/modules/mes/web/MesPlanController.java
  18. 28 0
      src/main/java/com/jeesite/modules/mes/web/MesProductGp12Controller.java
  19. 2 1
      src/main/java/com/jeesite/modules/mes/web/MesProductReportController.java
  20. 64 61
      src/main/java/com/jeesite/modules/scheduling/FrameworkQmScheduling.java
  21. 11 5
      src/main/java/com/jeesite/modules/scheduling/FswModbusScheduling.java
  22. 5 78
      src/main/java/com/jeesite/modules/scheduling/ProductTjScheduling.java
  23. 9 9
      src/main/resources/config/application.yml
  24. 4 4
      src/main/resources/views/modules/mes/mesMaterialCheckRecordFormEdit.html
  25. 2 2
      src/main/resources/views/modules/mes/mesMaterialReportForm.html
  26. 3 3
      src/main/resources/views/modules/mes/mesMaterialRkForm.html
  27. 278 0
      src/test/java/com/jeesite/test/ProductTjQualityCheckTest.java

+ 7 - 2
src/main/java/com/jeesite/modules/erp/service/MesProductOrderService.java

@@ -107,7 +107,8 @@ public class MesProductOrderService extends CrudService<MesProductOrderDao, MesP
 	}
 
 	/**
-	 * nowNums = 同生产订单号(sn = moDocNo)下完工报告入库数量 completeQty 之和(仅填充返回值,不落库)
+	 * nowNums = 同生产订单号(sn = moDocNo)下完工报告入库数量 completeQty 之和
+	 * (仅统计 doc_no 不为空的记录;仅填充返回值,不落库)
 	 */
 	public void fillNowNumsFromProductReport(List<MesProductOrder> orders) {
 		if (orders == null || orders.isEmpty()) {
@@ -125,10 +126,14 @@ public class MesProductOrderService extends CrudService<MesProductOrderDao, MesP
 		}
 		MesProductReport query = new MesProductReport();
 		query.getSqlMap().getWhere().and("mo_doc_no", QueryType.IN, sns);
+		query.getSqlMap().getWhere().and("doc_no", QueryType.IS_NOT_NULL, "");
+		query.getSqlMap().getWhere().and("doc_no", QueryType.NE, "");
 		List<MesProductReport> reports = mesProductReportService.findList(query);
 		Map<String, BigDecimal> sumMap = new HashMap<>();
 		for (MesProductReport report : reports) {
-			if (StringUtils.isBlank(report.getMoDocNo()) || StringUtils.isBlank(report.getCompleteQty())) {
+			if (StringUtils.isBlank(report.getDocNo())
+					|| StringUtils.isBlank(report.getMoDocNo())
+					|| StringUtils.isBlank(report.getCompleteQty())) {
 				continue;
 			}
 			try {

+ 3 - 3
src/main/java/com/jeesite/modules/mes/entity/MesMaterialBfRecordItems.java

@@ -42,7 +42,7 @@ public class MesMaterialBfRecordItems extends DataEntity<MesMaterialBfRecordItem
 	private static final long serialVersionUID = 1L;
 	private String recordId;		// 记录id
 	private String materialId;		// 物料id
-	private Long nums;		// 报废数量
+	private String nums;		// 报废数量(支持小数)
 	private String materialTitle;
 	private String kcId;
 	private String batchSn;
@@ -75,11 +75,11 @@ public class MesMaterialBfRecordItems extends DataEntity<MesMaterialBfRecordItem
 		this.materialId = materialId;
 	}
 	
-	public Long getNums() {
+	public String getNums() {
 		return nums;
 	}
 
-	public void setNums(Long nums) {
+	public void setNums(String nums) {
 		this.nums = nums;
 	}
 

+ 6 - 6
src/main/java/com/jeesite/modules/mes/entity/MesMaterialCheckRecord.java

@@ -44,8 +44,8 @@ public class MesMaterialCheckRecord extends DataEntity<MesMaterialCheckRecord> {
 	private static final long serialVersionUID = 1L;
 	private String materialId;		// 物料
 	private String faDate;		// 发货日期
-	private Long faNums;		// 发货数量
-	private Long nums;		// 实际数量
+	private String faNums;		// 发货数量(支持小数)
+	private String nums;		// 实际数量(支持小数)
 
 	private String hgQty;   //合格数量
 	private String bhgQty;  //不合格数量
@@ -96,19 +96,19 @@ public class MesMaterialCheckRecord extends DataEntity<MesMaterialCheckRecord> {
 		this.faDate = faDate;
 	}
 
-	public Long getFaNums() {
+	public String getFaNums() {
 		return faNums;
 	}
 
-	public void setFaNums(Long faNums) {
+	public void setFaNums(String faNums) {
 		this.faNums = faNums;
 	}
 
-	public Long getNums() {
+	public String getNums() {
 		return nums;
 	}
 
-	public void setNums(Long nums) {
+	public void setNums(String nums) {
 		this.nums = nums;
 	}
 

+ 3 - 3
src/main/java/com/jeesite/modules/mes/entity/MesMaterialCkLog.java

@@ -42,7 +42,7 @@ public class MesMaterialCkLog extends DataEntity<MesMaterialCkLog> {
 	private String llRecordId;		// 领料单id
 	private String materialId;		// 物料
 	private String batchSn;		// 批次码
-	private Long nums;		// 数量
+	private String nums;		// 数量(支持小数)
 	private Date createDate;
 	private String materialTitle;
 	private String remark;
@@ -82,11 +82,11 @@ public class MesMaterialCkLog extends DataEntity<MesMaterialCkLog> {
 		this.batchSn = batchSn;
 	}
 	
-	public Long getNums() {
+	public String getNums() {
 		return nums;
 	}
 
-	public void setNums(Long nums) {
+	public void setNums(String nums) {
 		this.nums = nums;
 	}
 	

+ 3 - 3
src/main/java/com/jeesite/modules/mes/entity/MesMaterialKcLog.java

@@ -57,7 +57,7 @@ public class MesMaterialKcLog extends DataEntity<MesMaterialKcLog> {
 	private String materialId;		// 物料
 	private String batchSn;		// 批次码
 	private String kwId;		// 库位
-	private Long nums;		// 数量
+	private String nums;		// 数量(支持小数)
 	private Date createDate;
 	private String materialTitle;
 	private String erpSn;
@@ -113,11 +113,11 @@ public class MesMaterialKcLog extends DataEntity<MesMaterialKcLog> {
 		this.kwId = kwId;
 	}
 	
-	public Long getNums() {
+	public String getNums() {
 		return nums;
 	}
 
-	public void setNums(Long nums) {
+	public void setNums(String nums) {
 		this.nums = nums;
 	}
 	

+ 6 - 6
src/main/java/com/jeesite/modules/mes/entity/MesMaterialLlRecordItems.java

@@ -36,8 +36,8 @@ public class MesMaterialLlRecordItems extends DataEntity<MesMaterialLlRecordItem
 	private static final long serialVersionUID = 1L;
 	private String recordId;		// 记录id
 	private String materialId;		// 物料id
-	private Long nums;		// 数量
-	private Long numd;
+	private String nums;		// 数量(支持小数)
+	private String numd;
 	private String materialTitle;
 	private List ckLog = ListUtils.newArrayList();
 
@@ -67,11 +67,11 @@ public class MesMaterialLlRecordItems extends DataEntity<MesMaterialLlRecordItem
 		this.materialId = materialId;
 	}
 	
-	public Long getNums() {
+	public String getNums() {
 		return nums;
 	}
 
-	public void setNums(Long nums) {
+	public void setNums(String nums) {
 		this.nums = nums;
 	}
 
@@ -83,11 +83,11 @@ public class MesMaterialLlRecordItems extends DataEntity<MesMaterialLlRecordItem
 		this.materialTitle = materialTitle;
 	}
 
-	public Long getNumd() {
+	public String getNumd() {
 		return numd;
 	}
 
-	public void setNumd(Long numd) {
+	public void setNumd(String numd) {
 		this.numd = numd;
 	}
 	public List getCkLog() {

+ 9 - 9
src/main/java/com/jeesite/modules/mes/entity/MesMaterialRk.java

@@ -51,9 +51,9 @@ public class MesMaterialRk extends DataEntity<MesMaterialRk> {
 	private String srcDocNo;		// 采购订单号
 	private Long srcDocLineNo;		// 采购订单行号
 	private String lotCode;		// 批号
-	private Long qty;		// 合格数量
-	private Long rtnDeductQty;		// 退补数量
-	private Long rtnFillQty;		// 退扣数量
+	private String qty;		// 合格数量(支持小数)
+	private String rtnDeductQty;		// 退补数量(支持小数)
+	private String rtnFillQty;		// 退扣数量(支持小数)
 
 
 	public String getSn() {
@@ -149,27 +149,27 @@ public class MesMaterialRk extends DataEntity<MesMaterialRk> {
 		this.lotCode = lotCode;
 	}
 
-	public Long getQty() {
+	public String getQty() {
 		return qty;
 	}
 
-	public void setQty(Long qty) {
+	public void setQty(String qty) {
 		this.qty = qty;
 	}
 
-	public Long getRtnDeductQty() {
+	public String getRtnDeductQty() {
 		return rtnDeductQty;
 	}
 
-	public void setRtnDeductQty(Long rtnDeductQty) {
+	public void setRtnDeductQty(String rtnDeductQty) {
 		this.rtnDeductQty = rtnDeductQty;
 	}
 
-	public Long getRtnFillQty() {
+	public String getRtnFillQty() {
 		return rtnFillQty;
 	}
 
-	public void setRtnFillQty(Long rtnFillQty) {
+	public void setRtnFillQty(String rtnFillQty) {
 		this.rtnFillQty = rtnFillQty;
 	}
 

+ 3 - 3
src/main/java/com/jeesite/modules/mes/entity/MesMaterialRkLog.java

@@ -46,7 +46,7 @@ public class MesMaterialRkLog extends DataEntity<MesMaterialRkLog> {
 	private static final long serialVersionUID = 1L;
 	private String rkRecordId;		// 入库单id
 	private String materialId;		// 物料
-	private Long nums;		// 数量
+	private String nums;		// 数量(支持小数)
 	private String batchSn;		// 批次码
 	private String kwId;		// 库位
 	private String expireDate;		// 过期时间
@@ -80,11 +80,11 @@ public class MesMaterialRkLog extends DataEntity<MesMaterialRkLog> {
 		this.materialId = materialId;
 	}
 	
-	public Long getNums() {
+	public String getNums() {
 		return nums;
 	}
 
-	public void setNums(Long nums) {
+	public void setNums(String nums) {
 		this.nums = nums;
 	}
 	

+ 6 - 6
src/main/java/com/jeesite/modules/mes/entity/MesMaterialRkRecordItems.java

@@ -39,8 +39,8 @@ public class MesMaterialRkRecordItems extends DataEntity<MesMaterialRkRecordItem
 	private static final long serialVersionUID = 1L;
 	private String recordId;		// 记录id
 	private String materialId;		// 物料id
-	private Long nums;		// 入库数量
-	private Long numd;		// 已入库数量
+	private String nums;		// 入库数量(支持小数)
+	private String numd;		// 已入库数量
 	private String materialTitle;
 	private String type;
 	private String oldBatchSn;
@@ -72,11 +72,11 @@ public class MesMaterialRkRecordItems extends DataEntity<MesMaterialRkRecordItem
 		this.materialId = materialId;
 	}
 	
-	public Long getNums() {
+	public String getNums() {
 		return nums;
 	}
 
-	public void setNums(Long nums) {
+	public void setNums(String nums) {
 		this.nums = nums;
 	}
 
@@ -96,11 +96,11 @@ public class MesMaterialRkRecordItems extends DataEntity<MesMaterialRkRecordItem
 		this.type = type;
 	}
 
-	public Long getNumd() {
+	public String getNumd() {
 		return numd;
 	}
 
-	public void setNumd(Long numd) {
+	public void setNumd(String numd) {
 		this.numd = numd;
 	}
 	public List getRkLog() {

+ 3 - 3
src/main/java/com/jeesite/modules/mes/entity/MesMaterialThRecordItems.java

@@ -34,7 +34,7 @@ public class MesMaterialThRecordItems extends DataEntity<MesMaterialThRecordItem
 	private static final long serialVersionUID = 1L;
 	private String recordId;		// 记录id
 	private String materialId;		// 物料id
-	private Long nums;		// 入库数量
+	private String nums;		// 入库数量(支持小数)
 	private String materialTitle;
 
 	public MesMaterialThRecordItems() {
@@ -63,11 +63,11 @@ public class MesMaterialThRecordItems extends DataEntity<MesMaterialThRecordItem
 		this.materialId = materialId;
 	}
 	
-	public Long getNums() {
+	public String getNums() {
 		return nums;
 	}
 
-	public void setNums(Long nums) {
+	public void setNums(String nums) {
 		this.nums = nums;
 	}
 

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

@@ -1,5 +1,6 @@
 package com.jeesite.modules.mes.service;
 
+import java.math.BigDecimal;
 import java.util.List;
 
 import com.jeesite.modules.mes.entity.*;
@@ -113,10 +114,18 @@ public class MesMaterialBfRecordService extends CrudService<MesMaterialBfRecordD
 					MesMaterialKcLog mesMaterialKcLog = new MesMaterialKcLog();
 					mesMaterialKcLog.setId(im.getKcId());
 					MesMaterialKcLog ml = mesMaterialKcLogService.get(mesMaterialKcLog);
-					ml.setNums(ml.getNums()-im.getNums());
+					BigDecimal remain = toBd(ml.getNums()).subtract(toBd(im.getNums()));
+					ml.setNums(remain.stripTrailingZeros().toPlainString());
 					mesMaterialKcLogService.save(ml);
 				}
 			}
 		}
 	}
+
+	private static BigDecimal toBd(String value) {
+		if (value == null || value.trim().isEmpty()) {
+			return BigDecimal.ZERO;
+		}
+		return new BigDecimal(value.trim());
+	}
 }

+ 8 - 4
src/main/java/com/jeesite/modules/mes/web/MesMaterialBfRecordController.java

@@ -1,5 +1,6 @@
 package com.jeesite.modules.mes.web;
 
+import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
@@ -160,10 +161,13 @@ public class MesMaterialBfRecordController extends BaseController {
 			return renderResult(Global.FALSE, text("请选择入库记录"));
 		}
 		for (int i=0;i<mecheck.size();i=i+1){
-			if(!mecheck.getJSONObject(i).containsKey("num") || mecheck.getJSONObject(i).getString("num").equals("")|| Integer.valueOf(mecheck.getJSONObject(i).getString("num"))==0){
+			String num = mecheck.getJSONObject(i).getString("num");
+			String nums = mecheck.getJSONObject(i).getString("nums");
+			if(!mecheck.getJSONObject(i).containsKey("num") || StringUtils.isEmpty(num)
+					|| new BigDecimal(num).compareTo(BigDecimal.ZERO) == 0){
 				return renderResult(Global.FALSE, text("请输入物料报废数量"));
 			}
-			if(Integer.valueOf(mecheck.getJSONObject(i).getString("num")) > Integer.valueOf(mecheck.getJSONObject(i).getString("nums"))){
+			if(new BigDecimal(num).compareTo(new BigDecimal(nums)) > 0){
 				return renderResult(Global.FALSE, text("报废数量超过入库数量"));
 			}
 		}
@@ -176,8 +180,8 @@ public class MesMaterialBfRecordController extends BaseController {
 			mesMaterialBfRecordItems.setKcId(mecheck.getJSONObject(i).getString("id"));
 			mesMaterialBfRecordItems.setRecordId(mesMaterialBfRecord.getId());
 			mesMaterialBfRecordItems.setMaterialId(mecheck.getJSONObject(i).getString("materialId"));
-			Long s = Long.valueOf(mecheck.getJSONObject(i).getString("num"));
-			mesMaterialBfRecordItems.setNums(s);
+			BigDecimal s = new BigDecimal(mecheck.getJSONObject(i).getString("num"));
+			mesMaterialBfRecordItems.setNums(s.stripTrailingZeros().toPlainString());
 			mesMaterialBfRecordItemsService.save(mesMaterialBfRecordItems);
 		}
 

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

@@ -146,7 +146,7 @@ public class MesMaterialCheckRecordController extends BaseController {
 		}
 
 		mesMaterialCheckRecord.setState(result);
-		if (mesMaterialCheckRecord.getNums() == null && mesMaterialCheckRecord.getFaNums() != null) {
+		if (StringUtils.isBlank(mesMaterialCheckRecord.getNums()) && StringUtils.isNotBlank(mesMaterialCheckRecord.getFaNums())) {
 			mesMaterialCheckRecord.setNums(mesMaterialCheckRecord.getFaNums());
 		}
 		mesMaterialCheckRecordService.save(mesMaterialCheckRecord);

+ 43 - 29
src/main/java/com/jeesite/modules/mes/web/MesMaterialLlRecordController.java

@@ -1,5 +1,6 @@
 package com.jeesite.modules.mes.web;
 
+import java.math.BigDecimal;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Date;
@@ -119,7 +120,9 @@ public class MesMaterialLlRecordController extends BaseController {
 			return renderResult(Global.FALSE, text("请选择物料"));
 		}
 		for (int i=0;i<mecheck.size();i=i+1){
-			if(!mecheck.getJSONObject(i).containsKey("num") || mecheck.getJSONObject(i).getString("num").equals("")|| Integer.valueOf(mecheck.getJSONObject(i).getString("num"))==0){
+			String num = mecheck.getJSONObject(i).getString("num");
+			if(!mecheck.getJSONObject(i).containsKey("num") || StringUtils.isEmpty(num)
+					|| new BigDecimal(num).compareTo(BigDecimal.ZERO) == 0){
 				return renderResult(Global.FALSE, text("请输入领料数量"));
 			}
 		}
@@ -131,8 +134,9 @@ public class MesMaterialLlRecordController extends BaseController {
 			MesMaterialLlRecordItems mesMaterialLlRecordItems = new MesMaterialLlRecordItems();
 			mesMaterialLlRecordItems.setRecordId(mesMaterialLlRecord.getId());
 			mesMaterialLlRecordItems.setMaterialId(mecheck.getJSONObject(i).getString("id"));
-			Long s = Long.valueOf(mecheck.getJSONObject(i).getString("num"))*Long.valueOf(mecheck.getJSONObject(i).getString("nums"));
-			mesMaterialLlRecordItems.setNums(s);
+			BigDecimal s = new BigDecimal(mecheck.getJSONObject(i).getString("num"))
+					.multiply(new BigDecimal(mecheck.getJSONObject(i).getString("nums")));
+			mesMaterialLlRecordItems.setNums(s.stripTrailingZeros().toPlainString());
 			mesMaterialLlRecordItemsService.save(mesMaterialLlRecordItems);
 		}
 		return renderResult(Global.TRUE, text("保存领料记录表成功!"));
@@ -200,12 +204,11 @@ public class MesMaterialLlRecordController extends BaseController {
 			mesMaterialCkLog.setLlRecordId(item.getRecordId());
 			mesMaterialCkLog.setMaterialId(item.getMaterialId());
 			List<MesMaterialCkLog> numdList = mesMaterialCkLogService.findList(mesMaterialCkLog);
-			Long s = 0L;
+			BigDecimal s = BigDecimal.ZERO;
 			for (MesMaterialCkLog i:numdList){
-				Long ss = i.getNums();
-				s+=ss;
+				s = s.add(toBd(i.getNums()));
 			}
-			item.setNumd(s);
+			item.setNumd(strip(s));
 //			MesMaterialCkLog mesMaterialCkLog1 = new MesMaterialCkLog();
 //			mesMaterialCkLog1.setLlRecordId(mesMaterialLlRecord.getId());
 //			mesMaterialCkLog1.setMaterialId(item.getMaterialId());
@@ -242,12 +245,11 @@ public class MesMaterialLlRecordController extends BaseController {
 		mesMaterialCkLog.setLlRecordId(recordId);
 		mesMaterialCkLog.setMaterialId(materialId);
 		List<MesMaterialCkLog> numdList = mesMaterialCkLogService.findList(mesMaterialCkLog);
-		Long s = 0L;
+		BigDecimal s = BigDecimal.ZERO;
 		for (MesMaterialCkLog i:numdList){
-			Long ss = i.getNums();
-			s+=ss;
+			s = s.add(toBd(i.getNums()));
 		}
-		mmllitem.get(0).setNumd(s);
+		mmllitem.get(0).setNumd(strip(s));
 		mmllitem.get(0).setCkLog(numdList);
 		resp.setData(mmllitem.get(0));
 		resp.setResult(Global.TRUE);
@@ -286,15 +288,15 @@ public class MesMaterialLlRecordController extends BaseController {
 		}
 
 
-		Long llnum = 0L;//领料数量
-		Long cknum = 0L;//出库数量
+		BigDecimal llnum = BigDecimal.ZERO;//领料数量
+		BigDecimal cknum = BigDecimal.ZERO;//出库数量
 
 		MesMaterialLlRecordItems mesMaterialLlRecordItems = new MesMaterialLlRecordItems();
 		mesMaterialLlRecordItems.setRecordId(recordId);
 		mesMaterialLlRecordItems.setMaterialId(materialId);
 		List<MesMaterialLlRecordItems> numdList = mesMaterialLlRecordItemsService.findList(mesMaterialLlRecordItems);
 		for (MesMaterialLlRecordItems i:numdList){
-			llnum+=i.getNums();
+			llnum = llnum.add(toBd(i.getNums()));
 		}
 
 		MesMaterialCkLog mesMaterialCkLog1 = new MesMaterialCkLog();
@@ -302,9 +304,9 @@ public class MesMaterialLlRecordController extends BaseController {
 		mesMaterialCkLog1.setMaterialId(materialId);
 		List<MesMaterialCkLog> cknumdList = mesMaterialCkLogService.findList(mesMaterialCkLog1);
 		for (MesMaterialCkLog i:cknumdList){
-			cknum+=i.getNums();
+			cknum = cknum.add(toBd(i.getNums()));
 		}
-		if(cknum.equals(llnum)){
+		if(cknum.compareTo(llnum) == 0){
 			resp.setMessage("当前物料已全部出库");
 			return resp;
 		}
@@ -319,7 +321,7 @@ public class MesMaterialLlRecordController extends BaseController {
 			resp.setMessage("当前批次对应物料不匹配");
 			return resp;
 		}
-		if(kcInfo.get(0).getNums()==0){
+		if(toBd(kcInfo.get(0).getNums()).compareTo(BigDecimal.ZERO) == 0){
 			resp.setMessage("当前批次已出库");
 			return resp;
 		}
@@ -349,41 +351,42 @@ public class MesMaterialLlRecordController extends BaseController {
 			return resp;
 		}
 
-		Long nnm = 0L;
-		if(cknum+kcInfo.get(0).getNums() <= llnum){
-			nnm = kcInfo.get(0).getNums();
+		BigDecimal nnm;
+		BigDecimal kcNums = toBd(kcInfo.get(0).getNums());
+		if(cknum.add(kcNums).compareTo(llnum) <= 0){
+			nnm = kcNums;
 		}else {
-			nnm = llnum-cknum;
+			nnm = llnum.subtract(cknum);
 		}
 		MesMaterialCkLog mesMaterialCkLog = new MesMaterialCkLog();
 		mesMaterialCkLog.setLlRecordId(recordId);
 		mesMaterialCkLog.setMaterialId(materialId);
 		mesMaterialCkLog.setBatchSn(kcInfo.get(0).getBatchSn());
 		mesMaterialCkLog.setRemark(remark);
-		mesMaterialCkLog.setNums(nnm);
+		mesMaterialCkLog.setNums(strip(nnm));
 		mesMaterialCkLogService.save(mesMaterialCkLog);
 
 
-		kcInfo.get(0).setNums(kcInfo.get(0).getNums()-nnm);
+		kcInfo.get(0).setNums(strip(kcNums.subtract(nnm)));
 		mesMaterialKcLogService.save(kcInfo.get(0));
 
 
-		Long llnum1 = 0L;//领料数量
-		Long cknum1 = 0L;//出库数量
+		BigDecimal llnum1 = BigDecimal.ZERO;//领料数量
+		BigDecimal cknum1 = BigDecimal.ZERO;//出库数量
 		MesMaterialLlRecordItems mesMaterialLlRecordItems1 = new MesMaterialLlRecordItems();
 		mesMaterialLlRecordItems1.setRecordId(recordId);
 		List<MesMaterialLlRecordItems> numdList1 = mesMaterialLlRecordItemsService.findList(mesMaterialLlRecordItems1);
 		for (MesMaterialLlRecordItems i:numdList1){
-			llnum1+=i.getNums();
+			llnum1 = llnum1.add(toBd(i.getNums()));
 		}
 
 		MesMaterialCkLog mesMaterialCkLog2 = new MesMaterialCkLog();
 		mesMaterialCkLog2.setLlRecordId(recordId);
 		List<MesMaterialCkLog> cknumdList1 = mesMaterialCkLogService.findList(mesMaterialCkLog2);
 		for (MesMaterialCkLog i:cknumdList1){
-			cknum1+=i.getNums();
+			cknum1 = cknum1.add(toBd(i.getNums()));
 		}
-		if(cknum1.equals(llnum1)){
+		if(cknum1.compareTo(llnum1) == 0){
 			mml.setState("2");
 			mesMaterialLlRecordService.save(mml);
 		}
@@ -439,10 +442,21 @@ public class MesMaterialLlRecordController extends BaseController {
 		if(ListUtils.isEmpty(kcInfo)){
 			return renderResult(Global.FALSE, text("库存记录不存在!"));
 		}
-		kcInfo.get(0).setNums(kcInfo.get(0).getNums()+ml.get(0).getNums());
+		kcInfo.get(0).setNums(strip(toBd(kcInfo.get(0).getNums()).add(toBd(ml.get(0).getNums()))));
 
 		mesMaterialKcLogService.save(kcInfo.get(0));
 		mesMaterialCkLogService.delete(ml.get(0));
 		return renderResult(Global.TRUE, text("操作成功!"));
 	}
+
+	private static BigDecimal toBd(String value) {
+		if (value == null || value.trim().isEmpty()) {
+			return BigDecimal.ZERO;
+		}
+		return new BigDecimal(value.trim());
+	}
+
+	private static String strip(BigDecimal value) {
+		return value.stripTrailingZeros().toPlainString();
+	}
 }

+ 7 - 9
src/main/java/com/jeesite/modules/mes/web/MesMaterialRkController.java

@@ -297,15 +297,13 @@ public class MesMaterialRkController extends BaseController {
 	@RequestMapping(value = "mesToErp")
 	@ResponseBody
 	public String mesToErp(MesMaterialRk mesMaterialRk) {
-		MesMaterialRk erpCode=new MesMaterialRk();
-		erpCode.setLotCode(mesMaterialRk.getLotCode());
-		List<MesMaterialRk> list = mesMaterialRkService.findList(erpCode);
-		if (list.isEmpty()){
-			mesMaterialRkService.save(mesMaterialRk);
-		}
 		if (mesMaterialRk == null) {
 			return renderResult(Global.FALSE, text("采购入库单不存在!"));
 		}
+		// 每次提交都新增入库记录(不因同批号已存在而跳过)
+		mesMaterialRk.setId(null);
+		mesMaterialRk.setIsNewRecord(true);
+		mesMaterialRkService.save(mesMaterialRk);
 
 		String requestJson = buildCreateRcvJson(mesMaterialRk);
 		JSONObject response = U9cAuthUtil.createRCV(requestJson);
@@ -355,9 +353,9 @@ public class MesMaterialRkController extends BaseController {
 		line.put("SrcDocNo", StringUtils.defaultIfBlank(mesMaterialRk.getSrcDocNo(), ""));
 		line.put("SrcDocLineNo", mesMaterialRk.getSrcDocLineNo());
 		line.put("LotCode", StringUtils.defaultIfBlank(mesMaterialRk.getLotCode(), ""));
-		line.put("Qty", mesMaterialRk.getQty());
-		line.put("RtnDeductQty", mesMaterialRk.getRtnDeductQty());
-		line.put("RtnFillQty", mesMaterialRk.getRtnFillQty());
+		line.put("Qty", StringUtils.defaultIfBlank(mesMaterialRk.getQty(), ""));
+		line.put("RtnDeductQty", StringUtils.defaultIfBlank(mesMaterialRk.getRtnDeductQty(), ""));
+		line.put("RtnFillQty", StringUtils.defaultIfBlank(mesMaterialRk.getRtnFillQty(), ""));
 
 		JSONArray lineDtos = new JSONArray();
 		lineDtos.add(line);

+ 33 - 22
src/main/java/com/jeesite/modules/mes/web/MesMaterialRkRecordController.java

@@ -1,5 +1,6 @@
 package com.jeesite.modules.mes.web;
 
+import java.math.BigDecimal;
 import java.text.SimpleDateFormat;
 import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
@@ -138,7 +139,9 @@ public class MesMaterialRkRecordController extends BaseController {
 			return renderResult(Global.FALSE, text("请选择物料"));
 		}
 		for (int i=0;i<mecheck.size();i=i+1){
-			if(!mecheck.getJSONObject(i).containsKey("num") || mecheck.getJSONObject(i).getString("num").equals("")|| Integer.valueOf(mecheck.getJSONObject(i).getString("num"))==0){
+			String num = mecheck.getJSONObject(i).getString("num");
+			if(!mecheck.getJSONObject(i).containsKey("num") || StringUtils.isBlank(num)
+					|| new BigDecimal(num).compareTo(BigDecimal.ZERO) == 0){
 				return renderResult(Global.FALSE, text("请输入入库数量"));
 			}
 		}
@@ -152,8 +155,7 @@ public class MesMaterialRkRecordController extends BaseController {
 			MesMaterialRkRecordItems mesMaterialRkRecordItems = new MesMaterialRkRecordItems();
 			mesMaterialRkRecordItems.setRecordId(mesMaterialRkRecord.getId());
 			mesMaterialRkRecordItems.setMaterialId(mecheck.getJSONObject(i).getString("id"));
-			Long s = Long.valueOf(mecheck.getJSONObject(i).getString("num"));
-			mesMaterialRkRecordItems.setNums(s);
+			mesMaterialRkRecordItems.setNums(mecheck.getJSONObject(i).getString("num"));
 			mesMaterialRkRecordItems.setOldBatchSn(mecheck.getJSONObject(i).getString("oldBatchSn"));
 			logger.info(JSON.toJSONString(mecheck.getJSONObject(i)));
 			logger.info(JSON.toJSONString(mesMaterialRkRecordItems));
@@ -195,12 +197,11 @@ public class MesMaterialRkRecordController extends BaseController {
 			mesMaterialRkLog.setRkRecordId(item.getRecordId());
 			mesMaterialRkLog.setMaterialId(item.getMaterialId());
 			List<MesMaterialRkLog> numdList = mesMaterialRkLogService.findList(mesMaterialRkLog);
-		    Long s = 0L;
+		    BigDecimal s = BigDecimal.ZERO;
 		    for (MesMaterialRkLog i:numdList){
-				  Long ss = i.getNums();
-				  s+=ss;
+				  s = s.add(toBd(i.getNums()));
 			}
-			item.setNumd(s);
+			item.setNumd(strip(s));
 
 			MesMaterialRkLog mesMaterialRkLog1 = new MesMaterialRkLog();
 			mesMaterialRkLog1.setRkRecordId(mesMaterialRkRecord.getId());
@@ -253,12 +254,11 @@ public class MesMaterialRkRecordController extends BaseController {
 		mesMaterialKcLog.setRkRecordId(recordId);
 		mesMaterialKcLog.setMaterialId(materialId);
 		List<MesMaterialKcLog> numdList = mesMaterialKcLogService.findList(mesMaterialKcLog);
-		Long s = 0L;
+		BigDecimal s = BigDecimal.ZERO;
 		for (MesMaterialKcLog i:numdList){
-			Long ss = i.getNums();
-			s+=ss;
+			s = s.add(toBd(i.getNums()));
 		}
-		if(s.equals(rkRecordItems.get(0).getNums())){
+		if(s.compareTo(toBd(rkRecordItems.get(0).getNums())) == 0){
 			return renderResult(Global.FALSE, text("当前物料已全部入库"));
 		}
 
@@ -294,7 +294,7 @@ public class MesMaterialRkRecordController extends BaseController {
 						list.add(codeList[x]);
 					}
 				}
-		    	if(list.size() !=rkRecordItems.get(0).getNums()){
+		    	if(list.size() != toBd(rkRecordItems.get(0).getNums()).intValue()){
 					return renderResult(Global.FALSE, text("精追码数量有误"));
 				}
 				for(String item:list){
@@ -303,7 +303,7 @@ public class MesMaterialRkRecordController extends BaseController {
 					mesMaterialKcLog1.setMaterialId(materialId);
 					mesMaterialKcLog1.setBatchSn(item);
 					mesMaterialKcLog1.setKwId(kwId);
-					mesMaterialKcLog1.setNums(1L);
+					mesMaterialKcLog1.setNums("1");
 					mesMaterialKcLog1.setExpireDate(expire);
 					mesMaterialKcLogService.save(mesMaterialKcLog1);
 
@@ -312,19 +312,19 @@ public class MesMaterialRkRecordController extends BaseController {
 					mesMaterialRkLog1.setMaterialId(materialId);
 					mesMaterialRkLog1.setBatchSn(item);
 					mesMaterialRkLog1.setKwId(kwId);
-					mesMaterialRkLog1.setNums(1L);
+					mesMaterialRkLog1.setNums("1");
 					mesMaterialRkLog1.setExpireDate(expire);
 					mesMaterialRkLogService.save(mesMaterialRkLog1);
 				}
 
 			}else {
-				for (int i = 1; i <= rkRecordItems.get(0).getNums(); i++) {
+				for (int i = 1; i <= toBd(rkRecordItems.get(0).getNums()).intValue(); i++) {
 					MesMaterialKcLog mesMaterialKcLog12 = new MesMaterialKcLog();
 					mesMaterialKcLog12.setRkRecordId(recordId);
 					mesMaterialKcLog12.setMaterialId(materialId);
 					mesMaterialKcLog12.setBatchSn(orderNum+"_"+i);
 					mesMaterialKcLog12.setKwId(kwId);
-					mesMaterialKcLog12.setNums(1L);
+					mesMaterialKcLog12.setNums("1");
 					mesMaterialKcLog12.setExpireDate(expire);
 					mesMaterialKcLogService.save(mesMaterialKcLog12);
 
@@ -334,7 +334,7 @@ public class MesMaterialRkRecordController extends BaseController {
 					mesMaterialRkLog.setMaterialId(materialId);
 					mesMaterialRkLog.setBatchSn(orderNum+"_"+i);
 					mesMaterialRkLog.setKwId(kwId);
-					mesMaterialRkLog.setNums(1L);
+					mesMaterialRkLog.setNums("1");
 					mesMaterialRkLog.setExpireDate(expire);
 					mesMaterialRkLogService.save(mesMaterialRkLog);
 				}
@@ -387,21 +387,32 @@ public class MesMaterialRkRecordController extends BaseController {
 		mesMaterialRkLog1.setRkRecordId(recordId);
 		List<MesMaterialRkLog> oldsList= mesMaterialRkLogService.findList(mesMaterialRkLog1);
 
-		Long s1 = 0L;
-		Long s2 = 0L;
+		BigDecimal s1 = BigDecimal.ZERO;
+		BigDecimal s2 = BigDecimal.ZERO;
 		for (MesMaterialRkRecordItems it:oldList){
-			s1+=it.getNums();
+			s1 = s1.add(toBd(it.getNums()));
 		}
 		for (MesMaterialRkLog it1:oldsList){
-			s2+=it1.getNums();
+			s2 = s2.add(toBd(it1.getNums()));
 		}
-		if(s1.equals(s2)){
+		if(s1.compareTo(s2) == 0){
 			minfo.get(0).setState("2");
 			mesMaterialRkRecordService.save(minfo.get(0));
 		}
 		return renderResult(Global.TRUE, text("操作成功!"));
 	}
 
+	private static BigDecimal toBd(String value) {
+		if (StringUtils.isBlank(value)) {
+			return BigDecimal.ZERO;
+		}
+		return new BigDecimal(value.trim());
+	}
+
+	private static String strip(BigDecimal value) {
+		return value.stripTrailingZeros().toPlainString();
+	}
+
 	public static Date addMonths(Date date, int months) {
 		Calendar cal = Calendar.getInstance();
 		cal.setTime(date);

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

@@ -149,8 +149,8 @@ public class MesPlanController extends BaseController {
 
 				mesMaterialLlRecordItems.setRecordId(mesMaterialLlRecord1.getId());
 				mesMaterialLlRecordItems.setMaterialId(item.getId());
-				Long s = Long.valueOf(item.getNums()*mesPlan.getNums());
-				mesMaterialLlRecordItems.setNums(s);
+				long s = (long) item.getNums() * mesPlan.getNums();
+				mesMaterialLlRecordItems.setNums(String.valueOf(s));
 				mesMaterialLlRecordItemsService.save(mesMaterialLlRecordItems);
 			}
 

+ 28 - 0
src/main/java/com/jeesite/modules/mes/web/MesProductGp12Controller.java

@@ -1,5 +1,6 @@
 package com.jeesite.modules.mes.web;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import javax.servlet.http.HttpServletRequest;
@@ -10,7 +11,12 @@ import com.jeesite.common.mybatis.mapper.query.QueryType;
 import com.jeesite.modules.file.entity.FileUpload;
 import com.jeesite.modules.file.service.FileUploadService;
 import com.jeesite.modules.file.utils.FileUploadUtils;
+import com.jeesite.modules.mes.resp.BindMaterialResp;
 import com.jeesite.modules.mes.resp.CommonResp;
+import com.jeesite.modules.mes.resp.ParamsResp;
+import com.jeesite.modules.mes.service.MesProductRecordService;
+import com.jeesite.modules.sys.entity.User;
+import com.jeesite.modules.sys.utils.UserUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -47,6 +53,9 @@ public class MesProductGp12Controller extends BaseController {
 
 	@Autowired
 	protected FileUploadService fileUploadService;
+
+	@Autowired
+	private MesProductRecordService mesProductRecordService;
 	/**
 	 * 获取数据
 	 */
@@ -198,6 +207,25 @@ public class MesProductGp12Controller extends BaseController {
 		mesProductGp12Service.save(entry);
 
 		FileUploadUtils.saveFileUpload(entry, entry.getId(), "mesProductGp12_image");
+
+		// 图片提交成功后,将该工位结果提交为 OK
+		String stationOprno = oprno;
+		if (stationOprno.length() == 5) {
+			stationOprno = stationOprno + "A";
+		}
+		String userCode = "";
+		User user = UserUtils.getUser();
+		if (user != null && StringUtils.isNotBlank(user.getUserCode())) {
+			userCode = user.getUserCode();
+		}
+		try {
+			List<ParamsResp> params = new ArrayList<>();
+			List<BindMaterialResp> bmlists = new ArrayList<>();
+			mesProductRecordService.updateRecordResult(sn, stationOprno, "100000", "", "OK",
+					userCode, params, lineSn, true, bmlists, null);
+		} catch (Exception e) {
+			return renderResult(Global.FALSE, StringUtils.isNotBlank(e.getMessage()) ? e.getMessage() : "工位结果提交失败");
+		}
 		return renderResult(Global.TRUE, "操作成功");
 	}
 

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

@@ -164,7 +164,8 @@ public class MesProductReportController extends BaseController {
 			return renderResult(Global.FALSE, text("同步ERP成功,但未找到对应生产订单!"));
 		}
 		mesProductOrder = list.get(0);
-		mesProductOrder.setNowNums(Integer.parseInt(mesProductReport.getCompleteQty()));
+		mesProductOrder.setNowNums(new java.math.BigDecimal(
+				StringUtils.defaultIfBlank(mesProductReport.getCompleteQty(), "0")).intValue());
 		mesProductOrderService.update(mesProductOrder);
 
 		// 同步成功后写入报废/入库数量统计表

+ 64 - 61
src/main/java/com/jeesite/modules/scheduling/FrameworkQmScheduling.java

@@ -29,25 +29,28 @@ import java.util.concurrent.atomic.AtomicBoolean;
 /**
  * 框架气密 S7 采集与质量握手(OP360)。
  * <pre>
- * DB200 点位:
- *   0.0   质量校验通过反馈(MES 写 true)
- *   2.0   框架码 String[30]
- *   34.0  扫码反馈信号
- *   36.0  液冷板充气时间 Real
- *   40.0  液冷板测试时间 Real
- *   44.0  液冷板充气压力 Real
- *   48.0  液冷板稳定压力 Real
- *   52.0  液冷板泄漏量 Real
- *   56.0  框架充气时间 Real
- *   60.0  框架测试时间 Real
- *   64.0  框架充气压力 Real
- *   68.0  框架稳定压力 Real
- *   72.0  框架泄露量 Real
- *   76.0  过程数据采集触发
- *   78.0  液冷板保压时间 Real
- *   82.0  框架保压时间 Real
- *   86.0  液冷板判定结果 Bool
- *   86.1  框架判定结果 Bool
+ * DB9100 控制点位:
+ *  48.0   液冷板判定结果 Bool
+ *  48.1   框架判定结果 Bool
+ *  48.2   过程数据采集触发 Bool
+ * 112.0   质量校验通过反馈(MES 写 true)
+ * 112.1   扫码反馈信号(校验通过后 MES 写 false 复位)
+ * 114.0   扫码框架码 String[60]
+ *  50.0   气密框架码 String[60](提交数据及工位结果使用)
+ *
+ * DB9100 过程数据(Real):
+ *   0.0   液冷板充气时间
+ *   4.0   液冷板保压时间
+ *   8.0   液冷板测试时间
+ *  12.0   液冷板充气压力
+ *  16.0   液冷板稳定压力
+ *  20.0   液冷板泄漏量
+ *  24.0   框架充气时间
+ *  28.0   框架保压时间
+ *  32.0   框架测试时间
+ *  36.0   框架充气压力
+ *  40.0   框架稳定压力
+ *  44.0   框架泄漏量
  *
  * 配置(application.yml):
  *   mes.framework.qm.enabled   是否启用(默认 false)
@@ -62,27 +65,28 @@ public class FrameworkQmScheduling {
 
 	private static final Logger logger = LoggerFactory.getLogger(FrameworkQmScheduling.class);
 
-	private static final String ADDR_QUALITY_PASS = "DB200.0.0";
-	private static final String ADDR_SN = "DB200.2.0";
-	private static final int SN_LENGTH = 30;
-	private static final String ADDR_SCAN_FEEDBACK = "DB200.34.0";
+	private static final String ADDR_QUALITY_PASS = "DB9100.112.0";
+	private static final String ADDR_SCAN_SN = "DB9100.114.0";
+	private static final String ADDR_QM_SN = "DB9100.50.0";
+	private static final int SN_LENGTH = 60;
+	private static final String ADDR_SCAN_FEEDBACK = "DB9100.112.1";
 
-	private static final String ADDR_YLB_INFLATION_TIME = "DB200.36.0";
-	private static final String ADDR_YLB_TEST_TIME = "DB200.40.0";
-	private static final String ADDR_YLB_INFLATION_PRESSURE = "DB200.44.0";
-	private static final String ADDR_YLB_STABLE_PRESSURE = "DB200.48.0";
-	private static final String ADDR_YLB_LEAK = "DB200.52.0";
-	private static final String ADDR_YLB_HOLD_TIME = "DB200.78.0";
+	private static final String ADDR_YLB_INFLATION_TIME = "DB9100.0.0";
+	private static final String ADDR_YLB_HOLD_TIME = "DB9100.4.0";
+	private static final String ADDR_YLB_TEST_TIME = "DB9100.8.0";
+	private static final String ADDR_YLB_INFLATION_PRESSURE = "DB9100.12.0";
+	private static final String ADDR_YLB_STABLE_PRESSURE = "DB9100.16.0";
+	private static final String ADDR_YLB_LEAK = "DB9100.20.0";
 
-	private static final String ADDR_INFLATION_TIME = "DB200.56.0";
-	private static final String ADDR_TEST_TIME = "DB200.60.0";
-	private static final String ADDR_INFLATION_PRESSURE = "DB200.64.0";
-	private static final String ADDR_STABLE_PRESSURE = "DB200.68.0";
-	private static final String ADDR_LEAK_AMOUNT = "DB200.72.0";
-	private static final String ADDR_COLLECT_FLAG = "DB200.76.0";
-	private static final String ADDR_HOLD_TIME = "DB200.82.0";
-	private static final String ADDR_YLB_JUDGE_RESULT = "DB200.86.0";
-	private static final String ADDR_JUDGE_RESULT = "DB200.86.1";
+	private static final String ADDR_INFLATION_TIME = "DB9100.24.0";
+	private static final String ADDR_HOLD_TIME = "DB9100.28.0";
+	private static final String ADDR_TEST_TIME = "DB9100.32.0";
+	private static final String ADDR_INFLATION_PRESSURE = "DB9100.36.0";
+	private static final String ADDR_STABLE_PRESSURE = "DB9100.40.0";
+	private static final String ADDR_LEAK_AMOUNT = "DB9100.44.0";
+	private static final String ADDR_COLLECT_FLAG = "DB9100.48.2";
+	private static final String ADDR_YLB_JUDGE_RESULT = "DB9100.48.0";
+	private static final String ADDR_JUDGE_RESULT = "DB9100.48.1";
 
 	private static final String DEFAULT_PLC_IP = "192.168.1.100";
 	private static final String DEFAULT_OPRNO = "OP360";
@@ -101,7 +105,7 @@ public class FrameworkQmScheduling {
 
 	private final AtomicBoolean running = new AtomicBoolean(false);
 	private volatile String lastCheckedSn;
-	private volatile String lastKnownSn;
+	private volatile String lastKnownScanSn;
 
 	@Scheduled(fixedRate = 1000)
 	public void collect() {
@@ -139,17 +143,17 @@ public class FrameworkQmScheduling {
 	}
 
 	/**
-	 * 扫码反馈为 true:读框架码 → 质量校验 → 通过则写 DB200.0.0=true
+	 * 扫码反馈为 true:读扫码框架码 → 质量校验 → 通过则写 DB9100.112.0=true,并将 DB9100.112.1 复位为 false
 	 */
 	private void handleScanFeedback(S7PLC s7PLC) {
 		if (!s7PLC.readBoolean(ADDR_SCAN_FEEDBACK)) {
 			return;
 		}
-		String sn = readWorkpieceCode(s7PLC);
+		String sn = readWorkpieceCode(s7PLC, ADDR_SCAN_SN);
 		if (StringUtils.isBlank(sn)) {
 			return;
 		}
-		lastKnownSn = sn;
+		lastKnownScanSn = sn;
 		if (sn.equals(lastCheckedSn)) {
 			return;
 		}
@@ -157,7 +161,9 @@ public class FrameworkQmScheduling {
 		boolean pass = doQualityCheck(sn);
 		if (pass) {
 			s7PLC.writeBoolean(ADDR_QUALITY_PASS, true);
-			logger.info("框架气密质量校验通过 sn={},已写 {}=true", sn, ADDR_QUALITY_PASS);
+			s7PLC.writeBoolean(ADDR_SCAN_FEEDBACK, false);
+			logger.info("框架气密质量校验通过 sn={},已写 {}=true,已将 {} 复位为 false",
+					sn, ADDR_QUALITY_PASS, ADDR_SCAN_FEEDBACK);
 		} else {
 			logger.warn("框架气密质量校验未通过 sn={}", sn);
 		}
@@ -172,15 +178,11 @@ public class FrameworkQmScheduling {
 			return;
 		}
 
-		String sn = readWorkpieceCode(s7PLC);
-		if (StringUtils.isBlank(sn)) {
-			sn = lastKnownSn;
-		}
-		if (StringUtils.isBlank(sn)) {
-			logger.error("框架气密采集失败:无工件码,保持 {}=true 待重试", ADDR_COLLECT_FLAG);
+		String qmSn = readWorkpieceCode(s7PLC, ADDR_QM_SN);
+		if (StringUtils.isBlank(qmSn)) {
+			logger.error("框架气密采集失败:无气密框架码,保持 {}=true 待重试", ADDR_COLLECT_FLAG);
 			return;
 		}
-		lastKnownSn = sn;
 
 		float ylbInflationTime = s7PLC.readFloat32(ADDR_YLB_INFLATION_TIME);
 		float ylbTestTime = s7PLC.readFloat32(ADDR_YLB_TEST_TIME);
@@ -200,27 +202,27 @@ public class FrameworkQmScheduling {
 		String ylbStationResult = ylbJudgePass ? "OK" : "NG";
 		String stationResult = judgePass ? "OK" : "NG";
 
-		logger.info("框架气密采集 sn={} 液冷板[充气时间={} 测试时间={} 充气压力={} 稳定压力={} 泄漏量={} 保压时间={} 判定={}] "
+		logger.info("框架气密采集 qmSn={} 液冷板[充气时间={} 测试时间={} 充气压力={} 稳定压力={} 泄漏量={} 保压时间={} 判定={}] "
 						+ "框架[充气时间={} 测试时间={} 充气压力={} 稳定压力={} 泄露量={} 保压时间={} 判定={}]",
-				sn, ylbInflationTime, ylbTestTime, ylbInflationPressure, ylbStablePressure, ylbLeak, ylbHoldTime, ylbStationResult,
+				qmSn, ylbInflationTime, ylbTestTime, ylbInflationPressure, ylbStablePressure, ylbLeak, ylbHoldTime, ylbStationResult,
 				inflationTime, testTime, inflationPressure, stablePressure, leakAmount, holdTime, stationResult);
 
-		if (!saveQmzcData(ylbInflationTime, ylbTestTime, ylbInflationPressure, ylbStablePressure, ylbLeak, ylbHoldTime,
+		if (!saveQmzcData(qmSn, ylbInflationTime, ylbTestTime, ylbInflationPressure, ylbStablePressure, ylbLeak, ylbHoldTime,
 				inflationTime, testTime, inflationPressure, stablePressure, leakAmount, holdTime,
 				ylbStationResult, stationResult)) {
-			logger.error("框架气密数据保存失败,保持 {}=true 待重试 sn={}", ADDR_COLLECT_FLAG, sn);
+			logger.error("框架气密数据保存失败,保持 {}=true 待重试 qmSn={}", ADDR_COLLECT_FLAG, qmSn);
 			return;
 		}
-		if (!submitStationResult(sn, stationResult)) {
-			logger.error("框架气密工位结果提交失败,保持 {}=true 待重试 sn={}", ADDR_COLLECT_FLAG, sn);
+		if (!submitStationResult(qmSn, stationResult)) {
+			logger.error("框架气密工位结果提交失败,保持 {}=true 待重试 qmSn={}", ADDR_COLLECT_FLAG, qmSn);
 			return;
 		}
 
 		s7PLC.writeBoolean(ADDR_COLLECT_FLAG, false);
-		logger.info("框架气密采集完成 sn={} result={},已将 {} 置 false", sn, stationResult, ADDR_COLLECT_FLAG);
+		logger.info("框架气密采集完成 qmSn={} result={},已将 {} 置 false", qmSn, stationResult, ADDR_COLLECT_FLAG);
 	}
 
-	private boolean saveQmzcData(float ylbInflationTime, float ylbTestTime, float ylbInflationPressure,
+	private boolean saveQmzcData(String qmSn, float ylbInflationTime, float ylbTestTime, float ylbInflationPressure,
 			float ylbStablePressure, float ylbLeak, float ylbHoldTime,
 			float inflationTime, float testTime, float inflationPressure, float stablePressure,
 			float leakAmount, float holdTime, String ylbStationResult, String stationResult) {
@@ -241,9 +243,10 @@ public class FrameworkQmScheduling {
 			entity.setBysj(fmt(holdTime));
 			entity.setResult(stationResult);
 			mesProductQmzcService.save(entity);
+			logger.info("框架气密数据已保存 qmSn={}", qmSn);
 			return true;
 		} catch (Exception e) {
-			logger.error("框架气密数据保存异常:{}", e.getMessage());
+			logger.error("框架气密数据保存异常 qmSn={}:{}", qmSn, e.getMessage());
 			return false;
 		}
 	}
@@ -310,8 +313,8 @@ public class FrameworkQmScheduling {
 		return line.getSn();
 	}
 
-	private String readWorkpieceCode(S7PLC s7PLC) {
-		String raw = s7PLC.readString(ADDR_SN, SN_LENGTH);
+	private String readWorkpieceCode(S7PLC s7PLC, String address) {
+		String raw = s7PLC.readString(address, SN_LENGTH);
 		if (StringUtils.isBlank(raw)) {
 			return null;
 		}

+ 11 - 5
src/main/java/com/jeesite/modules/scheduling/FswModbusScheduling.java

@@ -32,7 +32,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
  * 寄存器约定:
  *   4740  总使能(1=执行整套逻辑,其它值跳过)
  *   3600  数据采集触发(1=请求采集,MES读完后置0)
- *   3610  质量校验结果(1=合格,2=不合格)
+ *   3610  质量校验结果(0=待校验/已复位,1=合格,2=不合格;MES 写 1/2 后同一 SN 不重复校验,3610 回到 0 后可再校验)
  *   3620~3649  工件码(30个字,ASCII拼接)
  *   3740  进给速度 → feedSpeed
  *   3780  进给倍率 → feedRate
@@ -72,13 +72,13 @@ public class FswModbusScheduling {
 	private static final int ADDR_WELD_OFFSET = 4590;
 	private static final int ADDR_B_AXIS_ANGLE = 4742;
 
-	private static final String DEFAULT_HOST = "10.0.20.220";
+	private static final String DEFAULT_HOST = "10.0.40.101";
 	private static final int DEFAULT_PORT = 502;
 	private static final int DEFAULT_UNIT_ID = 1;
 
 	/** 过程参数落库固定产线 / 工位 */
 	private static final String FIXED_LINE_SN = "XT";
-	private static final String FIXED_OPRNO = "OP230B";
+	private static final String FIXED_OPRNO = "OP230";
 
 	@Autowired
 	private MesProductRecordController mesProductRecordController;
@@ -93,7 +93,7 @@ public class FswModbusScheduling {
 
 	private final AtomicBoolean running = new AtomicBoolean(false);
 	private volatile ModbusTcp modbusTcp;
-	/** 质量校验去重:同一工件码不重复校验 */
+	/** 质量校验去重:同一工件码仅校验一次,3610 复位为 0 后清空 */
 	private volatile String lastCheckedSn;
 	/** 最近一次非空工件码(采集时 PLC 可能已清空 3620,用此兜底) */
 	private volatile String lastKnownSn;
@@ -189,9 +189,15 @@ public class FswModbusScheduling {
 	}
 
 	/**
-	 * 读取工件码 → 质量校验 → 写回 3610(1合格 / 2不合格)
+	 * 读取工件码 → 质量校验 → 写回 3610(1合格 / 2不合格)。
+	 * 同一工件码只校验一次;PLC 将 3610 置 0 后允许再次校验。
 	 */
 	private void handleQualityCheck(ModbusTcp plc) {
+		Short qualityFlag = plc.readInt16(ADDR_QUALITY_RESULT);
+		if (qualityFlag == null || qualityFlag == 0) {
+			lastCheckedSn = null;
+		}
+
 		String sn = readWorkpieceCode(plc);
 		if (StringUtils.isBlank(sn)) {
 			// 不清空 lastKnownSn:采集触发时寄存器可能已被 PLC 清掉

+ 5 - 78
src/main/java/com/jeesite/modules/scheduling/ProductTjScheduling.java

@@ -8,10 +8,7 @@ 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.MesProductTj;
-import com.jeesite.modules.mes.resp.BindMaterialResp;
-import com.jeesite.modules.mes.resp.ParamsResp;
 import com.jeesite.modules.mes.service.MesLineService;
-import com.jeesite.modules.mes.service.MesProductRecordService;
 import com.jeesite.modules.mes.service.MesProductService;
 import com.jeesite.modules.mes.service.MesProductTjService;
 import com.jeesite.modules.mes.web.MesProductRecordController;
@@ -22,19 +19,16 @@ import org.springframework.context.annotation.Configuration;
 import org.springframework.scheduling.annotation.EnableScheduling;
 import org.springframework.scheduling.annotation.Scheduled;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
- * 涂胶 S7 采集与质量握手(OP240)。
+ * 涂胶 S7 采集与质量握手(OP220,固定工位号,不提交工位结果)。
  * <pre>
  * DB1300 点位:
  *   0.0    框架码 String[30]
  *   64.0   扫码反馈信号
- *   64.2   质量校验通过反馈(校验通过 MES 写 true,工位完成后 MES 写 false)
+ *   64.2   质量校验通过反馈(校验通过 MES 写 true)
  *   64.4   涂胶数据采集触发
- *   64.5   整体完成信号
  *   66.0   涂胶序号 Word
  *   68.0   涂胶速度 Real
  *   72.0   涂胶长度 Word
@@ -42,7 +36,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
  * 配置(application.yml):
  *   mes.tj.enabled       是否启用(默认 false)
  *   mes.tj.plc.ip        PLC IP,默认 10.0.40.100
- *   mes.tj.oprno         工位号,默认 OP240
  *   mes.tj.lineSn        产线编号,默认 XT
  * </pre>
  */
@@ -57,13 +50,12 @@ public class ProductTjScheduling {
 	private static final String ADDR_SCAN_FEEDBACK = "DB1300.64.0";
 	private static final String ADDR_QUALITY_PASS = "DB1300.64.2";
 	private static final String ADDR_GLUE_COLLECT = "DB1300.64.4";
-	private static final String ADDR_COMPLETE = "DB1300.64.5";
 	private static final String ADDR_GLUE_SEQ = "DB1300.66.0";
 	private static final String ADDR_GLUE_SPEED = "DB1300.68.0";
 	private static final String ADDR_GLUE_LENGTH = "DB1300.72.0";
 
 	private static final String DEFAULT_PLC_IP = "10.0.40.100";
-	private static final String DEFAULT_OPRNO = "OP240";
+	private static final String OPRNO = "OP220";
 	private static final String DEFAULT_LINE_SN = "XT";
 
 	@Autowired
@@ -74,13 +66,10 @@ public class ProductTjScheduling {
 	private MesLineService mesLineService;
 	@Autowired
 	private MesProductTjService mesProductTjService;
-	@Autowired
-	private MesProductRecordService mesProductRecordService;
 
 	private final AtomicBoolean running = new AtomicBoolean(false);
 	private volatile String lastCheckedSn;
 	private volatile String lastKnownSn;
-	private volatile String lastCompletedSn;
 
 	@Scheduled(fixedRate = 1000)
 	public void collect() {
@@ -104,7 +93,6 @@ public class ProductTjScheduling {
 			s7PLC = new S7PLC(EPlcType.S1200, ip);
 			handleScanFeedback(s7PLC);
 			handleGlueCollect(s7PLC);
-			handleComplete(s7PLC);
 		} catch (Exception e) {
 			logger.error("涂胶S7任务失败[{}]:{}", ip, e.getMessage());
 		} finally {
@@ -124,7 +112,6 @@ public class ProductTjScheduling {
 	private void handleScanFeedback(S7PLC s7PLC) {
 		if (!s7PLC.readBoolean(ADDR_SCAN_FEEDBACK)) {
 			lastCheckedSn = null;
-			lastCompletedSn = null;
 			return;
 		}
 		String sn = readWorkpieceCode(s7PLC);
@@ -179,42 +166,11 @@ public class ProductTjScheduling {
 		logger.info("涂胶数据采集完成 sn={} 序号={},已将 {} 置 false", sn, glueSeq, ADDR_GLUE_COLLECT);
 	}
 
-	/**
-	 * 整体完成信号为 true:提交 OP240 工位 OK → 成功后将 DB1300.64.2 置 false
-	 */
-	private void handleComplete(S7PLC s7PLC) {
-		if (!s7PLC.readBoolean(ADDR_COMPLETE)) {
-			return;
-		}
-
-		String sn = readWorkpieceCode(s7PLC);
-		if (StringUtils.isBlank(sn)) {
-			sn = lastKnownSn;
-		}
-		if (StringUtils.isBlank(sn)) {
-			logger.error("涂胶工位完成提交失败:无工件码,保持 {}=true 待重试", ADDR_COMPLETE);
-			return;
-		}
-		if (sn.equals(lastCompletedSn)) {
-			return;
-		}
-
-		if (!submitStationResult(sn, "OK")) {
-			logger.error("涂胶工位结果提交失败,保持信号待重试 sn={}", sn);
-			return;
-		}
-
-		s7PLC.writeBoolean(ADDR_QUALITY_PASS, false);
-		lastCompletedSn = sn;
-		lastCheckedSn = null;
-		logger.info("涂胶工位完成 sn={},已提交 OK 并将 {} 置 false", sn, ADDR_QUALITY_PASS);
-	}
-
 	private boolean saveTjData(String sn, int glueSeq, float glueSpeed, int glueLength) {
 		try {
 			MesProductTj entity = new MesProductTj();
 			entity.setSn(sn);
-			entity.setOprno(getOprno());
+			entity.setOprno(OPRNO);
 			entity.setTjData(String.valueOf(glueSeq));
 			entity.setTjSpeed(fmt(glueSpeed));
 			entity.setTjLength(String.valueOf(glueLength));
@@ -226,32 +182,8 @@ public class ProductTjScheduling {
 		}
 	}
 
-	private boolean submitStationResult(String sn, String result) {
-		try {
-			String oprno = getOprno();
-			if (oprno.length() == 5) {
-				oprno = oprno + "A";
-			}
-			String lineSn = getLineSn(sn);
-			if (StringUtils.isBlank(lineSn)) {
-				logger.warn("涂胶工位结果提交失败:无法解析产线编号 sn={}", sn);
-				return false;
-			}
-			List<ParamsResp> params = new ArrayList<>();
-			List<BindMaterialResp> bmlists = new ArrayList<>();
-			mesProductRecordService.updateRecordResult(sn, oprno, "100000", "", result,
-					"", params, lineSn, true, bmlists, null);
-			logger.info("涂胶工位结果已提交 sn={} oprno={} result={}", sn, oprno, result);
-			return true;
-		} catch (Exception e) {
-			logger.error("涂胶工位结果提交异常 sn={}:{}", sn, e.getMessage());
-			return false;
-		}
-	}
-
 	private boolean doQualityCheck(String sn) {
-		String oprno = getOprno();
-		String oldOprno = oprno.length() == 5 ? oprno + "A" : oprno;
+		String oldOprno = OPRNO + "A";
 		String lineSn = getLineSn(sn);
 		if (StringUtils.isBlank(lineSn)) {
 			logger.warn("涂胶质量校验失败:无法解析产线编号 sn={}", sn);
@@ -311,11 +243,6 @@ public class ProductTjScheduling {
 		return StringUtils.isBlank(ip) ? DEFAULT_PLC_IP : ip.trim();
 	}
 
-	private String getOprno() {
-		String oprno = Global.getConfig("mes.tj.oprno");
-		return StringUtils.isBlank(oprno) ? DEFAULT_OPRNO : oprno.trim();
-	}
-
 	private static String fmt(float val) {
 		return String.format("%.3f", val);
 	}

+ 9 - 9
src/main/resources/config/application.yml

@@ -1098,38 +1098,38 @@ msg:
 # FSW 采集(OPC UA / SINUMERIK 828D,DRIES 鼎捷点位)
 mes:
   fsw:
-    enabled: true
+    enabled: false
     opcua:
       url: opc.tcp://10.0.20.141:4840
       username: OpcUaClient
       password: oPCuA_123
     # FSW Modbus TCP 握手(工件码质量校验 + 过程参数采集)
     modbus:
-      enabled: false
-      host: 10.0.20.220
+      enabled: true
+      host: 10.0.40.101
       port: 502
       unitId: 1
-      oprno:          # 工位号,必填,例如 OP210A
-      lineSn:         # 产线编号,可空(空则按工件类型自动解析)
+      oprno:  OP230        # 工位号,必填,例如 OP210A
+      lineSn:  XT       # 产线编号,可空(空则按工件类型自动解析)
   # 框架气密 S7 采集(OP360)
   framework:
     qm:
       enabled: true
       plc:
-        ip: 192.168.1.100
+        ip: 10.0.20.96
       oprno: OP360
       lineSn: XT
-  # 涂胶 S7 采集(OP240)
+  # 涂胶 S7 采集(OP220)
   tj:
     enabled: true
     plc:
       ip: 10.0.40.100
-    oprno: OP240
+    oprno: OP220
     lineSn: XT
     # 涂胶 S7 采集(OP240B)
     b:
       enabled: true
       plc:
         ip: 10.0.40.100
-      oprno: OP240B
+      oprno: OP240
       lineSn: XT

+ 4 - 4
src/main/resources/views/modules/mes/mesMaterialCheckRecordFormEdit.html

@@ -47,7 +47,7 @@
                         <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="faNums" readonly="true" maxlength="9" class="form-control digits"/>
+                            <#form:input path="faNums" readonly="true" maxlength="20" class="form-control number"/>
                         </div>
                     </div>
                 </div>
@@ -56,7 +56,7 @@
                         <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="nums" maxlength="9" class="form-control digits" readonly="true"/>
+                            <#form:input path="nums" maxlength="20" class="form-control number" readonly="true"/>
                         </div>
                     </div>
                 </div>
@@ -65,7 +65,7 @@
                         <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="hgQty" maxlength="20" class="form-control digits"/>
+                            <#form:input path="hgQty" maxlength="20" class="form-control number"/>
                         </div>
                     </div>
                 </div>
@@ -74,7 +74,7 @@
                         <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="bhgQty" maxlength="20" class="form-control digits"/>
+                            <#form:input path="bhgQty" maxlength="20" class="form-control number"/>
                         </div>
                     </div>
                 </div>

+ 2 - 2
src/main/resources/views/modules/mes/mesMaterialReportForm.html

@@ -38,7 +38,7 @@
 							<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="faNums" readonly="true" maxlength="9" class="form-control digits"/>
+								<#form:input path="faNums" readonly="true" maxlength="20" class="form-control number"/>
 							</div>
 						</div>
 					</div>
@@ -47,7 +47,7 @@
 							<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="nums" maxlength="9" class="form-control digits"/>
+								<#form:input path="nums" maxlength="20" class="form-control number"/>
 							</div>
 						</div>
 					</div>

+ 3 - 3
src/main/resources/views/modules/mes/mesMaterialRkForm.html

@@ -93,7 +93,7 @@
 							<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="qty" maxlength="9" class="form-control digits"/>
+								<#form:input path="qty" maxlength="20" class="form-control number"/>
 							</div>
 						</div>
 					</div>
@@ -102,7 +102,7 @@
 							<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="rtnDeductQty" maxlength="9" class="form-control digits"/>
+								<#form:input path="rtnDeductQty" maxlength="20" class="form-control number"/>
 							</div>
 						</div>
 					</div>
@@ -111,7 +111,7 @@
 							<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="rtnFillQty" maxlength="9" class="form-control digits"/>
+								<#form:input path="rtnFillQty" maxlength="20" class="form-control number"/>
 							</div>
 						</div>
 					</div>

+ 278 - 0
src/test/java/com/jeesite/test/ProductTjQualityCheckTest.java

@@ -0,0 +1,278 @@
+package com.jeesite.test;
+
+import com.jeesite.common.config.Global;
+import com.jeesite.common.lang.StringUtils;
+import com.jeesite.common.tests.BaseSpringContextTests;
+import com.jeesite.modules.Application;
+import com.jeesite.modules.mes.web.MesProductRecordController;
+import org.junit.Assume;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.test.context.ActiveProfiles;
+
+/**
+ * 涂胶 / FSW 定时任务质量校验联调测试。
+ * <p>
+ * 覆盖以下类的 {@code checkQualityCommon} 调用逻辑,不连接 PLC/Modbus:
+ * <ul>
+ *   <li>{@link com.jeesite.modules.scheduling.ProductTjScheduling}(OP220)</li>
+ *   <li>{@link com.jeesite.modules.scheduling.ProductTjBScheduling}(mes.tj.b.oprno)</li>
+ *   <li>{@link com.jeesite.modules.scheduling.FswModbusScheduling}(mes.fsw.modbus.oprno)</li>
+ * </ul>
+ * <p>
+ * 使用方式:
+ * <ul>
+ *   <li>JUnit:填写下方 TEST_SN 后运行 test 方法</li>
+ *   <li>main:右键运行 main,需先启动数据库等服务</li>
+ * </ul>
+ */
+@ActiveProfiles("test")
+@SpringBootTest(classes = Application.class)
+public class ProductTjQualityCheckTest extends BaseSpringContextTests {
+
+	// ===================== 自行填写 =====================
+
+	/** 待测工件码(必填) */
+	private static final String TEST_SN = "0050010200173E5240017067171A02";
+
+	/** 产线编号,与 application.yml 中 mes.tj.lineSn / mes.tj.b.lineSn 一致 */
+	private static final String TEST_LINE_SN = "XT";
+
+	/**
+	 * B 侧工位号;留空则读取 mes.tj.b.oprno,仍为空时使用 OP240B(与 ProductTjBScheduling 默认一致)
+	 */
+	private static final String TJ_B_OPRNO = "";
+
+	/**
+	 * FSW 工位号;留空则读取 mes.fsw.modbus.oprno(与 FswModbusScheduling 一致,未配置则校验失败)
+	 */
+	private static final String FSW_OPRNO = "OP230";
+
+	/**
+	 * FSW 产线编号;留空则读取 mes.fsw.modbus.lineSn,仍为空则使用 TEST_LINE_SN
+	 */
+	private static final String FSW_LINE_SN = "XT";
+
+	// ===================== 自行填写结束 =====================
+
+	/** ProductTjScheduling 固定工位号 */
+	private static final String TJ_A_OPRNO = "OP220";
+
+	private static final String DEFAULT_TJ_B_OPRNO = "OP240B";
+
+	@Autowired
+	private MesProductRecordController mesProductRecordController;
+
+	@Test
+	public void testProductTjSchedulingQualityCheck() {
+		Assume.assumeTrue("请先在类顶部填写 TEST_SN", StringUtils.isNotBlank(TEST_SN));
+
+		QualityCheckResult result = checkQualityLikeTjScheduling(TEST_SN.trim(), TEST_LINE_SN.trim());
+		printResult("ProductTjScheduling", TJ_A_OPRNO, result);
+
+		org.junit.Assert.assertNotNull(result.ret);
+		org.junit.Assert.assertTrue("返回码长度不足: " + result.ret, result.ret.length() >= 5);
+	}
+
+	@Test
+	public void testProductTjBSchedulingQualityCheck() {
+		Assume.assumeTrue("请先在类顶部填写 TEST_SN", StringUtils.isNotBlank(TEST_SN));
+
+		String oprno = resolveTjBOprno();
+		QualityCheckResult result = checkQualityLikeTjBScheduling(TEST_SN.trim(), TEST_LINE_SN.trim(), oprno);
+		printResult("ProductTjBScheduling", oprno, result);
+
+		org.junit.Assert.assertNotNull(result.ret);
+		org.junit.Assert.assertTrue("返回码长度不足: " + result.ret, result.ret.length() >= 5);
+	}
+
+	@Test
+	public void testFswModbusSchedulingQualityCheck() {
+		Assume.assumeTrue("请先在类顶部填写 TEST_SN", StringUtils.isNotBlank(TEST_SN));
+
+		String oprno = resolveFswOprno();
+		Assume.assumeTrue("请配置 mes.fsw.modbus.oprno 或填写 FSW_OPRNO", StringUtils.isNotBlank(oprno));
+
+		String lineSn = resolveFswLineSn();
+		QualityCheckResult result = checkQualityLikeFswModbusScheduling(TEST_SN.trim(), lineSn, oprno);
+		printResult("FswModbusScheduling", oprno, result, lineSn);
+
+		org.junit.Assert.assertNotNull(result.ret);
+		org.junit.Assert.assertTrue("返回码长度不足: " + result.ret, result.ret.length() >= 5);
+	}
+
+	/**
+	 * 手动联调:依次执行涂胶 A/B 与 FSW 质量校验。
+	 */
+	public static void main(String[] args) {
+		if (StringUtils.isBlank(TEST_SN)) {
+			System.out.println("请先在 ProductTjQualityCheckTest 类顶部填写 TEST_SN 后再运行。");
+			return;
+		}
+
+		System.out.println("========== 涂胶/FSW 质量校验联调(不连 PLC/Modbus)==========");
+		ConfigurableApplicationContext ctx = SpringApplication.run(Application.class, args);
+		try {
+			MesProductRecordController controller = ctx.getBean(MesProductRecordController.class);
+			ProductTjQualityCheckTest tester = new ProductTjQualityCheckTest();
+			tester.mesProductRecordController = controller;
+
+			String sn = TEST_SN.trim();
+			String lineSn = TEST_LINE_SN.trim();
+
+			System.out.println("\n--- ProductTjScheduling (OP220) ---");
+			QualityCheckResult aResult = tester.checkQualityLikeTjScheduling(sn, lineSn);
+			printResult("ProductTjScheduling", TJ_A_OPRNO, aResult, lineSn);
+
+			String bOprno = resolveTjBOprnoStatic();
+			System.out.println("\n--- ProductTjBScheduling (" + bOprno + ") ---");
+			QualityCheckResult bResult = tester.checkQualityLikeTjBScheduling(sn, lineSn, bOprno);
+			printResult("ProductTjBScheduling", bOprno, bResult, lineSn);
+
+			String fswOprno = resolveFswOprnoStatic();
+			if (StringUtils.isBlank(fswOprno)) {
+				System.out.println("\n--- FswModbusScheduling:未配置 mes.fsw.modbus.oprno,跳过 ---");
+			} else {
+				String fswLineSn = resolveFswLineSnStatic();
+				System.out.println("\n--- FswModbusScheduling (" + fswOprno + ") ---");
+				QualityCheckResult fswResult = tester.checkQualityLikeFswModbusScheduling(sn, fswLineSn, fswOprno);
+				printResult("FswModbusScheduling", fswOprno, fswResult, fswLineSn);
+			}
+
+			System.out.println("\n完成。");
+		} finally {
+			ctx.close();
+		}
+	}
+
+	/**
+	 * 与 ProductTjScheduling.doQualityCheck 相同:固定 OP220,校验工位 OP220A。
+	 */
+	QualityCheckResult checkQualityLikeTjScheduling(String sn, String lineSn) {
+		String checkOprno = TJ_A_OPRNO + "A";
+		return invokeQualityCheck(checkOprno, sn, lineSn);
+	}
+
+	/**
+	 * 与 ProductTjBScheduling.doQualityCheck 相同:5 位工位号补 A,6 位及以上原样传入。
+	 */
+	QualityCheckResult checkQualityLikeTjBScheduling(String sn, String lineSn, String oprno) {
+		String checkOprno = oprno.length() == 5 ? oprno + "A" : oprno;
+		return invokeQualityCheck(checkOprno, sn, lineSn);
+	}
+
+	/**
+	 * 与 FswModbusScheduling.doQualityCheck 相同:读 mes.fsw.modbus.oprno,5 位工位号补 A。
+	 */
+	QualityCheckResult checkQualityLikeFswModbusScheduling(String sn, String lineSn, String oprno) {
+		if (StringUtils.isBlank(oprno)) {
+			return QualityCheckResult.error(null, "未配置 mes.fsw.modbus.oprno");
+		}
+		String checkOprno = oprno;
+		if (checkOprno.length() == 5) {
+			checkOprno = checkOprno + "A";
+		}
+		return invokeQualityCheck(checkOprno, sn, lineSn);
+	}
+
+	private QualityCheckResult invokeQualityCheck(String checkOprno, String sn, String lineSn) {
+		if (StringUtils.isBlank(lineSn)) {
+			return QualityCheckResult.fail(checkOprno, "产线编号为空");
+		}
+		try {
+			String ret = mesProductRecordController.checkQualityCommon(checkOprno, sn, lineSn, "");
+			if (StringUtils.isBlank(ret) || ret.length() < 5) {
+				return QualityCheckResult.fail(checkOprno, ret);
+			}
+			boolean pass = "UD".equals(ret.substring(3, 5));
+			return new QualityCheckResult(checkOprno, ret, pass);
+		} catch (Exception e) {
+			return QualityCheckResult.error(checkOprno, e.getMessage());
+		}
+	}
+
+	private String resolveTjBOprno() {
+		return resolveTjBOprnoStatic();
+	}
+
+	private static String resolveTjBOprnoStatic() {
+		if (StringUtils.isNotBlank(TJ_B_OPRNO)) {
+			return TJ_B_OPRNO.trim();
+		}
+		String oprno = Global.getConfig("mes.tj.b.oprno");
+		return StringUtils.isBlank(oprno) ? DEFAULT_TJ_B_OPRNO : oprno.trim();
+	}
+
+	private String resolveFswOprno() {
+		return resolveFswOprnoStatic();
+	}
+
+	private static String resolveFswOprnoStatic() {
+		if (StringUtils.isNotBlank(FSW_OPRNO)) {
+			return FSW_OPRNO.trim();
+		}
+		String oprno = Global.getConfig("mes.fsw.modbus.oprno");
+		return StringUtils.isBlank(oprno) ? null : oprno.trim();
+	}
+
+	private String resolveFswLineSn() {
+		return resolveFswLineSnStatic();
+	}
+
+	private static String resolveFswLineSnStatic() {
+		if (StringUtils.isNotBlank(FSW_LINE_SN)) {
+			return FSW_LINE_SN.trim();
+		}
+		String lineSn = Global.getConfig("mes.fsw.modbus.lineSn");
+		if (StringUtils.isNotBlank(lineSn)) {
+			return lineSn.trim();
+		}
+		return StringUtils.isBlank(TEST_LINE_SN) ? null : TEST_LINE_SN.trim();
+	}
+
+	private static void printResult(String taskName, String configuredOprno, QualityCheckResult result) {
+		printResult(taskName, configuredOprno, result, TEST_LINE_SN);
+	}
+
+	private static void printResult(String taskName, String configuredOprno, QualityCheckResult result, String lineSn) {
+		System.out.println("任务类     : " + taskName);
+		System.out.println("配置工位号 : " + configuredOprno);
+		System.out.println("校验工位号 : " + result.checkOprno);
+		System.out.println("工件码     : " + TEST_SN);
+		System.out.println("产线       : " + lineSn);
+		System.out.println("返回码     : " + result.ret);
+		System.out.println("是否通过   : " + (result.pass ? "是 (UD)" : "否"));
+		if (result.error != null) {
+			System.out.println("异常信息   : " + result.error);
+		}
+	}
+
+	private static final class QualityCheckResult {
+		private final String checkOprno;
+		private final String ret;
+		private final boolean pass;
+		private final String error;
+
+		private QualityCheckResult(String checkOprno, String ret, boolean pass) {
+			this(checkOprno, ret, pass, null);
+		}
+
+		private QualityCheckResult(String checkOprno, String ret, boolean pass, String error) {
+			this.checkOprno = checkOprno;
+			this.ret = ret;
+			this.pass = pass;
+			this.error = error;
+		}
+
+		private static QualityCheckResult fail(String checkOprno, String ret) {
+			return new QualityCheckResult(checkOprno, ret, false);
+		}
+
+		private static QualityCheckResult error(String checkOprno, String error) {
+			return new QualityCheckResult(checkOprno, null, false, error);
+		}
+	}
+}