Kaynağa Gözat

Merge branch 'master' of http://121.36.46.135:3000/mes-ljz/mescloud

sunxianglong 2 ay önce
ebeveyn
işleme
5523b1c2f9

+ 23 - 0
api.http

@@ -540,3 +540,26 @@ Content-Type: application/x-www-form-urlencoded
 __ajax=json&oprno=OP110&lineSn=XT&sn=501901660007119990FC70100012&result=OK&ucode=system
 ###
 
+### ========== 来料报检 / 送检单联调 ==========
+### 1) 登录拿到 sessionid,填到下面 __sid
+POST http://127.0.0.1:8980/js/a/login
+Content-Type: application/x-www-form-urlencoded
+
+__login=true&__ajax=json&username=c3lzdGVt&password=QWExMTExMTE=&validCode=&__sid=
+###
+### 2) 扫码反查(改 __sid 与 materialCode)
+POST http://127.0.0.1:8980/js/a/mes/mesMaterialReport/getMaterialInfo
+Content-Type: application/x-www-form-urlencoded
+
+__ajax=json&__sid=替换为sessionid&materialCode=S-00015,LJR-PO2608050001,10,A0901-000001,20260805,2608050001,1000
+###
+### 3) PDA 报检提交(成功后自动写 mes_material_inspect)
+POST http://127.0.0.1:8980/js/a/mes/mesMaterialReport/submit
+Content-Type: application/x-www-form-urlencoded
+
+__ajax=json&__sid=替换为sessionid&materialCode=S-00015,LJR-PO2608050001,10,A0901-000001,20260805,2608050001,1000&nums=980&remark=http测试
+###
+### 4) 导出送检单 PDF(id 换成列表里的送检单主键)
+GET http://127.0.0.1:8980/js/a/mes/mesMaterialInspect/exportInspectPdf?id=替换为送检单id&__sid=替换为sessionid
+###
+

+ 6 - 0
pom.xml

@@ -263,6 +263,12 @@
 <!--			<version>5.2.3</version>-->
 <!--		</dependency>-->
 
+		<!-- 来料送检单 PDF -->
+		<dependency>
+			<groupId>com.openhtmltopdf</groupId>
+			<artifactId>openhtmltopdf-pdfbox</artifactId>
+			<version>1.0.10</version>
+		</dependency>
 
 	</dependencies>
 

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

@@ -0,0 +1,14 @@
+package com.jeesite.modules.mes.dao;
+
+import com.jeesite.common.dao.CrudDao;
+import com.jeesite.common.mybatis.annotation.MyBatisDao;
+import com.jeesite.modules.mes.entity.MesMaterialInspect;
+
+/**
+ * 来料送检单DAO
+ */
+@MyBatisDao
+public interface MesMaterialInspectDao extends CrudDao<MesMaterialInspect> {
+
+	MesMaterialInspect findInfo(MesMaterialInspect mesMaterialInspect);
+}

+ 168 - 0
src/main/java/com/jeesite/modules/mes/entity/MesMaterialInspect.java

@@ -0,0 +1,168 @@
+package com.jeesite.modules.mes.entity;
+
+import java.util.Date;
+
+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;
+import com.jeesite.common.utils.excel.annotation.ExcelField;
+import com.jeesite.common.utils.excel.annotation.ExcelField.Align;
+import com.jeesite.common.utils.excel.annotation.ExcelFields;
+
+/**
+ * 来料送检单Entity(独立新表,不改动现有报检)
+ */
+@Table(name = "mes_material_inspect", alias = "a", label = "来料送检单", columns = {
+		@Column(name = "id", attrName = "id", label = "id", isPK = true),
+		@Column(name = "sn", attrName = "sn", label = "来料码"),
+		@Column(name = "material_id", attrName = "materialId", label = "物料id"),
+		@Column(name = "supplier_code", attrName = "supplierCode", label = "供应商ERP代码"),
+		@Column(name = "supplier_name", attrName = "supplierName", label = "供应商名称", queryType = QueryType.LIKE),
+		@Column(name = "po_no", attrName = "poNo", label = "采购订单号", queryType = QueryType.LIKE),
+		@Column(name = "po_line", attrName = "poLine", label = "采购订单行号"),
+		@Column(name = "erp_sn", attrName = "erpSn", label = "物料编码", queryType = QueryType.LIKE),
+		@Column(name = "produce_date", attrName = "produceDate", label = "来料日期"),
+		@Column(name = "batch_no", attrName = "batchNo", label = "批次号", queryType = QueryType.LIKE),
+		@Column(name = "delivery_no", attrName = "deliveryNo", label = "送货单号"),
+		@Column(name = "material_name", attrName = "materialName", label = "物料名称", queryType = QueryType.LIKE),
+		@Column(name = "spec", attrName = "spec", label = "规格型号"),
+		@Column(name = "unit", attrName = "unit", label = "单位"),
+		@Column(name = "delivery_qty", attrName = "deliveryQty", label = "送货数量"),
+		@Column(name = "inspect_qty", attrName = "inspectQty", label = "送检数量"),
+		@Column(name = "remark", attrName = "remark", label = "备注"),
+		@Column(name = "create_by", attrName = "createBy", label = "送检人"),
+		@Column(name = "create_date", attrName = "createDate", label = "送检时间", isUpdateForce = true),
+		@Column(name = "update_by", attrName = "updateBy", label = "update_by", isQuery = false),
+		@Column(name = "update_date", attrName = "updateDate", label = "update_date", isQuery = false, isUpdateForce = true),
+		@Column(name = "remarks", attrName = "remarks", label = "备注信息", isQuery = false),
+		@Column(name = "status", attrName = "status", label = "状态", isUpdate = false),
+}, orderBy = "a.create_date DESC")
+public class MesMaterialInspect extends DataEntity<MesMaterialInspect> {
+
+	private static final long serialVersionUID = 1L;
+
+	private String sn;
+	private String materialId;
+	private String supplierCode;
+	private String supplierName;
+	private String poNo;
+	private String poLine;
+	private String erpSn;
+	private String produceDate;
+	private String batchNo;
+	private String deliveryNo;
+	private String materialName;
+	private String spec;
+	private String unit;
+	private String deliveryQty;
+	private String inspectQty;
+	private String remark;
+
+	public MesMaterialInspect() {
+		this(null);
+	}
+
+	public MesMaterialInspect(String id) {
+		super(id);
+	}
+
+	@ExcelFields({
+			@ExcelField(title = "来料码", attrName = "sn", align = Align.CENTER, sort = 10),
+			@ExcelField(title = "采购订单号", attrName = "poNo", align = Align.CENTER, sort = 20),
+			@ExcelField(title = "供应商名称", attrName = "supplierName", align = Align.CENTER, sort = 30),
+			@ExcelField(title = "物料编码", attrName = "erpSn", align = Align.CENTER, sort = 40),
+			@ExcelField(title = "物料名称", attrName = "materialName", align = Align.CENTER, sort = 50),
+			@ExcelField(title = "规格型号", attrName = "spec", align = Align.CENTER, sort = 60),
+			@ExcelField(title = "单位", attrName = "unit", align = Align.CENTER, sort = 70),
+			@ExcelField(title = "送货数量", attrName = "deliveryQty", align = Align.CENTER, sort = 80),
+			@ExcelField(title = "送检数量", attrName = "inspectQty", align = Align.CENTER, sort = 90),
+			@ExcelField(title = "批次号", attrName = "batchNo", align = Align.CENTER, sort = 100),
+			@ExcelField(title = "来料日期", attrName = "produceDate", align = Align.CENTER, sort = 110),
+			@ExcelField(title = "送货单号", attrName = "deliveryNo", align = Align.CENTER, sort = 120),
+			@ExcelField(title = "备注", attrName = "remark", align = Align.CENTER, sort = 130),
+			@ExcelField(title = "送检人", attrName = "createBy", align = Align.CENTER, sort = 140),
+			@ExcelField(title = "送检时间", attrName = "createDate", align = Align.CENTER, sort = 150, dataFormat = "yyyy-MM-dd HH:mm"),
+	})
+	@Size(min = 0, max = 200, message = "来料码长度不能超过 200 个字符")
+	public String getSn() { return sn; }
+	public void setSn(String sn) { this.sn = sn; }
+
+	@Size(min = 0, max = 64, message = "物料id长度不能超过 64 个字符")
+	public String getMaterialId() { return materialId; }
+	public void setMaterialId(String materialId) { this.materialId = materialId; }
+
+	@Size(min = 0, max = 64, message = "供应商ERP代码长度不能超过 64 个字符")
+	public String getSupplierCode() { return supplierCode; }
+	public void setSupplierCode(String supplierCode) { this.supplierCode = supplierCode; }
+
+	@Size(min = 0, max = 255, message = "供应商名称长度不能超过 255 个字符")
+	public String getSupplierName() { return supplierName; }
+	public void setSupplierName(String supplierName) { this.supplierName = supplierName; }
+
+	@Size(min = 0, max = 64, message = "采购订单号长度不能超过 64 个字符")
+	public String getPoNo() { return poNo; }
+	public void setPoNo(String poNo) { this.poNo = poNo; }
+
+	@Size(min = 0, max = 32, message = "采购订单行号长度不能超过 32 个字符")
+	public String getPoLine() { return poLine; }
+	public void setPoLine(String poLine) { this.poLine = poLine; }
+
+	@Size(min = 0, max = 64, message = "物料编码长度不能超过 64 个字符")
+	public String getErpSn() { return erpSn; }
+	public void setErpSn(String erpSn) { this.erpSn = erpSn; }
+
+	@Size(min = 0, max = 32, message = "来料日期长度不能超过 32 个字符")
+	public String getProduceDate() { return produceDate; }
+	public void setProduceDate(String produceDate) { this.produceDate = produceDate; }
+
+	@Size(min = 0, max = 64, message = "批次号长度不能超过 64 个字符")
+	public String getBatchNo() { return batchNo; }
+	public void setBatchNo(String batchNo) { this.batchNo = batchNo; }
+
+	@Size(min = 0, max = 64, message = "送货单号长度不能超过 64 个字符")
+	public String getDeliveryNo() { return deliveryNo; }
+	public void setDeliveryNo(String deliveryNo) { this.deliveryNo = deliveryNo; }
+
+	@Size(min = 0, max = 255, message = "物料名称长度不能超过 255 个字符")
+	public String getMaterialName() { return materialName; }
+	public void setMaterialName(String materialName) { this.materialName = materialName; }
+
+	@Size(min = 0, max = 255, message = "规格型号长度不能超过 255 个字符")
+	public String getSpec() { return spec; }
+	public void setSpec(String spec) { this.spec = spec; }
+
+	@Size(min = 0, max = 32, message = "单位长度不能超过 32 个字符")
+	public String getUnit() { return unit; }
+	public void setUnit(String unit) { this.unit = unit; }
+
+	@Size(min = 0, max = 32, message = "送货数量长度不能超过 32 个字符")
+	public String getDeliveryQty() { return deliveryQty; }
+	public void setDeliveryQty(String deliveryQty) { this.deliveryQty = deliveryQty; }
+
+	@Size(min = 0, max = 32, message = "送检数量长度不能超过 32 个字符")
+	public String getInspectQty() { return inspectQty; }
+	public void setInspectQty(String inspectQty) { this.inspectQty = inspectQty; }
+
+	@Size(min = 0, max = 255, message = "备注长度不能超过 255 个字符")
+	public String getRemark() { return remark; }
+	public void setRemark(String remark) { this.remark = remark; }
+
+	public Date getCreateDate_gte() {
+		return sqlMap.getWhere().getValue("create_date", QueryType.GTE);
+	}
+
+	public void setCreateDate_gte(Date createDate) {
+		sqlMap.getWhere().and("create_date", QueryType.GTE, createDate);
+	}
+
+	public Date getCreateDate_lte() {
+		return sqlMap.getWhere().getValue("create_date", QueryType.LTE);
+	}
+
+	public void setCreateDate_lte(Date createDate) {
+		sqlMap.getWhere().and("create_date", QueryType.LTE, createDate);
+	}
+}

+ 21 - 0
src/main/java/com/jeesite/modules/mes/entity/MesMaterialLl.java

@@ -81,6 +81,27 @@ public class MesMaterialLl extends DataEntity<MesMaterialLl> {
 
 	private String state;       //状态0=草稿 1=下发 2=完成
 
+	/** 已领取数量(来自备料表 nums_yl,非持久化,供 PDA 初始显示) */
+	private String numsYl;
+	/** 领取数量(来自备料表确认数量 nums_qr,非持久化,供 PDA 初始显示) */
+	private String numsQr;
+
+	public String getNumsYl() {
+		return numsYl;
+	}
+
+	public void setNumsYl(String numsYl) {
+		this.numsYl = numsYl;
+	}
+
+	public String getNumsQr() {
+		return numsQr;
+	}
+
+	public void setNumsQr(String numsQr) {
+		this.numsQr = numsQr;
+	}
+
 	public String getState() {
 		return state;
 	}

+ 129 - 0
src/main/java/com/jeesite/modules/mes/service/MesMaterialInspectExportService.java

@@ -0,0 +1,129 @@
+package com.jeesite.modules.mes.service;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+
+import com.jeesite.modules.mes.entity.MesMaterialInspect;
+import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
+
+/**
+ * 来料送检单导出(版式对齐 Excel「来料送检单」模板)
+ */
+@Service
+public class MesMaterialInspectExportService {
+
+	public byte[] exportPdf(MesMaterialInspect row) throws Exception {
+		if (row == null || StringUtils.isBlank(row.getSn())) {
+			throw new IllegalArgumentException("数据不完整,无法导出");
+		}
+		String html = buildHtml(row);
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		PdfRendererBuilder builder = new PdfRendererBuilder();
+		builder.useFastMode();
+		builder.withHtmlContent(html, null);
+		registerChineseFont(builder);
+		builder.toStream(out);
+		builder.run();
+		return out.toByteArray();
+	}
+
+	private void registerChineseFont(PdfRendererBuilder builder) {
+		String[] candidates = {
+				"C:/Windows/Fonts/simsun.ttc",
+				"C:/Windows/Fonts/simhei.ttf",
+				"C:/Windows/Fonts/msyh.ttc",
+				"/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
+				"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"
+		};
+		for (String path : candidates) {
+			File font = new File(path);
+			if (font.exists()) {
+				builder.useFont(font, "ChineseFont");
+				return;
+			}
+		}
+		throw new IllegalStateException("未找到中文字体,无法生成送检单 PDF(请安装 SimSun/微软雅黑 或文泉驿)");
+	}
+
+	private String buildHtml(MesMaterialInspect row) {
+		String inspectDate = formatInspectDate(row.getCreateDate());
+		String supplierName = nvl(row.getSupplierName());
+		String batchNo = nvl(row.getBatchNo());
+		String poNo = nvl(row.getPoNo());
+		String deliveryNo = nvl(row.getDeliveryNo());
+
+		StringBuilder emptyRows = new StringBuilder();
+		for (int i = 0; i < 7; i++) {
+			emptyRows.append("<tr>")
+					.append("<td>&#160;</td><td></td><td></td><td></td><td></td>")
+					.append("<td></td><td></td><td></td><td></td>")
+					.append("</tr>");
+		}
+
+		return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+				+ "<html xmlns=\"http://www.w3.org/1999/xhtml\"><head><meta charset=\"UTF-8\"/>"
+				+ "<style>"
+				+ "body{font-family:ChineseFont,SimSun,sans-serif;font-size:12px;color:#000;}"
+				+ "table{border-collapse:collapse;width:100%;table-layout:fixed;}"
+				+ "td,th{border:1px solid #000;padding:6px 4px;text-align:center;vertical-align:middle;word-wrap:break-word;}"
+				+ ".title{font-size:20px;font-weight:bold;height:40px;}"
+				+ ".meta{text-align:left;border:none;padding:4px 2px;}"
+				+ ".meta-table td{border:none;}"
+				+ ".outer{border:1px solid #000;}"
+				+ ".col1{width:10%;}.col2{width:12%;}.col3{width:14%;}.col4{width:12%;}"
+				+ ".col5{width:8%;}.col6{width:10%;}.col7{width:10%;}.col8{width:12%;}.col9{width:12%;}"
+				+ "</style></head><body>"
+				+ "<table class=\"outer\">"
+				+ "<tr><td class=\"title\" colspan=\"9\">来料送检单</td></tr>"
+				+ "<tr><td class=\"meta\" colspan=\"9\">送检日期:" + escape(inspectDate) + "</td></tr>"
+				+ "<tr><td class=\"meta\" colspan=\"5\">供应商名称:" + escape(supplierName) + "</td>"
+				+ "<td class=\"meta\" colspan=\"4\">供应商批号:" + escape(batchNo) + "</td></tr>"
+				+ "<tr><td class=\"meta\" colspan=\"5\">采购订单号:" + escape(poNo) + "</td>"
+				+ "<td class=\"meta\" colspan=\"4\">送货单号:" + escape(deliveryNo) + "</td></tr>"
+				+ "<tr>"
+				+ "<th class=\"col1\">来料日期</th>"
+				+ "<th class=\"col2\">物料编码</th>"
+				+ "<th class=\"col3\">物料名称</th>"
+				+ "<th class=\"col4\">规格型号</th>"
+				+ "<th class=\"col5\">单位</th>"
+				+ "<th class=\"col6\">送货数量</th>"
+				+ "<th class=\"col7\">送检数量</th>"
+				+ "<th class=\"col8\">批次号</th>"
+				+ "<th class=\"col9\">备注</th>"
+				+ "</tr>"
+				+ "<tr>"
+				+ "<td>" + escape(nvl(row.getProduceDate())) + "</td>"
+				+ "<td>" + escape(nvl(row.getErpSn())) + "</td>"
+				+ "<td>" + escape(nvl(row.getMaterialName())) + "</td>"
+				+ "<td>" + escape(nvl(row.getSpec())) + "</td>"
+				+ "<td>" + escape(nvl(row.getUnit())) + "</td>"
+				+ "<td>" + escape(nvl(row.getDeliveryQty())) + "</td>"
+				+ "<td>" + escape(nvl(row.getInspectQty())) + "</td>"
+				+ "<td>" + escape(nvl(row.getBatchNo())) + "</td>"
+				+ "<td>" + escape(nvl(row.getRemark())) + "</td>"
+				+ "</tr>"
+				+ emptyRows
+				+ "</table></body></html>";
+	}
+
+	private String formatInspectDate(Date date) {
+		if (date == null) {
+			return "   年  月   日";
+		}
+		return new SimpleDateFormat("yyyy年MM月dd日").format(date);
+	}
+
+	private String nvl(String v) {
+		return v == null ? "" : v;
+	}
+
+	private String escape(String v) {
+		return v.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
+				.replace("\"", "&quot;");
+	}
+}

+ 99 - 0
src/main/java/com/jeesite/modules/mes/service/MesMaterialInspectService.java

@@ -0,0 +1,99 @@
+package com.jeesite.modules.mes.service;
+
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.lang.ObjectUtils;
+import com.jeesite.common.service.CrudService;
+import com.jeesite.modules.mes.dao.MesMaterialInspectDao;
+import com.jeesite.modules.mes.entity.MesMaterial;
+import com.jeesite.modules.mes.entity.MesMaterialInspect;
+import com.jeesite.modules.mes.util.MaterialQrCodeParser;
+import com.jeesite.modules.mes.util.MaterialQrCodeParser.ParsedCode;
+
+/**
+ * 来料送检单Service
+ */
+@Service
+public class MesMaterialInspectService extends CrudService<MesMaterialInspectDao, MesMaterialInspect> {
+
+	@Autowired
+	private MesMaterialInspectDao mesMaterialInspectDao;
+
+	@Override
+	public MesMaterialInspect get(MesMaterialInspect mesMaterialInspect) {
+		return super.get(mesMaterialInspect);
+	}
+
+	public MesMaterialInspect findInfo(MesMaterialInspect mesMaterialInspect) {
+		return mesMaterialInspectDao.findInfo(mesMaterialInspect);
+	}
+
+	@Override
+	public Page<MesMaterialInspect> findPage(MesMaterialInspect mesMaterialInspect) {
+		return super.findPage(mesMaterialInspect);
+	}
+
+	@Override
+	public List<MesMaterialInspect> findList(MesMaterialInspect mesMaterialInspect) {
+		return super.findList(mesMaterialInspect);
+	}
+
+	@Override
+	@Transactional
+	public void save(MesMaterialInspect mesMaterialInspect) {
+		super.save(mesMaterialInspect);
+	}
+
+	@Override
+	@Transactional
+	public void delete(MesMaterialInspect mesMaterialInspect) {
+		super.delete(mesMaterialInspect);
+	}
+
+	/**
+	 * 现有 PDA 来料报检 submit 成功后自动生成送检单(不新增 PDA 接口)
+	 */
+	@Transactional
+	public void createFromReportSubmit(String materialCode, String inspectQty, String remark, MesMaterial material) {
+		if (material == null || StringUtils.isBlank(materialCode)) {
+			return;
+		}
+		ParsedCode parsed;
+		try {
+			parsed = MaterialQrCodeParser.parse(materialCode);
+		} catch (IllegalArgumentException e) {
+			logger.warn("来料送检单自动生成跳过,码解析失败: {}", e.getMessage());
+			return;
+		}
+		MesMaterialInspect existsQuery = new MesMaterialInspect();
+		existsQuery.setSn(parsed.getRaw());
+		existsQuery.setStatus("0");
+		if (!ObjectUtils.isEmpty(findInfo(existsQuery))) {
+			return;
+		}
+		MesMaterialInspect entity = new MesMaterialInspect();
+		entity.setSn(parsed.getRaw());
+		entity.setMaterialId(material.getId());
+		entity.setSupplierCode(parsed.getSupplierCode());
+		entity.setSupplierName(material.getSupplier());
+		entity.setPoNo(parsed.getPoNo());
+		entity.setPoLine(parsed.getPoLine());
+		entity.setErpSn(parsed.getErpSn());
+		entity.setProduceDate(parsed.getProduceDate());
+		entity.setBatchNo(parsed.getBatchNo());
+		entity.setMaterialName(material.getTitle());
+		entity.setSpec(material.getSpec());
+		entity.setUnit(material.getUnit());
+		entity.setDeliveryQty(parsed.getQty());
+		entity.setInspectQty(inspectQty);
+		entity.setRemark(remark);
+		entity.setStatus("0");
+		save(entity);
+	}
+}

+ 46 - 7
src/main/java/com/jeesite/modules/mes/service/MesMaterialLlService.java

@@ -2,7 +2,9 @@ package com.jeesite.modules.mes.service;
 
 import java.math.BigDecimal;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -97,9 +99,10 @@ public class MesMaterialLlService extends CrudService<MesMaterialLlDao, MesMater
 
 	/**
 	 * 生产领料单出库:根据生产订单号匹配备料行号
+	 * @return 备料已领取/领取数量(仅回传展示,不覆盖手填 issuedQty)
 	 */
 	@Transactional
-	public void materialLl(MesMaterialLl mesMaterialLl) {
+	public Map<String, Object> materialLl(MesMaterialLl mesMaterialLl) {
 		MesMaterialLl entity;
 		if (StringUtils.isNotBlank(mesMaterialLl.getId())) {
 			entity = get(mesMaterialLl);
@@ -112,14 +115,10 @@ public class MesMaterialLlService extends CrudService<MesMaterialLlDao, MesMater
 		if (StringUtils.isBlank(entity.getMoDocNo())) {
 			throw new ServiceException(text("生产订单号不能为空!"));
 		}
-		MesMaterialBl query = new MesMaterialBl();
-		query.setOrderId(entity.getMoDocNo());
-		query.setErpSn(entity.getItemCode());
-		List<MesMaterialBl> blList = mesMaterialBlService.findList(query);
-		if (ListUtils.isEmpty(blList)) {
+		MesMaterialBl mesMaterialBl = findBlByMoAndItem(entity.getMoDocNo(), entity.getItemCode());
+		if (mesMaterialBl == null) {
 			throw new ServiceException(text("未找到对应备料信息!"));
 		}
-		MesMaterialBl mesMaterialBl = blList.get(0);
 		if (StringUtils.isBlank(mesMaterialBl.getDocLineNo())) {
 			throw new ServiceException(text("备料行号不能为空!"));
 		}
@@ -127,6 +126,46 @@ public class MesMaterialLlService extends CrudService<MesMaterialLlDao, MesMater
 		save(entity);
 		entity.setIsNewRecord(false);
 		syncIssueDocToErp(entity);
+
+		// 按生产订单号+料号从备料表带回已领取/领取数量,仅供 PDA 展示(不覆盖手填 issuedQty)
+		Map<String, Object> data = new HashMap<>();
+		data.put("numsYl", StringUtils.defaultIfBlank(mesMaterialBl.getNumsYl(), "")); // 已领取数量
+		data.put("numsQr", StringUtils.defaultIfBlank(mesMaterialBl.getNumsQr(), "")); // 领取数量(备料确认数量)
+		return data;
+	}
+
+	/**
+	 * 按生产订单号 + 料号编码查备料(取最新一条)
+	 */
+	public MesMaterialBl findBlByMoAndItem(String moDocNo, String itemCode) {
+		if (StringUtils.isBlank(moDocNo) || StringUtils.isBlank(itemCode)) {
+			return null;
+		}
+		MesMaterialBl query = new MesMaterialBl();
+		query.setOrderId(moDocNo);
+		query.setErpSn(itemCode);
+		List<MesMaterialBl> blList = mesMaterialBlService.findList(query);
+		if (ListUtils.isEmpty(blList)) {
+			return null;
+		}
+		return blList.get(0);
+	}
+
+	/**
+	 * 给领料单填充备料已领取/领取数量(非持久化字段,供 PDA 初始显示)
+	 */
+	public void fillBlQtyForDisplay(MesMaterialLl mesMaterialLl) {
+		if (mesMaterialLl == null) {
+			return;
+		}
+		MesMaterialBl bl = findBlByMoAndItem(mesMaterialLl.getMoDocNo(), mesMaterialLl.getItemCode());
+		if (bl == null) {
+			mesMaterialLl.setNumsYl("");
+			mesMaterialLl.setNumsQr("");
+			return;
+		}
+		mesMaterialLl.setNumsYl(StringUtils.defaultIfBlank(bl.getNumsYl(), "")); // 已领取数量
+		mesMaterialLl.setNumsQr(StringUtils.defaultIfBlank(bl.getNumsQr(), "")); // 领取数量(备料确认数量)
 	}
 
 	private String buildCreateIssueDocJson(MesMaterialLl mesMaterialLl) {

+ 78 - 0
src/main/java/com/jeesite/modules/mes/util/MaterialQrCodeParser.java

@@ -0,0 +1,78 @@
+package com.jeesite.modules.mes.util;
+
+import java.text.ParseException;
+
+import org.apache.commons.lang3.StringUtils;
+
+import com.jeesite.modules.utils.DateLocalUtils;
+
+/**
+ * 供应商送货二维码解析(半角逗号分隔 7 段):
+ * 供应商ERP代码,采购订单号,采购订单行号,物料编码,生产日期,生产批次,批次数量
+ */
+public final class MaterialQrCodeParser {
+
+	private MaterialQrCodeParser() {
+	}
+
+	public static class ParsedCode {
+		private String raw;
+		private String supplierCode;
+		private String poNo;
+		private String poLine;
+		private String erpSn;
+		private String produceDateRaw;
+		private String produceDate;
+		private String batchNo;
+		private String qty;
+
+		public String getRaw() { return raw; }
+		public void setRaw(String raw) { this.raw = raw; }
+		public String getSupplierCode() { return supplierCode; }
+		public void setSupplierCode(String supplierCode) { this.supplierCode = supplierCode; }
+		public String getPoNo() { return poNo; }
+		public void setPoNo(String poNo) { this.poNo = poNo; }
+		public String getPoLine() { return poLine; }
+		public void setPoLine(String poLine) { this.poLine = poLine; }
+		public String getErpSn() { return erpSn; }
+		public void setErpSn(String erpSn) { this.erpSn = erpSn; }
+		public String getProduceDateRaw() { return produceDateRaw; }
+		public void setProduceDateRaw(String produceDateRaw) { this.produceDateRaw = produceDateRaw; }
+		public String getProduceDate() { return produceDate; }
+		public void setProduceDate(String produceDate) { this.produceDate = produceDate; }
+		public String getBatchNo() { return batchNo; }
+		public void setBatchNo(String batchNo) { this.batchNo = batchNo; }
+		public String getQty() { return qty; }
+		public void setQty(String qty) { this.qty = qty; }
+	}
+
+	public static ParsedCode parse(String materialCode) {
+		if (StringUtils.isBlank(materialCode)) {
+			throw new IllegalArgumentException("来料码不能为空!");
+		}
+		String raw = materialCode.trim();
+		String[] parts = raw.split(",", -1);
+		if (parts.length != 7) {
+			throw new IllegalArgumentException("二维码规则不正确!");
+		}
+		ParsedCode parsed = new ParsedCode();
+		parsed.setRaw(raw);
+		parsed.setSupplierCode(parts[0].trim());
+		parsed.setPoNo(parts[1].trim());
+		parsed.setPoLine(parts[2].trim());
+		parsed.setErpSn(parts[3].trim());
+		parsed.setProduceDateRaw(parts[4].trim());
+		parsed.setBatchNo(parts[5].trim());
+		parsed.setQty(parts[6].trim());
+		if (StringUtils.isAnyBlank(parsed.getSupplierCode(), parsed.getPoNo(), parsed.getPoLine(),
+				parsed.getErpSn(), parsed.getProduceDateRaw(), parsed.getBatchNo(), parsed.getQty())) {
+			throw new IllegalArgumentException("二维码规则不正确!");
+		}
+		try {
+			parsed.setProduceDate(DateLocalUtils.getCurrentDate2(parsed.getProduceDateRaw()));
+		} catch (ParseException e) {
+			throw new IllegalArgumentException("二维码规则不正确!");
+		}
+		return parsed;
+	}
+}

+ 123 - 0
src/main/java/com/jeesite/modules/mes/web/MesMaterialInspectController.java

@@ -0,0 +1,123 @@
+package com.jeesite.modules.mes.web;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.StringUtils;
+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.lang.DateUtils;
+import com.jeesite.common.utils.excel.ExcelExport;
+import com.jeesite.common.web.BaseController;
+import com.jeesite.modules.mes.entity.MesMaterialInspect;
+import com.jeesite.modules.mes.service.MesMaterialInspectExportService;
+import com.jeesite.modules.mes.service.MesMaterialInspectService;
+
+/**
+ * 来料送检单(PC 列表 / 编辑 / PDF)。数据由现有 PDA 来料报检 submit 自动生成。
+ */
+@Controller
+@RequestMapping(value = "${adminPath}/mes/mesMaterialInspect")
+public class MesMaterialInspectController extends BaseController {
+
+	@Autowired
+	private MesMaterialInspectService mesMaterialInspectService;
+	@Autowired
+	private MesMaterialInspectExportService mesMaterialInspectExportService;
+
+	@ModelAttribute
+	public MesMaterialInspect get(String id, boolean isNewRecord) {
+		return mesMaterialInspectService.get(id, isNewRecord);
+	}
+
+	@RequiresPermissions("mes:mesMaterialInspect:view")
+	@RequestMapping(value = { "list", "" })
+	public String list(MesMaterialInspect mesMaterialInspect, Model model) {
+		model.addAttribute("mesMaterialInspect", mesMaterialInspect);
+		return "modules/mes/mesMaterialInspectList";
+	}
+
+	@RequiresPermissions("mes:mesMaterialInspect:view")
+	@RequestMapping(value = "listData")
+	@ResponseBody
+	public Page<MesMaterialInspect> listData(MesMaterialInspect mesMaterialInspect,
+			HttpServletRequest request, HttpServletResponse response) {
+		mesMaterialInspect.setPage(new Page<>(request, response));
+		return mesMaterialInspectService.findPage(mesMaterialInspect);
+	}
+
+	@RequiresPermissions("mes:mesMaterialInspect:view")
+	@RequestMapping(value = "form")
+	public String form(MesMaterialInspect mesMaterialInspect, Model model) {
+		model.addAttribute("mesMaterialInspect", mesMaterialInspect);
+		return "modules/mes/mesMaterialInspectForm";
+	}
+
+	@RequiresPermissions("mes:mesMaterialInspect:edit")
+	@PostMapping(value = "save")
+	@ResponseBody
+	public String save(@Validated MesMaterialInspect mesMaterialInspect) {
+		mesMaterialInspectService.save(mesMaterialInspect);
+		return renderResult(Global.TRUE, text("保存来料送检单成功!"));
+	}
+
+	@RequiresPermissions("mes:mesMaterialInspect:edit")
+	@RequestMapping(value = "delete")
+	@ResponseBody
+	public String delete(MesMaterialInspect mesMaterialInspect) {
+		mesMaterialInspectService.delete(mesMaterialInspect);
+		return renderResult(Global.TRUE, text("删除来料送检单成功!"));
+	}
+
+	@RequiresPermissions("mes:mesMaterialInspect:view")
+	@RequestMapping(value = "exportData")
+	public void exportData(MesMaterialInspect mesMaterialInspect, HttpServletResponse response) {
+		List<MesMaterialInspect> list = mesMaterialInspectService.findList(mesMaterialInspect);
+		String fileName = "来料送检单" + DateUtils.getDate("yyyyMMddHHmmss") + ".xlsx";
+		try (ExcelExport ee = new ExcelExport("来料送检单", MesMaterialInspect.class)) {
+			ee.setDataList(list).write(response, fileName);
+		}
+	}
+
+	/**
+	 * 按行导出送检单 PDF(版式对齐 Excel 模板)
+	 */
+	@RequiresPermissions("mes:mesMaterialInspect:view")
+	@RequestMapping(value = "exportInspectPdf")
+	public void exportInspectPdf(String id, HttpServletResponse response) throws Exception {
+		if (StringUtils.isBlank(id)) {
+			response.setContentType("text/plain;charset=UTF-8");
+			response.getWriter().write("参数错误");
+			return;
+		}
+		MesMaterialInspect row = mesMaterialInspectService.get(new MesMaterialInspect(id));
+		if (row == null || StringUtils.isBlank(row.getSn())) {
+			response.setContentType("text/plain;charset=UTF-8");
+			response.getWriter().write("数据不完整,无法导出");
+			return;
+		}
+		byte[] pdf = mesMaterialInspectExportService.exportPdf(row);
+		String name = "来料送检单_" + StringUtils.defaultIfBlank(row.getPoNo(), "NA")
+				+ "_" + StringUtils.defaultIfBlank(row.getBatchNo(), "NA") + ".pdf";
+		String encoded = URLEncoder.encode(name, StandardCharsets.UTF_8.name()).replace("+", "%20");
+		response.setContentType("application/pdf");
+		response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encoded);
+		response.setContentLength(pdf.length);
+		response.getOutputStream().write(pdf);
+		response.getOutputStream().flush();
+	}
+}

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

@@ -1,6 +1,7 @@
 package com.jeesite.modules.mes.web;
 
 import java.util.List;
+import java.util.Map;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
@@ -143,8 +144,9 @@ public class MesMaterialLlController extends BaseController {
 	@ResponseBody
 	public String materialLl(MesMaterialLl mesMaterialLl) {
 		try {
-			mesMaterialLlService.materialLl(mesMaterialLl);
-			return renderResult(Global.TRUE, text("生产领料单出库成功!"));
+			// data 含备料 numsYl(已领取)、numsQr(领取数量=确认数量),仅回传展示,不覆盖手填 issuedQty
+			Map<String, Object> data = mesMaterialLlService.materialLl(mesMaterialLl);
+			return renderResult(Global.TRUE, text("生产领料单出库成功!"), data);
 		} catch (ServiceException e) {
 			return renderResult(Global.FALSE, text(e.getMessage()));
 		}
@@ -181,6 +183,8 @@ public class MesMaterialLlController extends BaseController {
 			resp.setMessage("没有该生产领料单!");
 			return resp;
 		}
+		// 按生产订单号+料号关联备料,带回已领取/领取数量,供 PDA 初始显示(不覆盖手填 issuedQty)
+		mesMaterialLlService.fillBlQtyForDisplay(mesMaterialLl1);
 		resp.setData(mesMaterialLl1);
 		resp.setResult(Global.TRUE);
 		resp.setMessage("操作成功!");

+ 5 - 0
src/main/java/com/jeesite/modules/mes/web/MesMaterialReportController.java

@@ -30,6 +30,7 @@ import com.jeesite.common.utils.excel.annotation.ExcelField.Type;
 import org.springframework.web.multipart.MultipartFile;
 import com.jeesite.common.web.BaseController;
 import com.jeesite.modules.mes.entity.MesMaterialReport;
+import com.jeesite.modules.mes.service.MesMaterialInspectService;
 import com.jeesite.modules.mes.service.MesMaterialReportService;
 
 /**
@@ -45,6 +46,8 @@ public class MesMaterialReportController extends BaseController {
 	private MesMaterialReportService mesMaterialReportService;
 	@Autowired
 	private MesMaterialService mesMaterialService;
+	@Autowired
+	private MesMaterialInspectService mesMaterialInspectService;
 	/**
 	 * 获取数据
 	 * 注意:不能使用默认的 mesMaterialReportService.get(id, isNewRecord),
@@ -290,6 +293,8 @@ public class MesMaterialReportController extends BaseController {
 		mesMaterialReport1.setRemark(remark);
 		mesMaterialReport1.setNums(nums);
 		mesMaterialReportService.save(mesMaterialReport1);
+		// 报检成功后自动生成来料送检单(PC 列表展示 / 导出 PDF),PDA 无需改接口
+		mesMaterialInspectService.createFromReportSubmit(materialCode, nums, remark, material);
 		resp.setResult(Global.TRUE);
 		return resp;
 	}

+ 40 - 0
src/main/java/com/jeesite/modules/mes/web/MesMaterialRkController.java

@@ -13,7 +13,11 @@ import com.alibaba.fastjson.JSONObject;
 import com.jeesite.common.lang.DateUtils;
 import com.jeesite.common.lang.StringUtils;
 import com.jeesite.modules.mes.entity.MesMaterial;
+import com.jeesite.modules.mes.entity.MesMaterialCheckRecord;
+import com.jeesite.modules.mes.entity.MesMaterialReport;
 import com.jeesite.modules.mes.resp.CommonResp;
+import com.jeesite.modules.mes.service.MesMaterialCheckRecordService;
+import com.jeesite.modules.mes.service.MesMaterialReportService;
 import com.jeesite.modules.mes.service.MesMaterialService;
 import com.jeesite.modules.mes.util.U9cAuthUtil;
 import com.jeesite.modules.sys.utils.EmpUtils;
@@ -30,6 +34,7 @@ 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.DataEntity;
 import com.jeesite.common.entity.Page;
 import com.jeesite.common.web.BaseController;
 import com.jeesite.modules.mes.entity.MesMaterialRk;
@@ -50,6 +55,12 @@ public class MesMaterialRkController extends BaseController {
 	@Autowired
 	private MesMaterialService mesMaterialService;
 
+	@Autowired
+	private MesMaterialCheckRecordService mesMaterialCheckRecordService;
+
+	@Autowired
+	private MesMaterialReportService mesMaterialReportService;
+
 	/**
 	 * 获取数据
 	 */
@@ -148,6 +159,8 @@ public class MesMaterialRkController extends BaseController {
 		String materialCode = req.getParameter("materialCode");
 		CommonResp<Map<String, Object>> resp = new CommonResp<>();
 
+
+
 		if (StringUtils.isBlank(materialCode)) {
 			resp.setResult(Global.FALSE);
 			resp.setMessage("来料码不能为空!");
@@ -214,6 +227,27 @@ public class MesMaterialRkController extends BaseController {
 			return resp;
 		}
 		String unit=list.get(0).getUnit();
+
+		// 入库前校验:必须已报检且已来料检查,否则不允许入库
+		MesMaterialReport reportQuery = new MesMaterialReport();
+		reportQuery.setSn(materialCode.trim());
+		reportQuery.setStatus(DataEntity.STATUS_NORMAL);
+		MesMaterialReport report = mesMaterialReportService.findInfo(reportQuery);
+		if (report == null) {
+			resp.setResult(Global.FALSE);
+			resp.setMessage("该来料码未报检,无法入库!");
+			return resp;
+		}
+		MesMaterialCheckRecord checkQuery = new MesMaterialCheckRecord();
+		checkQuery.setSn(materialCode.trim());
+		checkQuery.setStatus(DataEntity.STATUS_NORMAL);
+		MesMaterialCheckRecord checkRecord = mesMaterialCheckRecordService.findInfo(checkQuery);
+		if (checkRecord == null) {
+			resp.setResult(Global.FALSE);
+			resp.setMessage("该来料码未检查,无法入库!");
+			return resp;
+		}
+
 		HashMap<String, Object> data = new HashMap<>();
 //		data.put("materialCode", materialCode);       // 来料码
 		data.put("docTypeCode", docTypeCode);         // ERP代码/单据类型编码
@@ -226,6 +260,12 @@ public class MesMaterialRkController extends BaseController {
 		data.put("pcQty", pcQty);                         // 批次数量
 		data.put("unit", unit);                         // 单位
 
+		// 按来料码关联来料检查记录,带回合格/不合格数量,仅供 PDA 入库页初始显示(不覆盖手填,不参与 mesToErp)
+		String hgQty = StringUtils.defaultIfBlank(checkRecord.getHgQty(), "");
+		String bhgQty = StringUtils.defaultIfBlank(checkRecord.getBhgQty(), "");
+		data.put("hgQty", hgQty);   // 合格数量(来自检查记录)
+		data.put("bhgQty", bhgQty); // 不合格数量(来自检查记录)
+
 		resp.setData(data);
 		resp.setResult(Global.TRUE);
 		resp.setMessage("解析成功");

+ 15 - 0
src/main/resources/mappings/modules/mes/MesMaterialInspectDao.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.MesMaterialInspectDao">
+
+	<select id="findInfo" resultType="com.jeesite.modules.mes.entity.MesMaterialInspect">
+		SELECT ${sqlMap.column.toSql()}
+		FROM ${sqlMap.table.toSql()}
+		<where>
+			${sqlMap.where.toSql()}
+		</where>
+		ORDER BY ${sqlMap.order.toSql()}
+		LIMIT 1
+	</select>
+
+</mapper>

+ 144 - 0
src/main/resources/views/modules/mes/mesMaterialInspectForm.html

@@ -0,0 +1,144 @@
+<% 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(mesMaterialInspect.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="${mesMaterialInspect}" action="${ctx}/mes/mesMaterialInspect/save" method="post" class="form-horizontal">
+			<div class="box-body">
+				<div class="form-unit">${text('基本信息')}</div>
+				<#form:hidden path="id"/>
+				<#form:hidden path="materialId"/>
+				<#form:hidden path="sn"/>
+				<div class="row">
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4">${text('采购订单号')}:</label>
+							<div class="col-sm-8">
+								<#form:input path="poNo" maxlength="64" class="form-control" readonly="true"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4">${text('供应商名称')}:</label>
+							<div class="col-sm-8">
+								<#form:input path="supplierName" maxlength="255" class="form-control" readonly="true"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4">${text('物料编码')}:</label>
+							<div class="col-sm-8">
+								<#form:input path="erpSn" maxlength="64" class="form-control" readonly="true"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4">${text('物料名称')}:</label>
+							<div class="col-sm-8">
+								<#form:input path="materialName" maxlength="255" class="form-control" readonly="true"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4">${text('规格型号')}:</label>
+							<div class="col-sm-8">
+								<#form:input path="spec" maxlength="255" class="form-control" readonly="true"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4">${text('单位')}:</label>
+							<div class="col-sm-8">
+								<#form:input path="unit" maxlength="32" class="form-control" readonly="true"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4">${text('送货数量')}:</label>
+							<div class="col-sm-8">
+								<#form:input path="deliveryQty" maxlength="32" class="form-control" readonly="true"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4">${text('送检数量')}:</label>
+							<div class="col-sm-8">
+								<#form:input path="inspectQty" maxlength="32" class="form-control digits"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4">${text('批次号')}:</label>
+							<div class="col-sm-8">
+								<#form:input path="batchNo" maxlength="64" class="form-control" readonly="true"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4">${text('来料日期')}:</label>
+							<div class="col-sm-8">
+								<#form:input path="produceDate" maxlength="32" class="form-control" readonly="true"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4">${text('送货单号')}:</label>
+							<div class="col-sm-8">
+								<#form:input path="deliveryNo" maxlength="64" class="form-control"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4">${text('备注')}:</label>
+							<div class="col-sm-8">
+								<#form:input path="remark" 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:mesMaterialInspect: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>

+ 107 - 0
src/main/resources/views/modules/mes/mesMaterialInspectList.html

@@ -0,0 +1,107 @@
+<% 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 fa-file-text-o"></i> ${text('来料送检单')}
+			</div>
+			<div class="box-tools pull-right">
+				<a href="#" class="btn btn-default" id="btnSearch" title="${text('查询')}"><i class="fa fa-filter"></i> ${text('查询')}</a>
+				<a href="#" class="btn btn-default" id="btnExport"><i class="glyphicon glyphicon-export"></i> ${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="${mesMaterialInspect}" action="${ctx}/mes/mesMaterialInspect/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="200" class="form-control width-260"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('采购订单号')}:</label>
+					<div class="control-inline">
+						<#form:input path="poNo" maxlength="64" class="form-control width-160"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('物料编码')}:</label>
+					<div class="control-inline">
+						<#form:input path="erpSn" maxlength="64" class="form-control width-140"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('物料名称')}:</label>
+					<div class="control-inline">
+						<#form:input path="materialName" maxlength="255" class="form-control width-140"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('批次号')}:</label>
+					<div class="control-inline">
+						<#form:input path="batchNo" 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="createDate_gte" readonly="true" maxlength="20" class="form-control laydate width-datetime"
+							dataFormat="datetime" data-type="datetime" data-format="yyyy-MM-dd HH:mm" data-done="createDate_lte.click()"/>
+						&nbsp;-&nbsp;
+						<#form:input path="createDate_lte" readonly="true" maxlength="20" class="form-control laydate width-datetime"
+							dataFormat="datetime" data-type="datetime" data-format="yyyy-MM-dd HH:mm"/>
+					</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({
+	searchForm: $("#searchForm"),
+	columnModel: [
+		{header:'${text("采购订单号")}', name:'poNo', index:'a.po_no', width:140, align:"center"},
+		{header:'${text("供应商名称")}', name:'supplierName', index:'a.supplier_name', width:140, align:"center"},
+		{header:'${text("物料编码")}', name:'erpSn', index:'a.erp_sn', width:120, align:"center"},
+		{header:'${text("物料名称")}', name:'materialName', index:'a.material_name', width:140, align:"center"},
+		{header:'${text("规格型号")}', name:'spec', index:'a.spec', width:100, align:"center"},
+		{header:'${text("单位")}', name:'unit', index:'a.unit', width:60, align:"center"},
+		{header:'${text("送货数量")}', name:'deliveryQty', index:'a.delivery_qty', width:80, align:"center"},
+		{header:'${text("送检数量")}', name:'inspectQty', index:'a.inspect_qty', width:80, align:"center"},
+		{header:'${text("批次号")}', name:'batchNo', index:'a.batch_no', width:100, align:"center"},
+		{header:'${text("来料日期")}', name:'produceDate', index:'a.produce_date', width:100, align:"center"},
+		{header:'${text("送货单号")}', name:'deliveryNo', index:'a.delivery_no', width:100, align:"center"},
+		{header:'${text("来料码")}', name:'sn', index:'a.sn', width:220, align:"center"},
+		{header:'${text("备注")}', name:'remark', index:'a.remark', width:120, align:"center"},
+		{header:'${text("送检人")}', name:'createBy', index:'a.create_by', width:90, align:"center"},
+		{header:'${text("送检时间")}', name:'createDate', index:'a.create_date', width:140, align:"center"},
+		{header:'${text("操作")}', name:'actions', width:100, formatter: function(val, obj, row, act){
+			var actions = [];
+			//<% if(hasPermi('mes:mesMaterialInspect:view')){ %>
+				actions.push('<a href="${ctx}/mes/mesMaterialInspect/exportInspectPdf?id='+row.id+'" title="${text("导出送检单PDF")}" target="_blank"><i class="fa fa-file-pdf-o"></i></a>&nbsp;');
+			//<% } %>
+			//<% if(hasPermi('mes:mesMaterialInspect:edit')){ %>
+				actions.push('<a href="${ctx}/mes/mesMaterialInspect/form?id='+row.id+'" class="btnList" title="${text("编辑")}"><i class="fa fa-pencil"></i></a>&nbsp;');
+				actions.push('<a href="${ctx}/mes/mesMaterialInspect/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){}
+});
+$('#btnExport').click(function(){
+	js.ajaxSubmitForm($('#searchForm'), {
+		url:'${ctx}/mes/mesMaterialInspect/exportData',
+		downloadFile:true
+	});
+});
+</script>

+ 207 - 0
src/test/java/com/jeesite/test/MesMaterialInspectApiTest.java

@@ -0,0 +1,207 @@
+package com.jeesite.test;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Base64;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 来料报检接口联调:模拟 PDA 正常流程。
+ *
+ * 运行前先启动服务,并在下方「自行填写」区域填入真实数据,然后右键运行 main。
+ * 流程:登录 → getMaterialInfo → submit(自动生成送检单)→ 导出 PDF
+ */
+public class MesMaterialInspectApiTest {
+
+	// ===================== 自行填写 =====================
+
+	/** 服务地址,例如 http://127.0.0.1:8980/js */
+	private static final String BASE = "http://127.0.0.1:8980/js";
+
+	/** PC 登录账号(明文) */
+	private static final String LOGIN_USER = "system";
+
+	/** PC 登录密码(明文) */
+	private static final String LOGIN_PWD = "Aa111111";
+
+	/**
+	 * 来料码(供应商二维码,7 段逗号分隔)
+	 * 格式: 供应商代码,采购订单号,行号,物料编码,生产日期yyyyMMdd,生产批次,批次数量
+	 * 示例: S-00015,LJR-PO2606030001,10,A0901-000002,20260608,26060001,1000
+	 */
+	private static final String MATERIAL_CODE = "S-00033,LJR-PO2606030003,10,A0901-001006,20260805,26060001,1000";
+
+	/** 送检数量(PDA 提交的 nums) */
+	private static final String INSPECT_QTY = "999";
+
+	/** 备注,可空 */
+	private static final String REMARK = "测试嘞";
+
+	/** 是否导出 PDF */
+	private static final boolean EXPORT_PDF = true;
+
+	// ===================== 自行填写结束 =====================
+
+	public static void main(String[] args) throws Exception {
+		if (isBlank(MATERIAL_CODE) || isBlank(INSPECT_QTY)) {
+			System.out.println("请先在类顶部填写 MATERIAL_CODE、INSPECT_QTY 后再运行。");
+			System.out.println("来料码格式: 供应商代码,采购订单号,行号,物料编码,生产日期yyyyMMdd,生产批次,批次数量");
+			System.out.println("示例: S-00015,LJR-PO2606030001,10,A0901-000002,20260608,26060001,1000");
+			return;
+		}
+
+		System.out.println("=== 1. 登录 ===");
+		String sid = login();
+		System.out.println("__sid = " + sid);
+
+		System.out.println("\n=== 2. getMaterialInfo(扫码反查)===");
+		System.out.println("materialCode = " + MATERIAL_CODE);
+		String infoResp = post("/a/mes/mesMaterialReport/getMaterialInfo",
+				"__ajax=json&__sid=" + enc(sid)
+						+ "&materialCode=" + enc(MATERIAL_CODE));
+		System.out.println(infoResp);
+		if (!isOk(infoResp)) {
+			System.out.println("反查失败,停止。");
+			return;
+		}
+
+		System.out.println("\n=== 3. submit(报检,成功后自动生成送检单)===");
+		String submitResp = post("/a/mes/mesMaterialReport/submit",
+				"__ajax=json&__sid=" + enc(sid)
+						+ "&materialCode=" + enc(MATERIAL_CODE)
+						+ "&nums=" + enc(INSPECT_QTY)
+						+ "&remark=" + enc(nullToEmpty(REMARK)));
+		System.out.println(submitResp);
+		if (!isOk(submitResp)) {
+			System.out.println("提交失败,停止。");
+			return;
+		}
+
+		String inspectId = extractJsonField(submitResp, "inspectId");
+		System.out.println("inspectId = " + inspectId);
+		if (isBlank(inspectId)) {
+			System.out.println("未返回 inspectId:请确认服务端已部署送检单自动生成逻辑并已重启。");
+			return;
+		}
+
+		if (!EXPORT_PDF) {
+			System.out.println("\n已关闭导出 PDF。完成。");
+			return;
+		}
+
+		System.out.println("\n=== 4. exportInspectPdf ===");
+		String pdfPath = Paths.get(System.getProperty("java.io.tmpdir"),
+				"来料送检单_" + inspectId + ".pdf").toString();
+		downloadPdf("/a/mes/mesMaterialInspect/exportInspectPdf?id=" + enc(inspectId) + "&__sid=" + enc(sid),
+				pdfPath);
+		System.out.println("PDF 已保存: " + pdfPath);
+		System.out.println("\n完成。");
+	}
+
+	private static String login() throws Exception {
+		String body = "__login=true&__ajax=json"
+				+ "&username=" + Base64.getEncoder().encodeToString(LOGIN_USER.getBytes(StandardCharsets.UTF_8))
+				+ "&password=" + Base64.getEncoder().encodeToString(LOGIN_PWD.getBytes(StandardCharsets.UTF_8))
+				+ "&validCode=&__sid=";
+		String resp = post("/a/login", body);
+		System.out.println(resp);
+		String sid = extractJsonField(resp, "sessionid");
+		if (sid == null) {
+			sid = extractJsonField(resp, "sessionId");
+		}
+		if (sid == null) {
+			throw new IllegalStateException("登录失败,未拿到 sessionid。请检查 BASE / 账号密码 / 服务是否启动。");
+		}
+		return sid;
+	}
+
+	private static boolean isOk(String json) {
+		return json != null && (json.contains("\"result\":\"true\"") || json.contains("\"result\":true"));
+	}
+
+	private static boolean isBlank(String s) {
+		return s == null || s.trim().isEmpty();
+	}
+
+	private static String nullToEmpty(String s) {
+		return s == null ? "" : s;
+	}
+
+	private static String enc(String s) throws Exception {
+		return URLEncoder.encode(s, "UTF-8");
+	}
+
+	private static String extractJsonField(String json, String field) {
+		if (json == null) {
+			return null;
+		}
+		Pattern p = Pattern.compile("\"" + field + "\"\\s*:\\s*\"([^\"]+)\"");
+		Matcher m = p.matcher(json);
+		return m.find() ? m.group(1) : null;
+	}
+
+	private static String post(String path, String formBody) throws Exception {
+		URL url = new URL(BASE + path);
+		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+		conn.setRequestMethod("POST");
+		conn.setDoOutput(true);
+		conn.setConnectTimeout(15000);
+		conn.setReadTimeout(60000);
+		conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
+		byte[] bytes = formBody.getBytes(StandardCharsets.UTF_8);
+		conn.setRequestProperty("Content-Length", String.valueOf(bytes.length));
+		try (OutputStream os = conn.getOutputStream()) {
+			os.write(bytes);
+		}
+		int code = conn.getResponseCode();
+		InputStream in = code >= 400 ? conn.getErrorStream() : conn.getInputStream();
+		if (in == null) {
+			return "HTTP " + code + " (no body)";
+		}
+		try (BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
+			StringBuilder sb = new StringBuilder();
+			String line;
+			while ((line = br.readLine()) != null) {
+				sb.append(line);
+			}
+			return sb.toString();
+		} finally {
+			conn.disconnect();
+		}
+	}
+
+	private static void downloadPdf(String pathAndQuery, String saveTo) throws Exception {
+		URL url = new URL(BASE + pathAndQuery);
+		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+		conn.setRequestMethod("GET");
+		conn.setConnectTimeout(15000);
+		conn.setReadTimeout(60000);
+		int code = conn.getResponseCode();
+		InputStream in = code >= 400 ? conn.getErrorStream() : conn.getInputStream();
+		ByteArrayOutputStream bos = new ByteArrayOutputStream();
+		byte[] buf = new byte[8192];
+		int n;
+		while ((n = in.read(buf)) >= 0) {
+			bos.write(buf, 0, n);
+		}
+		conn.disconnect();
+		byte[] data = bos.toByteArray();
+		String head = new String(data, 0, Math.min(20, data.length), StandardCharsets.ISO_8859_1);
+		if (!head.startsWith("%PDF")) {
+			System.out.println("导出未得到 PDF,响应内容:");
+			System.out.println(new String(data, StandardCharsets.UTF_8));
+			return;
+		}
+		Files.write(Paths.get(saveTo), data);
+	}
+}