From f26623eb3861a57822e43e5204e3b4ce3e9596eb Mon Sep 17 00:00:00 2001 From: "han\\hanst" Date: Sun, 5 Oct 2025 15:53:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=B6=E4=BB=96=E5=87=BA=E5=BA=93=EF=BC=8C?= =?UTF-8?q?=E6=8A=A5=E5=BA=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/scrap/entity/ScrapLabelDto.java | 1 + .../scrap/service/impl/ScrapServiceImpl.java | 298 ++++++++++++++---- 2 files changed, 246 insertions(+), 53 deletions(-) diff --git a/src/main/java/com/gaotao/modules/scrap/entity/ScrapLabelDto.java b/src/main/java/com/gaotao/modules/scrap/entity/ScrapLabelDto.java index 5174075..7ab0809 100644 --- a/src/main/java/com/gaotao/modules/scrap/entity/ScrapLabelDto.java +++ b/src/main/java/com/gaotao/modules/scrap/entity/ScrapLabelDto.java @@ -17,4 +17,5 @@ public class ScrapLabelDto { private String batchNo; private String locationId; private String warehouseId; + private String inStockFlag; } diff --git a/src/main/java/com/gaotao/modules/scrap/service/impl/ScrapServiceImpl.java b/src/main/java/com/gaotao/modules/scrap/service/impl/ScrapServiceImpl.java index 8c295d1..c262d50 100644 --- a/src/main/java/com/gaotao/modules/scrap/service/impl/ScrapServiceImpl.java +++ b/src/main/java/com/gaotao/modules/scrap/service/impl/ScrapServiceImpl.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.gaotao.common.exception.XJException; import com.gaotao.common.utils.HttpUtils; +import com.gaotao.common.utils.IfsErrorMessageUtils; import com.gaotao.modules.scrap.entity.*; import com.gaotao.modules.scrap.service.ScrapService; import com.gaotao.modules.scrap.dao.ScrapMapper; @@ -16,6 +17,11 @@ import com.gaotao.modules.trans.service.TransDetailService; import com.gaotao.modules.trans.service.TransNoControlService; import com.gaotao.modules.sys.entity.SysUserEntity; import com.gaotao.modules.sys.controller.AbstractController; +import com.gaotao.modules.handlingunit.entity.HandlingUnit; +import com.gaotao.modules.handlingunit.service.HandlingUnitService; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.gaotao.modules.warehouse.entity.InventoryStock; +import com.gaotao.modules.warehouse.service.InventoryStockService; import org.apache.shiro.SecurityUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -24,10 +30,8 @@ import org.springframework.transaction.annotation.Transactional; import lombok.extern.slf4j.Slf4j; import java.math.BigDecimal; -import java.util.Date; -import java.util.List; -import java.util.ArrayList; -import java.util.Map; +import java.util.*; + import org.apache.commons.lang.StringUtils; import org.apache.shiro.SecurityUtils; import com.gaotao.modules.sys.entity.SysUserEntity; @@ -51,13 +55,19 @@ public class ScrapServiceImpl extends AbstractController implements ScrapService @Autowired private TransNoControlService transNoControlService; + @Autowired + private HandlingUnitService handlingUnitService; + + @Autowired + private InventoryStockService inventoryStockService; + @Value("${custom.ifs-url}") private String ifsUrl; @Value("${custom.ifs-ifsDBName}") private String ifsDBName; @Value("${custom.ifs-domainUserID}") private String domainUserID; - + @Value("${ldap-control.control-flag:false}") private Boolean ldapFlag; @@ -77,36 +87,37 @@ public class ScrapServiceImpl extends AbstractController implements ScrapService } return domainUserID; } - + @Override public ScrapLabelDto scanScrapLabel(ScrapLabelDto dto) { try { - // 从IFS接口获取标签信息 - Map params = Map.of( - "ifsDBName", ifsDBName, - "domainUserID", getCurrentDomainUserID(), - "ifsSiteID", dto.getSite(), - "labelCode", dto.getLabelCode() - ); + // 1. 先从本地数据库查询HandlingUnit信息 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("site", dto.getSite()) + .eq("unit_id", dto.getLabelCode()); - ObjectMapper objectMapper = new ObjectMapper(); - String jsonBody = objectMapper.writeValueAsString(params); - String ifsResponse = HttpUtils.doGetWithBody(ifsUrl + "LabelInfo", jsonBody, null); + HandlingUnit handlingUnit = handlingUnitService.getOne(wrapper); + if (handlingUnit == null) { + throw new XJException("未找到对应的HandlingUnit: " + dto.getLabelCode()); + } - // 解析IFS响应 - Map ifsData = objectMapper.readValue(ifsResponse, new TypeReference>() {}); + // 2. 校验inStockFlag是否为Y + if (!"Y".equals(handlingUnit.getInStockFlag())) { + throw new XJException("库存不存在,该HU未入库或已出库"); + } - // 构建标签信息 + // 3. 构建标签信息 ScrapLabelDto labelInfo = new ScrapLabelDto(); labelInfo.setLabelCode(dto.getLabelCode()); labelInfo.setSite(dto.getSite()); - labelInfo.setPartNo((String) ifsData.get("partNo")); - labelInfo.setPartDesc((String) ifsData.get("partDesc")); - labelInfo.setQuantity(new BigDecimal(ifsData.get("quantity").toString())); - labelInfo.setUnit((String) ifsData.get("unit")); - labelInfo.setBatchNo((String) ifsData.get("batchNo")); - labelInfo.setLocationId((String) ifsData.get("locationId")); - labelInfo.setWarehouseId((String) ifsData.get("warehouseId")); + labelInfo.setPartNo(handlingUnit.getPartNo()); + labelInfo.setPartDesc(handlingUnit.getPartDesc()); + labelInfo.setQuantity(handlingUnit.getQty() != null ? handlingUnit.getQty() : BigDecimal.ZERO); + labelInfo.setUnit(handlingUnit.getUmId() != null ? handlingUnit.getUmId() : "个"); + labelInfo.setBatchNo(handlingUnit.getBatchNo()); + labelInfo.setLocationId(handlingUnit.getLocationId()); + labelInfo.setWarehouseId(handlingUnit.getWarehouseId()); + labelInfo.setInStockFlag(handlingUnit.getInStockFlag()); return labelInfo; @@ -129,15 +140,14 @@ public class ScrapServiceImpl extends AbstractController implements ScrapService outData.setOrderRef1(dto.getScrapReason()); // 存储报废原因 // 生成报废出库事务号 - TransHeader outTransHeader = transHeaderService.genTrans(outData, "SCRAP"); // 报废事务类型 - outTransHeader.setStatus("COMPLETED"); - outTransHeader.setStatusDb("COMPLETED"); - transHeaderService.updateById(outTransHeader); + TransHeader outTransHeader = transHeaderService.genTrans(outData, "OC"); // 报废事务类型 - // 2. 保存报废明细 + // 2. 处理每个标签的报废 + List processedLabels = new ArrayList<>(); for (int i = 0; i < dto.getLabels().size(); i++) { ScrapLabelDto label = dto.getLabels().get(i); + // 2.1 保存报废明细 TransDetail outDetail = new TransDetail(); outDetail.setSite(dto.getSite()); outDetail.setTransNo(outTransHeader.getTransNo()); @@ -149,41 +159,223 @@ public class ScrapServiceImpl extends AbstractController implements ScrapService outDetail.setOrderRef1(dto.getScrapReason()); // 存储报废原因 outDetail.setOrderRef2(label.getLabelCode()); // 存储标签编码 outDetail.setRemark("物料报废"); - transDetailService.save(outDetail); + + // 2.2 更新HandlingUnit状态,将inStockFlag改为N + QueryWrapper huWrapper = new QueryWrapper<>(); + huWrapper.eq("site", dto.getSite()) + .eq("unit_id", label.getLabelCode()); + + HandlingUnit huUpdate = new HandlingUnit(); + huUpdate.setInStockFlag("N"); // 设置为未入库状态 + huUpdate.setStatus("SCRAPPED"); // 设置为报废状态 + huUpdate.setModifiedDate(new Date()); + huUpdate.setModifiedBy(currentUser != null ? currentUser.getUserDisplay() : "SYSTEM"); + + boolean huResult = handlingUnitService.update(huUpdate, huWrapper); + if (!huResult) { + throw new RuntimeException("更新HandlingUnit状态失败: " + label.getLabelCode()); + } + + // 2.3 减少库存数量 + updateInventoryStock(dto.getSite(), label); + + // 2.4 添加到已处理列表,用于后续汇总调用IFS接口 + processedLabels.add(label); } - // 3. 调用IFS接口同步报废信息 + // 3. 汇总调用IFS的ScrapInventoryPart接口 + callIfsScrapInventoryPartBatch(dto.getSite(), processedLabels, dto.getScrapReason()); + + log.info("报废处理完成 - 事务号: {}, 处理数量: {}", outTransHeader.getTransNo(), dto.getLabels().size()); + } + + /** + * 更新库存数量 + */ + private void updateInventoryStock(String site, ScrapLabelDto label) { try { - Map ifsParams = Map.of( - "ifsDBName", ifsDBName, - "domainUserID", getCurrentDomainUserID(), - "ifsSiteID", dto.getSite(), - "transNo", outTransHeader.getTransNo(), - "scrapReason", dto.getScrapReason(), - "labels", dto.getLabels() - ); + // 先查询HandlingUnit获取WDR信息 + QueryWrapper huWrapper = new QueryWrapper<>(); + huWrapper.eq("site", site) + .eq("unit_id", label.getLabelCode()); - ObjectMapper objectMapper = new ObjectMapper(); - String jsonBody = objectMapper.writeValueAsString(ifsParams); - String ifsResponse = HttpUtils.doPost(ifsUrl + "ScrapSync", jsonBody, null); + HandlingUnit handlingUnit = handlingUnitService.getOne(huWrapper); + if (handlingUnit == null) { + log.warn("未找到HandlingUnit,无法更新库存: {}", label.getLabelCode()); + return; + } + + // 查询对应的库存记录,增加WDR条件 + QueryWrapper stockWrapper = new QueryWrapper<>(); + stockWrapper.eq("site", site) + .eq("part_no", label.getPartNo()) + .eq("batch_no", label.getBatchNo()) + .eq("location_id", label.getLocationId()) + .eq("warehouse_id", label.getWarehouseId()) + .eq("wdr", handlingUnit.getWdr() != null ? handlingUnit.getWdr() : "*"); + + InventoryStock stock = inventoryStockService.getOne(stockWrapper); + if (stock != null) { + // 减少现有库存数量 + BigDecimal currentQty = stock.getQtyOnHand() != null ? stock.getQtyOnHand() : BigDecimal.ZERO; + BigDecimal newQty = currentQty.subtract(label.getQuantity()); + + // 确保库存不为负数 + if (newQty.compareTo(BigDecimal.ZERO) < 0) { + log.warn("库存数量不足,当前库存: {}, 报废数量: {}", currentQty, label.getQuantity()); + newQty = BigDecimal.ZERO; + } + + stock.setQtyOnHand(newQty); + stock.setOutQty((stock.getOutQty() != null ? stock.getOutQty() : BigDecimal.ZERO).add(label.getQuantity())); + stock.setLatestOutDate(new Date()); + + inventoryStockService.updateById(stock); + log.info("库存更新成功 - 物料: {}, 批次: {}, 库位: {}, WDR: {}, 原库存: {}, 报废数量: {}, 新库存: {}", + label.getPartNo(), label.getBatchNo(), label.getLocationId(), + handlingUnit.getWdr() != null ? handlingUnit.getWdr() : "", currentQty, label.getQuantity(), newQty); + } else { + log.warn("未找到对应的库存记录 - 物料: {}, 批次: {}, 库位: {}, WDR: {}", + label.getPartNo(), label.getBatchNo(), label.getLocationId(), + handlingUnit.getWdr() != null ? handlingUnit.getWdr() : ""); + throw new RuntimeException("库存不存在"); + } + } catch (Exception e) { + log.error("更新库存失败: {}", e.getMessage(), e); + throw new RuntimeException("更新库存失败: " + e.getMessage()); + } + } - log.info("IFS报废同步响应: {}", ifsResponse); + /** + * 批量调用IFS的ScrapInventoryPart接口 + */ + private void callIfsScrapInventoryPartBatch(String site, List labels, String scrapReason) { + try { + // 按物料、批次、库位进行汇总 + Map summaryMap = new HashMap<>(); + + for (ScrapLabelDto label : labels) { + // 查询HandlingUnit获取更多信息 + QueryWrapper huWrapper = new QueryWrapper<>(); + huWrapper.eq("site", site) + .eq("unit_id", label.getLabelCode()); + + HandlingUnit handlingUnit = handlingUnitService.getOne(huWrapper); + if (handlingUnit == null) { + log.warn("未找到HandlingUnit: {}", label.getLabelCode()); + continue; + } + + // 生成汇总键:物料号+批次号+库位+WDR + String summaryKey = String.format("%s|%s|%s|%s", + label.getPartNo(), + label.getBatchNo() != null ? label.getBatchNo() : "", + label.getLocationId() != null ? label.getLocationId() : "", + handlingUnit.getWdr() != null ? handlingUnit.getWdr() : "*" + ); + + ScrapSummaryInfo summaryInfo = summaryMap.computeIfAbsent(summaryKey, k -> { + ScrapSummaryInfo info = new ScrapSummaryInfo(); + info.setPartNo(label.getPartNo()); + info.setBatchNo(label.getBatchNo() != null ? label.getBatchNo() : ""); + info.setLocationId(label.getLocationId() != null ? label.getLocationId() : ""); + info.setWdr(handlingUnit.getWdr() != null ? handlingUnit.getWdr() : "*"); + info.setTotalQty(BigDecimal.ZERO); + info.setHuList(new ArrayList<>()); + return info; + }); + + // 累加数量 + summaryInfo.setTotalQty(summaryInfo.getTotalQty().add(label.getQuantity())); + summaryInfo.getHuList().add(label.getLabelCode()); + } + + // 对每个汇总项调用IFS接口 + for (ScrapSummaryInfo summaryInfo : summaryMap.values()) { + callIfsScrapInventoryPartSingle(site, summaryInfo, scrapReason); + } + + log.info("IFS批量报废接口调用完成 - 汇总项数: {}, 总HU数: {}", summaryMap.size(), labels.size()); } catch (Exception e) { - log.error("IFS报废同步失败: {}", e.getMessage()); - // 注意:这里不抛出异常,因为WMS事务已经完成,IFS同步失败不影响WMS操作 + log.error("调用IFS批量报废接口失败: {}", e.getMessage(), e); + throw new XJException(e.getMessage()); } + } - // 4. 更新库存(这里需要调用库存服务) - // updateInventoryStock(dto); + /** + * 调用IFS的ScrapInventoryPart接口(单个汇总项) + */ + private void callIfsScrapInventoryPartSingle(String site, ScrapSummaryInfo summaryInfo, String scrapReason) { + try { + // 构建IFS接口参数 + Map params = Map.ofEntries( + Map.entry("ifsDBName", ifsDBName), + Map.entry("domainUserID", getCurrentDomainUserID()), + Map.entry("ifsSiteID", site), + Map.entry("ifsPartNo", summaryInfo.getPartNo()), + Map.entry("ifsConfigurationID", "*"), + Map.entry("ifsLocationNo", summaryInfo.getLocationId() != null ? summaryInfo.getLocationId() : ""), + Map.entry("ifsLotBatchNo", summaryInfo.getBatchNo() != null ? summaryInfo.getBatchNo() : ""), + Map.entry("ifsSerialNo", "*"), + Map.entry("ifsEngChgLevel", "1"), + Map.entry("ifsWdrNo", summaryInfo.getWdr() != null ? summaryInfo.getWdr() : "*"), + Map.entry("ifsHandlingUntiID", "0"), + Map.entry("ifsQtyScrapped", summaryInfo.getTotalQty()), // 保持 BigDecimal 精度 + Map.entry("ifsScrapCause", scrapReason), + Map.entry("ifsScrapNote", "WMS批量报废") + ); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonBody = objectMapper.writeValueAsString(params); + String ifsResponse = HttpUtils.doPost(ifsUrl + "ScrapInventoryPart", jsonBody, null); + if ("IFSUpdated".equals(ifsResponse) || "\"IFSUpdated\"".equals(ifsResponse)) { + log.info("IFS报废接口调用成功 - 物料: {}, 批次: {}, 库位: {}, 数量: {}, HU列表: {}, 响应: {}", + summaryInfo.getPartNo(), summaryInfo.getBatchNo(), summaryInfo.getLocationId(), + summaryInfo.getTotalQty(), summaryInfo.getHuList(), ifsResponse); + + } else { + log.error("IFS报废接口调用失败,物料: {}, 响应: {}", summaryInfo.getPartNo(), ifsResponse); + // 同步失败需要回滚前面所有的数据库操作 + String errorMessage = IfsErrorMessageUtils.extractOracleErrorMessage(ifsResponse); + throw new XJException(errorMessage); + } + + } catch (Exception e) { + log.error("调用IFS报废接口失败 - 物料: {}, 批次: {}, 库位: {}, 错误: {}", + summaryInfo.getPartNo(), summaryInfo.getBatchNo(), summaryInfo.getLocationId(), e.getMessage(), e); + throw new XJException(e.getMessage()); + } } /** - * 更新库存(需要根据实际的库存服务实现) + * 报废汇总信息内部类 */ - private void updateInventoryStock(ScrapConfirmDto dto) { - // TODO: 实现库存更新逻辑 - // 减少对应仓库和库位的库存 + private static class ScrapSummaryInfo { + private String partNo; + private String batchNo; + private String locationId; + private String wdr; + private BigDecimal totalQty; + private List huList; + + // Getters and Setters + public String getPartNo() { return partNo; } + public void setPartNo(String partNo) { this.partNo = partNo; } + + public String getBatchNo() { return batchNo; } + public void setBatchNo(String batchNo) { this.batchNo = batchNo; } + + public String getLocationId() { return locationId; } + public void setLocationId(String locationId) { this.locationId = locationId; } + + public String getWdr() { return wdr; } + public void setWdr(String wdr) { this.wdr = wdr; } + + public BigDecimal getTotalQty() { return totalQty; } + public void setTotalQty(BigDecimal totalQty) { this.totalQty = totalQty; } + + public List getHuList() { return huList; } + public void setHuList(List huList) { this.huList = huList; } } }