Commit 573a79cad6851cc7b8a36bad99824d3242339238

Authored by YunaiV
1 parent f9396302

✨ ERP:增加 ERP 调拨单的实现 100%

yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
... ... @@ -40,9 +40,17 @@ public interface ErrorCodeConstants {
40 40 ErrorCode STOCK_OUT_NO_EXISTS = new ErrorCode(1_030_402_004, "生成出库单失败,请重新提交");
41 41 ErrorCode STOCK_OUT_UPDATE_FAIL_APPROVE = new ErrorCode(1_030_402_005, "其它出库单({})已审核,无法修改");
42 42  
  43 + // ========== ERP 其它出库单 1-030-403-000 ==========
  44 + ErrorCode STOCK_MOVE_NOT_EXISTS = new ErrorCode(1_030_402_000, "库存调拨单不存在");
  45 + ErrorCode STOCK_MOVE_DELETE_FAIL_APPROVE = new ErrorCode(1_030_402_001, "库存调拨单({})已审核,无法删除");
  46 + ErrorCode STOCK_MOVE_PROCESS_FAIL = new ErrorCode(1_030_402_002, "反审核失败,只有已审核的出库单才能反审核");
  47 + ErrorCode STOCK_MOVE_APPROVE_FAIL = new ErrorCode(1_030_402_003, "审核失败,只有未审核的出库单才能审核");
  48 + ErrorCode STOCK_MOVE_NO_EXISTS = new ErrorCode(1_030_402_004, "生成调拨号失败,请重新提交");
  49 + ErrorCode STOCK_MOVE_UPDATE_FAIL_APPROVE = new ErrorCode(1_030_402_005, "库存调拨单({})已审核,无法修改");
  50 +
43 51 // ========== ERP 产品库存 1-030-403-000 ==========
44   - ErrorCode STOCK_COUNT_NEGATIVE = new ErrorCode(1_030_403_000, "操作失败,产品当前库存:{},小于变更数量:{}");
45   - ErrorCode STOCK_COUNT_NEGATIVE2 = new ErrorCode(1_030_403_000, "操作失败,库存不足");
  52 + ErrorCode STOCK_COUNT_NEGATIVE = new ErrorCode(1_030_403_000, "操作失败,产品({})所在仓库({})的库存:{},小于变更数量:{}");
  53 + ErrorCode STOCK_COUNT_NEGATIVE2 = new ErrorCode(1_030_403_000, "操作失败,产品({})所在仓库({})的库存不足");
46 54  
47 55 // ========== ERP 产品 1-030-500-000 ==========
48 56 ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_030_500_000, "产品不存在");
... ...
yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/stock/ErpStockRecordBizTypeEnum.java
... ... @@ -20,6 +20,11 @@ public enum ErpStockRecordBizTypeEnum implements IntArrayValuable {
20 20  
21 21 OTHER_OUT(20, "其它出库"),
22 22 OTHER_OUT_CANCEL(21, "其它出库(作废)"),
  23 +
  24 + MOVE_IN(30, "调拨入库"),
  25 + MOVE_IN_CANCEL(31, "调拨入库(作废)"),
  26 + MOVE_OUT(32, "调拨出库"),
  27 + MOVE_OUT_CANCEL(33, "调拨出库(作废)"),
23 28 ;
24 29  
25 30 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ErpStockRecordBizTypeEnum::getType).toArray();
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/move/ErpStockMoveSaveReqVO.java
1 1 package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move;
2 2  
  3 +import cn.hutool.core.util.ObjectUtil;
  4 +import com.fasterxml.jackson.annotation.JsonIgnore;
3 5 import io.swagger.v3.oas.annotations.media.Schema;
4 6 import jakarta.validation.Valid;
  7 +import jakarta.validation.constraints.AssertTrue;
5 8 import jakarta.validation.constraints.NotEmpty;
6 9 import jakarta.validation.constraints.NotNull;
7 10 import lombok.Data;
... ... @@ -63,6 +66,12 @@ public class ErpStockMoveSaveReqVO {
63 66 @Schema(description = "备注", example = "随便")
64 67 private String remark;
65 68  
  69 + @AssertTrue(message = "调出、调仓仓库不能相同")
  70 + @JsonIgnore
  71 + public boolean isWarehouseValid() {
  72 + return ObjectUtil.notEqual(fromWarehouseId, toWarehouseId);
  73 + }
  74 +
66 75 }
67 76  
68 77 }
69 78 \ No newline at end of file
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockMoveDO.java
... ... @@ -14,8 +14,8 @@ import java.time.LocalDateTime;
14 14 *
15 15 * @author 芋道源码
16 16 */
17   -@TableName("erp_stock_in")
18   -@KeySequence("erp_stock_in_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
  17 +@TableName("erp_stock_move")
  18 +@KeySequence("erp_stock_move_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
19 19 @Data
20 20 @EqualsAndHashCode(callSuper = true)
21 21 @ToString(callSuper = true)
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockMoveItemDO.java
... ... @@ -14,8 +14,8 @@ import java.math.BigDecimal;
14 14 *
15 15 * @author 芋道源码
16 16 */
17   -@TableName("erp_stock_in_item")
18   -@KeySequence("erp_stock_in_item_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
  17 +@TableName("erp_stock_move_item")
  18 +@KeySequence("erp_stock_move_item_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
19 19 @Data
20 20 @EqualsAndHashCode(callSuper = true)
21 21 @ToString(callSuper = true)
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java
... ... @@ -28,6 +28,11 @@ public class ErpNoRedisDAO {
28 28 */
29 29 public static final String STOCK_OUT_NO_PREFIX = "QCKD";
30 30  
  31 + /**
  32 + * 库存调拨 {@link cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveDO}
  33 + */
  34 + public static final String STOCK_MOVE_NO_PREFIX = "QCDB";
  35 +
31 36 @Resource
32 37 private StringRedisTemplate stringRedisTemplate;
33 38  
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInServiceImpl.java
... ... @@ -66,7 +66,7 @@ public class ErpStockInServiceImpl implements ErpStockInService {
66 66 List<ErpStockInItemDO> stockInItems = validateStockInItems(createReqVO.getItems());
67 67 // 1.2 校验供应商
68 68 supplierService.validateSupplier(createReqVO.getSupplierId());
69   - // 1.3
  69 + // 1.3 生成入库单号,并校验唯一性
70 70 String no = noRedisDAO.generate(ErpNoRedisDAO.STOCK_IN_NO_PREFIX);
71 71 if (stockInMapper.selectByNo(no) != null) {
72 72 throw exception(STOCK_IN_NO_EXISTS);
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockMoveServiceImpl.java
... ... @@ -15,7 +15,7 @@ import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
15 15 import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
16 16 import cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum;
17 17 import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
18   -import cn.iocoder.yudao.module.erp.service.sale.ErpCustomerService;
  18 +import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO;
19 19 import jakarta.annotation.Resource;
20 20 import org.springframework.stereotype.Service;
21 21 import org.springframework.transaction.annotation.Transactional;
... ... @@ -26,6 +26,7 @@ import java.util.Collection;
26 26 import java.util.Collections;
27 27 import java.util.List;
28 28 import java.util.Map;
  29 +import java.util.stream.Stream;
29 30  
30 31 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
31 32 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
... ... @@ -55,8 +56,6 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
55 56 @Resource
56 57 private ErpWarehouseService warehouseService;
57 58 @Resource
58   - private ErpCustomerService customerService;
59   - @Resource
60 59 private ErpStockRecordService stockRecordService;
61 60  
62 61 @Override
... ... @@ -64,12 +63,10 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
64 63 public Long createStockMove(ErpStockMoveSaveReqVO createReqVO) {
65 64 // 1.1 校验出库项的有效性
66 65 List<ErpStockMoveItemDO> stockMoveItems = validateStockMoveItems(createReqVO.getItems());
67   - // 1.2 校验客户
68   - customerService.validateCustomer(createReqVO.getCustomerId());
69   - // 1.3
70   - String no = noRedisDAO.generate(ErpNoRedisDAO.STOCK_OUT_NO_PREFIX);
  66 + // 1.2 生成调拨单号,并校验唯一性
  67 + String no = noRedisDAO.generate(ErpNoRedisDAO.STOCK_MOVE_NO_PREFIX);
71 68 if (stockMoveMapper.selectByNo(no) != null) {
72   - throw exception(STOCK_OUT_NO_EXISTS);
  69 + throw exception(STOCK_MOVE_NO_EXISTS);
73 70 }
74 71  
75 72 // 2.1 插入出库单
... ... @@ -90,11 +87,9 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
90 87 // 1.1 校验存在
91 88 ErpStockMoveDO stockMove = validateStockMoveExists(updateReqVO.getId());
92 89 if (ErpAuditStatus.APPROVE.getStatus().equals(stockMove.getStatus())) {
93   - throw exception(STOCK_OUT_UPDATE_FAIL_APPROVE, stockMove.getNo());
  90 + throw exception(STOCK_MOVE_UPDATE_FAIL_APPROVE, stockMove.getNo());
94 91 }
95   - // 1.2 校验客户
96   - customerService.validateCustomer(updateReqVO.getCustomerId());
97   - // 1.3 校验出库项的有效性
  92 + // 1.2 校验出库项的有效性
98 93 List<ErpStockMoveItemDO> stockMoveItems = validateStockMoveItems(updateReqVO.getItems());
99 94  
100 95 // 2.1 更新出库单
... ... @@ -114,26 +109,31 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
114 109 ErpStockMoveDO stockMove = validateStockMoveExists(id);
115 110 // 1.2 校验状态
116 111 if (stockMove.getStatus().equals(status)) {
117   - throw exception(approve ? STOCK_OUT_APPROVE_FAIL : STOCK_OUT_PROCESS_FAIL);
  112 + throw exception(approve ? STOCK_MOVE_APPROVE_FAIL : STOCK_MOVE_PROCESS_FAIL);
118 113 }
119 114  
120 115 // 2. 更新状态
121 116 int updateCount = stockMoveMapper.updateByIdAndStatus(id, stockMove.getStatus(),
122 117 new ErpStockMoveDO().setStatus(status));
123 118 if (updateCount == 0) {
124   - throw exception(approve ? STOCK_OUT_APPROVE_FAIL : STOCK_OUT_PROCESS_FAIL);
  119 + throw exception(approve ? STOCK_MOVE_APPROVE_FAIL : STOCK_MOVE_PROCESS_FAIL);
125 120 }
126 121  
127 122 // 3. 变更库存
128 123 List<ErpStockMoveItemDO> stockMoveItems = stockMoveItemMapper.selectListByMoveId(id);
129   - Integer bizType = approve ? ErpStockRecordBizTypeEnum.OTHER_OUT.getType()
130   - : ErpStockRecordBizTypeEnum.OTHER_OUT_CANCEL.getType();
  124 + Integer fromBizType = approve ? ErpStockRecordBizTypeEnum.MOVE_OUT.getType()
  125 + : ErpStockRecordBizTypeEnum.MOVE_OUT_CANCEL.getType();
  126 + Integer toBizType = approve ? ErpStockRecordBizTypeEnum.MOVE_IN.getType()
  127 + : ErpStockRecordBizTypeEnum.MOVE_IN_CANCEL.getType();
131 128 stockMoveItems.forEach(stockMoveItem -> {
132   - BigDecimal count = approve ? stockMoveItem.getCount() : stockMoveItem.getCount().negate();
133   - // TODO 芋艿:稍后搞
134   -// stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
135   -// stockMoveItem.getProductId(), stockMoveItem.getWarehouseId(), count,
136   -// bizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo()));
  129 + BigDecimal fromCount = approve ? stockMoveItem.getCount().negate() : stockMoveItem.getCount();
  130 + BigDecimal toCount = approve ? stockMoveItem.getCount() : stockMoveItem.getCount().negate();
  131 + stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
  132 + stockMoveItem.getProductId(), stockMoveItem.getFromWarehouseId(), fromCount,
  133 + fromBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo()));
  134 + stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
  135 + stockMoveItem.getProductId(), stockMoveItem.getToWarehouseId(), toCount,
  136 + toBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo()));
137 137 });
138 138 }
139 139  
... ... @@ -143,8 +143,8 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
143 143 convertSet(list, ErpStockMoveSaveReqVO.Item::getProductId));
144 144 Map<Long, ErpProductDO> productMap = convertMap(productList, ErpProductDO::getId);
145 145 // 1.2 校验仓库存在
146   - // TODO 芋艿:稍后搞
147   -// warehouseService.validWarehouseList(convertSet(list, ErpStockMoveSaveReqVO.Item::getWarehouseId));
  146 + warehouseService.validWarehouseList(convertSetByFlatMap(list,
  147 + item -> Stream.of(item.getFromWarehouseId(), item.getToWarehouseId())));
148 148 // 2. 转化为 ErpStockMoveItemDO 列表
149 149 return convertList(list, o -> BeanUtils.toBean(o, ErpStockMoveItemDO.class, item -> item
150 150 .setProductUnitId(productMap.get(item.getProductId()).getUnitId())
... ... @@ -180,7 +180,7 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
180 180 }
181 181 stockMoves.forEach(stockMove -> {
182 182 if (ErpAuditStatus.APPROVE.getStatus().equals(stockMove.getStatus())) {
183   - throw exception(STOCK_OUT_DELETE_FAIL_APPROVE, stockMove.getNo());
  183 + throw exception(STOCK_MOVE_DELETE_FAIL_APPROVE, stockMove.getNo());
184 184 }
185 185 });
186 186  
... ... @@ -196,7 +196,7 @@ public class ErpStockMoveServiceImpl implements ErpStockMoveService {
196 196 private ErpStockMoveDO validateStockMoveExists(Long id) {
197 197 ErpStockMoveDO stockMove = stockMoveMapper.selectById(id);
198 198 if (stockMove == null) {
199   - throw exception(STOCK_OUT_NOT_EXISTS);
  199 + throw exception(STOCK_MOVE_NOT_EXISTS);
200 200 }
201 201 return stockMove;
202 202 }
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockOutServiceImpl.java
... ... @@ -67,7 +67,7 @@ public class ErpStockOutServiceImpl implements ErpStockOutService {
67 67 List<ErpStockOutItemDO> stockOutItems = validateStockOutItems(createReqVO.getItems());
68 68 // 1.2 校验客户
69 69 customerService.validateCustomer(createReqVO.getCustomerId());
70   - // 1.3
  70 + // 1.3 生成出库单号,并校验唯一性
71 71 String no = noRedisDAO.generate(ErpNoRedisDAO.STOCK_OUT_NO_PREFIX);
72 72 if (stockOutMapper.selectByNo(no) != null) {
73 73 throw exception(STOCK_OUT_NO_EXISTS);
... ... @@ -130,7 +130,7 @@ public class ErpStockOutServiceImpl implements ErpStockOutService {
130 130 Integer bizType = approve ? ErpStockRecordBizTypeEnum.OTHER_OUT.getType()
131 131 : ErpStockRecordBizTypeEnum.OTHER_OUT_CANCEL.getType();
132 132 stockOutItems.forEach(stockOutItem -> {
133   - BigDecimal count = approve ? stockOutItem.getCount() : stockOutItem.getCount().negate();
  133 + BigDecimal count = approve ? stockOutItem.getCount().negate() : stockOutItem.getCount();
134 134 stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO(
135 135 stockOutItem.getProductId(), stockOutItem.getWarehouseId(), count,
136 136 bizType, stockOutItem.getOutId(), stockOutItem.getId(), stockOut.getNo()));
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java
... ... @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock.ErpStockPageReqVO;
5 5 import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO;
6 6 import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockMapper;
  7 +import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
7 8 import jakarta.annotation.Resource;
8 9 import org.springframework.stereotype.Service;
9 10 import org.springframework.validation.annotation.Validated;
... ... @@ -31,6 +32,11 @@ public class ErpStockServiceImpl implements ErpStockService {
31 32 private static final Boolean NEGATIVE_STOCK_COUNT_ENABLE = false;
32 33  
33 34 @Resource
  35 + private ErpProductService productService;
  36 + @Resource
  37 + private ErpWarehouseService warehouseService;
  38 +
  39 + @Resource
34 40 private ErpStockMapper stockMapper;
35 41  
36 42 @Override
... ... @@ -58,13 +64,16 @@ public class ErpStockServiceImpl implements ErpStockService {
58 64 }
59 65 // 1.2 校验库存是否充足
60 66 if (!NEGATIVE_STOCK_COUNT_ENABLE && stock.getCount().add(count).compareTo(BigDecimal.ZERO) < 0) {
61   - throw exception(STOCK_COUNT_NEGATIVE, stock.getCount(), count);
  67 + throw exception(STOCK_COUNT_NEGATIVE, productService.getProduct(productId).getName(),
  68 + warehouseService.getWarehouse(warehouseId).getName(), stock.getCount(), count);
62 69 }
63 70  
64 71 // 2. 库存变更
65 72 int updateCount = stockMapper.updateCountIncrement(stock.getId(), count, NEGATIVE_STOCK_COUNT_ENABLE);
66 73 if (updateCount == 0) {
67   - throw exception(STOCK_COUNT_NEGATIVE2); // 此时不好去查询最新库存,所以直接抛出该提示,不提供具体库存数字
  74 + // 此时不好去查询最新库存,所以直接抛出该提示,不提供具体库存数字
  75 + throw exception(STOCK_COUNT_NEGATIVE2, productService.getProduct(productId).getName(),
  76 + warehouseService.getWarehouse(warehouseId).getName());
68 77 }
69 78  
70 79 // 3. 返回最新库存
... ...