Quellcode durchsuchen

Add station client JAR upgrades and backfill DBJ main SN from later binds.

Station clients can check and download JAR packages by line/oprno, and later OK records write the main workpiece onto historical DBJ rows.

Co-authored-by: Cursor <cursoragent@cursor.com>
hou vor 2 Wochen
Ursprung
Commit
01b0fac998

+ 9 - 0
db/mysql/alter_mes_client_upgrade_version.sql

@@ -0,0 +1,9 @@
+-- 客户端版本号改为 0.0.0 格式
+ALTER TABLE `mes_client_upgrade`
+  MODIFY COLUMN `version` varchar(32) NULL DEFAULT NULL COMMENT 'version';
+
+UPDATE `mes_client_upgrade`
+SET `version` = CONCAT('0.0.', `version`)
+WHERE `version` IS NOT NULL
+  AND `version` <> ''
+  AND `version` NOT LIKE '%.%';

+ 39 - 0
db/mysql/create_mes_client_upgrade.sql

@@ -0,0 +1,39 @@
+-- 工位客户端 JAR/EXE 升级包
+CREATE TABLE IF NOT EXISTS `mes_client_upgrade` (
+  `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'id',
+  `version` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'version',
+  `line_sn` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'line sn',
+  `oprno` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'station oprno',
+  `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT 'update content',
+  `state` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT 'state',
+  `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'create by',
+  `create_date` datetime NOT NULL COMMENT 'create date',
+  `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'update by',
+  `update_date` datetime NOT NULL COMMENT 'update date',
+  PRIMARY KEY (`id`) USING BTREE,
+  KEY `idx_mes_client_upgrade_match` (`state`, `line_sn`, `oprno`, `version`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'client upgrade package' ROW_FORMAT = DYNAMIC;
+
+-- 系统设置 / 客户端版本上传(可从工位管理「JAR管理」进入,菜单用于全局包)
+INSERT INTO `js_sys_menu`
+SELECT
+'2092800000000000001', '1582259828007895040', '0,1582259822626603008,1582259828007895040,',
+160, '0000009000,0000000500,0000000160,', '1', 2,
+'系统管理/系统设置/客户端版本上传', '客户端版本上传', '1', '/mes/mesClientUpgrade/list',
+'', '', '', '', 'mes:mesLineProcess:view,mes:mesLineProcess:edit',
+40, '1', 'default', 'core', NULL, NULL, '0',
+'system', NOW(), 'system', NOW(),
+'', '', '', '', '', '', '', '', '',
+NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM `js_sys_menu` WHERE `menu_code` = '2092800000000000001');
+
+INSERT INTO `js_sys_role_menu` (`role_code`, `menu_code`)
+SELECT rm.`role_code`, '2092800000000000001'
+FROM `js_sys_role_menu` rm
+WHERE rm.`menu_code` = '1669223760170397696'
+  AND NOT EXISTS (
+    SELECT 1 FROM `js_sys_role_menu` x
+    WHERE x.`role_code` = rm.`role_code`
+      AND x.`menu_code` = '2092800000000000001'
+  );

+ 12 - 0
src/main/java/com/jeesite/modules/mes/dao/MesClientUpgradeDao.java

@@ -0,0 +1,12 @@
+package com.jeesite.modules.mes.dao;
+
+import java.util.List;
+
+import com.jeesite.common.dao.CrudDao;
+import com.jeesite.common.mybatis.annotation.MyBatisDao;
+import com.jeesite.modules.mes.entity.MesClientUpgrade;
+
+@MyBatisDao
+public interface MesClientUpgradeDao extends CrudDao<MesClientUpgrade> {
+	List<MesClientUpgrade> findVersionCandidates(MesClientUpgrade mesClientUpgrade);
+}

+ 5 - 0
src/main/java/com/jeesite/modules/mes/dao/MesProductDbjRecordDao.java

@@ -3,6 +3,7 @@ package com.jeesite.modules.mes.dao;
 import com.jeesite.common.dao.CrudDao;
 import com.jeesite.common.mybatis.annotation.MyBatisDao;
 import com.jeesite.modules.mes.entity.MesProductDbjRecord;
+import org.apache.ibatis.annotations.Param;
 
 /**
  * 单部件加工记录Dao
@@ -10,4 +11,8 @@ import com.jeesite.modules.mes.entity.MesProductDbjRecord;
 @MyBatisDao
 public interface MesProductDbjRecordDao extends CrudDao<MesProductDbjRecord> {
 
+	/**
+	 * 按精追码从后续工序加工记录中反查主工件码
+	 */
+	String findMainSnByPartSn(@Param("partSn") String partSn);
 }

+ 97 - 0
src/main/java/com/jeesite/modules/mes/entity/MesClientUpgrade.java

@@ -0,0 +1,97 @@
+package com.jeesite.modules.mes.entity;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+
+import com.jeesite.common.entity.DataEntity;
+import com.jeesite.common.mybatis.annotation.Column;
+import com.jeesite.common.mybatis.annotation.Table;
+import com.jeesite.common.mybatis.mapper.query.QueryType;
+
+@Table(name="mes_client_upgrade", alias="a", label="client upgrade", columns={
+		@Column(name="id", attrName="id", label="id", isPK=true),
+		@Column(name="version", attrName="version", label="version", isUpdateForce=true),
+		@Column(name="line_sn", attrName="lineSn", label="lineSn", queryType=QueryType.LIKE),
+		@Column(name="oprno", attrName="oprno", label="oprno", queryType=QueryType.LIKE),
+		@Column(name="content", attrName="content", label="content", queryType=QueryType.LIKE),
+		@Column(name="state", attrName="state", label="state"),
+		@Column(name="create_by", attrName="createBy", label="createBy", isUpdate=false, isQuery=false),
+		@Column(name="create_date", attrName="createDate", label="createDate", isUpdate=false, isQuery=false),
+		@Column(name="update_by", attrName="updateBy", label="updateBy", isQuery=false),
+		@Column(name="update_date", attrName="updateDate", label="updateDate", isQuery=false),
+	}, orderBy="a.update_date DESC"
+)
+public class MesClientUpgrade extends DataEntity<MesClientUpgrade> {
+
+	private static final long serialVersionUID = 1L;
+	private String version;
+	private String lineSn;
+	private String oprno;
+	private String content;
+	private String state;
+	private String path;
+
+	public MesClientUpgrade() {
+		this(null);
+	}
+
+	public MesClientUpgrade(String id) {
+		super(id);
+	}
+
+	@NotBlank(message="版本号不能为空")
+	@Pattern(regexp="^\\d+\\.\\d+\\.\\d+$", message="版本号格式为 0.0.0")
+	@Size(min=0, max=32)
+	public String getVersion() {
+		return version;
+	}
+
+	public void setVersion(String version) {
+		this.version = version;
+	}
+
+	@Size(min=0, max=100)
+	public String getLineSn() {
+		return lineSn;
+	}
+
+	public void setLineSn(String lineSn) {
+		this.lineSn = lineSn;
+	}
+
+	@Size(min=0, max=255)
+	public String getOprno() {
+		return oprno;
+	}
+
+	public void setOprno(String oprno) {
+		this.oprno = oprno;
+	}
+
+	public String getContent() {
+		return content;
+	}
+
+	public void setContent(String content) {
+		this.content = content;
+	}
+
+	@NotBlank
+	@Size(min=0, max=1)
+	public String getState() {
+		return state;
+	}
+
+	public void setState(String state) {
+		this.state = state;
+	}
+
+	public String getPath() {
+		return path;
+	}
+
+	public void setPath(String path) {
+		this.path = path;
+	}
+}

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

@@ -102,6 +102,14 @@ public class MesLineProcess extends DataEntity<MesLineProcess> {
 	private String userProcessString;
 	private String line;
 	private List<MesLineProcessUser> mesLineProcessUserList = ListUtils.newArrayList();		// 子表列表
+
+	public MesLineProcess() {
+		this(null);
+	}
+
+	public MesLineProcess(String id) {
+		super(id);
+	}
 	
 	@Size(min=0, max=64, message="生产线id长度不能超过 64 个字符")
 	public String getLineId() {

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

@@ -0,0 +1,99 @@
+package com.jeesite.modules.mes.service;
+
+import java.util.List;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.service.CrudService;
+import com.jeesite.modules.file.utils.FileUploadUtils;
+import com.jeesite.modules.mes.dao.MesClientUpgradeDao;
+import com.jeesite.modules.mes.entity.MesClientUpgrade;
+import com.jeesite.modules.mes.util.ClientVersionUtils;
+
+@Service
+public class MesClientUpgradeService extends CrudService<MesClientUpgradeDao, MesClientUpgrade> {
+
+	@Override
+	public MesClientUpgrade get(MesClientUpgrade mesClientUpgrade) {
+		return super.get(mesClientUpgrade);
+	}
+
+	@Override
+	public Page<MesClientUpgrade> findPage(MesClientUpgrade mesClientUpgrade) {
+		return super.findPage(mesClientUpgrade);
+	}
+
+	@Override
+	public List<MesClientUpgrade> findList(MesClientUpgrade mesClientUpgrade) {
+		return super.findList(mesClientUpgrade);
+	}
+
+	@Override
+	@Transactional
+	public void save(MesClientUpgrade mesClientUpgrade) {
+		super.save(mesClientUpgrade);
+		FileUploadUtils.saveFileUpload(mesClientUpgrade, mesClientUpgrade.getId(), "mesClientUpgrade_file");
+	}
+
+	@Override
+	@Transactional
+	public void updateStatus(MesClientUpgrade mesClientUpgrade) {
+		super.updateStatus(mesClientUpgrade);
+	}
+
+	@Override
+	@Transactional
+	public void delete(MesClientUpgrade mesClientUpgrade) {
+		super.delete(mesClientUpgrade);
+	}
+
+	public MesClientUpgrade findLatestVersion(String lineSn, String oprno) {
+		MesClientUpgrade mesClientUpgrade = new MesClientUpgrade();
+		mesClientUpgrade.setState("0");
+		mesClientUpgrade.setLineSn(trimToNull(lineSn));
+		mesClientUpgrade.setOprno(trimToNull(oprno));
+		List<MesClientUpgrade> candidates = dao.findVersionCandidates(mesClientUpgrade);
+		if (candidates == null || candidates.isEmpty()) {
+			return null;
+		}
+		String matchLineSn = trimToNull(lineSn);
+		String matchOprno = trimToNull(oprno);
+		MesClientUpgrade specificBest = null;
+		MesClientUpgrade globalBest = null;
+		for (MesClientUpgrade item : candidates) {
+			boolean specific = matchLineSn != null && matchOprno != null
+					&& matchLineSn.equals(item.getLineSn()) && matchOprno.equals(item.getOprno());
+			if (specific) {
+				specificBest = newerRecord(specificBest, item);
+			} else {
+				globalBest = newerRecord(globalBest, item);
+			}
+		}
+		return specificBest != null ? specificBest : globalBest;
+	}
+
+	private MesClientUpgrade newerRecord(MesClientUpgrade current, MesClientUpgrade candidate) {
+		if (current == null) {
+			return candidate;
+		}
+		int cmp = ClientVersionUtils.compare(candidate.getVersion(), current.getVersion());
+		if (cmp > 0) {
+			return candidate;
+		}
+		if (cmp == 0 && candidate.getCreateDate() != null && current.getCreateDate() != null
+				&& candidate.getCreateDate().after(current.getCreateDate())) {
+			return candidate;
+		}
+		return current;
+	}
+
+	private String trimToNull(String value) {
+		if (value == null) {
+			return null;
+		}
+		value = value.trim();
+		return value.length() == 0 ? null : value;
+	}
+}

+ 95 - 4
src/main/java/com/jeesite/modules/mes/service/MesProductDbjRecordService.java

@@ -40,12 +40,18 @@ public class MesProductDbjRecordService extends CrudService<MesProductDbjRecordD
 
 	@Override
 	public MesProductDbjRecord get(MesProductDbjRecord mesProductDbjRecord) {
-		return super.get(mesProductDbjRecord);
+		MesProductDbjRecord record = super.get(mesProductDbjRecord);
+		if (record != null) {
+			backfillMissingDbjSn(java.util.Collections.singletonList(record));
+		}
+		return record;
 	}
 
 	@Override
 	public Page<MesProductDbjRecord> findPage(MesProductDbjRecord mesProductDbjRecord) {
-		return super.findPage(mesProductDbjRecord);
+		Page<MesProductDbjRecord> page = super.findPage(mesProductDbjRecord);
+		backfillMissingDbjSn(page.getList());
+		return page;
 	}
 
 	@Override
@@ -102,8 +108,8 @@ public class MesProductDbjRecordService extends CrudService<MesProductDbjRecordD
 		record.setLineSn(lineSn);
 		record.setOprno(oldOprno);
 		record.setSn(StringUtils.isEmpty(partSn) ? null : partSn);
-		// 单部件工位仅扫精追码,主工件码在后续工序绑定后再回写
-		record.setDbjSn(null);
+		// 单部件工位仅扫精追码;若后续工序已绑定主工件,则直接带上
+		record.setDbjSn(StringUtils.isEmpty(partSn) ? null : resolveMainSn(partSn));
 		record.setCraft(StringUtils.isEmpty(craft) ? "100000" : craft);
 		record.setResult(result);
 		record.setMaterielSn(materielSn);
@@ -161,6 +167,9 @@ public class MesProductDbjRecordService extends CrudService<MesProductDbjRecordD
 		query.setSn(partSn);
 		List<MesProductDbjRecord> records = findList(query);
 		for (MesProductDbjRecord record : records) {
+			if (!partSn.equals(record.getSn())) {
+				continue;
+			}
 			if (StringUtils.isEmpty(record.getDbjSn())) {
 				record.setDbjSn(dbjSn);
 				super.save(record);
@@ -169,6 +178,88 @@ public class MesProductDbjRecordService extends CrudService<MesProductDbjRecordD
 	}
 
 	/**
+	 * 后续整件工位质量 OK 时,把绑定的精追码关联到主工件并回写历史记录
+	 */
+	@Transactional
+	public void bindFromLaterProcess(String mainSn, String oprno, List<BindMaterialResp> bmlists) {
+		if (StringUtils.isEmpty(mainSn) || ListUtils.isEmpty(bmlists)) {
+			return;
+		}
+		for (BindMaterialResp bindMaterialResp : bmlists) {
+			String partSn = StringUtils.trimToEmpty(bindMaterialResp.getBatchSn());
+			if (StringUtils.isEmpty(partSn) || !hasDbjRecord(partSn)) {
+				continue;
+			}
+			saveBindIfAbsent(mainSn, oprno, partSn);
+			backfillDbjSn(partSn, mainSn);
+		}
+	}
+
+	private boolean hasDbjRecord(String partSn) {
+		MesProductDbjRecord query = new MesProductDbjRecord();
+		query.setSn(partSn);
+		List<MesProductDbjRecord> records = findList(query);
+		if (ListUtils.isEmpty(records)) {
+			return false;
+		}
+		for (MesProductDbjRecord record : records) {
+			if (partSn.equals(record.getSn())) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	private void saveBindIfAbsent(String mainSn, String oprno, String partSn) {
+		MesProductDbjBind query = new MesProductDbjBind();
+		query.setDbjSn(mainSn);
+		query.setMaterielSn(partSn);
+		if (!ListUtils.isEmpty(mesProductDbjBindService.findList(query))) {
+			return;
+		}
+		MesProductDbjBind bind = new MesProductDbjBind();
+		bind.setDbjSn(mainSn);
+		bind.setOprno(oprno);
+		bind.setMaterielSn(partSn);
+		mesProductDbjBindService.save(bind);
+	}
+
+	/**
+	 * 历史记录未回写时,按绑定表 / 后续工序物料码补主工件码
+	 */
+	private void backfillMissingDbjSn(List<MesProductDbjRecord> records) {
+		if (ListUtils.isEmpty(records)) {
+			return;
+		}
+		for (MesProductDbjRecord record : records) {
+			if (record == null || StringUtils.isNotEmpty(record.getDbjSn()) || StringUtils.isEmpty(record.getSn())) {
+				continue;
+			}
+			String mainSn = resolveMainSn(record.getSn());
+			if (StringUtils.isEmpty(mainSn)) {
+				continue;
+			}
+			record.setDbjSn(mainSn);
+			super.save(record);
+		}
+	}
+
+	private String resolveMainSn(String partSn) {
+		MesProductDbjBind byPartSn = new MesProductDbjBind();
+		byPartSn.setMaterielSn(partSn);
+		byPartSn.getSqlMap().getOrder().setOrderBy("a.create_date DESC");
+		List<MesProductDbjBind> bindList = mesProductDbjBindService.findList(byPartSn);
+		if (!ListUtils.isEmpty(bindList)) {
+			for (MesProductDbjBind bind : bindList) {
+				if (partSn.equals(bind.getMaterielSn()) && StringUtils.isNotEmpty(bind.getDbjSn())) {
+					return bind.getDbjSn();
+				}
+			}
+		}
+		return dao.findMainSnByPartSn(partSn);
+	}
+
+	/**
 	 * 按精追码或主工件码查询关联绑定记录
 	 */
 	public List<MesProductDbjBind> findBindListForRecord(MesProductDbjRecord record) {

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

@@ -838,6 +838,8 @@ public class MesProductRecordService extends CrudService<MesProductRecordDao, Me
 		if(mesLineProcess1.getType().equals("3")){
 			mesProductDbjRecordService.saveFromUpload(sn, oldOprno, lineSn, craft, content, userCode,
 					paramsResps, bmlists, mesLineProcess1, intervalSec);
+		} else if ("OK".equals(content)) {
+			mesProductDbjRecordService.bindFromLaterProcess(sn, oldOprno, bmlists);
 		}
 
 	}

+ 75 - 0
src/main/java/com/jeesite/modules/mes/util/ClientVersionUtils.java

@@ -0,0 +1,75 @@
+package com.jeesite.modules.mes.util;
+
+/**
+ * 客户端版本号比较,格式 0.0.0 / 0.0.1。
+ * 没有点的旧数字(如 1)按 0.0.1 处理。
+ */
+public final class ClientVersionUtils {
+
+	private ClientVersionUtils() {
+	}
+
+	public static int compare(String left, String right) {
+		int[] a = parse(left);
+		int[] b = parse(right);
+		int n = Math.max(a.length, b.length);
+		for (int i = 0; i < n; i++) {
+			int va = i < a.length ? a[i] : 0;
+			int vb = i < b.length ? b[i] : 0;
+			if (va != vb) {
+				return va < vb ? -1 : 1;
+			}
+		}
+		return 0;
+	}
+
+	public static boolean isNewer(String latest, String current) {
+		return compare(latest, current) > 0;
+	}
+
+	public static String max(String left, String right) {
+		if (isBlank(left)) {
+			return normalize(right);
+		}
+		if (isBlank(right)) {
+			return normalize(left);
+		}
+		return compare(left, right) >= 0 ? normalize(left) : normalize(right);
+	}
+
+	public static String normalize(String version) {
+		if (isBlank(version)) {
+			return "0.0.0";
+		}
+		int[] parts = parse(version);
+		return parts[0] + "." + parts[1] + "." + parts[2];
+	}
+
+	private static int[] parse(String version) {
+		int[] result = new int[] {0, 0, 0};
+		if (isBlank(version)) {
+			return result;
+		}
+		String[] parts = version.trim().split("\\.");
+		if (parts.length == 1) {
+			result[2] = parseInt(parts[0]);
+			return result;
+		}
+		for (int i = 0; i < parts.length && i < 3; i++) {
+			result[i] = parseInt(parts[i]);
+		}
+		return result;
+	}
+
+	private static int parseInt(String value) {
+		try {
+			return Integer.parseInt(value.trim());
+		} catch (Exception e) {
+			return 0;
+		}
+	}
+
+	private static boolean isBlank(String value) {
+		return value == null || value.trim().length() == 0;
+	}
+}

+ 300 - 0
src/main/java/com/jeesite/modules/mes/web/MesClientUpgradeController.java

@@ -0,0 +1,300 @@
+package com.jeesite.modules.mes.web;
+
+import com.jeesite.common.collect.ListUtils;
+import com.jeesite.common.config.Global;
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.lang.ObjectUtils;
+import com.jeesite.common.lang.StringUtils;
+import com.jeesite.common.mybatis.mapper.query.QueryType;
+import com.jeesite.common.web.BaseController;
+import com.jeesite.modules.file.entity.FileUpload;
+import com.jeesite.modules.file.service.FileUploadService;
+import com.jeesite.modules.mes.entity.MesClientUpgrade;
+import com.jeesite.modules.mes.entity.MesLine;
+import com.jeesite.modules.mes.entity.MesLineProcess;
+import com.jeesite.modules.mes.resp.CommonResp;
+import com.jeesite.modules.mes.service.MesClientUpgradeService;
+import com.jeesite.modules.mes.service.MesLineService;
+import com.jeesite.modules.mes.service.MesLineProcessService;
+import com.jeesite.modules.mes.util.ClientVersionUtils;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.ArrayList;
+import java.util.List;
+
+@Controller
+@RequestMapping(value = "${adminPath}/mes/mesClientUpgrade")
+public class MesClientUpgradeController extends BaseController {
+
+	@Autowired
+	private MesClientUpgradeService mesClientUpgradeService;
+
+	@Autowired
+	private MesLineProcessService mesLineProcessService;
+
+	@Autowired
+	private MesLineService mesLineService;
+
+	@Resource
+	private FileUploadService fileUploadService;
+
+	@ModelAttribute
+	public MesClientUpgrade get(String id, boolean isNewRecord) {
+		return mesClientUpgradeService.get(id, isNewRecord);
+	}
+
+	@RequiresPermissions("mes:mesLineProcess:view")
+	@RequestMapping(value = {"list", ""})
+	public String list(MesClientUpgrade mesClientUpgrade, Model model, HttpServletRequest request) {
+		boolean lineProcessJar = "1".equals(request.getParameter("lineProcessJar"));
+		String lineProcessId = request.getParameter("lineProcessId");
+		fillLineProcessJarLineSn(mesClientUpgrade, lineProcessId);
+		model.addAttribute("mesClientUpgrade", mesClientUpgrade);
+		model.addAttribute("lineProcessJar", lineProcessJar);
+		model.addAttribute("lineProcessId", lineProcessId);
+		return "modules/mes/mesClientUpgradeList";
+	}
+
+	@RequiresPermissions("mes:mesLineProcess:view")
+	@RequestMapping(value = "listData")
+	@ResponseBody
+	public Page<MesClientUpgrade> listData(MesClientUpgrade mesClientUpgrade,
+			HttpServletRequest request, HttpServletResponse response) {
+		if ("1".equals(request.getParameter("lineProcessJar"))) {
+			List<String> oprnos = fillLineProcessJarLineSn(mesClientUpgrade, request.getParameter("lineProcessId"));
+			if (StringUtils.isNotBlank(mesClientUpgrade.getLineSn())) {
+				mesClientUpgrade.getSqlMap().getWhere().and("a.line_sn", QueryType.EQ, mesClientUpgrade.getLineSn());
+				mesClientUpgrade.setLineSn(null);
+			}
+			if (!oprnos.isEmpty()) {
+				mesClientUpgrade.getSqlMap().getWhere().and("a.oprno", QueryType.IN, oprnos.toArray(new String[0]));
+				mesClientUpgrade.setOprno(null);
+			} else if (StringUtils.isNotBlank(mesClientUpgrade.getOprno())) {
+				mesClientUpgrade.getSqlMap().getWhere().and("a.oprno", QueryType.EQ, mesClientUpgrade.getOprno());
+				mesClientUpgrade.setOprno(null);
+			}
+		}
+		mesClientUpgrade.setPage(new Page<>(request, response));
+		return mesClientUpgradeService.findPage(mesClientUpgrade);
+	}
+
+	private List<String> fillLineProcessJarLineSn(MesClientUpgrade mesClientUpgrade, String lineProcessId) {
+		List<String> oprnos = new ArrayList<>();
+		if (StringUtils.isBlank(lineProcessId)) {
+			return oprnos;
+		}
+		MesLineProcess lineProcess = mesLineProcessService.get(new MesLineProcess(lineProcessId));
+		if (lineProcess == null) {
+			return oprnos;
+		}
+		if (StringUtils.isBlank(mesClientUpgrade.getLineSn())) {
+			String lineSn = lineProcess.getLineSn();
+			if (StringUtils.isBlank(lineSn) && StringUtils.isNotBlank(lineProcess.getLineId())) {
+				MesLine line = mesLineService.get(new MesLine(lineProcess.getLineId()));
+				if (line != null) {
+					lineSn = line.getSn();
+				}
+			}
+			mesClientUpgrade.setLineSn(lineSn);
+		}
+		for (MesLineProcess child : findChildLineProcesses(lineProcessId)) {
+			if (StringUtils.isNotBlank(child.getOprno())) {
+				oprnos.add(child.getOprno());
+			}
+		}
+		if (oprnos.isEmpty() && StringUtils.isNotBlank(lineProcess.getOprno())) {
+			oprnos.add(lineProcess.getOprno());
+		}
+		return oprnos;
+	}
+
+	private List<MesLineProcess> findChildLineProcesses(String lineProcessId) {
+		if (StringUtils.isBlank(lineProcessId)) {
+			return ListUtils.newArrayList();
+		}
+		MesLineProcess childQuery = new MesLineProcess();
+		childQuery.setPid(lineProcessId);
+		return mesLineProcessService.findList(childQuery);
+	}
+
+	@RequiresPermissions("mes:mesLineProcess:view")
+	@RequestMapping(value = "form")
+	public String form(MesClientUpgrade mesClientUpgrade, Model model, HttpServletRequest request) {
+		String lineProcessId = request.getParameter("lineProcessId");
+		boolean lineProcessJar = StringUtils.isNotBlank(lineProcessId);
+		if (lineProcessJar) {
+			fillLineProcessJarLineSn(mesClientUpgrade, lineProcessId);
+			model.addAttribute("childLineProcessList", findChildLineProcesses(lineProcessId));
+		}
+		model.addAttribute("mesClientUpgrade", mesClientUpgrade);
+		model.addAttribute("lineProcessJar", lineProcessJar);
+		model.addAttribute("lineProcessId", lineProcessId);
+		return "modules/mes/mesClientUpgradeForm";
+	}
+
+	@RequiresPermissions("mes:mesLineProcess:edit")
+	@PostMapping(value = "save")
+	@ResponseBody
+	public String save(@Validated MesClientUpgrade mesClientUpgrade) {
+		mesClientUpgradeService.save(mesClientUpgrade);
+		return renderResult(Global.TRUE, text("保存客户端升级版本成功!"));
+	}
+
+	@RequiresPermissions("mes:mesLineProcess:edit")
+	@RequestMapping(value = "delete")
+	@ResponseBody
+	public String delete(MesClientUpgrade mesClientUpgrade) {
+		mesClientUpgradeService.delete(mesClientUpgrade);
+		return renderResult(Global.TRUE, text("删除客户端升级版本成功!"));
+	}
+
+	@RequestMapping(value = "checkUpdate")
+	@ResponseBody
+	public CommonResp<ClientUpdateInfo> checkUpdate(HttpServletRequest request) {
+		CommonResp<ClientUpdateInfo> resp = new CommonResp<>();
+		String lineSn = trimToNull(request.getParameter("lineSn"));
+		String oprno = trimToNull(request.getParameter("oprno"));
+		MesClientUpgrade latest = mesClientUpgradeService.findLatestVersion(lineSn, oprno);
+		if (ObjectUtils.isEmpty(latest)) {
+			resp.setResult(Global.FALSE);
+			resp.setMessage("no version record");
+			return resp;
+		}
+
+		FileUpload fu = new FileUpload();
+		fu.setBizKey(latest.getId());
+		fu.setBizType("mesClientUpgrade_file");
+		List<FileUpload> files = fileUploadService.findList(fu);
+		if (ListUtils.isEmpty(files)) {
+			resp.setResult(Global.FALSE);
+			resp.setMessage("no downloadable file");
+			return resp;
+		}
+
+		String currentVersion = trimToNull(request.getParameter("currentVersion"));
+		FileUpload file = files.get(0);
+		ClientUpdateInfo updateInfo = new ClientUpdateInfo();
+		updateInfo.setCurrentVersion(currentVersion);
+		updateInfo.setLatestVersion(latest.getVersion());
+		updateInfo.setLineSn(latest.getLineSn());
+		updateInfo.setOprno(latest.getOprno());
+		updateInfo.setHasUpdate(ClientVersionUtils.isNewer(latest.getVersion(), currentVersion));
+		updateInfo.setContent(latest.getContent());
+		updateInfo.setDownloadUrl(buildFileUrl(request, file.getFileUrl()));
+		String originalName = file.getFileName();
+		if (StringUtils.isBlank(originalName)) {
+			originalName = extractFileName(file.getFileUrl());
+		}
+		updateInfo.setFileName(originalName);
+		resp.setData(updateInfo);
+		resp.setResult(Global.TRUE);
+		return resp;
+	}
+
+	private String buildFileUrl(HttpServletRequest request, String fileUrl) {
+		String host = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort();
+		return host + request.getContextPath() + fileUrl;
+	}
+
+	private String trimToNull(String value) {
+		if (value == null) {
+			return null;
+		}
+		value = value.trim();
+		return value.length() == 0 ? null : value;
+	}
+
+	private String extractFileName(String fileUrl) {
+		if (fileUrl == null) {
+			return null;
+		}
+		int index = fileUrl.lastIndexOf('/');
+		return index >= 0 ? fileUrl.substring(index + 1) : fileUrl;
+	}
+
+	public static class ClientUpdateInfo {
+		private String currentVersion;
+		private String latestVersion;
+		private String lineSn;
+		private String oprno;
+		private boolean hasUpdate;
+		private String content;
+		private String downloadUrl;
+		private String fileName;
+
+		public String getCurrentVersion() {
+			return currentVersion;
+		}
+
+		public void setCurrentVersion(String currentVersion) {
+			this.currentVersion = currentVersion;
+		}
+
+		public String getLatestVersion() {
+			return latestVersion;
+		}
+
+		public void setLatestVersion(String latestVersion) {
+			this.latestVersion = latestVersion;
+		}
+
+		public String getLineSn() {
+			return lineSn;
+		}
+
+		public void setLineSn(String lineSn) {
+			this.lineSn = lineSn;
+		}
+
+		public String getOprno() {
+			return oprno;
+		}
+
+		public void setOprno(String oprno) {
+			this.oprno = oprno;
+		}
+
+		public boolean isHasUpdate() {
+			return hasUpdate;
+		}
+
+		public void setHasUpdate(boolean hasUpdate) {
+			this.hasUpdate = hasUpdate;
+		}
+
+		public String getContent() {
+			return content;
+		}
+
+		public void setContent(String content) {
+			this.content = content;
+		}
+
+		public String getDownloadUrl() {
+			return downloadUrl;
+		}
+
+		public void setDownloadUrl(String downloadUrl) {
+			this.downloadUrl = downloadUrl;
+		}
+
+		public String getFileName() {
+			return fileName;
+		}
+
+		public void setFileName(String fileName) {
+			this.fileName = fileName;
+		}
+	}
+}

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

@@ -103,6 +103,7 @@ public class MesProductDbjBindController extends BaseController {
     @ResponseBody
     public String save(@Validated MesProductDbjBind mesProductDbjBind) {
         mesProductDbjBindService.save(mesProductDbjBind);
+        mesProductDbjRecordService.backfillDbjSn(mesProductDbjBind.getMaterielSn(), mesProductDbjBind.getDbjSn());
         return renderResult(Global.TRUE, text("保存单部件绑定表成功!"));
     }
 

+ 19 - 0
src/main/java/com/jeesite/modules/sys/web/AdminRootRedirectController.java

@@ -0,0 +1,19 @@
+package com.jeesite.modules.sys.web;
+
+import com.jeesite.common.web.BaseController;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * JeeSite 5 访问 ${adminPath}(如 /js/a)没有对应页面,登录后按 __url 回跳会 404。
+ * 将该短地址转到工作台 ${adminPath}/index。
+ */
+@Controller
+public class AdminRootRedirectController extends BaseController {
+
+	@RequestMapping(value = {"${adminPath}", "${adminPath}/"}, method = RequestMethod.GET)
+	public String redirectToIndex() {
+		return REDIRECT + adminPath + "/index";
+	}
+}

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

@@ -615,6 +615,7 @@ shiro:
   # 提示:填写过滤规则,请注意先后顺序,从上到下,先匹配先受用规则,匹配成功后不再继续匹配。
   filterChainDefinitions: |
     ${adminPath}/mes/mesApp/ver = anon
+    ${adminPath}/mes/mesClientUpgrade/checkUpdate = anon
     ${adminPath}/mes/mesLogin/login = anon
     ${adminPath}/mes/mesProductRecord/ghtime = anon
     ${adminPath}/mes/mesProductRecord/qmcheck = anon

+ 23 - 0
src/main/resources/mappings/modules/mes/MesClientUpgradeDao.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.jeesite.modules.mes.dao.MesClientUpgradeDao">
+
+    <select id="findVersionCandidates" resultType="com.jeesite.modules.mes.entity.MesClientUpgrade">
+        SELECT ${sqlMap.column.toSql()}
+        FROM ${sqlMap.table.toSql()}
+        WHERE a.state = #{state}
+        <choose>
+            <when test="lineSn != null and lineSn != '' and oprno != null and oprno != ''">
+                AND (
+                    (a.line_sn = #{lineSn} AND a.oprno = #{oprno})
+                    OR ((a.line_sn IS NULL OR a.line_sn = '') AND (a.oprno IS NULL OR a.oprno = ''))
+                )
+            </when>
+            <otherwise>
+                AND (a.line_sn IS NULL OR a.line_sn = '')
+                AND (a.oprno IS NULL OR a.oprno = '')
+            </otherwise>
+        </choose>
+        ORDER BY a.create_date DESC
+    </select>
+</mapper>

+ 9 - 0
src/main/resources/mappings/modules/mes/MesProductDbjRecordDao.xml

@@ -2,4 +2,13 @@
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.jeesite.modules.mes.dao.MesProductDbjRecordDao">
 
+	<select id="findMainSnByPartSn" resultType="java.lang.String">
+		SELECT p.sn
+		FROM mes_product_record p
+		WHERE p.content = 'OK'
+		  AND p.materiel_sn = #{partSn}
+		ORDER BY p.create_date DESC
+		LIMIT 1
+	</select>
+
 </mapper>

+ 118 - 0
src/main/resources/views/modules/mes/mesClientUpgradeForm.html

@@ -0,0 +1,118 @@
+<% layout('/layouts/default.html', {title: '客户端版本上传', libs: ['validate','fileupload']}){ %>
+<div class="main-content">
+	<div class="box box-main">
+		<div class="box-header with-border">
+			<div class="box-title">
+				<i class="fa icon-cloud-upload"></i> ${text(mesClientUpgrade.isNewRecord ? '新增客户端版本' : '编辑客户端版本')}
+			</div>
+			<div class="box-tools pull-right">
+				<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
+			</div>
+		</div>
+		<#form:form id="inputForm" model="${mesClientUpgrade}" action="${ctx}/mes/mesClientUpgrade/save" method="post" class="form-horizontal">
+			<div class="box-body">
+				<div class="form-unit">${text('基本信息')}</div>
+				<#form:hidden path="id"/>
+				<div class="row">
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required">*</span> ${text('版本号')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="version" maxlength="32" class="form-control required" placeholder="0.0.0"/>
+								<div class="text-muted" style="margin-top:6px;">格式如 0.0.0、0.0.1、0.0.2</div>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required">*</span> ${text('状态')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:select path="state" dictType="mes_app_status" class="form-control required"/>
+							</div>
+						</div>
+					</div>
+				</div>
+				<div class="row">
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required hide">*</span> ${text('产线编号')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="lineSn" maxlength="100" class="form-control"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required hide">*</span> ${text('工位')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<% if(lineProcessJar && !isEmpty(childLineProcessList)){ %>
+									<#form:select path="oprno" items="${childLineProcessList}" itemLabel="oprno" itemValue="oprno" blankOption="true" class="form-control required"/>
+								<% } else { %>
+									<#form:input path="oprno" maxlength="255" class="form-control"/>
+								<% } %>
+							</div>
+						</div>
+					</div>
+				</div>
+				<div class="row">
+					<div class="col-xs-12">
+						<div class="form-group">
+							<label class="control-label col-sm-2" title="">
+								<span class="required hide">*</span> ${text('更新内容')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-10">
+								<#form:textarea path="content" rows="4" class="form-control"/>
+							</div>
+						</div>
+					</div>
+				</div>
+				<div class="row">
+					<div class="col-xs-12">
+						<div class="form-group">
+							<label class="control-label col-sm-2">
+								<span class="required hide">*</span> ${text('客户端包')}:</label>
+							<div class="col-sm-10">
+								<#form:fileupload id="uploadFile" bizKey="${mesClientUpgrade.id}" bizType="mesClientUpgrade_file"
+									uploadType="all" class="" allowSuffixes="jar,exe" maxUploadNum="1" readonly="false" preview="true"/>
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+			<div class="box-footer">
+				<div class="row">
+					<div class="col-sm-offset-2 col-sm-10">
+						<% 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>
+					</div>
+				</div>
+			</div>
+		</#form:form>
+	</div>
+</div>
+<% } %>
+<script>
+jQuery.validator.addMethod("clientVer", function(value, element) {
+	return this.optional(element) || /^\d+\.\d+\.\d+$/.test(value);
+}, "版本号格式为 0.0.0");
+$("#inputForm").validate({
+	rules: {
+		version: { required: true, clientVer: true }
+	},
+	submitHandler: function(form){
+		js.ajaxSubmitForm($(form), function(data){
+			js.showMessage(data.message);
+			if(data.result == Global.TRUE){
+				js.closeCurrentTabPage(function(contentWindow){
+					contentWindow.page();
+				});
+			}
+		}, "json");
+    }
+});
+</script>

+ 88 - 0
src/main/resources/views/modules/mes/mesClientUpgradeList.html

@@ -0,0 +1,88 @@
+<% layout('/layouts/default.html', {title: '客户端版本上传', libs: ['dataGrid']}){ %>
+<div class="main-content">
+	<div class="box box-main">
+		<div class="box-header">
+			<div class="box-title">
+				<i class="fa icon-cloud-upload"></i> ${text('客户端版本上传')}
+			</div>
+			<div class="box-tools pull-right">
+				<a href="#" class="btn btn-default" id="btnSearch" title="${text('查询')}"><i class="fa fa-filter"></i> ${text('查询')}</a>
+				<% if(hasPermi('mes:mesLineProcess:edit')){ %>
+					<% if(lineProcessJar){ %>
+						<a href="${ctx}/mes/mesClientUpgrade/form?lineProcessId=${lineProcessId}" class="btn btn-default btnTool" title="${text('新增客户端版本')}"><i class="fa fa-plus"></i> ${text('新增')}</a>
+					<% } else { %>
+						<a href="${ctx}/mes/mesClientUpgrade/form" class="btn btn-default btnTool" title="${text('新增客户端版本')}"><i class="fa fa-plus"></i> ${text('新增')}</a>
+					<% } %>
+				<% } %>
+				<a href="#" class="btn btn-default" id="btnSetting" title="${text('设置')}"><i class="fa fa-navicon"></i></a>
+			</div>
+		</div>
+		<div class="box-body">
+			<#form:form id="searchForm" model="${mesClientUpgrade}" action="${ctx}/mes/mesClientUpgrade/listData" method="post" class="form-inline hide"
+					data-page-no="${parameter.pageNo}" data-page-size="${parameter.pageSize}" data-order-by="${parameter.orderBy}">
+				<% if(lineProcessJar){ %>
+					<input type="hidden" name="lineProcessJar" value="1"/>
+					<input type="hidden" name="lineProcessId" value="${lineProcessId}"/>
+				<% } %>
+				<div class="form-group">
+					<label class="control-label">${text('版本号')}:</label>
+					<div class="control-inline">
+						<#form:input path="version" maxlength="32" class="form-control width-120" placeholder="0.0.0"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('产线编号')}:</label>
+					<div class="control-inline">
+						<#form:input path="lineSn" 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="oprno" maxlength="255" class="form-control width-120"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('状态')}:</label>
+					<div class="control-inline width-120">
+						<#form:select path="state" dictType="mes_app_status" blankOption="true" class="form-control"/>
+					</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:'version', index:'a.version', width:120, align:"center", frozen:true, formatter: function(val, obj, row, act){
+			return '<a href="${ctx}/mes/mesClientUpgrade/form?id='+row.id+'<% if(lineProcessJar){ %>&lineProcessId=${lineProcessId}<% } %>" class="btnList" data-title="${text("编辑客户端版本")}">'+(val||row.id)+'</a>';
+		}},
+		{header:'${text("产线编号")}', name:'lineSn', index:'a.line_sn', width:120, align:"center"},
+		{header:'${text("工位")}', name:'oprno', index:'a.oprno', width:120, align:"center"},
+		{header:'${text("更新内容")}', name:'content', index:'a.content', width:220, align:"center"},
+		{header:'${text("状态")}', name:'state', index:'a.state', width:100, align:"center", formatter: function(val, obj, row, act){
+				return js.getDictLabel(${@DictUtils.getDictListJson('mes_app_status')}, val, '${text("未知")}', true);
+			}},
+		{header:'${text("更新时间")}', name:'updateDate', index:'a.update_date', width:150, align:"center"},
+		{header:'${text("操作")}', name:'actions', width:100, formatter: function(val, obj, row, act){
+			var actions = [];
+			//<% if(hasPermi('mes:mesLineProcess:edit')){ %>
+				actions.push('<a href="${ctx}/mes/mesClientUpgrade/form?id='+row.id+'<% if(lineProcessJar){ %>&lineProcessId=${lineProcessId}<% } %>" class="btnList" title="${text("编辑客户端版本")}"><i class="fa fa-pencil"></i></a>&nbsp;');
+				actions.push('<a href="${ctx}/mes/mesClientUpgrade/delete?id='+row.id+'" class="btnList" title="${text("删除客户端版本")}" data-confirm="${text("确认要删除该客户端版本吗?")}"><i class="fa fa-trash-o"></i></a>&nbsp;');
+			//<% } %>
+			return actions.join('');
+		}}
+	],
+	ajaxSuccess: function(data){
+	}
+});
+</script>

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

@@ -88,7 +88,7 @@ $('#dataGrid').dataGrid({
 		// 		return js.getDictLabel(${@DictUtils.getDictListJson('mes_line_process_stop')}, val, '${text("允许")}', true);
 		// 	}},
 		// {header:'${text("修改时间")}', name:'updateDate', index:'a.update_date', width:150, align:"center"},
-		{header:'${text("操作")}', name:'actions', width:250, align:"center", frozen:true, formatter: function(val, obj, row, act){
+		{header:'${text("操作")}', name:'actions', width:280, align:"center", frozen:true, formatter: function(val, obj, row, act){
 			var actions = [];
 			//<% if(hasPermi('mes:mesLineProcess:edit')){ %>
 				actions.push('<a href="${ctx}/mes/mesLineProcess/form?id='+row.id+'" class="btn btn-default btn-xs btnList" title="${text("编辑")}">编辑</a>&nbsp;');
@@ -97,7 +97,8 @@ $('#dataGrid').dataGrid({
 				actions.push('<a href="${ctx}/mes/mesLineProcessMaterial/list?lineProcessId='+row.id+'" class="btn btn-warning btn-xs btnList" title="物料">物料</a>&nbsp;');
 				actions.push('<a href="${ctx}/mes/mesLineProcessUser/list?lineProcessId='+row.id+'" class="btn btn-success btn-xs btnList" title="人员">人员</a>&nbsp;');
 				actions.push('<a href="${ctx}/mes/mesLineProcess/sublist?pid='+row.id+'" class="btn btn-primary btn-xs btnList" title="子工位">子工位</a>&nbsp;');
-				return actions.join('');
+				actions.push('<a href="${ctx}/mes/mesClientUpgrade/list?lineProcessJar=1&lineProcessId='+row.id+'" class="btn btn-xs btnList" title="JAR管理" style="background-color: #605ca8; border-color: #605ca8; color: #fff;">JAR管理</a>&nbsp;');
+				return '<div style="overflow-x: auto; white-space: nowrap; width: 100%; text-align: left; padding: 2px 0;">' + actions.join('') + '</div>';
 		}}
 	],
 	frozenCols: true,