ERP:增加库存变更记录

This commit is contained in:
YunaiV 2024-02-07 12:24:56 +08:00
parent 3479ef8123
commit dbc6134f80
9 changed files with 181 additions and 9 deletions

View File

@ -27,6 +27,12 @@ public interface ErrorCodeConstants {
ErrorCode STOCK_IN_APPROVE_FAIL = new ErrorCode(1_030_401_003, "审核失败,只有未审核的入库单才能审核"); ErrorCode STOCK_IN_APPROVE_FAIL = new ErrorCode(1_030_401_003, "审核失败,只有未审核的入库单才能审核");
ErrorCode STOCK_IN_NO_EXISTS = new ErrorCode(1_030_401_004, "生成入库单失败,请重新提交"); ErrorCode STOCK_IN_NO_EXISTS = new ErrorCode(1_030_401_004, "生成入库单失败,请重新提交");
// ========== ERP 其它出库单 1-030-402-000 ==========
// ========== ERP 产品库存 1-030-403-000 ==========
ErrorCode STOCK_COUNT_NEGATIVE = new ErrorCode(1_030_403_000, "操作失败,产品当前库存:{},小于变更数量:{}");
ErrorCode STOCK_COUNT_NEGATIVE2 = new ErrorCode(1_030_403_000, "操作失败,库存不足");
// ========== 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, "产品不存在");
ErrorCode PRODUCT_NOT_ENABLE = new ErrorCode(1_030_500_001, "产品({})未启用"); ErrorCode PRODUCT_NOT_ENABLE = new ErrorCode(1_030_500_001, "产品({})未启用");

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.erp.dal.dataobject.stock;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
import cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
@ -56,25 +57,25 @@ public class ErpStockRecordDO extends BaseDO {
/** /**
* 业务类型 * 业务类型
* *
* 枚举 {@link cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum} * 枚举 {@link ErpStockRecordBizTypeEnum}
*/ */
private Integer bizType; private Integer bizType;
/** /**
* 业务编号 * 业务编号
* *
* 例如说TODO * 例如说{@link ErpStockInDO#getId()}
*/ */
private Long bizId; private Long bizId;
/** /**
* 业务项编号 * 业务项编号
* *
* 例如说TODO * 例如说{@link ErpStockInItemDO#getId()}
*/ */
private Long bizItemId; private Long bizItemId;
/** /**
* 业务单号 * 业务单号
* *
* 例如说TODO * 例如说{@link ErpStockInDO#getNo()}
*/ */
private String bizNo; private String bizNo;

View File

@ -5,8 +5,11 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock.ErpStockPageReqVO; import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock.ErpStockPageReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO; import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import java.math.BigDecimal;
/** /**
* ERP 产品库存 Mapper * ERP 产品库存 Mapper
* *
@ -27,4 +30,18 @@ public interface ErpStockMapper extends BaseMapperX<ErpStockDO> {
ErpStockDO::getWarehouseId, warehouseId); ErpStockDO::getWarehouseId, warehouseId);
} }
default int updateCountIncrement(Long id, BigDecimal count, boolean negativeEnable) {
LambdaUpdateWrapper<ErpStockDO> updateWrapper = new LambdaUpdateWrapper<ErpStockDO>()
.eq(ErpStockDO::getId, id);
if (count.compareTo(BigDecimal.ZERO) > 0) {
updateWrapper.setSql("count = count + " + count);
} else if (count.compareTo(BigDecimal.ZERO) < 0) {
if (!negativeEnable) {
updateWrapper.gt(ErpStockDO::getCount, count.abs());
}
updateWrapper.setSql("count = count - " + count.abs());
}
return update(null, updateWrapper);
}
} }

View File

@ -13,8 +13,10 @@ import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockInItemMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockInMapper; import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockInMapper;
import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO; import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
import cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum;
import cn.iocoder.yudao.module.erp.service.product.ErpProductService; import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService; import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService;
import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockInCreateReqBO;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -54,6 +56,8 @@ public class ErpStockInServiceImpl implements ErpStockInService {
private ErpWarehouseService warehouseService; private ErpWarehouseService warehouseService;
@Resource @Resource
private ErpSupplierService supplierService; private ErpSupplierService supplierService;
@Resource
private ErpStockRecordService stockRecordService;
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@ -102,23 +106,31 @@ public class ErpStockInServiceImpl implements ErpStockInService {
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void updateStockInStatus(Long id, Integer status) { public void updateStockInStatus(Long id, Integer status) {
boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status);
// 1.1 校验存在 // 1.1 校验存在
ErpStockInDO stockIn = validateStockInExists(id); ErpStockInDO stockIn = validateStockInExists(id);
// 1.2 校验状态 // 1.2 校验状态
if (stockIn.getStatus().equals(status)) { if (stockIn.getStatus().equals(status)) {
throw exception(ErpAuditStatus.PROCESS.getStatus().equals(status) ? throw exception(approve ? STOCK_IN_APPROVE_FAIL : STOCK_IN_PROCESS_FAIL);
STOCK_IN_PROCESS_FAIL : STOCK_IN_APPROVE_FAIL);
} }
// 2. 更新状态 // 2. 更新状态
int updateCount = stockInMapper.updateByIdAndStatus(id, stockIn.getStatus(), int updateCount = stockInMapper.updateByIdAndStatus(id, stockIn.getStatus(),
new ErpStockInDO().setStatus(status)); new ErpStockInDO().setStatus(status));
if (updateCount == 0) { if (updateCount == 0) {
throw exception(ErpAuditStatus.PROCESS.getStatus().equals(status) ? throw exception(approve ? STOCK_IN_APPROVE_FAIL : STOCK_IN_PROCESS_FAIL);
STOCK_IN_PROCESS_FAIL : STOCK_IN_APPROVE_FAIL);
} }
// 3. TODO 芋艿调整库存记录 // 3. 变更库存
List<ErpStockInItemDO> stockInItems = stockInItemMapper.selectListByInId(id);
Integer bizType = approve ? ErpStockRecordBizTypeEnum.OTHER_IN.getType()
: ErpStockRecordBizTypeEnum.OTHER_IN_CANCEL.getType();
stockInItems.forEach(stockInItem -> {
BigDecimal count = approve ? stockInItem.getCount() : stockInItem.getCount().negate();
stockRecordService.createStockRecord(new ErpStockInCreateReqBO(
stockInItem.getProductId(), stockInItem.getWarehouseId(), count,
bizType, stockInItem.getInId(), stockInItem.getId(), stockIn.getNo()));
});
} }
private List<ErpStockInItemDO> validateStockInItems(List<ErpStockInSaveReqVO.Item> list) { private List<ErpStockInItemDO> validateStockInItems(List<ErpStockInSaveReqVO.Item> list) {

View File

@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.erp.service.stock;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.record.ErpStockRecordPageReqVO; import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.record.ErpStockRecordPageReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockRecordDO; import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockRecordDO;
import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockInCreateReqBO;
import jakarta.validation.Valid;
/** /**
* ERP 产品库存明细 Service 接口 * ERP 产品库存明细 Service 接口
@ -27,4 +29,11 @@ public interface ErpStockRecordService {
*/ */
PageResult<ErpStockRecordDO> getStockRecordPage(ErpStockRecordPageReqVO pageReqVO); PageResult<ErpStockRecordDO> getStockRecordPage(ErpStockRecordPageReqVO pageReqVO);
/**
* 创建库存明细
*
* @param createReqBO 创建库存明细 BO
*/
void createStockRecord(@Valid ErpStockInCreateReqBO createReqBO);
} }

View File

@ -1,13 +1,18 @@
package cn.iocoder.yudao.module.erp.service.stock; package cn.iocoder.yudao.module.erp.service.stock;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.record.ErpStockRecordPageReqVO; import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.record.ErpStockRecordPageReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockRecordDO; import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockRecordDO;
import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockRecordMapper; import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockRecordMapper;
import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockInCreateReqBO;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
/** /**
* ERP 产品库存明细 Service 实现类 * ERP 产品库存明细 Service 实现类
* *
@ -20,6 +25,9 @@ public class ErpStockRecordServiceImpl implements ErpStockRecordService {
@Resource @Resource
private ErpStockRecordMapper stockRecordMapper; private ErpStockRecordMapper stockRecordMapper;
@Resource
private ErpStockService stockService;
@Override @Override
public ErpStockRecordDO getStockRecord(Long id) { public ErpStockRecordDO getStockRecord(Long id) {
return stockRecordMapper.selectById(id); return stockRecordMapper.selectById(id);
@ -30,4 +38,16 @@ public class ErpStockRecordServiceImpl implements ErpStockRecordService {
return stockRecordMapper.selectPage(pageReqVO); return stockRecordMapper.selectPage(pageReqVO);
} }
@Override
@Transactional(rollbackFor = Exception.class)
public void createStockRecord(ErpStockInCreateReqBO createReqBO) {
// 1. 更新库存
BigDecimal totalCount = stockService.updateStockCountIncrement(
createReqBO.getProductId(), createReqBO.getWarehouseId(), createReqBO.getCount());
// 2. 创建库存明细
ErpStockRecordDO stockRecord = BeanUtils.toBean(createReqBO, ErpStockRecordDO.class)
.setTotalCount(totalCount);
stockRecordMapper.insert(stockRecord);
}
} }

View File

@ -4,6 +4,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock.ErpStockPageReqVO; import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock.ErpStockPageReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO; import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO;
import java.math.BigDecimal;
/** /**
* ERP 产品库存 Service 接口 * ERP 产品库存 Service 接口
* *
@ -36,4 +38,14 @@ public interface ErpStockService {
*/ */
PageResult<ErpStockDO> getStockPage(ErpStockPageReqVO pageReqVO); PageResult<ErpStockDO> getStockPage(ErpStockPageReqVO pageReqVO);
/**
* 增量更新产品库存数量
*
* @param productId 产品编号
* @param warehouseId 仓库编号
* @param count 增量数量正数表示增加负数表示减少
* @return 更新后的库存
*/
BigDecimal updateStockCountIncrement(Long productId, Long warehouseId, BigDecimal count);
} }

View File

@ -8,6 +8,12 @@ import jakarta.annotation.Resource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.STOCK_COUNT_NEGATIVE;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.STOCK_COUNT_NEGATIVE2;
/** /**
* ERP 产品库存 Service 实现类 * ERP 产品库存 Service 实现类
* *
@ -17,6 +23,13 @@ import org.springframework.validation.annotation.Validated;
@Validated @Validated
public class ErpStockServiceImpl implements ErpStockService { public class ErpStockServiceImpl implements ErpStockService {
/**
* 允许库存为负数
*
* TODO 芋艿后续做成 db 配置
*/
private static final Boolean NEGATIVE_STOCK_COUNT_ENABLE = false;
@Resource @Resource
private ErpStockMapper stockMapper; private ErpStockMapper stockMapper;
@ -35,4 +48,27 @@ public class ErpStockServiceImpl implements ErpStockService {
return stockMapper.selectPage(pageReqVO); return stockMapper.selectPage(pageReqVO);
} }
@Override
public BigDecimal updateStockCountIncrement(Long productId, Long warehouseId, BigDecimal count) {
// 1.1 查询当前库存
ErpStockDO stock = stockMapper.selectByProductIdAndWarehouseId(productId, warehouseId);
if (stock == null) {
stock = new ErpStockDO().setProductId(productId).setWarehouseId(warehouseId).setCount(BigDecimal.ZERO);
stockMapper.insert(stock);
}
// 1.2 校验库存是否充足
if (!NEGATIVE_STOCK_COUNT_ENABLE && stock.getCount().add(count).compareTo(BigDecimal.ZERO) < 0) {
throw exception(STOCK_COUNT_NEGATIVE, stock.getCount(), count);
}
// 2. 库存变更
int updateCount = stockMapper.updateCountIncrement(stock.getId(), count, NEGATIVE_STOCK_COUNT_ENABLE);
if (updateCount == 0) {
throw exception(STOCK_COUNT_NEGATIVE2); // 此时不好去查询最新库存所以直接抛出该提示不提供具体库存数字
}
// 3. 返回最新库存
return stock.getCount().add(count);
}
} }

View File

@ -0,0 +1,59 @@
package cn.iocoder.yudao.module.erp.service.stock.bo;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
/**
* 库存明细的创建 Request BO
*
* @author 芋道源码
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ErpStockInCreateReqBO {
/**
* 产品编号
*/
@NotNull(message = "产品编号不能为空")
private Long productId;
/**
* 仓库编号
*/
@NotNull(message = "仓库编号不能为空")
private Long warehouseId;
/**
* 出入库数量
*
* 正数表示入库负数表示出库
*/
@NotNull(message = "出入库数量不能为空")
private BigDecimal count;
/**
* 业务类型
*/
@NotNull(message = "业务类型不能为空")
private Integer bizType;
/**
* 业务编号
*/
@NotNull(message = "业务编号不能为空")
private Long bizId;
/**
* 业务项编号
*/
@NotNull(message = "业务项编号不能为空")
private Long bizItemId;
/**
* 业务单号
*/
@NotNull(message = "业务单号不能为空")
private String bizNo;
}