diff --git a/src/main/java/com/gaotao/modules/api/entity/IfsInventoryPartInStock.java b/src/main/java/com/gaotao/modules/api/entity/IfsInventoryPartInStock.java new file mode 100644 index 0000000..97d8dea --- /dev/null +++ b/src/main/java/com/gaotao/modules/api/entity/IfsInventoryPartInStock.java @@ -0,0 +1,178 @@ +package com.gaotao.modules.api.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * IFS库存在库查询响应实体类 + * + * @Description 用于接收IFS InventoryPartInStock接口返回的库存数据 + * @Author AI Assistant + * @Date 2025/10/11 + */ +@Data +public class IfsInventoryPartInStock { + + @JsonProperty("ActivitySeq") + private String activitySeq; + + @JsonProperty("AvailabilityControlId") + private String availabilityControlId; + + @JsonProperty("AvgUnitTransitCost") + private String avgUnitTransitCost; + + @JsonProperty("BayNo") + private String bayNo; + + @JsonProperty("BinNo") + private String binNo; + + @JsonProperty("CatchQtyInTransit") + private String catchQtyInTransit; + + @JsonProperty("CatchQtyOnhand") + private String catchQtyOnhand; + + @JsonProperty("ConditionCode") + private String conditionCode; + + @JsonProperty("ConfigurationId") + private String configurationId; + + @JsonProperty("Contract") + private String contract; + + @JsonProperty("CountVariance") + private String countVariance; + + @JsonProperty("EngChgLevel") + private String engChgLevel; + + @JsonProperty("ExpirationDate") + private String expirationDate; + + @JsonProperty("FreezeFlag") + private String freezeFlag; + + @JsonProperty("FreezeFlagDb") + private String freezeFlagDb; + + @JsonProperty("HandlingUnitId") + private String handlingUnitId; + + @JsonProperty("LastActivityDate") + private String lastActivityDate; + + @JsonProperty("LastCountDate") + private String lastCountDate; + + @JsonProperty("LocationNo") + private String locationNo; + + @JsonProperty("LocationType") + private String locationType; + + @JsonProperty("LocationTypeDb") + private String locationTypeDb; + + @JsonProperty("LotBatchNo") + private String lotBatchNo; + + @JsonProperty("Objid") + private String objid; + + @JsonProperty("Objkey") + private String objkey; + + @JsonProperty("Objversion") + private String objversion; + + @JsonProperty("OwningCustomerNo") + private String owningCustomerNo; + + @JsonProperty("OwningVendorNo") + private String owningVendorNo; + + @JsonProperty("PartNo") + private String partNo; + + @JsonProperty("PartOwnership") + private String partOwnership; + + @JsonProperty("PartOwnershipDb") + private String partOwnershipDb; + + @JsonProperty("ProjectId") + private String projectId; + + @JsonProperty("QtyInTransit") + private String qtyInTransit; + + @JsonProperty("QtyOnhand") + private String qtyOnhand; + + @JsonProperty("QtyReserved") + private String qtyReserved; + + @JsonProperty("ReceiptDate") + private String receiptDate; + + @JsonProperty("RowNo") + private String rowNo; + + @JsonProperty("SerialNo") + private String serialNo; + + @JsonProperty("Source") + private String source; + + @JsonProperty("TierNo") + private String tierNo; + + @JsonProperty("WaivDevRejNo") + private String waivDevRejNo; + + @JsonProperty("Warehouse") + private String warehouse; + + /** + * 获取可用库存数量(在库数量 - 预留数量) + * @return 可用库存数量 + */ + public BigDecimal getAvailableQty() { + try { + BigDecimal onhand = new BigDecimal(qtyOnhand != null ? qtyOnhand : "0"); + BigDecimal reserved = new BigDecimal(qtyReserved != null ? qtyReserved : "0"); + return onhand.subtract(reserved); + } catch (NumberFormatException e) { + return BigDecimal.ZERO; + } + } + + /** + * 获取在库数量 + * @return 在库数量 + */ + public BigDecimal getQtyOnhandAsBigDecimal() { + try { + return new BigDecimal(qtyOnhand != null ? qtyOnhand : "0"); + } catch (NumberFormatException e) { + return BigDecimal.ZERO; + } + } + + /** + * 获取预留数量 + * @return 预留数量 + */ + public BigDecimal getQtyReservedAsBigDecimal() { + try { + return new BigDecimal(qtyReserved != null ? qtyReserved : "0"); + } catch (NumberFormatException e) { + return BigDecimal.ZERO; + } + } +} diff --git a/src/main/java/com/gaotao/modules/api/service/IfsApiService.java b/src/main/java/com/gaotao/modules/api/service/IfsApiService.java index 8e19672..7e239a9 100644 --- a/src/main/java/com/gaotao/modules/api/service/IfsApiService.java +++ b/src/main/java/com/gaotao/modules/api/service/IfsApiService.java @@ -2,6 +2,7 @@ package com.gaotao.modules.api.service; import com.fasterxml.jackson.core.JsonProcessingException; import com.gaotao.modules.api.entity.IfsInventoryPart; +import com.gaotao.modules.api.entity.IfsInventoryPartInStock; import com.gaotao.modules.api.entity.IfsShopOrder; import com.gaotao.modules.base.entity.SOScheduledRoutingData; import com.gaotao.modules.notify.entity.vo.ShopOrderMaterialVo; @@ -18,5 +19,13 @@ public interface IfsApiService { List getIfsInventoryPart(IfsInventoryPart data) throws JsonProcessingException; + /** + * 获取IFS库存在库信息 + * @param site 站点 + * @return 库存在库信息列表 + * @throws JsonProcessingException JSON处理异常 + */ + List getInventoryPartInStock(String site) throws JsonProcessingException; + } diff --git a/src/main/java/com/gaotao/modules/api/service/impl/IfsApiServiceImpl.java b/src/main/java/com/gaotao/modules/api/service/impl/IfsApiServiceImpl.java index 5993a3f..b0e9896 100644 --- a/src/main/java/com/gaotao/modules/api/service/impl/IfsApiServiceImpl.java +++ b/src/main/java/com/gaotao/modules/api/service/impl/IfsApiServiceImpl.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.gaotao.common.utils.HttpUtils; import com.gaotao.modules.api.entity.IfsInventoryPart; +import com.gaotao.modules.api.entity.IfsInventoryPartInStock; import com.gaotao.modules.api.entity.IfsShopOrder; import com.gaotao.modules.api.service.IfsApiService; import com.gaotao.modules.base.entity.SOScheduledRoutingData; @@ -88,6 +89,24 @@ public class IfsApiServiceImpl implements IfsApiService { }); } + @Override + public List getInventoryPartInStock(String site) throws JsonProcessingException { + Map params = Map.of( + "ifsDBName", ifsDBName, + "domainUserID", domainUserID, + "ifsSiteID", site + ); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonBody = objectMapper.writeValueAsString(params); + String ifsResponse = HttpUtils.doGetWithBody(ifsUrl + "InventoryPartInStock", jsonBody, null); + ObjectMapper mapper = new ObjectMapper(); + List result = mapper.readValue(ifsResponse, new TypeReference>() { + }); + + log.info("获取IFS库存在库信息成功,站点: {}, 返回记录数: {}", site, result.size()); + return result; + } + private String getCurrentDomainUserID() { // if (ldapFlag) { // try { diff --git a/src/main/java/com/gaotao/modules/other/service/impl/InventoryMoveServiceImpl.java b/src/main/java/com/gaotao/modules/other/service/impl/InventoryMoveServiceImpl.java index 8023beb..e92c6e7 100644 --- a/src/main/java/com/gaotao/modules/other/service/impl/InventoryMoveServiceImpl.java +++ b/src/main/java/com/gaotao/modules/other/service/impl/InventoryMoveServiceImpl.java @@ -3,7 +3,9 @@ package com.gaotao.modules.other.service.impl; import com.gaotao.common.exception.XJException; import com.gaotao.common.utils.HttpUtils; import com.gaotao.common.utils.IfsErrorMessageUtils; +import com.gaotao.modules.api.entity.IfsInventoryPartInStock; import com.gaotao.modules.api.service.IfsApiIssueAndReturnService; +import com.gaotao.modules.api.service.IfsApiService; import com.gaotao.modules.api.service.IfsCallErrorLogService; import com.gaotao.modules.handlingunit.entity.HandlingUnit; import com.gaotao.modules.handlingunit.service.HandlingUnitService; @@ -62,6 +64,9 @@ public class InventoryMoveServiceImpl implements InventoryMoveService { @Autowired private IfsCallErrorLogService ifsCallErrorLogService; + @Autowired + private IfsApiService ifsApiService; + @Value("${custom.ifs-url}") private String ifsUrl; @@ -187,7 +192,7 @@ public class InventoryMoveServiceImpl implements InventoryMoveService { } // 合并相同条件的HandlingUnit后调用IFS接口 - syncToIFSBatchForPallet(dto, handlingUnits); + syncToIFSBatch(dto, handlingUnits); log.info("库存移库完成,处理了{}个HandlingUnit", handlingUnits.size()); } catch (Exception e) { @@ -271,6 +276,9 @@ public class InventoryMoveServiceImpl implements InventoryMoveService { group.totalQty = group.totalQty.add(hu.getQty()); } + // 校验所有分组的库存数量是否足够 + validateInventoryStock(moveGroups); + // 为每个分组调用IFS接口 for (MoveGroup group : moveGroups.values()) { syncSingleGroupToIFS(group); @@ -326,8 +334,95 @@ public class InventoryMoveServiceImpl implements InventoryMoveService { } catch (Exception e) { // rqrq - 不抛异常,只记录警告日志,失败的记录已经存入错误日志表 log.warn("IFS批量移库同步异常: {},失败的记录已存入错误日志表供后续手工处理", e.getMessage()); - } - } + } + } + + /** + * 校验库存数量是否足够 + * @param moveGroups 移库分组数据 + * @throws XJException 库存不足时抛出异常 + */ + private void validateInventoryStock(Map moveGroups) throws XJException { + try { + log.info("开始校验库存数量,共{}个分组需要校验", moveGroups.size()); + + // 按站点分组获取库存信息,减少API调用次数 + Map> siteInventoryMap = new HashMap<>(); + + for (MoveGroup group : moveGroups.values()) { + String site = group.site; + if (!siteInventoryMap.containsKey(site)) { + // 获取该站点的所有库存信息 + List inventoryList = ifsApiService.getInventoryPartInStock(site); + siteInventoryMap.put(site, inventoryList); + log.info("获取站点{}的库存信息,共{}条记录", site, inventoryList.size()); + } + } + + // 校验每个分组的库存是否足够 + List insufficientStockErrors = new ArrayList<>(); + + for (MoveGroup group : moveGroups.values()) { + List siteInventory = siteInventoryMap.get(group.site); + + // 查找匹配的库存记录 + IfsInventoryPartInStock matchedStock = findMatchingStock(siteInventory, group); + + if (matchedStock == null) { + insufficientStockErrors.add(String.format( + "物料%s批次%s在库位%s未找到库存记录", + group.partNo, group.lotBatchNo, group.sourceLocationNo)); + continue; + } + + // 计算可用库存(在库数量 - 预留数量) + BigDecimal availableQty = matchedStock.getAvailableQty(); + + // 检查库存是否足够 + if (availableQty.compareTo(group.totalQty) < 0) { + insufficientStockErrors.add(String.format( + "物料%s批次%s在库位%s库存不足,需要数量:%s,可用数量:%s(在库:%s,预留:%s)", + group.partNo, group.lotBatchNo, group.sourceLocationNo, + group.totalQty, availableQty, + matchedStock.getQtyOnhand(), matchedStock.getQtyReserved())); + } else { + log.debug("物料{}批次{}库存校验通过,需要:{},可用:{}", + group.partNo, group.lotBatchNo, group.totalQty, availableQty); + } + } + + // 如果有库存不足的情况,抛出异常 + if (!insufficientStockErrors.isEmpty()) { + String errorMessage = "库存校验失败:\n" + String.join("\n", insufficientStockErrors); + log.error("库存校验失败:{}", errorMessage); + throw new XJException(errorMessage); + } + + log.info("库存数量校验通过,所有分组库存充足"); + + } catch (Exception e) { + log.error("库存校验过程中发生异常", e); + throw new XJException("库存校验失败:" + e.getMessage()); + } + } + + /** + * 查找匹配的库存记录 + * @param inventoryList 库存列表 + * @param group 移库分组 + * @return 匹配的库存记录,未找到返回null + */ + private IfsInventoryPartInStock findMatchingStock(List inventoryList, MoveGroup group) { + for (IfsInventoryPartInStock stock : inventoryList) { + // 匹配条件:物料号、批次号、库位号 + if (group.partNo.equals(stock.getPartNo()) && + group.lotBatchNo.equals(stock.getLotBatchNo()) && + group.sourceLocationNo.equals(stock.getLocationNo())) { + return stock; + } + } + return null; + } /** * 同步单个分组到IFS diff --git a/src/main/java/com/gaotao/modules/other/service/impl/OtherInboundServiceImpl.java b/src/main/java/com/gaotao/modules/other/service/impl/OtherInboundServiceImpl.java index b95a334..6e41426 100644 --- a/src/main/java/com/gaotao/modules/other/service/impl/OtherInboundServiceImpl.java +++ b/src/main/java/com/gaotao/modules/other/service/impl/OtherInboundServiceImpl.java @@ -104,8 +104,6 @@ public class OtherInboundServiceImpl implements OtherInboundService { String ifsResponse = ifsApiIssueAndReturnService.addOtherInbound(ifsDto); // 检查IFS响应,如果失败可能需要回滚事务 if (!"IFSUpdated".equals(ifsResponse) && !"\"IFSUpdated\"".equals(ifsResponse)) { - throw new XJException("IFS同步失败,响应: " + ifsResponse); - } else { String errorMessage = IfsErrorMessageUtils.extractOracleErrorMessage(ifsResponse); throw new Exception(errorMessage); } @@ -115,7 +113,7 @@ public class OtherInboundServiceImpl implements OtherInboundService { } // 7. 更新HandlingUnit状态 - updateHandlingUnitStatus(handlingUnits, detailList, currentUser); + updateHandlingUnitStatus(handlingUnits, detailList, currentUser,warehouseId); } catch (Exception e) { throw new XJException("其它入库失败: " + e.getMessage()); @@ -344,7 +342,8 @@ public class OtherInboundServiceImpl implements OtherInboundService { /** * 更新HandlingUnit状态 */ - private void updateHandlingUnitStatus(List handlingUnits, List detailList, SysUserEntity currentUser) { + private void updateHandlingUnitStatus(List handlingUnits, List detailList, SysUserEntity currentUser + ,String warehouseId) { for (HandlingUnit hu : handlingUnits) { // 更新HandlingUnit的库位到目标库位 TransDetail matchedDetail = detailList.stream() @@ -358,7 +357,7 @@ public class OtherInboundServiceImpl implements OtherInboundService { hu.setModifiedDate(new Date()); hu.setModifiedBy(currentUser.getUserDisplay()); hu.setInStockFlag("Y"); // 设置为在库状态 - + hu.setWarehouseId(warehouseId); handlingUnitService.updateById(hu); } } diff --git a/src/main/java/com/gaotao/modules/other/service/impl/OtherOutboundServiceImpl.java b/src/main/java/com/gaotao/modules/other/service/impl/OtherOutboundServiceImpl.java index 5698f9a..4c8c015 100644 --- a/src/main/java/com/gaotao/modules/other/service/impl/OtherOutboundServiceImpl.java +++ b/src/main/java/com/gaotao/modules/other/service/impl/OtherOutboundServiceImpl.java @@ -102,8 +102,6 @@ public class OtherOutboundServiceImpl implements OtherOutboundService { String ifsResponse = ifsApiIssueAndReturnService.addOtherOutbound(ifsDto); // 检查IFS响应,如果失败可能需要回滚事务 if (!"IFSUpdated".equals(ifsResponse) && !"\"IFSUpdated\"".equals(ifsResponse)) { - throw new XJException("IFS同步失败,响应: " + ifsResponse); - } else { String errorMessage = IfsErrorMessageUtils.extractOracleErrorMessage(ifsResponse); throw new Exception(errorMessage); }