feat: 支持按箱号展开可用库存子卷
This commit is contained in:
@@ -38,6 +38,22 @@ public class IostorInvController {
|
||||
@Resource
|
||||
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")
|
||||
@Operation(summary = "创建出入库单主表")
|
||||
@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 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.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 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.*;
|
||||
|
||||
/**
|
||||
@@ -17,6 +22,27 @@ import cn.code.nl.module.wms.controller.admin.iostorinvdtl.vo.*;
|
||||
@Mapper
|
||||
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) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<IostorinvDtlDO>()
|
||||
.eqIfPresent(IostorinvDtlDO::getIostorinvId, reqVO.getIostorinvId())
|
||||
|
||||
@@ -22,6 +22,22 @@ public interface IostorInvService {
|
||||
*/
|
||||
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.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.util.collection.CollectionUtils.convertList;
|
||||
@@ -32,11 +33,26 @@ public class IostorInvServiceImpl implements IostorInvService {
|
||||
@Resource
|
||||
private IostorInvMapper iostorInvMapper;
|
||||
|
||||
@Resource
|
||||
private IostorinvDtlMapper iostorinvDtlMapper;
|
||||
|
||||
@Override
|
||||
public String createOutbound(IostorInvCreateReqVO reqVO) {
|
||||
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
|
||||
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">
|
||||
<mapper namespace="cn.code.nl.module.wms.dal.mysql.iostorinvdtl.IostorinvDtlMapper">
|
||||
|
||||
<!--
|
||||
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
|
||||
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
|
||||
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
|
||||
文档可见:https://www.iocoder.cn/MyBatis/x-plugins/
|
||||
-->
|
||||
<sql id="availableInventoryColumns">
|
||||
gp.vehicle_code AS vehicle_code,
|
||||
gp.pcsn AS pcsn,
|
||||
gp.material_id AS material_id,
|
||||
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>
|
||||
@@ -1,17 +1,22 @@
|
||||
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.AvailableInventoryRespVO;
|
||||
import cn.code.nl.module.wms.controller.admin.iostorinv.vo.ExpandAvailableInventoryReqVO;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("local")
|
||||
@@ -34,4 +39,23 @@ class IostorInvServiceLocalSpringTest {
|
||||
assertTrue(exception.getConstraintViolations().stream()
|
||||
.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