diff --git a/src/main/java/com/xujie/sys/modules/ecss/service/impl/CoDelExcelServiceImpl.java b/src/main/java/com/xujie/sys/modules/ecss/service/impl/CoDelExcelServiceImpl.java index 46845099..4ca3311d 100644 --- a/src/main/java/com/xujie/sys/modules/ecss/service/impl/CoDelExcelServiceImpl.java +++ b/src/main/java/com/xujie/sys/modules/ecss/service/impl/CoDelExcelServiceImpl.java @@ -1357,103 +1357,210 @@ public class CoDelExcelServiceImpl implements CoDelExcelService { deleteEmptyRow(sheet); // 获取行数 int rows = sheet.getPhysicalNumberOfRows(); - // 遍历每一行(从第二行开始) - int groupSeqNo = 0; - BigDecimal boxQty = BigDecimal.ZERO; - BigDecimal grossWeight = BigDecimal.ZERO; - BigDecimal netWeight = BigDecimal.ZERO; + + // 根据图片分析Excel列位置: + // 列A (索引0): 序号 (itemNo) - 对应发货通知单明细的序号 + // 列B (索引1): 箱数 (boxQty) - 需要检测是否合并来判断是否合箱 + // 列C (索引2): SKU/PN + // 列D (索引3): SO + // 列E (索引4): 数量 (qty) + // 列F (索引5): 卷数 (rolls) + // 列G (索引6): 毛重 (grossWeight) + // 列H (索引7): 发票号 (invoice) + + // 遍历每一行(从第二行开始,第一行是表头) + int groupSeqNo = 0; // 箱子的组号 for (int j = 1; j < rows; j++) { - // 创建对象 - EcssCoDelPalletData excelData = new EcssCoDelPalletData(); // 获得该行 XSSFRow row = sheet.getRow(j); - if (row.getCell(1) != null && !inData.getCmcInvoice().equals(row.getCell(1).getStringCellValue())) { + if (row == null) { continue; } - if (row.getCell(6) == null) { - throw new RuntimeException("第" + j + "行的PN不能为空!"); + + // 验证发票号是否匹配(列H,索引7) + if (row.getCell(7) != null && !inData.getCmcInvoice().equals(getStringCellValue(row, 7))) { + continue; + } + + // 验证必填字段 + if (row.getCell(0) == null) { + throw new RuntimeException("第" + (j + 1) + "行的序号不能为空!"); } - if (row.getCell(7) == null) { - throw new RuntimeException("第" + j + "行的数量不能为空!"); + if (row.getCell(2) == null) { + throw new RuntimeException("第" + (j + 1) + "行的SKU/PN不能为空!"); } + if (row.getCell(4) == null) { + throw new RuntimeException("第" + (j + 1) + "行的数量不能为空!"); + } + + // 创建对象 + EcssCoDelPalletData excelData = new EcssCoDelPalletData(); + // 为对象赋值 - excelData.setSite(inData.getSite()); // site - excelData.setBuNo(inData.getBuNo()); // bu + excelData.setSite(inData.getSite()); + excelData.setBuNo(inData.getBuNo()); excelData.setDelNo(inData.getDelNo()); - excelData.setPoNo(getStringCellValue(row, 5)); - excelData.setPn(getStringCellValue(row, 6)); - List parts = coDelMapper.getPartNo(excelData.getSite(), excelData.getPn(),currentUser.getUsername(),inData.getBuNo()); - if (parts.isEmpty()) { - throw new RuntimeException("导入失败:物料:" + excelData.getPn() + "不存在!"); + + // 读取序号(对应发货通知单明细的itemNo) + Integer notifyDetailItemNo = getIntegerCellValue(row, 0); + excelData.setNotifyDetailItemNo(notifyDetailItemNo); + + // 读取SKU/PN + excelData.setPn(getStringCellValue(row, 2)); + + // 读取SO + //excelData.setPoNo(getStringCellValue(row, 3)); + + // 读取数量 + excelData.setQty(getNumericCellValueOrDefault(row, 4, "数量")); + + // 读取箱数(列B,索引1)- 用于判断是否合箱 + BigDecimal boxQty = getNumericCellValueOrDefault(row, 1); + + // 读取卷数(列F,索引5) + BigDecimal rolls = getNumericCellValueOrDefault(row, 5); + + // 读取毛重(列G,索引6) + BigDecimal grossWeight = getNumericCellValueOrDefault(row, 6); + + // 验证合并单元格的一致性:如果箱数列合并,卷数和毛重列也必须合并 + boolean boxQtyMerged = isCellInMergedRegion(sheet, j, 1); + boolean rollsMerged = isCellInMergedRegion(sheet, j, 5); + boolean grossWeightMerged = isCellInMergedRegion(sheet, j, 6); + + if (boxQtyMerged != rollsMerged || boxQtyMerged != grossWeightMerged) { + throw new RuntimeException("第" + (j + 1) + "行错误:箱数、卷数、毛重列的合并状态必须一致!" + + "箱数列" + (boxQtyMerged ? "已合并" : "未合并") + + ",卷数列" + (rollsMerged ? "已合并" : "未合并") + + ",毛重列" + (grossWeightMerged ? "已合并" : "未合并")); } - excelData.setPartNo(parts.get(0).getPartNo()); - excelData.setQty(getNumericCellValueOrDefault(row, 7, "Qty (pcs)")); - excelData.setBoxQty(getNumericCellValueOrDefault(row, 2)); - excelData.setRolls(getNumericCellValueOrDefault(row, 8)); - excelData.setGrossWeight(getNumericCellValueOrDefault(row, 3)); - excelData.setNetWeight(getNumericCellValueOrDefault(row, 4)); - if (excelData.getBoxQty() == null && excelData.getGrossWeight() == null && excelData.getNetWeight() == null) { - excelData.setGroupSeqNo(groupSeqNo); + + // 判断是否是新的箱子: + // 1. 如果箱数列有值且不在合并区域中,或者是合并区域的第一行,则是新箱子 + // 2. 如果箱数列没有值或者在合并区域中但不是第一行,则属于上一个箱子 + if (boxQty != null && !isMergedCellButNotFirst(sheet, j, 1)) { + // 新的箱子 + groupSeqNo++; excelData.setBoxQty(boxQty); + excelData.setRolls(rolls); excelData.setGrossWeight(grossWeight); - excelData.setNetWeight(netWeight); } else { - groupSeqNo++; - excelData.setGroupSeqNo(groupSeqNo); - boxQty = excelData.getBoxQty(); - grossWeight = excelData.getGrossWeight(); - netWeight = excelData.getNetWeight(); + // 属于上一个箱子(合箱),使用上一行的箱数、卷数、毛重 + if (j > 1) { + // 从上一个已添加的数据获取信息 + if (!excelList.isEmpty()) { + EcssCoDelPalletData lastData = excelList.get(excelList.size() - 1); + excelData.setBoxQty(lastData.getBoxQty()); + excelData.setRolls(lastData.getRolls()); + excelData.setGrossWeight(lastData.getGrossWeight()); + } + } } + excelData.setGroupSeqNo(groupSeqNo); + + // 验证物料是否存在 + List parts = coDelMapper.getPartNo(excelData.getSite(), excelData.getPn(), + currentUser.getUsername(), inData.getBuNo()); + if (parts.isEmpty()) { + throw new RuntimeException("导入失败:第" + (j + 1) + "行,物料:" + excelData.getPn() + "不存在!"); + } + excelData.setPartNo(parts.get(0).getPartNo()); + + // 验证物料在当前工厂是否存在 List checkPart = coDelMapper.checkPart(excelData.getSite(), excelData.getPartNo()); if (checkPart.isEmpty()) { - throw new RuntimeException("导入失败:物料:" + excelData.getPartNo() + "在当前工厂不存在!"); + throw new RuntimeException("导入失败:第" + (j + 1) + "行,物料:" + excelData.getPartNo() + "在当前工厂不存在!"); } + excelList.add(excelData); } + + // 验证导入数据的序号和数量是否与发货通知单明细匹配 + validateImportDataWithNotifyDetail(excelList, inData); + + // 获取发货通知单明细,用于填充PO等信息 + List notifyDetailList = coDelMapper.searchEcssCoDelNotifyDetail(inData); + Map notifyDetailMap = new HashMap<>(); + for (EcssCoDelNotifyDetailData detailData : notifyDetailList) { + notifyDetailMap.put(detailData.getItemNo(), detailData); + } + + // 根据序号从发货通知单明细中获取PO等信息 + for (EcssCoDelPalletData palletData : excelList) { + EcssCoDelNotifyDetailData notifyDetail = notifyDetailMap.get(palletData.getNotifyDetailItemNo()); + if (notifyDetail != null) { + // 从发货通知单明细中获取PO + palletData.setPoNo(notifyDetail.getCustomerPO()); + } + } + } catch (NullPointerException e) { throw new RuntimeException("导入失败:网络错误,请重新上传文档"); } catch (Exception e) { throw new RuntimeException("导入失败:" + e.getMessage()); } - // 如果是沃尔玛订单,按照pn分组,同一个pn是一个EcssCoDelPalletHeaderData, - // 如果是非沃尔玛订单,按照序号分组,同一个序号是一个EcssCoDelPalletHeaderData, - Map> palletListMap = new HashMap<>(); - Map> palletListMap2 = new HashMap<>(); - for (EcssCoDelPalletData itemData : excelList){ - if (palletListMap2.containsKey(itemData.getGroupSeqNo())) { - palletListMap2.get(itemData.getGroupSeqNo()).add(itemData); + + // 按groupSeqNo分组,同一个groupSeqNo是同一个箱子 + Map> palletListMap = new LinkedHashMap<>(); + for (EcssCoDelPalletData itemData : excelList) { + if (palletListMap.containsKey(itemData.getGroupSeqNo())) { + palletListMap.get(itemData.getGroupSeqNo()).add(itemData); } else { List palletDataList = new ArrayList<>(); palletDataList.add(itemData); - palletListMap2.put(itemData.getGroupSeqNo(),palletDataList); + palletListMap.put(itemData.getGroupSeqNo(), palletDataList); } } - EcssWalMartOrder task = new EcssWalMartOrder(); - List headerList = new ArrayList<>(); + + // 保存装箱数据 List boxList = new ArrayList<>(); List detailList = new ArrayList<>(); int seqNo = 0; - for (Map.Entry> entry : palletListMap2.entrySet()) { + + for (Map.Entry> entry : palletListMap.entrySet()) { seqNo++; + + // 保存箱子信息 EcssCoDelBoxListData boxListData = new EcssCoDelBoxListData(); boxListData.setSite(inData.getSite()); boxListData.setBuNo(inData.getBuNo()); boxListData.setDelNo(inData.getDelNo()); boxListData.setItemNo(seqNo); - boxListData.setGrossWeight(entry.getValue().get(0).getGrossWeight()); - boxListData.setNetWeight(entry.getValue().get(0).getNetWeight()); - boxListData.setBoxQty(entry.getValue().get(0).getBoxQty()); + + // 箱数:取第一条记录的箱数(同一箱内所有明细的箱数都相同) + BigDecimal boxQty = entry.getValue().get(0).getBoxQty(); + boxListData.setBoxQty(boxQty); + + // 毛重:如果是合箱,则毛重已经在合并单元格中,直接取第一条记录的毛重 + // 如果不是合箱,每行有各自的毛重,不需要汇总(因为一行就是一个箱子) + BigDecimal grossWeight = entry.getValue().get(0).getGrossWeight(); + boxListData.setGrossWeight(grossWeight); + + // 卷数:如果是合箱,则毛重已经在合并单元格中,直接取第一条记录的卷数 + // 如果不是合箱,每行有各自的卷数,不需要汇总(因为一行就是一个箱子) + BigDecimal totalRolls = entry.getValue().get(0).getRolls(); + boxListData.setRolls(totalRolls); + // 净重计算:净重 = 毛重 - 箱数/2 + BigDecimal netWeight = BigDecimal.ZERO; + if (grossWeight != null && boxQty != null) { + netWeight = grossWeight.subtract(boxQty.divide(new BigDecimal("2"), 6, RoundingMode.HALF_UP)); + } + boxListData.setNetWeight(netWeight); + boxListData.setCreateBy(currentUser.getUsername()); boxList.add(boxListData); - int i=0; + + // 保存箱子内的明细 + int itemNo = 0; for (EcssCoDelPalletData thisData : entry.getValue()) { + itemNo++; EcssCoDelPalletDetailData detailData = new EcssCoDelPalletDetailData(); detailData.setSite(thisData.getSite()); detailData.setBuNo(thisData.getBuNo()); detailData.setDelNo(thisData.getDelNo()); - detailData.setSeqNo(seqNo); - detailData.setItemNo(i + 1); - i++; + detailData.setSeqNo(seqNo); // 箱号 + detailData.setItemNo(itemNo); // 箱内明细行号 + detailData.setNotifyDetailItemNo(thisData.getNotifyDetailItemNo()); // 对应的发货通知单明细序号 detailData.setPartNo(thisData.getPartNo()); detailData.setPn(thisData.getPn()); detailData.setQty(thisData.getQty()); @@ -1464,32 +1571,16 @@ public class CoDelExcelServiceImpl implements CoDelExcelService { detailList.add(detailData); } } + + // 保存到数据库 for (EcssCoDelBoxListData boxData : boxList) { coDelMapper.saveCodelBoxList(boxData); } - Map palletDetailMap = new HashMap<>(); - for (EcssCoDelPalletDetailData ecssCoDelPalletDetailData : detailList) { - coDelMapper.saveCodelPalletDetail(ecssCoDelPalletDetailData); - if (!palletDetailMap.containsKey(ecssCoDelPalletDetailData.getPn())) { - palletDetailMap.put(ecssCoDelPalletDetailData.getPn(), ecssCoDelPalletDetailData.getQty()); - } else { - palletDetailMap.put(ecssCoDelPalletDetailData.getPn(), palletDetailMap.get(ecssCoDelPalletDetailData.getPn()).add(ecssCoDelPalletDetailData.getQty())); - } - } - List ecssCoDelNotifyDetail = coDelMapper.searchEcssCoDelNotifyDetail(inData); - Map notifyDetailMap = new HashMap<>(); - for (EcssCoDelNotifyDetailData detailData : ecssCoDelNotifyDetail) { - if (!notifyDetailMap.containsKey(detailData.getPn())) { - notifyDetailMap.put(detailData.getPn(), detailData.getQty()); - } else { - notifyDetailMap.put(detailData.getPn(), notifyDetailMap.get(detailData.getPn()).add(detailData.getQty())); - } - } - for (Map.Entry entry : palletDetailMap.entrySet()) { - if (notifyDetailMap.get(entry.getKey()).compareTo(entry.getValue())!=0) { - throw new RuntimeException("物料["+entry.getKey()+"]的数量和发货通知单数量不一致!"); - } + + for (EcssCoDelPalletDetailData detailData : detailList) { + coDelMapper.saveCodelPalletDetail(detailData); } + // 处理栈板记录 palletHeaderSave(inData.getSite(), inData.getBuNo(), inData.getDelNo(), palletRecords, currentUser); @@ -1499,6 +1590,88 @@ public class CoDelExcelServiceImpl implements CoDelExcelService { coDelMapper.updateEcssDelDetailForModify(inData); } + /** + * 验证导入数据的序号和数量是否与发货通知单明细匹配 + * 规则:导入数据中同一个序号的数量总和必须等于发货通知单明细中该序号的数量 + */ + private void validateImportDataWithNotifyDetail(List excelList, EcssCoDelNotifyHeaderData inData) { + // 获取发货通知单明细 + List notifyDetailList = coDelMapper.searchEcssCoDelNotifyDetail(inData); + + // 将发货通知单明细按itemNo分组 + Map notifyDetailMap = new HashMap<>(); + for (EcssCoDelNotifyDetailData detailData : notifyDetailList) { + notifyDetailMap.put(detailData.getItemNo(), detailData); + } + + // 将导入数据按notifyDetailItemNo分组并汇总数量 + Map importQtyMap = new HashMap<>(); + for (EcssCoDelPalletData palletData : excelList) { + Integer itemNo = palletData.getNotifyDetailItemNo(); + BigDecimal qty = palletData.getQty(); + + if (importQtyMap.containsKey(itemNo)) { + importQtyMap.put(itemNo, importQtyMap.get(itemNo).add(qty)); + } else { + importQtyMap.put(itemNo, qty); + } + } + + // 验证每个序号的数量是否匹配 + for (Map.Entry entry : importQtyMap.entrySet()) { + Integer itemNo = entry.getKey(); + BigDecimal importQty = entry.getValue(); + + EcssCoDelNotifyDetailData notifyDetail = notifyDetailMap.get(itemNo); + if (notifyDetail == null) { + throw new RuntimeException("序号[" + itemNo + "]在发货通知单明细中不存在!"); + } + + BigDecimal notifyQty = notifyDetail.getQty(); + if (notifyQty.compareTo(importQty) != 0) { + throw new RuntimeException("序号[" + itemNo + "]的数量不一致!" + + "发货通知单数量:" + notifyQty.setScale(0, RoundingMode.HALF_UP) + ",导入数量:" + importQty.setScale(0,RoundingMode.HALF_UP)); + } + } + + // 验证是否有发货通知单明细没有对应的导入数据 + for (EcssCoDelNotifyDetailData notifyDetail : notifyDetailList) { + if (!importQtyMap.containsKey(notifyDetail.getItemNo())) { + throw new RuntimeException("发货通知单明细序号[" + notifyDetail.getItemNo() + + "]没有对应的装箱数据!"); + } + } + } + + /** + * 判断单元格是否在合并区域中 + */ + private boolean isCellInMergedRegion(XSSFSheet sheet, int row, int column) { + int numberOfMergedRegions = sheet.getNumMergedRegions(); + for (int i = 0; i < numberOfMergedRegions; i++) { + org.apache.poi.ss.util.CellRangeAddress mergedRegion = sheet.getMergedRegion(i); + if (mergedRegion.isInRange(row, column)) { + return true; + } + } + return false; + } + + /** + * 判断单元格是否在合并区域中但不是第一行 + */ + private boolean isMergedCellButNotFirst(XSSFSheet sheet, int row, int column) { + int numberOfMergedRegions = sheet.getNumMergedRegions(); + for (int i = 0; i < numberOfMergedRegions; i++) { + org.apache.poi.ss.util.CellRangeAddress mergedRegion = sheet.getMergedRegion(i); + if (mergedRegion.isInRange(row, column)) { + // 如果在合并区域中,且不是第一行 + return row != mergedRegion.getFirstRow(); + } + } + return false; + } + private void palletHeaderSave(String site,String buNo,String delNo, String palletRecords, SysUserEntity currentUser) { List> palletRecordList = new ArrayList<>(); if (palletRecords != null && !palletRecords.isEmpty()) { diff --git a/src/main/resources/templates/packing-template.xlsx b/src/main/resources/templates/packing-template.xlsx index 8911c0fb..1c8af08a 100644 Binary files a/src/main/resources/templates/packing-template.xlsx and b/src/main/resources/templates/packing-template.xlsx differ