Преглед на файлове

优化ERP功能,即其他若干优化

jingbo преди 2 месеца
родител
ревизия
7a1e3fc5c1
променени са 29 файла, в които са добавени 766 реда и са изтрити 191 реда
  1. 295 0
      scripts/backup-ui.html
  2. 1 1
      src/main/java/com/jeesite/modules/config/PdaSidFilterConfig.java
  3. 56 0
      src/main/java/com/jeesite/modules/interceptor/EmpUserFormPostInterceptor.java
  4. 1 6
      src/main/java/com/jeesite/modules/interceptor/PdaSidCookieFilter.java
  5. 1 36
      src/main/java/com/jeesite/modules/mes/entity/MesPdaScanRecord.java
  6. 106 5
      src/main/java/com/jeesite/modules/mes/erp/ErpPickMaterialService.java
  7. 19 0
      src/main/java/com/jeesite/modules/mes/erp/ErpPickMaterialSubmitRequest.java
  8. 42 0
      src/main/java/com/jeesite/modules/mes/erp/ErpProductionOrderService.java
  9. 9 18
      src/main/java/com/jeesite/modules/mes/erp/ErpTransferDirectSaveService.java
  10. 4 4
      src/main/java/com/jeesite/modules/mes/erp/KingdeeErpProperties.java
  11. 31 6
      src/main/java/com/jeesite/modules/mes/erp/MesErpLineMaterialMapping.java
  12. 2 2
      src/main/java/com/jeesite/modules/mes/service/MesBatchSnService.java
  13. 27 1
      src/main/java/com/jeesite/modules/mes/web/EmpUserCustomController.java
  14. 7 7
      src/main/java/com/jeesite/modules/mes/web/MesLineProcessConsumeController.java
  15. 2 1
      src/main/java/com/jeesite/modules/mes/web/MesMaterialKcTotalController.java
  16. 4 3
      src/main/java/com/jeesite/modules/mes/web/MesPdaScanRecordController.java
  17. 9 0
      src/main/java/com/jeesite/modules/mes/web/MesProductQmController.java
  18. 12 2
      src/main/java/com/jeesite/modules/mes/web/MesProductRecordController.java
  19. 7 3
      src/main/resources/config/application.yml
  20. 3 1
      src/main/resources/views/modules/mes/erpPrdInStockList.html
  21. 66 66
      src/main/resources/views/modules/mes/erpPrdInStockSelectDetail.html
  22. 2 1
      src/main/resources/views/modules/mes/erpPrdInStockSelectOrder.html
  23. 2 8
      src/main/resources/views/modules/mes/erpTransferDirectForm.html
  24. 3 1
      src/main/resources/views/modules/mes/erpTransferDirectList.html
  25. 2 1
      src/main/resources/views/modules/mes/erpTransferDirectSelectOrder.html
  26. 1 15
      src/main/resources/views/modules/mes/erpTransferDirectSelectTray.html
  27. 1 1
      src/main/resources/views/modules/mes/mesLineProcessConsumeForm.html
  28. 2 2
      src/main/resources/views/modules/mes/mesLineProcessConsumeList.html
  29. 49 0
      src/main/resources/views/modules/sys/user/empUserForm.html

+ 295 - 0
scripts/backup-ui.html

@@ -0,0 +1,295 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="UTF-8"/>
+  <meta name="viewport" content="width=device-width, initial-scale=1"/>
+  <title>MES 数据库手动备份</title>
+  <style>
+    :root {
+      --bg: #0f172a; --card: #1e293b; --line: #334155; --text: #e2e8f0;
+      --muted: #94a3b8; --accent: #38bdf8; --ok: #34d399; --warn: #fbbf24; --bad: #f87171;
+    }
+    * { box-sizing: border-box; }
+    body {
+      margin: 0;
+      font-family: "Microsoft YaHei", "Segoe UI", sans-serif;
+      background: radial-gradient(1200px 600px at 10% -10%, #1e3a5f 0%, var(--bg) 45%);
+      color: var(--text); min-height: 100vh;
+    }
+    .wrap { max-width: 980px; margin: 0 auto; padding: 28px 20px 48px; }
+    h1 { font-size: 22px; margin: 0 0 6px; }
+    .sub { color: var(--muted); margin: 0 0 22px; font-size: 13px; line-height: 1.6; }
+    .card {
+      background: var(--card); border: 1px solid var(--line); border-radius: 14px;
+      padding: 18px; margin-bottom: 16px;
+    }
+    .row { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
+    .row h2 { margin: 0; font-size: 15px; }
+    .btn {
+      border: 0; border-radius: 8px; padding: 8px 14px; cursor: pointer;
+      font-size: 13px; color: #0f172a; background: var(--accent); font-weight: 600;
+    }
+    .btn:disabled { opacity: .45; cursor: not-allowed; }
+    .btn.ghost { background: #334155; color: var(--text); }
+    .btn.go { background: #22c55e; color: #052e16; padding: 12px 22px; font-size: 15px; }
+    .drives { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 10px; }
+    .drive {
+      border: 1px solid var(--line); border-radius: 10px; padding: 12px;
+      cursor: pointer; background: #0f172a; text-align: left;
+    }
+    .drive:hover { border-color: var(--accent); }
+    .drive.on { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
+    .drive.usb { border-color: #6366f1; }
+    .drive .t { font-size: 12px; color: var(--muted); }
+    .drive .n { font-size: 16px; font-weight: 700; margin: 4px 0; }
+    .drive .s { font-size: 12px; color: var(--muted); }
+    .tag { display: inline-block; font-size: 11px; padding: 1px 6px; border-radius: 999px; background: #334155; }
+    .tag.usb { background: #3730a3; color: #c7d2fe; }
+    .picked {
+      background: #0f172a; border: 1px dashed var(--line); border-radius: 10px;
+      padding: 14px 14px; font-size: 15px; word-break: break-all; min-height: 48px;
+    }
+    .picked.empty { color: var(--muted); }
+    .folders { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
+    .folder {
+      background: #0f172a; border: 1px solid var(--line); color: var(--text);
+      border-radius: 8px; padding: 6px 10px; cursor: pointer; font-size: 13px;
+    }
+    .check { display: flex; align-items: center; gap: 8px; color: var(--muted); font-size: 13px; margin: 12px 0; }
+    .hint { color: var(--muted); font-size: 12px; margin: 8px 0 0; }
+    .bar { height: 10px; background: #0f172a; border-radius: 999px; overflow: hidden; margin: 10px 0 6px; }
+    .bar > span { display: block; height: 100%; width: 0; background: linear-gradient(90deg, #22c55e, #38bdf8); transition: width .4s; }
+    .bar.indeterminate > span { width: 40% !important; animation: slide 1.2s infinite; }
+    @keyframes slide { 0% { transform: translateX(-100%); } 100% { transform: translateX(280%); } }
+    .log {
+      background: #020617; border: 1px solid var(--line); border-radius: 10px;
+      padding: 12px; height: 200px; overflow: auto; font-family: Consolas, monospace;
+      font-size: 12px; white-space: pre-wrap; color: #cbd5e1;
+    }
+    .status { font-size: 13px; margin-left: 10px; }
+    .ok { color: var(--ok); } .bad { color: var(--bad); } .run { color: var(--warn); }
+  </style>
+</head>
+<body>
+  <div class="wrap">
+    <h1>MES 数据库手动备份</h1>
+    <p class="sub">独立工具。点磁盘或点「浏览文件夹」选择保存位置(U 盘 / 移动硬盘 / 本机磁盘),不用手输路径。数据会边导出边压缩;库很大时请预留足够空间,完成前不要拔盘。</p>
+
+    <div class="card">
+      <div class="row">
+        <h2>1. 点选磁盘</h2>
+        <button class="btn ghost" id="btnRefresh" type="button">刷新磁盘</button>
+      </div>
+      <div class="drives" id="drives">正在读取磁盘...</div>
+      <p class="hint" id="dbHint"></p>
+    </div>
+
+    <div class="card">
+      <div class="row">
+        <h2>2. 保存位置(点选,不用输入)</h2>
+        <div>
+          <button class="btn ghost" id="btnUp" type="button">上一级</button>
+          <button class="btn" id="btnBrowse" type="button">浏览文件夹...</button>
+        </div>
+      </div>
+      <div class="picked empty" id="picked">尚未选择。请点上方磁盘,或点「浏览文件夹」弹出系统窗口选择。</div>
+      <div class="folders" id="folders"></div>
+      <p class="hint" id="spaceHint"></p>
+      <label class="check">
+        <input id="keepOld" type="checkbox"/>
+        同时删除该目录中超过 7 天的旧备份(U 盘建议不勾选)
+      </label>
+      <div class="bar" id="bar"><span id="barInner"></span></div>
+      <div class="hint" id="progHint"></div>
+      <button class="btn go" id="btnBackup" type="button">开始备份</button>
+      <span class="status" id="status"></span>
+    </div>
+
+    <div class="card">
+      <h2>运行日志</h2>
+      <div class="log" id="log">等待操作...</div>
+    </div>
+  </div>
+  <script>
+    var selectedPath = "";
+    var parentPath = "";
+    var polling = null;
+    var dbBytes = 0;
+
+    function $(id) { return document.getElementById(id); }
+    function asArray(x) {
+      if (!x) return [];
+      return Object.prototype.toString.call(x) === "[object Array]" ? x : [x];
+    }
+    function gb(n) { return (n / 1073741824).toFixed(2); }
+    function mb(n) { return (n / 1048576).toFixed(1); }
+
+    function setStatus(text, cls) {
+      $("status").className = "status " + (cls || "");
+      $("status").textContent = text ? ("  " + text) : "";
+    }
+    function setLog(lines) {
+      $("log").textContent = (lines && lines.length) ? lines.join("\n") : "等待操作...";
+      $("log").scrollTop = $("log").scrollHeight;
+    }
+    function setPicked(path) {
+      selectedPath = path || "";
+      var el = $("picked");
+      if (!selectedPath) {
+        el.className = "picked empty";
+        el.textContent = "尚未选择。请点上方磁盘,或点「浏览文件夹」弹出系统窗口选择。";
+      } else {
+        el.className = "picked";
+        el.textContent = selectedPath;
+      }
+    }
+
+    function api(url, opt) {
+      return fetch(url, opt).then(function (r) {
+        return r.text().then(function (t) {
+          var j = {};
+          try { j = JSON.parse(t); } catch (e) { throw new Error(t || "接口无响应"); }
+          if (!r.ok || j.ok === false) throw new Error(j.message || "请求失败");
+          return j;
+        });
+      });
+    }
+
+    function loadDrives() {
+      $("drives").textContent = "正在读取磁盘...";
+      api("/api/drives").then(function (j) {
+        dbBytes = j.dbBytes || 0;
+        if (dbBytes > 0) {
+          $("dbHint").textContent = "当前数据库约 " + gb(dbBytes) + " GB。建议目标盘剩余空间大于该值。备份采用 gzip 压缩,边导边压。";
+        }
+        var html = "";
+        asArray(j.drives).forEach(function (d) {
+          var cls = "drive" + (d.removable ? " usb" : "");
+          var space = d.ready ? ("剩余 " + d.freeGB + " GB / 共 " + d.sizeGB + " GB") : "未就绪";
+          html += '<button type="button" class="' + cls + '" data-path="' + d.path + '" data-free="' + (d.freeBytes || 0) + '">'
+            + '<div class="t"><span class="tag' + (d.removable ? " usb" : "") + '">' + d.type + "</span></div>"
+            + '<div class="n">' + d.letter + "  " + (d.name || "") + "</div>"
+            + '<div class="s">' + space + "</div></button>";
+        });
+        $("drives").innerHTML = html || "没有发现可用磁盘";
+        Array.prototype.forEach.call($("drives").querySelectorAll(".drive"), function (el) {
+          el.onclick = function () {
+            Array.prototype.forEach.call($("drives").querySelectorAll(".drive"), function (x) { x.classList.remove("on"); });
+            el.classList.add("on");
+            var p = el.getAttribute("data-path");
+            setPicked(p + "MES_DB_Backup");
+            showSpace(parseInt(el.getAttribute("data-free") || "0", 10));
+            loadFolders(p);
+          };
+        });
+      }).catch(function (e) {
+        $("drives").textContent = "读取磁盘失败:" + e.message;
+      });
+    }
+
+    function showSpace(freeBytes) {
+      if (!freeBytes) { $("spaceHint").textContent = ""; return; }
+      var msg = "该磁盘剩余 " + gb(freeBytes) + " GB。";
+      if (dbBytes > 0 && freeBytes < dbBytes * 1.2) {
+        msg += " 空间可能不够,建议换更大的盘。";
+      }
+      $("spaceHint").textContent = msg;
+    }
+
+    function loadFolders(path) {
+      if (!path) return;
+      api("/api/folders?path=" + encodeURIComponent(path)).then(function (j) {
+        var d = j.data || {};
+        parentPath = d.parent || "";
+        var html = "";
+        asArray(d.folders).forEach(function (f) {
+          html += '<button class="folder" type="button" data-path="' + f.path.replace(/"/g, "") + '">📁 ' + f.name + "</button>";
+        });
+        $("folders").innerHTML = html || '<span style="color:#94a3b8;font-size:13px;">该目录下没有子文件夹,将备份到上面显示的位置</span>';
+        Array.prototype.forEach.call($("folders").querySelectorAll(".folder"), function (el) {
+          el.onclick = function () {
+            var p = el.getAttribute("data-path");
+            setPicked(p);
+            loadFolders(p);
+          };
+        });
+      }).catch(function (e) {
+        $("folders").innerHTML = '<span style="color:#f87171;font-size:13px;">' + e.message + "</span>";
+      });
+    }
+
+    function pollStatus() {
+      api("/api/status").then(function (j) {
+        setLog(asArray(j.log));
+        var written = j.writtenBytes || 0;
+        var est = j.dbBytes || dbBytes || 0;
+        $("bar").className = "bar" + (j.running && (!est || written < 1024) ? " indeterminate" : "");
+        var pct = 0;
+        if (est > 0 && written > 0) {
+          pct = Math.max(2, Math.min(99, Math.round(written / est * 100)));
+        } else if (!j.running && j.result === "ok") {
+          pct = 100;
+        }
+        $("barInner").style.width = pct + "%";
+        if (j.running) {
+          $("btnBackup").disabled = true;
+          $("progHint").textContent = "已写入 " + mb(written) + " MB" + (j.fileName ? ("(" + j.fileName + ")") : "") + ",请勿拔盘";
+          setStatus("正在备份,数据量大时可能需要较长时间...", "run");
+        } else {
+          $("btnBackup").disabled = false;
+          $("bar").className = "bar";
+          if (j.result === "ok") {
+            $("barInner").style.width = "100%";
+            $("progHint").textContent = "完成,文件约 " + mb(written) + " MB:" + (j.fileName || "");
+            setStatus("备份完成", "ok");
+          } else if (j.result === "error") {
+            setStatus(j.error || "备份失败", "bad");
+          }
+          if (polling) { clearInterval(polling); polling = null; }
+        }
+      }).catch(function () {});
+    }
+
+    $("btnRefresh").onclick = loadDrives;
+    $("btnUp").onclick = function () {
+      if (parentPath) {
+        setPicked(parentPath);
+        loadFolders(parentPath);
+      }
+    };
+    $("btnBrowse").onclick = function () {
+      setStatus("请在弹出的系统窗口中选择文件夹(窗口可能在浏览器后面)", "run");
+      api("/api/browse", { method: "POST" }).then(function (j) {
+        if (j.cancelled) { setStatus("", ""); return; }
+        setPicked(j.path);
+        showSpace(j.freeBytes || 0);
+        loadFolders(j.path);
+        setStatus("已选择保存位置", "ok");
+      }).catch(function (e) {
+        setStatus(e.message, "bad");
+      });
+    };
+    $("btnBackup").onclick = function () {
+      if (!selectedPath) { setStatus("请先点选磁盘或浏览文件夹", "bad"); return; }
+      $("btnBackup").disabled = true;
+      setStatus("正在启动备份...", "run");
+      $("bar").className = "bar indeterminate";
+      api("/api/backup", {
+        method: "POST",
+        headers: { "Content-Type": "application/json" },
+        body: JSON.stringify({ path: selectedPath, keepDays: $("keepOld").checked ? 7 : 0 })
+      }).then(function () {
+        if (polling) clearInterval(polling);
+        polling = setInterval(pollStatus, 1000);
+        pollStatus();
+      }).catch(function (e) {
+        $("btnBackup").disabled = false;
+        $("bar").className = "bar";
+        setStatus(e.message, "bad");
+      });
+    };
+
+    loadDrives();
+  </script>
+</body>
+</html>

+ 1 - 1
src/main/java/com/jeesite/modules/config/PdaSidFilterConfig.java

@@ -18,7 +18,7 @@ public class PdaSidFilterConfig {
 		bean.setFilter(new PdaSidCookieFilter());
 		bean.addUrlPatterns("/a/*");
 		bean.setName("pdaSidCookieFilter");
-		// 必须排在 CharacterEncodingFilter 之后,否则 getParameter 会按 ISO-8859-1 解析中文
+		// 排在 Spring 编码过滤器之后,避免过早 getParameter 锁死 ISO-8859-1
 		bean.setOrder(Ordered.HIGHEST_PRECEDENCE + 20);
 		return bean;
 	}

+ 56 - 0
src/main/java/com/jeesite/modules/interceptor/EmpUserFormPostInterceptor.java

@@ -5,6 +5,8 @@ import com.jeesite.modules.mes.entity.MesEmployee;
 import com.jeesite.modules.mes.service.MesEmployeeService;
 import com.jeesite.modules.sys.entity.EmpUser;
 import com.jeesite.modules.sys.entity.EmployeeOffice;
+import com.jeesite.modules.sys.entity.Role;
+import com.jeesite.modules.sys.service.RoleService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import org.springframework.web.servlet.HandlerInterceptor;
@@ -23,6 +25,9 @@ public class EmpUserFormPostInterceptor implements HandlerInterceptor {
     @Autowired
     private MesEmployeeService mesEmployeeService;
 
+    @Autowired
+    private RoleService roleService;
+
     @Override
     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
             ModelAndView modelAndView) {
@@ -34,6 +39,8 @@ public class EmpUserFormPostInterceptor implements HandlerInterceptor {
             return;
         }
         EmpUser empUser = (EmpUser) obj;
+        // 编辑页需要回显全部可选角色和当前用户已分配角色
+        fillRoleList(empUser, modelAndView);
         MesEmployee employee = findEmployee(empUser);
         if (employee == null) {
             return;
@@ -56,6 +63,55 @@ public class EmpUserFormPostInterceptor implements HandlerInterceptor {
         modelAndView.addObject("empUser", empUser);
     }
 
+    /**
+     * 加载员工类型可选角色,以及当前用户已绑定的角色(多角色全部回显)。
+     */
+    private void fillRoleList(EmpUser empUser, ModelAndView modelAndView) {
+        Role query = new Role();
+        query.setUserType("employee");
+        List<Role> allRoleList = roleService.findList(query);
+        if (allRoleList == null) {
+            allRoleList = new ArrayList<>();
+        }
+
+        List<Role> userRoleList = new ArrayList<>();
+        if (StringUtils.isNotBlank(empUser.getUserCode())) {
+            Role userRoleQuery = new Role();
+            userRoleQuery.setUserCode(empUser.getUserCode());
+            List<Role> dbRoles = roleService.findListByUserCode(userRoleQuery);
+            if (dbRoles != null && !dbRoles.isEmpty()) {
+                userRoleList = dbRoles;
+            }
+        }
+        // 已分配角色始终出现在下拉中,避免数据权限把当前角色过滤掉后无法回显
+        for (Role userRole : userRoleList) {
+            if (userRole == null || StringUtils.isBlank(userRole.getRoleCode())) {
+                continue;
+            }
+            boolean exists = false;
+            for (Role role : allRoleList) {
+                if (role != null && StringUtils.equals(userRole.getRoleCode(), role.getRoleCode())) {
+                    exists = true;
+                    break;
+                }
+            }
+            if (!exists) {
+                allRoleList.add(userRole);
+            }
+        }
+        modelAndView.addObject("allRoleList", allRoleList);
+        modelAndView.addObject("userRoleList", userRoleList);
+
+        List<String> codes = new ArrayList<>();
+        for (Role role : userRoleList) {
+            if (role != null && StringUtils.isNotBlank(role.getRoleCode())) {
+                codes.add(role.getRoleCode());
+            }
+        }
+        String userRoleString = StringUtils.join(codes, ",");
+        modelAndView.addObject("userRoleString", userRoleString);
+    }
+
     private MesEmployee findEmployee(EmpUser empUser) {
         if (empUser.getEmployee() != null && StringUtils.isNotBlank(empUser.getEmployee().getEmpCode())) {
             MesEmployee employee = mesEmployeeService.get(new MesEmployee(empUser.getEmployee().getEmpCode()));

+ 1 - 6
src/main/java/com/jeesite/modules/interceptor/PdaSidCookieFilter.java

@@ -31,13 +31,8 @@ public class PdaSidCookieFilter implements Filter {
 	@Override
 	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
 			throws IOException, ServletException {
-		if (request.getCharacterEncoding() == null) {
-			request.setCharacterEncoding("UTF-8");
-		}
-		if (response.getCharacterEncoding() == null || "ISO-8859-1".equalsIgnoreCase(response.getCharacterEncoding())) {
-			response.setCharacterEncoding("UTF-8");
-		}
 		HttpServletRequest httpRequest = (HttpServletRequest) request;
+		// 优先 Header / 查询串,避免为取 sid 而解析整个 POST 表单
 		String sid = firstNonBlank(httpRequest.getHeader(SID_HEADER), querySid(httpRequest));
 		if (StringUtils.isBlank(sid)) {
 			sid = httpRequest.getParameter(SID_PARAM);

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

@@ -7,7 +7,6 @@ import com.jeesite.common.mybatis.annotation.Column;
 import com.jeesite.common.mybatis.annotation.Table;
 import com.jeesite.common.mybatis.mapper.query.QueryType;
 
-import java.nio.charset.StandardCharsets;
 import java.util.Date;
 
 /**
@@ -75,47 +74,13 @@ public class MesPdaScanRecord extends DataEntity<MesPdaScanRecord> {
 
 	@Size(min=0, max=64, message="ucode length must be less than 64")
 	public String getUcode() {
-		return repairUtf8Mojibake(ucode);
+		return ucode;
 	}
 
 	public void setUcode(String ucode) {
 		this.ucode = ucode;
 	}
 
-	/**
-	 * JDBC 若把 UTF-8 字节按 ISO-8859-1 读出,页面会显示乱码;库里其实是正常中文。
-	 * 已是汉字则原样返回,避免二次转换。
-	 */
-	public static String repairUtf8Mojibake(String value) {
-		if (value == null || value.isEmpty()) {
-			return value;
-		}
-		if (containsCjk(value)) {
-			return value;
-		}
-		try {
-			byte[] latin1 = value.getBytes(StandardCharsets.ISO_8859_1);
-			String utf8 = new String(latin1, StandardCharsets.UTF_8);
-			if (containsCjk(utf8) && !utf8.contains("\uFFFD")) {
-				return utf8;
-			}
-		} catch (Exception ignored) {
-		}
-		return value;
-	}
-
-	private static boolean containsCjk(String value) {
-		for (int i = 0; i < value.length(); i++) {
-			Character.UnicodeBlock block = Character.UnicodeBlock.of(value.charAt(i));
-			if (block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
-					|| block == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
-					|| block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A) {
-				return true;
-			}
-		}
-		return false;
-	}
-
 	public Date getCreateDate_gte() {
 		return sqlMap.getWhere().getValue("create_date", QueryType.GTE);
 	}

+ 106 - 5
src/main/java/com/jeesite/modules/mes/erp/ErpPickMaterialService.java

@@ -31,19 +31,25 @@ public class ErpPickMaterialService {
 	private final ErpInventoryLotService erpInventoryLotService;
 	private final MesErpPickKcService mesErpPickKcService;
 	private final ErpInteractionLogService erpInteractionLogService;
+	private final ErpStockLocService erpStockLocService;
+	private final ErpProductionOrderService erpProductionOrderService;
 
 	public ErpPickMaterialService(KingdeeErpClient erpClient,
 			KingdeeErpProperties erpProperties,
 			ErpProductionMaterialService erpProductionMaterialService,
 			ErpInventoryLotService erpInventoryLotService,
 			MesErpPickKcService mesErpPickKcService,
-			ErpInteractionLogService erpInteractionLogService) {
+			ErpInteractionLogService erpInteractionLogService,
+			ErpStockLocService erpStockLocService,
+			ErpProductionOrderService erpProductionOrderService) {
 		this.erpClient = erpClient;
 		this.erpProperties = erpProperties;
 		this.erpProductionMaterialService = erpProductionMaterialService;
 		this.erpInventoryLotService = erpInventoryLotService;
 		this.mesErpPickKcService = mesErpPickKcService;
 		this.erpInteractionLogService = erpInteractionLogService;
+		this.erpStockLocService = erpStockLocService;
+		this.erpProductionOrderService = erpProductionOrderService;
 	}
 
 	public ErpPickMaterialResult submit(ErpPickMaterialSubmitRequest request) {
@@ -66,6 +72,12 @@ public class ErpPickMaterialService {
 			return ErpPickMaterialResult.fail("未找到对应的生产用料清单");
 		}
 
+		String statusMsg = erpProductionOrderService.validatePickableBusinessStatus(
+				request.getBillNo(), request.getEntryId());
+		if (StringUtils.isNotBlank(statusMsg)) {
+			return ErpPickMaterialResult.fail(statusMsg);
+		}
+
 		List<PickLineContext> pickLines = new ArrayList<>();
 		for (ErpPickMaterialSubmitRequest.Line line : request.getLines()) {
 			if (line == null || StringUtils.isBlank(line.getPpbomEntryId())) {
@@ -84,11 +96,32 @@ public class ErpPickMaterialService {
 				return ErpPickMaterialResult.fail("物料 " + material.getMaterialNumber()
 						+ " 领料数量不能超过未领数量 " + noPickedQty);
 			}
-			String validateMsg = validateMaterial(material, pickQty, firstNonBlank(line.getLotNumber(), material.getLotNumber()));
+			erpProductionMaterialService.resolveBatchManageFlag(material);
+			String lotNumber = resolvePickLotNumber(material, pickQty,
+					firstNonBlank(line.getLotNumber(), material.getLotNumber()));
+			String validateMsg = validateMaterial(material, pickQty, lotNumber);
 			if (StringUtils.isNotBlank(validateMsg)) {
 				return ErpPickMaterialResult.fail(validateMsg);
 			}
-			pickLines.add(new PickLineContext(material, pickQty, firstNonBlank(line.getLotNumber(), material.getLotNumber())));
+			String locNumber = StringUtils.trim(line.getStockLocNumber());
+			String locKey = StringUtils.trim(line.getStockLocKey());
+			String locMsg = resolveAndValidateStockLoc(material, locNumber, locKey);
+			if (StringUtils.isNotBlank(locMsg)) {
+				return ErpPickMaterialResult.fail(locMsg);
+			}
+			if (StringUtils.isBlank(locNumber) && erpStockLocService.requiresStockLocation(material.getStockNumber())) {
+				List<ErpStockLocOption> locations = erpStockLocService.findLocationsByStock(material.getStockNumber());
+				locNumber = erpStockLocService.resolveDefaultLocNumber(material.getStockNumber(), locations);
+				if (StringUtils.isBlank(locKey) && locations != null) {
+					for (ErpStockLocOption option : locations) {
+						if (option != null && locNumber.equals(option.getLocNumber())) {
+							locKey = option.getLocKey();
+							break;
+						}
+					}
+				}
+			}
+			pickLines.add(new PickLineContext(material, pickQty, lotNumber, locNumber, locKey));
 		}
 
 		if (pickLines.isEmpty()) {
@@ -170,7 +203,27 @@ public class ErpPickMaterialService {
 			return "物料 " + materialLabel
 					+ " ERP 即时库存无批号(与 ERP 查询一致),无法领料。请先在 ERP 做采购入库/生产入库/调拨后再领";
 		}
-		return "物料 " + materialLabel + " 启用了批号管理,请先点击「选批号」选择批号后再提交";
+		return "物料 " + materialLabel + " 启用了批号管理,发料仓无可用批号可自动带出,无法领料";
+	}
+
+	/**
+	 * PDA 可不传仓库/仓位/批号:仓库用用料清单发料仓,仓位用仓库默认,批号按发料仓库存自动推荐。
+	 */
+	private String resolvePickLotNumber(ErpProductionMaterial material, BigDecimal pickQty, String requestedLot) {
+		String lotNumber = StringUtils.trim(requestedLot);
+		if (StringUtils.isNotBlank(lotNumber)) {
+			return lotNumber;
+		}
+		if (material == null || !material.isBatchManage()) {
+			return "";
+		}
+		List<ErpInventoryLotOption> lots = erpInventoryLotService.findAvailableLots(
+				material.getMaterialNumber(),
+				material.getStockNumber(),
+				material.getStockStatusNumber(),
+				material.getStockOrgNumber());
+		return StringUtils.trimToEmpty(erpInventoryLotService.suggestLotNumber(
+				lots, pickQty, material.getStockNumber()));
 	}
 
 	private String formatQty(BigDecimal qty) {
@@ -270,6 +323,7 @@ public class ErpPickMaterialService {
 		entity.put("FStockUnitId", numberObject(stockUnit, false));
 		putPickQuantities(entity, qty);
 		entity.put("FStockId", numberObject(material.getStockNumber(), false));
+		putStockLoc(entity, material.getStockNumber(), pickLine.stockLocNumber, pickLine.stockLocKey);
 		entity.put("FStockStatusId", numberObject(stockStatus, false));
 		entity.put("FMoBillNo", material.getBillNo());
 		if (StringUtils.isNotBlank(material.getMoId())) {
@@ -326,6 +380,48 @@ public class ErpPickMaterialService {
 				&& unitNumber.equals(stockUnit);
 	}
 
+	private String resolveAndValidateStockLoc(ErpProductionMaterial material, String locNumber, String locKey) {
+		String stockNumber = material == null ? "" : material.getStockNumber();
+		if (!erpStockLocService.requiresStockLocation(stockNumber)) {
+			return "";
+		}
+		String loc = StringUtils.trim(locNumber);
+		String key = StringUtils.trim(locKey);
+		if (StringUtils.isBlank(loc)) {
+			List<ErpStockLocOption> locations = erpStockLocService.findLocationsByStock(stockNumber);
+			loc = erpStockLocService.resolveDefaultLocNumber(stockNumber, locations);
+		}
+		String msg = erpStockLocService.validateStockLocForSave(stockNumber, loc, key);
+		if (StringUtils.isBlank(msg)) {
+			return "";
+		}
+		return "物料 " + material.getMaterialNumber() + " 发料仓已启用仓位管理," + msg;
+	}
+
+	private void putStockLoc(JSONObject entity, String stockNumber, String locNumber, String locKey) {
+		if (!erpStockLocService.requiresStockLocation(stockNumber)) {
+			return;
+		}
+		String loc = StringUtils.trim(locNumber);
+		String key = StringUtils.trim(locKey);
+		if (StringUtils.isBlank(loc)) {
+			List<ErpStockLocOption> locations = erpStockLocService.findLocationsByStock(stockNumber);
+			loc = erpStockLocService.resolveDefaultLocNumber(stockNumber, locations);
+			if (StringUtils.isBlank(key) && locations != null) {
+				for (ErpStockLocOption option : locations) {
+					if (option != null && loc.equals(option.getLocNumber())) {
+						key = option.getLocKey();
+						break;
+					}
+				}
+			}
+		}
+		Long stockLocId = erpStockLocService.resolveStockLocIdForSave(stockNumber, loc, key);
+		if (stockLocId != null && stockLocId > 0L) {
+			entity.put("FStockLocId", stockLocId);
+		}
+	}
+
 	private JSONArray buildEntityLink(ErpProductionMaterial material, BigDecimal qty) {
 		Object erpQty = ErpKingdeeParseUtils.toErpQty(qty);
 		JSONObject link = new JSONObject(true);
@@ -407,11 +503,16 @@ public class ErpPickMaterialService {
 		private final ErpProductionMaterial material;
 		private final BigDecimal pickQty;
 		private final String lotNumber;
+		private final String stockLocNumber;
+		private final String stockLocKey;
 
-		private PickLineContext(ErpProductionMaterial material, BigDecimal pickQty, String lotNumber) {
+		private PickLineContext(ErpProductionMaterial material, BigDecimal pickQty, String lotNumber,
+				String stockLocNumber, String stockLocKey) {
 			this.material = material;
 			this.pickQty = pickQty;
 			this.lotNumber = lotNumber;
+			this.stockLocNumber = stockLocNumber;
+			this.stockLocKey = stockLocKey;
 		}
 	}
 }

+ 19 - 0
src/main/java/com/jeesite/modules/mes/erp/ErpPickMaterialSubmitRequest.java

@@ -74,6 +74,9 @@ public class ErpPickMaterialSubmitRequest implements Serializable {
 		private BigDecimal pickQty;
 		/** 批号(启用批号管理的物料必填) */
 		private String lotNumber;
+		/** 发料仓位编码(仓库启用仓位管理时必填,可空则用仓库默认仓位) */
+		private String stockLocNumber;
+		private String stockLocKey;
 
 		public String getPpbomEntryId() {
 			return ppbomEntryId;
@@ -98,5 +101,21 @@ public class ErpPickMaterialSubmitRequest implements Serializable {
 		public void setLotNumber(String lotNumber) {
 			this.lotNumber = lotNumber;
 		}
+
+		public String getStockLocNumber() {
+			return stockLocNumber;
+		}
+
+		public void setStockLocNumber(String stockLocNumber) {
+			this.stockLocNumber = stockLocNumber;
+		}
+
+		public String getStockLocKey() {
+			return stockLocKey;
+		}
+
+		public void setStockLocKey(String stockLocKey) {
+			this.stockLocKey = stockLocKey;
+		}
 	}
 }

+ 42 - 0
src/main/java/com/jeesite/modules/mes/erp/ErpProductionOrderService.java

@@ -99,6 +99,48 @@ public class ErpProductionOrderService {
 		return !findPageList(billNo.trim(), 0, 1).isEmpty();
 	}
 
+	/**
+	 * 生产领料前校验订单行业务状态:须为下达、开工或完工。
+	 * 查不到状态时返回空,仍交给金蝶保存接口校验。
+	 */
+	public String validatePickableBusinessStatus(String billNo, String entryId) {
+		if (StringUtils.isBlank(billNo) || StringUtils.isBlank(entryId)) {
+			return "";
+		}
+		String status = queryEntryBusinessStatus(billNo.trim(), entryId.trim());
+		if (StringUtils.isBlank(status)) {
+			return "";
+		}
+		if ("3".equals(status) || "4".equals(status) || "5".equals(status)) {
+			return "";
+		}
+		String statusName = businessStatusName(status);
+		if (StringUtils.isBlank(statusName)) {
+			statusName = status;
+		}
+		return "生产订单 " + billNo.trim() + " 第1行业务状态为「" + statusName
+				+ "」,金蝶只允许下达、开工或完工后领料。请先在金蝶对该订单执行下达或开工";
+	}
+
+	private String queryEntryBusinessStatus(String billNo, String entryId) {
+		try {
+			JSONObject data = new JSONObject();
+			data.put("FormId", FORM_ID);
+			data.put("FieldKeys", "FStatus,FTreeEntity_FEntryId,FBILLNO");
+			data.put("FilterString", "FBILLNO='" + billNo.replace("'", "''")
+					+ "' AND FTreeEntity_FEntryId=" + Long.parseLong(entryId));
+			data.put("Limit", 1);
+			JSONArray rows = erpClient.billQuery(data);
+			if (rows == null || rows.isEmpty()) {
+				return "";
+			}
+			return ErpKingdeeParseUtils.readBillQueryString(rows.get(0), 0, "FStatus");
+		} catch (Exception ex) {
+			logger.warn("查询生产订单业务状态失败, billNo={}, entryId={}: {}", billNo, entryId, ex.getMessage());
+			return "";
+		}
+	}
+
 	public List<ErpProductionOrder> findPageList(String billNo, int startRow, int limit) {
 		if (!erpProperties.isInventoryCategoryFilterEnabled()) {
 			return findPageListWithDetailSummary(billNo, startRow, limit);

+ 9 - 18
src/main/java/com/jeesite/modules/mes/erp/ErpTransferDirectSaveService.java

@@ -161,7 +161,8 @@ public class ErpTransferDirectSaveService {
 	}
 
 	/**
-	 * 提交时复核扫码批次:须已发货检;物料/批号须一致;未传数量时用绑定数量合计。
+	 * 提交时复核托盘:须已发货检、物料一致;数量按绑定数量合计。
+	 * 调拨批号以页面所选 ERP 即时库存批号为准,不与 MES 托盘入库回写批号比对。
 	 */
 	private String applyScannedBatches(ErpTransferDirectSubmitRequest request) {
 		List<String> traySns = request.resolveTraySnList();
@@ -169,10 +170,8 @@ public class ErpTransferDirectSaveService {
 			return "";
 		}
 		String selectedMaterial = StringUtils.trim(request.getMaterialNumber());
-		String selectedLot = StringUtils.trim(request.getLotNumber());
 		long totalQty = 0L;
 		String resolvedMaterial = "";
-		String resolvedLot = "";
 		for (String sn : traySns) {
 			Map<String, Object> data = new HashMap<>(12);
 			String error = fillScanData(data, sn, selectedMaterial);
@@ -180,17 +179,11 @@ public class ErpTransferDirectSaveService {
 				return error;
 			}
 			String material = StringUtils.trim((String) data.get("materialNumber"));
-			String lot = StringUtils.trim((String) data.get("lotNumber"));
 			if (StringUtils.isBlank(resolvedMaterial)) {
 				resolvedMaterial = material;
 			} else if (StringUtils.isNotBlank(material) && !resolvedMaterial.equals(material)) {
 				return "扫码批次物料不一致,不能提交同一张调拨单";
 			}
-			if (StringUtils.isBlank(resolvedLot)) {
-				resolvedLot = lot;
-			} else if (StringUtils.isNotBlank(lot) && !resolvedLot.equals(lot)) {
-				return "扫码批次 ERP 批号不一致,不能提交同一张调拨单";
-			}
 			Object increment = data.get("qtyIncrement");
 			if (increment instanceof Number) {
 				totalQty += ((Number) increment).longValue();
@@ -199,13 +192,6 @@ public class ErpTransferDirectSaveService {
 		if (StringUtils.isBlank(selectedMaterial) && StringUtils.isNotBlank(resolvedMaterial)) {
 			request.setMaterialNumber(resolvedMaterial);
 		}
-		if (StringUtils.isNotBlank(selectedLot) && StringUtils.isNotBlank(resolvedLot)
-				&& !selectedLot.equals(resolvedLot)) {
-			return "扫码批次 ERP 批号与所选批号不一致";
-		}
-		if (StringUtils.isBlank(selectedLot) && StringUtils.isNotBlank(resolvedLot)) {
-			request.setLotNumber(resolvedLot);
-		}
 		if (request.getQty() == null && totalQty > 0) {
 			request.setQty(BigDecimal.valueOf(totalQty));
 		}
@@ -220,6 +206,11 @@ public class ErpTransferDirectSaveService {
 		long nums = batch.getNums() == null ? 0L : batch.getNums();
 		String batchStatus = StringUtils.defaultIfBlank(batch.getBatchStatus(), MesBatchSn.BATCH_STATUS_PACKING);
 		String materialNumber = MesErpLineMaterialMapping.resolveMaterialByLineSnLoose(batch.getLineSn());
+		String selected = MesErpLineMaterialMapping.normalizeMaterialNumber(selectedMaterial);
+		if (StringUtils.isNotBlank(selected)
+				&& MesErpLineMaterialMapping.isLineAllowedForMaterialLoose(batch.getLineSn(), selected)) {
+			materialNumber = selected;
+		}
 		String lotNumber = StringUtils.trimToEmpty(batch.getErpLotNumber());
 		data.put("batchSn", batch.getBatchSn());
 		data.put("nums", nums);
@@ -242,8 +233,8 @@ public class ErpTransferDirectSaveService {
 		if (nums <= 0) {
 			return "批次码 " + batchSn + " 绑定数量为 0,不能调拨";
 		}
-		if (StringUtils.isNotBlank(selectedMaterial) && StringUtils.isNotBlank(materialNumber)
-				&& !selectedMaterial.equals(materialNumber)) {
+		if (StringUtils.isNotBlank(selected) && StringUtils.isNotBlank(materialNumber)
+				&& !selected.equals(materialNumber)) {
 			return "批次码物料 " + materialNumber + " 与当前选择物料不一致";
 		}
 		return "";

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

@@ -115,7 +115,7 @@ public class KingdeeErpProperties {
 	private String transferDirectBillType;
 
 	/** 直接调拨单固定物料范围(HEV / 115) */
-	@Value("${erp.k3cloud.transfer-direct.material-numbers:8.10.HEV.10.047696,8.35.115.10.5003146600021}")
+	@Value("${erp.k3cloud.transfer-direct.material-numbers:8.10.HEV.10.047696,8.10.HEV.10.049025,8.35.115.10.5003146600021}")
 	private String transferDirectMaterialNumbers;
 
 	@Value("${erp.k3cloud.transfer-direct.transfer-biz-type:InnerOrgTransfer}")
@@ -147,7 +147,7 @@ public class KingdeeErpProperties {
 	private String transferDirectDefaultDestStockNumber;
 
 	/** 生产入库单 PRD_INSTOCK 固定物料范围(HEV / 115,与直接调拨单一致) */
-	@Value("${erp.k3cloud.prd-in-stock.material-numbers:8.10.HEV.10.047696,8.35.115.10.5003146600021}")
+	@Value("${erp.k3cloud.prd-in-stock.material-numbers:8.10.HEV.10.047696,8.10.HEV.10.049025,8.35.115.10.5003146600021}")
 	private String prdInStockMaterialNumbers;
 
 	@Value("${erp.k3cloud.prd-in-stock.bill-type:SCRKD01_SYS}")
@@ -346,7 +346,7 @@ public class KingdeeErpProperties {
 		if (StringUtils.isBlank(materialNumber)) {
 			return false;
 		}
-		String trimmed = materialNumber.trim();
+		String trimmed = MesErpLineMaterialMapping.normalizeMaterialNumber(materialNumber);
 		for (String allowed : getTransferDirectMaterialNumbers()) {
 			if (trimmed.equals(allowed)) {
 				return true;
@@ -399,7 +399,7 @@ public class KingdeeErpProperties {
 		if (StringUtils.isBlank(materialNumber)) {
 			return false;
 		}
-		String trimmed = materialNumber.trim();
+		String trimmed = MesErpLineMaterialMapping.normalizeMaterialNumber(materialNumber);
 		for (String allowed : getPrdInStockMaterialNumbers()) {
 			if (trimmed.equals(allowed)) {
 				return true;

+ 31 - 6
src/main/java/com/jeesite/modules/mes/erp/MesErpLineMaterialMapping.java

@@ -10,11 +10,14 @@ import java.util.Map;
 
 /**
  * MES 产线与 ERP 物料编码映射(生产入库选批次用)。
- * HEVXT → HEV;115XT → 115(与 MesErpScheduleService、产线设备配置一致)。
+ * HEVXT → HEV(两种成品编码);115XT → 115(与 MesErpScheduleService、产线设备配置一致)。
  */
 public final class MesErpLineMaterialMapping {
 
+	/** HEV 成品物料(原编码) */
 	private static final String HEV_MATERIAL = "8.10.HEV.10.047696";
+	/** HEV 成品物料(新增编码) */
+	private static final String HEV_MATERIAL_ALT = "8.10.HEV.10.049025";
 	private static final String MATERIAL_115 = "8.35.115.10.5003146600021";
 
 	private static final Map<String, String> LINE_TO_MATERIAL;
@@ -39,6 +42,7 @@ public final class MesErpLineMaterialMapping {
 
 	/**
 	 * 按产线解析物料;精确映射没有时按 HEV / 115 关键字兜底(发货检等场景产线号可能为 HEVXI)。
+	 * HEV 产线默认返回原编码 047696,匹配两种 HEV 成品请用 isHevMaterial / isLineAllowedForMaterialLoose。
 	 */
 	public static String resolveMaterialByLineSnLoose(String lineSn) {
 		String mapped = resolveMaterialByLineSn(lineSn);
@@ -55,12 +59,18 @@ public final class MesErpLineMaterialMapping {
 		return "";
 	}
 
+	/** 是否 HEV 成品物料(047696 / 049025) */
+	public static boolean isHevMaterial(String materialNumber) {
+		String material = normalizeMaterialNumber(materialNumber);
+		return HEV_MATERIAL.equals(material) || HEV_MATERIAL_ALT.equals(material);
+	}
+
 	/** HEV / 115 物料显示名 */
 	public static String resolveMaterialName(String materialNumber) {
-		if (HEV_MATERIAL.equals(StringUtils.trim(materialNumber))) {
+		if (isHevMaterial(materialNumber)) {
 			return "HEV";
 		}
-		if (MATERIAL_115.equals(StringUtils.trim(materialNumber))) {
+		if (MATERIAL_115.equals(normalizeMaterialNumber(materialNumber))) {
 			return "115";
 		}
 		return "";
@@ -74,6 +84,9 @@ public final class MesErpLineMaterialMapping {
 			return "";
 		}
 		String trimmed = materialNumber.trim();
+		if (trimmed.startsWith(HEV_MATERIAL_ALT)) {
+			return HEV_MATERIAL_ALT;
+		}
 		if (trimmed.startsWith(HEV_MATERIAL)) {
 			return HEV_MATERIAL;
 		}
@@ -103,8 +116,12 @@ public final class MesErpLineMaterialMapping {
 		if (StringUtils.isBlank(materialNumber)) {
 			return new ArrayList<>();
 		}
-		String trimmed = materialNumber.trim();
+		String trimmed = normalizeMaterialNumber(materialNumber);
 		List<String> lines = new ArrayList<>();
+		if (isHevMaterial(trimmed)) {
+			lines.add("HEVXT");
+			return lines;
+		}
 		for (Map.Entry<String, String> entry : LINE_TO_MATERIAL.entrySet()) {
 			if (trimmed.equals(entry.getValue())) {
 				lines.add(entry.getKey());
@@ -121,14 +138,22 @@ public final class MesErpLineMaterialMapping {
 	}
 
 	/**
-	 * 产线是否允许入库当前物料。精确映射没有时按 HEV / 115 关键字兜底(如 HEVX1、HEVXI)。
+	 * 产线是否允许入库/调拨当前物料。精确映射没有时按 HEV / 115 关键字兜底(如 HEVX1、HEVXI)。
+	 * HEV 产线同时允许 047696 与 049025。
 	 */
 	public static boolean isLineAllowedForMaterialLoose(String lineSn, String materialNumber) {
 		if (StringUtils.isBlank(lineSn) || StringUtils.isBlank(materialNumber)) {
 			return false;
 		}
 		String material = normalizeMaterialNumber(materialNumber);
-		return material.equals(resolveMaterialByLineSnLoose(lineSn));
+		String key = StringUtils.trimToEmpty(lineSn).toUpperCase();
+		if (isHevMaterial(material) && key.contains("HEV")) {
+			return true;
+		}
+		if (MATERIAL_115.equals(material) && key.contains("115")) {
+			return true;
+		}
+		return material.equals(resolveMaterialByLineSn(lineSn));
 	}
 
 	public static List<String> allLineSnList() {

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

@@ -67,7 +67,7 @@ public class MesBatchSnService extends CrudService<MesBatchSnDao, MesBatchSn> {
 			return result;
 		}
 		for (MesBatchSn batch : list) {
-			if (material.equals(MesErpLineMaterialMapping.resolveMaterialByLineSnLoose(batch.getLineSn()))) {
+			if (MesErpLineMaterialMapping.isLineAllowedForMaterialLoose(batch.getLineSn(), material)) {
 				result.add(batch);
 			}
 		}
@@ -98,7 +98,7 @@ public class MesBatchSnService extends CrudService<MesBatchSnDao, MesBatchSn> {
 			return result;
 		}
 		for (MesBatchSn batch : list) {
-			if (material.equals(MesErpLineMaterialMapping.resolveMaterialByLineSnLoose(batch.getLineSn()))) {
+			if (MesErpLineMaterialMapping.isLineAllowedForMaterialLoose(batch.getLineSn(), material)) {
 				result.add(batch);
 			}
 		}

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

@@ -205,7 +205,9 @@ public class EmpUserCustomController extends BaseController {
             }
             return renderResult(Global.TRUE, "保存用户 '" + empUser.getLoginCode() + "' 成功");
         }
-        return empUserController.save(empUser, oldLoginCode, request);
+        String result = empUserController.save(empUser, oldLoginCode, request);
+        saveRolesOnEdit(empUser, request, result);
+        return result;
     }
 
     /**
@@ -379,6 +381,30 @@ public class EmpUserCustomController extends BaseController {
         return "auth".equals(request.getParameter("op")) || request.getParameterMap().containsKey("userRoleString");
     }
 
+    /**
+     * 编辑用户时原生保存不会写角色,这里在保存成功后补写。
+     */
+    private void saveRolesOnEdit(EmpUser empUser, HttpServletRequest request, String result) {
+        if (!"edit".equals(request.getParameter("op"))) {
+            return;
+        }
+        if (!shouldSaveAuth(request) || !isSuccessResult(result)) {
+            return;
+        }
+        if (UserUtils.getSubject() == null || !UserUtils.getSubject().isPermitted("sys:empUser:authRole")) {
+            return;
+        }
+        userService.saveAuth(empUser);
+    }
+
+    private boolean isSuccessResult(String result) {
+        if (StringUtils.isBlank(result)) {
+            return false;
+        }
+        return StringUtils.contains(result, "\"result\":\"" + Global.TRUE + "\"")
+                || StringUtils.contains(result, "'result':'" + Global.TRUE + "'");
+    }
+
     @RequestMapping(value = "exportData")
     public void exportData(EmpUser empUser, String ctrlPermi, String ctrlPermiFilter, HttpServletResponse response) {
         applyOfficeTreeFilter(empUser, false);

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

@@ -44,7 +44,7 @@ public class MesLineProcessConsumeController extends BaseController {
 		return mesLineProcessConsumeService.get(id, isNewRecord);
 	}
 
-	@RequiresPermissions("mes:mesLineProcessConsume:view")
+	@RequiresPermissions("mes:mesLineProcess:view")
 	@RequestMapping(value = {"list", "bindList", ""})
 	public String list(MesLineProcessConsume mesLineProcessConsume, Model model) {
 		model.addAttribute("mesLineProcessConsume", mesLineProcessConsume);
@@ -52,7 +52,7 @@ public class MesLineProcessConsumeController extends BaseController {
 		return "modules/mes/mesLineProcessConsumeList";
 	}
 
-	@RequiresPermissions("mes:mesLineProcessConsume:view")
+	@RequiresPermissions("mes:mesLineProcess:view")
 	@RequestMapping(value = "listData")
 	@ResponseBody
 	public Page<MesLineProcessConsume> listData(MesLineProcessConsume mesLineProcessConsume,
@@ -61,7 +61,7 @@ public class MesLineProcessConsumeController extends BaseController {
 		return mesLineProcessConsumeService.findPage(mesLineProcessConsume);
 	}
 
-	@RequiresPermissions("mes:mesLineProcessConsume:view")
+	@RequiresPermissions("mes:mesLineProcess:view")
 	@RequestMapping(value = {"form", "bindForm"})
 	public String form(MesLineProcessConsume mesLineProcessConsume, Model model) {
 		model.addAttribute("mesLineProcessConsume", mesLineProcessConsume);
@@ -76,7 +76,7 @@ public class MesLineProcessConsumeController extends BaseController {
 		return "modules/mes/mesLineProcessConsumeForm";
 	}
 
-	@RequiresPermissions("mes:mesLineProcessConsume:edit")
+	@RequiresPermissions("mes:mesLineProcess:edit")
 	@PostMapping(value = "save")
 	@ResponseBody
 	public String save(@Validated MesLineProcessConsume mesLineProcessConsume) {
@@ -95,7 +95,7 @@ public class MesLineProcessConsumeController extends BaseController {
 		return renderResult(Global.TRUE, text("保存工位加工物料绑定成功!"));
 	}
 
-	@RequiresPermissions("mes:mesLineProcessConsume:edit")
+	@RequiresPermissions("mes:mesLineProcess:edit")
 	@RequestMapping(value = "disable")
 	@ResponseBody
 	public String disable(MesLineProcessConsume mesLineProcessConsume) {
@@ -104,7 +104,7 @@ public class MesLineProcessConsumeController extends BaseController {
 		return renderResult(Global.TRUE, text("停用成功"));
 	}
 
-	@RequiresPermissions("mes:mesLineProcessConsume:edit")
+	@RequiresPermissions("mes:mesLineProcess:edit")
 	@RequestMapping(value = "enable")
 	@ResponseBody
 	public String enable(MesLineProcessConsume mesLineProcessConsume) {
@@ -113,7 +113,7 @@ public class MesLineProcessConsumeController extends BaseController {
 		return renderResult(Global.TRUE, text("启用成功"));
 	}
 
-	@RequiresPermissions("mes:mesLineProcessConsume:edit")
+	@RequiresPermissions("mes:mesLineProcess:edit")
 	@RequestMapping(value = "delete")
 	@ResponseBody
 	public String delete(MesLineProcessConsume mesLineProcessConsume) {

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

@@ -6,6 +6,7 @@ import com.jeesite.common.lang.StringUtils;
 import com.jeesite.common.web.BaseController;
 import com.jeesite.modules.mes.entity.MesMaterialKcTotal;
 import com.jeesite.modules.mes.service.MesMaterialKcTotalService;
+import org.apache.shiro.authz.annotation.Logical;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
@@ -67,7 +68,7 @@ public class MesMaterialKcTotalController extends BaseController {
 	/**
 	 * 按物料查询当前总库存(工位绑物料表单用)
 	 */
-	@RequiresPermissions("mes:mesLineProcessConsume:view")
+	@RequiresPermissions(value = {"mes:mesLineProcessConsume:view", "mes:mesLineProcess:view"}, logical = Logical.OR)
 	@RequestMapping(value = "stockByMaterial")
 	@ResponseBody
 	public String stockByMaterial(String materialId) {

+ 4 - 3
src/main/java/com/jeesite/modules/mes/web/MesPdaScanRecordController.java

@@ -8,6 +8,7 @@ import com.jeesite.modules.mes.entity.MesPdaScanRecord;
 import com.jeesite.modules.mes.req.MesSearchDateReq;
 import com.jeesite.modules.mes.service.MesPdaScanRecordService;
 import com.jeesite.modules.mes.util.CommonUitl;
+import com.jeesite.modules.mes.util.Utf8DisplayUtils;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
@@ -51,11 +52,10 @@ public class MesPdaScanRecordController extends BaseController {
 	}
 
 	@RequiresPermissions("mes:mesPdaScanRecord:view")
-	@RequestMapping(value = "listData", produces = "application/json;charset=UTF-8")
+	@RequestMapping(value = "listData")
 	@ResponseBody
 	public Page<MesPdaScanRecord> listData(MesPdaScanRecord mesPdaScanRecord,
 			HttpServletRequest request, HttpServletResponse response) {
-		response.setCharacterEncoding("UTF-8");
 		// 列表按模糊查询,不影响扫码业务里的精确 findCount
 		likeIfPresent(mesPdaScanRecord, "sn", mesPdaScanRecord.getSn());
 		likeIfPresent(mesPdaScanRecord, "oprno", mesPdaScanRecord.getOprno());
@@ -69,10 +69,11 @@ public class MesPdaScanRecordController extends BaseController {
 		if (page.getList() != null) {
 			for (MesPdaScanRecord row : page.getList()) {
 				if (row != null) {
-					row.setUcode(row.getUcode());
+					row.setUcode(Utf8DisplayUtils.repair(row.getUcode()));
 				}
 			}
 		}
+		Utf8DisplayUtils.repairGraph(page);
 		return page;
 	}
 

+ 9 - 0
src/main/java/com/jeesite/modules/mes/web/MesProductQmController.java

@@ -14,6 +14,7 @@ import com.jeesite.modules.mes.entity.MesProduct;
 import com.jeesite.modules.mes.req.MesSearchDateReq;
 import com.jeesite.modules.mes.resp.CommonResp;
 import com.jeesite.modules.mes.util.CommonUitl;
+import com.jeesite.modules.mes.util.Utf8DisplayUtils;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.apache.tomcat.util.codec.binary.Base64;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -95,6 +96,14 @@ public class MesProductQmController extends BaseController {
 	public Page<MesProductQm> listData(MesProductQm mesProductQm, HttpServletRequest request, HttpServletResponse response) {
 		mesProductQm.setPage(new Page<>(request, response));
 		Page<MesProductQm> page = mesProductQmService.findPage(mesProductQm);
+		if (page.getList() != null) {
+			for (MesProductQm row : page.getList()) {
+				if (row != null) {
+					row.setWorkNum(Utf8DisplayUtils.repair(row.getWorkNum()));
+				}
+			}
+		}
+		Utf8DisplayUtils.repairGraph(page);
 		return page;
 	}
 

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

@@ -22,6 +22,7 @@ import com.jeesite.modules.mes.resp.CommonResp;
 import com.jeesite.modules.mes.resp.ParamsResp;
 import com.jeesite.modules.mes.service.*;
 import com.jeesite.modules.mes.util.CommonUitl;
+import com.jeesite.modules.mes.util.Utf8DisplayUtils;
 import com.jeesite.modules.sys.entity.User;
 import com.jeesite.modules.sys.utils.UserUtils;
 import com.jeesite.modules.utils.ErrorMsg;
@@ -329,8 +330,11 @@ public class MesProductRecordController extends BaseController {
 				if (row.getSerial() != null) {
 					row.setRemark("明细 " + row.getSerial() + " 条");
 				}
+				row.setUpdateBy(Utf8DisplayUtils.repair(row.getUpdateBy()));
+				row.setCreateBy(Utf8DisplayUtils.repair(row.getCreateBy()));
 			}
 		}
+		Utf8DisplayUtils.repairGraph(page);
 		return page;
 	}
 
@@ -374,11 +378,13 @@ public class MesProductRecordController extends BaseController {
 					MesLineProcess info = mesLineProcessService.findInfo(mesLineProcess);
 					oprnoTitleMap.put(key, ObjectUtils.isEmpty(info) ? "" : info.getTitle());
 				}
-				row.setOprnoTitle(oprnoTitleMap.get(key));
-			}
+			row.setOprnoTitle(oprnoTitleMap.get(key));
 		}
+		Utf8DisplayUtils.repairGraph(list);
+	}
 		page.setList(list);
 		page.setCount(list == null ? 0 : list.size());
+		Utf8DisplayUtils.repairGraph(page);
 		return page;
 	}
 
@@ -405,6 +411,7 @@ public class MesProductRecordController extends BaseController {
 		fillProductRecordListFields(list);
 		page.setList(list);
 		page.setCount(list == null ? 0 : list.size());
+		Utf8DisplayUtils.repairGraph(page);
 		return page;
 	}
 
@@ -425,6 +432,9 @@ public class MesProductRecordController extends BaseController {
 		}
 		Map<String, String> oprnoTitleMap = new HashMap<>();
 		for (MesProductRecord record : list) {
+			record.setCreateBy(Utf8DisplayUtils.repair(record.getCreateBy()));
+			record.setUpdateBy(Utf8DisplayUtils.repair(record.getUpdateBy()));
+			record.setMaterielSn(Utf8DisplayUtils.repair(record.getMaterielSn()));
 			MesTemplateItems mesTemplateItems = new MesTemplateItems();
 			mesTemplateItems.setId(record.getTid());
 			MesTemplateItems mt = mesTemplateItemsService.get(mesTemplateItems);

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

@@ -100,7 +100,7 @@ erp:
     # 直接调拨单 STK_TransferDirect(固定 HEV / 115 物料,外发调拨)
     transfer-direct:
       bill-type: ZJDB01_SYS
-      material-numbers: 8.10.HEV.10.047696,8.35.115.10.5003146600021
+      material-numbers: 8.10.HEV.10.047696,8.10.HEV.10.049025,8.35.115.10.5003146600021
       transfer-biz-type: InnerOrgTransfer
       transfer-direct-type: GENERAL
       transfer-category: "3"
@@ -112,7 +112,7 @@ erp:
       default-dest-stock-number:
     # 生产入库单 PRD_INSTOCK 查询(固定 HEV / 115 物料,与直接调拨单一致)
     prd-in-stock:
-      material-numbers: 8.10.HEV.10.047696,8.35.115.10.5003146600021
+      material-numbers: 8.10.HEV.10.047696,8.10.HEV.10.049025,8.35.115.10.5003146600021
       # Save 配置(单据类型/入库类型需与 ERP 账套一致,Save 报错时可调整)
       bill-type: SCRKD01_SYS
       in-stock-type: "1"
@@ -136,7 +136,11 @@ server:
   servlet:
     context-path: /js
     register-default-servlet: false
-  #    encoding.enabled: true
+    encoding:
+      charset: UTF-8
+      enabled: true
+      force-request: true
+      force-response: false
   tomcat:
     uri-encoding: UTF-8
     # 表单/文件上传请求体最大大小(须大于客户端安装包,与 file.maxFileSize 保持一致)

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

@@ -14,7 +14,8 @@
 		<div class="box-body">
 			<div class="alert alert-info" style="margin-bottom:10px;">
 				<i class="fa fa-info-circle"></i>
-				${text('仅展示物料')} <strong>8.10.HEV.10.047696(HEV)</strong> ${text('与')}
+				${text('仅展示物料')} <strong>8.10.HEV.10.047696(HEV)</strong>、
+				<strong>8.10.HEV.10.049025(HEV)</strong> ${text('与')}
 				<strong>8.35.115.10.5003146600021(115)</strong> ${text('的生产入库明细分录,并按当前组织过滤。')}
 			</div>
 			<#form:form id="searchForm" model="${erpPrdInStock}" action="${ctx}/mes/erpPrdInStock/listData" method="post" class="form-inline hide"
@@ -37,6 +38,7 @@
 						<select name="materialNumberQuery" id="materialNumberQuery" class="form-control width-260">
 							<option value="">${text('全部(HEV+115)')}</option>
 							<option value="8.10.HEV.10.047696">8.10.HEV.10.047696(HEV)</option>
+							<option value="8.10.HEV.10.049025">8.10.HEV.10.049025(HEV)</option>
 							<option value="8.35.115.10.5003146600021">8.35.115.10.5003146600021(115)</option>
 						</select>
 					</div>

+ 66 - 66
src/main/resources/views/modules/mes/erpPrdInStockSelectDetail.html

@@ -1,66 +1,66 @@
-<% 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-list"></i> ${text('增加生产入库 - 选择订单明细')}:${erpProductionOrder.billNo}

-			</div>

-			<div class="box-tools pull-right">

-				<a href="${ctx}/mes/erpPrdInStock/selectOrder" class="btn btn-default addTabPage" title="${text('返回')}"><i class="fa fa-mail-reply"></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">

-			<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/erpPrdInStock/selectDetailData" 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:120, align:"center", frozen:true, formatter: function(val, obj, row){

-			var disabled = row.remainQty != null && parseFloat(row.remainQty) <= 0;

-			if (disabled) {

-				return '<span class="text-muted" title="${text("该分录已无未入库数量")}">${text("已满入")}</span>';

-			}

-			return '<a href="${ctx}/mes/erpPrdInStock/form?moBillNo='+encodeURIComponent(row.billNo)+'&entryId='+encodeURIComponent(row.entryId)+'" class="btnList addTabPage" data-title="${text("新增生产入库")}">'+

-				'<i class="fa fa-check"></i> ${text("选批次入库")}</a>';

-		}},

-		{header:'${text("行号")}', name:'seq', index:'seq', width:70, align:"center", frozen:true},

-		{header:'${text("物料编码")}', name:'materialNumber', index:'materialNumber', width:200, align:"left"},

-		{header:'${text("物料名称")}', name:'materialName', index:'materialName', width:160, align:"left"},

-		{header:'${text("规格型号")}', name:'specification', index:'specification', width:120, align:"left"},

-		{header:'${text("存货类别")}', name:'inventoryCategory', index:'inventoryCategory', width:100, align:"center"},

-		{header:'${text("订单数量")}', name:'qty', index:'qty', width:100, align:"right", formatter: formatErpQty},

-		{header:'${text("已入库")}', name:'instockQty', index:'instockQty', width:100, align:"right", formatter: formatErpQty},

-		{header:'${text("未入库")}', name:'remainQty', index:'remainQty', width:100, align:"right", formatter: function(val){

-			if (val == null || val === '') return '';

-			var num = parseFloat(val);

-			var text = formatErpQty(val);

-			return num > 0 ? '<strong class="text-danger">'+text+'</strong>' : text;

-		}},

-		{header:'${text("单位")}', name:'unitName', index:'unitName', width:70, align:"center"},

-		{header:'${text("生产车间")}', name:'workshopName', index:'workshopName', width:120, align:"left"},

-		{header:'${text("入库仓库")}', name:'stockName', index:'stockName', width:120, align:"left"},

-		{header:'${text("业务状态")}', name:'businessStatusName', index:'businessStatusName', width:90, align:"center"},

-		{header:'${text("计划开工")}', name:'planStartDate', index:'planStartDate', width:110, align:"center"},

-		{header:'${text("计划完工")}', name:'planFinishDate', index:'planFinishDate', width:110, align:"center"},

-		{header:'${text("BOM")}', name:'bomNumber', index:'bomNumber', width:120, align:"left"},

-		{header:'${text("工艺路线")}', name:'routingNumber', index:'routingNumber', width:120, align:"left"},

-		{header:'${text("项目号")}', name:'projectNo', index:'projectNo', width:100, align:"left"}

-	],

-	ajaxSuccess: function(){}

-});

-</script>

+<% 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-list"></i> ${text('增加生产入库 - 选择订单明细')}:${erpProductionOrder.billNo}
+			</div>
+			<div class="box-tools pull-right">
+				<a href="${ctx}/mes/erpPrdInStock/selectOrder" class="btn btn-default addTabPage" title="${text('返回')}"><i class="fa fa-mail-reply"></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">
+			<div class="alert alert-info" style="margin-bottom:10px;">
+				<i class="fa fa-info-circle"></i>
+				${text('仅展示配置物料分录')}(8.10.HEV.10.047696 / 8.10.HEV.10.049025 / 8.35.115.10.5003146600021)。
+				${text('已入库=ERP 已审核生产入库实收合计;未入库=订单数量-已入库。')}
+			</div>
+			<#form:form id="searchForm" model="${erpProductionOrder}" action="${ctx}/mes/erpPrdInStock/selectDetailData" 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:120, align:"center", frozen:true, formatter: function(val, obj, row){
+			var disabled = row.remainQty != null && parseFloat(row.remainQty) <= 0;
+			if (disabled) {
+				return '<span class="text-muted" title="${text("该分录已无未入库数量")}">${text("已满入")}</span>';
+			}
+			return '<a href="${ctx}/mes/erpPrdInStock/form?moBillNo='+encodeURIComponent(row.billNo)+'&entryId='+encodeURIComponent(row.entryId)+'" class="btnList addTabPage" data-title="${text("新增生产入库")}">'+
+				'<i class="fa fa-check"></i> ${text("选批次入库")}</a>';
+		}},
+		{header:'${text("行号")}', name:'seq', index:'seq', width:70, align:"center", frozen:true},
+		{header:'${text("物料编码")}', name:'materialNumber', index:'materialNumber', width:200, align:"left"},
+		{header:'${text("物料名称")}', name:'materialName', index:'materialName', width:160, align:"left"},
+		{header:'${text("规格型号")}', name:'specification', index:'specification', width:120, align:"left"},
+		{header:'${text("存货类别")}', name:'inventoryCategory', index:'inventoryCategory', width:100, align:"center"},
+		{header:'${text("订单数量")}', name:'qty', index:'qty', width:100, align:"right", formatter: formatErpQty},
+		{header:'${text("已入库")}', name:'instockQty', index:'instockQty', width:100, align:"right", formatter: formatErpQty},
+		{header:'${text("未入库")}', name:'remainQty', index:'remainQty', width:100, align:"right", formatter: function(val){
+			if (val == null || val === '') return '';
+			var num = parseFloat(val);
+			var text = formatErpQty(val);
+			return num > 0 ? '<strong class="text-danger">'+text+'</strong>' : text;
+		}},
+		{header:'${text("单位")}', name:'unitName', index:'unitName', width:70, align:"center"},
+		{header:'${text("生产车间")}', name:'workshopName', index:'workshopName', width:120, align:"left"},
+		{header:'${text("入库仓库")}', name:'stockName', index:'stockName', width:120, align:"left"},
+		{header:'${text("业务状态")}', name:'businessStatusName', index:'businessStatusName', width:90, align:"center"},
+		{header:'${text("计划开工")}', name:'planStartDate', index:'planStartDate', width:110, align:"center"},
+		{header:'${text("计划完工")}', name:'planFinishDate', index:'planFinishDate', width:110, align:"center"},
+		{header:'${text("BOM")}', name:'bomNumber', index:'bomNumber', width:120, align:"left"},
+		{header:'${text("工艺路线")}', name:'routingNumber', index:'routingNumber', width:120, align:"left"},
+		{header:'${text("项目号")}', name:'projectNo', index:'projectNo', width:100, align:"left"}
+	],
+	ajaxSuccess: function(){}
+});
+</script>

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

@@ -13,7 +13,8 @@
 		<div class="box-body">
 			<div class="alert alert-info" style="margin-bottom:10px;">
 				<i class="fa fa-info-circle"></i>
-				${text('仅展示配置物料')} <strong>8.10.HEV.10.047696(HEV)</strong> ${text('与')}
+				${text('仅展示配置物料')} <strong>8.10.HEV.10.047696(HEV)</strong>、
+				<strong>8.10.HEV.10.049025(HEV)</strong> ${text('与')}
 				<strong>8.35.115.10.5003146600021(115)</strong> ${text('的生产订单;再选分录、勾选 MES 批次码后推 ERP 生产入库。')}
 			</div>
 			<#form:form id="searchForm" model="${erpProductionOrder}" action="${ctx}/mes/erpPrdInStock/selectOrderData" method="post" class="form-inline hide"

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

@@ -27,7 +27,7 @@
 					<select id="materialNumber" class="form-control" style="max-width:480px;">
 						<option value="">${text('请选择')}</option>
 						<% for(mat in materialNumbers){ %>
-						<% if(mat == '8.10.HEV.10.047696'){ %>
+						<% if(mat.contains('.HEV.')){ %>
 						<option value="${mat}">${mat}(HEV)</option>
 						<% }else if(mat == '8.35.115.10.5003146600021'){ %>
 						<option value="${mat}">${mat}(115)</option>
@@ -110,7 +110,7 @@
 					<input type="hidden" id="lotNumber" value=""/>
 					<input type="hidden" id="lotMaxQty" value=""/>
 					<div class="input-group" style="max-width:480px;">
-						<input type="text" id="lotNumberDisplay" class="form-control" readonly placeholder="${text('选择托盘后自动带出,也可手工选批号')}"/>
+						<input type="text" id="lotNumberDisplay" class="form-control" readonly placeholder="${text('请从 ERP 库存选择批号')}"/>
 						<span class="input-group-btn">
 							<button type="button" class="btn btn-default" id="btnPickLot"><i class="fa fa-search"></i> ${text('选批号')}</button>
 						</span>
@@ -246,12 +246,6 @@ window.applyTransferTrays = function(payload) {
 		: '${text("仅可选择发货检合格的托盘,数量按绑定数量累加")}');
 	$('#transferQty').val(qty > 0 ? qty : '');
 	renderSelectedTrays(items);
-	var lotNumber = payload && payload.lotNumber ? $.trim(payload.lotNumber) : '';
-	if (lotNumber) {
-		$('#lotNumber').val(lotNumber);
-		$('#lotNumberDisplay').val(lotNumber);
-		$('#lotHint').text('批号来自所选托盘 ' + lotNumber);
-	}
 };
 
 function clearUnitSelection() {

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

@@ -14,7 +14,8 @@
 		<div class="box-body">
 			<div class="alert alert-info" style="margin-bottom:10px;">
 				<i class="fa fa-info-circle"></i>
-				${text('仅展示物料')} <strong>8.10.HEV.10.047696(HEV)</strong> ${text('与')}
+				${text('仅展示物料')} <strong>8.10.HEV.10.047696(HEV)</strong>、
+				<strong>8.10.HEV.10.049025(HEV)</strong> ${text('与')}
 				<strong>8.35.115.10.5003146600021(115)</strong> ${text('的直接调拨明细分录,并按当前组织过滤。')}
 			</div>
 			<#form:form id="searchForm" model="${erpTransferDirect}" action="${ctx}/mes/erpTransferDirect/listData" method="post" class="form-inline hide"
@@ -31,6 +32,7 @@
 						<select name="materialNumberQuery" id="materialNumberQuery" class="form-control width-260">
 							<option value="">${text('全部(HEV+115)')}</option>
 							<option value="8.10.HEV.10.047696">8.10.HEV.10.047696(HEV)</option>
+							<option value="8.10.HEV.10.049025">8.10.HEV.10.049025(HEV)</option>
 							<option value="8.35.115.10.5003146600021">8.35.115.10.5003146600021(115)</option>
 						</select>
 					</div>

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

@@ -14,7 +14,8 @@
 		<div class="box-body">
 			<div class="alert alert-info" style="margin-bottom:10px;">
 				<i class="fa fa-info-circle"></i>
-				${text('仅展示配置物料')} <strong>8.10.HEV.10.047696(HEV)</strong> ${text('与')}
+				${text('仅展示配置物料')} <strong>8.10.HEV.10.047696(HEV)</strong>、
+				<strong>8.10.HEV.10.049025(HEV)</strong> ${text('与')}
 				<strong>8.35.115.10.5003146600021(115)</strong> ${text('的生产订单(与生产入库一致);')}
 				${text('选单后加载该订单 HEV/115 生产入库行作为调拨批号来源。')}
 			</div>

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

@@ -161,19 +161,6 @@ $('#btnConfirmTray').on('click', function() {
 		js.showMessage('${text("请勾选托盘码")}');
 		return;
 	}
-	var lot = '';
-	for (var i = 0; i < items.length; i++) {
-		var cur = $.trim(items[i].erpLotNumber);
-		if (!cur) {
-			continue;
-		}
-		if (!lot) {
-			lot = cur;
-		} else if (lot !== cur) {
-			js.showMessage('${text("所选托盘 ERP 批号不一致,不能提交同一张调拨单")}');
-			return;
-		}
-	}
 	var opener = getApplyTransferTraysWindow(parent)
 		|| getApplyTransferTraysWindow(top)
 		|| window.opener;
@@ -182,8 +169,7 @@ $('#btnConfirmTray').on('click', function() {
 		return;
 	}
 	opener.applyTransferTrays({
-		items: items,
-		lotNumber: lot
+		items: items
 	});
 	closeSelectPage();
 });

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

@@ -57,7 +57,7 @@
 			<div class="box-footer">
 				<div class="row">
 					<div class="col-sm-offset-2 col-sm-10">
-						<% if (hasPermi('mes:mesLineProcessConsume:edit')){ %>
+						<% if (hasPermi('mes:mesLineProcess:edit')){ %>
 							<button type="submit" class="btn btn-sm btn-primary" id="btnSubmit"><i class="fa fa-check"></i> ${text('保 存')}</button>&nbsp;
 						<% } %>
 						<button type="button" class="btn btn-sm btn-default" id="btnCancel" onclick="js.closeCurrentTabPage()"><i class="fa fa-reply-all"></i> ${text('关 闭')}</button>

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

@@ -11,7 +11,7 @@
 			</div>
 			<div class="box-tools pull-right">
 				<a href="#" class="btn btn-default" id="btnSearch" title="${text('查询')}"><i class="fa fa-filter"></i> ${text('查询')}</a>
-				<% if(hasPermi('mes:mesLineProcessConsume:edit')){ %>
+				<% if(hasPermi('mes:mesLineProcess:edit')){ %>
 					<a href="${ctx}/mes/mesLineProcessConsume/form?lineProcessId=${mesLineProcessConsume.lineProcessId}" class="btn btn-default btnTool" title="${text('绑定物料')}"><i class="fa fa-plus"></i> ${text('绑定物料')}</a>
 				<% } %>
 				<a href="#" class="btn btn-default" id="btnSetting" title="${text('设置')}"><i class="fa fa-navicon"></i></a>
@@ -72,7 +72,7 @@ $('#dataGrid').dataGrid({
 		}},
 		{header:'${text("操作")}', name:'actions', width:140, formatter: function(val, obj, row, act){
 			var actions = [];
-			//<% if(hasPermi('mes:mesLineProcessConsume:edit')){ %>
+			//<% if(hasPermi('mes:mesLineProcess:edit')){ %>
 				actions.push('<a href="${ctx}/mes/mesLineProcessConsume/form?id='+row.id+'" class="btnList" title="${text("编辑")}"><i class="fa fa-pencil"></i></a>&nbsp;');
 				if (row.status == Global.STATUS_NORMAL){
 					actions.push('<a href="${ctx}/mes/mesLineProcessConsume/disable?id='+row.id+'" class="btnList" title="${text("停用")}" data-confirm="${text("确认要停用该绑定吗?")}"><i class="glyphicon glyphicon-ban-circle"></i></a>&nbsp;');

+ 49 - 0
src/main/resources/views/modules/sys/user/empUserForm.html

@@ -152,6 +152,37 @@
 						</div>
 					</div>
 				</div>
+				<% if(op == 'edit') { %>
+				<div class="col-xs-6">
+					<div class="form-group">
+						<label class="control-label col-sm-4" title="">
+							<span class="required hide">*</span> ${text('角色列表')}:<i class="fa icon-question hide"></i></label>
+						<div class="col-sm-8">
+							<% if (hasPermi('sys:empUser:authRole')){ %>
+							<select id="userRoleSelect" name="userRoleSelect" multiple="multiple" class="form-control">
+								<% for (role in allRoleList!){ %>
+								<option value="${role.roleCode}"
+									<% for (ur in userRoleList!){ %><% if(ur.roleCode == role.roleCode){ %>selected="selected"<% } %><% } %>
+									>${role.roleName}</option>
+								<% } %>
+							</select>
+							<input type="hidden" id="userRoleString" name="userRoleString" value="${userRoleString!}"/>
+							<span class="help-block">${text('可多选,多个角色会同时列出')}</span>
+							<% } else { %>
+							<div class="form-control-static">
+								<% if(userRoleList.~size > 0){ %>
+									<% for (role in userRoleList){ %>
+									<span class="label label-info" style="display:inline-block;margin:0 4px 4px 0;">${role.roleName}</span>
+									<% } %>
+								<% } else { %>
+									${text('无')}
+								<% } %>
+							</div>
+							<% } %>
+						</div>
+					</div>
+				</div>
+				<% } %>
 			</div>
 			<div class="row">
 				<div class="col-xs-12">
@@ -250,6 +281,18 @@
 
 		}
 	});
+	//<% if(hasPermi('sys:empUser:authRole') && op == 'edit') { %>
+	(function(){
+		var selectedCodes = [];
+		//<% for (role in userRoleList!){ %>
+		selectedCodes.push('${role.roleCode}');
+		//<% } %>
+		if ($('#userRoleSelect').length) {
+			js.select2($('#userRoleSelect'));
+			$('#userRoleSelect').val(selectedCodes).trigger('change');
+		}
+	})();
+	//<% } %>
 	//<% if(hasPermi('sys:empUser:authRole') && (op == 'add' || op == 'auth')) { %>
 	// 加载角色列表
 	var roleGrid = $("#roleGrid").dataGrid({
@@ -292,6 +335,12 @@
 			//<% if(hasPermi('sys:empUser:authRole') && (op == 'add' || op == 'auth')) { %>
 			$("#userRoleString").val(roleGrid.dataGrid('getSelectRows').join(','));
 			//<% } %>
+			//<% if(hasPermi('sys:empUser:authRole') && op == 'edit') { %>
+			if ($('#userRoleSelect').length) {
+				var selectedRoles = $('#userRoleSelect').val();
+				$('#userRoleString').val(selectedRoles && selectedRoles.length ? selectedRoles.join(',') : '');
+			}
+			//<% } %>
 			var empNo = $('#employee_empNo').val();
 			if (empNo == ''){
 				$('#employee_empNo').val($('#loginCode').val());