Quellcode durchsuchen

出货扫码校验

wangxichen vor 1 Woche
Ursprung
Commit
3ed9e6dfa1

+ 57 - 0
db/mysql/mes_ship_scan_log.sql

@@ -0,0 +1,57 @@
+-- =====================================================
+-- 出货扫码校验记录表
+-- 校验规则: 标签码前 N 位 == 标识卡完整内容(默认 N=15)
+-- =====================================================
+
+DROP TABLE IF EXISTS `mes_ship_scan_log`;
+CREATE TABLE `mes_ship_scan_log` (
+  `id`          VARCHAR(64)  NOT NULL                COMMENT '主键ID',
+  `card_code`   VARCHAR(128) NOT NULL                COMMENT '标识卡二维码',
+  `label_code`  VARCHAR(128) NOT NULL                COMMENT '产品标签二维码',
+  `match_state` CHAR(1)      NOT NULL                COMMENT '0=不匹配 1=匹配',
+  `fail_reason` VARCHAR(255) DEFAULT NULL            COMMENT '不匹配原因',
+  `create_by`   VARCHAR(64)  DEFAULT NULL,
+  `create_date` DATETIME     DEFAULT NULL,
+  `update_by`   VARCHAR(64)  DEFAULT NULL,
+  `update_date` DATETIME     DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `idx_state_date` (`match_state`, `create_date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='出货扫码校验记录';
+
+
+-- =====================================================
+-- 菜单注册(生产管理 → 出货扫码校验)
+-- 父菜单 menu_code = 1672118505143263232 (生产管理)
+-- =====================================================
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`,
+  `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`, `tree_names`,
+  `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`,
+  `permission`, `weight`, `is_show`, `sys_code`, `module_codes`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`
+) VALUES (
+  '2050000000000001001', '1672118505143263232', '0,1672118505143263232,',
+  1830, '0000009290,0000001830,', '1', 1, '生产管理/出货扫码校验',
+  '出货扫码校验', '1', '/mes/mesShipScan/list', '', 'fa-qrcode',
+  'mes:mesShipScan:view,mes:mesShipScan:edit', 40, '1', 'default', 'core',
+  '0', 'system', NOW(), 'system', NOW()
+);
+
+
+-- =====================================================
+-- APP模块权限节点(让角色权限树里能勾选)
+-- 父节点 menu_code = 1689510779897856000 (APP模块权限)
+-- =====================================================
+INSERT INTO `js_sys_menu` (
+  `menu_code`, `parent_code`, `parent_codes`,
+  `tree_sort`, `tree_sorts`, `tree_leaf`, `tree_level`, `tree_names`,
+  `menu_name`, `menu_type`, `menu_href`, `menu_target`, `menu_icon`,
+  `permission`, `weight`, `is_show`, `sys_code`, `module_codes`,
+  `status`, `create_by`, `create_date`, `update_by`, `update_date`
+) VALUES (
+  '2050000000000001002', '1689510779897856000', '0,1689510779897856000,',
+  1510, '0000009590,0000001510,', '1', 1, 'APP模块权限/标识卡标签校验',
+  '标识卡标签校验', '2', '', '', '',
+  'mes:mesShipScan:api', 40, '1', 'default', 'app',
+  '0', 'system', NOW(), 'system', NOW()
+);

+ 15 - 0
src/main/java/com/jeesite/modules/mes/dao/MesShipScanDao.java

@@ -0,0 +1,15 @@
+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.MesShipScan;
+
+/**
+ * 出货扫码校验记录DAO
+ * @author mes
+ * @version 2026-06-10
+ */
+@MyBatisDao
+public interface MesShipScanDao extends CrudDao<MesShipScan> {
+
+}

+ 93 - 0
src/main/java/com/jeesite/modules/mes/entity/MesShipScan.java

@@ -0,0 +1,93 @@
+package com.jeesite.modules.mes.entity;
+
+import javax.validation.constraints.Size;
+
+import com.jeesite.common.entity.DataEntity;
+import com.jeesite.common.mybatis.annotation.Column;
+import com.jeesite.common.mybatis.annotation.Table;
+import com.jeesite.common.mybatis.mapper.query.QueryType;
+import com.jeesite.common.utils.excel.annotation.ExcelField;
+import com.jeesite.common.utils.excel.annotation.ExcelFields;
+
+/**
+ * 出货扫码校验记录Entity
+ * @author mes
+ * @version 2026-06-10
+ */
+@Table(name="mes_ship_scan_log", alias="a", label="出货扫码校验记录", columns={
+		@Column(name="id", attrName="id", label="主键ID", isPK=true),
+		@Column(name="card_code", attrName="cardCode", label="标识卡二维码", queryType=QueryType.LIKE),
+		@Column(name="label_code", attrName="labelCode", label="标签二维码", queryType=QueryType.LIKE),
+		@Column(name="match_state", attrName="matchState", label="匹配状态", isQuery=true),
+		@Column(name="fail_reason", attrName="failReason", label="不匹配原因"),
+		@Column(name="create_by", attrName="createBy", label="创建者", isUpdate=false, isQuery=false),
+		@Column(name="create_date", attrName="createDate", label="创建时间", isUpdate=false, isQuery=false),
+		@Column(name="update_by", attrName="updateBy", label="更新者", isQuery=false),
+		@Column(name="update_date", attrName="updateDate", label="更新时间", isQuery=false, isUpdateForce=true),
+	}, orderBy="a.create_date DESC"
+)
+public class MesShipScan extends DataEntity<MesShipScan> {
+
+	@ExcelFields({
+			@ExcelField(title="标识卡二维码", attrName="cardCode", align= ExcelField.Align.CENTER, sort=10),
+			@ExcelField(title="标签二维码", attrName="labelCode", align= ExcelField.Align.CENTER, sort=20),
+			@ExcelField(title="匹配状态", attrName="matchState", align= ExcelField.Align.CENTER, sort=30),
+			@ExcelField(title="不匹配原因", attrName="failReason", align= ExcelField.Align.LEFT, sort=40),
+			@ExcelField(title="扫码时间", attrName="createDate", align= ExcelField.Align.CENTER, sort=50, dataFormat="yyyy-MM-dd HH:mm:ss"),
+	})
+
+	private static final long serialVersionUID = 1L;
+
+	/** 匹配状态:1=匹配 */
+	public static final String MATCH_OK = "1";
+	/** 匹配状态:0=不匹配 */
+	public static final String MATCH_FAIL = "0";
+
+	private String cardCode;     // 标识卡二维码
+	private String labelCode;    // 标签二维码
+	private String matchState;   // 匹配状态 0=不匹配 1=匹配
+	private String failReason;   // 不匹配原因
+
+	public MesShipScan() {
+		this(null);
+	}
+
+	public MesShipScan(String id){
+		super(id);
+	}
+
+	@Size(min=0, max=128, message="标识卡二维码长度不能超过 128 个字符")
+	public String getCardCode() {
+		return cardCode;
+	}
+
+	public void setCardCode(String cardCode) {
+		this.cardCode = cardCode;
+	}
+
+	@Size(min=0, max=128, message="标签二维码长度不能超过 128 个字符")
+	public String getLabelCode() {
+		return labelCode;
+	}
+
+	public void setLabelCode(String labelCode) {
+		this.labelCode = labelCode;
+	}
+
+	public String getMatchState() {
+		return matchState;
+	}
+
+	public void setMatchState(String matchState) {
+		this.matchState = matchState;
+	}
+
+	@Size(min=0, max=255, message="不匹配原因长度不能超过 255 个字符")
+	public String getFailReason() {
+		return failReason;
+	}
+
+	public void setFailReason(String failReason) {
+		this.failReason = failReason;
+	}
+}

+ 83 - 0
src/main/java/com/jeesite/modules/mes/service/MesShipScanService.java

@@ -0,0 +1,83 @@
+package com.jeesite.modules.mes.service;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.lang.StringUtils;
+import com.jeesite.common.service.CrudService;
+import com.jeesite.modules.mes.dao.MesShipScanDao;
+import com.jeesite.modules.mes.entity.MesShipScan;
+
+/**
+ * 出货扫码校验记录Service
+ * @author mes
+ * @version 2026-06-10
+ */
+@Service
+@Transactional(readOnly = true)
+public class MesShipScanService extends CrudService<MesShipScanDao, MesShipScan> {
+
+	/** 校验长度(标签前 N 位 == 标识卡) 默认 15 */
+	@Value("${mes.ship.scan.match.length:15}")
+	private int matchLength;
+
+	@Override
+	public Page<MesShipScan> findPage(MesShipScan entity) {
+		return super.findPage(entity);
+	}
+
+	@Override
+	public List<MesShipScan> findList(MesShipScan entity) {
+		return super.findList(entity);
+	}
+
+	@Override
+	@Transactional(readOnly = false)
+	public void save(MesShipScan entity) {
+		super.save(entity);
+	}
+
+	@Override
+	@Transactional(readOnly = false)
+	public void delete(MesShipScan entity) {
+		super.delete(entity);
+	}
+
+	/**
+	 * 安卓上传:扫码校验 + 落库
+	 * 规则:标签码前 N 位 == 标识卡完整内容
+	 * 一致或不一致都落库(方案C)
+	 *
+	 * @return 已保存的记录(含 matchState、failReason)
+	 */
+	@Transactional(readOnly = false)
+	public MesShipScan upload(String cardCode, String labelCode) {
+		MesShipScan log = new MesShipScan();
+		log.setCardCode(StringUtils.trimToEmpty(cardCode));
+		log.setLabelCode(StringUtils.trimToEmpty(labelCode));
+
+		// 取标签前 N 位
+		String labelSeg = log.getLabelCode().length() >= matchLength
+				? log.getLabelCode().substring(0, matchLength)
+				: log.getLabelCode();
+
+		boolean match = StringUtils.isNotBlank(log.getCardCode())
+				&& StringUtils.equals(log.getCardCode(), labelSeg);
+
+		if (match) {
+			log.setMatchState(MesShipScan.MATCH_OK);
+			log.setFailReason(null);
+		} else {
+			log.setMatchState(MesShipScan.MATCH_FAIL);
+			log.setFailReason("二维码不一致:标识卡=" + log.getCardCode()
+					+ " 标签前" + matchLength + "位=" + labelSeg);
+		}
+
+		super.save(log);
+		return log;
+	}
+}

+ 122 - 0
src/main/java/com/jeesite/modules/mes/web/MesShipScanController.java

@@ -0,0 +1,122 @@
+package com.jeesite.modules.mes.web;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+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.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.jeesite.common.config.Global;
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.lang.DateUtils;
+import com.jeesite.common.lang.StringUtils;
+import com.jeesite.common.utils.excel.ExcelExport;
+import com.jeesite.common.web.BaseController;
+import com.jeesite.modules.mes.entity.MesShipScan;
+import com.jeesite.modules.mes.service.MesShipScanService;
+
+/**
+ * 出货扫码校验记录Controller
+ * @author mes
+ * @version 2026-06-10
+ */
+@Controller
+@RequestMapping(value = "${adminPath}/mes/mesShipScan")
+public class MesShipScanController extends BaseController {
+
+	@Autowired
+	private MesShipScanService mesShipScanService;
+
+	@ModelAttribute
+	public MesShipScan get(String id, boolean isNewRecord) {
+		return mesShipScanService.get(id, isNewRecord);
+	}
+
+	/**
+	 * 列表页
+	 */
+	@RequiresPermissions("mes:mesShipScan:view")
+	@RequestMapping(value = {"list", ""})
+	public String list(MesShipScan mesShipScan, Model model) {
+		model.addAttribute("mesShipScan", mesShipScan);
+		return "modules/mes/mesShipScanList";
+	}
+
+	/**
+	 * 列表数据
+	 */
+	@RequiresPermissions("mes:mesShipScan:view")
+	@RequestMapping(value = "listData")
+	@ResponseBody
+	public Page<MesShipScan> listData(MesShipScan mesShipScan,
+			HttpServletRequest request, HttpServletResponse response) {
+		mesShipScan.setPage(new Page<>(request, response));
+		return mesShipScanService.findPage(mesShipScan);
+	}
+
+	/**
+	 * 导出 Excel
+	 */
+	@RequiresPermissions("mes:mesShipScan:view")
+	@RequestMapping(value = "exportData")
+	public void exportData(MesShipScan mesShipScan, HttpServletResponse response) {
+		List<MesShipScan> list = mesShipScanService.findList(mesShipScan);
+		String fileName = "出货扫码校验记录" + DateUtils.getDate("yyyyMMddHHmmss") + ".xlsx";
+		try (ExcelExport ee = new ExcelExport("出货扫码校验记录", MesShipScan.class)) {
+			ee.setDataList(list).write(response, fileName);
+		}
+	}
+
+	/**
+	 * 删除
+	 */
+	@RequiresPermissions("mes:mesShipScan:edit")
+	@RequestMapping(value = "delete")
+	@ResponseBody
+	public String delete(MesShipScan mesShipScan) {
+		mesShipScanService.delete(mesShipScan);
+		return renderResult(Global.TRUE, text("删除成功!"));
+	}
+
+	/**
+	 * 安卓接口:上传扫码记录并校验
+	 *
+	 * 入参(form):
+	 *   cardCode  标识卡二维码
+	 *   labelCode 标签二维码完整内容
+	 *
+	 * 返回(JSON):
+	 *   { result, matchState, message }
+	 *   matchState: "1"=一致 "0"=不一致
+	 */
+	@PostMapping(value = "upload")
+	@ResponseBody
+	public Map<String, Object> upload(String cardCode, String labelCode) {
+		Map<String, Object> resp = new LinkedHashMap<>();
+
+		if (StringUtils.isBlank(cardCode) || StringUtils.isBlank(labelCode)) {
+			resp.put("result", false);
+			resp.put("matchState", MesShipScan.MATCH_FAIL);
+			resp.put("message", "参数缺失:cardCode 与 labelCode 必填");
+			return resp;
+		}
+
+		MesShipScan log = mesShipScanService.upload(cardCode, labelCode);
+
+		resp.put("result", true);
+		resp.put("matchState", log.getMatchState());
+		resp.put("message", MesShipScan.MATCH_OK.equals(log.getMatchState())
+				? "校验通过" : log.getFailReason());
+		return resp;
+	}
+}

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

@@ -676,6 +676,7 @@ shiro:
     ${adminPath}/mes/mesProductDhbKc/check = anon
     ${adminPath}/mes/mesProductDhbKc/ping = anon
     ${adminPath}/mes/mesProductDhbKc/listData = anon
+    ${adminPath}/mes/mesShipScan/upload = anon
 
     ${adminPath}/** = user
 
@@ -1013,3 +1014,10 @@ mes:
       # 部分老服务返回 row.data 而无 prefixMap 时,按此规则把 data 注入到指定前缀
       # 格式:peer1=prefix1;peer2=prefix2
       peerPrefixHints: http://192.168.112.99:8980=+KB23
+
+  # 出货扫码校验
+  ship:
+    scan:
+      # 标签码前 N 位 == 标识卡完整内容(默认 15)
+      match:
+        length: 15

+ 5 - 0
src/main/resources/mappings/modules/mes/MesShipScanDao.xml

@@ -0,0 +1,5 @@
+<?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.MesShipScanDao">
+
+</mapper>

+ 81 - 0
src/main/resources/views/modules/mes/mesShipScanList.html

@@ -0,0 +1,81 @@
+<% 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-qrcode"></i> ${text('出货扫码校验记录')}
+			</div>
+			<div class="box-tools pull-right">
+				<a href="#" class="btn btn-default" id="btnSearch" title="${text('查询')}"><i class="fa fa-filter"></i> ${text('查询')}</a>
+				<a href="#" class="btn btn-default" id="btnExport"><i class="glyphicon glyphicon-export"></i> ${text('导出')}</a>
+				<a href="#" class="btn btn-default" id="btnSetting" title="${text('设置')}"><i class="fa fa-navicon"></i></a>
+			</div>
+		</div>
+		<div class="box-body">
+			<#form:form id="searchForm" model="${mesShipScan}" action="${ctx}/mes/mesShipScan/listData" method="post" class="form-inline hide"
+					data-page-no="${parameter.pageNo}" data-page-size="${parameter.pageSize}" data-order-by="${parameter.orderBy}">
+				<div class="form-group">
+					<label class="control-label">${text('标识卡')}:</label>
+					<div class="control-inline">
+						<#form:input path="cardCode" maxlength="128" class="form-control width-180" placeholder="如 S2510094CDE2200"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('标签码')}:</label>
+					<div class="control-inline">
+						<#form:input path="labelCode" maxlength="128" class="form-control width-220"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('状态')}:</label>
+					<div class="control-inline width-120">
+						<select name="matchState" class="form-control isQuick">
+							<option value="">${text('全部')}</option>
+							<option value="1">${text('匹配')}</option>
+							<option value="0">${text('不匹配')}</option>
+						</select>
+					</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:'cardCode', index:'a.card_code', width:170, align:"center", frozen:true},
+		{header:'${text("标签二维码")}', name:'labelCode', index:'a.label_code', width:240, align:"center"},
+		{header:'${text("匹配状态")}', name:'matchState', index:'a.match_state', width:90, align:"center", formatter: function(val, obj, row, act){
+			if (val === '1') {
+				return '<span class="label label-success">匹配</span>';
+			}
+			return '<span class="label label-danger">不匹配</span>';
+		}},
+		{header:'${text("不匹配原因")}', name:'failReason', index:'a.fail_reason', width:300, align:"left"},
+		{header:'${text("扫码时间")}', name:'createDate', index:'a.create_date', width:140, align:"center"},
+		{header:'${text("操作")}', name:'actions', width:80, formatter: function(val, obj, row, act){
+			var actions = [];
+			<% if(hasPermi('mes:mesShipScan:edit')){ %>
+				actions.push('<a href="${ctx}/mes/mesShipScan/delete?id='+row.id+'" class="btnList" title="${text("删除记录")}" data-confirm="${text("确认要删除该记录吗?")}"><i class="fa fa-trash-o"></i></a>');
+			<% } %>
+			return actions.join('');
+		}}
+	],
+	ajaxSuccess: function(data){}
+});
+
+$('#btnExport').click(function(){
+	js.ajaxSubmitForm($('#searchForm'), {
+		url:'${ctx}/mes/mesShipScan/exportData',
+		downloadFile:true
+	});
+});
+</script>