package com.ruoyi.stock.service.impl;
|
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.ruoyi.basic.mapper.ProductMapper;
|
import com.ruoyi.basic.mapper.ProductModelMapper;
|
import com.ruoyi.basic.pojo.Product;
|
import com.ruoyi.basic.pojo.ProductModel;
|
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
|
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
|
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.framework.web.domain.R;
|
import com.ruoyi.stock.dto.StockInventoryDto;
|
import com.ruoyi.stock.mapper.StockCheckDetailMapper;
|
import com.ruoyi.stock.mapper.StockCheckMainMapper;
|
import com.ruoyi.stock.mapper.StockInventoryMapper;
|
import com.ruoyi.stock.pojo.StockCheckDetail;
|
import com.ruoyi.stock.pojo.StockCheckMain;
|
import com.ruoyi.stock.pojo.StockInventory;
|
import com.ruoyi.stock.service.StockCheckService;
|
import com.ruoyi.stock.service.StockInventoryService;
|
import lombok.RequiredArgsConstructor;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.math.BigDecimal;
|
import java.time.LocalDate;
|
import java.time.format.DateTimeFormatter;
|
import java.util.ArrayList;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.stream.Collectors;
|
|
@Service
|
@RequiredArgsConstructor
|
@Transactional(rollbackFor = Exception.class)
|
public class StockCheckServiceImpl implements StockCheckService {
|
|
private final StockCheckMainMapper stockCheckMainMapper;
|
private final StockCheckDetailMapper stockCheckDetailMapper;
|
private final StockInventoryMapper stockInventoryMapper;
|
private final StockInventoryService stockInventoryService;
|
private final ProductModelMapper productModelMapper;
|
private final ProductMapper productMapper;
|
|
@Override
|
public R start(StockCheckMain main, List<Long> productModelIds) {
|
if (main.getCheckName() == null || main.getCheckName().trim().isEmpty()) {
|
throw new ServiceException("盘点名称不能为空");
|
}
|
List<StockInventory> inventories = stockInventoryMapper.selectList(
|
Wrappers.<StockInventory>lambdaQuery()
|
.in(productModelIds != null && !productModelIds.isEmpty(),
|
StockInventory::getProductModelId,
|
productModelIds == null ? List.of() : productModelIds));
|
if (inventories.isEmpty()) {
|
throw new ServiceException("没有可盘点的库存记录");
|
}
|
//同一产品规格不允许同时在两张盘点中
|
List<Long> modelIds = inventories.stream().map(StockInventory::getProductModelId)
|
.filter(Objects::nonNull).distinct().collect(Collectors.toList());
|
List<Long> pendingIds = stockCheckDetailMapper.selectList(
|
Wrappers.<StockCheckDetail>lambdaQuery()
|
.select(StockCheckDetail::getProductModelId)
|
.in(StockCheckDetail::getProductModelId, modelIds)
|
.eq(StockCheckDetail::getAdjusted, 0))
|
.stream().map(StockCheckDetail::getProductModelId).distinct().collect(Collectors.toList());
|
List<Long> conflictModelIds = new ArrayList<>();
|
if (!pendingIds.isEmpty()) {
|
List<StockCheckMain> pendingMains = stockCheckMainMapper.selectList(
|
Wrappers.<StockCheckMain>lambdaQuery()
|
.select(StockCheckMain::getId)
|
.in(StockCheckMain::getStatus, 0));
|
if (!pendingMains.isEmpty()) {
|
List<Long> pendingMainIds = pendingMains.stream().map(StockCheckMain::getId).collect(Collectors.toList());
|
conflictModelIds = stockCheckDetailMapper.selectList(
|
Wrappers.<StockCheckDetail>lambdaQuery()
|
.select(StockCheckDetail::getProductModelId)
|
.in(StockCheckDetail::getMainId, pendingMainIds)
|
.in(StockCheckDetail::getProductModelId, pendingIds))
|
.stream().map(StockCheckDetail::getProductModelId).distinct().collect(Collectors.toList());
|
}
|
}
|
if (!conflictModelIds.isEmpty()) {
|
throw new ServiceException("以下产品规格存在盘点中的单据,请先完成或取消:" + joinModels(conflictModelIds));
|
}
|
|
main.setCheckNo(generateCheckNo());
|
main.setStatus(0);
|
if (main.getCheckDate() == null) {
|
main.setCheckDate(LocalDate.now());
|
}
|
main.setTotalKinds(inventories.size());
|
main.setDiffKinds(0);
|
stockCheckMainMapper.insert(main);
|
|
Map<Long, ProductModel> modelMap = loadModels(inventories);
|
Map<Long, Product> productMap = loadProducts(modelMap.values());
|
for (StockInventory inv : inventories) {
|
StockCheckDetail detail = new StockCheckDetail();
|
detail.setMainId(main.getId());
|
detail.setInventoryId(inv.getId());
|
detail.setProductModelId(inv.getProductModelId());
|
ProductModel pm = modelMap.get(inv.getProductModelId());
|
if (pm != null) {
|
detail.setProductModel(pm.getModel());
|
detail.setUnit(pm.getUnit());
|
Product p = productMap.get(pm.getProductId());
|
if (p != null) {
|
detail.setProductName(p.getProductName());
|
}
|
}
|
detail.setBatchNo(inv.getBatchNo());
|
detail.setBookQty(inv.getQualitity());
|
detail.setAdjusted(0);
|
stockCheckDetailMapper.insert(detail);
|
}
|
return R.ok(main.getId());
|
}
|
|
@Override
|
public IPage<StockCheckMain> listPage(Page<StockCheckMain> page, StockCheckMain query) {
|
return stockCheckMainMapper.selectPage(page, Wrappers.<StockCheckMain>lambdaQuery()
|
.like(query.getCheckNo() != null && !query.getCheckNo().isEmpty(),
|
StockCheckMain::getCheckNo, query.getCheckNo())
|
.like(query.getCheckName() != null && !query.getCheckName().isEmpty(),
|
StockCheckMain::getCheckName, query.getCheckName())
|
.eq(query.getStatus() != null, StockCheckMain::getStatus, query.getStatus())
|
.ge(query.getBeginTime() != null, StockCheckMain::getCreateTime, query.getBeginTime())
|
.le(query.getEndTime() != null, StockCheckMain::getCreateTime, query.getEndTime())
|
.orderByDesc(StockCheckMain::getId));
|
}
|
|
@Override
|
public StockCheckMain getMain(Long id) {
|
StockCheckMain main = stockCheckMainMapper.selectById(id);
|
if (main == null) {
|
throw new ServiceException("盘点单不存在");
|
}
|
return main;
|
}
|
|
@Override
|
public IPage<StockCheckDetail> detailPage(Page<StockCheckDetail> page, Long mainId, Boolean onlyDiff) {
|
getMain(mainId);
|
return stockCheckDetailMapper.selectPage(page, Wrappers.<StockCheckDetail>lambdaQuery()
|
.eq(StockCheckDetail::getMainId, mainId)
|
.isNotNull(Boolean.TRUE.equals(onlyDiff), StockCheckDetail::getActualQty)
|
.apply(Boolean.TRUE.equals(onlyDiff),
|
"actual_qty is not null and ifnull(actual_qty,0) <> ifnull(book_qty,0)")
|
.orderByAsc(StockCheckDetail::getId));
|
}
|
|
@Override
|
public R entry(List<StockCheckDetail> details) {
|
if (details == null || details.isEmpty()) {
|
throw new ServiceException("请传入实盘数据");
|
}
|
for (StockCheckDetail item : details) {
|
if (item.getId() == null || item.getActualQty() == null
|
|| item.getActualQty().compareTo(BigDecimal.ZERO) < 0) {
|
throw new ServiceException("实盘数据错误:id与实盘数量(>=0)必传");
|
}
|
StockCheckDetail db = stockCheckDetailMapper.selectById(item.getId());
|
if (db == null) {
|
throw new ServiceException("盘点明细不存在,ID=" + item.getId());
|
}
|
StockCheckMain main = getMain(db.getMainId());
|
if (!Integer.valueOf(0).equals(main.getStatus())) {
|
throw new ServiceException("盘点单[" + main.getCheckNo() + "]已结束,不能再录入");
|
}
|
StockCheckDetail update = new StockCheckDetail();
|
update.setId(db.getId());
|
update.setActualQty(item.getActualQty());
|
update.setDiffQty(item.getActualQty().subtract(
|
db.getBookQty() == null ? BigDecimal.ZERO : db.getBookQty()));
|
if (item.getRemark() != null) {
|
update.setRemark(item.getRemark());
|
}
|
stockCheckDetailMapper.updateById(update);
|
}
|
return R.ok();
|
}
|
|
@Override
|
public R finish(Long id) {
|
StockCheckMain main = getMain(id);
|
if (!Integer.valueOf(0).equals(main.getStatus())) {
|
throw new ServiceException("仅盘点中的单据可以完成");
|
}
|
List<StockCheckDetail> details = stockCheckDetailMapper.selectList(
|
Wrappers.<StockCheckDetail>lambdaQuery().eq(StockCheckDetail::getMainId, id));
|
if (details.isEmpty()) {
|
throw new ServiceException("盘点明细为空");
|
}
|
List<StockCheckDetail> notEntered = details.stream()
|
.filter(d -> d.getActualQty() == null).collect(Collectors.toList());
|
if (!notEntered.isEmpty()) {
|
throw new ServiceException("还有 " + notEntered.size() + " 条明细未录入实盘数量,请先录满");
|
}
|
int diffKinds = 0;
|
int adjusted = 0;
|
for (StockCheckDetail detail : details) {
|
BigDecimal diff = detail.getDiffQty() == null ? BigDecimal.ZERO : detail.getDiffQty();
|
if (diff.compareTo(BigDecimal.ZERO) == 0) {
|
continue;
|
}
|
diffKinds++;
|
StockInventoryDto dto = new StockInventoryDto();
|
dto.setRecordId(detail.getId());
|
dto.setProductModelId(detail.getProductModelId());
|
dto.setBatchNo(detail.getBatchNo());
|
dto.setQualitity(diff.abs());
|
dto.setRemark("盘点调账 " + main.getCheckNo());
|
if (diff.compareTo(BigDecimal.ZERO) > 0) {
|
//盘盈:入库
|
dto.setRecordType(StockInQualifiedRecordTypeEnum.CUSTOMIZATION_STOCK_IN.getCode());
|
stockInventoryService.addStockInRecordOnly(dto);
|
} else {
|
//盘亏:出库(超可用库存会被出库校验拦截)
|
dto.setRecordType(StockOutQualifiedRecordTypeEnum.CUSTOMIZATION_STOCK_OUT.getCode());
|
stockInventoryService.addStockOutRecordOnly(dto);
|
}
|
StockCheckDetail update = new StockCheckDetail();
|
update.setId(detail.getId());
|
update.setAdjusted(1);
|
stockCheckDetailMapper.updateById(update);
|
adjusted++;
|
}
|
StockCheckMain update = new StockCheckMain();
|
update.setId(id);
|
update.setStatus(1);
|
update.setDiffKinds(diffKinds);
|
stockCheckMainMapper.updateById(update);
|
return R.ok("盘点完成:差异 " + diffKinds + " 条,已生成调账单据 " + adjusted
|
+ " 条(待出入库记录审核通过后账面生效)");
|
}
|
|
@Override
|
public R cancel(Long id) {
|
StockCheckMain main = getMain(id);
|
if (!Integer.valueOf(0).equals(main.getStatus())) {
|
throw new ServiceException("仅盘点中的单据可以取消");
|
}
|
StockCheckMain update = new StockCheckMain();
|
update.setId(id);
|
update.setStatus(2);
|
stockCheckMainMapper.updateById(update);
|
return R.ok();
|
}
|
|
@Override
|
public R delete(List<Long> ids) {
|
if (ids == null || ids.isEmpty()) {
|
throw new ServiceException("请选择至少一条数据");
|
}
|
stockCheckDetailMapper.delete(Wrappers.<StockCheckDetail>lambdaQuery()
|
.in(StockCheckDetail::getMainId, ids));
|
stockCheckMainMapper.deleteBatchIds(ids);
|
return R.ok();
|
}
|
|
@Override
|
public List<StockCheckDetail> diffList(Long mainId) {
|
getMain(mainId);
|
return stockCheckDetailMapper.selectList(Wrappers.<StockCheckDetail>lambdaQuery()
|
.eq(StockCheckDetail::getMainId, mainId)
|
.isNotNull(StockCheckDetail::getActualQty)
|
.apply("ifnull(actual_qty,0) <> ifnull(book_qty,0)")
|
.orderByAsc(StockCheckDetail::getId));
|
}
|
|
@Override
|
public List<StockCheckMain> listForExport(StockCheckMain query) {
|
return stockCheckMainMapper.selectList(Wrappers.<StockCheckMain>lambdaQuery()
|
.like(query != null && query.getCheckNo() != null && !query.getCheckNo().isEmpty(),
|
StockCheckMain::getCheckNo, query == null ? null : query.getCheckNo())
|
.like(query != null && query.getCheckName() != null && !query.getCheckName().isEmpty(),
|
StockCheckMain::getCheckName, query == null ? null : query.getCheckName())
|
.eq(query != null && query.getStatus() != null,
|
StockCheckMain::getStatus, query == null ? null : query.getStatus())
|
.orderByDesc(StockCheckMain::getId));
|
}
|
|
private String generateCheckNo() {
|
String prefix = "PD" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
|
StockCheckMain latest = stockCheckMainMapper.selectOne(Wrappers.<StockCheckMain>lambdaQuery()
|
.select(StockCheckMain::getCheckNo)
|
.likeRight(StockCheckMain::getCheckNo, prefix)
|
.orderByDesc(StockCheckMain::getCheckNo)
|
.last("limit 1"), false);
|
int sequence = 1;
|
if (latest != null && latest.getCheckNo() != null && latest.getCheckNo().startsWith(prefix)) {
|
try {
|
sequence = Integer.parseInt(latest.getCheckNo().substring(prefix.length())) + 1;
|
} catch (NumberFormatException ignored) {
|
sequence = 1;
|
}
|
}
|
return prefix + String.format("%03d", sequence);
|
}
|
|
private Map<Long, ProductModel> loadModels(List<StockInventory> inventories) {
|
List<Long> ids = inventories.stream().map(StockInventory::getProductModelId)
|
.filter(Objects::nonNull).distinct().collect(Collectors.toList());
|
if (ids.isEmpty()) {
|
return new HashMap<>();
|
}
|
return productModelMapper.selectBatchIds(ids).stream()
|
.collect(Collectors.toMap(ProductModel::getId, m -> m, (a, b) -> a));
|
}
|
|
private Map<Long, Product> loadProducts(java.util.Collection<ProductModel> models) {
|
List<Long> ids = models.stream().map(ProductModel::getProductId)
|
.filter(Objects::nonNull).distinct().collect(Collectors.toList());
|
if (ids.isEmpty()) {
|
return new HashMap<>();
|
}
|
return productMapper.selectBatchIds(ids).stream()
|
.collect(Collectors.toMap(Product::getId, p -> p, (a, b) -> a));
|
}
|
|
private String joinModels(List<Long> modelIds) {
|
Map<Long, ProductModel> modelMap = productModelMapper.selectBatchIds(modelIds).stream()
|
.collect(Collectors.toMap(ProductModel::getId, m -> m, (a, b) -> a));
|
return modelIds.stream()
|
.map(id -> {
|
ProductModel pm = modelMap.get(id);
|
return pm == null || pm.getModel() == null ? String.valueOf(id) : pm.getModel();
|
})
|
.collect(Collectors.joining("、"));
|
}
|
}
|