Commit dbc6134f805d82d4a3d6d214d71e8eda4064df7f
1 parent
3479ef81
✨ ERP:增加库存变更记录
Showing
9 changed files
with
181 additions
and
9 deletions
yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
| ... | ... | @@ -27,6 +27,12 @@ public interface ErrorCodeConstants { |
| 27 | 27 | ErrorCode STOCK_IN_APPROVE_FAIL = new ErrorCode(1_030_401_003, "审核失败,只有未审核的入库单才能审核"); |
| 28 | 28 | ErrorCode STOCK_IN_NO_EXISTS = new ErrorCode(1_030_401_004, "生成入库单失败,请重新提交"); |
| 29 | 29 | |
| 30 | + // ========== ERP 其它出库单 1-030-402-000 ========== | |
| 31 | + | |
| 32 | + // ========== ERP 产品库存 1-030-403-000 ========== | |
| 33 | + ErrorCode STOCK_COUNT_NEGATIVE = new ErrorCode(1_030_403_000, "操作失败,产品当前库存:{},小于变更数量:{}"); | |
| 34 | + ErrorCode STOCK_COUNT_NEGATIVE2 = new ErrorCode(1_030_403_000, "操作失败,库存不足"); | |
| 35 | + | |
| 30 | 36 | // ========== ERP 产品 1-030-500-000 ========== |
| 31 | 37 | ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_030_500_000, "产品不存在"); |
| 32 | 38 | ErrorCode PRODUCT_NOT_ENABLE = new ErrorCode(1_030_500_001, "产品({})未启用"); | ... | ... |
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockRecordDO.java
| ... | ... | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.erp.dal.dataobject.stock; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| 4 | 4 | import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; |
| 5 | +import cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum; | |
| 5 | 6 | import com.baomidou.mybatisplus.annotation.KeySequence; |
| 6 | 7 | import com.baomidou.mybatisplus.annotation.TableId; |
| 7 | 8 | import com.baomidou.mybatisplus.annotation.TableName; |
| ... | ... | @@ -56,25 +57,25 @@ public class ErpStockRecordDO extends BaseDO { |
| 56 | 57 | /** |
| 57 | 58 | * 业务类型 |
| 58 | 59 | * |
| 59 | - * 枚举 {@link cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum} | |
| 60 | + * 枚举 {@link ErpStockRecordBizTypeEnum} | |
| 60 | 61 | */ |
| 61 | 62 | private Integer bizType; |
| 62 | 63 | /** |
| 63 | 64 | * 业务编号 |
| 64 | 65 | * |
| 65 | - * 例如说:TODO | |
| 66 | + * 例如说:{@link ErpStockInDO#getId()} | |
| 66 | 67 | */ |
| 67 | 68 | private Long bizId; |
| 68 | 69 | /** |
| 69 | 70 | * 业务项编号 |
| 70 | 71 | * |
| 71 | - * 例如说:TODO | |
| 72 | + * 例如说:{@link ErpStockInItemDO#getId()} | |
| 72 | 73 | */ |
| 73 | 74 | private Long bizItemId; |
| 74 | 75 | /** |
| 75 | 76 | * 业务单号 |
| 76 | 77 | * |
| 77 | - * 例如说:TODO | |
| 78 | + * 例如说:{@link ErpStockInDO#getNo()} | |
| 78 | 79 | */ |
| 79 | 80 | private String bizNo; |
| 80 | 81 | ... | ... |
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockMapper.java
| ... | ... | @@ -5,8 +5,11 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 5 | 5 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 6 | 6 | import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock.ErpStockPageReqVO; |
| 7 | 7 | import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO; |
| 8 | +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |
| 8 | 9 | import org.apache.ibatis.annotations.Mapper; |
| 9 | 10 | |
| 11 | +import java.math.BigDecimal; | |
| 12 | + | |
| 10 | 13 | /** |
| 11 | 14 | * ERP 产品库存 Mapper |
| 12 | 15 | * |
| ... | ... | @@ -27,4 +30,18 @@ public interface ErpStockMapper extends BaseMapperX<ErpStockDO> { |
| 27 | 30 | ErpStockDO::getWarehouseId, warehouseId); |
| 28 | 31 | } |
| 29 | 32 | |
| 33 | + default int updateCountIncrement(Long id, BigDecimal count, boolean negativeEnable) { | |
| 34 | + LambdaUpdateWrapper<ErpStockDO> updateWrapper = new LambdaUpdateWrapper<ErpStockDO>() | |
| 35 | + .eq(ErpStockDO::getId, id); | |
| 36 | + if (count.compareTo(BigDecimal.ZERO) > 0) { | |
| 37 | + updateWrapper.setSql("count = count + " + count); | |
| 38 | + } else if (count.compareTo(BigDecimal.ZERO) < 0) { | |
| 39 | + if (!negativeEnable) { | |
| 40 | + updateWrapper.gt(ErpStockDO::getCount, count.abs()); | |
| 41 | + } | |
| 42 | + updateWrapper.setSql("count = count - " + count.abs()); | |
| 43 | + } | |
| 44 | + return update(null, updateWrapper); | |
| 45 | + } | |
| 46 | + | |
| 30 | 47 | } |
| 31 | 48 | \ No newline at end of file | ... | ... |
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInServiceImpl.java
| ... | ... | @@ -13,8 +13,10 @@ import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockInItemMapper; |
| 13 | 13 | import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockInMapper; |
| 14 | 14 | import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO; |
| 15 | 15 | import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; |
| 16 | +import cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum; | |
| 16 | 17 | import cn.iocoder.yudao.module.erp.service.product.ErpProductService; |
| 17 | 18 | import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService; |
| 19 | +import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockInCreateReqBO; | |
| 18 | 20 | import jakarta.annotation.Resource; |
| 19 | 21 | import org.springframework.stereotype.Service; |
| 20 | 22 | import org.springframework.transaction.annotation.Transactional; |
| ... | ... | @@ -54,6 +56,8 @@ public class ErpStockInServiceImpl implements ErpStockInService { |
| 54 | 56 | private ErpWarehouseService warehouseService; |
| 55 | 57 | @Resource |
| 56 | 58 | private ErpSupplierService supplierService; |
| 59 | + @Resource | |
| 60 | + private ErpStockRecordService stockRecordService; | |
| 57 | 61 | |
| 58 | 62 | @Override |
| 59 | 63 | @Transactional(rollbackFor = Exception.class) |
| ... | ... | @@ -102,23 +106,31 @@ public class ErpStockInServiceImpl implements ErpStockInService { |
| 102 | 106 | @Override |
| 103 | 107 | @Transactional(rollbackFor = Exception.class) |
| 104 | 108 | public void updateStockInStatus(Long id, Integer status) { |
| 109 | + boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status); | |
| 105 | 110 | // 1.1 校验存在 |
| 106 | 111 | ErpStockInDO stockIn = validateStockInExists(id); |
| 107 | 112 | // 1.2 校验状态 |
| 108 | 113 | if (stockIn.getStatus().equals(status)) { |
| 109 | - throw exception(ErpAuditStatus.PROCESS.getStatus().equals(status) ? | |
| 110 | - STOCK_IN_PROCESS_FAIL : STOCK_IN_APPROVE_FAIL); | |
| 114 | + throw exception(approve ? STOCK_IN_APPROVE_FAIL : STOCK_IN_PROCESS_FAIL); | |
| 111 | 115 | } |
| 112 | 116 | |
| 113 | 117 | // 2. 更新状态 |
| 114 | 118 | int updateCount = stockInMapper.updateByIdAndStatus(id, stockIn.getStatus(), |
| 115 | 119 | new ErpStockInDO().setStatus(status)); |
| 116 | 120 | if (updateCount == 0) { |
| 117 | - throw exception(ErpAuditStatus.PROCESS.getStatus().equals(status) ? | |
| 118 | - STOCK_IN_PROCESS_FAIL : STOCK_IN_APPROVE_FAIL); | |
| 121 | + throw exception(approve ? STOCK_IN_APPROVE_FAIL : STOCK_IN_PROCESS_FAIL); | |
| 119 | 122 | } |
| 120 | 123 | |
| 121 | - // 3. TODO 芋艿:调整库存记录 | |
| 124 | + // 3. 变更库存 | |
| 125 | + List<ErpStockInItemDO> stockInItems = stockInItemMapper.selectListByInId(id); | |
| 126 | + Integer bizType = approve ? ErpStockRecordBizTypeEnum.OTHER_IN.getType() | |
| 127 | + : ErpStockRecordBizTypeEnum.OTHER_IN_CANCEL.getType(); | |
| 128 | + stockInItems.forEach(stockInItem -> { | |
| 129 | + BigDecimal count = approve ? stockInItem.getCount() : stockInItem.getCount().negate(); | |
| 130 | + stockRecordService.createStockRecord(new ErpStockInCreateReqBO( | |
| 131 | + stockInItem.getProductId(), stockInItem.getWarehouseId(), count, | |
| 132 | + bizType, stockInItem.getInId(), stockInItem.getId(), stockIn.getNo())); | |
| 133 | + }); | |
| 122 | 134 | } |
| 123 | 135 | |
| 124 | 136 | private List<ErpStockInItemDO> validateStockInItems(List<ErpStockInSaveReqVO.Item> list) { | ... | ... |
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockRecordService.java
| ... | ... | @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.erp.service.stock; |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 4 | import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.record.ErpStockRecordPageReqVO; |
| 5 | 5 | import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockRecordDO; |
| 6 | +import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockInCreateReqBO; | |
| 7 | +import jakarta.validation.Valid; | |
| 6 | 8 | |
| 7 | 9 | /** |
| 8 | 10 | * ERP 产品库存明细 Service 接口 |
| ... | ... | @@ -27,4 +29,11 @@ public interface ErpStockRecordService { |
| 27 | 29 | */ |
| 28 | 30 | PageResult<ErpStockRecordDO> getStockRecordPage(ErpStockRecordPageReqVO pageReqVO); |
| 29 | 31 | |
| 32 | + /** | |
| 33 | + * 创建库存明细 | |
| 34 | + * | |
| 35 | + * @param createReqBO 创建库存明细 BO | |
| 36 | + */ | |
| 37 | + void createStockRecord(@Valid ErpStockInCreateReqBO createReqBO); | |
| 38 | + | |
| 30 | 39 | } |
| 31 | 40 | \ No newline at end of file | ... | ... |
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockRecordServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.erp.service.stock; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | |
| 4 | 5 | import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.record.ErpStockRecordPageReqVO; |
| 5 | 6 | import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockRecordDO; |
| 6 | 7 | import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockRecordMapper; |
| 8 | +import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockInCreateReqBO; | |
| 7 | 9 | import jakarta.annotation.Resource; |
| 8 | 10 | import org.springframework.stereotype.Service; |
| 11 | +import org.springframework.transaction.annotation.Transactional; | |
| 9 | 12 | import org.springframework.validation.annotation.Validated; |
| 10 | 13 | |
| 14 | +import java.math.BigDecimal; | |
| 15 | + | |
| 11 | 16 | /** |
| 12 | 17 | * ERP 产品库存明细 Service 实现类 |
| 13 | 18 | * |
| ... | ... | @@ -20,6 +25,9 @@ public class ErpStockRecordServiceImpl implements ErpStockRecordService { |
| 20 | 25 | @Resource |
| 21 | 26 | private ErpStockRecordMapper stockRecordMapper; |
| 22 | 27 | |
| 28 | + @Resource | |
| 29 | + private ErpStockService stockService; | |
| 30 | + | |
| 23 | 31 | @Override |
| 24 | 32 | public ErpStockRecordDO getStockRecord(Long id) { |
| 25 | 33 | return stockRecordMapper.selectById(id); |
| ... | ... | @@ -30,4 +38,16 @@ public class ErpStockRecordServiceImpl implements ErpStockRecordService { |
| 30 | 38 | return stockRecordMapper.selectPage(pageReqVO); |
| 31 | 39 | } |
| 32 | 40 | |
| 41 | + @Override | |
| 42 | + @Transactional(rollbackFor = Exception.class) | |
| 43 | + public void createStockRecord(ErpStockInCreateReqBO createReqBO) { | |
| 44 | + // 1. 更新库存 | |
| 45 | + BigDecimal totalCount = stockService.updateStockCountIncrement( | |
| 46 | + createReqBO.getProductId(), createReqBO.getWarehouseId(), createReqBO.getCount()); | |
| 47 | + // 2. 创建库存明细 | |
| 48 | + ErpStockRecordDO stockRecord = BeanUtils.toBean(createReqBO, ErpStockRecordDO.class) | |
| 49 | + .setTotalCount(totalCount); | |
| 50 | + stockRecordMapper.insert(stockRecord); | |
| 51 | + } | |
| 52 | + | |
| 33 | 53 | } |
| 34 | 54 | \ No newline at end of file | ... | ... |
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockService.java
| ... | ... | @@ -4,6 +4,8 @@ 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 | |
| 7 | +import java.math.BigDecimal; | |
| 8 | + | |
| 7 | 9 | /** |
| 8 | 10 | * ERP 产品库存 Service 接口 |
| 9 | 11 | * |
| ... | ... | @@ -36,4 +38,14 @@ public interface ErpStockService { |
| 36 | 38 | */ |
| 37 | 39 | PageResult<ErpStockDO> getStockPage(ErpStockPageReqVO pageReqVO); |
| 38 | 40 | |
| 41 | + /** | |
| 42 | + * 增量更新产品库存数量 | |
| 43 | + * | |
| 44 | + * @param productId 产品编号 | |
| 45 | + * @param warehouseId 仓库编号 | |
| 46 | + * @param count 增量数量:正数,表示增加;负数,表示减少 | |
| 47 | + * @return 更新后的库存 | |
| 48 | + */ | |
| 49 | + BigDecimal updateStockCountIncrement(Long productId, Long warehouseId, BigDecimal count); | |
| 50 | + | |
| 39 | 51 | } |
| 40 | 52 | \ No newline at end of file | ... | ... |
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java
| ... | ... | @@ -8,6 +8,12 @@ import jakarta.annotation.Resource; |
| 8 | 8 | import org.springframework.stereotype.Service; |
| 9 | 9 | import org.springframework.validation.annotation.Validated; |
| 10 | 10 | |
| 11 | +import java.math.BigDecimal; | |
| 12 | + | |
| 13 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | |
| 14 | +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.STOCK_COUNT_NEGATIVE; | |
| 15 | +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.STOCK_COUNT_NEGATIVE2; | |
| 16 | + | |
| 11 | 17 | /** |
| 12 | 18 | * ERP 产品库存 Service 实现类 |
| 13 | 19 | * |
| ... | ... | @@ -17,6 +23,13 @@ import org.springframework.validation.annotation.Validated; |
| 17 | 23 | @Validated |
| 18 | 24 | public class ErpStockServiceImpl implements ErpStockService { |
| 19 | 25 | |
| 26 | + /** | |
| 27 | + * 允许库存为负数 | |
| 28 | + * | |
| 29 | + * TODO 芋艿:后续做成 db 配置 | |
| 30 | + */ | |
| 31 | + private static final Boolean NEGATIVE_STOCK_COUNT_ENABLE = false; | |
| 32 | + | |
| 20 | 33 | @Resource |
| 21 | 34 | private ErpStockMapper stockMapper; |
| 22 | 35 | |
| ... | ... | @@ -35,4 +48,27 @@ public class ErpStockServiceImpl implements ErpStockService { |
| 35 | 48 | return stockMapper.selectPage(pageReqVO); |
| 36 | 49 | } |
| 37 | 50 | |
| 51 | + @Override | |
| 52 | + public BigDecimal updateStockCountIncrement(Long productId, Long warehouseId, BigDecimal count) { | |
| 53 | + // 1.1 查询当前库存 | |
| 54 | + ErpStockDO stock = stockMapper.selectByProductIdAndWarehouseId(productId, warehouseId); | |
| 55 | + if (stock == null) { | |
| 56 | + stock = new ErpStockDO().setProductId(productId).setWarehouseId(warehouseId).setCount(BigDecimal.ZERO); | |
| 57 | + stockMapper.insert(stock); | |
| 58 | + } | |
| 59 | + // 1.2 校验库存是否充足 | |
| 60 | + if (!NEGATIVE_STOCK_COUNT_ENABLE && stock.getCount().add(count).compareTo(BigDecimal.ZERO) < 0) { | |
| 61 | + throw exception(STOCK_COUNT_NEGATIVE, stock.getCount(), count); | |
| 62 | + } | |
| 63 | + | |
| 64 | + // 2. 库存变更 | |
| 65 | + int updateCount = stockMapper.updateCountIncrement(stock.getId(), count, NEGATIVE_STOCK_COUNT_ENABLE); | |
| 66 | + if (updateCount == 0) { | |
| 67 | + throw exception(STOCK_COUNT_NEGATIVE2); // 此时不好去查询最新库存,所以直接抛出该提示,不提供具体库存数字 | |
| 68 | + } | |
| 69 | + | |
| 70 | + // 3. 返回最新库存 | |
| 71 | + return stock.getCount().add(count); | |
| 72 | + } | |
| 73 | + | |
| 38 | 74 | } |
| 39 | 75 | \ No newline at end of file | ... | ... |
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/bo/ErpStockInCreateReqBO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.erp.service.stock.bo; | |
| 2 | + | |
| 3 | +import jakarta.validation.constraints.NotNull; | |
| 4 | +import lombok.AllArgsConstructor; | |
| 5 | +import lombok.Data; | |
| 6 | +import lombok.NoArgsConstructor; | |
| 7 | + | |
| 8 | +import java.math.BigDecimal; | |
| 9 | + | |
| 10 | +/** | |
| 11 | + * 库存明细的创建 Request BO | |
| 12 | + * | |
| 13 | + * @author 芋道源码 | |
| 14 | + */ | |
| 15 | +@Data | |
| 16 | +@NoArgsConstructor | |
| 17 | +@AllArgsConstructor | |
| 18 | +public class ErpStockInCreateReqBO { | |
| 19 | + | |
| 20 | + /** | |
| 21 | + * 产品编号 | |
| 22 | + */ | |
| 23 | + @NotNull(message = "产品编号不能为空") | |
| 24 | + private Long productId; | |
| 25 | + /** | |
| 26 | + * 仓库编号 | |
| 27 | + */ | |
| 28 | + @NotNull(message = "仓库编号不能为空") | |
| 29 | + private Long warehouseId; | |
| 30 | + /** | |
| 31 | + * 出入库数量 | |
| 32 | + * | |
| 33 | + * 正数,表示入库;负数,表示出库 | |
| 34 | + */ | |
| 35 | + @NotNull(message = "出入库数量不能为空") | |
| 36 | + private BigDecimal count; | |
| 37 | + | |
| 38 | + /** | |
| 39 | + * 业务类型 | |
| 40 | + */ | |
| 41 | + @NotNull(message = "业务类型不能为空") | |
| 42 | + private Integer bizType; | |
| 43 | + /** | |
| 44 | + * 业务编号 | |
| 45 | + */ | |
| 46 | + @NotNull(message = "业务编号不能为空") | |
| 47 | + private Long bizId; | |
| 48 | + /** | |
| 49 | + * 业务项编号 | |
| 50 | + */ | |
| 51 | + @NotNull(message = "业务项编号不能为空") | |
| 52 | + private Long bizItemId; | |
| 53 | + /** | |
| 54 | + * 业务单号 | |
| 55 | + */ | |
| 56 | + @NotNull(message = "业务单号不能为空") | |
| 57 | + private String bizNo; | |
| 58 | + | |
| 59 | +} | ... | ... |