From 872c77eb68dc28010b1b6dc804b2a9eadcf90d6c Mon Sep 17 00:00:00 2001 From: "han\\hanst" Date: Fri, 5 Dec 2025 11:03:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=B9=E9=87=8F=E4=BF=AE=E6=94=B9=E7=AE=B1?= =?UTF-8?q?=E6=98=8E=E7=BB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ecss/controller/CoDelController.java | 26 +- .../modules/ecss/service/CoDelService.java | 6 + .../ecss/service/impl/CoDelServiceImpl.java | 407 +++++++++++++++++- 3 files changed, 427 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/xujie/sys/modules/ecss/controller/CoDelController.java b/src/main/java/com/xujie/sys/modules/ecss/controller/CoDelController.java index c0f13736..623a6e40 100644 --- a/src/main/java/com/xujie/sys/modules/ecss/controller/CoDelController.java +++ b/src/main/java/com/xujie/sys/modules/ecss/controller/CoDelController.java @@ -261,6 +261,24 @@ public class CoDelController { return R.ok(); } + /** + * @Author System + * @Description 批量修改装箱信息(Box和明细) + * @Date 2025/12/05 + * @Param [batchData] 包含boxList和detailList的批量更新数据 + * @return com.xujie.sys.common.utils.R + **/ + @PostMapping("/batchUpdatePackingInfo") + @ResponseBody + public R batchUpdatePackingInfo(@RequestBody Map batchData){ + try { + coDelService.batchUpdatePackingInfo(batchData); + return R.ok(); + } catch (Exception e) { + return R.error("批量修改失败: " + e.getMessage()); + } + } + @PostMapping("/saveCoDelPalletDataByExcel") public R saveCoDelPalletDataByExcel(@RequestParam(value = "file") MultipartFile file, @RequestParam(value = "palletRecords", required = false) String palletRecords, @@ -590,7 +608,7 @@ public class CoDelController { String site = (String) params.get("site"); String buNo = (String) params.get("buNo"); String partNo = (String) params.get("partNo"); - + // 参数校验 if (site == null || site.trim().isEmpty()) { return R.error("站点(site)不能为空"); @@ -601,16 +619,16 @@ public class CoDelController { if (partNo == null || partNo.trim().isEmpty()) { return R.error("物料编号(partNo)不能为空"); } - + Map result = coDelService.getPartPackageProperties(site, buNo, partNo); - + // 根据业务结果返回 Boolean success = (Boolean) result.get("success"); if (success != null && !success) { String message = (String) result.get("message"); return R.error(message != null ? message : "获取物料包装属性失败"); } - + return R.ok().put("data", result); } diff --git a/src/main/java/com/xujie/sys/modules/ecss/service/CoDelService.java b/src/main/java/com/xujie/sys/modules/ecss/service/CoDelService.java index d36eac73..f5d5e990 100644 --- a/src/main/java/com/xujie/sys/modules/ecss/service/CoDelService.java +++ b/src/main/java/com/xujie/sys/modules/ecss/service/CoDelService.java @@ -70,6 +70,12 @@ public interface CoDelService { void deleteDetailInfo(EcssCoDelPalletData detailData); + /** + * 批量修改装箱信息(Box和明细) + * @param batchData 包含boxList和detailList的批量更新数据 + */ + void batchUpdatePackingInfo(Map batchData); + //void deleteEmptyBoxAfterDetailDelete(EcssCoDelPalletData detailData); void saveCoDelPalletDataByExcel(MultipartFile file, EcssCoDelNotifyHeaderData data, String palletRecords); diff --git a/src/main/java/com/xujie/sys/modules/ecss/service/impl/CoDelServiceImpl.java b/src/main/java/com/xujie/sys/modules/ecss/service/impl/CoDelServiceImpl.java index 4d1eb560..25713ed7 100644 --- a/src/main/java/com/xujie/sys/modules/ecss/service/impl/CoDelServiceImpl.java +++ b/src/main/java/com/xujie/sys/modules/ecss/service/impl/CoDelServiceImpl.java @@ -665,11 +665,28 @@ public class CoDelServiceImpl implements CoDelService { // 使用动态索引读取数据(必填列) task.setCustomerPO(getStringCellValue(row, poIdx)); task.setPn(getStringCellValue(row, pnIdx)); - task.setQty(getNumericCellValueOrDefault(row, qtyIdx)); + + // 读取Qty (pcs),捕获格式错误 + try { + task.setQty(getNumericCellValueOrDefault(row, qtyIdx, "Qty (pcs)")); + } catch (RuntimeException e) { + currentSheetError.addErrorDetail("第" + (j+1) + "行的 " + e.getMessage()); + hasRowError = true; + continue; + } + task.setDestination(getStringCellValue(row, destinationIdx)); task.setShippingMode(getStringCellValue(row, shippingModeIdx)); task.setCurrency(getStringCellValue(row, currencyIdx)); - task.setTp(getNumericCellValueOrDefault(row, tpIdx)); + + // 读取TP,捕获格式错误 + try { + task.setTp(getNumericCellValueOrDefault(row, tpIdx, "TP")); + } catch (RuntimeException e) { + currentSheetError.addErrorDetail("第" + (j+1) + "行的 " + e.getMessage()); + hasRowError = true; + continue; + } // 处理CMC Invoice:如果CMC Invoice为空/0/Excel错误/超过20位则取Shipping Number,如果两者都为空则报错 String cmcInvoiceValue = getStringCellValueSafe(row, columnMap, "CMC Invoice"); @@ -974,11 +991,30 @@ public class CoDelServiceImpl implements CoDelService { return -1; } + /** + * 获取数值单元格的值(无列名版本,保持向后兼容,遇到不支持的类型返回null) + */ private BigDecimal getNumericCellValueOrDefault(XSSFRow row, int columnIndex) { + return getNumericCellValueOrDefault(row, columnIndex, null); + } + + /** + * 获取数值单元格的值 + * @param row Excel行 + * @param columnIndex 列索引 + * @param columnName 列名(用于错误提示,如 "Qty (pcs)"、"TP" 等必填列会抛出异常,其他列返回null) + * @return BigDecimal值 + */ + private BigDecimal getNumericCellValueOrDefault(XSSFRow row, int columnIndex, String columnName) { Cell cell = row.getCell(columnIndex); if (cell == null || cell.getCellType() == CellType.BLANK) { return null; } + + // 必填数字列列表 + boolean isRequiredColumn = columnName != null && + ("Qty (pcs)".equalsIgnoreCase(columnName) || "TP".equalsIgnoreCase(columnName)); + switch (cell.getCellType()) { case NUMERIC: BigDecimal value = BigDecimal.valueOf(cell.getNumericCellValue()); @@ -991,7 +1027,10 @@ public class CoDelServiceImpl implements CoDelService { BigDecimal stringValue = new BigDecimal(cell.getStringCellValue()); return stringValue.setScale(6, RoundingMode.HALF_UP); // 四舍五入保留四位小数 } catch (NumberFormatException e) { - throw new RuntimeException("无效的数值格式: " + cell.getStringCellValue()); + if (isRequiredColumn) { + throw new RuntimeException("[" + columnName + "] 列无效的数值格式: " + cell.getStringCellValue()); + } + return null; } case FORMULA: // 获取缓存结果 @@ -1007,12 +1046,21 @@ public class CoDelServiceImpl implements CoDelService { BigDecimal stringValue = new BigDecimal(cell.getStringCellValue()); return stringValue.setScale(6, RoundingMode.HALF_UP); // 四舍五入保留四位小数 } catch (NumberFormatException e) { - throw new RuntimeException("无效的数值格式: " + cell.getStringCellValue()); + if (isRequiredColumn) { + throw new RuntimeException("[" + columnName + "] 列无效的数值格式: " + cell.getStringCellValue()); + } + return null; } - default:return null; + default: + return null; } default: - throw new RuntimeException("不支持的单元格类型: " + cell.getCellType()); + // 如果是必填列,抛出异常提示 + if (isRequiredColumn) { + throw new RuntimeException("[" + columnName + "] 列不支持的单元格类型: " + cell.getCellType()); + } + // 其他非必填列返回null + return null; } } @@ -1726,7 +1774,7 @@ public class CoDelServiceImpl implements CoDelService { throw new RuntimeException("导入失败:物料:" + excelData.getPn() + "不存在!"); } excelData.setPartNo(parts.get(0).getPartNo()); - excelData.setQty(getNumericCellValueOrDefault(row, 7)); + excelData.setQty(getNumericCellValueOrDefault(row, 7, "Qty (pcs)")); excelData.setBoxQty(getNumericCellValueOrDefault(row, 2)); excelData.setRolls(getNumericCellValueOrDefault(row, 8)); excelData.setGrossWeight(getNumericCellValueOrDefault(row, 3)); @@ -4024,7 +4072,7 @@ public class CoDelServiceImpl implements CoDelService { task.setBuNo(inData.getBuNo()); // bu task.setSku(getStringCellValue(row, 0)); task.setSo(getStringCellValue(row, 1)); - task.setQty(getNumericCellValueOrDefault(row, 2)); + task.setQty(getNumericCellValueOrDefault(row, 2, "Qty (pcs)")); List orderDataList = sqlSession.selectList("ecssMapper" + "." + "searchWalMartOrderList", task); if (!orderDataList.isEmpty()) { if (sb.toString().length()>1) { @@ -4424,6 +4472,349 @@ public class CoDelServiceImpl implements CoDelService { } } + /** + * 批量修改装箱信息(Box和明细) + * + * @param batchData 包含boxList和detailList的批量更新数据 + */ + @Override + @Transactional + public void batchUpdatePackingInfo(Map batchData) { + String site = (String) batchData.get("site"); + String buNo = (String) batchData.get("buNo"); + String delNo = (String) batchData.get("delNo"); + String updateBy = (String) batchData.get("updateBy"); + + @SuppressWarnings("unchecked") + List> boxList = (List>) batchData.get("boxList"); + @SuppressWarnings("unchecked") + List> detailList = (List>) batchData.get("detailList"); + + log.info("=== 开始批量修改装箱信息 ==="); + log.info("发货单号: {}, Box数量: {}, 明细数量: {}", + delNo, + boxList != null ? boxList.size() : 0, + detailList != null ? detailList.size() : 0); + + // 获取发货通知单信息 + EcssCoDelNotifyHeaderData notifyHeader = coDelMapper.getEcssCoDelNotifyHeader(site, delNo); + boolean isCustomsCleared = "已报关".equals(notifyHeader.getNotifyStatus()); + + // 保存修改前的数据(用于邮件对比) + List oldBoxList = coDelMapper.selectBoxList(notifyHeader); + EcssCoDelPalletHeaderData queryData = new EcssCoDelPalletHeaderData(); + queryData.setSite(site); + queryData.setBuNo(buNo); + queryData.setDelNo(delNo); + List oldPalletDetailList = coDelMapper.searchEcssCoDelPalletDetailData(queryData); + + // 记录修改内容 + List> boxChanges = new ArrayList<>(); + List> detailChanges = new ArrayList<>(); + + // 1. 处理Box修改 + if (boxList != null && !boxList.isEmpty()) { + for (Map box : boxList) { + Map boxChange = new HashMap<>(); + boxChange.put("item_no", box.get("item_no")); + + // 获取修改前的Box数据 + Map oldBox = oldBoxList.stream() + .filter(b -> String.valueOf(((Map)b).get("item_no")).equals(String.valueOf(box.get("item_no")))) + .findFirst().orElse(null); + + if (oldBox != null) { + // 记录变更 + if (box.containsKey("box_qty")) { + boxChange.put("box_qty_old", oldBox.get("box_qty")); + boxChange.put("box_qty_new", box.get("box_qty")); + } + if (box.containsKey("grossWeight")) { + boxChange.put("grossWeight_old", oldBox.get("grossWeight")); + boxChange.put("grossWeight_new", box.get("grossWeight")); + } + if (box.containsKey("netWeight")) { + boxChange.put("netWeight_old", oldBox.get("netWeight")); + boxChange.put("netWeight_new", box.get("netWeight")); + } + } + boxChanges.add(boxChange); + + // 执行更新 + Map updateParams = new HashMap<>(); + updateParams.put("site", site); + updateParams.put("buNo", buNo); + updateParams.put("delNo", delNo); + updateParams.put("item_no", box.get("item_no")); + updateParams.put("palletRemark", box.get("palletRemark")); + updateParams.put("box_qty", box.get("box_qty")); + updateParams.put("grossWeight", box.get("grossWeight")); + updateParams.put("netWeight", box.get("netWeight")); + updateParams.put("updateBy", updateBy); + + coDelMapper.updateBoxInfo(updateParams); + log.info("更新Box: item_no={}", box.get("item_no")); + } + } + + // 2. 处理明细修改 + if (detailList != null && !detailList.isEmpty()) { + for (Map detail : detailList) { + Map detailChange = new HashMap<>(); + detailChange.put("seqNo", detail.get("seqNo")); + detailChange.put("itemNo", detail.get("itemNo")); + detailChange.put("poNo", detail.get("poNo")); + detailChange.put("pn", detail.get("pn")); + + // 获取修改前的明细数据 + EcssCoDelPalletDetailData oldDetail = oldPalletDetailList.stream() + .filter(d -> String.valueOf(d.getSeqNo()).equals(String.valueOf(detail.get("seqNo"))) + && String.valueOf(d.getItemNo()).equals(String.valueOf(detail.get("itemNo")))) + .findFirst().orElse(null); + + BigDecimal oldQty = null; + if (oldDetail != null) { + oldQty = oldDetail.getQty(); + // 记录变更 + if (detail.containsKey("qty")) { + detailChange.put("qty_old", oldDetail.getQty()); + detailChange.put("qty_new", detail.get("qty")); + } + if (detail.containsKey("rolls")) { + detailChange.put("rolls_old", oldDetail.getRolls()); + detailChange.put("rolls_new", detail.get("rolls")); + } + } + detailChanges.add(detailChange); + + // 构造更新参数 + EcssCoDelPalletData detailData = new EcssCoDelPalletData(); + detailData.setSite(site); + detailData.setBuNo(buNo); + detailData.setDelNo(delNo); + detailData.setSeqNo(detail.get("seqNo") != null ? Integer.valueOf(detail.get("seqNo").toString()) : null); + detailData.setItemNo(detail.get("itemNo") != null ? Integer.valueOf(detail.get("itemNo").toString()) : null); + detailData.setNotifyDetailItemNo(detail.get("notifyDetailItemNo") != null ? Integer.valueOf(detail.get("notifyDetailItemNo").toString()) : null); + detailData.setPoNo((String) detail.get("poNo")); + detailData.setPn((String) detail.get("pn")); + detailData.setQty(detail.get("qty") != null ? new BigDecimal(detail.get("qty").toString()) : null); + detailData.setOldQty(oldQty); + detailData.setRolls(detail.get("rolls") != null ? new BigDecimal(detail.get("rolls").toString()) : null); + detailData.setUpdateBy(updateBy); + + // 执行更新 + coDelMapper.updateDetailInfo(detailData); + + // 更新发货通知单明细的剩余可装箱数量 + Integer notifyDetailItemNo = detailData.getNotifyDetailItemNo(); + BigDecimal qty = detailData.getQty(); + if (notifyDetailItemNo != null && qty != null && oldQty != null) { + EcssCoDelNotifyDetailData notifyDetail = coDelMapper.getEcssCoDelNotifyDetailByItemNo( + site, buNo, delNo, notifyDetailItemNo + ); + if (notifyDetail != null) { + BigDecimal currentSurplusQty = notifyDetail.getSurplusQty() != null ? notifyDetail.getSurplusQty() : BigDecimal.ZERO; + BigDecimal newSurplusQty = currentSurplusQty.add(oldQty).subtract(qty); + notifyDetail.setSurplusQty(newSurplusQty); + coDelMapper.updateEcssCoDelNotifyDetailSurplus(notifyDetail); + } + } + + log.info("更新明细: seqNo={}, itemNo={}", detail.get("seqNo"), detail.get("itemNo")); + } + } + + log.info("=== 批量修改装箱信息完成 === Box: {}, 明细: {}", boxChanges.size(), detailChanges.size()); + + // 3. 如果状态为已报关,发送邮件通知 + if (isCustomsCleared && (!boxChanges.isEmpty() || !detailChanges.isEmpty())) { + sendBatchUpdateNotificationEmail(notifyHeader, boxChanges, detailChanges, updateBy); + } + } + + /** + * 发送批量修改通知邮件 + * + * @param notifyHeader 发货通知单头数据 + * @param boxChanges Box修改记录 + * @param detailChanges 明细修改记录 + * @param updateBy 修改人 + */ + private void sendBatchUpdateNotificationEmail(EcssCoDelNotifyHeaderData notifyHeader, + List> boxChanges, + List> detailChanges, + String updateBy) { + try { + log.info("开始发送批量修改通知邮件,发货通知单号:{}", notifyHeader.getDelNo()); + + // 生成邮件内容 + String emailContent = generateBatchUpdateEmailContent(notifyHeader, boxChanges, detailChanges, updateBy); + + // 获取发货通知单创建人邮箱 + SysUserEntity creator = coDelMapper.queryByUserName(notifyHeader.getCreateBy()); + if (creator == null || StringUtils.isBlank(creator.getEmail())) { + log.warn("发货通知单创建人{}不存在或没有配置邮箱地址", notifyHeader.getCreateBy()); + return; + } + String creatorEmail = creator.getEmail(); + + // 发送邮件 + String subject = String.format("发货通知单%s【发票:%s】批量修改通知", + notifyHeader.getDelNo(), notifyHeader.getCmcInvoice()); + String[] mailAddress = {creatorEmail}; + + sendMailUtil(subject, emailContent, mailAddress, notifyHeader); + + log.info("批量修改通知邮件发送成功,收件人:{}", creatorEmail); + + } catch (Exception e) { + log.error("发送批量修改通知邮件失败,发货通知单号:{}, 错误信息:{}", + notifyHeader.getDelNo(), e.getMessage(), e); + } + } + + /** + * 生成批量修改邮件内容 + * + * @param notifyHeader 发货通知单头数据 + * @param boxChanges Box修改记录 + * @param detailChanges 明细修改记录 + * @param updateBy 修改人 + * @return HTML格式的邮件内容 + */ + private String generateBatchUpdateEmailContent(EcssCoDelNotifyHeaderData notifyHeader, + List> boxChanges, + List> detailChanges, + String updateBy) { + StringBuilder emailContent = new StringBuilder(); + + emailContent.append(""); + emailContent.append(""); + emailContent.append(""); + emailContent.append(""); + emailContent.append(""); + emailContent.append(""); + emailContent.append(""); + + // 邮件标题 + emailContent.append("

📦 发货通知单批量修改通知

"); + + // 基本信息 + emailContent.append("
"); + emailContent.append("

发货通知单号:").append(notifyHeader.getDelNo()).append("

"); + emailContent.append("

发票号:").append(notifyHeader.getCmcInvoice()).append("

"); + emailContent.append("

客户:").append(notifyHeader.getCustomerName() != null ? notifyHeader.getCustomerName() : "-").append("

"); + emailContent.append("

修改人:").append(updateBy).append("

"); + emailContent.append("

修改时间:").append(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date())).append("

"); + emailContent.append("
"); + + // Box修改记录 + if (boxChanges != null && !boxChanges.isEmpty()) { + emailContent.append("
📋 箱信息修改(共 ").append(boxChanges.size()).append(" 条)
"); + emailContent.append(""); + emailContent.append(""); + + for (Map change : boxChanges) { + emailContent.append(""); + emailContent.append(""); + + // 箱数 + emailContent.append(""); + + // 毛重 + emailContent.append(""); + + // 净重 + emailContent.append(""); + + emailContent.append(""); + } + emailContent.append("
序号箱数毛重净重
").append(change.get("item_no")).append(""); + if (change.containsKey("box_qty_old") && change.containsKey("box_qty_new")) { + emailContent.append("").append(change.get("box_qty_old")).append(""); + emailContent.append(""); + emailContent.append("").append(change.get("box_qty_new")).append(""); + } else { + emailContent.append("-"); + } + emailContent.append(""); + if (change.containsKey("grossWeight_old") && change.containsKey("grossWeight_new")) { + emailContent.append("").append(change.get("grossWeight_old")).append(""); + emailContent.append(""); + emailContent.append("").append(change.get("grossWeight_new")).append(""); + } else { + emailContent.append("-"); + } + emailContent.append(""); + if (change.containsKey("netWeight_old") && change.containsKey("netWeight_new")) { + emailContent.append("").append(change.get("netWeight_old")).append(""); + emailContent.append(""); + emailContent.append("").append(change.get("netWeight_new")).append(""); + } else { + emailContent.append("-"); + } + emailContent.append("
"); + } + + // 明细修改记录 + if (detailChanges != null && !detailChanges.isEmpty()) { + emailContent.append("
📝 明细信息修改(共 ").append(detailChanges.size()).append(" 条)
"); + emailContent.append(""); + emailContent.append(""); + + for (Map change : detailChanges) { + emailContent.append(""); + emailContent.append(""); + emailContent.append(""); + emailContent.append(""); + + // 数量 + emailContent.append(""); + + // Rolls + emailContent.append(""); + + emailContent.append(""); + } + emailContent.append("
序号POPN数量Rolls
").append(change.get("seqNo")).append("-").append(change.get("itemNo")).append("").append(change.get("poNo") != null ? change.get("poNo") : "-").append("").append(change.get("pn") != null ? change.get("pn") : "-").append(""); + if (change.containsKey("qty_old") && change.containsKey("qty_new")) { + emailContent.append("").append(change.get("qty_old")).append(""); + emailContent.append(""); + emailContent.append("").append(change.get("qty_new")).append(""); + } else { + emailContent.append("-"); + } + emailContent.append(""); + if (change.containsKey("rolls_old") && change.containsKey("rolls_new")) { + emailContent.append("").append(change.get("rolls_old")).append(""); + emailContent.append(""); + emailContent.append("").append(change.get("rolls_new")).append(""); + } else { + emailContent.append("-"); + } + emailContent.append("
"); + } + + // 页脚 + emailContent.append("
"); + emailContent.append("

此邮件由系统自动发送,请勿直接回复。如有疑问请联系相关人员。

"); + + emailContent.append(""); + emailContent.append(""); + + return emailContent.toString(); + } + @Override public List> getCustomerTemplateList(Map params) { return coDelMapper.getCustomerTemplateList(params);