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

This commit is contained in:
YunaiV 2024-02-06 23:28:57 +08:00
parent bfe3dc95fe
commit 593e1fd59c
9 changed files with 125 additions and 34 deletions

View File

@ -288,11 +288,16 @@ public class CollectionUtils {
public static <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc, public static <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc,
BinaryOperator<V> accumulator) { BinaryOperator<V> accumulator) {
if (CollUtil.isEmpty(from)) { return getSumValue(from, valueFunc, accumulator, null);
return null;
} }
assert from.size() > 0; // 断言避免告警
return from.stream().map(valueFunc).reduce(accumulator).get(); public static <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc,
BinaryOperator<V> accumulator, V defaultValue) {
if (CollUtil.isEmpty(from)) {
return defaultValue;
}
assert !from.isEmpty(); // 断言避免告警
return from.stream().map(valueFunc).filter(Objects::nonNull).reduce(accumulator).orElse(defaultValue);
} }
public static <T> void addIfNotNull(Collection<T> coll, T item) { public static <T> void addIfNotNull(Collection<T> coll, T item) {
@ -302,8 +307,8 @@ public class CollectionUtils {
coll.add(item); coll.add(item);
} }
public static <T> Collection<T> singleton(T deptId) { public static <T> Collection<T> singleton(T obj) {
return deptId == null ? Collections.emptyList() : Collections.singleton(deptId); return obj == null ? Collections.emptyList() : Collections.singleton(obj);
} }
} }

View File

@ -1,7 +1,10 @@
package cn.iocoder.yudao.framework.common.util.number; package cn.iocoder.yudao.framework.common.util.number;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import java.math.BigDecimal;
/** /**
* 数字的工具类补全 {@link cn.hutool.core.util.NumberUtil} 的功能 * 数字的工具类补全 {@link cn.hutool.core.util.NumberUtil} 的功能
* *
@ -37,4 +40,21 @@ public class NumberUtils {
return distance; return distance;
} }
/**
* 提供精确的乘法运算
*
* hutool {@link NumberUtil#mul(BigDecimal...)} 的差别是如果存在 null则返回 null
*
* @param values 多个被乘值
* @return
*/
public static BigDecimal mul(BigDecimal... values) {
for (BigDecimal value : values) {
if (value == null) {
return null;
}
}
return NumberUtil.mul(values);
}
} }

View File

@ -13,7 +13,7 @@ public interface ErrorCodeConstants {
ErrorCode SUPPLIER_NOT_EXISTS = new ErrorCode(1_030_100_000, "供应商不存在"); ErrorCode SUPPLIER_NOT_EXISTS = new ErrorCode(1_030_100_000, "供应商不存在");
ErrorCode SUPPLIER_NOT_ENABLE = new ErrorCode(1_030_100_000, "供应商({})未启用"); ErrorCode SUPPLIER_NOT_ENABLE = new ErrorCode(1_030_100_000, "供应商({})未启用");
// ========== 销售订单1-030-200-000 ========== // ========== ERP 销售订单1-030-200-000 ==========
ErrorCode SALE_ORDER_NOT_EXISTS = new ErrorCode(1_020_200_000, "销售订单不存在"); ErrorCode SALE_ORDER_NOT_EXISTS = new ErrorCode(1_020_200_000, "销售订单不存在");
// ========== ERP 仓库 1-030-400-000 ========== // ========== ERP 仓库 1-030-400-000 ==========
@ -22,6 +22,10 @@ public interface ErrorCodeConstants {
// ========== ERP 其它入库单 1-030-401-000 ========== // ========== ERP 其它入库单 1-030-401-000 ==========
ErrorCode STOCK_IN_NOT_EXISTS = new ErrorCode(1_030_401_000, "其它入库单不存在"); ErrorCode STOCK_IN_NOT_EXISTS = new ErrorCode(1_030_401_000, "其它入库单不存在");
ErrorCode STOCK_IN_DELETE_FAIL_APPROVE = new ErrorCode(1_030_401_001, "其它入库单({})已审核,无法删除");
ErrorCode STOCK_IN_PROCESS_FAIL = new ErrorCode(1_030_401_002, "反审核失败,只有已审核的入库单才能反审核");
ErrorCode STOCK_IN_APPROVE_FAIL = new ErrorCode(1_030_401_003, "审核失败,只有未审核的入库单才能审核");
// ========== ERP 产品 1-030-500-000 ========== // ========== ERP 产品 1-030-500-000 ==========
ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_030_500_000, "产品不存在"); ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_030_500_000, "产品不存在");

View File

@ -75,12 +75,21 @@ public class ErpStockInController {
return success(true); return success(true);
} }
@PutMapping("/update-status")
@Operation(summary = "更新其它入库单的状态")
@PreAuthorize("@ss.hasPermission('erp:stock-in:update')")
public CommonResult<Boolean> updateStockInStatus(@RequestParam("id") Long id,
@RequestParam("status") Integer status) {
stockInService.updateStockInStatus(id, status);
return success(true);
}
@DeleteMapping("/delete") @DeleteMapping("/delete")
@Operation(summary = "删除其它入库单") @Operation(summary = "删除其它入库单")
@Parameter(name = "id", description = "编号", required = true) @Parameter(name = "ids", description = "编号数组", required = true)
@PreAuthorize("@ss.hasPermission('erp:stock-in:delete')") @PreAuthorize("@ss.hasPermission('erp:stock-in:delete')")
public CommonResult<Boolean> deleteStockIn(@RequestParam("id") Long id) { public CommonResult<Boolean> deleteStockIn(@RequestParam("ids") List<Long> ids) {
stockInService.deleteStockIn(id); stockInService.deleteStockIn(ids);
return success(true); return success(true);
} }
@ -93,12 +102,15 @@ public class ErpStockInController {
if (stockIn == null) { if (stockIn == null) {
return success(null); return success(null);
} }
List<ErpStockInItemDO> stockInItems = stockInService.getStockInItemListByInId(id); List<ErpStockInItemDO> stockInItemList = stockInService.getStockInItemListByInId(id);
// TODO 芋艿有个锤子 Map<Long, ErpProductRespVO> productMap = productService.getProductVOMap(
convertSet(stockInItemList, ErpStockInItemDO::getProductId));
return success(BeanUtils.toBean(stockIn, ErpStockInRespVO.class, stockInVO -> return success(BeanUtils.toBean(stockIn, ErpStockInRespVO.class, stockInVO ->
stockInVO.setItems(BeanUtils.toBean(stockInItems, ErpStockInRespVO.Item.class, item -> { stockInVO.setItems(BeanUtils.toBean(stockInItemList, ErpStockInRespVO.Item.class, item -> {
ErpStockDO stock = stockService.getStock(item.getProductId(), item.getWarehouseId()); ErpStockDO stock = stockService.getStock(item.getProductId(), item.getWarehouseId());
item.setStockCount(stock != null ? stock.getCount() : BigDecimal.ZERO); item.setStockCount(stock != null ? stock.getCount() : BigDecimal.ZERO);
MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName())
.setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName()));
})))); }))));
} }
@ -142,8 +154,8 @@ public class ErpStockInController {
// 2. 开始拼接 // 2. 开始拼接
return BeanUtils.toBean(pageResult, ErpStockInRespVO.class, stockIn -> { return BeanUtils.toBean(pageResult, ErpStockInRespVO.class, stockIn -> {
stockIn.setItems(BeanUtils.toBean(stockInItemMap.get(stockIn.getId()), ErpStockInRespVO.Item.class, stockIn.setItems(BeanUtils.toBean(stockInItemMap.get(stockIn.getId()), ErpStockInRespVO.Item.class,
item -> MapUtils.findAndThen(productMap, item.getProductId(), item -> MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName())
product -> item.setProductName(product.getName()).setProductUnitName(product.getUnitName())))); .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName()))));
stockIn.setProductNames(CollUtil.join(stockIn.getItems(), "", ErpStockInRespVO.Item::getProductName)); stockIn.setProductNames(CollUtil.join(stockIn.getItems(), "", ErpStockInRespVO.Item::getProductName));
MapUtils.findAndThen(supplierMap, stockIn.getSupplierId(), supplier -> stockIn.setSupplierName(supplier.getName())); MapUtils.findAndThen(supplierMap, stockIn.getSupplierId(), supplier -> stockIn.setSupplierName(supplier.getName()));
MapUtils.findAndThen(userMap, Long.parseLong(stockIn.getCreator()), user -> stockIn.setCreatorName(user.getNickname())); MapUtils.findAndThen(userMap, Long.parseLong(stockIn.getCreator()), user -> stockIn.setCreatorName(user.getNickname()));

View File

@ -97,6 +97,8 @@ public class ErpStockInRespVO {
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "巧克力") @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "巧克力")
private String productName; private String productName;
@Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "A9985")
private String productBarCode;
@Schema(description = "产品单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "") @Schema(description = "产品单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "")
private String productUnitName; private String productUnitName;

View File

@ -53,8 +53,7 @@ public class ErpStockInSaveReqVO {
@NotNull(message = "产品编号不能为空") @NotNull(message = "产品编号不能为空")
private Long productId; private Long productId;
@Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") @Schema(description = "产品单价", example = "100.00")
@NotNull(message = "产品单价不能为空")
private BigDecimal productPrice; private BigDecimal productPrice;
@Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")

View File

@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInPageReqVO; import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInPageReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInDO; import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInItemDO; import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInItemDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
/** /**
@ -34,4 +35,9 @@ public interface ErpStockInMapper extends BaseMapperX<ErpStockInDO> {
return selectJoinPage(reqVO, ErpStockInDO.class, query); return selectJoinPage(reqVO, ErpStockInDO.class, query);
} }
default int updateByIdAndStatus(Long id, Integer status, ErpStockInDO updateObj) {
return update(updateObj, new LambdaUpdateWrapper<ErpStockInDO>()
.eq(ErpStockInDO::getId, id).eq(ErpStockInDO::getStatus, status));
}
} }

View File

@ -33,11 +33,19 @@ public interface ErpStockInService {
void updateStockIn(@Valid ErpStockInSaveReqVO updateReqVO); void updateStockIn(@Valid ErpStockInSaveReqVO updateReqVO);
/** /**
* 删除其它入库单 * 更新其它入库单的状态
* *
* @param id 编号 * @param id 编号
* @param status 状态
*/ */
void deleteStockIn(Long id); void updateStockInStatus(Long id, Integer status);
/**
* 删除其它入库单
*
* @param ids 编号数组
*/
void deleteStockIn(List<Long> ids);
/** /**
* 获得其它入库单 * 获得其它入库单

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.erp.service.stock;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInPageReqVO; import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInSaveReqVO; import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInSaveReqVO;
@ -26,9 +27,9 @@ import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.STOCK_IN_NOT_EXISTS; import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
// TODO 芋艿记录操作日志
/** /**
* ERP 其它入库单 Service 实现类 * ERP 其它入库单 Service 实现类
* *
@ -62,9 +63,9 @@ public class ErpStockInServiceImpl implements ErpStockInService {
ErpStockInDO stockIn = BeanUtils.toBean(createReqVO, ErpStockInDO.class, in -> in ErpStockInDO stockIn = BeanUtils.toBean(createReqVO, ErpStockInDO.class, in -> in
.setStatus(ErpAuditStatus.PROCESS.getStatus()) .setStatus(ErpAuditStatus.PROCESS.getStatus())
.setTotalCount(getSumValue(stockInItems, ErpStockInItemDO::getCount, BigDecimal::add)) .setTotalCount(getSumValue(stockInItems, ErpStockInItemDO::getCount, BigDecimal::add))
.setTotalPrice(getSumValue(stockInItems, ErpStockInItemDO::getTotalPrice, BigDecimal::add))); .setTotalPrice(getSumValue(stockInItems, ErpStockInItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO)));
stockInMapper.insert(stockIn); stockInMapper.insert(stockIn);
// 2. 插入子表 // 2.2 插入入库单项
stockInItems.forEach(o -> o.setInId(stockIn.getId())); stockInItems.forEach(o -> o.setInId(stockIn.getId()));
stockInItemMapper.insertBatch(stockInItems); stockInItemMapper.insertBatch(stockInItems);
return stockIn.getId(); return stockIn.getId();
@ -89,6 +90,28 @@ public class ErpStockInServiceImpl implements ErpStockInService {
updateStockInItemList(updateReqVO.getId(), stockInItems); updateStockInItemList(updateReqVO.getId(), stockInItems);
} }
@Override
@Transactional(rollbackFor = Exception.class)
public void updateStockInStatus(Long id, Integer status) {
// 1.1 校验存在
ErpStockInDO stockIn = validateStockInExists(id);
// 1.2 校验状态
if (stockIn.getStatus().equals(status)) {
throw exception(ErpAuditStatus.PROCESS.getStatus().equals(status) ?
STOCK_IN_PROCESS_FAIL : STOCK_IN_APPROVE_FAIL);
}
// 2. 更新状态
int updateCount = stockInMapper.updateByIdAndStatus(id, stockIn.getStatus(),
new ErpStockInDO().setStatus(status));
if (updateCount == 0) {
throw exception(ErpAuditStatus.PROCESS.getStatus().equals(status) ?
STOCK_IN_PROCESS_FAIL : STOCK_IN_APPROVE_FAIL);
}
// 3. TODO 芋艿调整库存记录
}
private List<ErpStockInItemDO> validateStockInItems(List<ErpStockInSaveReqVO.Item> list) { private List<ErpStockInItemDO> validateStockInItems(List<ErpStockInSaveReqVO.Item> list) {
// 1.1 校验产品存在 // 1.1 校验产品存在
List<ErpProductDO> productList = productService.validProductList(convertSet(list, ErpStockInSaveReqVO.Item::getProductId)); List<ErpProductDO> productList = productService.validProductList(convertSet(list, ErpStockInSaveReqVO.Item::getProductId));
@ -98,7 +121,7 @@ public class ErpStockInServiceImpl implements ErpStockInService {
// 2. 转化为 ErpStockInItemDO 列表 // 2. 转化为 ErpStockInItemDO 列表
return convertList(list, o -> BeanUtils.toBean(o, ErpStockInItemDO.class, item -> item return convertList(list, o -> BeanUtils.toBean(o, ErpStockInItemDO.class, item -> item
.setProductUnitId(productMap.get(item.getProductId()).getUnitId()) .setProductUnitId(productMap.get(item.getProductId()).getUnitId())
.setTotalPrice(item.getProductPrice().multiply(item.getCount())))); .setTotalPrice(NumberUtils.mul(item.getProductPrice(), item.getCount()))));
} }
private void updateStockInItemList(Long id, List<ErpStockInItemDO> newList) { private void updateStockInItemList(Long id, List<ErpStockInItemDO> newList) {
@ -122,21 +145,33 @@ public class ErpStockInServiceImpl implements ErpStockInService {
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void deleteStockIn(Long id) { public void deleteStockIn(List<Long> ids) {
// 1. 校验存在 // 1. 校验不处于已审批
validateStockInExists(id); List<ErpStockInDO> stockIns = stockInMapper.selectBatchIds(ids);
// TODO 芋艿校验一下 if (CollUtil.isEmpty(stockIns)) {
return;
}
stockIns.forEach(stockIn -> {
if (ErpAuditStatus.APPROVE.getStatus().equals(stockIn.getStatus())) {
throw exception(STOCK_IN_DELETE_FAIL_APPROVE, stockIn.getNo());
}
});
// 2.1 删除 // 2. 遍历删除并记录操作日志
stockInMapper.deleteById(id); stockIns.forEach(stockIn -> {
// 2.2 删除子表 // 2.1 删除入库单
stockInItemMapper.deleteByInId(id); stockInMapper.deleteById(stockIn.getId());
// 2.2 删除入库单项
stockInItemMapper.deleteByInId(stockIn.getId());
});
} }
private void validateStockInExists(Long id) { private ErpStockInDO validateStockInExists(Long id) {
if (stockInMapper.selectById(id) == null) { ErpStockInDO stockIn = stockInMapper.selectById(id);
if (stockIn == null) {
throw exception(STOCK_IN_NOT_EXISTS); throw exception(STOCK_IN_NOT_EXISTS);
} }
return stockIn;
} }
@Override @Override
@ -149,7 +184,7 @@ public class ErpStockInServiceImpl implements ErpStockInService {
return stockInMapper.selectPage(pageReqVO); return stockInMapper.selectPage(pageReqVO);
} }
// ==================== 子表ERP 其它入库 ==================== // ==================== 入库项 ====================
@Override @Override
public List<ErpStockInItemDO> getStockInItemListByInId(Long inId) { public List<ErpStockInItemDO> getStockInItemListByInId(Long inId) {