Files
klp-oa/klp-wms/src/main/java/com/klp/service/impl/WmsStockIoServiceImpl.java
Joshi 8179284126 refactor(wms): 优化退库操作功能
- 修改退库操作接口路径,简化为 /returnStock
- 移除退库操作日志记录功能
2025-08-12 11:19:29 +08:00

472 lines
20 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.klp.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.klp.common.core.page.TableDataInfo;
import com.klp.common.core.domain.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.klp.common.utils.StringUtils;
import com.klp.domain.*;
import com.klp.domain.vo.WmsStockIoDetailVo;
import com.klp.mapper.*;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import com.klp.domain.bo.WmsStockIoBo;
import com.klp.domain.vo.WmsStockIoVo;
import com.klp.service.IWmsStockIoService;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.*;
import com.klp.common.exception.ServiceException;
import com.klp.domain.bo.WmsStockIoWithDetailBo;
import com.klp.domain.bo.WmsStockIoDetailBo;
import javax.annotation.Resource;
/**
* 出入库单主Service业务层处理
*
* @author Joshi
* @date 2025-07-18
*/
@RequiredArgsConstructor
@Service
public class WmsStockIoServiceImpl implements IWmsStockIoService {
private final WmsStockIoMapper baseMapper;
private final WmsStockIoDetailMapper stockIoDetailMapper;
private final WmsStockMapper stockMapper;
private final WmsProductMapper productMapper;
private final WmsRawMaterialMapper rawMaterialMapper;
private final WmsWarehouseMapper warehouseMapper;
@Resource
private WmsStockLogMapper stockLogMapper;
/**
* 查询出入库单主
*/
@Override
public WmsStockIoVo queryById(Long stockIoId){
return baseMapper.selectVoById(stockIoId);
}
/**
* 查询出入库单主列表
*/
@Override
public TableDataInfo<WmsStockIoVo> queryPageList(WmsStockIoBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<WmsStockIo> lqw = buildQueryWrapper(bo);
Page<WmsStockIoVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询出入库单主列表
*/
@Override
public List<WmsStockIoVo> queryList(WmsStockIoBo bo) {
LambdaQueryWrapper<WmsStockIo> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<WmsStockIo> buildQueryWrapper(WmsStockIoBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<WmsStockIo> lqw = Wrappers.lambdaQuery();
lqw.eq(StringUtils.isNotBlank(bo.getStockIoCode()), WmsStockIo::getStockIoCode, bo.getStockIoCode());
lqw.eq(StringUtils.isNotBlank(bo.getIoType()), WmsStockIo::getIoType, bo.getIoType());
lqw.eq(StringUtils.isNotBlank(bo.getBizType()), WmsStockIo::getBizType, bo.getBizType());
lqw.eq(bo.getStatus() != null, WmsStockIo::getStatus, bo.getStatus());
return lqw;
}
/**
* 新增出入库单主
*/
@Override
public Boolean insertByBo(WmsStockIoBo bo) {
WmsStockIo add = BeanUtil.toBean(bo, WmsStockIo.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setStockIoId(add.getStockIoId());
}
return flag;
}
/**
* 修改出入库单主
*/
@Override
public Boolean updateByBo(WmsStockIoBo bo) {
WmsStockIo update = BeanUtil.toBean(bo, WmsStockIo.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(WmsStockIo entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 批量删除出入库单主
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 批量新增主表和明细
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void addWithDetail(WmsStockIoWithDetailBo bo) {
// 插入主表
WmsStockIo stockIo = BeanUtil.toBean(bo, WmsStockIo.class);
stockIo.setParentId(bo.getParentId());
baseMapper.insert(stockIo);
// 插入明细
if (bo.getDetails() != null && !bo.getDetails().isEmpty()) {
for (WmsStockIoDetailBo detailBo : bo.getDetails()) {
WmsStockIoDetail detail = BeanUtil.toBean(detailBo, WmsStockIoDetail.class);
detail.setStockIoId(stockIo.getStockIoId());
stockIoDetailMapper.insert(detail);
}
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean scanInStockByBo(WmsStockIoDetailBo bo) {
String unit = bo.getUnit();
// 如果unit为空自动查item表补全
if (unit == null || unit.trim().isEmpty()) {
if ("product".equals(bo.getItemType())) {
WmsProduct p = productMapper.selectById(bo.getItemId());
unit = p != null ? p.getUnit() : null;
} else if ("raw_material".equals(bo.getItemType())) {
WmsRawMaterial r = rawMaterialMapper.selectById(bo.getItemId());
unit = r != null ? r.getUnit() : null;
}
}
if (unit == null || unit.trim().isEmpty()) {
throw new RuntimeException("未能获取到单位");
}
// 入库操作
changeStock(bo.getWarehouseId(), bo.getItemType(), bo.getItemId(), bo.getQuantity(), true, unit);
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean scanOutStockByBo(WmsStockIoDetailBo bo) {
String unit = bo.getUnit();
// 如果unit为空自动查item表补全
if (unit == null || unit.trim().isEmpty()) {
if ("product".equals(bo.getItemType())) {
WmsProduct p = productMapper.selectById(bo.getItemId());
unit = p != null ? p.getUnit() : null;
} else if ("raw_material".equals(bo.getItemType())) {
WmsRawMaterial r = rawMaterialMapper.selectById(bo.getItemId());
unit = r != null ? r.getUnit() : null;
}
}
if (unit == null || unit.trim().isEmpty()) {
throw new RuntimeException("未能获取到单位");
}
// 出库操作
changeStock(bo.getWarehouseId(), bo.getItemType(), bo.getItemId(), bo.getQuantity(), false, unit);
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean scanReturnStockByBo(WmsStockIoBo bo) {
// 退库操作根据主表ID查询明细列表验证退库数量执行退库
if (bo.getStockIoId() == null) {
throw new ServiceException("退库单ID不能为空");
}
// 1. 获取退库单主表信息
WmsStockIo returnStockIo = baseMapper.selectById(bo.getStockIoId());
if (returnStockIo == null || returnStockIo.getParentId() == null) {
throw new ServiceException("退库单不存在或未关联原出库单");
}
// 2. 获取退库单明细列表
List<WmsStockIoDetail> returnDetails = stockIoDetailMapper.selectList(
Wrappers.<WmsStockIoDetail>lambdaQuery().eq(WmsStockIoDetail::getStockIoId, bo.getStockIoId())
);
if (returnDetails == null || returnDetails.isEmpty()) {
throw new ServiceException("退库单明细不能为空");
}
// 3. 获取原出库单明细列表
List<WmsStockIoDetail> originalOutDetails = stockIoDetailMapper.selectList(
Wrappers.<WmsStockIoDetail>lambdaQuery().eq(WmsStockIoDetail::getStockIoId, returnStockIo.getParentId())
);
if (originalOutDetails == null || originalOutDetails.isEmpty()) {
throw new ServiceException("原出库单明细不存在");
}
// 4. 遍历退库明细,验证数量并执行退库
for (WmsStockIoDetail returnDetail : returnDetails) {
// 查找对应的原出库明细
WmsStockIoDetail originalOutDetail = originalOutDetails.stream()
.filter(detail -> detail.getItemType().equals(returnDetail.getItemType()) &&
detail.getItemId().equals(returnDetail.getItemId()) &&
detail.getWarehouseId().equals(returnDetail.getWarehouseId()))
.findFirst()
.orElse(null);
if (originalOutDetail == null) {
throw new ServiceException("未找到对应的原出库明细:" + returnDetail.getItemType() + "-" + returnDetail.getItemId());
}
// 验证退库数量不能超过原出库数量
BigDecimal totalReturnedQty = baseMapper.selectList(
Wrappers.<WmsStockIo>lambdaQuery()
.eq(WmsStockIo::getParentId, returnStockIo.getParentId())
.eq(WmsStockIo::getIoType, "withdraw")
.ne(WmsStockIo::getStockIoId, returnStockIo.getStockIoId())
).stream()
.flatMap(returnStockIoItem -> stockIoDetailMapper.selectList(
Wrappers.<WmsStockIoDetail>lambdaQuery()
.eq(WmsStockIoDetail::getStockIoId, returnStockIoItem.getStockIoId())
.eq(WmsStockIoDetail::getItemType, returnDetail.getItemType())
.eq(WmsStockIoDetail::getItemId, returnDetail.getItemId())
.eq(WmsStockIoDetail::getWarehouseId, returnDetail.getWarehouseId())
).stream())
.map(WmsStockIoDetail::getQuantity)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal remainingQty = originalOutDetail.getQuantity().subtract(totalReturnedQty);
if (returnDetail.getQuantity().compareTo(remainingQty) > 0) {
throw new ServiceException("退库数量超过原出库数量,物品:" + returnDetail.getItemType() + "-" + returnDetail.getItemId() +
",原出库数量:" + originalOutDetail.getQuantity() + ",已退库数量:" + totalReturnedQty +
",剩余可退数量:" + remainingQty + ",本次退库数量:" + returnDetail.getQuantity());
}
// 执行退库操作(增加库存)
String unit = returnDetail.getUnit();
if (unit == null || unit.trim().isEmpty()) {
if ("product".equals(returnDetail.getItemType())) {
WmsProduct p = productMapper.selectById(returnDetail.getItemId());
unit = p != null ? p.getUnit() : "";
} else if ("raw_material".equals(returnDetail.getItemType())) {
WmsRawMaterial r = rawMaterialMapper.selectById(returnDetail.getItemId());
unit = r != null ? r.getUnit() : "";
}
}
// 执行入库操作(退库就是入库)
changeStock(returnDetail.getWarehouseId(), returnDetail.getItemType(),
returnDetail.getItemId(), returnDetail.getQuantity(), true, unit);
}
// 更新单据状态为已审核2
returnStockIo.setStatus(2);
return baseMapper.updateById(returnStockIo) > 0;
}
/**
* 审核出入库/移库单,变更库存,含库存校验
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean auditStockIo(Long stockIoId) {
WmsStockIo stockIo = baseMapper.selectById(stockIoId);
if (stockIo == null || stockIo.getStatus() == null || stockIo.getStatus() != 1) {
// 只允许审核“已提交”状态的单据
throw new RuntimeException("单据不存在或状态不允许审核");
}
List<WmsStockIoDetail> details = stockIoDetailMapper.selectList(
Wrappers.<WmsStockIoDetail>lambdaQuery().eq(WmsStockIoDetail::getStockIoId, stockIoId)
);
if (details == null || details.isEmpty()) {
throw new RuntimeException("单据明细不能为空");
}
for (WmsStockIoDetail detail : details) {
String ioType = stockIo.getIoType();
if ("in".equals(ioType)) {
// 入库:目标库位库存增加
changeStock(detail.getWarehouseId(), detail.getItemType(), detail.getItemId(), detail.getQuantity(), true, detail.getUnit());
} else if ("out".equals(ioType)) {
// 出库:目标库位库存减少
changeStock(detail.getWarehouseId(), detail.getItemType(), detail.getItemId(), detail.getQuantity(), false, detail.getUnit());
} else if ("transfer".equals(ioType)) {
// 移库fromWarehouseId减少warehouseId增加
if (detail.getFromWarehouseId() == null) {
throw new RuntimeException("移库明细缺少源库位ID");
}
// 先减少源库位
changeStock(detail.getFromWarehouseId(), detail.getItemType(), detail.getItemId(), detail.getQuantity(), false, detail.getUnit());
// 再增加目标库位
changeStock(detail.getWarehouseId(), detail.getItemType(), detail.getItemId(), detail.getQuantity(), true, detail.getUnit());
} else {
throw new RuntimeException("未知的出入库类型");
}
}
// 更新单据状态为已审核2
stockIo.setStatus(2);
return baseMapper.updateById(stockIo) > 0;
}
/**
* 撤销出入库/移库单,库存回滚
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean cancelStockIo(Long stockIoId) {
WmsStockIo stockIo = baseMapper.selectById(stockIoId);
if (stockIo == null || stockIo.getStatus() == null || stockIo.getStatus() != 2) {
throw new ServiceException("只能撤销已审核的单据");
}
List<WmsStockIoDetail> details = stockIoDetailMapper.selectList(
Wrappers.<WmsStockIoDetail>lambdaQuery().eq(WmsStockIoDetail::getStockIoId, stockIoId)
);
if (details == null || details.isEmpty()) {
throw new ServiceException("单据明细不能为空");
}
for (WmsStockIoDetail detail : details) {
String ioType = stockIo.getIoType();
if ("in".equals(ioType)) {
// 入库撤销:目标库位库存减少
// changeStock(detail.getWarehouseId(), detail.getItemType(), detail.getItemId(), detail.getBatchNo(), detail.getQuantity(), false, detail.getUnit());
changeStock(detail.getWarehouseId(), detail.getItemType(), detail.getItemId(), detail.getQuantity(), false, detail.getUnit());
} else if ("out".equals(ioType)) {
// 出库撤销:目标库位库存增加
changeStock(detail.getWarehouseId(), detail.getItemType(), detail.getItemId(), detail.getQuantity(), true, detail.getUnit());
} else if ("transfer".equals(ioType)) {
if (detail.getFromWarehouseId() == null) {
throw new ServiceException("移库明细缺少源库位ID");
}
// 源库位库存增加
changeStock(detail.getFromWarehouseId(), detail.getItemType(), detail.getItemId(), detail.getQuantity(), true, detail.getUnit());
// 目标库位库存减少
changeStock(detail.getWarehouseId(), detail.getItemType(), detail.getItemId(), detail.getQuantity(), false, detail.getUnit());
} else {
throw new ServiceException("未知的出入库类型");
}
}
// 更新单据状态为待审核1
stockIo.setStatus(1);
baseMapper.updateById(stockIo);
return true;
}
/**
* 根据ioType和stockIoId联查明细
*/
@Override
public List<WmsStockIoDetailVo> detailByTypeAndId(String ioType, Long stockIoId) {
WmsStockIo stockIo = baseMapper.selectById(stockIoId);
if (stockIo == null) {
throw new ServiceException("单据不存在");
}
if (!ioType.equals(stockIo.getIoType())) {
throw new ServiceException("单据类型不匹配");
}
List<WmsStockIoDetail> details = stockIoDetailMapper.selectList(
Wrappers.<WmsStockIoDetail>lambdaQuery().eq(WmsStockIoDetail::getStockIoId, stockIoId)
);
if (details == null || details.isEmpty()) {
return java.util.Collections.emptyList();
}
// 转VO
List<WmsStockIoDetailVo> voList = new ArrayList<>();
for (WmsStockIoDetail detail : details) {
WmsStockIoDetailVo vo = new WmsStockIoDetailVo();
BeanUtils.copyProperties(detail, vo);
voList.add(vo);
}
return voList;
}
/**
* 更新出入库单状态
*/
@Override
public Boolean updateStatus(Long stockIoId, Integer status) {
WmsStockIo stockIo = baseMapper.selectById(stockIoId);
if (stockIo == null) {
throw new ServiceException("单据不存在");
}
// 状态流转验证
if (stockIo.getStatus() == 0 && status == 1) {
// 草稿 -> 已提交:需要检查是否有明细
List<WmsStockIoDetail> details = stockIoDetailMapper.selectList(
Wrappers.<WmsStockIoDetail>lambdaQuery().eq(WmsStockIoDetail::getStockIoId, stockIoId)
);
if (details == null || details.isEmpty()) {
throw new ServiceException("单据明细不能为空,无法提交");
}
} else if (stockIo.getStatus() == 1 && status == 0) {
// 已提交 -> 草稿:允许回退
} else {
throw new ServiceException("状态流转不允许");
}
stockIo.setStatus(status);
return baseMapper.updateById(stockIo) > 0;
}
/**
* 库存增减isAdd=true为增加false为减少减少时校验库存是否足够
*/
private void changeStock(Long warehouseId, String itemType, Long itemId, BigDecimal quantity, boolean isAdd, String unit) {
WmsStock stock = stockMapper.selectOne(Wrappers.<WmsStock>lambdaQuery()
.eq(WmsStock::getWarehouseId, warehouseId)
.eq(WmsStock::getItemType, itemType)
.eq(WmsStock::getItemId, itemId)
// .eq(WmsStock::getBatchNo, batchNo)
.last("limit 1"));
if (stock == null) {
if (!isAdd) {
throw new RuntimeException("库存不足,无法出库/移库");
}
// 新增库存记录
stock = new WmsStock();
stock.setWarehouseId(warehouseId);
stock.setItemType(itemType);
stock.setItemId(itemId);
stock.setBatchNo("B-100");
stock.setQuantity(quantity);
stock.setUnit(unit);
stockMapper.insert(stock);
} else {
BigDecimal newQty = isAdd ? stock.getQuantity().add(quantity) : stock.getQuantity().subtract(quantity);
if (newQty.compareTo(BigDecimal.ZERO) < 0) {
throw new RuntimeException("库存不足,无法出库/移库");
}
stock.setQuantity(newQty);
stockMapper.updateById(stock);
}
// 记录库存变更日志
WmsStockLog log = new WmsStockLog();
log.setWarehouseId(warehouseId);
log.setItemType(itemType);
log.setItemId(itemId);
// 变动数量(正=入库,负=出库)
log.setChangeQty(isAdd ? quantity : quantity.negate());
// 变动后的库存数量
log.setAfterQty(stock.getQuantity());
// 变动类型(入库/出库等)
log.setChangeType(isAdd ? "入库" : "出库");
log.setChangeTime(new Date());
stockLogMapper.insert(log);
}
}