Quellcode durchsuchen

ERP对接,优化页面选择参数及排产功能完善

jingbo vor 4 Monaten
Ursprung
Commit
66ffae7de6
30 geänderte Dateien mit 1940 neuen und 94 gelöschten Zeilen
  1. 16 0
      src/main/java/com/jeesite/modules/mes/dao/MesErpScheduleDao.java
  2. 7 0
      src/main/java/com/jeesite/modules/mes/dao/MesLineProcessDao.java
  3. 4 3
      src/main/java/com/jeesite/modules/mes/entity/MesErpPrdInstockLog.java
  4. 1 1
      src/main/java/com/jeesite/modules/mes/entity/MesLineProcess.java
  5. 20 67
      src/main/java/com/jeesite/modules/mes/erp/ErpPickMaterialBillService.java
  6. 75 8
      src/main/java/com/jeesite/modules/mes/erp/ErpProductionOrderService.java
  7. 32 0
      src/main/java/com/jeesite/modules/mes/erp/KingdeeErpProperties.java
  8. 12 0
      src/main/java/com/jeesite/modules/mes/service/MesBatchSnService.java
  9. 13 0
      src/main/java/com/jeesite/modules/mes/service/MesErpPrdInstockLogService.java
  10. 325 0
      src/main/java/com/jeesite/modules/mes/service/MesErpScheduleService.java
  11. 11 0
      src/main/java/com/jeesite/modules/mes/service/MesLineProcessService.java
  12. 86 1
      src/main/java/com/jeesite/modules/mes/web/ErpProductionOrderController.java
  13. 88 0
      src/main/java/com/jeesite/modules/mes/web/MesErpPrdInstockLogController.java
  14. 43 0
      src/main/java/com/jeesite/modules/mes/web/MesErpScheduleController.java
  15. 3 0
      src/main/resources/config/application.yml
  16. 28 0
      src/main/resources/mappings/modules/mes/MesErpScheduleDao.xml
  17. 21 0
      src/main/resources/mappings/modules/mes/MesLineProcessDao.xml
  18. 6 1
      src/main/resources/views/modules/mes/erpPickMaterialBillList.html
  19. 3 0
      src/main/resources/views/modules/mes/erpPrdInStockList.html
  20. 0 4
      src/main/resources/views/modules/mes/erpProductionOrderDetail.html
  21. 210 2
      src/main/resources/views/modules/mes/erpProductionOrderDownstream.html
  22. 89 4
      src/main/resources/views/modules/mes/erpProductionOrderList.html
  23. 69 0
      src/main/resources/views/modules/mes/erpProductionOrderPushdown.html
  24. 47 0
      src/main/resources/views/modules/mes/erpProductionOrderScheduleSelect.html
  25. 178 0
      src/main/resources/views/modules/mes/mesErpPrdInstockLogDetail.html
  26. 182 0
      src/main/resources/views/modules/mes/mesErpPrdInstockLogList.html
  27. 269 0
      src/main/resources/views/modules/mes/mesErpScheduleBoard.html
  28. 97 0
      src/main/resources/views/modules/mes/mesErpScheduleDetail.html
  29. 1 1
      src/main/resources/views/modules/mes/mesErpScheduleForm.html
  30. 4 2
      src/main/resources/views/modules/mes/mesErpScheduleList.html

+ 16 - 0
src/main/java/com/jeesite/modules/mes/dao/MesErpScheduleDao.java

@@ -5,6 +5,8 @@ import com.jeesite.common.mybatis.annotation.MyBatisDao;
 import com.jeesite.modules.mes.entity.MesErpSchedule;
 import org.apache.ibatis.annotations.Param;
 
+import java.util.List;
+
 /**
  * ERP排产计划DAO接口
  */
@@ -17,4 +19,18 @@ public interface MesErpScheduleDao extends CrudDao<MesErpSchedule> {
 	Integer sumScheduleQty(@Param("erpBillNo") String erpBillNo,
 			@Param("erpEntryId") String erpEntryId,
 			@Param("excludeId") String excludeId);
+
+	/**
+	 * 查询日期范围内与指定产线冲突的排产计划(日期区间有重叠即冲突)
+	 */
+	MesErpSchedule findLineDateConflict(@Param("lineId") String lineId,
+			@Param("scheduleStartDate") String scheduleStartDate,
+			@Param("scheduleEndDate") String scheduleEndDate,
+			@Param("excludeId") String excludeId);
+
+	/**
+	 * 查询日期范围内的排产计划
+	 */
+	List<MesErpSchedule> findListInDateRange(@Param("startDate") String startDate,
+			@Param("endDate") String endDate);
 }

+ 7 - 0
src/main/java/com/jeesite/modules/mes/dao/MesLineProcessDao.java

@@ -7,6 +7,8 @@ import com.jeesite.modules.mes.entity.MesLineProcess;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
 
+import org.apache.ibatis.annotations.Param;
+
 import java.util.List;
 
 /**
@@ -42,4 +44,9 @@ public interface MesLineProcessDao extends CrudDao<MesLineProcess> {
      */
     @Select("select jar from mes_line_process where line_id=#{lineId} and oprno=#{oprno}")
     String getJarPath(String lineId, String oprno);
+
+    /**
+     * 按产线查询主工位(与工位管理列表一致:pid 为空)
+     */
+    List<MesLineProcess> findParentListByLine(@Param("lineId") String lineId, @Param("lineSn") String lineSn);
 }

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

@@ -3,6 +3,7 @@ package com.jeesite.modules.mes.entity;
 import com.jeesite.common.entity.DataEntity;
 import com.jeesite.common.mybatis.annotation.Column;
 import com.jeesite.common.mybatis.annotation.Table;
+import com.jeesite.common.mybatis.mapper.query.QueryType;
 
 import java.math.BigDecimal;
 import java.util.Date;
@@ -12,10 +13,10 @@ import java.util.Date;
  */
 @Table(name = "mes_erp_prd_instock_log", alias = "a", label = "ERP生产入库日志", columns = {
 		@Column(name = "id", attrName = "id", label = "主键", isPK = true),
-		@Column(name = "mo_bill_no", attrName = "moBillNo", label = "生产订单号"),
+		@Column(name = "mo_bill_no", attrName = "moBillNo", label = "生产订单号", queryType = QueryType.LIKE),
 		@Column(name = "mo_entry_id", attrName = "moEntryId", label = "生产订单分录"),
-		@Column(name = "material_number", attrName = "materialNumber", label = "物料编码"),
-		@Column(name = "erp_bill_no", attrName = "erpBillNo", label = "ERP入库单号"),
+		@Column(name = "material_number", attrName = "materialNumber", label = "物料编码", queryType = QueryType.LIKE),
+		@Column(name = "erp_bill_no", attrName = "erpBillNo", label = "ERP入库单号", queryType = QueryType.LIKE),
 		@Column(name = "erp_lot_number", attrName = "erpLotNumber", label = "ERP批号"),
 		@Column(name = "total_qty", attrName = "totalQty", label = "合计数量", isUpdateForce = true),
 		@Column(name = "batch_count", attrName = "batchCount", label = "批次数", isUpdateForce = true),

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

@@ -22,7 +22,7 @@ import com.jeesite.common.mybatis.mapper.query.QueryType;
  */
 @Table(name="mes_line_process", alias="a", label="生成工序表信息", columns={
 		@Column(name="id", attrName="id", label="编号", isPK=true),
-		@Column(name="line_id", attrName="lineId", label="生产线id"),
+		@Column(name="line_id", attrName="lineId", label="生产线id", queryType=QueryType.EQ),
 		@Column(name="title", attrName="title", label="工序名称", queryType=QueryType.LIKE),
 		@Column(name="remark", attrName="remark", label="备注",queryType=QueryType.LIKE),
 		@Column(name="status", attrName="status", label="状态", comment="状态(0正常 1删除 2停用)", isUpdate=false),

+ 20 - 67
src/main/java/com/jeesite/modules/mes/erp/ErpPickMaterialBillService.java

@@ -21,7 +21,6 @@ public class ErpPickMaterialBillService {
 
 	private static final Logger logger = LoggerFactory.getLogger(ErpPickMaterialBillService.class);
 	private static final String FORM_ID = "PRD_PickMtrl";
-	private static final int VISIBLE_MO_LIMIT = 500;
 	/** BillQuery 分录字段:除分录内码外,明细字段一般不带 FEntity_ 前缀 */
 	private static final String MO_BILL_NO_FIELD = "FMoBillNo";
 	private static final String MATERIAL_NUMBER_FIELD = "FMaterialId.FNUMBER";
@@ -60,62 +59,21 @@ public class ErpPickMaterialBillService {
 
 	private final KingdeeErpClient erpClient;
 	private final KingdeeErpProperties erpProperties;
-	private final ErpProductionOrderService erpProductionOrderService;
 	private final MesErpPickKcService mesErpPickKcService;
 
 	public ErpPickMaterialBillService(KingdeeErpClient erpClient, KingdeeErpProperties erpProperties,
-			ErpProductionOrderService erpProductionOrderService, MesErpPickKcService mesErpPickKcService) {
+			MesErpPickKcService mesErpPickKcService) {
 		this.erpClient = erpClient;
 		this.erpProperties = erpProperties;
-		this.erpProductionOrderService = erpProductionOrderService;
 		this.mesErpPickKcService = mesErpPickKcService;
 	}
 
 	/**
-	 * 列表页:按领料单号聚合。
+	 * 列表页:按领料单号聚合(始终走明细分录 BillQuery,避免表头查询 + FMoBillNo 过滤时条数偏少)。
 	 */
 	public List<ErpPickMaterialBill> findPageList(ErpPickMaterialBill query, int startRow, int limit) {
 		String moFilter = buildProductionOrderFilter(query);
-		if (moFilter == null) {
-			return new ArrayList<>();
-		}
-		if (StringUtils.isNotBlank(query == null ? "" : query.getMaterialNumberQuery())) {
-			return findAggregatedPageList(query, moFilter, startRow, limit);
-		}
-		List<ErpPickMaterialBill> headers = findHeaderList(query, moFilter, startRow, limit);
-		if (headers.isEmpty()) {
-			return headers;
-		}
-		List<String> billNos = new ArrayList<>();
-		Map<String, ErpPickMaterialBill> billMap = new LinkedHashMap<>();
-		for (ErpPickMaterialBill header : headers) {
-			header.setEntryCount(0);
-			header.setTotalAppQty(BigDecimal.ZERO);
-			header.setTotalActualQty(BigDecimal.ZERO);
-			billMap.put(header.getBillNo(), header);
-			billNos.add(header.getBillNo());
-		}
-		for (ErpPickMaterialBill detail : findDetailListByBillNos(billNos, moFilter)) {
-			ErpPickMaterialBill header = billMap.get(detail.getBillNo());
-			if (header == null) {
-				continue;
-			}
-			if (StringUtils.isBlank(header.getMoBillNo()) && StringUtils.isNotBlank(detail.getMoBillNo())) {
-				header.setMoBillNo(detail.getMoBillNo());
-			}
-			if (StringUtils.isBlank(header.getWorkshopName()) && StringUtils.isNotBlank(detail.getWorkshopName())) {
-				header.setWorkshopNumber(detail.getWorkshopNumber());
-				header.setWorkshopName(detail.getWorkshopName());
-			}
-			header.setEntryCount(header.getEntryCount() + 1);
-			if (detail.getAppQty() != null) {
-				header.setTotalAppQty(header.getTotalAppQty().add(detail.getAppQty()));
-			}
-			if (detail.getActualQty() != null) {
-				header.setTotalActualQty(header.getTotalActualQty().add(detail.getActualQty()));
-			}
-		}
-		return new ArrayList<>(billMap.values());
+		return findAggregatedPageList(query, moFilter, startRow, limit);
 	}
 
 	/**
@@ -128,9 +86,6 @@ public class ErpPickMaterialBillService {
 		ErpPickMaterialBill query = new ErpPickMaterialBill();
 		query.setMoBillNoQuery(moBillNo.trim());
 		String moFilter = buildProductionOrderFilter(query);
-		if (moFilter == null) {
-			return new ArrayList<>();
-		}
 		return findDetailList(query, moFilter, 0, 2000);
 	}
 
@@ -154,10 +109,12 @@ public class ErpPickMaterialBillService {
 
 	private List<ErpPickMaterialBill> findAggregatedPageList(ErpPickMaterialBill query, String moFilter,
 			int startRow, int limit) {
-		int targetSize = startRow + limit;
+		int pageSize = Math.max(limit, 1);
+		int targetSize = startRow + pageSize;
 		int fetchStart = 0;
-		int fetchLimit = Math.max(limit * 3, 50);
-		int maxFetchRows = Math.max(targetSize * 5, 200);
+		// 每张领料单往往有多行分录,需多拉分录行再按单号聚合
+		int fetchLimit = Math.max(pageSize * 15, 100);
+		int maxFetchRows = Math.max(targetSize * 40, 3000);
 		Map<String, ErpPickMaterialBill> billMap = new LinkedHashMap<>();
 
 		while (billMap.size() < targetSize && fetchStart < maxFetchRows) {
@@ -174,6 +131,13 @@ public class ErpPickMaterialBillService {
 					header.setTotalActualQty(BigDecimal.ZERO);
 					billMap.put(detail.getBillNo(), header);
 				}
+				if (StringUtils.isBlank(header.getMoBillNo()) && StringUtils.isNotBlank(detail.getMoBillNo())) {
+					header.setMoBillNo(detail.getMoBillNo());
+				}
+				if (StringUtils.isBlank(header.getWorkshopName()) && StringUtils.isNotBlank(detail.getWorkshopName())) {
+					header.setWorkshopNumber(detail.getWorkshopNumber());
+					header.setWorkshopName(detail.getWorkshopName());
+				}
 				header.setEntryCount(header.getEntryCount() + 1);
 				if (detail.getAppQty() != null) {
 					header.setTotalAppQty(header.getTotalAppQty().add(detail.getAppQty()));
@@ -191,7 +155,7 @@ public class ErpPickMaterialBillService {
 		if (startRow >= allBills.size()) {
 			return new ArrayList<>();
 		}
-		return new ArrayList<>(allBills.subList(startRow, Math.min(startRow + limit, allBills.size())));
+		return new ArrayList<>(allBills.subList(startRow, Math.min(startRow + pageSize, allBills.size())));
 	}
 
 	private List<ErpPickMaterialBill> findHeaderList(ErpPickMaterialBill query, String moFilter, int startRow, int limit) {
@@ -337,25 +301,14 @@ public class ErpPickMaterialBillService {
 	}
 
 	/**
-	 * 按 MES 可见生产订单范围筛选;无关联生产订单的领料行不查。
-	 *
-	 * @return null 表示当前条件下无需请求 ERP(结果为空)
+	 * 按生产订单号筛选;留空时不限制具体订单,仅要求分录已关联生产订单(FMoBillNo 非空)。
 	 */
 	private String buildProductionOrderFilter(ErpPickMaterialBill query) {
 		String moBillNo = query == null ? "" : StringUtils.trim(query.getMoBillNoQuery());
-		if (StringUtils.isNotBlank(moBillNo)) {
-			if (!erpProductionOrderService.isVisibleProductionOrder(moBillNo)) {
-				logger.info("ERP pick material bill skip: production order not in MES scope, moBillNo={}", moBillNo);
-				return null;
-			}
-			return MO_BILL_NO_FIELD + "='" + escape(moBillNo) + "'";
-		}
-		List<String> visibleBillNos = erpProductionOrderService.findVisibleBillNos(VISIBLE_MO_LIMIT);
-		if (visibleBillNos.isEmpty()) {
-			logger.info("ERP pick material bill skip: no visible production orders");
-			return null;
+		if (StringUtils.isBlank(moBillNo)) {
+			return "";
 		}
-		return buildInFilter(MO_BILL_NO_FIELD, visibleBillNos);
+		return MO_BILL_NO_FIELD + "='" + escape(moBillNo) + "'";
 	}
 
 	private String buildFilterString(ErpPickMaterialBill query, String moFilter, boolean includeMaterialFilter) {

+ 75 - 8
src/main/java/com/jeesite/modules/mes/erp/ErpProductionOrderService.java

@@ -117,14 +117,27 @@ public class ErpProductionOrderService {
 	}
 
 	/**
-	 * 生产入库选单:仅含配置物料(HEV/115 具体编码)的生产订单,按单号汇总。
+	 * 生产入库选单:仍按配置 HEV/115 物料编码过滤。
 	 */
 	public List<ErpProductionOrder> findPrdInStockOrderPageList(String billNo, int startRow, int limit) {
 		List<String> materialNumbers = erpProperties.getPrdInStockMaterialNumbers();
-		if (materialNumbers.isEmpty()) {
-			return new ArrayList<>();
+		if (!materialNumbers.isEmpty()) {
+			return findAggregatedPageListByMaterialNumbers(billNo, startRow, limit, materialNumbers);
+		}
+		if (!erpProperties.isInventoryCategoryFilterEnabled()) {
+			return findPageListWithDetailSummary(billNo, startRow, limit);
+		}
+		if (useBillQueryCategoryField()) {
+			try {
+				return findAggregatedPageListByBillQuery(billNo, startRow, limit);
+			} catch (IllegalStateException ex) {
+				if (disableBillQueryCategoryFieldIfInvalid(ex)) {
+					return findAggregatedPageListByView(billNo, startRow, limit);
+				}
+				throw ex;
+			}
 		}
-		return findAggregatedPageListByMaterialNumbers(billNo, startRow, limit, materialNumbers);
+		return findAggregatedPageListByView(billNo, startRow, limit);
 	}
 
 	private List<ErpProductionOrder> findAggregatedPageListByMaterialNumbers(String billNo, int startRow, int limit,
@@ -141,7 +154,7 @@ public class ErpProductionOrderService {
 				break;
 			}
 			for (ErpProductionOrder detail : chunk) {
-				if (!erpProperties.isAllowedPrdInStockMaterial(detail.getMaterialNumber())) {
+				if (!erpProperties.isAllowedProductionOrderMaterial(detail.getMaterialNumber())) {
 					continue;
 				}
 				ErpProductionOrder order = orderMap.get(detail.getBillNo());
@@ -149,6 +162,8 @@ public class ErpProductionOrderService {
 					order = toHeaderFromDetail(detail);
 					order.setEntryCount(0);
 					order.setTotalQty(BigDecimal.ZERO);
+					order.setMaterialNumber(detail.getMaterialNumber());
+					order.setMaterialName(detail.getMaterialName());
 					order.setInventoryCategory(resolveInventoryCategoryLabel(detail.getMaterialNumber()));
 					orderMap.put(detail.getBillNo(), order);
 				}
@@ -180,7 +195,7 @@ public class ErpProductionOrderService {
 		data.put("OrderString", "FDate DESC,FBILLNO DESC,FTreeEntity_FSeq");
 		data.put("StartRow", Math.max(startRow, 0));
 		data.put("Limit", Math.max(limit, 1));
-		logger.info("ERP production order prd-instock query: {}", data.toJSONString());
+		logger.info("ERP production order material-filter query: {}", data.toJSONString());
 
 		JSONArray rows = erpClient.billQuery(data);
 		List<ErpProductionOrder> list = new ArrayList<>();
@@ -279,7 +294,7 @@ public class ErpProductionOrderService {
 	}
 
 	private List<ErpProductionOrder> findPageListWithDetailSummary(String billNo, int startRow, int limit) {
-		List<ErpProductionOrder> headers = findHeaderPageList(billNo, startRow, limit);
+		List<ErpProductionOrder> headers = findHeaderListOrgOnly(billNo, startRow, limit);
 		if (headers.isEmpty()) {
 			return headers;
 		}
@@ -300,6 +315,14 @@ public class ErpProductionOrderService {
 			if (detail.getQty() != null) {
 				order.setTotalQty(order.getTotalQty().add(detail.getQty()));
 			}
+			if (StringUtils.isBlank(order.getMaterialNumber()) && StringUtils.isNotBlank(detail.getMaterialNumber())) {
+				order.setMaterialNumber(detail.getMaterialNumber());
+				order.setMaterialName(detail.getMaterialName());
+				order.setInventoryCategory(detail.getInventoryCategory());
+				if (StringUtils.isBlank(order.getInventoryCategory())) {
+					order.setInventoryCategory(resolveInventoryCategoryLabel(detail.getMaterialNumber()));
+				}
+			}
 			mergeHeaderFieldsFromDetail(order, detail);
 		}
 		return new ArrayList<>(orderMap.values());
@@ -505,11 +528,31 @@ public class ErpProductionOrderService {
 		return list;
 	}
 
+	/** 列表/明细:仅按组织与单号过滤,不含物料/存货类别限制 */
+	private List<ErpProductionOrder> findHeaderListOrgOnly(String billNo, int startRow, int limit) {
+		JSONObject data = new JSONObject();
+		data.put("FormId", FORM_ID);
+		data.put("FieldKeys", HEADER_FIELD_KEYS);
+		data.put("FilterString", buildOrgOnlyFilterString(billNo));
+		data.put("OrderString", "FDate DESC,FBILLNO DESC");
+		data.put("StartRow", Math.max(startRow, 0));
+		data.put("Limit", Math.max(limit, 1));
+		logger.info("ERP production order org-only header query: {}", data.toJSONString());
+
+		JSONArray rows = erpClient.billQuery(data);
+		List<ErpProductionOrder> list = new ArrayList<>();
+		for (int i = 0; i < rows.size(); i++) {
+			list.add(toHeader(rows.getJSONObject(i)));
+		}
+		return list;
+	}
+
 	private List<ErpProductionOrder> findDetailListByBillNos(List<String> billNos) {
 		JSONObject data = new JSONObject();
 		data.put("FormId", FORM_ID);
 		data.put("FieldKeys", buildDetailFieldKeys());
-		data.put("FilterString", buildInFilterString(billNos));
+		data.put("FilterString", erpProperties.isInventoryCategoryFilterEnabled()
+				? buildInFilterString(billNos) : buildInFilterStringOrgOnly(billNos));
 		data.put("OrderString", "FBILLNO DESC,FTreeEntity_FSeq");
 		data.put("Limit", 2000);
 		logger.info("ERP production order detail query: {}", data.toJSONString());
@@ -837,6 +880,30 @@ public class ErpProductionOrderService {
 		return appendAnd(baseFilter, "FBILLNO='" + billNo.trim().replace("'", "''") + "'");
 	}
 
+	/** 仅组织 + 可选单号,供生产订单列表/明细查询 */
+	private String buildOrgOnlyFilterString(String billNo) {
+		if (StringUtils.isBlank(billNo)) {
+			return buildCurrentOrgFilter();
+		}
+		return appendAnd(buildCurrentOrgFilter(), "FBILLNO='" + escapeBillNo(billNo) + "'");
+	}
+
+	private String buildInFilterStringOrgOnly(List<String> billNos) {
+		StringBuilder builder = new StringBuilder(buildCurrentOrgFilter());
+		if (builder.length() > 0) {
+			builder.append(" AND ");
+		}
+		builder.append("FBILLNO IN(");
+		for (int i = 0; i < billNos.size(); i++) {
+			if (i > 0) {
+				builder.append(",");
+			}
+			builder.append("'").append(billNos.get(i).trim().replace("'", "''")).append("'");
+		}
+		builder.append(")");
+		return builder.toString();
+	}
+
 	private String buildInventoryCategoryFilter() {
 		if (!useBillQueryCategoryField()) {
 			return "";

+ 32 - 0
src/main/java/com/jeesite/modules/mes/erp/KingdeeErpProperties.java

@@ -31,6 +31,10 @@ public class KingdeeErpProperties {
 	@Value("${erp.k3cloud.inventory-category-field:}")
 	private String inventoryCategoryField;
 
+	/** 生产订单可见物料编码(逗号分隔,替代存货类别过滤) */
+	@Value("${erp.k3cloud.production-order.material-numbers:}")
+	private String productionOrderMaterialNumbers;
+
 	@Value("${erp.k3cloud.lcid:2052}")
 	private int lcid;
 
@@ -199,6 +203,34 @@ public class KingdeeErpProperties {
 		return inventoryCategoryField;
 	}
 
+	/**
+	 * 生产订单列表/选单可见物料编码;未配置时与生产入库物料范围一致。
+	 */
+	public java.util.List<String> getProductionOrderMaterialNumbers() {
+		java.util.List<String> configured = parseMaterialNumbers(productionOrderMaterialNumbers);
+		if (!configured.isEmpty()) {
+			return configured;
+		}
+		return getPrdInStockMaterialNumbers();
+	}
+
+	public boolean isAllowedProductionOrderMaterial(String materialNumber) {
+		if (StringUtils.isBlank(materialNumber)) {
+			return false;
+		}
+		String trimmed = materialNumber.trim();
+		for (String allowed : getProductionOrderMaterialNumbers()) {
+			if (trimmed.equals(allowed)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	public boolean isProductionOrderMaterialFilterEnabled() {
+		return !getProductionOrderMaterialNumbers().isEmpty();
+	}
+
 	public int getLcid() {
 		return lcid;
 	}

+ 12 - 0
src/main/java/com/jeesite/modules/mes/service/MesBatchSnService.java

@@ -46,6 +46,18 @@ public class MesBatchSnService extends CrudService<MesBatchSnDao, MesBatchSn> {
 	}
 
 	/**
+	 * 按 ERP 生产入库单号查询绑定的 MES 批次码。
+	 */
+	public List<MesBatchSn> findByErpInstockBillNo(String erpInstockBillNo) {
+		if (erpInstockBillNo == null || erpInstockBillNo.trim().isEmpty()) {
+			return new ArrayList<>();
+		}
+		MesBatchSn query = new MesBatchSn();
+		query.setErpInstockBillNo(erpInstockBillNo.trim());
+		return findList(query);
+	}
+
+	/**
 	 * 按主键批量加载批次码。
 	 */
 	public List<MesBatchSn> findByIds(List<String> ids) {

+ 13 - 0
src/main/java/com/jeesite/modules/mes/service/MesErpPrdInstockLogService.java

@@ -1,11 +1,14 @@
 package com.jeesite.modules.mes.service;
 
 import com.jeesite.common.service.CrudService;
+import com.jeesite.common.lang.StringUtils;
 import com.jeesite.modules.mes.dao.MesErpPrdInstockLogDao;
 import com.jeesite.modules.mes.entity.MesErpPrdInstockLog;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.util.List;
+
 /**
  * MES 推 ERP 生产入库日志 Service
  */
@@ -16,4 +19,14 @@ public class MesErpPrdInstockLogService extends CrudService<MesErpPrdInstockLogD
 	public void saveLog(MesErpPrdInstockLog log) {
 		super.save(log);
 	}
+
+	public MesErpPrdInstockLog findByErpBillNo(String erpBillNo) {
+		if (StringUtils.isBlank(erpBillNo)) {
+			return null;
+		}
+		MesErpPrdInstockLog query = new MesErpPrdInstockLog();
+		query.setErpBillNo(erpBillNo.trim());
+		List<MesErpPrdInstockLog> list = findList(query);
+		return list.isEmpty() ? null : list.get(0);
+	}
 }

+ 325 - 0
src/main/java/com/jeesite/modules/mes/service/MesErpScheduleService.java

@@ -4,9 +4,12 @@ import com.jeesite.common.entity.Page;
 import com.jeesite.common.lang.StringUtils;
 import com.jeesite.common.service.CrudService;
 import com.jeesite.common.service.ServiceException;
+import com.jeesite.common.mybatis.mapper.query.QueryType;
 import com.jeesite.modules.mes.dao.MesErpScheduleDao;
 import com.jeesite.modules.mes.entity.MesErpSchedule;
 import com.jeesite.modules.mes.entity.MesLine;
+import com.jeesite.modules.mes.entity.MesLineProcess;
+import com.jeesite.modules.mes.entity.MesProductRecord;
 import com.jeesite.modules.mes.erp.ErpProductionOrder;
 import com.jeesite.modules.mes.erp.ErpProductionOrderService;
 import org.springframework.stereotype.Service;
@@ -14,13 +17,23 @@ import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.ObjectUtils;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.DayOfWeek;
 import java.time.LocalDate;
+import java.time.YearMonth;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalAdjusters;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * ERP排产计划Service
@@ -29,6 +42,7 @@ import java.util.Map;
 public class MesErpScheduleService extends CrudService<MesErpScheduleDao, MesErpSchedule> {
 
 	private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+	private static final String CRAFT_TOTAL_RESULT = "100000";
 	private static final Map<String, String> CATEGORY_LINE_SN_MAP;
 
 	static {
@@ -41,13 +55,19 @@ public class MesErpScheduleService extends CrudService<MesErpScheduleDao, MesErp
 
 	private final MesErpScheduleDao mesErpScheduleDao;
 	private final MesLineService mesLineService;
+	private final MesLineProcessService mesLineProcessService;
+	private final MesProductRecordService mesProductRecordService;
 	private final ErpProductionOrderService erpProductionOrderService;
 
 	public MesErpScheduleService(MesErpScheduleDao mesErpScheduleDao,
 			MesLineService mesLineService,
+			MesLineProcessService mesLineProcessService,
+			MesProductRecordService mesProductRecordService,
 			ErpProductionOrderService erpProductionOrderService) {
 		this.mesErpScheduleDao = mesErpScheduleDao;
 		this.mesLineService = mesLineService;
+		this.mesLineProcessService = mesLineProcessService;
+		this.mesProductRecordService = mesProductRecordService;
 		this.erpProductionOrderService = erpProductionOrderService;
 	}
 
@@ -136,6 +156,292 @@ public class MesErpScheduleService extends CrudService<MesErpScheduleDao, MesErp
 		return sum == null ? 0 : sum;
 	}
 
+	/**
+	 * 排产看板数据:按月份返回周历行与计划条
+	 */
+	public Map<String, Object> buildBoardDataForMonth(String monthStr) {
+		YearMonth yearMonth = parseYearMonth(monthStr);
+		LocalDate monthStart = yearMonth.atDay(1);
+		LocalDate monthEnd = yearMonth.atEndOfMonth();
+		String startStr = monthStart.format(DATE_FORMAT);
+		String endStr = monthEnd.format(DATE_FORMAT);
+
+		List<MesErpSchedule> plans = mesErpScheduleDao.findListInDateRange(startStr, endStr);
+		if (plans == null) {
+			plans = Collections.emptyList();
+		}
+
+		Set<String> lineIdsWithPlan = new LinkedHashSet<>();
+		for (MesErpSchedule plan : plans) {
+			if (plan != null && StringUtils.isNotBlank(plan.getLineId())) {
+				lineIdsWithPlan.add(plan.getLineId());
+			}
+		}
+
+		Map<String, Object> result = new HashMap<>();
+		result.put("month", yearMonth.toString());
+		result.put("monthLabel", yearMonth.getYear() + "\u5e74" + yearMonth.getMonthValue() + "\u6708");
+		result.put("startDate", startStr);
+		result.put("endDate", endStr);
+		result.put("weeks", buildMonthWeeks(monthStart, monthEnd));
+		result.put("lines", buildBoardLines(lineIdsWithPlan));
+		result.put("plans", plans);
+		return result;
+	}
+
+	/**
+	 * 排产详情:产线工位及工艺号100000 OK数量
+	 */
+	public Map<String, Object> buildScheduleDetail(MesErpSchedule schedule) {
+		if (schedule == null || StringUtils.isBlank(schedule.getId())) {
+			throw new ServiceException(text("排产计划不存在"));
+		}
+
+		List<Map<String, Object>> stationStats = buildStationProductionStats(schedule);
+		long totalOk = 0;
+		for (Map<String, Object> row : stationStats) {
+			Object okQty = row.get("okQty");
+			if (okQty instanceof Number) {
+				totalOk += ((Number) okQty).longValue();
+			}
+		}
+
+		Map<String, Object> result = new HashMap<>();
+		result.put("schedule", schedule);
+		result.put("stationStats", stationStats);
+		result.put("totalOkQty", totalOk);
+		result.put("stationCount", stationStats.size());
+		return result;
+	}
+
+	private List<Map<String, Object>> buildStationProductionStats(MesErpSchedule schedule) {
+		MesLine line = resolveScheduleLine(schedule);
+		if (line == null || StringUtils.isBlank(line.getId())) {
+			return Collections.emptyList();
+		}
+		String lineId = line.getId().trim();
+		String lineSn = StringUtils.trim(line.getSn());
+		schedule.setLineId(lineId);
+		schedule.setLineSn(lineSn);
+		schedule.setLineTitle(line.getTitle());
+
+		List<MesLineProcess> processes = mesLineProcessService.findParentListByLine(lineId, lineSn);
+		if (processes == null || processes.isEmpty()) {
+			return Collections.emptyList();
+		}
+
+		String startTime = normalizeDate(schedule.getScheduleStartDate()) + " 00:00:00";
+		String endTime = normalizeDate(schedule.getScheduleEndDate()) + " 23:59:59";
+		Integer scheduleQty = schedule.getScheduleQty();
+		List<Map<String, Object>> rows = new ArrayList<>();
+		for (MesLineProcess process : processes) {
+			if (process == null || StringUtils.isBlank(process.getOprno())) {
+				continue;
+			}
+			long okQty = countStationCraftOk(lineSn, process.getOprno(), startTime, endTime);
+			Map<String, Object> row = new LinkedHashMap<>();
+			row.put("oprno", process.getOprno());
+			row.put("title", StringUtils.defaultString(process.getTitle()));
+			row.put("lineTitle", StringUtils.defaultString(process.getLineTitle()));
+			row.put("lineSn", StringUtils.defaultString(process.getLineSn()));
+			row.put("sorts", process.getSorts());
+			row.put("okQty", okQty);
+			row.put("scheduleQty", scheduleQty);
+			row.put("completionRate", calcCompletionRate(okQty, scheduleQty));
+			rows.add(row);
+		}
+		return rows;
+	}
+
+	private MesLine resolveScheduleLine(MesErpSchedule schedule) {
+		if (schedule == null) {
+			return null;
+		}
+		MesLine byId = null;
+		if (StringUtils.isNotBlank(schedule.getLineId())) {
+			byId = mesLineService.get(new MesLine(schedule.getLineId().trim()));
+		}
+		MesLine bySn = null;
+		if (StringUtils.isNotBlank(schedule.getLineSn())) {
+			MesLine query = new MesLine();
+			query.setSn(schedule.getLineSn().trim());
+			query.setStatus(MesLine.STATUS_NORMAL);
+			bySn = mesLineService.findInfo(query);
+		}
+		if (bySn != null) {
+			return bySn;
+		}
+		return byId;
+	}
+
+	private long countStationCraftOk(String lineSn, String oprno, String startTime, String endTime) {
+		if (StringUtils.isBlank(lineSn) || StringUtils.isBlank(oprno)) {
+			return 0;
+		}
+		MesProductRecord query = new MesProductRecord();
+		query.setContent("OK");
+		query.setCraft(CRAFT_TOTAL_RESULT);
+		query.setLineSn(lineSn);
+		query.getSqlMap().getWhere()
+				.and("a.oprno", QueryType.RIGHT_LIKE, oprno.trim())
+				.and("a.update_date", QueryType.GTE, startTime)
+				.and("a.update_date", QueryType.LTE, endTime);
+		Long count = mesProductRecordService.findCount(query);
+		return count == null ? 0L : count;
+	}
+
+	private String calcCompletionRate(long okQty, Integer scheduleQty) {
+		if (scheduleQty == null || scheduleQty <= 0) {
+			return "-";
+		}
+		BigDecimal rate = BigDecimal.valueOf(okQty)
+				.multiply(BigDecimal.valueOf(100))
+				.divide(BigDecimal.valueOf(scheduleQty), 1, RoundingMode.HALF_UP);
+		return rate.toPlainString() + "%";
+	}
+
+	private List<Map<String, Object>> buildMonthWeeks(LocalDate monthStart, LocalDate monthEnd) {
+		List<Map<String, Object>> weeks = new ArrayList<>();
+		LocalDate weekStart = monthStart.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
+		LocalDate lastWeekStart = monthEnd.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
+		while (!weekStart.isAfter(lastWeekStart)) {
+			List<Map<String, Object>> days = new ArrayList<>();
+			boolean weekInMonth = false;
+			for (int i = 0; i < 7; i++) {
+				LocalDate day = weekStart.plusDays(i);
+				boolean inMonth = !day.isBefore(monthStart) && !day.isAfter(monthEnd);
+				if (inMonth) {
+					weekInMonth = true;
+				}
+				Map<String, Object> cell = new HashMap<>();
+				cell.put("date", day.format(DATE_FORMAT));
+				cell.put("day", day.getDayOfMonth());
+				cell.put("inMonth", inMonth);
+				cell.put("weekend", day.getDayOfWeek() == DayOfWeek.SATURDAY
+						|| day.getDayOfWeek() == DayOfWeek.SUNDAY);
+				days.add(cell);
+			}
+			if (weekInMonth) {
+				Map<String, Object> week = new HashMap<>();
+				week.put("weekStart", weekStart.format(DATE_FORMAT));
+				week.put("days", days);
+				weeks.add(week);
+			}
+			weekStart = weekStart.plusDays(7);
+		}
+		return weeks;
+	}
+
+	private YearMonth parseYearMonth(String monthStr) {
+		if (StringUtils.isBlank(monthStr)) {
+			return YearMonth.now();
+		}
+		try {
+			return YearMonth.parse(monthStr.trim());
+		} catch (Exception ex) {
+			throw new ServiceException(text("月份格式不正确,请使用 yyyy-MM"));
+		}
+	}
+
+	/**
+	 * 排产看板数据:按日期范围返回产线行与计划条
+	 */
+	public Map<String, Object> buildBoardData(String startDate, String endDate) {
+		LocalDate rangeStart = parseDate(startDate, "开始日期");
+		LocalDate rangeEnd = parseDate(endDate, "结束日期");
+		if (rangeEnd.isBefore(rangeStart)) {
+			throw new ServiceException(text("结束日期不能早于开始日期"));
+		}
+		long daySpan = ChronoUnit.DAYS.between(rangeStart, rangeEnd) + 1;
+		if (daySpan > 62) {
+			throw new ServiceException(text("看板日期范围不能超过 {0} 天", "62"));
+		}
+
+		List<String> dates = new ArrayList<>();
+		for (LocalDate cursor = rangeStart; !cursor.isAfter(rangeEnd); cursor = cursor.plusDays(1)) {
+			dates.add(cursor.format(DATE_FORMAT));
+		}
+
+		String startStr = rangeStart.format(DATE_FORMAT);
+		String endStr = rangeEnd.format(DATE_FORMAT);
+		List<MesErpSchedule> plans = mesErpScheduleDao.findListInDateRange(startStr, endStr);
+		if (plans == null) {
+			plans = Collections.emptyList();
+		}
+
+		Set<String> lineIdsWithPlan = new LinkedHashSet<>();
+		for (MesErpSchedule plan : plans) {
+			if (plan != null && StringUtils.isNotBlank(plan.getLineId())) {
+				lineIdsWithPlan.add(plan.getLineId());
+			}
+		}
+
+		List<Map<String, String>> lineRows = buildBoardLines(lineIdsWithPlan);
+		Map<String, Object> result = new HashMap<>();
+		result.put("startDate", startStr);
+		result.put("endDate", endStr);
+		result.put("dates", dates);
+		result.put("lines", lineRows);
+		result.put("plans", plans);
+		return result;
+	}
+
+	private List<Map<String, String>> buildBoardLines(Set<String> lineIdsWithPlan) {
+		MesLine query = new MesLine();
+		query.setStatus(MesLine.STATUS_NORMAL);
+		List<MesLine> allLines = mesLineService.findList(query);
+		if (allLines == null) {
+			allLines = Collections.emptyList();
+		}
+
+		Map<String, MesLine> lineMap = new LinkedHashMap<>();
+		for (MesLine line : allLines) {
+			if (line == null || StringUtils.isBlank(line.getId())) {
+				continue;
+			}
+			String sn = StringUtils.trim(line.getSn());
+			if ("HEVXT".equalsIgnoreCase(sn) || "115XT".equalsIgnoreCase(sn) || lineIdsWithPlan.contains(line.getId())) {
+				lineMap.put(line.getId(), line);
+			}
+		}
+		for (String lineId : lineIdsWithPlan) {
+			if (lineMap.containsKey(lineId)) {
+				continue;
+			}
+			MesLine line = mesLineService.get(new MesLine(lineId));
+			if (!ObjectUtils.isEmpty(line) && MesLine.STATUS_NORMAL.equals(line.getStatus())) {
+				lineMap.put(line.getId(), line);
+			}
+		}
+
+		List<MesLine> sorted = new ArrayList<>(lineMap.values());
+		sorted.sort(Comparator.comparingInt(this::boardLineSortOrder).thenComparing(
+				line -> StringUtils.defaultString(line.getSn())));
+		List<Map<String, String>> rows = new ArrayList<>();
+		for (MesLine line : sorted) {
+			Map<String, String> row = new HashMap<>();
+			row.put("id", line.getId());
+			row.put("sn", StringUtils.defaultString(line.getSn()));
+			row.put("title", StringUtils.defaultString(line.getTitle()));
+			rows.add(row);
+		}
+		return rows;
+	}
+
+	private int boardLineSortOrder(MesLine line) {
+		if (line == null) {
+			return 99;
+		}
+		String sn = StringUtils.trim(line.getSn());
+		if ("HEVXT".equalsIgnoreCase(sn)) {
+			return 0;
+		}
+		if ("115XT".equalsIgnoreCase(sn)) {
+			return 1;
+		}
+		return 10;
+	}
+
 	@Override
 	@Transactional
 	public void save(MesErpSchedule mesErpSchedule) {
@@ -193,6 +499,25 @@ public class MesErpScheduleService extends CrudService<MesErpScheduleDao, MesErp
 		if (mesErpSchedule.getScheduleQty() > remainQty) {
 			throw new ServiceException(text("排产数量不能超过剩余可排产量(剩余 {0})", String.valueOf(remainQty)));
 		}
+
+		validateLineDateConflict(mesErpSchedule.getLineId(), mesErpSchedule.getScheduleStartDate(),
+				mesErpSchedule.getScheduleEndDate(), mesErpSchedule.getId());
+	}
+
+	private void validateLineDateConflict(String lineId, String scheduleStartDate, String scheduleEndDate,
+			String excludeId) {
+		if (StringUtils.isBlank(lineId)) {
+			return;
+		}
+		MesErpSchedule conflict = mesErpScheduleDao.findLineDateConflict(lineId, scheduleStartDate, scheduleEndDate,
+				excludeId);
+		if (conflict == null) {
+			return;
+		}
+		throw new ServiceException(text("该产线在排产日期内已有其他计划({0},{1} ~ {2}),每条产线每天只能有一个计划",
+				StringUtils.defaultString(conflict.getErpBillNo()),
+				StringUtils.defaultString(conflict.getScheduleStartDate()),
+				StringUtils.defaultString(conflict.getScheduleEndDate())));
 	}
 
 	private void fillLineSnapshot(MesErpSchedule mesErpSchedule) {

+ 11 - 0
src/main/java/com/jeesite/modules/mes/service/MesLineProcessService.java

@@ -1,6 +1,7 @@
 package com.jeesite.modules.mes.service;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 import com.alibaba.fastjson.JSON;
@@ -112,6 +113,16 @@ public class MesLineProcessService extends CrudService<MesLineProcessDao, MesLin
 	}
 
 	/**
+	 * 按产线查询主工位(pid 为空,与工位管理列表一致)
+	 */
+	public List<MesLineProcess> findParentListByLine(String lineId, String lineSn) {
+		if (StringUtils.isBlank(lineId)) {
+			return Collections.emptyList();
+		}
+		return dao.findParentListByLine(lineId.trim(), StringUtils.trim(lineSn));
+	}
+
+	/**
 	 * 查询子表分页数据
 	 * @param mesLineProcessUser
 	 * @param mesLineProcessUser.page 分页对象

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

@@ -11,6 +11,12 @@ import com.jeesite.modules.mes.erp.ErpProductionOrder;
 import com.jeesite.modules.mes.erp.ErpProductionOrderService;
 import com.jeesite.modules.mes.erp.ErpTransferDirect;
 import com.jeesite.modules.mes.erp.ErpTransferDirectService;
+import com.jeesite.modules.mes.entity.MesErpPrdInstockLog;
+import com.jeesite.modules.mes.entity.MesErpSchedule;
+import com.jeesite.modules.mes.erp.KingdeeErpProperties;
+import com.jeesite.modules.mes.service.MesErpPrdInstockLogService;
+import com.jeesite.modules.mes.service.MesErpScheduleService;
+import org.apache.shiro.authz.annotation.Logical;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
@@ -32,15 +38,24 @@ public class ErpProductionOrderController extends BaseController {
 	private final ErpPickMaterialBillService erpPickMaterialBillService;
 	private final ErpPrdInStockService erpPrdInStockService;
 	private final ErpTransferDirectService erpTransferDirectService;
+	private final MesErpScheduleService mesErpScheduleService;
+	private final MesErpPrdInstockLogService mesErpPrdInstockLogService;
+	private final KingdeeErpProperties erpProperties;
 
 	public ErpProductionOrderController(ErpProductionOrderService erpProductionOrderService,
 			ErpPickMaterialBillService erpPickMaterialBillService,
 			ErpPrdInStockService erpPrdInStockService,
-			ErpTransferDirectService erpTransferDirectService) {
+			ErpTransferDirectService erpTransferDirectService,
+			MesErpScheduleService mesErpScheduleService,
+			MesErpPrdInstockLogService mesErpPrdInstockLogService,
+			KingdeeErpProperties erpProperties) {
 		this.erpProductionOrderService = erpProductionOrderService;
 		this.erpPickMaterialBillService = erpPickMaterialBillService;
 		this.erpPrdInStockService = erpPrdInStockService;
 		this.erpTransferDirectService = erpTransferDirectService;
+		this.mesErpScheduleService = mesErpScheduleService;
+		this.mesErpPrdInstockLogService = mesErpPrdInstockLogService;
+		this.erpProperties = erpProperties;
 	}
 
 	@RequiresPermissions("mes:erpProductionOrder:view")
@@ -84,6 +99,26 @@ public class ErpProductionOrderController extends BaseController {
 		return page;
 	}
 
+	@RequiresPermissions(value = {"mes:mesErpSchedule:view", "mes:erpProductionOrder:view"}, logical = Logical.OR)
+	@RequestMapping(value = "scheduleSelect")
+	public String scheduleSelect(ErpProductionOrder erpProductionOrder, Model model) {
+		model.addAttribute("erpProductionOrder", erpProductionOrder);
+		return "modules/mes/erpProductionOrderScheduleSelect";
+	}
+
+	@RequiresPermissions(value = {"mes:mesErpSchedule:view", "mes:erpProductionOrder:view"}, logical = Logical.OR)
+	@RequestMapping(value = "scheduleSelectData")
+	@ResponseBody
+	public Page<ErpProductionOrder> scheduleSelectData(ErpProductionOrder erpProductionOrder,
+			HttpServletRequest request, HttpServletResponse response) {
+		Page<ErpProductionOrder> page = new Page<>(request, response);
+		List<ErpProductionOrder> list = erpProductionOrderService.findDetailList(erpProductionOrder.getBillNo());
+		list.removeIf(row -> row == null || !erpProperties.isAllowedProductionOrderMaterial(row.getMaterialNumber()));
+		page.setList(list);
+		page.setCount(list.size());
+		return page;
+	}
+
 	@RequiresPermissions("mes:erpProductionOrder:view")
 	@RequestMapping(value = "downstream")
 	public String downstream(ErpProductionOrder erpProductionOrder, HttpServletRequest request, Model model) {
@@ -102,6 +137,20 @@ public class ErpProductionOrderController extends BaseController {
 	}
 
 	@RequiresPermissions("mes:erpProductionOrder:view")
+	@RequestMapping(value = "pushdown")
+	public String pushdown(ErpProductionOrder erpProductionOrder, HttpServletRequest request, Model model) {
+		String billNo = erpProductionOrder == null ? "" : erpProductionOrder.getBillNo();
+		if (StringUtils.isBlank(billNo) || !erpProductionOrderService.isVisibleProductionOrder(billNo.trim())) {
+			model.addAttribute("message", "生产订单不存在或不在 MES 可见范围内");
+		}
+		billNo = StringUtils.trim(billNo);
+		model.addAttribute("billNo", billNo);
+		model.addAttribute("modal", Boolean.TRUE.equals(
+				"true".equalsIgnoreCase(StringUtils.trim(request.getParameter("modal")))));
+		return "modules/mes/erpProductionOrderPushdown";
+	}
+
+	@RequiresPermissions("mes:erpProductionOrder:view")
 	@RequestMapping(value = "downstreamPickData")
 	@ResponseBody
 	public Page<ErpPickMaterialBill> downstreamPickData(ErpProductionOrder erpProductionOrder,
@@ -154,6 +203,42 @@ public class ErpProductionOrderController extends BaseController {
 		return page;
 	}
 
+	@RequiresPermissions("mes:erpProductionOrder:view")
+	@RequestMapping(value = "downstreamScheduleData")
+	@ResponseBody
+	public Page<MesErpSchedule> downstreamScheduleData(ErpProductionOrder erpProductionOrder,
+			HttpServletRequest request, HttpServletResponse response) {
+		Page<MesErpSchedule> page = new Page<>(request, response);
+		String billNo = erpProductionOrder == null ? "" : erpProductionOrder.getBillNo();
+		if (StringUtils.isBlank(billNo) || !erpProductionOrderService.isVisibleProductionOrder(billNo.trim())) {
+			return page;
+		}
+		MesErpSchedule query = new MesErpSchedule();
+		query.setErpBillNo(billNo.trim());
+		List<MesErpSchedule> list = mesErpScheduleService.findList(query);
+		page.setList(list);
+		page.setCount(list.size());
+		return page;
+	}
+
+	@RequiresPermissions("mes:erpProductionOrder:view")
+	@RequestMapping(value = "downstreamInstockBindData")
+	@ResponseBody
+	public Page<MesErpPrdInstockLog> downstreamInstockBindData(ErpProductionOrder erpProductionOrder,
+			HttpServletRequest request, HttpServletResponse response) {
+		Page<MesErpPrdInstockLog> page = new Page<>(request, response);
+		String billNo = erpProductionOrder == null ? "" : erpProductionOrder.getBillNo();
+		if (StringUtils.isBlank(billNo) || !erpProductionOrderService.isVisibleProductionOrder(billNo.trim())) {
+			return page;
+		}
+		MesErpPrdInstockLog query = new MesErpPrdInstockLog();
+		query.setMoBillNo(billNo.trim());
+		List<MesErpPrdInstockLog> list = mesErpPrdInstockLogService.findList(query);
+		page.setList(list);
+		page.setCount(list.size());
+		return page;
+	}
+
 	private List<String> extractInstockLots(List<ErpPrdInStock> instockList) {
 		Set<String> lots = new LinkedHashSet<>();
 		if (instockList != null) {

+ 88 - 0
src/main/java/com/jeesite/modules/mes/web/MesErpPrdInstockLogController.java

@@ -0,0 +1,88 @@
+package com.jeesite.modules.mes.web;
+
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.lang.StringUtils;
+import com.jeesite.common.mybatis.mapper.query.QueryType;
+import com.jeesite.common.web.BaseController;
+import com.jeesite.modules.mes.entity.MesBatchSn;
+import com.jeesite.modules.mes.entity.MesErpPrdInstockLog;
+import com.jeesite.modules.mes.service.MesBatchSnService;
+import com.jeesite.modules.mes.service.MesErpPrdInstockLogService;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * MES 生产入库单与批次码绑定明细。
+ */
+@Controller
+@RequestMapping(value = "${adminPath}/mes/mesErpPrdInstockLog")
+public class MesErpPrdInstockLogController extends BaseController {
+
+	private final MesErpPrdInstockLogService mesErpPrdInstockLogService;
+	private final MesBatchSnService mesBatchSnService;
+
+	public MesErpPrdInstockLogController(MesErpPrdInstockLogService mesErpPrdInstockLogService,
+			MesBatchSnService mesBatchSnService) {
+		this.mesErpPrdInstockLogService = mesErpPrdInstockLogService;
+		this.mesBatchSnService = mesBatchSnService;
+	}
+
+	@ModelAttribute
+	public MesErpPrdInstockLog get(String id, boolean isNewRecord) {
+		return mesErpPrdInstockLogService.get(id, isNewRecord);
+	}
+
+	@RequiresPermissions("mes:mesErpPrdInstockLog:view")
+	@RequestMapping(value = {"list", ""})
+	public String list(MesErpPrdInstockLog mesErpPrdInstockLog, Model model) {
+		model.addAttribute("mesErpPrdInstockLog", mesErpPrdInstockLog);
+		return "modules/mes/mesErpPrdInstockLogList";
+	}
+
+	@RequiresPermissions("mes:mesErpPrdInstockLog:view")
+	@RequestMapping(value = "listData")
+	@ResponseBody
+	public Page<MesErpPrdInstockLog> listData(MesErpPrdInstockLog mesErpPrdInstockLog,
+			HttpServletRequest request, HttpServletResponse response) {
+		mesErpPrdInstockLog.setPage(new Page<>(request, response));
+		String batchSnQuery = StringUtils.trim(request.getParameter("batchSnQuery"));
+		if (StringUtils.isNotBlank(batchSnQuery)) {
+			mesErpPrdInstockLog.getSqlMap().getWhere()
+					.and("a.batch_sn_list", QueryType.LIKE, batchSnQuery);
+		}
+		return mesErpPrdInstockLogService.findPage(mesErpPrdInstockLog);
+	}
+
+	@RequiresPermissions("mes:mesErpPrdInstockLog:view")
+	@RequestMapping(value = "detail")
+	public String detail(MesErpPrdInstockLog mesErpPrdInstockLog, Model model) {
+		List<MesBatchSn> batchList = mesBatchSnService.findByErpInstockBillNo(mesErpPrdInstockLog.getErpBillNo());
+		model.addAttribute("mesErpPrdInstockLog", mesErpPrdInstockLog);
+		model.addAttribute("batchList", batchList);
+		return "modules/mes/mesErpPrdInstockLogDetail";
+	}
+
+	/** 从 ERP 入库单号跳转绑定明细(MES 推送才有记录) */
+	@RequiresPermissions("mes:mesErpPrdInstockLog:view")
+	@RequestMapping(value = "byErpBill")
+	public String byErpBill(String erpBillNo, Model model) {
+		String billNo = StringUtils.trim(erpBillNo);
+		MesErpPrdInstockLog log = mesErpPrdInstockLogService.findByErpBillNo(billNo);
+		if (log == null) {
+			MesErpPrdInstockLog query = new MesErpPrdInstockLog();
+			query.setErpBillNo(billNo);
+			model.addAttribute("mesErpPrdInstockLog", query);
+			model.addAttribute("message", "入库单 " + billNo + " 无 MES 批次绑定记录(可能非 MES 推送生成)");
+			return "modules/mes/mesErpPrdInstockLogList";
+		}
+		return detail(log, model);
+	}
+}

+ 43 - 0
src/main/java/com/jeesite/modules/mes/web/MesErpScheduleController.java

@@ -20,6 +20,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import java.time.YearMonth;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -61,6 +62,48 @@ public class MesErpScheduleController extends BaseController {
 		return mesErpScheduleService.findPage(mesErpSchedule);
 	}
 
+	@RequiresPermissions("mes:mesErpSchedule:view")
+	@RequestMapping(value = "board")
+	public String board(String month, Model model) {
+		String monthVal = StringUtils.isNotBlank(month) ? month.trim() : YearMonth.now().toString();
+		model.addAttribute("month", monthVal);
+		return "modules/mes/mesErpScheduleBoard";
+	}
+
+	@RequiresPermissions("mes:mesErpSchedule:view")
+	@RequestMapping(value = "boardData")
+	@ResponseBody
+	public Map<String, Object> boardData(String month) {
+		Map<String, Object> map = new HashMap<>();
+		try {
+			Map<String, Object> data = mesErpScheduleService.buildBoardDataForMonth(month);
+			map.put("result", Global.TRUE);
+			map.put("data", data);
+		} catch (Exception ex) {
+			map.put("result", Global.FALSE);
+			map.put("message", ex.getMessage());
+		}
+		return map;
+	}
+
+	@RequiresPermissions("mes:mesErpSchedule:view")
+	@RequestMapping(value = "detail")
+	public String detail(MesErpSchedule mesErpSchedule, Model model) {
+		Map<String, Object> detail = mesErpScheduleService.buildScheduleDetail(mesErpSchedule);
+		model.addAttribute("mesErpSchedule", detail.get("schedule"));
+		model.addAttribute("stationStats", detail.get("stationStats"));
+		model.addAttribute("totalOkQty", detail.get("totalOkQty"));
+		model.addAttribute("stationCount", detail.get("stationCount"));
+		MesErpSchedule schedule = (MesErpSchedule) detail.get("schedule");
+		String boardMonth = "";
+		if (schedule != null && StringUtils.isNotBlank(schedule.getScheduleStartDate())
+				&& schedule.getScheduleStartDate().length() >= 7) {
+			boardMonth = schedule.getScheduleStartDate().substring(0, 7);
+		}
+		model.addAttribute("boardMonth", boardMonth);
+		return "modules/mes/mesErpScheduleDetail";
+	}
+
 	@RequiresPermissions(value = {"mes:mesErpSchedule:view", "mes:erpProductionOrder:view"}, logical = Logical.OR)
 	@RequestMapping(value = "form")
 	public String form(MesErpSchedule mesErpSchedule, Model model) {

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

@@ -49,6 +49,9 @@ erp:
     inventory-category-filter-enabled: true
     # PRD_MO BillQuery 存货类别字段(Apifox 已验证:用 F_XYLX_BaseProperty_jv4,不要加 FTreeEntity_ 前缀)
     inventory-category-field: F_XYLX_BaseProperty_jv4
+    # 生产订单 PRD_MO 列表已不再按物料编码过滤;以下配置仅作兼容保留,生产入库选单仍用 prd-in-stock.material-numbers
+    production-order:
+      material-numbers:
     lcid: 2052
     # 生产领料单 PRD_PickMtrl Save(方案B),编码需与 Apifox 实测一致
     pick-mtrl:

+ 28 - 0
src/main/resources/mappings/modules/mes/MesErpScheduleDao.xml

@@ -13,4 +13,32 @@
 		</if>
 	</select>
 
+	<select id="findLineDateConflict" resultType="com.jeesite.modules.mes.entity.MesErpSchedule">
+		SELECT id, erp_bill_no AS erpBillNo, schedule_start_date AS scheduleStartDate,
+			schedule_end_date AS scheduleEndDate, line_title AS lineTitle, material_name AS materialName
+		FROM mes_erp_schedule
+		WHERE status = '0'
+		  AND line_id = #{lineId}
+		  AND schedule_start_date &lt;= #{scheduleEndDate}
+		  AND schedule_end_date &gt;= #{scheduleStartDate}
+		<if test="excludeId != null and excludeId != ''">
+			AND id != #{excludeId}
+		</if>
+		ORDER BY schedule_start_date ASC
+		LIMIT 1
+	</select>
+
+	<select id="findListInDateRange" resultType="com.jeesite.modules.mes.entity.MesErpSchedule">
+		SELECT id, erp_bill_no AS erpBillNo, erp_entry_id AS erpEntryId, erp_seq AS erpSeq,
+			material_number AS materialNumber, material_name AS materialName,
+			inventory_category AS inventoryCategory, line_id AS lineId, line_sn AS lineSn,
+			line_title AS lineTitle, schedule_start_date AS scheduleStartDate,
+			schedule_end_date AS scheduleEndDate, schedule_qty AS scheduleQty
+		FROM mes_erp_schedule
+		WHERE status = '0'
+		  AND schedule_start_date &lt;= #{endDate}
+		  AND schedule_end_date &gt;= #{startDate}
+		ORDER BY line_id ASC, schedule_start_date ASC
+	</select>
+
 </mapper>

+ 21 - 0
src/main/resources/mappings/modules/mes/MesLineProcessDao.xml

@@ -38,4 +38,25 @@
 		update   mes_line_process set stop=#{stop}  where oprno=#{oprno}
 	</update>
 
+	<select id="findParentListByLine" resultType="com.jeesite.modules.mes.entity.MesLineProcess">
+		SELECT a.id,
+			a.line_id AS lineId,
+			a.title,
+			a.oprno,
+			a.sorts,
+			a.status,
+			a.pid,
+			b.title AS lineTitle,
+			b.sn AS lineSn
+		FROM mes_line_process a
+		INNER JOIN mes_line b ON b.id = a.line_id AND b.status = '0'
+		WHERE a.status = '0'
+		  AND a.line_id = #{lineId}
+		<if test="lineSn != null and lineSn != ''">
+		  AND b.sn = #{lineSn}
+		</if>
+		  AND (a.pid IS NULL OR a.pid = '')
+		ORDER BY a.sorts ASC, a.oprno ASC
+	</select>
+
 </mapper>

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

@@ -26,6 +26,11 @@
 
 		<div class="box-body">
 
+			<div class="alert alert-info" style="margin-bottom:10px;">
+				<i class="fa fa-info-circle"></i>
+				${text('本页统计的是')} <strong>${text('领料单')}</strong>${text('张数,与生产订单条数不是一一对应:一张生产订单可有多张领料单,也可能尚未领料。留空生产订单号时展示全部已关联生产订单的领料单。')}
+			</div>
+
 			<#form:form id="searchForm" model="${erpPickMaterialBill}" action="${ctx}/mes/erpPickMaterialBill/listData" method="post" class="form-inline hide"
 
 					data-page-no="${parameter.pageNo}" data-page-size="${parameter.pageSize}" data-order-by="${parameter.orderBy}">
@@ -36,7 +41,7 @@
 
 					<div class="control-inline">
 
-						<#form:input path="moBillNoQuery" maxlength="100" class="form-control width-180" placeholder="${text('留空则显示MES可见订单的全部领料')}"/>
+						<#form:input path="moBillNoQuery" maxlength="100" class="form-control width-180" placeholder="${text('留空则显示全部已关联生产订单的领料')}"/>
 
 					</div>
 

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

@@ -71,6 +71,9 @@ $('#dataGrid').dataGrid({
 		{header:'${text("入库单号")}', name:'billNo', index:'billNo', width:170, align:"center", frozen:true, formatter: function(val){
 			return '<a href="${ctx}/mes/erpPrdInStock/detail?billNo='+encodeURIComponent(val)+'" class="addTabPage" data-title="${text("ERP生产入库单明细")}">'+val+'</a>';
 		}},
+		{header:'${text("MES绑定")}', name:'bindActions', width:90, align:"center", formatter: function(val, obj, row){
+			return '<a href="${ctx}/mes/mesErpPrdInstockLog/byErpBill?erpBillNo='+encodeURIComponent(row.billNo)+'" class="addTabPage" data-title="${text("批次绑定明细")}"><i class="fa fa-link"></i> ${text("绑定")}</a>';
+		}},
 		{header:'${text("入库日期")}', name:'date', index:'date', width:100, align:"center"},
 		{header:'${text("单据状态")}', name:'documentStatusName', index:'documentStatusName', width:90, align:"center"},
 		{header:'${text("生产订单号")}', name:'moBillNo', index:'moBillNo', width:170, align:"center"},

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

@@ -25,10 +25,6 @@
 $('#dataGrid').dataGrid({
 	searchForm: $("#searchForm"),
 	columnModel: [
-		{header:'${text("操作")}', name:'actions', width:100, align:"center", frozen:true, formatter: function(val, obj, row){
-			return '<a href="${ctx}/mes/mesErpSchedule/form?erpBillNo='+encodeURIComponent(row.billNo)+'&erpEntryId='+encodeURIComponent(row.entryId)+'" class="btnList addTabPage" data-title="${text("ERP排产")}">'+
-				'<i class="fa fa-calendar-plus-o"></i> ${text("排产")}</a>';
-		}},
 		{header:'${text("行号")}', name:'seq', index:'seq', width:80, align:"center", frozen:true},
 		{header:'${text("物料编码")}', name:'materialNumber', index:'materialNumber', width:220, align:"left"},
 		{header:'${text("物料名称")}', name:'materialName', index:'materialName', width:260, align:"left"},

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

@@ -36,7 +36,7 @@
 
 				<i class="fa fa-info-circle"></i>
 
-				${text('直接调拨单无生产订单号字段,按生产入库批号及源单号关联展示。生产入库与直接调拨单均仅展示 HEV/115 物料。')}
+				${text('直接调拨单无生产订单号字段,按生产入库批号及源单号关联展示。生产入库与直接调拨单仅展示 HEV/115 物料;领料单展示全部用料明细。')}
 
 			</div>
 
@@ -60,6 +60,18 @@
 
 				</li>
 
+				<li role="presentation">
+
+					<a href="#tabSchedule" aria-controls="tabSchedule" role="tab" data-toggle="tab">${text('ERP排产计划')}</a>
+
+				</li>
+
+				<li role="presentation">
+
+					<a href="#tabInstockBind" aria-controls="tabInstockBind" role="tab" data-toggle="tab">${text('批次绑定')}</a>
+
+				</li>
+
 			</ul>
 
 			<div class="tab-content erp-mo-downstream-tab-content">
@@ -124,6 +136,38 @@
 
 				</div>
 
+				<div role="tabpanel" class="tab-pane" id="tabSchedule">
+
+					<#form:form id="scheduleSearchForm" model="${erpProductionOrder!}" action="${ctx}/mes/erpProductionOrder/downstreamScheduleData" method="post" class="hide"
+
+							data-page-no="1" data-page-size="500">
+
+						<#form:hidden path="billNo"/>
+
+					</#form:form>
+
+					<table id="scheduleGrid"></table>
+
+					<div id="scheduleGridPage"></div>
+
+				</div>
+
+				<div role="tabpanel" class="tab-pane" id="tabInstockBind">
+
+					<#form:form id="instockBindSearchForm" model="${erpProductionOrder!}" action="${ctx}/mes/erpProductionOrder/downstreamInstockBindData" method="post" class="hide"
+
+							data-page-no="1" data-page-size="500">
+
+						<#form:hidden path="billNo"/>
+
+					</#form:form>
+
+					<table id="instockBindGrid"></table>
+
+					<div id="instockBindGridPage"></div>
+
+				</div>
+
 			</div>
 
 			<% } %>
@@ -168,6 +212,26 @@ var prdInStockGridReady = false;
 
 var transferDirectGridReady = false;
 
+var scheduleGridReady = false;
+
+var instockBindGridReady = false;
+
+
+
+function formatMoInventoryCategory(val, obj, row) {
+
+	if (val) return val;
+
+	var m = (row.materialNumber || '').trim();
+
+	if (m.indexOf('HEV') >= 0) return 'HEV';
+
+	if (m.indexOf('115') >= 0) return '${text("欣旺达115")}';
+
+	return '';
+
+}
+
 
 
 function downstreamGridHeight() {
@@ -178,7 +242,7 @@ function downstreamGridHeight() {
 
 		- ($('.erp-mo-downstream-page .alert-info').outerHeight(true) || 0)
 
-		- ($('#pickGridPage:visible, #prdInStockGridPage:visible, #transferDirectGridPage:visible').outerHeight(true) || 0) - 150);
+		- ($('#pickGridPage:visible, #prdInStockGridPage:visible, #transferDirectGridPage:visible, #scheduleGridPage:visible, #instockBindGridPage:visible').outerHeight(true) || 0) - 150);
 
 }
 
@@ -264,6 +328,8 @@ function initPrdInStockGrid() {
 
 			{header:'${text("物料名称")}', name:'materialName', width:160, align:"left"},
 
+			{header:'${text("存货类别")}', name:'inventoryCategory', width:100, align:"center", formatter: formatMoInventoryCategory},
+
 			{header:'${text("入库数量")}', name:'realQty', width:90, align:"right", formatter: formatErpQty},
 
 			{header:'${text("单位")}', name:'unitName', width:60, align:"center"},
@@ -330,6 +396,8 @@ function initTransferDirectGrid() {
 
 			{header:'${text("物料名称")}', name:'materialName', width:160, align:"left"},
 
+			{header:'${text("存货类别")}', name:'inventoryCategory', width:100, align:"center", formatter: formatMoInventoryCategory},
+
 			{header:'${text("调拨数量")}', name:'qty', width:90, align:"right", formatter: formatErpQty},
 
 			{header:'${text("单位")}', name:'unitName', width:60, align:"center"},
@@ -364,6 +432,124 @@ function initTransferDirectGrid() {
 
 
 
+function initScheduleGrid() {
+
+	if (scheduleGridReady) {
+
+		$('#scheduleGrid').dataGrid('refresh');
+
+		$('#scheduleGrid').jqGrid('setGridWidth', $('#tabSchedule').width(), true);
+
+		return;
+
+	}
+
+	scheduleGridReady = true;
+
+	$('#scheduleGrid').dataGrid({
+
+		searchForm: $('#scheduleSearchForm'),
+
+		dataGridPage: $('#scheduleGridPage'),
+
+		autoGridHeight: downstreamGridHeight,
+
+		columnModel: [
+
+			{header:'${text("行号")}', name:'erpSeq', width:60, align:"center"},
+
+			{header:'${text("物料编码")}', name:'materialNumber', width:180, align:"left"},
+
+			{header:'${text("物料名称")}', name:'materialName', width:160, align:"left"},
+
+			{header:'${text("存货类别")}', name:'inventoryCategory', width:100, align:"center"},
+
+			{header:'${text("产线")}', name:'lineTitle', width:100, align:"center"},
+
+			{header:'${text("排产开始")}', name:'scheduleStartDate', width:110, align:"center"},
+
+			{header:'${text("排产结束")}', name:'scheduleEndDate', width:110, align:"center"},
+
+			{header:'${text("排产数量")}', name:'scheduleQty', width:90, align:"right"},
+
+			{header:'${text("订单数量")}', name:'orderQty', width:90, align:"right"},
+
+			{header:'${text("ERP计划")}', name:'erpPlanStartDate', width:180, align:"center", formatter: function(val, obj, row){
+
+				return (row.erpPlanStartDate || '') + ' ~ ' + (row.erpPlanFinishDate || '');
+
+			}}
+
+		]
+
+	});
+
+	$('#scheduleGrid').jqGrid('setGridWidth', $('#tabSchedule').width(), true);
+
+}
+
+
+
+function initInstockBindGrid() {
+
+	if (instockBindGridReady) {
+
+		$('#instockBindGrid').dataGrid('refresh');
+
+		$('#instockBindGrid').jqGrid('setGridWidth', $('#tabInstockBind').width(), true);
+
+		return;
+
+	}
+
+	instockBindGridReady = true;
+
+	$('#instockBindGrid').dataGrid({
+
+		searchForm: $('#instockBindSearchForm'),
+
+		dataGridPage: $('#instockBindGridPage'),
+
+		autoGridHeight: downstreamGridHeight,
+
+		columnModel: [
+
+			{header:'${text("ERP入库单号")}', name:'erpBillNo', width:170, align:"center", formatter: function(val, obj, row){
+
+				if (!val) return '';
+
+				return '<a href="${ctx}/mes/mesErpPrdInstockLog/detail?id='+row.id+'" class="addTabPage" data-title="${text("绑定明细")}">'+val+'</a>';
+
+			}},
+
+			{header:'${text("ERP批号")}', name:'erpLotNumber', width:130, align:"left"},
+
+			{header:'${text("物料编码")}', name:'materialNumber', width:200, align:"left", formatter: function(val, obj, row){
+
+				return val || '';
+
+			}},
+
+			{header:'${text("存货类别")}', name:'inventoryCategory', width:100, align:"center", formatter: formatMoInventoryCategory},
+
+			{header:'${text("入库数量")}', name:'totalQty', width:100, align:"right", formatter: formatErpQty},
+
+			{header:'${text("批次数")}', name:'batchCount', width:80, align:"right"},
+
+			{header:'${text("MES批次码")}', name:'batchSnList', width:260, align:"left"},
+
+			{header:'${text("推送时间")}', name:'createDate', width:150, align:"center"}
+
+		]
+
+	});
+
+	$('#instockBindGrid').jqGrid('setGridWidth', $('#tabInstockBind').width(), true);
+
+}
+
+
+
 $('#pickGrid').dataGrid({
 
 	searchForm: $('#pickSearchForm'),
@@ -386,6 +572,8 @@ $('#pickGrid').dataGrid({
 
 		{header:'${text("物料名称")}', name:'materialName', width:160, align:"left"},
 
+		{header:'${text("存货类别")}', name:'inventoryCategory', width:100, align:"center", formatter: formatMoInventoryCategory},
+
 		{header:'${text("申请数量")}', name:'appQty', width:90, align:"right", formatter: formatErpQty},
 
 		{header:'${text("实发数量")}', name:'actualQty', width:90, align:"right", formatter: formatErpQty},
@@ -418,6 +606,14 @@ $('a[data-toggle="tab"]').on('shown.bs.tab', function(e) {
 
 		initTransferDirectGrid();
 
+	} else if (target === '#tabSchedule') {
+
+		initScheduleGrid();
+
+	} else if (target === '#tabInstockBind') {
+
+		initInstockBindGrid();
+
 	}
 
 });
@@ -444,6 +640,18 @@ $(window).resize(function() {
 
 	}
 
+	if ($('#scheduleGrid')[0] && $('#scheduleGrid')[0].grid) {
+
+		$('#scheduleGrid').jqGrid('setGridHeight', downstreamGridHeight());
+
+	}
+
+	if ($('#instockBindGrid')[0] && $('#instockBindGrid')[0].grid) {
+
+		$('#instockBindGrid').jqGrid('setGridHeight', downstreamGridHeight());
+
+	}
+
 });
 
 <% } %>

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

@@ -11,6 +11,11 @@
 			</div>
 		</div>
 		<div class="box-body">
+			<div class="alert alert-info" style="margin-bottom:10px;">
+				<i class="fa fa-info-circle"></i>
+				${text('仅展示存货类别为')} <strong>HEV</strong>、<strong>${text('欣旺达115')}</strong> ${text('或')} <strong>115</strong>
+				${text('的生产订单分录;明细行数/总数量仅统计上述类别(不再按固定物料编码过滤)。')}
+			</div>
 			<#form:form id="searchForm" model="${erpProductionOrder}" action="${ctx}/mes/erpProductionOrder/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">
@@ -44,6 +49,30 @@ function openMoDownstreamModal(billNo) {
 	});
 }
 
+function openMoPushdownModal(billNo) {
+	if (!billNo) return;
+	js.layer.open({
+		type: 1,
+		title: '${text("下推")} - ' + billNo,
+		area: ['560px', '480px'],
+		resize: false,
+		shadeClose: true,
+		scrollbar: true,
+		content: js.template('moPushdownTpl'),
+		success: function(layero, index) {
+			layero.find('.mo-pushdown-bill-no').text(billNo);
+			layero.find('.mo-pushdown-item').on('click', function(e) {
+				e.preventDefault();
+				var url = $(this).attr('data-url');
+				var title = $(this).attr('data-title');
+				if (!url) return;
+				js.layer.close(index);
+				js.addTabPage(null, title + ' - ' + billNo, url + encodeURIComponent(billNo), true);
+			});
+		}
+	});
+}
+
 $('#dataGrid').dataGrid({
 	searchForm: $("#searchForm"),
 	columnModel: [
@@ -53,13 +82,16 @@ $('#dataGrid').dataGrid({
 		{header:'${text("单据日期")}', name:'date', index:'date', width:150, align:"center"},
 		{header:'${text("单据状态")}', name:'documentStatusName', index:'documentStatusName', width:100, align:"center"},
 		{header:'${text("生产组织")}', name:'prdOrgName', index:'prdOrgName', width:180, align:"left"},
-		{header:'${text("存货类别")}', name:'inventoryCategory', index:'inventoryCategory', width:120, align:"center"},
+		{header:'${text("存货类别")}', name:'inventoryCategory', index:'inventoryCategory', width:100, align:"center"},
 		{header:'${text("生产车间")}', name:'workshopName', index:'workshopName', width:140, align:"left"},
 		{header:'${text("明细行数")}', name:'entryCount', index:'entryCount', width:100, align:"right"},
 		{header:'${text("总数量")}', name:'totalQty', index:'totalQty', width:120, align:"right", formatter: formatErpQty},
-		{header:'${text("操作")}', name:'actions', width:80, align:"center", sortable:false, frozen:false, formatter: function(val, obj, row){
-			return '<a href="javascript:" class="btn btn-primary btn-xs btn-mo-downstream" data-bill-no="'+row.billNo+'" title="${text("下查")}">'+
-				'<i class="fa fa-sitemap"></i> ${text("下查")}</a>';
+		{header:'${text("操作")}', name:'actions', width:150, align:"center", sortable:false, frozen:false, formatter: function(val, obj, row){
+			var downstream = '<a href="javascript:" class="btn btn-primary btn-xs btn-mo-downstream" data-bill-no="'+row.billNo+'" title="${text("下查")}">'+
+				'<i class="fa fa-sitemap"></i> ${text("下查")}</a> ';
+			var pushdown = '<a href="javascript:" class="btn btn-success btn-xs btn-mo-pushdown" data-bill-no="'+row.billNo+'" title="${text("下推")}">'+
+				'<i class="fa fa-share"></i> ${text("下推")}</a>';
+			return downstream + pushdown;
 		}}
 	],
 	ajaxSuccess: function(){}
@@ -70,4 +102,57 @@ $(document).on('click', '.btn-mo-downstream', function(e) {
 	e.stopPropagation();
 	openMoDownstreamModal($(this).attr('data-bill-no'));
 });
+
+$(document).on('click', '.btn-mo-pushdown', function(e) {
+	e.preventDefault();
+	e.stopPropagation();
+	openMoPushdownModal($(this).attr('data-bill-no'));
+});
 </script>
+<script id="moPushdownTpl" type="text/template">//<!--
+<div style="padding:18px 22px;">
+	<p style="margin:0 0 14px;color:#666;">${text('生产订单')}:<strong class="mo-pushdown-bill-no"></strong></p>
+	<p style="margin:0 0 16px;font-size:12px;color:#999;">${text('请选择要下推的单据类型,系统将带入当前生产订单号。')}</p>
+	<div class="list-group" style="margin-bottom:0;">
+		<% if(hasPermi('mes:erpPrdInStock:view')){ %>
+		<a href="javascript:" class="list-group-item mo-pushdown-item"
+				data-url="${ctx}/mes/erpPrdInStock/selectDetail?billNo="
+				data-title="${text('增加生产入库')}">
+			<i class="fa fa-download fa-fw text-primary"></i>
+			<strong>${text('新增生产入库单')}</strong>
+			<span class="text-muted" style="display:block;margin:4px 0 0 22px;font-size:12px;">${text('选择 HEV/115 分录,勾选 MES 批次码入库')}</span>
+		</a>
+		<% } %>
+		<% if(hasPermi('mes:erpTransferDirect:view')){ %>
+		<a href="javascript:" class="list-group-item mo-pushdown-item"
+				data-url="${ctx}/mes/erpTransferDirect/form?moBillNo="
+				data-title="${text('新增直接调拨单')}">
+			<i class="fa fa-exchange fa-fw text-success"></i>
+			<strong>${text('新增直接调拨单')}</strong>
+			<span class="text-muted" style="display:block;margin:4px 0 0 22px;font-size:12px;">${text('按该订单生产入库批号外发调拨')}</span>
+		</a>
+		<% } %>
+		<% if(hasPermi('mes:erpPickMaterialBill:view')){ %>
+		<a href="javascript:" class="list-group-item mo-pushdown-item"
+				data-url="${ctx}/mes/erpPickMaterialBill/selectDetail?billNo="
+				data-title="${text('增加领料单')}">
+			<i class="fa fa-sign-out fa-fw text-warning"></i>
+			<strong>${text('新增生产领料单')}</strong>
+			<span class="text-muted" style="display:block;margin:4px 0 0 22px;font-size:12px;">${text('选择订单分录及生产用料清单')}</span>
+		</a>
+		<% } %>
+		<% if(hasPermi('mes:mesErpSchedule:view') || hasPermi('mes:erpProductionOrder:view')){ %>
+		<a href="javascript:" class="list-group-item mo-pushdown-item"
+				data-url="${ctx}/mes/erpProductionOrder/scheduleSelect?billNo="
+				data-title="${text('ERP排产')}">
+			<i class="fa fa-calendar-plus-o fa-fw text-info"></i>
+			<strong>${text('新增ERP排产计划')}</strong>
+			<span class="text-muted" style="display:block;margin:4px 0 0 22px;font-size:12px;">${text('选择 HEV/115 分录后创建排产计划')}</span>
+		</a>
+		<% } %>
+	</div>
+	<% if(!hasPermi('mes:erpPrdInStock:view') && !hasPermi('mes:erpTransferDirect:view') && !hasPermi('mes:erpPickMaterialBill:view') && !hasPermi('mes:mesErpSchedule:view') && !hasPermi('mes:erpProductionOrder:view')){ %>
+	<div class="alert alert-warning" style="margin:12px 0 0;">${text('暂无下推权限,请联系管理员授权相关菜单。')}</div>
+	<% } %>
+</div>
+//--></script>

+ 69 - 0
src/main/resources/views/modules/mes/erpProductionOrderPushdown.html

@@ -0,0 +1,69 @@
+<% layout('/layouts/default.html', {title: '生产订单下推', libs: []}){ %>
+<div class="main-content">
+	<div class="box box-main">
+		<div class="box-header">
+			<div class="box-title">
+				<i class="fa fa-share"></i> ${text('下推')}:${billNo!}
+			</div>
+			<% if (!modal) { %>
+			<div class="box-tools pull-right">
+				<a href="${ctx}/mes/erpProductionOrder/list" class="btn btn-default" title="${text('返回')}"><i class="fa fa-mail-reply"></i> ${text('返回')}</a>
+			</div>
+			<% } %>
+		</div>
+		<div class="box-body">
+			<p style="margin:0 0 14px;color:#666;">${text('请选择要下推的单据类型,系统将带入当前生产订单号。')}</p>
+			<div class="list-group">
+				<% if(hasPermi('mes:erpPrdInStock:view')){ %>
+				<a href="${ctx}/mes/erpPrdInStock/selectDetail?billNo=${billNo!}" class="list-group-item mo-pushdown-link" data-title="${text('增加生产入库')} - ${billNo!}">
+					<i class="fa fa-download fa-fw text-primary"></i>
+					<strong>${text('新增生产入库单')}</strong>
+					<span class="text-muted" style="display:block;margin:4px 0 0 22px;font-size:12px;">${text('选择 HEV/115 分录,勾选 MES 批次码入库')}</span>
+				</a>
+				<% } %>
+				<% if(hasPermi('mes:erpTransferDirect:view')){ %>
+				<a href="${ctx}/mes/erpTransferDirect/form?moBillNo=${billNo!}" class="list-group-item mo-pushdown-link" data-title="${text('新增直接调拨单')} - ${billNo!}">
+					<i class="fa fa-exchange fa-fw text-success"></i>
+					<strong>${text('新增直接调拨单')}</strong>
+					<span class="text-muted" style="display:block;margin:4px 0 0 22px;font-size:12px;">${text('按该订单生产入库批号外发调拨')}</span>
+				</a>
+				<% } %>
+				<% if(hasPermi('mes:erpPickMaterialBill:view')){ %>
+				<a href="${ctx}/mes/erpPickMaterialBill/selectDetail?billNo=${billNo!}" class="list-group-item mo-pushdown-link" data-title="${text('增加领料单')} - ${billNo!}">
+					<i class="fa fa-sign-out fa-fw text-warning"></i>
+					<strong>${text('新增生产领料单')}</strong>
+					<span class="text-muted" style="display:block;margin:4px 0 0 22px;font-size:12px;">${text('选择订单分录及生产用料清单')}</span>
+				</a>
+				<% } %>
+				<% if(hasPermi('mes:mesErpSchedule:view') || hasPermi('mes:erpProductionOrder:view')){ %>
+				<a href="${ctx}/mes/erpProductionOrder/scheduleSelect?billNo=${billNo!}" class="list-group-item mo-pushdown-link" data-title="${text('ERP排产')} - ${billNo!}">
+					<i class="fa fa-calendar-plus-o fa-fw text-info"></i>
+					<strong>${text('新增ERP排产计划')}</strong>
+					<span class="text-muted" style="display:block;margin:4px 0 0 22px;font-size:12px;">${text('选择 HEV/115 分录后创建排产计划')}</span>
+				</a>
+				<% } %>
+			</div>
+			<% if(!hasPermi('mes:erpPrdInStock:view') && !hasPermi('mes:erpTransferDirect:view') && !hasPermi('mes:erpPickMaterialBill:view') && !hasPermi('mes:mesErpSchedule:view') && !hasPermi('mes:erpProductionOrder:view')){ %>
+			<div class="alert alert-warning" style="margin:0;">${text('暂无下推权限,请联系管理员授权相关菜单。')}</div>
+			<% } %>
+		</div>
+	</div>
+</div>
+<% } %>
+<script>
+$(function(){
+	$('.mo-pushdown-link').on('click', function(e){
+		e.preventDefault();
+		var url = $(this).attr('href');
+		var title = $(this).attr('data-title') || '';
+		var topJs = (window.parent && window.parent.js) ? window.parent.js : js;
+		try {
+			var idx = window.parent.layer.getFrameIndex(window.name);
+			if (idx) {
+				window.parent.layer.close(idx);
+			}
+		} catch (err) {}
+		topJs.addTabPage(null, title, url, true);
+	});
+});
+</script>

+ 47 - 0
src/main/resources/views/modules/mes/erpProductionOrderScheduleSelect.html

@@ -0,0 +1,47 @@
+<% 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-calendar-plus-o"></i> ${text('下推排产 - 选择订单分录')}:${erpProductionOrder.billNo}
+			</div>
+			<div class="box-tools pull-right">
+				<a href="${ctx}/mes/erpProductionOrder/list" class="btn btn-default addTabPage" title="${text('返回')}"><i class="fa fa-mail-reply"></i> ${text('返回')}</a>
+			</div>
+		</div>
+		<div class="box-body">
+			<div class="alert alert-info" style="margin-bottom:10px;">
+				<i class="fa fa-info-circle"></i>
+				${text('仅展示配置物料分录')}(8.10.HEV.10.047696 / 8.35.115.10.5003146600021),${text('选择分录后创建 ERP 排产计划。')}
+			</div>
+			<#form:form id="searchForm" model="${erpProductionOrder}" action="${ctx}/mes/erpProductionOrder/scheduleSelectData" method="post" class="hide"
+					data-page-no="1" data-page-size="500">
+				<#form:hidden path="billNo"/>
+			</#form:form>
+			<table id="dataGrid"></table>
+			<div id="dataGridPage"></div>
+		</div>
+	</div>
+</div>
+<% } %>
+<script>
+$('#dataGrid').dataGrid({
+	searchForm: $("#searchForm"),
+	columnModel: [
+		{header:'${text("操作")}', name:'actions', width:100, align:"center", frozen:true, formatter: function(val, obj, row){
+			return '<a href="${ctx}/mes/mesErpSchedule/form?erpBillNo='+encodeURIComponent(row.billNo)+'&erpEntryId='+encodeURIComponent(row.entryId)+'" class="btnList addTabPage" data-title="${text("ERP排产")}">'+
+				'<i class="fa fa-calendar-plus-o"></i> ${text("排产")}</a>';
+		}},
+		{header:'${text("行号")}', name:'seq', index:'seq', width:80, align:"center", frozen:true},
+		{header:'${text("物料编码")}', name:'materialNumber', index:'materialNumber', width:220, align:"left"},
+		{header:'${text("物料名称")}', name:'materialName', index:'materialName', width:200, align:"left"},
+		{header:'${text("存货类别")}', name:'inventoryCategory', index:'inventoryCategory', width:100, align:"center"},
+		{header:'${text("数量")}', name:'qty', index:'qty', width:110, align:"right", formatter: formatErpQty},
+		{header:'${text("单位")}', name:'unitName', index:'unitName', width:90, align:"center"},
+		{header:'${text("生产车间")}', name:'workshopName', index:'workshopName', width:140, align:"left"},
+		{header:'${text("计划开工")}', name:'planStartDate', index:'planStartDate', width:120, align:"center"},
+		{header:'${text("计划完工")}', name:'planFinishDate', index:'planFinishDate', width:120, align:"center"}
+	],
+	ajaxSuccess: function(){}
+});
+</script>

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

@@ -0,0 +1,178 @@
+<% layout('/layouts/default.html', {title: '绑定明细', libs: []}){ %>
+
+<div class="main-content">
+
+	<div class="box box-main">
+
+		<div class="box-header">
+
+			<div class="box-title">
+
+				<i class="fa fa-link"></i> ${text('生产入库批次绑定明细')}:${mesErpPrdInstockLog.erpBillNo!}
+
+			</div>
+
+			<div class="box-tools pull-right">
+
+				<a href="${ctx}/mes/mesErpPrdInstockLog/list" class="btn btn-default addTabPage" title="${text('返回')}"><i class="fa fa-mail-reply"></i> ${text('返回')}</a>
+
+				<a href="${ctx}/mes/erpPrdInStock/detail?billNo=${mesErpPrdInstockLog.erpBillNo!}" class="btn btn-default addTabPage" title="${text('ERP入库单')}"><i class="fa fa-external-link"></i> ${text('ERP入库单')}</a>
+
+			</div>
+
+		</div>
+
+		<div class="box-body">
+
+			<table class="table table-bordered table-condensed" style="margin-bottom:16px;">
+
+				<tbody>
+
+					<tr>
+
+						<th class="text-right" style="width:140px;">${text('ERP入库单号')}</th>
+
+						<td>${mesErpPrdInstockLog.erpBillNo!}</td>
+
+						<th class="text-right" style="width:140px;">${text('ERP批号')}</th>
+
+						<td>${mesErpPrdInstockLog.erpLotNumber!}</td>
+
+					</tr>
+
+					<tr>
+
+						<th class="text-right">${text('生产订单号')}</th>
+
+						<td>${mesErpPrdInstockLog.moBillNo!}</td>
+
+						<th class="text-right">${text('订单分录内码')}</th>
+
+						<td>${mesErpPrdInstockLog.moEntryId!}</td>
+
+					</tr>
+
+					<tr>
+
+						<th class="text-right">${text('物料编码')}</th>
+
+						<td>${mesErpPrdInstockLog.materialNumber!}</td>
+
+						<th class="text-right">${text('入库合计数量')}</th>
+
+						<td><strong>${mesErpPrdInstockLog.totalQty!}</strong></td>
+
+					</tr>
+
+					<tr>
+
+						<th class="text-right">${text('入库仓库')}</th>
+
+						<td>${mesErpPrdInstockLog.stockNumber!}</td>
+
+						<th class="text-right">${text('入库仓位')}</th>
+
+						<td>${mesErpPrdInstockLog.stockLocNumber!}</td>
+
+					</tr>
+
+					<tr>
+
+						<th class="text-right">${text('绑定批次数')}</th>
+
+						<td>${mesErpPrdInstockLog.batchCount!}</td>
+
+						<th class="text-right">${text('MES推送时间')}</th>
+
+						<td>${mesErpPrdInstockLog.createDate!, 'yyyy-MM-dd HH:mm:ss'}</td>
+
+					</tr>
+
+					<tr>
+
+						<th class="text-right">${text('批次码汇总')}</th>
+
+						<td colspan="3">${mesErpPrdInstockLog.batchSnList!}</td>
+
+					</tr>
+
+				</tbody>
+
+			</table>
+
+
+
+			<h4 style="margin:0 0 10px;">${text('MES 批次码明细')}</h4>
+
+			<div class="table-responsive">
+
+				<table class="table table-bordered table-hover table-condensed">
+
+					<thead>
+
+						<tr>
+
+							<th>${text('批次码')}</th>
+
+							<th>${text('产线')}</th>
+
+							<th>${text('绑定数量')}</th>
+
+							<th>${text('是否用完')}</th>
+
+							<th>${text('ERP入库状态')}</th>
+
+							<th>${text('ERP批号')}</th>
+
+							<th>${text('回写时间')}</th>
+
+						</tr>
+
+					</thead>
+
+					<tbody>
+
+						<% if(batchList.~size == 0){ %>
+
+						<tr><td colspan="7" class="text-center text-muted">${text('未查到批次码回写记录,请查看上方汇总字段')}</td></tr>
+
+						<% }else{ %>
+
+						<% for(row in batchList){ %>
+
+						<tr>
+
+							<td><strong>${row.batchSn!}</strong></td>
+
+							<td class="text-center">${row.lineSn!}</td>
+
+							<td class="text-right">${row.nums!}</td>
+
+							<td class="text-center">${row.isJs!}</td>
+
+							<td class="text-center">${row.instockStatus=='1'?text('已入库'):text('待入库')}</td>
+
+							<td>${row.erpLotNumber!}</td>
+
+							<td class="text-center">${row.erpInstockDate!, 'yyyy-MM-dd HH:mm:ss'}</td>
+
+						</tr>
+
+						<% } %>
+
+						<% } %>
+
+					</tbody>
+
+				</table>
+
+			</div>
+
+		</div>
+
+	</div>
+
+</div>
+
+<% } %>
+

+ 182 - 0
src/main/resources/views/modules/mes/mesErpPrdInstockLogList.html

@@ -0,0 +1,182 @@
+<% 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-link"></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="btnSetting" title="${text('设置')}"><i class="fa fa-navicon"></i></a>
+
+			</div>
+
+		</div>
+
+		<div class="box-body">
+
+			<% if (isNotBlank(message!)) { %>
+
+			<div class="alert alert-warning">${message}</div>
+
+			<% } %>
+
+			<div class="alert alert-info" style="margin-bottom:10px;">
+
+				<i class="fa fa-info-circle"></i>
+
+				${text('展示 MES 推送 ERP 生产入库时绑定的托盘批次码;仅 MES「提交入库」成功的记录会出现在此列表。')}
+
+			</div>
+
+			<#form:form id="searchForm" model="${mesErpPrdInstockLog}" action="${ctx}/mes/mesErpPrdInstockLog/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('ERP入库单号')}:</label>
+
+					<div class="control-inline">
+
+						<#form:input path="erpBillNo" maxlength="100" class="form-control width-180"/>
+
+					</div>
+
+				</div>
+
+				<div class="form-group">
+
+					<label class="control-label">${text('生产订单号')}:</label>
+
+					<div class="control-inline">
+
+						<#form:input path="moBillNo" maxlength="100" class="form-control width-180"/>
+
+					</div>
+
+				</div>
+
+				<div class="form-group">
+
+					<label class="control-label">${text('物料编码')}:</label>
+
+					<div class="control-inline">
+
+						<#form:input path="materialNumber" maxlength="100" class="form-control width-200"/>
+
+					</div>
+
+				</div>
+
+				<div class="form-group">
+
+					<label class="control-label">${text('MES批次码')}:</label>
+
+					<div class="control-inline">
+
+						<input type="text" name="batchSnQuery" maxlength="100" 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:'actions', width:80, align:"center", frozen:true, formatter: function(val, obj, row){
+
+			return '<a href="${ctx}/mes/mesErpPrdInstockLog/detail?id='+row.id+'" class="btnList addTabPage" data-title="${text("绑定明细")}"><i class="fa fa-eye"></i></a>';
+
+		}},
+
+		{header:'${text("ERP入库单号")}', name:'erpBillNo', index:'erpBillNo', width:170, align:"center", frozen:true, formatter: function(val, obj, row){
+
+			return '<a href="${ctx}/mes/mesErpPrdInstockLog/detail?id='+row.id+'" class="addTabPage" data-title="${text("绑定明细")}">'+val+'</a>';
+
+		}},
+
+		{header:'${text("ERP批号")}', name:'erpLotNumber', index:'erpLotNumber', width:130, align:"left"},
+
+		{header:'${text("生产订单号")}', name:'moBillNo', index:'moBillNo', width:170, align:"center"},
+
+		{header:'${text("物料编码")}', name:'materialNumber', index:'materialNumber', width:200, align:"left"},
+
+		{header:'${text("入库数量")}', name:'totalQty', index:'totalQty', width:100, align:"right", formatter: formatErpQty},
+
+		{header:'${text("批次数")}', name:'batchCount', index:'batchCount', width:80, align:"right"},
+
+		{header:'${text("MES批次码")}', name:'batchSnList', index:'batchSnList', width:260, align:"left"},
+
+		{header:'${text("仓库")}', name:'stockNumber', index:'stockNumber', width:100, align:"center"},
+
+		{header:'${text("仓位")}', name:'stockLocNumber', index:'stockLocNumber', width:80, align:"center"},
+
+		{header:'${text("推送时间")}', name:'createDate', index:'createDate', width:150, align:"center"},
+
+		{header:'${text("推送人")}', name:'createBy', index:'createBy', width:100, align:"center"}
+
+	],
+
+	ajaxSuccess: function(){}
+
+});
+
+</script>
+

+ 269 - 0
src/main/resources/views/modules/mes/mesErpScheduleBoard.html

@@ -0,0 +1,269 @@
+<% layout('/layouts/default.html', {title: 'ERP排产看板', libs: []}){ %>
+<div class="main-content mes-erp-schedule-board-page">
+	<div class="box box-main">
+		<div class="box-header">
+			<div class="box-title">
+				<i class="fa fa-calendar"></i> ${text('ERP排产看板')}
+			</div>
+			<div class="box-tools pull-right">
+				<a href="${ctx}/mes/mesErpSchedule/list" class="btn btn-default btnTool" title="${text('列表视图')}"><i class="fa fa-list"></i> ${text('列表')}</a>
+				<a href="${ctx}/mes/erpProductionOrder/list" class="btn btn-default btnTool" title="${text('从ERP订单排产')}"><i class="fa fa-plus"></i> ${text('新增排产')}</a>
+			</div>
+		</div>
+		<div class="box-body">
+			<div class="alert alert-info" style="margin-bottom:10px;padding:8px 12px;">
+				<i class="fa fa-info-circle"></i>
+				${text('按月展示排产计划,每周一行;两条产线在同一日历中分色展示。点击计划条查看工位生产详情。')}
+			</div>
+			<div class="mes-erp-board-legend" id="boardLegend" style="display:none;margin-bottom:10px;">
+				<span class="mes-erp-legend-item"><i class="mes-erp-legend-dot mes-erp-legend-hev"></i> HEV ${text('产线')} (HEVXT)</span>
+				<span class="mes-erp-legend-item"><i class="mes-erp-legend-dot mes-erp-legend-115"></i> 115 ${text('产线')} (115XT)</span>
+			</div>
+			<div class="mes-erp-board-toolbar form-inline">
+				<div class="form-group">
+					<label class="control-label">${text('月份')}:</label>
+					<input type="text" id="boardMonth" class="form-control width-120" readonly value="${month!}">
+				</div>
+				<button type="button" class="btn btn-primary btn-sm" id="btnBoardSearch"><i class="fa fa-search"></i> ${text('查询')}</button>
+				<button type="button" class="btn btn-default btn-sm" id="btnBoardPrev"><i class="fa fa-chevron-left"></i> ${text('上一月')}</button>
+				<button type="button" class="btn btn-default btn-sm" id="btnBoardNext">${text('下一月')} <i class="fa fa-chevron-right"></i></button>
+				<button type="button" class="btn btn-default btn-sm" id="btnBoardCurrent"><i class="fa fa-dot-circle-o"></i> ${text('本月')}</button>
+			</div>
+			<div id="boardLoading" class="text-center text-muted" style="padding:40px 0;display:none;">
+				<i class="fa fa-spinner fa-spin"></i> ${text('加载中...')}
+			</div>
+			<div id="boardEmpty" class="text-center text-muted" style="padding:40px 0;display:none;">${text('当前月份暂无排产计划')}</div>
+			<div id="boardWrap" style="display:none;"></div>
+		</div>
+	</div>
+</div>
+<% } %>
+<style>
+.mes-erp-board-toolbar { margin-bottom: 12px; }
+.mes-erp-board-toolbar .form-group { margin-right: 12px; margin-bottom: 8px; }
+.mes-erp-month-panel {
+	border: 1px solid #ddd; border-radius: 4px; background: #fff;
+	margin-bottom: 16px; overflow: hidden;
+}
+.mes-erp-month-title {
+	padding: 10px 14px; font-size: 16px; font-weight: 600;
+	background: #f7f7f7; border-bottom: 1px solid #e5e5e5;
+}
+.mes-erp-board-legend { font-size: 13px; color: #555; }
+.mes-erp-legend-item { margin-right: 20px; }
+.mes-erp-legend-dot {
+	display: inline-block; width: 14px; height: 14px; border-radius: 3px;
+	vertical-align: -2px; margin-right: 4px;
+}
+.mes-erp-legend-hev { background: linear-gradient(180deg, #5cadff 0%, #3c8dbc 100%); }
+.mes-erp-legend-115 { background: linear-gradient(180deg, #7fd67a 0%, #52a447 100%); }
+.mes-erp-week-head { background: #f0f4f8; border-bottom: 1px solid #e5e5e5; }
+.mes-erp-week-row { border-bottom: 1px solid #f0f0f0; }
+.mes-erp-week-track-full {
+	position: relative; width: 100%; border-bottom: 1px solid #f0f0f0;
+}
+.mes-erp-week-days { display: flex; width: 100%; }
+.mes-erp-day-head,
+.mes-erp-day-cell {
+	flex: 1; min-width: 0; text-align: center;
+	border-right: 1px solid #ececec; padding: 6px 2px; font-size: 12px;
+}
+.mes-erp-day-head { font-weight: 600; color: #555; padding: 8px 2px; }
+.mes-erp-day-head.is-weekend,
+.mes-erp-day-cell.is-weekend { background: #fafafa; color: #999; }
+.mes-erp-day-cell.is-outside { background: #f5f5f5; color: #ccc; }
+.mes-erp-week-track-full {
+	position: relative; width: 100%;
+}
+.mes-erp-week-daynums {
+	border-bottom: 1px dashed #ececec; background: #fcfcfc;
+}
+.mes-erp-week-daynums .mes-erp-day-cell { padding: 4px 2px; font-size: 11px; }
+.mes-erp-week-lanes { position: relative; width: 100%; }
+.mes-erp-board-bar {
+	position: absolute; height: 30px; border-radius: 4px;
+	color: #fff; font-size: 11px; line-height: 1.2; padding: 3px 6px;
+	overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
+	cursor: pointer; z-index: 2;
+}
+.mes-erp-board-bar-hev {
+	background: linear-gradient(180deg, #5cadff 0%, #3c8dbc 100%);
+	box-shadow: 0 1px 3px rgba(60,141,188,0.35);
+}
+.mes-erp-board-bar-115 {
+	background: linear-gradient(180deg, #7fd67a 0%, #52a447 100%);
+	box-shadow: 0 1px 3px rgba(82,164,71,0.35);
+}
+.mes-erp-board-bar-default {
+	background: linear-gradient(180deg, #f0ad4e 0%, #d58512 100%);
+	box-shadow: 0 1px 3px rgba(213,133,18,0.35);
+}
+.mes-erp-board-bar:hover { filter: brightness(1.05); }
+.mes-erp-board-bar-title { font-weight: 600; display: block; overflow: hidden; text-overflow: ellipsis; }
+.mes-erp-board-bar-sub { opacity: 0.92; font-size: 10px; }
+</style>
+<script src="${ctxStatic}/layDate-v5.0.9/laydate.js"></script>
+<script>
+var WEEK_HEAD = ['${text("周一")}', '${text("周二")}', '${text("周三")}', '${text("周四")}', '${text("周五")}', '${text("周六")}', '${text("周日")}'];
+var boardPlans = [];
+var LANE_HEIGHT = 34;
+var LANE_GAP = 4;
+
+function getLineBarClass(line) {
+	var sn = ((line && line.sn) || '').toUpperCase();
+	if (sn === 'HEVXT') return 'mes-erp-board-bar-hev';
+	if (sn === '115XT') return 'mes-erp-board-bar-115';
+	return 'mes-erp-board-bar-default';
+}
+
+function escapeHtml(text) {
+	if (text == null) return '';
+	return String(text).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
+}
+
+function shiftMonth(monthStr, offset) {
+	var parts = (monthStr || '').split('-');
+	if (parts.length !== 2) return monthStr;
+	var date = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1 + offset, 1);
+	var y = date.getFullYear();
+	var m = ('0' + (date.getMonth() + 1)).slice(-2);
+	return y + '-' + m;
+}
+
+function calcBarStyleForWeek(plan, weekDays) {
+	if (!plan || !weekDays || weekDays.length !== 7) return null;
+	var weekDates = [];
+	for (var i = 0; i < weekDays.length; i++) weekDates.push(weekDays[i].date);
+	var weekStart = weekDates[0];
+	var weekEnd = weekDates[6];
+	var pStart = plan.scheduleStartDate || '';
+	var pEnd = plan.scheduleEndDate || '';
+	if (pEnd < weekStart || pStart > weekEnd) return null;
+
+	var startIdx = 0;
+	for (var s = 0; s < 7; s++) {
+		if (weekDates[s] >= pStart) { startIdx = s; break; }
+	}
+	if (pStart <= weekStart) startIdx = 0;
+
+	var endIdx = 6;
+	for (var e = 6; e >= 0; e--) {
+		if (weekDates[e] <= pEnd) { endIdx = e; break; }
+	}
+	if (pEnd >= weekEnd) endIdx = 6;
+
+	return {
+		left: ((startIdx / 7) * 100).toFixed(4) + '%',
+		width: (((endIdx - startIdx + 1) / 7) * 100).toFixed(4) + '%'
+	};
+}
+
+function renderBoard(data) {
+	var weeks = data.weeks || [];
+	var lines = data.lines || [];
+	boardPlans = data.plans || [];
+	var html = '';
+	var laneCount = Math.max(lines.length, 1);
+	var lanesHeight = laneCount * (LANE_HEIGHT + LANE_GAP) + 8;
+
+	html += '<div class="mes-erp-month-panel">';
+	html += '<div class="mes-erp-month-title">' + escapeHtml(data.monthLabel || data.month || '') + '</div>';
+	html += '<div class="mes-erp-week-head"><div class="mes-erp-week-days">';
+	for (var h = 0; h < WEEK_HEAD.length; h++) {
+		html += '<div class="mes-erp-day-head' + (h >= 5 ? ' is-weekend' : '') + '">' + WEEK_HEAD[h] + '</div>';
+	}
+	html += '</div></div>';
+
+	if (!weeks.length) {
+		html += '<div class="text-center text-muted" style="padding:24px;">${text("暂无数据")}</div>';
+	} else {
+		for (var w = 0; w < weeks.length; w++) {
+			var week = weeks[w];
+			var weekDays = week.days || [];
+			html += '<div class="mes-erp-week-row"><div class="mes-erp-week-track-full">';
+			html += '<div class="mes-erp-week-days mes-erp-week-daynums">';
+			for (var dc = 0; dc < weekDays.length; dc++) {
+				var dayCell = weekDays[dc];
+				var dayCls = 'mes-erp-day-cell';
+				if (!dayCell.inMonth) dayCls += ' is-outside';
+				else if (dayCell.weekend) dayCls += ' is-weekend';
+				html += '<div class="' + dayCls + '">' + (dayCell.inMonth ? dayCell.day : '') + '</div>';
+			}
+			html += '</div>';
+			html += '<div class="mes-erp-week-lanes" style="height:' + lanesHeight + 'px;">';
+			for (var l = 0; l < lines.length; l++) {
+				var line = lines[l];
+				var barTop = 4 + l * (LANE_HEIGHT + LANE_GAP);
+				var barClass = getLineBarClass(line);
+				for (var p = 0; p < boardPlans.length; p++) {
+					var plan = boardPlans[p];
+					if (plan.lineId !== line.id) continue;
+					var style = calcBarStyleForWeek(plan, weekDays);
+					if (!style) continue;
+					var title = plan.erpBillNo || '';
+					var sub = (plan.materialName || '') + (plan.scheduleQty != null ? ' · ' + plan.scheduleQty : '');
+					html += '<div class="mes-erp-board-bar ' + barClass + '" data-id="' + escapeHtml(plan.id) + '" style="top:' + barTop + 'px;left:' + style.left + ';width:' + style.width + ';" title="' +
+						escapeHtml('[' + (line.sn || '') + '] ' + title + ' ' + (plan.scheduleStartDate || '') + ' ~ ' + (plan.scheduleEndDate || '')) + '">' +
+						'<span class="mes-erp-board-bar-title">' + escapeHtml(title) + '</span>' +
+						'<span class="mes-erp-board-bar-sub">' + escapeHtml(sub) + '</span></div>';
+				}
+			}
+			html += '</div></div></div>';
+		}
+	}
+	html += '</div>';
+
+	$('#boardWrap').html(html);
+	$('#boardLegend').toggle(lines.length > 0);
+	if (!boardPlans.length) {
+		$('#boardEmpty').show().text('${text("当前月份暂无排产计划")}');
+	} else {
+		$('#boardEmpty').hide();
+	}
+	$('#boardWrap').show();
+}
+
+function loadBoard() {
+	var month = $('#boardMonth').val();
+	if (!month) return;
+	$('#boardLoading').show();
+	$('#boardWrap').hide();
+	$('#boardEmpty').hide();
+	$.get('${ctx}/mes/mesErpSchedule/boardData', { month: month }, function(res) {
+		$('#boardLoading').hide();
+		if (!res || res.result !== Global.TRUE) {
+			js.showMessage((res && res.message) ? res.message : '${text("加载排产看板失败")}');
+			return;
+		}
+		renderBoard(res.data || {});
+	}, 'json').fail(function() {
+		$('#boardLoading').hide();
+		js.showMessage('${text("加载排产看板失败")}');
+	});
+}
+
+laydate.render({ elem: '#boardMonth', trigger: 'click', type: 'month', format: 'yyyy-MM' });
+
+$('#btnBoardSearch').on('click', loadBoard);
+$('#btnBoardPrev').on('click', function() {
+	$('#boardMonth').val(shiftMonth($('#boardMonth').val(), -1));
+	loadBoard();
+});
+$('#btnBoardNext').on('click', function() {
+	$('#boardMonth').val(shiftMonth($('#boardMonth').val(), 1));
+	loadBoard();
+});
+$('#btnBoardCurrent').on('click', function() {
+	var now = new Date();
+	$('#boardMonth').val(now.getFullYear() + '-' + ('0' + (now.getMonth() + 1)).slice(-2));
+	loadBoard();
+});
+
+$(document).on('click', '.mes-erp-board-bar', function() {
+	var id = $(this).data('id');
+	if (!id) return;
+	js.addTabPage(null, '${text("ERP排产计划详情")}', '${ctx}/mes/mesErpSchedule/detail?id=' + id);
+});
+
+loadBoard();
+</script>

+ 97 - 0
src/main/resources/views/modules/mes/mesErpScheduleDetail.html

@@ -0,0 +1,97 @@
+<% layout('/layouts/default.html', {title: 'ERP排产计划详情', libs: []}){ %>
+<div class="main-content">
+	<div class="box box-main">
+		<div class="box-header">
+			<div class="box-title">
+				<i class="fa fa-calendar-check-o"></i> ${text('ERP排产计划详情')}:${mesErpSchedule.erpBillNo!}
+			</div>
+			<div class="box-tools pull-right">
+				<a href="${ctx}/mes/mesErpSchedule/board?month=${boardMonth!}" class="btn btn-default btnTool" title="${text('排产看板')}"><i class="fa fa-calendar"></i> ${text('看板')}</a>
+				<a href="${ctx}/mes/mesErpSchedule/list" class="btn btn-default btnTool" title="${text('返回列表')}"><i class="fa fa-mail-reply"></i> ${text('返回')}</a>
+				<% if(hasPermi('mes:mesErpSchedule:view') || hasPermi('mes:erpProductionOrder:view')){ %>
+				<a href="${ctx}/mes/mesErpSchedule/form?id=${mesErpSchedule.id!}" class="btn btn-default btnTool" title="${text('编辑排产计划')}"><i class="fa fa-pencil"></i> ${text('编辑')}</a>
+				<% } %>
+			</div>
+		</div>
+		<div class="box-body">
+			<table class="table table-bordered table-condensed" style="margin-bottom:16px;">
+				<tbody>
+					<tr>
+						<th class="text-right" style="width:140px;">${text('生产订单号')}</th>
+						<td>${mesErpSchedule.erpBillNo!}</td>
+						<th class="text-right" style="width:140px;">${text('行号')}</th>
+						<td>${mesErpSchedule.erpSeq!}</td>
+					</tr>
+					<tr>
+						<th class="text-right">${text('物料名称')}</th>
+						<td>${mesErpSchedule.materialName!}</td>
+						<th class="text-right">${text('存货类别')}</th>
+						<td>${mesErpSchedule.inventoryCategory!}</td>
+					</tr>
+					<tr>
+						<th class="text-right">${text('产线')}</th>
+						<td>${mesErpSchedule.lineTitle!} <span class="text-muted">(${mesErpSchedule.lineSn!})</span></td>
+						<th class="text-right">${text('排产数量')}</th>
+						<td><strong>${mesErpSchedule.scheduleQty!0}</strong></td>
+					</tr>
+					<tr>
+						<th class="text-right">${text('排产日期')}</th>
+						<td>${mesErpSchedule.scheduleStartDate!} ~ ${mesErpSchedule.scheduleEndDate!}</td>
+						<th class="text-right">${text('ERP计划')}</th>
+						<td>${mesErpSchedule.erpPlanStartDate!} ~ ${mesErpSchedule.erpPlanFinishDate!}</td>
+					</tr>
+				</tbody>
+			</table>
+
+			<div class="form-unit">${text('工位生产数据')} <small class="text-muted">${text('(产品生产记录 · 工艺号 100000 · 结果 OK · 排产日期范围内)')}</small></div>
+			<div class="alert alert-info" style="padding:8px 12px;margin-bottom:10px;">
+				<i class="fa fa-info-circle"></i>
+				${text('统计范围:产线')} <strong>${mesErpSchedule.lineSn!}</strong>,
+				${text('时间')} <strong>${mesErpSchedule.scheduleStartDate!}</strong> ~ <strong>${mesErpSchedule.scheduleEndDate!}</strong>,
+				${text('工位合计')} <strong>${stationCount!0}</strong> ${text('个')},
+				${text('OK合计')} <strong>${totalOkQty!0}</strong>
+			</div>
+			<table class="table table-bordered table-striped table-hover">
+				<thead>
+					<tr>
+						<th class="text-center" style="width:60px;">${text('序号')}</th>
+						<th class="text-center" style="width:120px;">${text('工位号')}</th>
+						<th>${text('工位名称')}</th>
+						<th class="text-right" style="width:120px;">${text('OK数量')}</th>
+						<th class="text-right" style="width:100px;">${text('排产数量')}</th>
+						<th class="text-right" style="width:100px;">${text('完成率')}</th>
+					</tr>
+				</thead>
+				<tbody>
+					<% if (stationCount!0 > 0) { %>
+						<% for (stat in stationStats!) { %>
+						<tr>
+							<td class="text-center">${statLP.index}</td>
+							<td class="text-center">${stat["oprno"]!}</td>
+							<td>${stat["title"]!}</td>
+							<td class="text-right"><strong>${stat["okQty"]!0}</strong></td>
+							<td class="text-right">${stat["scheduleQty"]!0}</td>
+							<td class="text-right">${stat["completionRate"]!}</td>
+						</tr>
+						<% } %>
+					<% } else { %>
+						<tr>
+							<td colspan="6" class="text-center text-muted">${text('该产线暂无工位配置或暂无生产数据')}</td>
+						</tr>
+					<% } %>
+				</tbody>
+				<% if (stationCount!0 > 0) { %>
+				<tfoot>
+					<tr>
+						<th colspan="3" class="text-right">${text('合计')}</th>
+						<th class="text-right">${totalOkQty!0}</th>
+						<th class="text-right">${mesErpSchedule.scheduleQty!0}</th>
+						<th></th>
+					</tr>
+				</tfoot>
+				<% } %>
+			</table>
+		</div>
+	</div>
+</div>
+<% } %>

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

@@ -101,7 +101,7 @@
 								<span class="required">*</span> ${text('产线')}:</label>
 							<div class="col-sm-8">
 								<#form:select path="lineId" items="${lines}" itemLabel="title" itemValue="id" blankOption="true" class="form-control required"/>
-								<p class="help-block">${text('HEV→HEV产线,115/欣旺达115→115产线')}</p>
+								<p class="help-block">${text('HEV→HEV产线,115/欣旺达115→115产线;每条产线每天只能有一个计划')}</p>
 							</div>
 						</div>
 					</div>

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

@@ -6,6 +6,7 @@
 				<i class="fa fa-calendar-check-o"></i> ${text('ERP排产计划')}
 			</div>
 			<div class="box-tools pull-right">
+				<a href="${ctx}/mes/mesErpSchedule/board" class="btn btn-default btnTool" title="${text('甘特看板')}"><i class="fa fa-calendar"></i> ${text('看板')}</a>
 				<a href="#" class="btn btn-default" id="btnSearch" title="${text('查询')}"><i class="fa fa-filter"></i> ${text('查询')}</a>
 				<a href="${ctx}/mes/erpProductionOrder/list" class="btn btn-default btnTool" title="${text('从ERP订单排产')}"><i class="fa fa-list-alt"></i> ${text('ERP订单')}</a>
 				<a href="#" class="btn btn-default" id="btnSetting" title="${text('设置')}"><i class="fa fa-navicon"></i></a>
@@ -48,7 +49,7 @@ $('#dataGrid').dataGrid({
 	searchForm: $("#searchForm"),
 	columnModel: [
 		{header:'${text("生产订单号")}', name:'erpBillNo', index:'a.erp_bill_no', width:170, align:"center", frozen:true, formatter: function(val, obj, row){
-			return '<a href="${ctx}/mes/mesErpSchedule/form?id='+row.id+'" class="btnList" data-title="${text("编辑ERP排产计划")}">'+val+'</a>';
+			return '<a href="${ctx}/mes/mesErpSchedule/detail?id='+row.id+'" class="btnList addTabPage" data-title="${text("ERP排产计划详情")}">'+val+'</a>';
 		}},
 		{header:'${text("行号")}', name:'erpSeq', index:'a.erp_seq', width:60, align:"center", frozen:true},
 		{header:'${text("物料名称")}', name:'materialName', index:'a.material_name', width:180, align:"left"},
@@ -61,9 +62,10 @@ $('#dataGrid').dataGrid({
 		{header:'${text("ERP计划")}', name:'erpPlanStartDate', index:'a.erp_plan_start_date', width:180, align:"center", formatter: function(val, obj, row){
 			return (row.erpPlanStartDate||'') + ' ~ ' + (row.erpPlanFinishDate||'');
 		}},
-		{header:'${text("操作")}', name:'actions', width:80, align:"center", formatter: function(val, obj, row){
+		{header:'${text("操作")}', name:'actions', width:100, align:"center", formatter: function(val, obj, row){
 			var actions = [];
 			//<% if(hasPermi('mes:mesErpSchedule:view') || hasPermi('mes:erpProductionOrder:view')){ %>
+				actions.push('<a href="${ctx}/mes/mesErpSchedule/detail?id='+row.id+'" class="btnList addTabPage" title="${text("详情")}"><i class="fa fa-eye"></i></a>&nbsp;');
 				actions.push('<a href="${ctx}/mes/mesErpSchedule/form?id='+row.id+'" class="btnList" title="${text("编辑")}"><i class="fa fa-pencil"></i></a>&nbsp;');
 				actions.push('<a href="${ctx}/mes/mesErpSchedule/delete?id='+row.id+'" class="btnList" title="${text("删除")}" data-confirm="${text("确认要删除该排产计划吗?")}"><i class="fa fa-trash-o"></i></a>&nbsp;');
 			//<% } %>