Browse Source

移库校验

master
han\hanst 5 months ago
parent
commit
0f8be95f25
  1. 178
      src/main/java/com/gaotao/modules/api/entity/IfsInventoryPartInStock.java
  2. 9
      src/main/java/com/gaotao/modules/api/service/IfsApiService.java
  3. 19
      src/main/java/com/gaotao/modules/api/service/impl/IfsApiServiceImpl.java
  4. 101
      src/main/java/com/gaotao/modules/other/service/impl/InventoryMoveServiceImpl.java
  5. 9
      src/main/java/com/gaotao/modules/other/service/impl/OtherInboundServiceImpl.java
  6. 2
      src/main/java/com/gaotao/modules/other/service/impl/OtherOutboundServiceImpl.java

178
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;
}
}
}

9
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<IfsInventoryPart> getIfsInventoryPart(IfsInventoryPart data) throws JsonProcessingException;
/**
* 获取IFS库存在库信息
* @param site 站点
* @return 库存在库信息列表
* @throws JsonProcessingException JSON处理异常
*/
List<IfsInventoryPartInStock> getInventoryPartInStock(String site) throws JsonProcessingException;
}

19
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<IfsInventoryPartInStock> getInventoryPartInStock(String site) throws JsonProcessingException {
Map<String, Object> 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<IfsInventoryPartInStock> result = mapper.readValue(ifsResponse, new TypeReference<List<IfsInventoryPartInStock>>() {
});
log.info("获取IFS库存在库信息成功,站点: {}, 返回记录数: {}", site, result.size());
return result;
}
private String getCurrentDomainUserID() {
// if (ldapFlag) {
// try {

101
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<String, MoveGroup> moveGroups) throws XJException {
try {
log.info("开始校验库存数量,共{}个分组需要校验", moveGroups.size());
// 按站点分组获取库存信息减少API调用次数
Map<String, List<IfsInventoryPartInStock>> siteInventoryMap = new HashMap<>();
for (MoveGroup group : moveGroups.values()) {
String site = group.site;
if (!siteInventoryMap.containsKey(site)) {
// 获取该站点的所有库存信息
List<IfsInventoryPartInStock> inventoryList = ifsApiService.getInventoryPartInStock(site);
siteInventoryMap.put(site, inventoryList);
log.info("获取站点{}的库存信息,共{}条记录", site, inventoryList.size());
}
}
// 校验每个分组的库存是否足够
List<String> insufficientStockErrors = new ArrayList<>();
for (MoveGroup group : moveGroups.values()) {
List<IfsInventoryPartInStock> 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<IfsInventoryPartInStock> 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

9
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<HandlingUnit> handlingUnits, List<TransDetail> detailList, SysUserEntity currentUser) {
private void updateHandlingUnitStatus(List<HandlingUnit> handlingUnits, List<TransDetail> 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);
}
}

2
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);
}

Loading…
Cancel
Save