wangxichen 1 день назад
Родитель
Сommit
5ac663987d
1 измененных файлов с 472 добавлено и 0 удалено
  1. 472 0
      src/main/java/com/jeesite/modules/mes/web/MesProductPackController.java

+ 472 - 0
src/main/java/com/jeesite/modules/mes/web/MesProductPackController.java

@@ -0,0 +1,472 @@
+package com.jeesite.modules.mes.web;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+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.transaction.annotation.Transactional;
+import org.springframework.transaction.interceptor.TransactionAspectSupport;
+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.collect.MapUtils;
+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.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.file.utils.FileUploadUtils;
+import com.jeesite.modules.mes.entity.MesBatchSn;
+import com.jeesite.modules.mes.entity.MesProduct;
+import com.jeesite.modules.mes.entity.MesProductBatch;
+import com.jeesite.modules.mes.entity.MesProductHmb;
+import com.jeesite.modules.mes.entity.MesProductRecord;
+import com.jeesite.modules.mes.resp.CommonResp;
+import com.jeesite.modules.mes.service.MesBatchSnService;
+import com.jeesite.modules.mes.service.MesProductBatchService;
+import com.jeesite.modules.mes.service.MesProductHmbService;
+import com.jeesite.modules.mes.service.MesProductRecordService;
+import com.jeesite.modules.mes.service.MesProductService;
+import com.jeesite.modules.mes.util.CommonUitl;
+import com.jeesite.modules.sys.utils.UserUtils;
+
+/**
+ * 成品包装入库 / 出库(安卓 PDA)
+ * 包装码由 MES 生成(/mes/mesBatchSn/getBatchSn、createBatchSn),逻辑同原 OP265 包装:
+ *   前工位 UDF/NG 拦截 → 绑定 mes_product_batch → 更新 mes_product → OP265 记录置 OK → 包装码 nums+1,满额封箱
+ * 出库:扫包装码,包装码及包装内所有工件 state 置为 1(已出库)
+ */
+@Controller
+@RequestMapping(value = "${adminPath}/mes/mesProductPack")
+public class MesProductPackController extends BaseController {
+
+	private static final String PACK_OPRNO = "OP265";  // 包装工位
+	private static final int PACK_OP = 265;
+	private static final String CRAFT = "100000";      // 工位结果记录
+	private static final String CATE = "2";            // 成品
+	private static final String TYPE = "0";            // mes_batch_sn.type 0=成品
+	private static final String STATE_PACK = "0";      // 已包装
+	private static final String STATE_SHIP = "1";      // 已出库
+	private static final String IMG_PACK = "mesProductPack_image";
+	private static final String IMG_SHIP = "mesProductShip_image";
+
+	@Autowired
+	private MesBatchSnService mesBatchSnService;
+	@Autowired
+	private MesProductBatchService mesProductBatchService;
+	@Autowired
+	private MesProductService mesProductService;
+	@Autowired
+	private MesProductHmbService mesProductHmbService;
+	@Autowired
+	private MesProductRecordService mesProductRecordService;
+	@Autowired
+	private FileUploadService fileUploadService;
+
+	/**
+	 * 每个包装码最大包装数量(系统参数 mes.batch.count)
+	 */
+	@RequiresPermissions("mes:mesProductPack:api")
+	@RequestMapping(value = "getMaxNum")
+	@ResponseBody
+	public CommonResp<Object> getMaxNum() {
+		Map<String, Object> data = MapUtils.newHashMap();
+		data.put("maxNum", maxNum());
+		return ok("获取成功", data);
+	}
+
+	/**
+	 * 扫一个长码校验一次,不落库
+	 */
+	@RequiresPermissions("mes:mesProductPack:api")
+	@PostMapping(value = "checkSn")
+	@ResponseBody
+	public CommonResp<Object> checkSn(HttpServletRequest request) {
+		String sn = trim(request.getParameter("sn"));
+		String lineSn = trim(request.getParameter("lineSn"));
+		if (StringUtils.isAnyBlank(sn, lineSn)) {
+			return fail("参数错误");
+		}
+		String err = checkPiece(sn, findProduct(sn), lineSn);
+		return err != null ? fail(err) : ok("验证通过", null);
+	}
+
+	/**
+	 * 包装入库:MES 包装码 + 多个长码一次提交
+	 */
+	@RequiresPermissions("mes:mesProductPack:api")
+	@PostMapping(value = "packSave")
+	@ResponseBody
+	@Transactional
+	public CommonResp<Object> packSave(HttpServletRequest request) {
+		String batchSn = trim(request.getParameter("batchSn"));
+		String lineSn = trim(request.getParameter("lineSn"));
+		String products = trim(request.getParameter("products"));
+		String remark = trim(request.getParameter("remark"));
+		if (StringUtils.isAnyBlank(batchSn, lineSn, products)) {
+			return fail("参数错误");
+		}
+		List<String> codes = Arrays.stream(products.split(","))
+				.map(String::trim).filter(StringUtils::isNotBlank).collect(Collectors.toList());
+		if (codes.isEmpty()) {
+			return fail("请先扫描工件码");
+		}
+		if (new HashSet<>(codes).size() != codes.size()) {
+			return fail("工件码重复");
+		}
+
+		// 包装码必须是 MES 生成的
+		MesBatchSn batch = findBatch(batchSn);
+		String batchErr = checkBatchForPack(batch, lineSn);
+		if (batchErr != null) {
+			return fail(batchErr);
+		}
+		int max = maxNum();
+		long used = batch.getNums() == null ? 0L : batch.getNums();
+		if (used + codes.size() > max) {
+			return fail("超出最大包装量(最多" + max + "个,已装" + used + "个)");
+		}
+
+		// 逐个校验工件,全部通过才落库
+		List<MesProduct> ps = codes.stream().map(this::findProduct).collect(Collectors.toList());
+		for (int i = 0; i < codes.size(); i++) {
+			String err = checkPiece(codes.get(i), ps.get(i), lineSn);
+			if (err != null) {
+				return fail(err);
+			}
+		}
+
+		String userCode = UserUtils.getUser().getUserCode();
+		Date now = new Date();
+		for (int i = 0; i < codes.size(); i++) {
+			// 行级锁 nums+1,已满返回 0(同原 OP265,防止多台 PDA 同时装同一个包装码超量)
+			if (mesBatchSnService.incrementNums(batchSn, max, userCode) == 0) {
+				TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
+				return fail("包装码已达最大包装量(最多" + max + "个)");
+			}
+			// 包装码与长码关系
+			MesProductBatch pb = new MesProductBatch();
+			pb.setBatchSn(batchSn);
+			pb.setSn(codes.get(i));
+			pb.setCate(CATE);
+			pb.setState(STATE_PACK);
+			mesProductBatchService.save(pb);
+			// 工件记录包装码、绑定人、绑定时间、状态(同原 OP265)
+			MesProduct p = ps.get(i);
+			p.setBatchSn(batchSn);
+			p.setBindBy(userCode);
+			p.setBindDate(now);
+			p.setState("1");
+			mesProductService.save(p);
+			// OP265 工位结果置 OK
+			passPackOprno(p.getSn());
+		}
+
+		// 重新读取最新 nums,满额封箱
+		MesBatchSn latest = findBatch(batchSn);
+		long nums = latest.getNums() == null ? 0L : latest.getNums();
+		latest.setIsJs(nums >= max ? "2" : "1");
+		latest.setState(STATE_PACK);
+		if (StringUtils.isNotBlank(remark)) {
+			latest.setRemark(remark);
+		}
+		mesBatchSnService.save(latest);
+		FileUploadUtils.saveFileUpload(latest, latest.getId(), IMG_PACK);
+
+		Map<String, Object> data = MapUtils.newHashMap();
+		data.put("batchSn", batchSn);
+		data.put("nums", nums);
+		data.put("maxNum", max);
+		data.put("isJs", latest.getIsJs());
+		return ok("入库成功", data);
+	}
+
+	/**
+	 * 出库:包装码及包装内所有工件标记为已出库
+	 */
+	@RequiresPermissions("mes:mesProductPack:api")
+	@PostMapping(value = "shipSave")
+	@ResponseBody
+	@Transactional
+	public CommonResp<Object> shipSave(HttpServletRequest request) {
+		String batchSn = trim(request.getParameter("batchSn"));
+		String lineSn = trim(request.getParameter("lineSn"));
+		String remark = trim(request.getParameter("remark"));
+		if (StringUtils.isAnyBlank(batchSn, lineSn)) {
+			return fail("参数错误");
+		}
+		MesBatchSn batch = findBatch(batchSn);
+		if (batch == null) {
+			return fail("包装码不存在");
+		}
+		if (StringUtils.isNotBlank(batch.getLineSn()) && !lineSn.equals(batch.getLineSn())) {
+			return fail("该包装码属于产线" + batch.getLineSn() + ",与当前产线不一致");
+		}
+		if (STATE_SHIP.equals(batch.getState())) {
+			return fail("该包装码已出库,勿重复操作");
+		}
+		List<MesProductBatch> items = findItems(batchSn);
+		if (items.isEmpty()) {
+			return fail("该包装码未包装工件");
+		}
+
+		items.forEach(it -> {
+			it.setState(STATE_SHIP);
+			mesProductBatchService.save(it);
+		});
+		batch.setState(STATE_SHIP);
+		batch.setIsJs("2"); // 出库即封箱,getBatchSn 不会再返回该包装码
+		batch.setRemark2(remark);
+		mesBatchSnService.save(batch);
+		FileUploadUtils.saveFileUpload(batch, batch.getId(), IMG_SHIP);
+		return ok("出库成功", null);
+	}
+
+	/**
+	 * 列表(包装入库 / 出库共用)
+	 * state 不传或 0:包装列表,返回全部包装记录(含已出库,出库后包装记录不消失),按包装时间倒序
+	 * state=1:出库列表,只返回已出库,按出库时间倒序
+	 */
+	@RequiresPermissions("mes:mesProductPack:api")
+	@RequestMapping(value = "appList")
+	@ResponseBody
+	public CommonResp<Object> appList(HttpServletRequest request, HttpServletResponse response) {
+		MesBatchSn q = new MesBatchSn();
+		q.setLineSn(trim(request.getParameter("lineSn")));
+		q.setType(TYPE);
+		if (STATE_SHIP.equals(trim(request.getParameter("state")))) {
+			q.setState(STATE_SHIP);
+		} else {
+			q.getSqlMap().getOrder().setOrderBy("a.create_date DESC");
+		}
+		q.getSqlMap().getWhere().and("nums", QueryType.GT, 0);
+		q.setPage(new Page<>(request, response));
+		Page<MesBatchSn> page = mesBatchSnService.findPage(q);
+		return ok("操作成功", page);
+	}
+
+	/**
+	 * 详情:包装码信息 + 工件列表 + 包装/出库图片
+	 */
+	@RequiresPermissions("mes:mesProductPack:api")
+	@RequestMapping(value = "appDetail")
+	@ResponseBody
+	public CommonResp<Object> appDetail(HttpServletRequest request) {
+		String batchSn = trim(request.getParameter("batchSn"));
+		if (StringUtils.isBlank(batchSn)) {
+			return fail("包装码为空");
+		}
+		MesBatchSn batch = findBatch(batchSn);
+		if (batch == null) {
+			return fail("包装码不存在");
+		}
+		List<Map<String, Object>> products = findItems(batchSn).stream().map(it -> {
+			Map<String, Object> m = MapUtils.newHashMap();
+			m.put("sn", it.getSn());
+			m.put("state", StringUtils.defaultIfBlank(it.getState(), STATE_PACK));
+			m.put("createDate", fmt(it.getCreateDate()));
+			return m;
+		}).collect(Collectors.toList());
+
+		Map<String, Object> data = MapUtils.newHashMap();
+		data.put("batchSn", batch.getBatchSn());
+		data.put("lineSn", batch.getLineSn());
+		data.put("nums", batch.getNums());
+		data.put("maxNum", maxNum());
+		data.put("isJs", batch.getIsJs());
+		data.put("state", StringUtils.defaultIfBlank(batch.getState(), STATE_PACK));
+		data.put("remark", batch.getRemark());
+		data.put("remark2", batch.getRemark2());
+		data.put("createDate", fmt(batch.getCreateDate()));
+		data.put("updateDate", fmt(batch.getUpdateDate()));
+		data.put("products", products);
+		data.put("imgList", images(request, batch.getId(), IMG_PACK));
+		data.put("imgList2", images(request, batch.getId(), IMG_SHIP));
+		return ok("操作成功", data);
+	}
+
+	// ---------------- 私有方法 ----------------
+
+	// 包装码校验:必须 MES 生成、成品、同产线、未满、未出库
+	private String checkBatchForPack(MesBatchSn batch, String lineSn) {
+		if (batch == null) {
+			return "包装码不存在,请使用 MES 生成的包装码";
+		}
+		if (StringUtils.isNotBlank(batch.getType()) && !TYPE.equals(batch.getType())) {
+			return "该包装码不是成品包装码";
+		}
+		if (StringUtils.isNotBlank(batch.getLineSn()) && !lineSn.equals(batch.getLineSn())) {
+			return "该包装码属于产线" + batch.getLineSn() + ",与当前产线不一致";
+		}
+		if (STATE_SHIP.equals(batch.getState())) {
+			return "该包装码已出库,不能再包装";
+		}
+		if ("2".equals(batch.getIsJs())) {
+			return "该包装码已装满,请重新获取包装码";
+		}
+		return null;
+	}
+
+	// 单件校验(同原 OP265 包装),返回错误信息,null 表示通过
+	private String checkPiece(String code, MesProduct p, String lineSn) {
+		if (p == null) {
+			return "工件【" + code + "】不存在";
+		}
+		if ("2".equals(p.getState())) {
+			return "工件【" + code + "】返修中,不能包装";
+		}
+		if ("3".equals(p.getState())) {
+			return "工件【" + code + "】已报废,不能包装";
+		}
+		if (StringUtils.isNotBlank(p.getBatchSn())) {
+			return "工件【" + code + "】已包装(包装码:" + p.getBatchSn() + "),勿重复操作";
+		}
+		MesProductBatch q = new MesProductBatch();
+		q.setSn(code);
+		q.setCate(CATE);
+		MesProductBatch bound = mesProductBatchService.findInfo(q);
+		if (bound != null) {
+			return "工件【" + code + "】已包装(包装码:" + bound.getBatchSn() + "),勿重复操作";
+		}
+		// 产线校验:后面板码表记录了产线才校验(CZ061 短码也是 CZ060 开头,不能按前缀判断)
+		String pieceLine = hmbLineSn(code);
+		if (StringUtils.isNoneBlank(lineSn, pieceLine) && !lineSn.equals(pieceLine)) {
+			return "工件【" + code + "】属于产线" + pieceLine + ",与当前产线不一致";
+		}
+		// 前工位检查:OP265 之前的工位不能有 UDF(未加工)/ NG,跳站配置 mes.pass.oprno.{产线} 不拦截
+		List<MesProductRecord> recs = findRecords(p.getSn(), null);
+		if (recs.isEmpty()) {
+			return "工件【" + code + "】追溯记录不存在";
+		}
+		String pass = CommonUitl.getPassOprnos(lineSn);
+		for (MesProductRecord r : recs) {
+			if (opNum(r.getOprno()) >= PACK_OP || CommonUitl.matchOprnos(pass, r.getOprno())) {
+				continue;
+			}
+			if ("UDF".equals(r.getContent())) {
+				return "工件【" + code + "】工位" + r.getOprno() + "未加工,不能打包";
+			}
+			if ("NG".equals(r.getContent())) {
+				return "工件【" + code + "】工位" + r.getOprno() + "加工NG,不能打包";
+			}
+		}
+		return null;
+	}
+
+	// OP265 工位结果置 OK
+	private void passPackOprno(String shortSn) {
+		findRecords(shortSn, PACK_OPRNO).stream()
+				.filter(r -> CommonUitl.formatOprno(r.getOprno()).equals(PACK_OPRNO))
+				.forEach(r -> {
+					r.setContent("OK");
+					mesProductRecordService.save(r);
+				});
+	}
+
+	// 工位结果记录(craft=100000),oprno 为空查全部
+	private List<MesProductRecord> findRecords(String shortSn, String oprno) {
+		MesProductRecord q = new MesProductRecord();
+		q.setSn(shortSn);
+		q.setCraft(CRAFT);
+		if (oprno != null) {
+			q.setOprno(oprno);
+		}
+		return mesProductRecordService.findList(q);
+	}
+
+	// 工位号取数字,OP035B -> 35,解析失败返回 -1
+	private static int opNum(String oprno) {
+		try {
+			return Integer.parseInt(oprno.substring(2, 5));
+		} catch (Exception e) {
+			return -1;
+		}
+	}
+
+	// 长码换短码后查工件
+	private MesProduct findProduct(String code) {
+		MesProduct q = new MesProduct();
+		q.setSn(mesProductHmbService.formatHmbSn(code));
+		return mesProductService.findInfo(q);
+	}
+
+	// 长码对应的产线(mes_product_hmb.line_sn),查不到返回 null
+	private String hmbLineSn(String code) {
+		MesProductHmb q = new MesProductHmb();
+		q.setSn(code);
+		MesProductHmb hmb = mesProductHmbService.findInfo(q);
+		return hmb == null ? null : hmb.getLineSn();
+	}
+
+	private MesBatchSn findBatch(String batchSn) {
+		MesBatchSn q = new MesBatchSn();
+		q.setBatchSn(batchSn);
+		return mesBatchSnService.findInfo(q);
+	}
+
+	private List<MesProductBatch> findItems(String batchSn) {
+		MesProductBatch q = new MesProductBatch();
+		q.setBatchSn(batchSn);
+		q.setCate(CATE);
+		return mesProductBatchService.findList(q);
+	}
+
+	private List<Map<String, Object>> images(HttpServletRequest request, String bizKey, String bizType) {
+		String host = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath();
+		FileUpload fu = new FileUpload();
+		fu.setBizKey(bizKey);
+		fu.setBizType(bizType);
+		return fileUploadService.findList(fu).stream().map(fi -> {
+			Map<String, Object> m = MapUtils.newHashMap();
+			m.put("id", fi.getFileEntity().getFileId());
+			m.put("url", host + fi.getFileUrl());
+			m.put("name", fi.getFileName());
+			m.put("size", fi.getFileEntity().getFileSize());
+			return m;
+		}).collect(Collectors.toList());
+	}
+
+	private int maxNum() {
+		try {
+			return Integer.parseInt(Global.getConfig("mes.batch.count", "6").trim());
+		} catch (NumberFormatException e) {
+			return 6;
+		}
+	}
+
+	private static String trim(String s) {
+		return s == null ? null : s.trim();
+	}
+
+	private static String fmt(Date d) {
+		return d == null ? "" : DateUtils.formatDateTime(d);
+	}
+
+	private static CommonResp<Object> ok(String msg, Object data) {
+		CommonResp<Object> resp = new CommonResp<>();
+		resp.setResult(Global.TRUE);
+		resp.setMessage(msg);
+		resp.setData(data);
+		return resp;
+	}
+
+	private static CommonResp<Object> fail(String msg) {
+		CommonResp<Object> resp = new CommonResp<>();
+		resp.setResult(Global.FALSE);
+		resp.setMessage(msg);
+		return resp;
+	}
+}