diff --git a/src/main/java/com/gaotao/modules/inspection/service/impl/InspectionInboundServiceImpl.java b/src/main/java/com/gaotao/modules/inspection/service/impl/InspectionInboundServiceImpl.java index 4dcbd27..338d2b8 100644 --- a/src/main/java/com/gaotao/modules/inspection/service/impl/InspectionInboundServiceImpl.java +++ b/src/main/java/com/gaotao/modules/inspection/service/impl/InspectionInboundServiceImpl.java @@ -426,8 +426,6 @@ public class InspectionInboundServiceImpl implements InspectionInboundService { newStock.setLength(poReceiptDetail.getLength()); newStock.setWidth(poReceiptDetail.getWidth()); - // 设置打印数量为1 - newStock.setPrintQty(BigDecimal.ONE); // 检验入库一般不设置HandlingUnitQty diff --git a/src/main/java/com/gaotao/modules/other/controller/OtherInboundController.java b/src/main/java/com/gaotao/modules/other/controller/OtherInboundController.java index 0f654cc..9d8e064 100644 --- a/src/main/java/com/gaotao/modules/other/controller/OtherInboundController.java +++ b/src/main/java/com/gaotao/modules/other/controller/OtherInboundController.java @@ -1,8 +1,10 @@ package com.gaotao.modules.other.controller; import com.gaotao.common.utils.R; +import com.gaotao.modules.other.entity.InventoryMoveRequestDto; import com.gaotao.modules.other.entity.OtherInboundRequestDto; import com.gaotao.modules.other.entity.OtherOutboundRequestDto; +import com.gaotao.modules.other.service.InventoryMoveService; import com.gaotao.modules.other.service.OtherInboundService; import com.gaotao.modules.other.service.OtherOutboundService; import com.gaotao.modules.warehouse.service.LocationService; @@ -28,6 +30,9 @@ public class OtherInboundController { @Autowired private OtherOutboundService otherOutboundService; + @Autowired + private InventoryMoveService inventoryMoveService; + /** * 确认其它入库 */ @@ -66,6 +71,19 @@ public class OtherInboundController { } } + /** + * 确认库存移库 + */ + @RequestMapping("/confirmInventoryMove") + public R confirmInventoryMove(@RequestBody InventoryMoveRequestDto dto) { + try { + inventoryMoveService.confirmInventoryMove(dto); + return R.ok("库存移库成功"); + } catch (Exception e) { + return R.error(e.getMessage()); + } + } + /** * 验证库位是否有效(测试接口) */ diff --git a/src/main/java/com/gaotao/modules/other/entity/InventoryMoveRequestDto.java b/src/main/java/com/gaotao/modules/other/entity/InventoryMoveRequestDto.java new file mode 100644 index 0000000..2609f70 --- /dev/null +++ b/src/main/java/com/gaotao/modules/other/entity/InventoryMoveRequestDto.java @@ -0,0 +1,38 @@ +package com.gaotao.modules.other.entity; + +import lombok.Data; + +import java.util.List; +import java.util.Map; + +/** + * 库存移库请求DTO + */ +@Data +public class InventoryMoveRequestDto { + + /** + * 站点 + */ + private String site; + + /** + * 移库原因 + */ + private String moveReason; + + /** + * 目标库位 + */ + private String targetLocationId; + + /** + * HandlingUnit ID列表 + */ + private List handlingUnitIds; + + /** + * 扫描的物料信息列表 + */ + private List> scannedItems; +} diff --git a/src/main/java/com/gaotao/modules/other/service/InventoryMoveService.java b/src/main/java/com/gaotao/modules/other/service/InventoryMoveService.java new file mode 100644 index 0000000..654fc96 --- /dev/null +++ b/src/main/java/com/gaotao/modules/other/service/InventoryMoveService.java @@ -0,0 +1,15 @@ +package com.gaotao.modules.other.service; + +import com.gaotao.modules.other.entity.InventoryMoveRequestDto; + +/** + * 库存移库服务接口 + */ +public interface InventoryMoveService { + + /** + * 确认库存移库 + * @param dto 移库请求数据 + */ + void confirmInventoryMove(InventoryMoveRequestDto dto); +} 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 new file mode 100644 index 0000000..73c1852 --- /dev/null +++ b/src/main/java/com/gaotao/modules/other/service/impl/InventoryMoveServiceImpl.java @@ -0,0 +1,333 @@ +package com.gaotao.modules.other.service.impl; + +import com.gaotao.common.exception.XJException; +import com.gaotao.common.utils.HttpUtils; +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.other.entity.InventoryMoveRequestDto; +import com.gaotao.modules.other.service.InventoryMoveService; +import com.gaotao.modules.sys.entity.SysUserEntity; +import com.gaotao.modules.trans.entity.TransDetail; +import com.gaotao.modules.trans.entity.TransHeader; +import com.gaotao.modules.trans.service.TransDetailService; +import com.gaotao.modules.trans.service.TransHeaderService; +import com.gaotao.modules.trans.service.TransNoControlService; +import com.gaotao.modules.warehouse.entity.Location; +import com.gaotao.modules.warehouse.service.InventoryStockService; +import com.gaotao.modules.warehouse.service.LocationService; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +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 java.math.BigDecimal; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 库存移库服务实现类 + */ +@Slf4j +@Service +public class InventoryMoveServiceImpl implements InventoryMoveService { + + @Autowired + private HandlingUnitService handlingUnitService; + + @Autowired + private TransHeaderService transHeaderService; + + @Autowired + private TransDetailService transDetailService; + + @Autowired + private TransNoControlService transNoControlService; + + @Autowired + private InventoryStockService inventoryStockService; + + @Autowired + private LocationService locationService; + + @Value("${custom.ifs-url}") + private String ifsUrl; + + @Value("${custom.ifs-ifsDBName}") + private String ifsDBName; + + @Value("${custom.ifs-domainUserID}") + private String domainUserID; + + @Override + @Transactional + public void confirmInventoryMove(InventoryMoveRequestDto dto) { + try { + SysUserEntity currentUser = (SysUserEntity) SecurityUtils.getSubject().getPrincipal(); + + // 1. 验证目标库位 + //validateTargetLocation(dto.getSite(), dto.getTargetLocationId()); + + // 2. 验证HandlingUnit + List handlingUnits = validateHandlingUnits(dto); + + // 3. 先保存原库位信息用于IFS同步,然后处理每个HandlingUnit + Map originalLocations = new HashMap<>(); + for (HandlingUnit hu : handlingUnits) { + originalLocations.put(hu.getUnitId(), hu.getLocationId()); + } + + // 4. 合并相同条件的HandlingUnit后调用IFS接口(在更新HU之前) + syncToIFSBatch(dto, handlingUnits); + + // 5. 为每个HandlingUnit创建出库和入库事务记录并更新数据 + for (HandlingUnit hu : handlingUnits) { + String originalLocationId = originalLocations.get(hu.getUnitId()); + + // 创建出库事务记录 + createOutboundTransaction(dto, hu, currentUser, originalLocationId); + + // 创建入库事务记录 + createInboundTransaction(dto, hu, currentUser); + + /*// 更新库存:从原库位出库 + inventoryStockService.changeInventoryStock( + hu.getSite(), + hu.getWarehouseId(), + hu.getPartNo(), + hu.getBatchNo(), + originalLocationId, // 使用原库位 + hu.getQty(), + hu.getWdr(), + "OUT" + ); + + // 更新库存:到目标库位入库 + inventoryStockService.changeInventoryStock( + hu.getSite(), + hu.getWarehouseId(), + hu.getPartNo(), + hu.getBatchNo(), + dto.getTargetLocationId(), + hu.getQty(), + hu.getWdr(), + "IN" + );*/ + + // 更新HandlingUnit库位 + hu.setLocationId(dto.getTargetLocationId()); + hu.setModifiedDate(new Date()); + hu.setModifiedBy(currentUser.getUserDisplay()); + handlingUnitService.updateById(hu); + } + + log.info("库存移库完成,处理了{}个HandlingUnit", handlingUnits.size()); + + } catch (Exception e) { + log.error("库存移库失败", e); + throw new XJException("库存移库失败: " + e.getMessage()); + } + } + + /** + * 验证目标库位 + */ + /* private void validateTargetLocation(String site, String targetLocationId) { + Location location = locationService.lambdaQuery() + .eq(Location::getSite, site) + .eq(Location::getLocationId, targetLocationId) + .one(); + + if (location == null) { + throw new XJException("目标库位不存在: " + targetLocationId); + } + + if (!"ACTIVE".equals(location.getStatus())) { + throw new XJException("目标库位状态不可用: " + targetLocationId); + } + } +*/ + /** + * 验证HandlingUnit + */ + private List validateHandlingUnits(InventoryMoveRequestDto dto) { + List handlingUnits = handlingUnitService.lambdaQuery() + .eq(HandlingUnit::getSite, dto.getSite()) + .in(HandlingUnit::getUnitId, dto.getHandlingUnitIds()) + .list(); + + if (handlingUnits.size() != dto.getHandlingUnitIds().size()) { + throw new XJException("部分HandlingUnit不存在或站点不匹配"); + } + + /* for (HandlingUnit hu : handlingUnits) { + if (!"Y".equals(hu.getInStockFlag())) { + throw new XJException("HandlingUnit不在库,无法移库: " + hu.getUnitId()); + } + + if (dto.getTargetLocationId().equals(hu.getLocationId())) { + throw new XJException("目标库位不能与当前库位相同: " + hu.getUnitId()); + } + }*/ + + return handlingUnits; + } + + /** + * 创建出库事务记录 + */ + private void createOutboundTransaction(InventoryMoveRequestDto dto, HandlingUnit hu, SysUserEntity currentUser, String originalLocationId) { + // 创建出库事务头 + TransHeader outboundHeader = new TransHeader(); + outboundHeader.setSite(dto.getSite()); + outboundHeader.setTransNo(transNoControlService.getTransNo(dto.getSite(), "MOV", 12).getNewTransNo()); + outboundHeader.setTransDate(new Date()); + outboundHeader.setTransTypeDb("MOV"); // 移库出库 + outboundHeader.setWarehouseId(hu.getWarehouseId()); + outboundHeader.setUserId(currentUser.getUserId().toString()); + outboundHeader.setUserName(currentUser.getUserDisplay()); + outboundHeader.setRemark("移库出库 - " + dto.getMoveReason()); + outboundHeader.setStatus("COMPLETED"); + outboundHeader.setStatusDb("C"); + outboundHeader.setEnterDate(new Date()); + outboundHeader.setIfsFlag("N"); + transHeaderService.save(outboundHeader); + + // 创建出库事务明细 + TransDetail outboundDetail = new TransDetail(); + outboundDetail.setSite(dto.getSite()); + outboundDetail.setTransNo(outboundHeader.getTransNo()); + outboundDetail.setItemNo(1.0); + outboundDetail.setPartNo(hu.getPartNo()); + outboundDetail.setBatchNo(hu.getBatchNo()); + outboundDetail.setLocationId(originalLocationId); // 原库位 + outboundDetail.setTransQty(hu.getQty()); + outboundDetail.setWdrNo(hu.getWdr()); + outboundDetail.setOrderRef4(hu.getWarehouseId()); + outboundDetail.setDirection("-"); + transDetailService.save(outboundDetail); + } + + /** + * 创建入库事务记录 + */ + private void createInboundTransaction(InventoryMoveRequestDto dto, HandlingUnit hu, SysUserEntity currentUser) { + // 创建入库事务头 + TransHeader inboundHeader = new TransHeader(); + inboundHeader.setSite(dto.getSite()); + inboundHeader.setTransNo(transNoControlService.getTransNo(dto.getSite(), "MOV", 12).getNewTransNo()); + inboundHeader.setTransDate(new Date()); + inboundHeader.setTransTypeDb("MOV"); // 移库入库 + inboundHeader.setWarehouseId(hu.getWarehouseId()); + inboundHeader.setUserId(currentUser.getUserId().toString()); + inboundHeader.setUserName(currentUser.getUserDisplay()); + inboundHeader.setRemark("移库入库 - " + dto.getMoveReason()); + inboundHeader.setStatus("COMPLETED"); + inboundHeader.setStatusDb("C"); + inboundHeader.setEnterDate(new Date()); + inboundHeader.setIfsFlag("N"); + transHeaderService.save(inboundHeader); + + // 创建入库事务明细 + TransDetail inboundDetail = new TransDetail(); + inboundDetail.setSite(dto.getSite()); + inboundDetail.setTransNo(inboundHeader.getTransNo()); + inboundDetail.setItemNo(1.0); + inboundDetail.setPartNo(hu.getPartNo()); + inboundDetail.setBatchNo(hu.getBatchNo()); + inboundDetail.setLocationId(dto.getTargetLocationId()); // 目标库位 + inboundDetail.setTransQty(hu.getQty()); + inboundDetail.setWdrNo(hu.getWdr()); + inboundDetail.setOrderRef4(hu.getWarehouseId()); + inboundDetail.setDirection("+"); + transDetailService.save(inboundDetail); + } + + /** + * 批量同步到IFS - 按site、partNo、locationNo、lotBatchNo合并后调用 + */ + private void syncToIFSBatch(InventoryMoveRequestDto dto, List handlingUnits) { + try { + // 按site、partNo、原库位、lotBatchNo分组合并数量 + Map moveGroups = new HashMap<>(); + + for (HandlingUnit hu : handlingUnits) { + String groupKey = String.format("%s|%s|%s|%s", + hu.getSite(), hu.getPartNo(), hu.getLocationId(), hu.getBatchNo()); + + MoveGroup group = moveGroups.computeIfAbsent(groupKey, k -> { + MoveGroup newGroup = new MoveGroup(); + newGroup.site = hu.getSite(); + newGroup.partNo = hu.getPartNo(); + newGroup.sourceLocationNo = hu.getLocationId(); // 注意:这里使用的是更新前的原库位 + newGroup.destLocationNo = dto.getTargetLocationId(); + newGroup.lotBatchNo = hu.getBatchNo(); + newGroup.totalQty = BigDecimal.ZERO; + return newGroup; + }); + + group.totalQty = group.totalQty.add(hu.getQty()); + } + + // 为每个分组调用IFS接口 + for (MoveGroup group : moveGroups.values()) { + syncSingleGroupToIFS(group); + } + + log.info("IFS批量移库同步完成,共{}个分组", moveGroups.size()); + + } catch (Exception e) { + log.error("IFS批量移库同步异常", e); + // 不抛异常,允许WMS内部移库成功但IFS同步失败 + } + } + + /** + * 同步单个分组到IFS + */ + private void syncSingleGroupToIFS(MoveGroup group) { + try { + Map params = new HashMap<>(); + params.put("ifsDBName", ifsDBName); + params.put("domainUserID", domainUserID); + params.put("ifsSiteID", group.site); + params.put("partNo", group.partNo); + params.put("qtyToIssue", group.totalQty.intValue()); + params.put("locationNo", group.sourceLocationNo); // 原库位 + params.put("destLocationNo", group.destLocationNo); // 目标库位 + params.put("lotBatchNo", group.lotBatchNo); + + ObjectMapper objectMapper = new ObjectMapper(); + String jsonBody = objectMapper.writeValueAsString(params); + String ifsResponse = HttpUtils.doPost(ifsUrl + "MoveInventoryPart", jsonBody, null); + + if ("IFSUpdated".equals(ifsResponse) || "\"IFSUpdated\"".equals(ifsResponse)) { + log.info("IFS移库同步成功 - 物料: {}, 批次: {}, 数量: {}, 从 {} 到 {}", + group.partNo, group.lotBatchNo, group.totalQty, group.sourceLocationNo, group.destLocationNo); + } else { + log.warn("IFS移库同步失败 - 物料: {}, 批次: {}, 响应: {}", + group.partNo, group.lotBatchNo, ifsResponse); + } + + } catch (Exception e) { + log.error("IFS移库同步异常 - 物料: {}, 批次: {}", group.partNo, group.lotBatchNo, e); + } + } + + /** + * 移库分组内部类 + */ + private static class MoveGroup { + String site; + String partNo; + String sourceLocationNo; + String destLocationNo; + String lotBatchNo; + BigDecimal totalQty; + } +} diff --git a/src/main/java/com/gaotao/modules/po/service/impl/PoServiceImpl.java b/src/main/java/com/gaotao/modules/po/service/impl/PoServiceImpl.java index 7de4110..d7567ee 100644 --- a/src/main/java/com/gaotao/modules/po/service/impl/PoServiceImpl.java +++ b/src/main/java/com/gaotao/modules/po/service/impl/PoServiceImpl.java @@ -363,14 +363,9 @@ public class PoServiceImpl extends ServiceImpl implemen newStock.setLatestInDate(new Date()); newStock.setActiveDate(new Date()); newStock.setWdr(wdr); - // 从 PoReceiptDetail 中获取长度和宽度 newStock.setLength(receiptDetail.getLength()); newStock.setWidth(receiptDetail.getWidth()); - - // 设置打印数量为1 - newStock.setPrintQty(BigDecimal.ONE); - if ("Y".equals(huFlag)) { newStock.setHandlingUnitQty(transQty); }