liding
2 天以前 2656ae9bce8544d81da66c07aaede5386d6fbebb
main-business/src/main/java/com/ruoyi/business/service/impl/SalesRecordServiceImpl.java
@@ -4,6 +4,7 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.entity.CoalInfo;
@@ -17,11 +18,16 @@
import com.ruoyi.business.mapper.OfficialInventoryMapper;
import com.ruoyi.business.mapper.SalesRecordMapper;
import com.ruoyi.business.service.SalesRecordService;
import com.ruoyi.business.vo.SalesRecordExportVo;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.system.mapper.SysUserMapper;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -33,13 +39,9 @@
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAdjusters;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.*;
/**
 * <p>
@@ -50,6 +52,7 @@
 * @since 2025-06-11
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class SalesRecordServiceImpl extends ServiceImpl<SalesRecordMapper, SalesRecord> implements SalesRecordService {
@@ -67,6 +70,29 @@
    public IPage<SalesRecordDto> selectSalesRecordList(Page<SalesRecord> page, SalesRecordDto salesRecordDto) {
        // 1. 创建查询条件,按创建时间倒序排序
        LambdaQueryWrapper<SalesRecord> queryWrapper = new LambdaQueryWrapper<>();
        // 按日期查询
        if (salesRecordDto.getSaleDate() != null) {
            queryWrapper.eq(SalesRecord::getSaleDate, salesRecordDto.getSaleDate());
        }
        // 按煤种名称查询
        if (StringUtils.isNotBlank(salesRecordDto.getCoal())) {
            LambdaQueryWrapper<CoalInfo> coalQueryWrapper = new LambdaQueryWrapper<>();
            coalQueryWrapper.like(CoalInfo::getCoal, salesRecordDto.getCoal());
            List<CoalInfo> coalInfos = coalInfoMapper.selectList(coalQueryWrapper);
            if (!coalInfos.isEmpty()) {
                List<Long> coalIds = coalInfos.stream()
                        .map(CoalInfo::getId)
                        .collect(Collectors.toList());
                queryWrapper.in(SalesRecord::getCoalId, coalIds);
            } else {
                // 如果没有匹配的煤种,直接返回空结果
                queryWrapper.eq(SalesRecord::getCoalId, -1L); // 使用不可能存在的ID
            }
        }
        queryWrapper.orderByDesc(SalesRecord::getCreateTime);
        // 2. 获取分页的销售记录
@@ -122,20 +148,20 @@
        // 参数校验
        validateSalesRecordDto(salesRecordDto);
        // 更新正式库待补库数量
        // 获取煤种库存信息
        OfficialInventory officialInventory = officialInventoryMapper.selectById(salesRecordDto.getCoalId());
        if (officialInventory == null) {
            throw new BaseException("正式库煤种信息不存在");
        }
        if (salesRecordDto.getSaleQuantity().compareTo(officialInventory.getInventoryQuantity()) > 0) {
            throw new BaseException("销售数量不能大于库存数量");
        }
        officialInventory.setInventoryQuantity(officialInventory.getInventoryQuantity().subtract(salesRecordDto.getSaleQuantity()));
        officialInventory.setPendingReplenishment(salesRecordDto.getSaleQuantity());
        officialInventoryMapper.updateById(officialInventory);
        // 处理销售数量变更逻辑
        SalesRecord existingRecord = salesRecordDto.getId() == null ? null : salesRecordMapper.selectById(salesRecordDto.getId());
        handleQuantityChanges(salesRecordDto, officialInventory, existingRecord);
        // 构建销售记录实体
        SalesRecord salesRecord = buildSalesRecord(salesRecordDto, officialInventory.getCoalId());
        // 设置销售记录中的库存数量
        salesRecord.setInventoryQuantity(officialInventory.getInventoryQuantity());
        // 处理新增/更新逻辑
        if (salesRecordDto.getId() == null) {
@@ -145,18 +171,52 @@
        }
    }
    private void validateSalesRecordDto(SalesRecordDto dto) {
        if (dto == null) {
            throw new BaseException("销售记录数据不能为空");
    private void handleQuantityChanges(SalesRecordDto dto, OfficialInventory officialInventory, SalesRecord existingRecord) {
        if (existingRecord == null) {
            // 新增记录
            if (dto.getSaleQuantity().compareTo(officialInventory.getInventoryQuantity()) > 0) {
                throw new BaseException("销售数量不能大于库存数量");
            }
            // 更新库存数量
            officialInventory.setInventoryQuantity(officialInventory.getInventoryQuantity().subtract(dto.getSaleQuantity()));
            // 设置待补库数量
            if (dto.isAdd()) {
                officialInventory.setPendingReplenishment(
                        officialInventory.getPendingReplenishment() == null ?
                                dto.getSaleQuantity() :
                                officialInventory.getPendingReplenishment().add(dto.getSaleQuantity())
                );
            }
        } else {
            // 更新记录
            // 比较销售数量是否有变化
            int quantityComparison = dto.getSaleQuantity().compareTo(existingRecord.getSaleQuantity());
            if (quantityComparison != 0) {
                // 计算数量差值
                BigDecimal quantityDiff = dto.getSaleQuantity().subtract(existingRecord.getSaleQuantity());
                // 检查新数量是否会导致库存不足
                if (quantityComparison > 0 && quantityDiff.compareTo(officialInventory.getInventoryQuantity()) > 0) {
                    throw new BaseException("销售数量增加后不能大于库存数量");
                }
                // 更新库存数量
                officialInventory.setInventoryQuantity(officialInventory.getInventoryQuantity().subtract(quantityDiff));
                // 更新待补库数量(如果是需要补库的记录)
                if (dto.isAdd()) {
                    BigDecimal pendingDiff = officialInventory.getPendingReplenishment() == null ?
                            quantityDiff :
                            officialInventory.getPendingReplenishment().add(quantityDiff);
                    officialInventory.setPendingReplenishment(pendingDiff);
                }
            }
        }
        if (dto.getRegistrantId() == null) {
            throw new BaseException("登记人ID不能为空");
        }
        if (dto.getCustomerId() == null) {
            throw new BaseException("客户ID不能为空");
        }
        if (dto.getCoalId() == null) {
            throw new BaseException("请选择一条煤种信息");
        // 更新库存记录
        int updateResult = officialInventoryMapper.updateById(officialInventory);
        if (updateResult <= 0) {
            throw new BaseException("库存更新失败");
        }
    }
@@ -169,7 +229,7 @@
        if (registrant == null) {
            throw new BaseException("登记人信息不存在");
        }
        record.setRegistrant(registrant.getUserName());
        record.setRegistrant(registrant.getNickName());
        // 设置客户信息
        Customer customer = customerMapper.selectById(dto.getCustomerId());
@@ -200,6 +260,21 @@
        return record;
    }
    private void validateSalesRecordDto(SalesRecordDto dto) {
        if (dto == null) {
            throw new BaseException("销售记录数据不能为空");
        }
        if (dto.getRegistrantId() == null) {
            throw new BaseException("登记人ID不能为空");
        }
        if (dto.getCustomerId() == null) {
            throw new BaseException("客户ID不能为空");
        }
        if (dto.getCoalId() == null) {
            throw new BaseException("请选择一条煤种信息");
        }
    }
    private int insertSalesRecord(SalesRecord record) {
        int result = salesRecordMapper.insert(record);
        if (result <= 0) {
@@ -228,6 +303,59 @@
                .set("deleted", 1);  // 设置 deleted 为 1 表示已删除
        // 执行批量逻辑删除
        return salesRecordMapper.update(null, updateWrapper);
    }
    @Override
    public void salesRecordExport(HttpServletResponse response, SalesRecordDto salesRecordDto) {
        List<Long> ids = salesRecordDto.getExportIds();
        List<SalesRecord> list;
        if (ids != null && !ids.isEmpty()) {
            list = salesRecordMapper.selectByIds(ids);
        } else {
            list = salesRecordMapper.selectList(null);
        }
        List<SalesRecordExportVo> exportData = convertToExportVo(list);
        ExcelUtil<SalesRecordExportVo> util = new ExcelUtil<>(SalesRecordExportVo.class);
        util.exportExcel(response, exportData, "销售出库数据");
    }
    private List<SalesRecordExportVo> convertToExportVo(List<SalesRecord> list) {
        // 1. 提前收集所有需要查询的coalId,避免N+1查询问题
        Set<Long> coalIds = list.stream()
                .filter(Objects::nonNull)
                .map(SalesRecord::getCoalId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        // 2. 批量查询coalInfo数据
        Map<Long, CoalInfo> coalInfoMap = CollectionUtils.isEmpty(coalIds)
                ? Collections.emptyMap()
                : coalInfoMapper.selectByIds(coalIds).stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toMap(CoalInfo::getId, Function.identity()));
        // 3. 转换数据
        return list.stream()
                .filter(Objects::nonNull)
                .map(record -> {
                    try {
                        SalesRecordExportVo vo = new SalesRecordExportVo();
                        // 拷贝基础属性
                        BeanUtils.copyProperties(record, vo);
                        // 设置关联的coal信息
                        Optional.ofNullable(record.getCoalId())
                                .map(coalInfoMap::get)
                                .ifPresent(coalInfo -> vo.setCoal(coalInfo.getCoal()));
                        return vo;
                    } catch (Exception e) {
                        log.error("转换销售记录VO异常,记录ID: {}", record.getId(), e);
                        return null;
                    }
                })
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }
    @Override
@@ -482,7 +610,7 @@
        // 4. 批量查询煤种信息并填充到结果中
        if (!coalIds.isEmpty()) {
            List<CoalInfo> coalInfos = coalInfoMapper.selectBatchIds(coalIds);
            List<CoalInfo> coalInfos = coalInfoMapper.selectByIds(coalIds);
            for (CoalInfo coalInfo : coalInfos) {
                Map<String, Object> record = resultMap.get(coalInfo.getId());
                if (record != null) {
@@ -491,8 +619,33 @@
            }
        }
        // 最终结果是一个List<Map>,每个Map包含合并后的销售数据和对应的煤种信息
        List<Map<String, Object>> results = new ArrayList<>(resultMap.values());
        //月度数据
        //查询所有煤种信息
        List<CoalInfo> allCoalTypes = coalInfoMapper.selectList(
                new QueryWrapper<CoalInfo>().orderByAsc("id")
        );
        //预计算销量:按coalId分组统计总销量
        Map<Long, BigDecimal> salesByCoalId = salesRecords.stream()
                .collect(Collectors.groupingBy(
                        SalesRecord::getCoalId,
                        Collectors.reducing(
                                BigDecimal.ZERO,
                                SalesRecord::getSaleQuantity,
                                BigDecimal::add
                        )
                ));
        // 2. 创建 resultMouth,存储煤种及其销量(Map 结构)
        Map<String, BigDecimal> resultMouthMap = new LinkedHashMap<>();
        for (CoalInfo coal : allCoalTypes) {
            resultMouthMap.put(
                    coal.getCoal(),  // 煤种名称(如 "无烟煤")
                    salesByCoalId.getOrDefault(coal.getId(), BigDecimal.valueOf(0)) // 销量
            );
        }
        result.put("revenueAmount", revenueAmount.setScale(2, RoundingMode.HALF_UP));
        result.put("changeRate", changeRate);
        result.put("trend", trend);
@@ -501,6 +654,7 @@
        result.put("trendQuantity", trendQuantity);
        result.put("revenueDistribution", revenueDistribution);
        result.put("salesResults", results);
        result.put("resultMouth", resultMouthMap);
        return result;
    }