From f82b6ac02f3ce00620ed0a5ef7cf2183d91fb505 Mon Sep 17 00:00:00 2001 From: "han\\hanst" Date: Fri, 3 Oct 2025 09:08:48 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=88=E6=A0=BC=E5=85=A5=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/utils/IfsErrorMessageUtils.java | 162 +++++++ .../PurchaseOrderMoveToStockDto.java | 34 ++ .../PurchaseOrderReceiptVo.java | 38 ++ .../service/IfsApiIssueAndReturnService.java | 10 + .../impl/IfsApiIssueAndReturnServiceImpl.java | 71 ++- .../QualifiedStorageController.java | 63 +++ .../service/QualifiedStorageService.java | 26 + .../impl/QualifiedStorageServiceImpl.java | 448 ++++++++++++++++++ .../impl/IfsInventoryInitServiceImpl.java | 12 +- 9 files changed, 856 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/gaotao/common/utils/IfsErrorMessageUtils.java create mode 100644 src/main/java/com/gaotao/modules/api/entity/issueAndReturnVo/PurchaseOrderMoveToStockDto.java create mode 100644 src/main/java/com/gaotao/modules/api/entity/issueAndReturnVo/PurchaseOrderReceiptVo.java create mode 100644 src/main/java/com/gaotao/modules/inspection/controller/QualifiedStorageController.java create mode 100644 src/main/java/com/gaotao/modules/inspection/service/QualifiedStorageService.java create mode 100644 src/main/java/com/gaotao/modules/inspection/service/impl/QualifiedStorageServiceImpl.java diff --git a/src/main/java/com/gaotao/common/utils/IfsErrorMessageUtils.java b/src/main/java/com/gaotao/common/utils/IfsErrorMessageUtils.java new file mode 100644 index 0000000..b061827 --- /dev/null +++ b/src/main/java/com/gaotao/common/utils/IfsErrorMessageUtils.java @@ -0,0 +1,162 @@ +package com.gaotao.common.utils; + +import org.apache.commons.lang.StringUtils; +import lombok.extern.slf4j.Slf4j; + +/** + * IFS错误信息处理工具类 + */ +@Slf4j +public class IfsErrorMessageUtils { + + /** + * 提取Oracle错误信息中的关键内容 + * 从第一个 "ORA-20110:" 到第一个 ".\r\n" 之间的内容 + * + * @param fullErrorMessage 完整的错误信息 + * @return 提取后的关键错误信息 + */ + public static String extractOracleErrorMessage(String fullErrorMessage) { + if (StringUtils.isBlank(fullErrorMessage)) { + return "未知错误"; + } + + try { + // 查找第一个 ORA-20110: 的位置 + String oraPattern = "ORA-20110:"; + int startIndex = fullErrorMessage.indexOf(oraPattern); + + if (startIndex == -1) { + // 如果没有找到 ORA-20110,尝试查找其他 ORA 错误 + oraPattern = "ORA-"; + startIndex = fullErrorMessage.indexOf(oraPattern); + if (startIndex == -1) { + return fullErrorMessage; // 如果没有找到任何 ORA 错误,返回原始信息 + } + } + + // 查找第一个 .\r\n 的位置 + String endPattern = ".\\r\\n"; + int endIndex = fullErrorMessage.indexOf(endPattern, startIndex); + + if (endIndex == -1) { + // 如果没有找到 .\r\n,返回原始信息 + return fullErrorMessage; + } + + // 提取错误信息:从 ORA-20110: 开始到第一个 .\r\n 之前(不包含 .\r\n) + String errorMessage = fullErrorMessage.substring(startIndex, endIndex).trim(); + + // 去掉 ORA-20110: 前缀,只保留具体的错误内容 + if (errorMessage.startsWith("ORA-20110:")) { + errorMessage = errorMessage.substring("ORA-20110:".length()).trim(); + } else if (errorMessage.startsWith("ORA-")) { + // 处理其他ORA错误,去掉ORA-xxxxx:前缀 + int colonIndex = errorMessage.indexOf(":"); + if (colonIndex != -1) { + errorMessage = errorMessage.substring(colonIndex + 1).trim(); + } + } + + // 如果提取的信息为空或太短,返回原始信息 + if (StringUtils.isBlank(errorMessage) || errorMessage.length() < 5) { + return fullErrorMessage; + } + + return errorMessage; + + } catch (Exception e) { + log.warn("提取Oracle错误信息失败: {}", e.getMessage()); + return fullErrorMessage; // 提取失败时返回原始信息 + } + } + + /** + * 提取IFS错误信息中的关键内容 + * 支持多种IFS错误格式的提取 + * + * @param fullErrorMessage 完整的错误信息 + * @return 提取后的关键错误信息 + */ + public static String extractIfsErrorMessage(String fullErrorMessage) { + if (StringUtils.isBlank(fullErrorMessage)) { + return "未知错误"; + } + + // 首先尝试提取Oracle错误 + String oracleError = extractOracleErrorMessage(fullErrorMessage); + if (!oracleError.equals(fullErrorMessage)) { + return oracleError; // 如果成功提取到Oracle错误,直接返回 + } + + try { + // 尝试提取其他IFS错误格式 + String[] ifsErrorPatterns = { + "Exception:", + "Error:", + "Fault:", + "ServerFaultException:" + }; + + for (String pattern : ifsErrorPatterns) { + int startIndex = fullErrorMessage.indexOf(pattern); + if (startIndex != -1) { + int messageStart = startIndex + pattern.length(); + + // 查找结束位置 + String[] endPatterns = {"\r\n", "\n", "at ", "\\s+at\\s+"}; + int endIndex = fullErrorMessage.length(); + + for (String endPattern : endPatterns) { + int tempEndIndex = fullErrorMessage.indexOf(endPattern, messageStart); + if (tempEndIndex != -1 && tempEndIndex < endIndex) { + endIndex = tempEndIndex; + break; + } + } + + String errorMessage = fullErrorMessage.substring(messageStart, endIndex).trim(); + if (StringUtils.isNotBlank(errorMessage) && errorMessage.length() > 5) { + return errorMessage; + } + } + } + + return fullErrorMessage; + + } catch (Exception e) { + log.warn("提取IFS错误信息失败: {}", e.getMessage()); + return fullErrorMessage; + } + } + + /** + * 清理错误信息,移除不必要的技术细节 + * + * @param errorMessage 原始错误信息 + * @return 清理后的错误信息 + */ + public static String cleanErrorMessage(String errorMessage) { + if (StringUtils.isBlank(errorMessage)) { + return "未知错误"; + } + + try { + // 移除时间戳和技术参数 + String cleaned = errorMessage.replaceAll("\\d{4}-\\d{2}-\\d{2}\\s+\\d{2}:\\d{2}:\\d{2}\\.\\d+\\(Timestamp\\),?\\s*", ""); + cleaned = cleaned.replaceAll("\\d+\\(BigDecimal\\),?\\s*", ""); + cleaned = cleaned.replaceAll("\\w+\\(String\\),?\\s*", ""); + cleaned = cleaned.replaceAll("\\s*Exception:\\s*", ""); + cleaned = cleaned.replaceAll("\\s*at\\s+.*", ""); + + // 移除多余的空白字符 + cleaned = cleaned.replaceAll("\\s+", " ").trim(); + + return StringUtils.isNotBlank(cleaned) ? cleaned : errorMessage; + + } catch (Exception e) { + log.warn("清理错误信息失败: {}", e.getMessage()); + return errorMessage; + } + } +} diff --git a/src/main/java/com/gaotao/modules/api/entity/issueAndReturnVo/PurchaseOrderMoveToStockDto.java b/src/main/java/com/gaotao/modules/api/entity/issueAndReturnVo/PurchaseOrderMoveToStockDto.java new file mode 100644 index 0000000..2a929e3 --- /dev/null +++ b/src/main/java/com/gaotao/modules/api/entity/issueAndReturnVo/PurchaseOrderMoveToStockDto.java @@ -0,0 +1,34 @@ +package com.gaotao.modules.api.entity.issueAndReturnVo; + +import lombok.Data; +import java.math.BigDecimal; + +/** + * 采购订单移库到库存DTO + */ +@Data +public class PurchaseOrderMoveToStockDto { + + private String ifsDBName; + private String domainUserID; + private String ifsSiteID; + private Long ifsReceiptSequence; + private String ifsSourceRef1; + private String ifsSourceRef2; + private String ifsSourceRef3; + private String ifsSourceRef4; + private Integer ifsReceiptNo; + private String ifsPartNo; + private String ifsConfigurationID; + private String ifsLocationNo; + private String ifsLotBatchNo; + private String ifsNewLotBatchNo; + private String ifsSerialNo; + private String ifsEngChgLevel; + private String ifsWDR; + private String ifsToLocationNo; + private Integer ifsHandlingUnitID; + private Integer ifsSourceQtyToMove; + private Integer ifsInQtyToMove; + private Integer ifsCatchQtyToMove; +} diff --git a/src/main/java/com/gaotao/modules/api/entity/issueAndReturnVo/PurchaseOrderReceiptVo.java b/src/main/java/com/gaotao/modules/api/entity/issueAndReturnVo/PurchaseOrderReceiptVo.java new file mode 100644 index 0000000..494ea2c --- /dev/null +++ b/src/main/java/com/gaotao/modules/api/entity/issueAndReturnVo/PurchaseOrderReceiptVo.java @@ -0,0 +1,38 @@ +package com.gaotao.modules.api.entity.issueAndReturnVo; + +import lombok.Data; + +/** + * 采购订单接收记录VO + */ +@Data +public class PurchaseOrderReceiptVo { + + private String sourceRef1; + private String sourceRef2; + private String sourceRef3; + private String sourceRef4; + private Integer receiptNo; + private String sourceRefType; + private String contract; + private String state; + private String sourcePartNo; + private String description; + private Double qtyArrived; + private Double qtyToInspect; + private Double qtyInspected; + private String uom; + private Integer noOfInspections; + private String receiver; + private String receiptReference; + private String deliverDate; + private String arrivalDate; + private String approvedDate; + private Double scrappedQty; + private Double returnedQty; + private Double invQtyArrived; + private String sender; + private String senderName; + private String senderType; + private Long receiptSequence; +} diff --git a/src/main/java/com/gaotao/modules/api/service/IfsApiIssueAndReturnService.java b/src/main/java/com/gaotao/modules/api/service/IfsApiIssueAndReturnService.java index 1db391a..e66dbaf 100644 --- a/src/main/java/com/gaotao/modules/api/service/IfsApiIssueAndReturnService.java +++ b/src/main/java/com/gaotao/modules/api/service/IfsApiIssueAndReturnService.java @@ -63,4 +63,14 @@ public interface IfsApiIssueAndReturnService { List getIssueForPurchaseOrder(String orderNo, String site, String releaseNo,String sequenceNo,String lineItemNo); String addPurchaseOrderUnIssueComponent(PurchaseOrderUnIssueComponentDto purchaseOrderUnIssueComponentDto); + + /** + * 获取采购订单接收记录 + */ + List getPurchaseOrderReceipt(String purchaseOrderNo, String site); + + /** + * 采购订单移库到库存 + */ + String purchaseOrderMoveToStockOneLocation(PurchaseOrderMoveToStockDto purchaseOrderMoveToStockDto); } diff --git a/src/main/java/com/gaotao/modules/api/service/impl/IfsApiIssueAndReturnServiceImpl.java b/src/main/java/com/gaotao/modules/api/service/impl/IfsApiIssueAndReturnServiceImpl.java index ca81e9e..6f8760c 100644 --- a/src/main/java/com/gaotao/modules/api/service/impl/IfsApiIssueAndReturnServiceImpl.java +++ b/src/main/java/com/gaotao/modules/api/service/impl/IfsApiIssueAndReturnServiceImpl.java @@ -3,6 +3,7 @@ package com.gaotao.modules.api.service.impl; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.gaotao.common.exception.XJException; +import com.gaotao.common.utils.IfsErrorMessageUtils; import com.gaotao.common.utils.HttpUtils; import com.gaotao.modules.api.entity.issueAndReturnVo.*; import com.gaotao.modules.api.service.IfsApiIssueAndReturnService; @@ -39,12 +40,12 @@ public class IfsApiIssueAndReturnServiceImpl implements IfsApiIssueAndReturnServ private String ifsDBName; @Value("${custom.ifs-domainUserID}") private String domainUserID; - + @Value("${ldap-control.control-flag:false}") private Boolean ldapFlag; private Logger logger = LogManager.getLogger(getClass()); - + /** * 获取当前用户的域控账号,如果开启了域控账号则获取用户的域控账号,否则使用配置的默认值 */ @@ -678,4 +679,70 @@ public class IfsApiIssueAndReturnServiceImpl implements IfsApiIssueAndReturnServ throw new XJException("UnIssue委外退料同步IFS失败"); } } + + @Override + public List getPurchaseOrderReceipt(String purchaseOrderNo, String site) { + try { + Map params = Map.of( + "ifsDBName", ifsDBName, + "domainUserID", getCurrentDomainUserID(), + "ifsSiteID", site, + "ifsPurchaseOrderNo", purchaseOrderNo + ); + + ObjectMapper objectMapper = new ObjectMapper(); + String jsonBody = objectMapper.writeValueAsString(params); + String ifsResponse = HttpUtils.doGetWithBody(ifsUrl + "PurchaseOrderReceipt", jsonBody, null); + return objectMapper.readValue(ifsResponse, new TypeReference>() {}); + } catch (Exception e) { + log.error("获取采购订单接收记录失败: {}", e.getMessage()); + throw new XJException("获取采购订单接收记录失败: " + e.getMessage()); + } + } + + @Override + public String purchaseOrderMoveToStockOneLocation(PurchaseOrderMoveToStockDto dto) { + try { + Map params = new HashMap<>(); + params.put("ifsDBName", dto.getIfsDBName()); + params.put("domainUserID", dto.getDomainUserID()); + params.put("ifsSiteID", dto.getIfsSiteID()); + params.put("ifsReceiptSequence", dto.getIfsReceiptSequence()); + params.put("ifsSourceRef1", dto.getIfsSourceRef1()); + params.put("ifsSourceRef2", dto.getIfsSourceRef2()); + params.put("ifsSourceRef3", dto.getIfsSourceRef3()); + params.put("ifsSourceRef4", dto.getIfsSourceRef4() != null ? dto.getIfsSourceRef4() : ""); + params.put("ifsReceiptNo", dto.getIfsReceiptNo()); + params.put("ifsPartNo", dto.getIfsPartNo()); + params.put("ifsConfigurationID", dto.getIfsConfigurationID() != null ? dto.getIfsConfigurationID() : "*"); + params.put("ifsLocationNo", dto.getIfsLocationNo()); + params.put("ifsLotBatchNo", dto.getIfsLotBatchNo()); + params.put("ifsNewLotBatchNo", dto.getIfsNewLotBatchNo()); + params.put("ifsSerialNo", dto.getIfsSerialNo() != null ? dto.getIfsSerialNo() : "*"); + params.put("ifsEngChgLevel", dto.getIfsEngChgLevel() != null ? dto.getIfsEngChgLevel() : "1"); + params.put("ifsWDR", dto.getIfsWDR()); + params.put("ifsToLocationNo", dto.getIfsToLocationNo()); + params.put("ifsHandlingUnitID", dto.getIfsHandlingUnitID() != null ? dto.getIfsHandlingUnitID() : 0); + params.put("ifsSourceQtyToMove", dto.getIfsSourceQtyToMove()); + params.put("ifsInQtyToMove", dto.getIfsInQtyToMove()); + params.put("ifsCatchQtyToMove", dto.getIfsCatchQtyToMove() != null ? dto.getIfsCatchQtyToMove() : 0); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonBody = objectMapper.writeValueAsString(params); + String ifsResponse = HttpUtils.doPost(ifsUrl + "PurchaseOrderMoveToStockOneLocation", jsonBody, null); + + if ("IFSUpdated".equals(ifsResponse) || "\"IFSUpdated\"".equals(ifsResponse)) { + log.info("IFS采购订单移库成功"); + return ifsResponse; + } else { + log.error("IFS采购订单移库失败,响应: {}", ifsResponse); + // 提取Oracle错误信息中的关键内容 + String errorMessage = IfsErrorMessageUtils.extractOracleErrorMessage(ifsResponse); + throw new XJException(errorMessage); + } + } catch (Exception e) { + log.error("采购订单移库失败: {}", e.getMessage()); + throw new XJException(e.getMessage()); + } + } + } diff --git a/src/main/java/com/gaotao/modules/inspection/controller/QualifiedStorageController.java b/src/main/java/com/gaotao/modules/inspection/controller/QualifiedStorageController.java new file mode 100644 index 0000000..283fa4f --- /dev/null +++ b/src/main/java/com/gaotao/modules/inspection/controller/QualifiedStorageController.java @@ -0,0 +1,63 @@ +package com.gaotao.modules.inspection.controller; + +import com.gaotao.common.utils.R; +import com.gaotao.modules.inspection.service.QualifiedStorageService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +/** + * 检验合格入库控制器 + */ +@RestController +@RequestMapping("/qualified-storage") +public class QualifiedStorageController { + + @Autowired + private QualifiedStorageService qualifiedStorageService; + + /** + * 获取采购订单接收记录列表(只显示ARRIVED状态) + */ + @PostMapping("/getPurchaseOrderReceiptList") + public R getPurchaseOrderReceiptList(@RequestBody Map params) { + try { + String purchaseOrderNo = (String) params.get("purchaseOrderNo"); + String site = (String) params.get("site"); + + return qualifiedStorageService.getPurchaseOrderReceiptList(purchaseOrderNo, site); + } catch (Exception e) { + return R.error("获取采购订单接收记录失败: " + e.getMessage()); + } + } + + /** + * 校验并获取HandlingUnit信息(用于检验合格入库) + */ + @PostMapping("/validateHandlingUnit") + public R validateHandlingUnit(@RequestBody Map params) { + try { + String unitId = (String) params.get("unitId"); + String site = (String) params.get("site"); + String expectedPartNo = (String) params.get("expectedPartNo"); + String expectedBatchNo = (String) params.get("expectedBatchNo"); + + return qualifiedStorageService.validateHandlingUnitForQualifiedStorage(unitId, site, expectedPartNo, expectedBatchNo); + } catch (Exception e) { + return R.error("HandlingUnit校验失败: " + e.getMessage()); + } + } + + /** + * 确认检验合格入库 + */ + @PostMapping("/confirmQualifiedStorage") + public R confirmQualifiedStorage(@RequestBody Map params) { + try { + return qualifiedStorageService.confirmQualifiedStorage(params); + } catch (Exception e) { + return R.error("检验合格入库失败: " + e.getMessage()); + } + } +} diff --git a/src/main/java/com/gaotao/modules/inspection/service/QualifiedStorageService.java b/src/main/java/com/gaotao/modules/inspection/service/QualifiedStorageService.java new file mode 100644 index 0000000..7518a06 --- /dev/null +++ b/src/main/java/com/gaotao/modules/inspection/service/QualifiedStorageService.java @@ -0,0 +1,26 @@ +package com.gaotao.modules.inspection.service; + +import com.gaotao.common.utils.R; + +import java.util.Map; + +/** + * 检验合格入库服务接口 + */ +public interface QualifiedStorageService { + + /** + * 获取采购订单接收记录列表(只显示ARRIVED状态) + */ + R getPurchaseOrderReceiptList(String purchaseOrderNo, String site); + + /** + * 校验并获取HandlingUnit信息(用于检验合格入库) + */ + R validateHandlingUnitForQualifiedStorage(String unitId, String site, String expectedPartNo, String expectedBatchNo); + + /** + * 确认检验合格入库 + */ + R confirmQualifiedStorage(Map params); +} diff --git a/src/main/java/com/gaotao/modules/inspection/service/impl/QualifiedStorageServiceImpl.java b/src/main/java/com/gaotao/modules/inspection/service/impl/QualifiedStorageServiceImpl.java new file mode 100644 index 0000000..0577789 --- /dev/null +++ b/src/main/java/com/gaotao/modules/inspection/service/impl/QualifiedStorageServiceImpl.java @@ -0,0 +1,448 @@ +package com.gaotao.modules.inspection.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.gaotao.common.exception.XJException; +import com.gaotao.common.utils.IfsErrorMessageUtils; +import com.gaotao.common.utils.R; +import com.gaotao.modules.api.entity.issueAndReturnVo.PurchaseOrderMoveToStockDto; +import com.gaotao.modules.api.entity.issueAndReturnVo.PurchaseOrderReceiptVo; +import com.gaotao.modules.api.service.IfsApiIssueAndReturnService; +import com.gaotao.modules.handlingunit.entity.HandlingUnit; +import com.gaotao.modules.handlingunit.service.HandlingUnitService; +import com.gaotao.modules.inspection.entity.InboundConfirmDto; +import com.gaotao.modules.inspection.service.QualifiedStorageService; +import com.gaotao.modules.warehouse.entity.InventoryStock; +import com.gaotao.modules.warehouse.dao.InventoryStockMapper; +import com.gaotao.modules.po.entity.PoReceipt; +import com.gaotao.modules.po.entity.PoReceiptDetail; +import com.gaotao.modules.po.service.PoReceiptDetailService; +import com.gaotao.modules.po.service.PoReceiptService; +import com.gaotao.modules.trans.entity.TransDetail; +import com.gaotao.modules.trans.entity.TransDetailSub; +import com.gaotao.modules.trans.entity.TransHeader; +import com.gaotao.modules.trans.entity.TransNoControl; +import com.gaotao.modules.trans.service.TransDetailService; +import com.gaotao.modules.trans.service.TransDetailSubService; +import com.gaotao.modules.trans.service.TransHeaderService; +import com.gaotao.modules.trans.service.TransNoControlService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; +import org.apache.shiro.SecurityUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.gaotao.modules.sys.entity.SysUserEntity; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 检验合格入库服务实现类 + */ +@Slf4j +@Service +public class QualifiedStorageServiceImpl implements QualifiedStorageService { + + @Autowired + private IfsApiIssueAndReturnService ifsApiIssueAndReturnService; + + @Autowired + private HandlingUnitService handlingUnitService; + + @Autowired + private PoReceiptService poReceiptService; + + @Autowired + private PoReceiptDetailService poReceiptDetailService; + + @Autowired + private TransHeaderService transHeaderService; + + @Autowired + private TransDetailService transDetailService; + + @Autowired + private TransDetailSubService transDetailSubService; + + @Autowired + private TransNoControlService transNoControlService; + + @Autowired + private InventoryStockMapper inventoryStockMapper; + + @Value("${custom.ifs-ifsDBName}") + private String ifsDBName; + + @Value("${custom.ifs-domainUserID}") + private String domainUserID; + + @Value("${ldap-control.control-flag:false}") + private Boolean ldapFlag; + + /** + * 获取当前用户的域控账号 + */ + private String getCurrentDomainUserID() { + if (ldapFlag) { + try { + SysUserEntity currentUser = (SysUserEntity) SecurityUtils.getSubject().getPrincipal(); + if (currentUser != null && StringUtils.isNotBlank(currentUser.getDomainAccount())) { + return currentUser.getDomainAccount(); + } + } catch (Exception e) { + // 如果获取当前用户失败,使用默认配置 + } + } + return domainUserID; + } + + @Override + public R getPurchaseOrderReceiptList(String purchaseOrderNo, String site) { + try { + // 调用IFS接口获取采购订单接收记录 + List allReceipts = ifsApiIssueAndReturnService.getPurchaseOrderReceipt(purchaseOrderNo, site); + + // 只保留状态为ARRIVED的记录 + List arrivedReceipts = allReceipts.stream() + .filter(receipt -> "ARRIVED".equals(receipt.getState()) && receipt.getQtyToInspect() == 0) + .collect(Collectors.toList()); + + return R.ok().put("rows", arrivedReceipts); + } catch (Exception e) { + log.error("获取采购订单接收记录失败: {}", e.getMessage()); + return R.error("获取采购订单接收记录失败: " + e.getMessage()); + } + } + + @Override + public R validateHandlingUnitForQualifiedStorage(String unitId, String site, String expectedPartNo, String expectedBatchNo) { + try { + // 1. 基础参数校验 + if (StringUtils.isBlank(unitId)) { + return R.error("HandlingUnit条码不能为空"); + } + if (StringUtils.isBlank(site)) { + return R.error("站点不能为空"); + } + if (StringUtils.isBlank(expectedPartNo)) { + return R.error("期望物料编码不能为空"); + } + + // 2. 查询HandlingUnit + HandlingUnit handlingUnit = handlingUnitService.lambdaQuery() + .eq(HandlingUnit::getUnitId, unitId) + .one(); + + if (handlingUnit == null) { + return R.error("HandlingUnit不存在: " + unitId); + } + + // 3. 站点匹配校验 + if (!site.equals(handlingUnit.getSite())) { + return R.error("HandlingUnit站点不匹配,期望: " + site + ",实际: " + handlingUnit.getSite()); + } + + // 4. 物料编码匹配校验 + if (!expectedPartNo.equals(handlingUnit.getPartNo())) { + return R.error("HandlingUnit物料编码不匹配,期望: " + expectedPartNo + ",实际: " + handlingUnit.getPartNo()); + } + + // 5. 批次号匹配校验 + if (StringUtils.isNotBlank(expectedBatchNo)) { + String actualBatchNo = handlingUnit.getBatchNo(); + if (!expectedBatchNo.equals(actualBatchNo)) { + return R.error("HandlingUnit批次号不匹配,期望: " + expectedBatchNo + ",实际: " + actualBatchNo); + } + } + + // 7. 在库状态校验(检验合格入库通常要求HU已经在库) + if (!"X".equals(handlingUnit.getInStockFlag())) { + return R.error("HandlingUnit不是未入库状态,无法进行检验合格入库"); + } + // 8. 返回HandlingUnit信息 + Map huInfo = Map.ofEntries( + Map.entry("unitId", handlingUnit.getUnitId()), + Map.entry("partNo", handlingUnit.getPartNo()), + Map.entry("partDesc", handlingUnit.getPartDesc() != null ? handlingUnit.getPartDesc() : ""), + Map.entry("qty", handlingUnit.getQty() != null ? handlingUnit.getQty() : BigDecimal.ZERO), + Map.entry("unit", handlingUnit.getUmId()!= null ? handlingUnit.getUmId() : "个"), + Map.entry("batchNo", handlingUnit.getBatchNo() != null ? handlingUnit.getBatchNo() : ""), + Map.entry("locationId", handlingUnit.getLocationId() != null ? handlingUnit.getLocationId() : ""), + Map.entry("wdr", handlingUnit.getWdr() != null ? handlingUnit.getWdr() : "*"), + Map.entry("site", handlingUnit.getSite()), + Map.entry("status", handlingUnit.getStatus()), + Map.entry("inStockFlag", handlingUnit.getInStockFlag()) + ); + + return R.ok().put("data", huInfo); + + } catch (Exception e) { + log.error("HandlingUnit校验失败: {}", e.getMessage()); + return R.error("HandlingUnit校验失败: " + e.getMessage()); + } + } + + @Override + @Transactional + public R confirmQualifiedStorage(Map params) { + try { + SysUserEntity currentUser = (SysUserEntity) SecurityUtils.getSubject().getPrincipal(); + + // 解析参数 + String site = (String) params.get("site"); + Long receiptSequence = Long.valueOf(params.get("receiptSequence").toString()); + String sourceRef1 = (String) params.get("sourceRef1"); + String sourceRef2 = (String) params.get("sourceRef2"); + String sourceRef3 = (String) params.get("sourceRef3"); + String sourceRef4 = (String) params.get("sourceRef4"); + Integer receiptNo = Integer.valueOf(params.get("receiptNo").toString()); + String partNo = (String) params.get("partNo"); + String locationNo = (String) params.get("locationNo"); + String lotBatchNo = (String) params.get("lotBatchNo"); + String wdr = (String) params.get("wdr"); + String toLocationNo = (String) params.get("toLocationNo"); + Integer qtyToMove = Integer.valueOf(params.get("qtyToMove").toString()); + + @SuppressWarnings("unchecked") + List handlingUnitIds = (List) params.get("handlingUnitIds"); + + // 1. 获取HandlingUnit信息并校验 + List handlingUnits = handlingUnitService.lambdaQuery() + .in(HandlingUnit::getUnitId, handlingUnitIds) + .eq(HandlingUnit::getSite, site) + .list(); + + if (handlingUnits.size() != handlingUnitIds.size()) { + return R.error("部分HandlingUnit不存在或站点不匹配"); + } + + // 计算HandlingUnit总数量 + BigDecimal totalScannedQty = handlingUnits.stream() + .map(hu -> hu.getQty() != null ? hu.getQty() : BigDecimal.ZERO) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + // 计算可用数量 (qtyArrived - scrappedQty - returnedQty) + Double qtyArrived = Double.valueOf(params.get("qtyArrived").toString()); + Double scrappedQty = Double.valueOf(params.get("scrappedQty").toString()); + Double returnedQty = Double.valueOf(params.get("returnedQty").toString()); + double availableQty = qtyArrived - scrappedQty - returnedQty; + + if (totalScannedQty.doubleValue() > availableQty) { + return R.error("扫描的HandlingUnit总数量不能大于可用数量"); + } + + // 从第一个HandlingUnit获取批次号和WDR信息(如果前端没有传递) + HandlingUnit firstHu = handlingUnits.get(0); + if (lotBatchNo == null || lotBatchNo.isEmpty()) { + lotBatchNo = firstHu.getBatchNo(); + } + if (wdr == null || wdr.isEmpty()) { + wdr = firstHu.getWdr() != null ? firstHu.getWdr() : "*"; + } + if (locationNo == null || locationNo.isEmpty()) { + locationNo = firstHu.getLocationId(); // 原库位 + } + + // 2. 创建入库事务头记录 + TransNoControl transNoControl = transNoControlService.getTransNo(site, "INB", 12); + String inboundTransNo = transNoControl.getNewTransNo(); + + TransHeader inboundHeader = new TransHeader(); + inboundHeader.setSite(site); + inboundHeader.setTransNo(inboundTransNo); + inboundHeader.setTransDate(new Date()); + inboundHeader.setTransTypeDb("INB"); // 检验合格入库 + inboundHeader.setWarehouseId("WH01"); // 默认仓库,可以从参数中获取 + inboundHeader.setUserId(currentUser.getUserId().toString()); + inboundHeader.setUserName(currentUser.getUserDisplay()); + inboundHeader.setRemark("检验合格入库 - 接收序列号: " + receiptSequence); + inboundHeader.setOrderRef1(sourceRef1); // PO号 + inboundHeader.setStatus("COMPLETED"); + inboundHeader.setStatusDb("C"); + inboundHeader.setEnterDate(new Date()); + inboundHeader.setIfsFlag("N"); + + transHeaderService.save(inboundHeader); + + // 3. 创建入库事务明细记录 + TransDetail inboundDetail = new TransDetail(); + inboundDetail.setSite(site); + inboundDetail.setTransNo(inboundTransNo); + inboundDetail.setItemNo(1.0); + inboundDetail.setPartNo(partNo); + inboundDetail.setTransQty(totalScannedQty); + inboundDetail.setBatchNo(lotBatchNo); + inboundDetail.setLocationId(toLocationNo); + inboundDetail.setDirection("+"); // 入库方向 + inboundDetail.setOrderRef1(sourceRef1); // PO号 + inboundDetail.setOrderRef2(receiptSequence.toString()); // 接收序列号 + inboundDetail.setOrderRef3(""); + inboundDetail.setOrderRef4(""); + inboundDetail.setOrderRef5(""); + inboundDetail.setRemark("检验合格入库"); + transDetailService.save(inboundDetail); + + // 4. 创建HandlingUnit子记录 + for (int i = 0; i < handlingUnits.size(); i++) { + HandlingUnit hu = handlingUnits.get(i); + TransDetailSub transDetailSub = new TransDetailSub(); + transDetailSub.setSite(site); + transDetailSub.setTransNo(inboundTransNo); + transDetailSub.setItemNo((double) (i + 1)); + //transDetailSub.setHandlingUnitId(hu.getUnitId()); + transDetailSub.setSeqNo(1.0); + transDetailSub.setSubQty(hu.getQty() != null ? hu.getQty().doubleValue() : 0.0); + transDetailSub.setOrderRef1(sourceRef1); + transDetailSub.setOrderRef2(receiptSequence.toString()); + transDetailSub.setLocationId(toLocationNo); + transDetailSub.setBatchNo(lotBatchNo); + transDetailSub.setPartNo(partNo); + transDetailSub.setSubNo(hu.getUnitId()); + transDetailSub.setDirection("+"); + transDetailSub.setRemark("检验合格入库 - HU: " + hu.getUnitId()); + transDetailSubService.save(transDetailSub); + } + + // 5. 更新HandlingUnit状态 + QueryWrapper huWrapper = new QueryWrapper<>(); + huWrapper.in("unit_id", handlingUnitIds); + HandlingUnit huUpdate = new HandlingUnit(); + huUpdate.setStatus("ACTIVE"); + huUpdate.setInStockFlag("Y"); + huUpdate.setLocationId(toLocationNo); // 更新库位 + huUpdate.setModifiedDate(new Date()); + handlingUnitService.update(huUpdate, huWrapper); + + // 6. 更新库存 + updateInventoryStock(inboundHeader, inboundDetail, qtyToMove, firstHu); + + // 7. 调用IFS接口进行移库 + PurchaseOrderMoveToStockDto moveDto = new PurchaseOrderMoveToStockDto(); + moveDto.setIfsDBName(ifsDBName); + moveDto.setDomainUserID(getCurrentDomainUserID()); + moveDto.setIfsSiteID(site); + moveDto.setIfsReceiptSequence(receiptSequence); + moveDto.setIfsSourceRef1(sourceRef1); + moveDto.setIfsSourceRef2(sourceRef2); + moveDto.setIfsSourceRef3(sourceRef3); + moveDto.setIfsSourceRef4(sourceRef4); + moveDto.setIfsReceiptNo(receiptNo); + moveDto.setIfsPartNo(partNo); + moveDto.setIfsConfigurationID("*"); + moveDto.setIfsLocationNo(locationNo); + moveDto.setIfsLotBatchNo(lotBatchNo); + moveDto.setIfsNewLotBatchNo(lotBatchNo); + moveDto.setIfsSerialNo("*"); + moveDto.setIfsEngChgLevel("1"); + moveDto.setIfsWDR(wdr); + moveDto.setIfsToLocationNo(toLocationNo); + moveDto.setIfsHandlingUnitID(0); + moveDto.setIfsSourceQtyToMove(qtyToMove); + moveDto.setIfsInQtyToMove(qtyToMove); + moveDto.setIfsCatchQtyToMove(0); + + String result = ifsApiIssueAndReturnService.purchaseOrderMoveToStockOneLocation(moveDto); + + if ("IFSUpdated".equals(result) || "\"IFSUpdated\"".equals(result)) { + log.info("检验合格入库成功,事务号: {}, HandlingUnit数量: {}", inboundTransNo, handlingUnits.size()); + return R.ok("检验合格入库成功"); + } else { + // 提取Oracle错误信息中的关键内容 + String errorMessage = IfsErrorMessageUtils.extractOracleErrorMessage(result); + throw new XJException("IFS移库失败: " + errorMessage); + } + + } catch (Exception e) { + log.error("检验合格入库失败: {}", e.getMessage()); + throw new RuntimeException(e.getMessage(), e); + } + } + + /** + * 更新库存 + */ + private void updateInventoryStock(TransHeader transHeader, TransDetail transDetail,Integer qty, + HandlingUnit firstHu) { + // 直接在此方法中实现库存创建逻辑,使用带行锁的库存操作,防止并发 + String site = transDetail.getSite(); + String warehouseId = transHeader.getWarehouseId(); + String partNo = transDetail.getPartNo(); + String batchNo = transDetail.getBatchNo(); + String locationId = transDetail.getLocationId(); + String wdr = firstHu.getWdr() != null ? firstHu.getWdr() : "*"; + BigDecimal transQty = BigDecimal.valueOf(qty); + + // 使用行锁查询现有库存 + InventoryStock existingStock = inventoryStockMapper.selectForUpdate(site, warehouseId, partNo, batchNo, locationId, wdr); + + if (existingStock != null) { + // 库存存在,更新库存(检验入库一般不涉及HandlingUnit) + updateExistingStockForInspection(site, warehouseId, partNo, batchNo, locationId, wdr, transQty); + } else { + // 库存不存在,创建新库存记录 + createNewStockForInspection(site, warehouseId, partNo, batchNo, locationId, wdr, transQty, firstHu); + } + } + + /** + * 更新现有库存(检验入库) + */ + private void updateExistingStockForInspection(String site, String warehouseId, String partNo, + String batchNo, String locationId, String wdr, + BigDecimal addQty) { + // 检验入库一般不更新HandlingUnit数量 + int updateResult = inventoryStockMapper.updateStockWithoutHandlingUnit( + site, warehouseId, partNo, batchNo, locationId, wdr, addQty); + + if (updateResult == 0) { + throw new XJException("库存更新失败,可能记录已被删除或修改"); + } + } + + /** + * 创建新库存记录(检验入库) + */ + private void createNewStockForInspection(String site, String warehouseId, String partNo, + String batchNo, String locationId, String wdr, + BigDecimal transQty, HandlingUnit firstHu) { + InventoryStock newStock = new InventoryStock(); + newStock.setSite(site); + newStock.setWarehouseId(warehouseId); + newStock.setPartNo(partNo); + newStock.setBatchNo(batchNo); + newStock.setLocationId(locationId); + newStock.setInQty(transQty); + newStock.setInStandardValue(transQty); + newStock.setInActualValue(transQty); + newStock.setQtyOnHand(transQty); + newStock.setOutQty(BigDecimal.ZERO); + newStock.setQtyReserved(BigDecimal.ZERO); + + // 从 PoReceiptDetail 中获取制造日期和失效日期 + newStock.setManufactureDate(firstHu.getManufactureDate()); + newStock.setExpiredDate(firstHu.getExpiredDate()); + + newStock.setFreezeFlag("N"); + newStock.setFirstInDate(firstHu.getReceiveDate()); + newStock.setLatestInDate(firstHu.getReceiveDate()); + newStock.setActiveDate(firstHu.getReceiveDate()); + newStock.setWdr(wdr); + + // 从 PoReceiptDetail 中获取长度和宽度 + newStock.setLength(firstHu.getLength()); + newStock.setWidth(firstHu.getWidth()); + + + // 检验入库一般不设置HandlingUnitQty + + int insertResult = inventoryStockMapper.insert(newStock); + if (insertResult == 0) { + throw new XJException("库存记录创建失败"); + } + } + +} diff --git a/src/main/java/com/gaotao/modules/warehouse/service/impl/IfsInventoryInitServiceImpl.java b/src/main/java/com/gaotao/modules/warehouse/service/impl/IfsInventoryInitServiceImpl.java index 0f51d6a..72d9df4 100644 --- a/src/main/java/com/gaotao/modules/warehouse/service/impl/IfsInventoryInitServiceImpl.java +++ b/src/main/java/com/gaotao/modules/warehouse/service/impl/IfsInventoryInitServiceImpl.java @@ -81,7 +81,7 @@ public class IfsInventoryInitServiceImpl implements IfsInventoryInitService { handlingUnit.setStatusDb("ACTIVE"); handlingUnit.setFreezeFlag("N"); handlingUnit.setMergedFlag("N"); - handlingUnit.setInStockFlag("X"); + handlingUnit.setInStockFlag("Y"); handlingUnit.setCreatedDate(new Date()); handlingUnit.setCreatedBy("SYSTEM"); handlingUnit.setSourceType("IFS_INIT"); @@ -113,7 +113,7 @@ public class IfsInventoryInitServiceImpl implements IfsInventoryInitService { handlingUnit.setWidth(width); handlingUnit.setLength(length); - handlingUnit.setModifiedDate(ifsStock.getManufactureDate()); + handlingUnit.setManufactureDate(ifsStock.getManufactureDate()); handlingUnit.setExpiredDate(ifsStock.getExpiredDate()); handlingUnit.setUmId(request.getUmid()); @@ -135,13 +135,13 @@ public class IfsInventoryInitServiceImpl implements IfsInventoryInitService { } // 校验打印数量不能超过入库数量 - if (ifsStock.getInQty() != null) { + if (ifsStock.getQtyOnHand() != null) { Integer currentPrintQty = ifsStock.getPrintQty() != null ? ifsStock.getPrintQty().intValue() : 0; int newPrintQty = currentPrintQty + totalPrintQty; - if (newPrintQty > ifsStock.getInQty().intValue()) { + if (newPrintQty > ifsStock.getQtyOnHand().intValue()) { throw new RuntimeException(String.format( - "打印数量不能超过入库数量!当前已打印:%d,本次打印:%d,入库数量:%d", - currentPrintQty, totalPrintQty, ifsStock.getInQty().intValue())); + "打印数量不能超过库存数量!当前已打印:%d,本次打印:%d,库存数量:%d", + currentPrintQty, totalPrintQty, ifsStock.getQtyOnHand().intValue())); } }