diff --git a/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/enums/ErrorCodeConstants.java b/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/enums/ErrorCodeConstants.java index ae31ec67..13105524 100644 --- a/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/enums/ErrorCodeConstants.java +++ b/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/enums/ErrorCodeConstants.java @@ -21,6 +21,11 @@ public interface ErrorCodeConstants { ErrorCode IOSTOR_INV_UPDATE_STATUS_INVALID = new ErrorCode(6_000_8, "只有生成状态的出库单允许修改"); ErrorCode IOSTOR_INV_NO_PENDING_ALLOCATION = new ErrorCode(6_000_9, "当前明细没有生成状态的分配记录"); ErrorCode IOSTOR_INV_NO_MOVE_LOCATION = new ErrorCode(6_001_0, "没有可用于移库的空闲仓位"); + ErrorCode INBOUND_GROUP_PLATE_INVALID = new ErrorCode(6_001_1, "组盘数据不存在或不是生成状态"); + ErrorCode INBOUND_STATUS_INVALID = new ErrorCode(6_001_2, "只有生成状态的入库单允许修改或分配"); + ErrorCode INBOUND_TARGET_INVALID = new ErrorCode(6_001_3, "目标货位不可用或与所选库区、仓库不一致"); + ErrorCode INBOUND_NOT_ALLOCATED = new ErrorCode(6_001_4, "入库单尚未完成货位分配"); + ErrorCode INBOUND_ALLOCATION_TASK_CREATED = new ErrorCode(6_001_5, "入库任务已创建,不允许取消货位分配"); // ================ 移库单相关错误码 ================= ErrorCode MOVE_INV_NOT_EXISTS = new ErrorCode(6_002_0, "移库单不存在"); diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/IostorInvController.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/IostorInvController.java index 9e8a8a33..3f6def93 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/IostorInvController.java +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/IostorInvController.java @@ -46,6 +46,80 @@ public class IostorInvController { return success(iostorInvService.createOutbound(reqVO)); } + @PostMapping("/create-inbound") + @Operation(summary = "创建入库单及明细") + @PreAuthorize("@ss.hasPermission('wms:iostor-inv:create')") + public CommonResult createInbound(@Valid @RequestBody InboundSaveReqVO reqVO) { + return success(iostorInvService.createInbound(reqVO)); + } + + @PutMapping("/update-inbound") + @Operation(summary = "修改入库单及明细") + @PreAuthorize("@ss.hasPermission('wms:iostor-inv:update')") + public CommonResult updateInbound(@Valid @RequestBody InboundSaveReqVO reqVO) { + iostorInvService.updateInbound(reqVO); + return success(true); + } + + @DeleteMapping("/delete-inbound") + @Operation(summary = "删除生成状态的入库单") + @PreAuthorize("@ss.hasPermission('wms:iostor-inv:delete')") + public CommonResult deleteInbound(@RequestParam("iostorinvId") Long iostorinvId) { + iostorInvService.deleteInbound(iostorinvId); + return success(true); + } + + @GetMapping("/inbound-group-plates") + @Operation(summary = "查询生成状态的待入库组盘数据") + @PreAuthorize("@ss.hasPermission('wms:iostor-inv:query')") + public CommonResult> + getInboundGroupPlates(@RequestParam("storId") String storId) { + return success(iostorInvService.getInboundGroupPlates(storId)); + } + + @PostMapping("/allocate-inbound") + @Operation(summary = "分配入库货位并按库区类型创建任务") + @PreAuthorize("@ss.hasPermission('wms:iostor-inv:update')") + public CommonResult allocateInbound(@Valid @RequestBody InboundAllocationReqVO reqVO) { + iostorInvService.allocateInbound(reqVO); + return success(true); + } + + @DeleteMapping("/cancel-inbound-allocation") + @Operation(summary = "取消单条入库货位分配并解锁货位") + @PreAuthorize("@ss.hasPermission('wms:iostor-inv:update')") + public CommonResult cancelInboundAllocation( + @RequestParam("iostorinvId") Long iostorinvId, + @RequestParam("iostorinvdtlId") Long iostorinvdtlId) { + iostorInvService.cancelInboundAllocation(iostorinvId, iostorinvdtlId); + return success(true); + } + + @GetMapping("/inbound-available-structs") + @Operation(summary = "查询入库可用货位") + @PreAuthorize("@ss.hasPermission('wms:iostor-inv:query')") + public CommonResult> + getInboundAvailableStructs(@RequestParam("storId") String storId, + @RequestParam("sectId") String sectId) { + return success(iostorInvService.getInboundAvailableStructs(storId, sectId)); + } + + @PostMapping("/set-inbound-point") + @Operation(summary = "设置入库起点并创建入库任务") + @PreAuthorize("@ss.hasPermission('wms:iostor-inv:update')") + public CommonResult setInboundPoint(@Valid @RequestBody InboundSetPointReqVO reqVO) { + iostorInvService.setInboundPoint(reqVO); + return success(true); + } + + @PostMapping("/complete-inbound") + @Operation(summary = "完成入库并绑定木箱与仓位") + @PreAuthorize("@ss.hasPermission('wms:iostor-inv:update')") + public CommonResult completeInbound(@RequestParam("iostorinvId") Long iostorinvId) { + iostorInvService.completeInbound(iostorinvId); + return success(true); + } + @GetMapping("/availableInventoryPage") @Operation(summary = "获得可用库存分页") @PreAuthorize("@ss.hasPermission('wms:iostor-inv:query')") diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/vo/IostorInvPageReqVO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/vo/IostorInvPageReqVO.java index ea47a533..740c22d6 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/vo/IostorInvPageReqVO.java +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/vo/IostorInvPageReqVO.java @@ -15,6 +15,9 @@ import static cn.code.nl.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_ @Data public class IostorInvPageReqVO extends PageParam { + @Schema(description = "出入类型:0入库,1出库") + private String ioType; + @Schema(description = "单据编号") private String billCode; @@ -22,7 +25,7 @@ public class IostorInvPageReqVO extends PageParam { private String billType; @Schema(description = "业务日期") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime[] bizDate; @Schema(description = "仓库标识", example = "12635") @@ -86,4 +89,4 @@ public class IostorInvPageReqVO extends PageParam { @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private String[] uploadTime; -} \ No newline at end of file +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/vo/OutboundAllocationRespVO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/vo/OutboundAllocationRespVO.java index 0be0d181..81357558 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/vo/OutboundAllocationRespVO.java +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/vo/OutboundAllocationRespVO.java @@ -17,6 +17,9 @@ public class OutboundAllocationRespVO { private String qtyUnitName; private String sectId; private String sectName; + private String structId; private String structCode; private String structName; + private String pointCode; + private String taskId; } diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/moveinv/vo/MoveInvRespVO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/moveinv/vo/MoveInvRespVO.java index 7fbab9c3..82805e7d 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/moveinv/vo/MoveInvRespVO.java +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/moveinv/vo/MoveInvRespVO.java @@ -1,6 +1,7 @@ package cn.code.nl.module.wms.controller.admin.moveinv.vo; import cn.code.nl.module.wms.dal.dataobject.moveinvdtl.MoveInvDtlDO; +import com.baomidou.mybatisplus.annotation.TableId; import lombok.Data; import cn.code.nl.module.system.api.user.AdminUserApi; import org.dromara.core.trans.anno.Trans; @@ -17,6 +18,10 @@ import java.util.List; @Data public class MoveInvRespVO implements VO { + /** + * EasyTrans 根据主键识别并缓存当前 VO,缺少该注解会导致分页结果翻译失败。 + */ + @TableId private Long moveinvId; private String billCode; private String billType; diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/iostorinv/IostorInvDO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/iostorinv/IostorInvDO.java index 939927ef..0f06f6b5 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/iostorinv/IostorInvDO.java +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/iostorinv/IostorInvDO.java @@ -60,6 +60,11 @@ public class IostorInvDO extends BaseDO { * 仓库名称 */ private String storName; + /** + * 创建人姓名,仅用于列表返回,不对应数据库字段。 + */ + @TableField(exist = false) + private String creatorName; /** * 来源方标识 */ diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/iostorinv/IostorInvMapper.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/iostorinv/IostorInvMapper.java index cc9d17ac..4068d429 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/iostorinv/IostorInvMapper.java +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/iostorinv/IostorInvMapper.java @@ -19,6 +19,7 @@ public interface IostorInvMapper extends BaseMapperX { default PageResult selectPage(IostorInvPageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(IostorInvDO::getIoType, reqVO.getIoType()) .eqIfPresent(IostorInvDO::getBillCode, reqVO.getBillCode()) .eqIfPresent(IostorInvDO::getBillType, reqVO.getBillType()) .betweenIfPresent(IostorInvDO::getBizDate, reqVO.getBizDate()) @@ -44,4 +45,4 @@ public interface IostorInvMapper extends BaseMapperX { .orderByDesc(IostorInvDO::getIostorinvId)); } -} \ No newline at end of file +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/iostorinvdis/IostorinvDisMapper.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/iostorinvdis/IostorinvDisMapper.java index 68e57e84..19f58e21 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/iostorinvdis/IostorinvDisMapper.java +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/iostorinvdis/IostorinvDisMapper.java @@ -7,6 +7,7 @@ import cn.code.nl.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.code.nl.framework.mybatis.core.mapper.BaseMapperX; import cn.code.nl.module.wms.dal.dataobject.iostorinvdis.IostorinvDisDO; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Delete; import cn.code.nl.module.wms.controller.admin.iostorinvdis.vo.*; import cn.code.nl.module.wms.controller.admin.iostorinv.vo.OutboundAllocationRespVO; import org.apache.ibatis.annotations.Param; @@ -24,6 +25,18 @@ public interface IostorinvDisMapper extends BaseMapperX { @Param("iostorinvdtlId") Long iostorinvdtlId, @Param("sectId") String sectId); + @Delete(""" + DELETE FROM wms_iostorinvdis + WHERE iostorinvdis_id = #{allocationId} + """) + int physicallyDeleteInboundAllocation(@Param("allocationId") String allocationId); + + @Delete(""" + DELETE FROM wms_iostorinvdis + WHERE iostorinv_id = #{iostorinvId} + """) + int physicallyDeleteByIostorinvId(@Param("iostorinvId") Long iostorinvId); + default PageResult selectPage(IostorinvDisPageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() .eqIfPresent(IostorinvDisDO::getIostorinvId, reqVO.getIostorinvId()) diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/iostorinvdtl/IostorinvDtlMapper.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/iostorinvdtl/IostorinvDtlMapper.java index 63af0536..82344123 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/iostorinvdtl/IostorinvDtlMapper.java +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/iostorinvdtl/IostorinvDtlMapper.java @@ -10,6 +10,7 @@ import cn.code.nl.framework.mybatis.core.mapper.BaseMapperX; import cn.code.nl.module.wms.dal.dataobject.iostorinvdtl.IostorinvDtlDO; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Delete; import cn.code.nl.module.wms.controller.admin.iostorinv.vo.AvailableInventoryPageReqVO; import cn.code.nl.module.wms.controller.admin.iostorinv.vo.AvailableInventoryRespVO; import cn.code.nl.module.wms.controller.admin.iostorinv.vo.IostorInvDetailRespVO; @@ -24,6 +25,12 @@ import cn.code.nl.module.wms.controller.admin.iostorinvdtl.vo.*; @Mapper public interface IostorinvDtlMapper extends BaseMapperX { + @Delete(""" + DELETE FROM wms_iostorinvdtl + WHERE iostorinv_id = #{iostorinvId} + """) + int physicallyDeleteByIostorinvId(@Param("iostorinvId") Long iostorinvId); + /** * 分页查询指定仓库的可用库存。 */ diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/structAttr/StrucAttrMapper.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/structAttr/StrucAttrMapper.java index 9c47471d..c43cb081 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/structAttr/StrucAttrMapper.java +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/structAttr/StrucAttrMapper.java @@ -83,6 +83,68 @@ public interface StrucAttrMapper extends BaseMapperX { int lockForTask(@Param("structId") String structId, @Param("taskCode") String taskCode); + @Update(""" + UPDATE wms_structattr + SET lock_type = '2', + inv_type = '0', + inv_id = #{invId}, + inv_code = #{invCode} + WHERE struct_id = #{structId} + AND deleted = '0' + AND is_used = 1 + AND lock_type = '1' + AND (storagevehicle_code IS NULL OR storagevehicle_code = '') + """) + int lockForInbound(@Param("structId") String structId, + @Param("invId") String invId, + @Param("invCode") String invCode); + + @Update(""" + UPDATE wms_structattr + SET storagevehicle_code = #{vehicleCode}, + storagevehicle_qty = 1, + lock_type = '1', + inv_type = NULL, + inv_id = NULL, + inv_code = NULL, + task_code = NULL + WHERE struct_id = #{structId} + AND deleted = '0' + AND lock_type = '2' + AND inv_id = #{invId} + """) + int completeInbound(@Param("structId") String structId, + @Param("invId") String invId, + @Param("vehicleCode") String vehicleCode); + + @Update(""" + UPDATE wms_structattr + SET lock_type = '1', + inv_type = NULL, + inv_id = NULL, + inv_code = NULL, + task_code = NULL + WHERE deleted = '0' + AND inv_id = #{invId} + AND lock_type = '2' + """) + int unlockInbound(@Param("invId") String invId); + + @Update(""" + UPDATE wms_structattr + SET lock_type = '1', + inv_type = NULL, + inv_id = NULL, + inv_code = NULL, + task_code = NULL + WHERE struct_id = #{structId} + AND deleted = '0' + AND inv_id = #{invId} + AND lock_type = '2' + """) + int unlockInboundStruct(@Param("structId") String structId, + @Param("invId") String invId); + default PageResult selectPage(StrucAttrPageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() .eqIfPresent(StrucAttrDO::getStructCode, reqVO.getStructCode()) diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/framework/rpc/config/RpcConfiguration.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/framework/rpc/config/RpcConfiguration.java index 672d1fa1..12b40d56 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/framework/rpc/config/RpcConfiguration.java +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/framework/rpc/config/RpcConfiguration.java @@ -2,6 +2,7 @@ package cn.code.nl.module.wms.framework.rpc.config; import cn.code.nl.module.base.api.codegen.CodeGenApi; import cn.code.nl.module.infra.api.config.ConfigApi; +import cn.code.nl.module.system.api.user.AdminUserApi; import cn.code.nl.module.task.api.TransportTaskApi; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Configuration; @@ -12,6 +13,6 @@ import org.springframework.context.annotation.Configuration; * @Date: 2026/7/22 13:50 */ @Configuration(value = "wmsRpcConfiguration", proxyBeanMethods = false) -@EnableFeignClients(clients = {CodeGenApi.class, TransportTaskApi.class, ConfigApi.class}) +@EnableFeignClients(clients = {CodeGenApi.class, TransportTaskApi.class, ConfigApi.class, AdminUserApi.class}) public class RpcConfiguration { } diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/iostorinv/IostorInvService.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/iostorinv/IostorInvService.java index 084dcc7c..6a359f9e 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/iostorinv/IostorInvService.java +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/iostorinv/IostorInvService.java @@ -23,6 +23,25 @@ public interface IostorInvService { */ Long createOutbound(@Valid IostorInvCreateReqVO reqVO); + Long createInbound(@Valid InboundSaveReqVO reqVO); + + void updateInbound(@Valid InboundSaveReqVO reqVO); + + void deleteInbound(Long iostorinvId); + + List getInboundGroupPlates(String storId); + + void allocateInbound(@Valid InboundAllocationReqVO reqVO); + + void cancelInboundAllocation(Long iostorinvId, Long iostorinvdtlId); + + List + getInboundAvailableStructs(String storId, String sectId); + + void setInboundPoint(@Valid InboundSetPointReqVO reqVO); + + void completeInbound(Long iostorinvId); + /** * 获得指定仓库的可用库存分页。 * diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/iostorinv/IostorInvServiceImpl.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/iostorinv/IostorInvServiceImpl.java index bde3b7f7..3635a1b5 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/iostorinv/IostorInvServiceImpl.java +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/iostorinv/IostorInvServiceImpl.java @@ -8,8 +8,14 @@ import cn.code.nl.module.base.api.codegen.CodeGenApi; import cn.code.nl.module.base.api.codegen.dto.CodeGenerateReqDTO; import cn.code.nl.module.task.api.TransportTaskApi; import cn.code.nl.module.task.dto.TransportTaskCreateReqDTO; +import cn.code.nl.module.wms.api.moveinv.dto.MoveInvAutoCreateReqDTO; +import cn.code.nl.module.wms.api.moveinv.dto.MoveInvAutoCreateRespDTO; +import cn.code.nl.module.system.api.user.AdminUserApi; +import cn.code.nl.module.system.api.user.dto.AdminUserRespDTO; import cn.code.nl.module.wms.dal.dataobject.bsrealstorattr.BsrealStorAttrDO; import cn.code.nl.module.wms.dal.dataobject.iostorinvdtl.IostorinvDtlDO; +import cn.code.nl.module.wms.dal.dataobject.moveinvdtl.MoveInvDtlDO; +import cn.code.nl.module.wms.service.moveinv.MoveInvService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import jakarta.annotation.Resource; @@ -18,6 +24,7 @@ import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.*; +import java.util.stream.Collectors; import cn.code.nl.module.wms.controller.admin.iostorinv.vo.*; import cn.code.nl.module.wms.dal.dataobject.iostorinv.IostorInvDO; import cn.code.nl.framework.common.pojo.PageResult; @@ -30,9 +37,11 @@ import cn.code.nl.module.wms.dal.mysql.bsrealstorattr.BsrealStorAttrMapper; import cn.code.nl.module.wms.dal.mysql.groupplate.GroupPlateMapper; import cn.code.nl.module.wms.dal.mysql.iostorinvdis.IostorinvDisMapper; import cn.code.nl.module.wms.dal.mysql.structAttr.StrucAttrMapper; +import cn.code.nl.module.wms.dal.mysql.sectattr.SectAttrMapper; import cn.code.nl.module.wms.dal.dataobject.groupplate.GroupPlateDO; import cn.code.nl.module.wms.dal.dataobject.iostorinvdis.IostorinvDisDO; import cn.code.nl.module.wms.dal.dataobject.structAttr.StrucAttrDO; +import cn.code.nl.module.wms.dal.dataobject.sectattr.SectAttrDO; import cn.code.nl.framework.mybatis.core.query.LambdaQueryWrapperX; import static cn.code.nl.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -74,10 +83,453 @@ public class IostorInvServiceImpl implements IostorInvService { @Resource private StrucAttrMapper strucAttrMapper; + @Resource + private SectAttrMapper sectAttrMapper; @Resource private TransportTaskApi transportTaskApi; + @Resource + private MoveInvService moveInvService; + @Resource + private AdminUserApi adminUserApi; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createInbound(InboundSaveReqVO reqVO) { + List groups = validateInboundGroups(reqVO.getGroupIds(), null); + IostorInvDO header = new IostorInvDO(); + header.setBillCode(generateIoBillCode("createInbound")); + header.setIoType("0"); + header.setBillType(reqVO.getBillType()); + header.setBizDate(reqVO.getBizDate()); + header.setStorId(reqVO.getStorId()); + header.setBillStatus("10"); + header.setCreateMode("01"); + header.setRemark(reqVO.getRemark()); + header.setDetailCount(groups.size()); + header.setTotalQty(groups.stream().map(GroupPlateDO::getQty) + .filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add)); + header.setTotalWeight(header.getTotalQty()); + iostorInvMapper.insert(header); + insertInboundDetails(header.getIostorinvId(), groups); + insertInboundAllocationPlaceholders(header, groups); + groups.forEach(group -> updateGroupStatus(group.getGroupId(), "01")); + return header.getIostorinvId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateInbound(InboundSaveReqVO reqVO) { + IostorInvDO header = requireInbound(reqVO.getIostorinvId(), "10"); + List oldDetails = iostorinvDtlMapper.selectList( + new LambdaQueryWrapperX() + .eq(IostorinvDtlDO::getIostorinvId, header.getIostorinvId())); + Set oldGroupIds = oldDetails.stream().map(IostorinvDtlDO::getSourceBilldtlId) + .filter(StrUtil::isNotBlank).map(Long::valueOf).collect(Collectors.toSet()); + List groups = validateInboundGroups(reqVO.getGroupIds(), oldGroupIds); + oldGroupIds.forEach(id -> updateGroupStatus(id, "00")); + strucAttrMapper.unlockInbound(String.valueOf(header.getIostorinvId())); + iostorinvDisMapper.delete(new LambdaQueryWrapperX() + .eq(IostorinvDisDO::getIostorinvId, String.valueOf(header.getIostorinvId()))); + iostorinvDtlMapper.delete(new LambdaQueryWrapperX() + .eq(IostorinvDtlDO::getIostorinvId, header.getIostorinvId())); + IostorInvDO update = new IostorInvDO(); + update.setIostorinvId(header.getIostorinvId()); + update.setBillType(reqVO.getBillType()); + update.setBizDate(reqVO.getBizDate()); + update.setStorId(reqVO.getStorId()); + update.setRemark(reqVO.getRemark()); + update.setDetailCount(groups.size()); + update.setTotalQty(groups.stream().map(GroupPlateDO::getQty) + .filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add)); + update.setTotalWeight(update.getTotalQty()); + iostorInvMapper.updateById(update); + insertInboundDetails(header.getIostorinvId(), groups); + insertInboundAllocationPlaceholders(header, groups); + groups.forEach(group -> updateGroupStatus(group.getGroupId(), "01")); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteInbound(Long iostorinvId) { + IostorInvDO header = requireInbound(iostorinvId, "10"); + List details = iostorinvDtlMapper.selectList( + new LambdaQueryWrapperX() + .eq(IostorinvDtlDO::getIostorinvId, iostorinvId)); + // 入库单删除后,原组盘数据恢复为生成状态,允许再次选择建单。 + details.stream() + .map(IostorinvDtlDO::getSourceBilldtlId) + .filter(StrUtil::isNotBlank) + .map(Long::valueOf) + .forEach(groupId -> updateGroupStatus(groupId, "00")); + // 分配表、明细表按业务要求物理删除,避免留下孤立记录。 + iostorinvDisMapper.physicallyDeleteByIostorinvId(iostorinvId); + iostorinvDtlMapper.physicallyDeleteByIostorinvId(iostorinvId); + // 主表沿用 MyBatis-Plus 逻辑删除,仅更新 deleted 字段。 + iostorInvMapper.deleteById(header.getIostorinvId()); + } + + @Override + public List getInboundGroupPlates(String storId) { + // 生成状态的组盘数据尚未进入仓库,因此此处不按 storId 过滤。 + return groupPlateMapper.selectList(new LambdaQueryWrapperX() + .eq(GroupPlateDO::getStatus, "00") + .orderByDesc(GroupPlateDO::getGroupId)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void allocateInbound(InboundAllocationReqVO reqVO) { + IostorInvDO header = iostorInvMapper.selectById(reqVO.getIostorinvId()); + if (header == null || !"0".equals(header.getIoType()) + || !Set.of("10", "20").contains(header.getBillStatus())) { + throw exception(INBOUND_STATUS_INVALID); + } + for (InboundAllocationReqVO.Item item : reqVO.getItems()) { + IostorinvDtlDO detail = iostorinvDtlMapper.selectById(item.getIostorinvdtlId()); + SectAttrDO sect = sectAttrMapper.selectById(item.getSectId()); + if (detail == null || !Objects.equals(detail.getIostorinvId(), header.getIostorinvId()) + || sect == null || !Objects.equals(header.getStorId(), sect.getStorId())) { + throw exception(INBOUND_TARGET_INVALID); + } + StrucAttrDO target; + if (Boolean.TRUE.equals(reqVO.getAutoAllocate())) { + target = getInboundAvailableStructs(header.getStorId(), item.getSectId()).stream() + .findFirst().orElseThrow(() -> exception(INBOUND_TARGET_INVALID)); + } else { + if (StrUtil.isBlank(item.getStructId())) { + throw exception(INBOUND_TARGET_INVALID); + } + target = strucAttrMapper.selectById(item.getStructId()); + } + if (target == null + || !Objects.equals(header.getStorId(), target.getStorId()) + || !Objects.equals(item.getSectId(), target.getSectId()) + || !"1".equals(target.getLockType()) + || StrUtil.isNotBlank(target.getStoragevehicleCode())) { + throw exception(INBOUND_TARGET_INVALID); + } + if (strucAttrMapper.lockForInbound(target.getStructId(), + String.valueOf(header.getIostorinvId()), header.getBillCode()) != 1) { + throw exception(INBOUND_TARGET_INVALID); + } + IostorinvDisDO allocation = iostorinvDisMapper.selectOne( + new LambdaQueryWrapperX() + .eq(IostorinvDisDO::getIostorinvId, String.valueOf(header.getIostorinvId())) + .eq(IostorinvDisDO::getIostorinvdtlId, + String.valueOf(detail.getIostorinvdtlId()))); + if (allocation == null || StrUtil.isNotBlank(allocation.getStructId())) { + throw exception(INBOUND_TARGET_INVALID); + } + allocation.setSectId(sect.getSectId()); + allocation.setSectCode(sect.getSectCode()); + allocation.setSectName(sect.getSectName()); + allocation.setStructId(target.getStructId()); + allocation.setStructCode(target.getStructCode()); + allocation.setStructName(target.getStructName()); + iostorinvDisMapper.updateById(allocation); + detail.setAssignQty(detail.getPlanQty()); + detail.setUnassignQty(BigDecimal.ZERO); + detail.setBillStatus("30"); + iostorinvDtlMapper.updateById(detail); + } + refreshInboundAllocationStatus(header.getIostorinvId()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void cancelInboundAllocation(Long iostorinvId, Long iostorinvdtlId) { + IostorInvDO header = iostorInvMapper.selectById(iostorinvId); + if (header == null || !"0".equals(header.getIoType()) + || !Set.of("20", "30").contains(header.getBillStatus())) { + throw exception(INBOUND_STATUS_INVALID); + } + IostorinvDtlDO detail = iostorinvDtlMapper.selectById(iostorinvdtlId); + IostorinvDisDO allocation = iostorinvDisMapper.selectOne( + new LambdaQueryWrapperX() + .eq(IostorinvDisDO::getIostorinvId, String.valueOf(iostorinvId)) + .eq(IostorinvDisDO::getIostorinvdtlId, String.valueOf(iostorinvdtlId))); + if (detail == null || !Objects.equals(detail.getIostorinvId(), iostorinvId) + || allocation == null || StrUtil.isBlank(allocation.getStructId())) { + throw exception(INBOUND_NOT_ALLOCATED); + } + // 任务一旦创建并下发,不能只释放货位;需走后续的任务撤销流程。 + if (StrUtil.isNotBlank(allocation.getTaskId())) { + throw exception(INBOUND_ALLOCATION_TASK_CREATED); + } + if (strucAttrMapper.unlockInboundStruct(allocation.getStructId(), + String.valueOf(iostorinvId)) != 1) { + throw exception(INBOUND_TARGET_INVALID); + } + // 取消分配要求直接物理删除分配记录,不保留逻辑删除数据。 + iostorinvDisMapper.physicallyDeleteInboundAllocation(allocation.getIostorinvdisId()); + detail.setAssignQty(BigDecimal.ZERO); + detail.setUnassignQty(detail.getPlanQty()); + detail.setBillStatus("10"); + iostorinvDtlMapper.updateById(detail); + refreshInboundAllocationStatus(iostorinvId); + } + + @Override + public List getInboundAvailableStructs(String storId, String sectId) { + return strucAttrMapper.selectList(new LambdaQueryWrapperX() + .eq(StrucAttrDO::getStorId, storId) + .eq(StrucAttrDO::getSectId, sectId) + .eq(StrucAttrDO::getIsUsed, true) + .eq(StrucAttrDO::getLockType, "1") + .and(wrapper -> wrapper.isNull(StrucAttrDO::getStoragevehicleCode) + .or().eq(StrucAttrDO::getStoragevehicleCode, "")) + .orderByAsc(StrucAttrDO::getRowNum, StrucAttrDO::getColNum, + StrucAttrDO::getLayerNum, StrucAttrDO::getStructId)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void setInboundPoint(InboundSetPointReqVO reqVO) { + IostorInvDO header = iostorInvMapper.selectById(reqVO.getIostorinvId()); + if (header == null || !"0".equals(header.getIoType()) + || !Set.of("10", "20", "30").contains(header.getBillStatus())) { + throw exception(INBOUND_STATUS_INVALID); + } + IostorinvDisDO allocation = iostorinvDisMapper.selectOne( + new LambdaQueryWrapperX() + .eq(IostorinvDisDO::getIostorinvId, String.valueOf(header.getIostorinvId())) + .eq(IostorinvDisDO::getIostorinvdtlId, + String.valueOf(reqVO.getIostorinvdtlId()))); + if (allocation == null || StrUtil.isBlank(allocation.getStructId())) { + throw exception(INBOUND_NOT_ALLOCATED); + } + if (StrUtil.isNotBlank(allocation.getTaskId())) { + throw exception(INBOUND_ALLOCATION_TASK_CREATED); + } + allocation.setPointCode(reqVO.getPointCode()); + SectAttrDO sect = sectAttrMapper.selectById(allocation.getSectId()); + if (sect != null && "主存区".equals(sect.getSectTypeAttr())) { + IostorinvDtlDO detail = iostorinvDtlMapper.selectById(reqVO.getIostorinvdtlId()); + GroupPlateDO group = groupPlateMapper.selectById(Long.valueOf(detail.getSourceBilldtlId())); + Long taskId = createAndIssueInboundTask(reqVO.getPointCode(), allocation.getStructCode(), + group, header.getIostorinvId(), detail.getSeqNo(), allocation.getIostorinvdisId()); + allocation.setTaskId(String.valueOf(taskId)); + allocation.setIsIssued("1"); + allocation.setWorkStatus("20"); + } + iostorinvDisMapper.updateById(allocation); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void completeInbound(Long iostorinvId) { + IostorInvDO header = requireInbound(iostorinvId, "30"); + List allocations = iostorinvDisMapper.selectList( + new LambdaQueryWrapperX() + .eq(IostorinvDisDO::getIostorinvId, String.valueOf(iostorinvId))); + if (allocations.isEmpty()) { + throw exception(INBOUND_NOT_ALLOCATED); + } + for (IostorinvDisDO allocation : allocations) { + if (strucAttrMapper.completeInbound(allocation.getStructId(), + String.valueOf(iostorinvId), allocation.getStoragevehicleCode()) != 1) { + throw exception(INBOUND_TARGET_INVALID); + } + IostorinvDtlDO detail = iostorinvDtlMapper.selectById( + Long.valueOf(allocation.getIostorinvdtlId())); + updateGroupStatus(Long.valueOf(detail.getSourceBilldtlId()), "02"); + allocation.setWorkStatus("99"); + allocation.setRealQty(allocation.getPlanQty()); + iostorinvDisMapper.updateById(allocation); + detail.setBillStatus("99"); + detail.setRealQty(detail.getPlanQty()); + iostorinvDtlMapper.updateById(detail); + } + IostorInvDO update = new IostorInvDO(); + update.setIostorinvId(iostorinvId); + update.setBillStatus("99"); + update.setConfirmTime(java.time.LocalDateTime.now()); + iostorInvMapper.updateById(update); + } + + private List validateInboundGroups(List groupIds, Set editableIds) { + Set ids = new LinkedHashSet<>(groupIds); + if (ids.size() != groupIds.size()) { + throw exception(INBOUND_GROUP_PLATE_INVALID); + } + List groups = groupPlateMapper.selectBatchIds(ids); + if (groups.size() != ids.size() || groups.stream().anyMatch(group -> + !("00".equals(group.getStatus()) + || editableIds != null && editableIds.contains(group.getGroupId())))) { + throw exception(INBOUND_GROUP_PLATE_INVALID); + } + return groups; + } + + private void insertInboundDetails(Long headerId, List groups) { + int seq = 1; + for (GroupPlateDO group : groups) { + IostorinvDtlDO detail = new IostorinvDtlDO(); + detail.setIostorinvId(headerId); + detail.setSeqNo(seq++); + detail.setMaterialId(group.getMaterialId()); + detail.setMaterialCode(group.getMaterialCode()); + detail.setPcsn(group.getPcsn()); + detail.setQtyUnitId(StrUtil.blankToDefault(group.getQtyUnitId(), "0")); + detail.setPlanQty(group.getQty()); + detail.setAssignQty(BigDecimal.ZERO); + detail.setUnassignQty(group.getQty()); + detail.setBillStatus("10"); + detail.setSourceBillCode(group.getExtCode()); + detail.setSourceBillType(group.getExtType()); + detail.setSourceBilldtlId(String.valueOf(group.getGroupId())); + // 入库明细表没有独立载具字段,使用来源装载点保存木箱号。 + detail.setSourceLoadPort(group.getVehicleCode()); + iostorinvDtlMapper.insert(detail); + } + } + + /** + * 入库单创建时即生成分配占位记录。此时没有库区、货位和起点,状态统一为生成。 + */ + private void insertInboundAllocationPlaceholders(IostorInvDO header, List groups) { + List details = iostorinvDtlMapper.selectList( + new LambdaQueryWrapperX() + .eq(IostorinvDtlDO::getIostorinvId, header.getIostorinvId()) + .orderByAsc(IostorinvDtlDO::getSeqNo)); + Map groupMap = groups.stream().collect( + Collectors.toMap(group -> String.valueOf(group.getGroupId()), group -> group)); + for (IostorinvDtlDO detail : details) { + insertInboundAllocationPlaceholder(header, detail, groupMap.get(detail.getSourceBilldtlId())); + } + } + + private void insertInboundAllocationPlaceholder(IostorInvDO header, IostorinvDtlDO detail) { + GroupPlateDO group = StrUtil.isBlank(detail.getSourceBilldtlId()) + ? null : groupPlateMapper.selectById(Long.valueOf(detail.getSourceBilldtlId())); + insertInboundAllocationPlaceholder(header, detail, group); + } + + private void insertInboundAllocationPlaceholder(IostorInvDO header, IostorinvDtlDO detail, + GroupPlateDO group) { + IostorinvDisDO row = new IostorinvDisDO(); + row.setIostorinvdisId(IdUtil.getSnowflakeNextIdStr()); + row.setIostorinvId(String.valueOf(header.getIostorinvId())); + row.setIostorinvdtlId(String.valueOf(detail.getIostorinvdtlId())); + row.setSeqNo(String.valueOf(detail.getSeqNo())); + row.setMaterialId(detail.getMaterialId()); + row.setMaterialCode(detail.getMaterialCode()); + row.setPcsn(StrUtil.blankToDefault(detail.getPcsn(), "")); + row.setWorkStatus("10"); + row.setStoragevehicleCode(group == null ? detail.getSourceLoadPort() : group.getVehicleCode()); + row.setIsIssued("0"); + row.setQtyUnitId(detail.getQtyUnitId()); + row.setQtyUnitName(group == null ? "" : StrUtil.blankToDefault(group.getQtyUnitName(), "")); + row.setPlanQty(detail.getPlanQty()); + row.setRealQty(BigDecimal.ZERO); + row.setHandType(false); + iostorinvDisMapper.insert(row); + } + + private void refreshInboundAllocationStatus(Long iostorinvId) { + List allocations = iostorinvDisMapper.selectList( + new LambdaQueryWrapperX() + .eq(IostorinvDisDO::getIostorinvId, String.valueOf(iostorinvId))); + boolean allAllocated = !allocations.isEmpty() + && allocations.stream().allMatch(item -> StrUtil.isNotBlank(item.getStructId())); + IostorInvDO update = new IostorInvDO(); + update.setIostorinvId(iostorinvId); + update.setBillStatus(allAllocated ? "30" : "20"); + update.setDisTime(java.time.LocalDateTime.now()); + iostorInvMapper.updateById(update); + } + + private IostorInvDO requireInbound(Long id, String status) { + IostorInvDO header = iostorInvMapper.selectById(id); + if (header == null || !"0".equals(header.getIoType())) { + throw exception(IOSTOR_INV_NOT_EXISTS); + } + if (!status.equals(header.getBillStatus())) { + throw exception(INBOUND_STATUS_INVALID); + } + return header; + } + + private void updateGroupStatus(Long groupId, String status) { + GroupPlateDO update = new GroupPlateDO(); + update.setGroupId(groupId); + update.setStatus(status); + groupPlateMapper.updateById(update); + } + + private String generateIoBillCode(String scene) { + try { + CommonResult result = codeGenApi.generate( + new CodeGenerateReqDTO().setRuleCode("IO_CODE")); + if (result != null && result.isSuccess() && StrUtil.isNotBlank(result.getData())) { + return result.getData(); + } + } catch (RuntimeException ex) { + log.error("[{}][生成入库单号异常]", scene, ex); + } + throw exception(IOSTOR_INV_CODE_GENERATE_FAILED); + } + + private IostorinvDisDO buildInboundAllocation(IostorInvDO header, IostorinvDtlDO detail, + GroupPlateDO group, SectAttrDO sect, + StrucAttrDO target, String pointCode) { + IostorinvDisDO row = new IostorinvDisDO(); + row.setIostorinvdisId(IdUtil.getSnowflakeNextIdStr()); + row.setIostorinvId(String.valueOf(header.getIostorinvId())); + row.setIostorinvdtlId(String.valueOf(detail.getIostorinvdtlId())); + row.setSeqNo(String.valueOf(detail.getSeqNo())); + row.setSectId(sect.getSectId()); + row.setSectCode(sect.getSectCode()); + row.setSectName(sect.getSectName()); + row.setStructId(target.getStructId()); + row.setStructCode(target.getStructCode()); + row.setStructName(target.getStructName()); + row.setMaterialId(detail.getMaterialId()); + row.setMaterialCode(detail.getMaterialCode()); + row.setPcsn(StrUtil.blankToDefault(detail.getPcsn(), "")); + row.setWorkStatus("10"); + row.setStoragevehicleCode(group.getVehicleCode()); + row.setIsIssued("0"); + row.setQtyUnitId(detail.getQtyUnitId()); + row.setQtyUnitName(StrUtil.blankToDefault(group.getQtyUnitName(), "")); + row.setPlanQty(detail.getPlanQty()); + row.setRealQty(BigDecimal.ZERO); + row.setPointCode(pointCode); + row.setHandType(false); + return row; + } + + private Long createAndIssueInboundTask(String sourcePoint, String targetPoint, + GroupPlateDO group, long taskGroupId, + long sortSeq, String bizId) { + TransportTaskCreateReqDTO task = new TransportTaskCreateReqDTO(); + task.setTaskName("入库任务"); + task.setOwnerService("WMS"); + task.setBizType("INBOUND"); + task.setBizId(bizId); + task.setHandleCode("INTASK"); + task.setTaskType("010501"); + task.setAcsTaskType("7"); + task.setAgvSystemType("1"); + task.setPointCode1(sourcePoint); + task.setPointCode2(targetPoint); + task.setVehicleCode(group.getVehicleCode()); + if (StrUtil.isNotBlank(group.getMaterialId())) { + task.setMaterialId(Long.valueOf(group.getMaterialId())); + } + task.setMaterialCode(group.getMaterialCode()); + task.setTaskGroupId(taskGroupId); + task.setSortSeq(sortSeq); + task.setIsAutoIssue("0"); + task.setProductArea("LK"); + task.setIsCreateFinish(true); + Long taskId = transportTaskApi.createTransportTask(task).getCheckedData(); + transportTaskApi.issueTransportTaskRollBack(taskId).getCheckedData(); + return taskId; + } + /** * 创建出库单及其明细。 */ @@ -300,9 +752,24 @@ public class IostorInvServiceImpl implements IostorInvService { } @Override + @Transactional(rollbackFor = Exception.class) public List getOutboundAllocations( Long iostorinvId, Long iostorinvdtlId, String sectId) { - return iostorinvDisMapper.selectAllocationList(iostorinvId, iostorinvdtlId, sectId); + List result = + iostorinvDisMapper.selectAllocationList(iostorinvId, iostorinvdtlId, sectId); + // 兼容入库功能上线前已经创建的单据:旧数据没有同步生成分配占位记录, + // 首次点击明细查询时自动补建,避免页面无法继续分配货位。 + if (result.isEmpty() && iostorinvdtlId != null) { + IostorInvDO header = iostorInvMapper.selectById(iostorinvId); + IostorinvDtlDO detail = iostorinvDtlMapper.selectById(iostorinvdtlId); + if (header != null && "0".equals(header.getIoType()) && detail != null + && Objects.equals(detail.getIostorinvId(), iostorinvId)) { + insertInboundAllocationPlaceholder(header, detail); + result = iostorinvDisMapper.selectAllocationList( + iostorinvId, iostorinvdtlId, sectId); + } + } + return result; } @Override @@ -413,6 +880,7 @@ public class IostorInvServiceImpl implements IostorInvService { } @Override + @Transactional(rollbackFor = Exception.class) public void oneClickSet(OutboundOneClickSetReqVO reqVO) { // 1. 校验出库单和当前选中明细,避免为其他单据的明细生成任务。 IostorInvDO header = iostorInvMapper.selectById(reqVO.getIostorinvId()); @@ -449,9 +917,7 @@ public class IostorInvServiceImpl implements IostorInvService { Map> targetGroups = targetStructs.stream() .collect(java.util.stream.Collectors.groupingBy(this::placementGroupKey, LinkedHashMap::new, java.util.stream.Collectors.toList())); - // 6. 预先准备移库空位、两个出库点当前负载,以及本批任务共用的任务组和执行序号。 - List freeMoveLocations = selectFreeMoveLocations(header.getStorId(), targetStructIds); - Iterator freeLocationIterator = freeMoveLocations.iterator(); + // 6. 预先查询两个出库点当前负载,并生成本批任务共用的任务组和执行序号。 long[] destinationLoads = loadDestinationCounts(); long taskGroupId = IdUtil.getSnowflakeNextId(); long sortSeq = 0; @@ -484,31 +950,36 @@ public class IostorInvServiceImpl implements IostorInvService { .findFirst().orElseThrow(); } if ("MOVE".equals(action.type())) { - // 9. 挡路箱创建移库任务,并锁定起点、目的仓位,防止规划后被其他任务占用。 - if (!freeLocationIterator.hasNext()) { - throw exception(IOSTOR_INV_NO_MOVE_LOCATION); - } - StrucAttrDO destination = freeLocationIterator.next(); - Long taskId = createAndIssueTask("010505", "移库任务", source.getStructCode(), - destination.getStructCode(), action.vehicleCode(), null, null, - taskGroupId, ++sortSeq, "MOVE", action.structId()); - strucAttrMapper.lockForTask(source.getStructId(), String.valueOf(taskId)); - strucAttrMapper.lockForTask(destination.getStructId(), String.valueOf(taskId)); + // 9. 先按挡路载具自动创建移库单,由移库单统一选择同库区同层目标仓位并加移出、移入锁。 + MoveInvAutoCreateReqDTO moveReq = new MoveInvAutoCreateReqDTO(); + moveReq.setVehicleCodes(List.of(action.vehicleCode())); + MoveInvAutoCreateRespDTO moveResult = moveInvService.autoCreate(moveReq); + MoveInvDtlDO moveDetail = moveInvService.get(moveResult.getMoveinvId()).getDetails().stream() + .filter(item -> action.vehicleCode().equals(item.getStoragevehicleCode())) + .findFirst() + .orElseThrow(); + // 10. 任务起点、目的点严格使用移库单明细,保证移库任务与移库单据完全一致。 + Long taskId = createAndIssueTask("010505", "移库任务", + moveDetail.getTurnoutStructCode(), moveDetail.getTurninStructCode(), + action.vehicleCode(), null, null, taskGroupId, ++sortSeq, + "MOVE", String.valueOf(moveDetail.getMoveinvdtlId())); + // 11. 将任务标识和下发状态回写移库明细,便于后续任务回调按明细处理移库完成。 + moveInvService.bindTask(moveDetail.getMoveinvdtlId(), taskId); continue; } - // 10. 非本次分配的载具只参与通道规划,不创建出库任务。 + // 12. 非本次分配的载具只参与通道规划,不创建出库任务。 List vehicleAllocations = allocationsByVehicle.get(action.vehicleCode()); if (CollUtil.isEmpty(vehicleAllocations)) { continue; } - // 11. 比较两个出库点未完成任务数,选择当前负载较小的目的点。 + // 13. 比较两个出库点未完成任务数,选择当前负载较小的目的点。 String destinationPoint = chooseDestination(destinationLoads); Long taskId = createAndIssueTask("010503", "出库任务", source.getStructCode(), destinationPoint, action.vehicleCode(), detail.getMaterialId(), detail.getMaterialCode(), taskGroupId, ++sortSeq, "OUT", vehicleAllocations.get(0).getIostorinvdisId()); destinationLoads[destinationPoint.equals(OUTBOUND_POINT_1.getPointCode()) ? 0 : 1]++; - // 12. 同载具的全部分配记录关联同一个任务,并推进到“执行中/已下发”状态。 + // 14. 同载具的全部分配记录关联同一个任务,并推进到“执行中/已下发”状态。 vehicleAllocations.forEach(allocation -> { IostorinvDisDO update = new IostorinvDisDO(); update.setIostorinvdisId(allocation.getIostorinvdisId()); @@ -518,7 +989,7 @@ public class IostorInvServiceImpl implements IostorInvService { update.setIsIssued("1"); iostorinvDisMapper.updateById(update); }); - // 13. 锁定出库起点仓位,直到任务回调完成后再由后续流程释放。 + // 15. 锁定出库起点仓位,直到任务回调完成后再由后续流程释放。 strucAttrMapper.lockForTask(source.getStructId(), String.valueOf(taskId)); } } @@ -798,6 +1269,20 @@ public class IostorInvServiceImpl implements IostorInvService { .in(BsrealStorAttrDO::getStorId, storIds)), BsrealStorAttrDO::getStorId, BsrealStorAttrDO::getStorName); pageResult.getList().forEach(item -> item.setStorName(storNameMap.get(item.getStorId()))); + Set creatorIds = pageResult.getList().stream() + .map(IostorInvDO::getCreator) + .filter(StrUtil::isNotBlank) + .map(Long::valueOf) + .collect(Collectors.toSet()); + if (CollUtil.isNotEmpty(creatorIds)) { + Map userMap = adminUserApi.getUserMap(creatorIds); + pageResult.getList().forEach(item -> { + if (StrUtil.isNotBlank(item.getCreator())) { + AdminUserRespDTO user = userMap.get(Long.valueOf(item.getCreator())); + item.setCreatorName(user == null ? null : user.getNickname()); + } + }); + } return pageResult; } diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/moveinv/MoveInvService.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/moveinv/MoveInvService.java index 5e9f5131..a1e2a12c 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/moveinv/MoveInvService.java +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/moveinv/MoveInvService.java @@ -31,4 +31,12 @@ public interface MoveInvService { List getAvailableTargetStructs(String sourceStructCode); MoveInvAutoCreateRespDTO autoCreate(MoveInvAutoCreateReqDTO reqDTO); + + /** + * 将已创建并下发的搬运任务绑定到移库明细。 + * + * @param moveinvdtlId 移库明细标识 + * @param taskId 搬运任务标识 + */ + void bindTask(Long moveinvdtlId, Long taskId); } diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/moveinv/MoveInvServiceImpl.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/moveinv/MoveInvServiceImpl.java index 95535792..d3fccf96 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/moveinv/MoveInvServiceImpl.java +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/moveinv/MoveInvServiceImpl.java @@ -1,11 +1,14 @@ package cn.code.nl.module.wms.service.moveinv; +import cn.hutool.core.util.StrUtil; import cn.code.nl.framework.common.pojo.CommonResult; import cn.code.nl.framework.common.pojo.PageResult; import cn.code.nl.module.base.api.codegen.CodeGenApi; import cn.code.nl.module.base.api.codegen.dto.CodeGenerateReqDTO; import cn.code.nl.module.wms.api.moveinv.dto.MoveInvAutoCreateReqDTO; import cn.code.nl.module.wms.api.moveinv.dto.MoveInvAutoCreateRespDTO; +import cn.code.nl.module.system.api.user.AdminUserApi; +import cn.code.nl.module.system.api.user.dto.AdminUserRespDTO; import cn.code.nl.module.wms.controller.admin.moveinv.vo.MoveInvDetailSaveReqVO; import cn.code.nl.module.wms.controller.admin.moveinv.vo.MoveInvInventoryRespVO; import cn.code.nl.module.wms.controller.admin.moveinv.vo.MoveInvPageReqVO; @@ -56,6 +59,8 @@ public class MoveInvServiceImpl implements MoveInvService { @Resource private MoveInvDtlMapper moveInvDtlMapper; @Resource + private AdminUserApi adminUserApi; + @Resource private StrucAttrMapper strucAttrMapper; @Resource private CodeGenApi codeGenApi; @@ -129,6 +134,7 @@ public class MoveInvServiceImpl implements MoveInvService { throw exception(MOVE_INV_NOT_EXISTS); } MoveInvRespVO respVO = convert(moveInv); + fillCreatorNames(List.of(respVO)); respVO.setDetails(moveInvDtlMapper.selectByMoveinvId(id)); return respVO; } @@ -140,9 +146,31 @@ public class MoveInvServiceImpl implements MoveInvService { public PageResult getPage(MoveInvPageReqVO reqVO) { PageResult page = moveInvMapper.selectPage(reqVO); List list = page.getList().stream().map(this::convert).toList(); + fillCreatorNames(list); return new PageResult<>(list, page.getTotal()); } + /** + * 创建人字段由框架自动填充为字符串,这里显式批量查询姓名,避免自动翻译类型不匹配。 + */ + private void fillCreatorNames(List list) { + Set creatorIds = list.stream() + .map(MoveInvRespVO::getCreator) + .filter(StrUtil::isNotBlank) + .map(Long::valueOf) + .collect(Collectors.toSet()); + if (creatorIds.isEmpty()) { + return; + } + Map userMap = adminUserApi.getUserMap(creatorIds); + list.forEach(item -> { + if (StrUtil.isNotBlank(item.getCreator())) { + AdminUserRespDTO user = userMap.get(Long.valueOf(item.getCreator())); + item.setCreatorName(user == null ? null : user.getNickname()); + } + }); + } + /** * 查询移库可用库存。 */ @@ -204,6 +232,19 @@ public class MoveInvServiceImpl implements MoveInvService { return new MoveInvAutoCreateRespDTO(moveInv.getMoveinvId(), moveInv.getBillCode()); } + /** + * 将任务标识回写到移库明细,并标记为已下发。 + */ + @Override + public void bindTask(Long moveinvdtlId, Long taskId) { + MoveInvDtlDO update = new MoveInvDtlDO(); + update.setMoveinvdtlId(moveinvdtlId); + update.setTaskId(taskId); + update.setWorkStatus("20"); + update.setIsIssued("1"); + moveInvDtlMapper.updateById(update); + } + /** * 创建主表、明细并锁定来源和目标仓位。 */ diff --git a/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/iostorinvdis/IostorinvDisMapper.xml b/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/iostorinvdis/IostorinvDisMapper.xml index 561a6269..bb630d92 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/iostorinvdis/IostorinvDisMapper.xml +++ b/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/iostorinvdis/IostorinvDisMapper.xml @@ -15,8 +15,11 @@ dis.qty_unit_name, dis.sect_id, dis.sect_name, + dis.struct_id, dis.struct_code, - dis.struct_name + dis.struct_name, + dis.point_code, + dis.task_id FROM wms_iostorinvdis dis LEFT JOIN base_materialbase mb ON mb.material_id = dis.material_id diff --git a/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/iostorinvdtl/IostorinvDtlMapper.xml b/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/iostorinvdtl/IostorinvDtlMapper.xml index 15e47e0d..3a985c0f 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/iostorinvdtl/IostorinvDtlMapper.xml +++ b/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/iostorinvdtl/IostorinvDtlMapper.xml @@ -108,6 +108,7 @@ d.source_bill_code, d.source_bill_type, d.source_billdtl_id, + d.source_load_port AS vehicle_code, d.remark FROM wms_iostorinvdtl d LEFT JOIN base_materialbase mb diff --git a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/wms/iostorinv/index.ts b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/wms/iostorinv/index.ts index 9bc437fb..d22913fb 100644 --- a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/wms/iostorinv/index.ts +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/wms/iostorinv/index.ts @@ -1,10 +1,24 @@ import type { Dayjs } from 'dayjs'; import type { PageParam, PageResult } from '@vben/request'; +import type { WmsStrucAttrApi } from '#/api/wms/structAttr'; import { requestClient } from '#/api/request'; export namespace WmsIostorInvApi { + export interface InboundGroupPlate { + groupId: string; + vehicleCode?: string; + status: string; + materialId?: string; + materialCode: string; + pcsn?: string; + qty: number; + qtyUnitId?: string; + qtyUnitName?: string; + extCode?: string; + } + /** 出入库单主表信息 */ export interface IostorInv { iostorinvId: string; // 出入单标识 @@ -127,8 +141,11 @@ export namespace WmsIostorInvApi { qtyUnitName?: string; sectId?: string; sectName?: string; + structId?: string; structCode?: string; structName?: string; + pointCode?: string; + taskId?: string; } export interface ManualAllocationInventory { @@ -157,6 +174,75 @@ export function getIostorInvPage(params: PageParam) { ); } +export function getInboundGroupPlates(storId: string) { + return requestClient.get( + '/wms/iostor-inv/inbound-group-plates', + { params: { storId } }, + ); +} + +export function createInbound(data: { + billType: string; + bizDate: string; + groupIds: string[]; + remark?: string; + storId: string; +}) { + return requestClient.post('/wms/iostor-inv/create-inbound', data); +} + +export function updateInbound(data: { + billType: string; + bizDate: string; + groupIds: string[]; + iostorinvId: string; + remark?: string; + storId: string; +}) { + return requestClient.put('/wms/iostor-inv/update-inbound', data); +} + +export function deleteInbound(iostorinvId: string) { + return requestClient.delete('/wms/iostor-inv/delete-inbound', { + params: { iostorinvId }, + }); +} + +export function allocateInbound(data: { + autoAllocate?: boolean; + iostorinvId: string; + items: { iostorinvdtlId: string; sectId: string; structId: string }[]; +}) { + return requestClient.post('/wms/iostor-inv/allocate-inbound', data); +} + +export function cancelInboundAllocation(iostorinvId: string, iostorinvdtlId: string) { + return requestClient.delete('/wms/iostor-inv/cancel-inbound-allocation', { + params: { iostorinvId, iostorinvdtlId }, + }); +} + +export function getInboundAvailableStructs(storId: string, sectId: string) { + return requestClient.get( + '/wms/iostor-inv/inbound-available-structs', + { params: { storId, sectId } }, + ); +} + +export function setInboundPoint(data: { + iostorinvId: string; + iostorinvdtlId: string; + pointCode: string; +}) { + return requestClient.post('/wms/iostor-inv/set-inbound-point', data); +} + +export function completeInbound(iostorinvId: string) { + return requestClient.post('/wms/iostor-inv/complete-inbound', undefined, { + params: { iostorinvId }, + }); +} + /** 查询出入库单主表详情 */ export function getIostorInv(id: string) { return requestClient.get( diff --git a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/detail.vue b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/detail.vue index 2b242723..fd7a6d9b 100644 --- a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/detail.vue +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/detail.vue @@ -1,7 +1,7 @@ diff --git a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/structAttr/data.ts b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/structAttr/data.ts index 7b820cf2..fa98e59a 100644 --- a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/structAttr/data.ts +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/structAttr/data.ts @@ -393,6 +393,13 @@ export function useGridColumns(): VxeTableGridOptions { field: 'blockNum', title: '块', minWidth: 120 }, { field: 'isTempstruct', title: '是否临时仓位', minWidth: 120, formatter: boolFormatter }, { field: 'isUsed', title: '是否启用', minWidth: 120, formatter: boolFormatter }, + { field: 'storagevehicleCode', title: '存储载具号', minWidth: 150 }, + { + field: 'lockType', + title: '锁定类型', + minWidth: 120, + cellRender: { name: 'CellDict', props: { type: DICT_TYPE.WMS_LOCK_TYPE } }, + }, { field: 'isZdepth', title: '是否判断高度', minWidth: 120, formatter: boolFormatter }, { field: 'createTime', title: '创建时间', minWidth: 120, formatter: 'formatDateTime' }, { field: 'remark', title: '备注', minWidth: 120 }, diff --git a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/packages/constants/src/dict-enum.ts b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/packages/constants/src/dict-enum.ts index c0819c91..b3e74d0e 100644 --- a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/packages/constants/src/dict-enum.ts +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/packages/constants/src/dict-enum.ts @@ -289,6 +289,7 @@ const WMS_DICT = { WMS_VEHICLE_TYPE: 'wms_vehicle_type', // 载具类型 WMS_OVER_STRUCT_TYPE: 'wms_over_struct_type', // 是否超限 WMS_OUT_BILL_TYPE: 'wms_out_bill_type', // 出库单据类型 + WMS_IN_BILL_TYPE: 'wms_in_bill_type', // 入库单据类型 WMS_IO_BILL_STATUS: 'wms_io_bill_status', // 出入库单据状态 WMS_CREATE_TYPE: 'wms_create_type', // 生成方式 } as const;