feat: 支持按箱号展开可用库存子卷
This commit is contained in:
@@ -38,6 +38,22 @@ public class IostorInvController {
|
|||||||
@Resource
|
@Resource
|
||||||
private IostorInvService iostorInvService;
|
private IostorInvService iostorInvService;
|
||||||
|
|
||||||
|
@GetMapping("/availableInventoryPage")
|
||||||
|
@Operation(summary = "获得可用库存分页")
|
||||||
|
@PreAuthorize("@ss.hasPermission('wms:iostor-inv:query')")
|
||||||
|
public CommonResult<PageResult<AvailableInventoryRespVO>> getAvailableInventoryPage(
|
||||||
|
@Valid AvailableInventoryPageReqVO reqVO) {
|
||||||
|
return success(iostorInvService.getAvailableInventoryPage(reqVO));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/expandAvailableInventory")
|
||||||
|
@Operation(summary = "按箱号展开全部可用子卷")
|
||||||
|
@PreAuthorize("@ss.hasPermission('wms:iostor-inv:query')")
|
||||||
|
public CommonResult<List<AvailableInventoryRespVO>> expandAvailableInventory(
|
||||||
|
@Valid @RequestBody ExpandAvailableInventoryReqVO reqVO) {
|
||||||
|
return success(iostorInvService.expandAvailableInventory(reqVO));
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/create")
|
@PostMapping("/create")
|
||||||
@Operation(summary = "创建出入库单主表")
|
@Operation(summary = "创建出入库单主表")
|
||||||
@PreAuthorize("@ss.hasPermission('wms:iostor-inv:create')")
|
@PreAuthorize("@ss.hasPermission('wms:iostor-inv:create')")
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package cn.code.nl.module.wms.controller.admin.iostorinv.vo;
|
||||||
|
|
||||||
|
import cn.code.nl.framework.common.pojo.PageParam;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 可用库存分页 Request VO")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class AvailableInventoryPageReqVO extends PageParam {
|
||||||
|
|
||||||
|
@Schema(description = "仓库标识", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotEmpty(message = "仓库标识不能为空")
|
||||||
|
private String storId;
|
||||||
|
|
||||||
|
@Schema(description = "物料编码")
|
||||||
|
private String materialCode;
|
||||||
|
|
||||||
|
@Schema(description = "箱号")
|
||||||
|
private String vehicleCode;
|
||||||
|
|
||||||
|
@Schema(description = "批次")
|
||||||
|
private String pcsn;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package cn.code.nl.module.wms.controller.admin.iostorinv.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 可用库存 Response VO")
|
||||||
|
@Data
|
||||||
|
public class AvailableInventoryRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "箱号")
|
||||||
|
private String vehicleCode;
|
||||||
|
@Schema(description = "批次")
|
||||||
|
private String pcsn;
|
||||||
|
@Schema(description = "物料标识")
|
||||||
|
private String materialId;
|
||||||
|
@Schema(description = "物料编码")
|
||||||
|
private String materialCode;
|
||||||
|
@Schema(description = "物料名称")
|
||||||
|
private String materialName;
|
||||||
|
@Schema(description = "可用数量")
|
||||||
|
private BigDecimal availableQty;
|
||||||
|
@Schema(description = "计量单位标识")
|
||||||
|
private String qtyUnitId;
|
||||||
|
@Schema(description = "计量单位名称")
|
||||||
|
private String qtyUnitName;
|
||||||
|
@Schema(description = "来源单据号")
|
||||||
|
private String extCode;
|
||||||
|
@Schema(description = "来源单据类型")
|
||||||
|
private String extType;
|
||||||
|
@Schema(description = "来源单据明细号")
|
||||||
|
private String extDtlCode;
|
||||||
|
@Schema(description = "仓库标识")
|
||||||
|
private String storId;
|
||||||
|
@Schema(description = "仓库编码")
|
||||||
|
private String storCode;
|
||||||
|
@Schema(description = "仓库名称")
|
||||||
|
private String storName;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package cn.code.nl.module.wms.controller.admin.iostorinv.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 按箱号展开可用库存 Request VO")
|
||||||
|
@Data
|
||||||
|
public class ExpandAvailableInventoryReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "仓库标识", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotEmpty(message = "仓库标识不能为空")
|
||||||
|
private String storId;
|
||||||
|
|
||||||
|
@Schema(description = "箱号列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotEmpty(message = "箱号列表不能为空")
|
||||||
|
private List<String> vehicleCodes;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,10 +3,15 @@ package cn.code.nl.module.wms.dal.mysql.iostorinvdtl;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import cn.code.nl.framework.common.pojo.PageResult;
|
import cn.code.nl.framework.common.pojo.PageResult;
|
||||||
|
import cn.code.nl.framework.mybatis.core.util.MyBatisUtils;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import cn.code.nl.framework.mybatis.core.query.LambdaQueryWrapperX;
|
import cn.code.nl.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||||
import cn.code.nl.framework.mybatis.core.mapper.BaseMapperX;
|
import cn.code.nl.framework.mybatis.core.mapper.BaseMapperX;
|
||||||
import cn.code.nl.module.wms.dal.dataobject.iostorinvdtl.IostorinvDtlDO;
|
import cn.code.nl.module.wms.dal.dataobject.iostorinvdtl.IostorinvDtlDO;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
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.iostorinvdtl.vo.*;
|
import cn.code.nl.module.wms.controller.admin.iostorinvdtl.vo.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,6 +22,27 @@ import cn.code.nl.module.wms.controller.admin.iostorinvdtl.vo.*;
|
|||||||
@Mapper
|
@Mapper
|
||||||
public interface IostorinvDtlMapper extends BaseMapperX<IostorinvDtlDO> {
|
public interface IostorinvDtlMapper extends BaseMapperX<IostorinvDtlDO> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询指定仓库的可用库存。
|
||||||
|
*/
|
||||||
|
default PageResult<AvailableInventoryRespVO> selectAvailableInventoryPage(AvailableInventoryPageReqVO reqVO) {
|
||||||
|
Page<AvailableInventoryRespVO> page = selectAvailableInventoryPage(
|
||||||
|
MyBatisUtils.buildPage(reqVO), reqVO);
|
||||||
|
return new PageResult<>(page.getRecords(), page.getTotal());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行可用库存分页 SQL。
|
||||||
|
*/
|
||||||
|
Page<AvailableInventoryRespVO> selectAvailableInventoryPage(
|
||||||
|
Page<AvailableInventoryRespVO> page, @Param("reqVO") AvailableInventoryPageReqVO reqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定仓库、指定箱号下的全部可用子卷。
|
||||||
|
*/
|
||||||
|
List<AvailableInventoryRespVO> selectAvailableInventoryByVehicleCodes(
|
||||||
|
@Param("storId") String storId, @Param("vehicleCodes") Collection<String> vehicleCodes);
|
||||||
|
|
||||||
default PageResult<IostorinvDtlDO> selectPage(IostorinvDtlPageReqVO reqVO) {
|
default PageResult<IostorinvDtlDO> selectPage(IostorinvDtlPageReqVO reqVO) {
|
||||||
return selectPage(reqVO, new LambdaQueryWrapperX<IostorinvDtlDO>()
|
return selectPage(reqVO, new LambdaQueryWrapperX<IostorinvDtlDO>()
|
||||||
.eqIfPresent(IostorinvDtlDO::getIostorinvId, reqVO.getIostorinvId())
|
.eqIfPresent(IostorinvDtlDO::getIostorinvId, reqVO.getIostorinvId())
|
||||||
|
|||||||
@@ -22,6 +22,22 @@ public interface IostorInvService {
|
|||||||
*/
|
*/
|
||||||
String createOutbound(@Valid IostorInvCreateReqVO reqVO);
|
String createOutbound(@Valid IostorInvCreateReqVO reqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得指定仓库的可用库存分页。
|
||||||
|
*
|
||||||
|
* @param reqVO 分页查询条件
|
||||||
|
* @return 可用库存分页
|
||||||
|
*/
|
||||||
|
PageResult<AvailableInventoryRespVO> getAvailableInventoryPage(@Valid AvailableInventoryPageReqVO reqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按箱号展开指定仓库中的全部可用子卷。
|
||||||
|
*
|
||||||
|
* @param reqVO 展开条件
|
||||||
|
* @return 可用子卷列表
|
||||||
|
*/
|
||||||
|
List<AvailableInventoryRespVO> expandAvailableInventory(@Valid ExpandAvailableInventoryReqVO reqVO);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建出入库单主表
|
* 创建出入库单主表
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import cn.code.nl.framework.common.pojo.PageParam;
|
|||||||
import cn.code.nl.framework.common.util.object.BeanUtils;
|
import cn.code.nl.framework.common.util.object.BeanUtils;
|
||||||
|
|
||||||
import cn.code.nl.module.wms.dal.mysql.iostorinv.IostorInvMapper;
|
import cn.code.nl.module.wms.dal.mysql.iostorinv.IostorInvMapper;
|
||||||
|
import cn.code.nl.module.wms.dal.mysql.iostorinvdtl.IostorinvDtlMapper;
|
||||||
|
|
||||||
import static cn.code.nl.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.code.nl.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
import static cn.code.nl.framework.common.util.collection.CollectionUtils.convertList;
|
import static cn.code.nl.framework.common.util.collection.CollectionUtils.convertList;
|
||||||
@@ -32,11 +33,26 @@ public class IostorInvServiceImpl implements IostorInvService {
|
|||||||
@Resource
|
@Resource
|
||||||
private IostorInvMapper iostorInvMapper;
|
private IostorInvMapper iostorInvMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IostorinvDtlMapper iostorinvDtlMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String createOutbound(IostorInvCreateReqVO reqVO) {
|
public String createOutbound(IostorInvCreateReqVO reqVO) {
|
||||||
throw new UnsupportedOperationException("出库单聚合保存功能尚未实现");
|
throw new UnsupportedOperationException("出库单聚合保存功能尚未实现");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageResult<AvailableInventoryRespVO> getAvailableInventoryPage(AvailableInventoryPageReqVO reqVO) {
|
||||||
|
return iostorinvDtlMapper.selectAvailableInventoryPage(reqVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AvailableInventoryRespVO> expandAvailableInventory(ExpandAvailableInventoryReqVO reqVO) {
|
||||||
|
// 用户可以独立勾选子卷;按涉及箱号去重后,由数据库展开箱内全部可用子卷。
|
||||||
|
Set<String> vehicleCodes = new LinkedHashSet<>(reqVO.getVehicleCodes());
|
||||||
|
return iostorinvDtlMapper.selectAvailableInventoryByVehicleCodes(reqVO.getStorId(), vehicleCodes);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String createIostorInv(IostorInvSaveReqVO createReqVO) {
|
public String createIostorInv(IostorInvSaveReqVO createReqVO) {
|
||||||
// 插入
|
// 插入
|
||||||
|
|||||||
@@ -2,11 +2,62 @@
|
|||||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
<mapper namespace="cn.code.nl.module.wms.dal.mysql.iostorinvdtl.IostorinvDtlMapper">
|
<mapper namespace="cn.code.nl.module.wms.dal.mysql.iostorinvdtl.IostorinvDtlMapper">
|
||||||
|
|
||||||
<!--
|
<sql id="availableInventoryColumns">
|
||||||
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
|
gp.vehicle_code AS vehicle_code,
|
||||||
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
|
gp.pcsn AS pcsn,
|
||||||
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
|
gp.material_id AS material_id,
|
||||||
文档可见:https://www.iocoder.cn/MyBatis/x-plugins/
|
gp.material_code AS material_code,
|
||||||
-->
|
mb.material_name AS material_name,
|
||||||
|
gp.qty - gp.frozen_qty AS available_qty,
|
||||||
|
gp.qty_unit_id AS qty_unit_id,
|
||||||
|
gp.qty_unit_name AS qty_unit_name,
|
||||||
|
gp.ext_code AS ext_code,
|
||||||
|
gp.ext_type AS ext_type,
|
||||||
|
gp.ext_dtl_code AS ext_dtl_code,
|
||||||
|
sa.stor_id AS stor_id,
|
||||||
|
sa.stor_code AS stor_code,
|
||||||
|
sa.stor_name AS stor_name
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="selectAvailableInventoryPage"
|
||||||
|
resultType="cn.code.nl.module.wms.controller.admin.iostorinv.vo.AvailableInventoryRespVO">
|
||||||
|
SELECT <include refid="availableInventoryColumns"/>
|
||||||
|
FROM wms_group_plate gp
|
||||||
|
INNER JOIN wms_structattr sa ON sa.storagevehicle_code = gp.vehicle_code
|
||||||
|
LEFT JOIN base_materialbase mb ON mb.material_id = gp.material_id AND mb.is_deleted = 0
|
||||||
|
WHERE sa.stor_id = #{reqVO.storId}
|
||||||
|
AND gp.status = '可用'
|
||||||
|
AND gp.qty - gp.frozen_qty > 0
|
||||||
|
AND gp.is_deleted = 0
|
||||||
|
AND sa.is_deleted = 0
|
||||||
|
<if test="reqVO.materialCode != null and reqVO.materialCode != ''">
|
||||||
|
AND gp.material_code LIKE CONCAT('%', #{reqVO.materialCode}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="reqVO.vehicleCode != null and reqVO.vehicleCode != ''">
|
||||||
|
AND gp.vehicle_code LIKE CONCAT('%', #{reqVO.vehicleCode}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="reqVO.pcsn != null and reqVO.pcsn != ''">
|
||||||
|
AND gp.pcsn LIKE CONCAT('%', #{reqVO.pcsn}, '%')
|
||||||
|
</if>
|
||||||
|
ORDER BY gp.vehicle_code, gp.group_id
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectAvailableInventoryByVehicleCodes"
|
||||||
|
resultType="cn.code.nl.module.wms.controller.admin.iostorinv.vo.AvailableInventoryRespVO">
|
||||||
|
SELECT <include refid="availableInventoryColumns"/>
|
||||||
|
FROM wms_group_plate gp
|
||||||
|
INNER JOIN wms_structattr sa ON sa.storagevehicle_code = gp.vehicle_code
|
||||||
|
LEFT JOIN base_materialbase mb ON mb.material_id = gp.material_id AND mb.is_deleted = 0
|
||||||
|
WHERE sa.stor_id = #{storId}
|
||||||
|
AND gp.status = '可用'
|
||||||
|
AND gp.qty - gp.frozen_qty > 0
|
||||||
|
AND gp.is_deleted = 0
|
||||||
|
AND sa.is_deleted = 0
|
||||||
|
AND gp.vehicle_code IN
|
||||||
|
<foreach collection="vehicleCodes" item="vehicleCode" open="(" separator="," close=")">
|
||||||
|
#{vehicleCode}
|
||||||
|
</foreach>
|
||||||
|
ORDER BY gp.vehicle_code, gp.group_id
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -1,17 +1,22 @@
|
|||||||
package cn.code.nl.module.wms.service.iostorinv;
|
package cn.code.nl.module.wms.service.iostorinv;
|
||||||
|
|
||||||
import cn.code.nl.module.wms.controller.admin.iostorinv.vo.IostorInvCreateReqVO;
|
import cn.code.nl.module.wms.controller.admin.iostorinv.vo.IostorInvCreateReqVO;
|
||||||
|
import cn.code.nl.module.wms.controller.admin.iostorinv.vo.AvailableInventoryRespVO;
|
||||||
|
import cn.code.nl.module.wms.controller.admin.iostorinv.vo.ExpandAvailableInventoryReqVO;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.validation.ConstraintViolationException;
|
import jakarta.validation.ConstraintViolationException;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.test.context.ActiveProfiles;
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@ActiveProfiles("local")
|
@ActiveProfiles("local")
|
||||||
@@ -34,4 +39,23 @@ class IostorInvServiceLocalSpringTest {
|
|||||||
assertTrue(exception.getConstraintViolations().stream()
|
assertTrue(exception.getConstraintViolations().stream()
|
||||||
.anyMatch(violation -> violation.getPropertyPath().toString().contains("details")));
|
.anyMatch(violation -> violation.getPropertyPath().toString().contains("details")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本地库数据前置:仓库 stor-1 的 BOX-001 有两个可用子卷,另一仓库也存在 BOX-001。
|
||||||
|
* 当前仓库没有可移植的 WMS 本地测试数据集,避免向开发库写入固定业务数据,准备好上述数据后可手动启用。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Disabled("需要 local 数据库预置跨仓库 BOX-001 测试数据")
|
||||||
|
void expandAvailableInventoryShouldReturnAllChildrenOnceAndOnlyFromRequestedWarehouse() {
|
||||||
|
ExpandAvailableInventoryReqVO reqVO = new ExpandAvailableInventoryReqVO();
|
||||||
|
reqVO.setStorId("stor-1");
|
||||||
|
reqVO.setVehicleCodes(List.of("BOX-001", "BOX-001"));
|
||||||
|
|
||||||
|
List<AvailableInventoryRespVO> result = iostorInvService.expandAvailableInventory(reqVO);
|
||||||
|
|
||||||
|
assertEquals(2, result.size());
|
||||||
|
assertTrue(result.stream().allMatch(item -> "BOX-001".equals(item.getVehicleCode())));
|
||||||
|
assertTrue(result.stream().allMatch(item -> "stor-1".equals(item.getStorId())));
|
||||||
|
assertEquals(2, result.stream().map(AvailableInventoryRespVO::getPcsn).distinct().count());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user