Commit 593e1fd59c68422b582c21a27ab8b9e43fcb2bc6

Authored by YunaiV
1 parent bfe3dc95

✨ ERP:增加入库单的审批功能

yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
... ... @@ -288,11 +288,16 @@ public class CollectionUtils {
288 288  
289 289 public static <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc,
290 290 BinaryOperator<V> accumulator) {
  291 + return getSumValue(from, valueFunc, accumulator, null);
  292 + }
  293 +
  294 + public static <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc,
  295 + BinaryOperator<V> accumulator, V defaultValue) {
291 296 if (CollUtil.isEmpty(from)) {
292   - return null;
  297 + return defaultValue;
293 298 }
294   - assert from.size() > 0; // 断言,避免告警
295   - return from.stream().map(valueFunc).reduce(accumulator).get();
  299 + assert !from.isEmpty(); // 断言,避免告警
  300 + return from.stream().map(valueFunc).filter(Objects::nonNull).reduce(accumulator).orElse(defaultValue);
296 301 }
297 302  
298 303 public static <T> void addIfNotNull(Collection<T> coll, T item) {
... ... @@ -302,8 +307,8 @@ public class CollectionUtils {
302 307 coll.add(item);
303 308 }
304 309  
305   - public static <T> Collection<T> singleton(T deptId) {
306   - return deptId == null ? Collections.emptyList() : Collections.singleton(deptId);
  310 + public static <T> Collection<T> singleton(T obj) {
  311 + return obj == null ? Collections.emptyList() : Collections.singleton(obj);
307 312 }
308 313  
309 314 }
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/number/NumberUtils.java
1 1 package cn.iocoder.yudao.framework.common.util.number;
2 2  
  3 +import cn.hutool.core.util.NumberUtil;
3 4 import cn.hutool.core.util.StrUtil;
4 5  
  6 +import java.math.BigDecimal;
  7 +
5 8 /**
6 9 * 数字的工具类,补全 {@link cn.hutool.core.util.NumberUtil} 的功能
7 10 *
... ... @@ -37,4 +40,21 @@ public class NumberUtils {
37 40 return distance;
38 41 }
39 42  
  43 + /**
  44 + * 提供精确的乘法运算
  45 + *
  46 + * 和 hutool {@link NumberUtil#mul(BigDecimal...)} 的差别是,如果存在 null,则返回 null
  47 + *
  48 + * @param values 多个被乘值
  49 + * @return 积
  50 + */
  51 + public static BigDecimal mul(BigDecimal... values) {
  52 + for (BigDecimal value : values) {
  53 + if (value == null) {
  54 + return null;
  55 + }
  56 + }
  57 + return NumberUtil.mul(values);
  58 + }
  59 +
40 60 }
... ...
yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
... ... @@ -13,7 +13,7 @@ public interface ErrorCodeConstants {
13 13 ErrorCode SUPPLIER_NOT_EXISTS = new ErrorCode(1_030_100_000, "供应商不存在");
14 14 ErrorCode SUPPLIER_NOT_ENABLE = new ErrorCode(1_030_100_000, "供应商({})未启用");
15 15  
16   - // ========== 销售订单(1-030-200-000) ==========
  16 + // ========== ERP 销售订单(1-030-200-000) ==========
17 17 ErrorCode SALE_ORDER_NOT_EXISTS = new ErrorCode(1_020_200_000, "销售订单不存在");
18 18  
19 19 // ========== ERP 仓库 1-030-400-000 ==========
... ... @@ -22,6 +22,10 @@ public interface ErrorCodeConstants {
22 22  
23 23 // ========== ERP 其它入库单 1-030-401-000 ==========
24 24 ErrorCode STOCK_IN_NOT_EXISTS = new ErrorCode(1_030_401_000, "其它入库单不存在");
  25 + ErrorCode STOCK_IN_DELETE_FAIL_APPROVE = new ErrorCode(1_030_401_001, "其它入库单({})已审核,无法删除");
  26 + ErrorCode STOCK_IN_PROCESS_FAIL = new ErrorCode(1_030_401_002, "反审核失败,只有已审核的入库单才能反审核");
  27 + ErrorCode STOCK_IN_APPROVE_FAIL = new ErrorCode(1_030_401_003, "审核失败,只有未审核的入库单才能审核");
  28 +
25 29  
26 30 // ========== ERP 产品 1-030-500-000 ==========
27 31 ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_030_500_000, "产品不存在");
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockInController.java
... ... @@ -75,12 +75,21 @@ public class ErpStockInController {
75 75 return success(true);
76 76 }
77 77  
  78 + @PutMapping("/update-status")
  79 + @Operation(summary = "更新其它入库单的状态")
  80 + @PreAuthorize("@ss.hasPermission('erp:stock-in:update')")
  81 + public CommonResult<Boolean> updateStockInStatus(@RequestParam("id") Long id,
  82 + @RequestParam("status") Integer status) {
  83 + stockInService.updateStockInStatus(id, status);
  84 + return success(true);
  85 + }
  86 +
78 87 @DeleteMapping("/delete")
79 88 @Operation(summary = "删除其它入库单")
80   - @Parameter(name = "id", description = "编号", required = true)
  89 + @Parameter(name = "ids", description = "编号数组", required = true)
81 90 @PreAuthorize("@ss.hasPermission('erp:stock-in:delete')")
82   - public CommonResult<Boolean> deleteStockIn(@RequestParam("id") Long id) {
83   - stockInService.deleteStockIn(id);
  91 + public CommonResult<Boolean> deleteStockIn(@RequestParam("ids") List<Long> ids) {
  92 + stockInService.deleteStockIn(ids);
84 93 return success(true);
85 94 }
86 95  
... ... @@ -93,12 +102,15 @@ public class ErpStockInController {
93 102 if (stockIn == null) {
94 103 return success(null);
95 104 }
96   - List<ErpStockInItemDO> stockInItems = stockInService.getStockInItemListByInId(id);
97   - // TODO 芋艿:有个锤子;
  105 + List<ErpStockInItemDO> stockInItemList = stockInService.getStockInItemListByInId(id);
  106 + Map<Long, ErpProductRespVO> productMap = productService.getProductVOMap(
  107 + convertSet(stockInItemList, ErpStockInItemDO::getProductId));
98 108 return success(BeanUtils.toBean(stockIn, ErpStockInRespVO.class, stockInVO ->
99   - stockInVO.setItems(BeanUtils.toBean(stockInItems, ErpStockInRespVO.Item.class, item -> {
  109 + stockInVO.setItems(BeanUtils.toBean(stockInItemList, ErpStockInRespVO.Item.class, item -> {
100 110 ErpStockDO stock = stockService.getStock(item.getProductId(), item.getWarehouseId());
101 111 item.setStockCount(stock != null ? stock.getCount() : BigDecimal.ZERO);
  112 + MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName())
  113 + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName()));
102 114 }))));
103 115 }
104 116  
... ... @@ -142,8 +154,8 @@ public class ErpStockInController {
142 154 // 2. 开始拼接
143 155 return BeanUtils.toBean(pageResult, ErpStockInRespVO.class, stockIn -> {
144 156 stockIn.setItems(BeanUtils.toBean(stockInItemMap.get(stockIn.getId()), ErpStockInRespVO.Item.class,
145   - item -> MapUtils.findAndThen(productMap, item.getProductId(),
146   - product -> item.setProductName(product.getName()).setProductUnitName(product.getUnitName()))));
  157 + item -> MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName())
  158 + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName()))));
147 159 stockIn.setProductNames(CollUtil.join(stockIn.getItems(), ",", ErpStockInRespVO.Item::getProductName));
148 160 MapUtils.findAndThen(supplierMap, stockIn.getSupplierId(), supplier -> stockIn.setSupplierName(supplier.getName()));
149 161 MapUtils.findAndThen(userMap, Long.parseLong(stockIn.getCreator()), user -> stockIn.setCreatorName(user.getNickname()));
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/in/ErpStockInRespVO.java
... ... @@ -97,6 +97,8 @@ public class ErpStockInRespVO {
97 97  
98 98 @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "巧克力")
99 99 private String productName;
  100 + @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "A9985")
  101 + private String productBarCode;
100 102 @Schema(description = "产品单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "盒")
101 103 private String productUnitName;
102 104  
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/in/ErpStockInSaveReqVO.java
... ... @@ -53,8 +53,7 @@ public class ErpStockInSaveReqVO {
53 53 @NotNull(message = "产品编号不能为空")
54 54 private Long productId;
55 55  
56   - @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
57   - @NotNull(message = "产品单价不能为空")
  56 + @Schema(description = "产品单价", example = "100.00")
58 57 private BigDecimal productPrice;
59 58  
60 59 @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockInMapper.java
... ... @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
6 6 import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInPageReqVO;
7 7 import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInDO;
8 8 import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInItemDO;
  9 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
9 10 import org.apache.ibatis.annotations.Mapper;
10 11  
11 12 /**
... ... @@ -34,4 +35,9 @@ public interface ErpStockInMapper extends BaseMapperX&lt;ErpStockInDO&gt; {
34 35 return selectJoinPage(reqVO, ErpStockInDO.class, query);
35 36 }
36 37  
  38 + default int updateByIdAndStatus(Long id, Integer status, ErpStockInDO updateObj) {
  39 + return update(updateObj, new LambdaUpdateWrapper<ErpStockInDO>()
  40 + .eq(ErpStockInDO::getId, id).eq(ErpStockInDO::getStatus, status));
  41 + }
  42 +
37 43 }
38 44 \ No newline at end of file
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInService.java
... ... @@ -33,11 +33,19 @@ public interface ErpStockInService {
33 33 void updateStockIn(@Valid ErpStockInSaveReqVO updateReqVO);
34 34  
35 35 /**
36   - * 删除其它入库单
  36 + * 更新其它入库单的状态
37 37 *
38 38 * @param id 编号
  39 + * @param status 状态
  40 + */
  41 + void updateStockInStatus(Long id, Integer status);
  42 +
  43 + /**
  44 + * 删除其它入库单
  45 + *
  46 + * @param ids 编号数组
39 47 */
40   - void deleteStockIn(Long id);
  48 + void deleteStockIn(List<Long> ids);
41 49  
42 50 /**
43 51 * 获得其它入库单
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInServiceImpl.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.erp.service.stock;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
5 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 7 import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInPageReqVO;
7 8 import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInSaveReqVO;
... ... @@ -26,9 +27,9 @@ import java.util.Map;
26 27  
27 28 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
28 29 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
29   -import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.STOCK_IN_NOT_EXISTS;
30   -
  30 +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
31 31  
  32 +// TODO 芋艿:记录操作日志
32 33 /**
33 34 * ERP 其它入库单 Service 实现类
34 35 *
... ... @@ -62,9 +63,9 @@ public class ErpStockInServiceImpl implements ErpStockInService {
62 63 ErpStockInDO stockIn = BeanUtils.toBean(createReqVO, ErpStockInDO.class, in -> in
63 64 .setStatus(ErpAuditStatus.PROCESS.getStatus())
64 65 .setTotalCount(getSumValue(stockInItems, ErpStockInItemDO::getCount, BigDecimal::add))
65   - .setTotalPrice(getSumValue(stockInItems, ErpStockInItemDO::getTotalPrice, BigDecimal::add)));
  66 + .setTotalPrice(getSumValue(stockInItems, ErpStockInItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO)));
66 67 stockInMapper.insert(stockIn);
67   - // 2. 插入子表
  68 + // 2.2 插入入库单项
68 69 stockInItems.forEach(o -> o.setInId(stockIn.getId()));
69 70 stockInItemMapper.insertBatch(stockInItems);
70 71 return stockIn.getId();
... ... @@ -89,6 +90,28 @@ public class ErpStockInServiceImpl implements ErpStockInService {
89 90 updateStockInItemList(updateReqVO.getId(), stockInItems);
90 91 }
91 92  
  93 + @Override
  94 + @Transactional(rollbackFor = Exception.class)
  95 + public void updateStockInStatus(Long id, Integer status) {
  96 + // 1.1 校验存在
  97 + ErpStockInDO stockIn = validateStockInExists(id);
  98 + // 1.2 校验状态
  99 + if (stockIn.getStatus().equals(status)) {
  100 + throw exception(ErpAuditStatus.PROCESS.getStatus().equals(status) ?
  101 + STOCK_IN_PROCESS_FAIL : STOCK_IN_APPROVE_FAIL);
  102 + }
  103 +
  104 + // 2. 更新状态
  105 + int updateCount = stockInMapper.updateByIdAndStatus(id, stockIn.getStatus(),
  106 + new ErpStockInDO().setStatus(status));
  107 + if (updateCount == 0) {
  108 + throw exception(ErpAuditStatus.PROCESS.getStatus().equals(status) ?
  109 + STOCK_IN_PROCESS_FAIL : STOCK_IN_APPROVE_FAIL);
  110 + }
  111 +
  112 + // 3. TODO 芋艿:调整库存记录
  113 + }
  114 +
92 115 private List<ErpStockInItemDO> validateStockInItems(List<ErpStockInSaveReqVO.Item> list) {
93 116 // 1.1 校验产品存在
94 117 List<ErpProductDO> productList = productService.validProductList(convertSet(list, ErpStockInSaveReqVO.Item::getProductId));
... ... @@ -98,7 +121,7 @@ public class ErpStockInServiceImpl implements ErpStockInService {
98 121 // 2. 转化为 ErpStockInItemDO 列表
99 122 return convertList(list, o -> BeanUtils.toBean(o, ErpStockInItemDO.class, item -> item
100 123 .setProductUnitId(productMap.get(item.getProductId()).getUnitId())
101   - .setTotalPrice(item.getProductPrice().multiply(item.getCount()))));
  124 + .setTotalPrice(NumberUtils.mul(item.getProductPrice(), item.getCount()))));
102 125 }
103 126  
104 127 private void updateStockInItemList(Long id, List<ErpStockInItemDO> newList) {
... ... @@ -122,21 +145,33 @@ public class ErpStockInServiceImpl implements ErpStockInService {
122 145  
123 146 @Override
124 147 @Transactional(rollbackFor = Exception.class)
125   - public void deleteStockIn(Long id) {
126   - // 1. 校验存在
127   - validateStockInExists(id);
128   - // TODO 芋艿:校验一下;
129   -
130   - // 2.1 删除
131   - stockInMapper.deleteById(id);
132   - // 2.2 删除子表
133   - stockInItemMapper.deleteByInId(id);
  148 + public void deleteStockIn(List<Long> ids) {
  149 + // 1. 校验不处于已审批
  150 + List<ErpStockInDO> stockIns = stockInMapper.selectBatchIds(ids);
  151 + if (CollUtil.isEmpty(stockIns)) {
  152 + return;
  153 + }
  154 + stockIns.forEach(stockIn -> {
  155 + if (ErpAuditStatus.APPROVE.getStatus().equals(stockIn.getStatus())) {
  156 + throw exception(STOCK_IN_DELETE_FAIL_APPROVE, stockIn.getNo());
  157 + }
  158 + });
  159 +
  160 + // 2. 遍历删除,并记录操作日志
  161 + stockIns.forEach(stockIn -> {
  162 + // 2.1 删除入库单
  163 + stockInMapper.deleteById(stockIn.getId());
  164 + // 2.2 删除入库单项
  165 + stockInItemMapper.deleteByInId(stockIn.getId());
  166 + });
134 167 }
135 168  
136   - private void validateStockInExists(Long id) {
137   - if (stockInMapper.selectById(id) == null) {
  169 + private ErpStockInDO validateStockInExists(Long id) {
  170 + ErpStockInDO stockIn = stockInMapper.selectById(id);
  171 + if (stockIn == null) {
138 172 throw exception(STOCK_IN_NOT_EXISTS);
139 173 }
  174 + return stockIn;
140 175 }
141 176  
142 177 @Override
... ... @@ -149,7 +184,7 @@ public class ErpStockInServiceImpl implements ErpStockInService {
149 184 return stockInMapper.selectPage(pageReqVO);
150 185 }
151 186  
152   - // ==================== 子表(ERP 其它入库单项) ====================
  187 + // ==================== 入库项 ====================
153 188  
154 189 @Override
155 190 public List<ErpStockInItemDO> getStockInItemListByInId(Long inId) {
... ...