liding
2 天以前 f6f57ba70679a0b050031f3cdf81b5bf5d4cbd60
main-business/src/main/java/com/ruoyi/business/service/impl/SalesRecordServiceImpl.java
@@ -1,13 +1,17 @@
package com.ruoyi.business.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.entity.CoalInfo;
import com.ruoyi.basic.entity.Customer;
import com.ruoyi.basic.mapper.CoalInfoMapper;
import com.ruoyi.basic.mapper.CustomerMapper;
import com.ruoyi.business.dto.SalesRecordDto;
import com.ruoyi.business.dto.YearlyQueryDto;
import com.ruoyi.business.entity.OfficialInventory;
import com.ruoyi.business.entity.SalesRecord;
import com.ruoyi.business.mapper.OfficialInventoryMapper;
@@ -15,13 +19,28 @@
import com.ruoyi.business.service.SalesRecordService;
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.system.mapper.SysUserMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.time.LocalDate;
import java.time.YearMonth;
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.function.Function;
import java.util.stream.Collectors;
import java.util.*;
/**
 * <p>
@@ -43,12 +62,86 @@
    private final OfficialInventoryMapper officialInventoryMapper;
    private final CoalInfoMapper coalInfoMapper;
    @Override
    public IPage<SalesRecord> selectSalesRecordList(Page page, SalesRecordDto salesRecordDto) {
    public IPage<SalesRecordDto> selectSalesRecordList(Page<SalesRecord> page, SalesRecordDto salesRecordDto) {
        // 1. 创建查询条件,按创建时间倒序排序
        LambdaQueryWrapper<SalesRecord> queryWrapper = new LambdaQueryWrapper<>();
        if (StringUtils.isNotBlank(salesRecordDto.getSearchAll())) {
            String searchValue = salesRecordDto.getSearchAll();
            // 1. 先尝试作为日期查询
            try {
                LocalDate saleDate = LocalDate.parse(searchValue);
                queryWrapper.eq(SalesRecord::getSaleDate, saleDate);
            } catch (DateTimeParseException e) {
                // 2. 如果不是日期,则作为煤种名称查询
                LambdaQueryWrapper<CoalInfo> coalQueryWrapper = new LambdaQueryWrapper<>();
                coalQueryWrapper.like(CoalInfo::getCoal, searchValue);
                List<CoalInfo> coalInfos = coalInfoMapper.selectList(coalQueryWrapper);
                if (!coalInfos.isEmpty()) {
                    // 提取所有匹配的煤种ID
                    List<Long> coalIds = coalInfos.stream()
                            .map(CoalInfo::getId)
                            .collect(Collectors.toList());
                    // 使用in查询匹配任意一个煤种ID
                    queryWrapper.in(SalesRecord::getCoalId, coalIds);
                } else {
                    // 3. 如果找不到煤种,可以返回空结果
                    queryWrapper.eq(SalesRecord::getCoalId, "-1"); // 使用不可能存在的ID
                }
            }
        }
        queryWrapper.orderByDesc(SalesRecord::getCreateTime);
        return salesRecordMapper.selectPage(page, queryWrapper);
        // 2. 获取分页的销售记录
        IPage<SalesRecord> salesRecordPage = salesRecordMapper.selectPage(page, queryWrapper);
        // 3. 批量查询所有CoalInfo
        List<Long> coalIds = salesRecordPage.getRecords().stream()
                .map(SalesRecord::getCoalId)  // 获取所有SalesRecord的coalId
                .collect(Collectors.toList());
        Map<Long, CoalInfo> coalInfoMap;
        // 如果有煤炭ID,执行批量查询
        if (!coalIds.isEmpty()) {
            // 使用selectList进行批量查询
            LambdaQueryWrapper<CoalInfo> coalInfoQueryWrapper = new LambdaQueryWrapper<>();
            coalInfoQueryWrapper.in(CoalInfo::getId, coalIds);
            List<CoalInfo> coalInfos = coalInfoMapper.selectList(coalInfoQueryWrapper);
            // 将查询结果放入Map中,煤炭ID为键,CoalInfo为值
            coalInfoMap = coalInfos.stream()
                    .collect(Collectors.toMap(CoalInfo::getId, Function.identity()));
        } else {
            coalInfoMap = new HashMap<>();
        }
        // 4. 创建返回结果页,使用BeanUtils进行属性复制
        Page<SalesRecordDto> resultPage = new Page<>();
        BeanUtils.copyProperties(salesRecordPage, resultPage);  // 复制分页信息
        // 5. 转换SalesRecord为SalesRecordDto,并设置每条销售记录的煤炭信息
        List<SalesRecordDto> dtoList = salesRecordPage.getRecords().stream().map(salesRecord -> {
            SalesRecordDto dto = new SalesRecordDto();
            BeanUtils.copyProperties(salesRecord, dto);  // 将SalesRecord的属性复制到SalesRecordDto中
            // 设置Coal信息
            CoalInfo coalInfo = coalInfoMap.get(salesRecord.getCoalId());
            if (coalInfo != null) {
                dto.setCoal(coalInfo.getCoal());
            }
            return dto;
        }).collect(Collectors.toList());
        resultPage.setRecords(dtoList);  // 设置转换后的DTO列表
        return resultPage;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -61,7 +154,7 @@
        if (officialInventory == null) {
            throw new BaseException("正式库煤种信息不存在");
        }
        if (salesRecordDto.getSaleQuantity().compareTo(officialInventory.getInventoryQuantity()) > 0){
        if (salesRecordDto.getSaleQuantity().compareTo(officialInventory.getInventoryQuantity()) > 0) {
            throw new BaseException("销售数量不能大于库存数量");
        }
        officialInventory.setInventoryQuantity(officialInventory.getInventoryQuantity().subtract(salesRecordDto.getSaleQuantity()));
@@ -69,7 +162,7 @@
        officialInventoryMapper.updateById(officialInventory);
        // 构建销售记录实体
        SalesRecord salesRecord = buildSalesRecord(salesRecordDto,officialInventory.getCoalId());
        SalesRecord salesRecord = buildSalesRecord(salesRecordDto, officialInventory.getCoalId());
        // 处理新增/更新逻辑
        if (salesRecordDto.getId() == null) {
@@ -94,7 +187,7 @@
        }
    }
    private SalesRecord buildSalesRecord(SalesRecordDto dto,Long coalId) {
    private SalesRecord buildSalesRecord(SalesRecordDto dto, Long coalId) {
        SalesRecord record = new SalesRecord();
        BeanUtils.copyProperties(dto, record);
@@ -163,4 +256,305 @@
        // 执行批量逻辑删除
        return salesRecordMapper.update(null, updateWrapper);
    }
    @Override
    public Map<String, Object> getYearlyMonthlySales(YearlyQueryDto query) {
        // 1. 处理默认查询(不传参数时)
        if (query == null || query.getTimeRange() == null || query.getTimeRange().length == 0) {
            query = getDefaultQuery();
        }
        // 2. 解析时间范围
        LocalDate[] dateRange = parseAndValidateDateRange(query.getTimeRange());
        LocalDate startDate = dateRange[0];
        LocalDate endDate = dateRange[1];
        // 3. 查询数据
        List<SalesRecord> records = salesRecordMapper.findByDateRange(
                startDate.withDayOfMonth(1),
                endDate.withDayOfMonth(1).with(TemporalAdjusters.lastDayOfMonth())
        );
        // 4. 构建响应
        return buildResponse(query.getTimeRange(), records, startDate, endDate);
    }
    // 获取默认查询(当前年度最近6个月)
    private YearlyQueryDto getDefaultQuery() {
        LocalDate now = LocalDate.now();
        LocalDate startDate = now.minusMonths(5).withDayOfMonth(1);
        String[] timeRange = {
                startDate.format(DateTimeFormatter.ofPattern("yyyy-MM")),
                now.format(DateTimeFormatter.ofPattern("yyyy-MM"))
        };
        YearlyQueryDto defaultQuery = new YearlyQueryDto();
        defaultQuery.setTimeRange(timeRange);
        return defaultQuery;
    }
    // 解析并验证日期范围
    private LocalDate[] parseAndValidateDateRange(String[] timeRange) {
        if (timeRange == null || timeRange.length != 2) {
            throw new IllegalArgumentException("时间范围参数格式不正确");
        }
        LocalDate startDate = parseDate(timeRange[0]);
        LocalDate endDate = parseDate(timeRange[1]);
        if (startDate.isAfter(endDate)) {
            throw new IllegalArgumentException("开始日期不能晚于结束日期");
        }
        return new LocalDate[]{startDate, endDate};
    }
    // 解析日期(格式:yyyy-MM)
    private LocalDate parseDate(String dateStr) {
        try {
            return YearMonth.parse(dateStr).atDay(1);
        } catch (DateTimeParseException e) {
            throw new IllegalArgumentException("日期格式不正确,应为 yyyy-MM", e);
        }
    }
    // 构建响应数据
    private Map<String, Object> buildResponse(String[] timeRange,
                                              List<SalesRecord> records,
                                              LocalDate startDate,
                                              LocalDate endDate) {
        Map<String, Object> response = new LinkedHashMap<>();
        response.put("timeRange", timeRange);
        response.put("data", formatMonthlyData(records, startDate, endDate));
        response.put("isDefaultQuery", timeRange.equals(getDefaultQuery().getTimeRange()));
        return response;
    }
    // 格式化月度数据
    private Map<String, Integer> formatMonthlyData(List<SalesRecord> records,
                                                   LocalDate startDate,
                                                   LocalDate endDate) {
        // 预定义格式化器
        DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyy-MM");
        // 使用 TreeMap 自动按键排序
        Map<String, Integer> monthlyData = records.stream()
                .collect(Collectors.groupingBy(
                        r -> r.getSaleDate().format(monthFormatter),
                        TreeMap::new,
                        // 关键修改:处理 saleQuantity 为 null 的情况,默认按 0 计算
                        Collectors.summingInt(r -> {
                            BigDecimal quantity = r.getSaleQuantity();
                            return quantity != null ? quantity.intValue() : 0; // 三目运算符判断
                        })
                ));
        // 填充缺失月份
        YearMonth start = YearMonth.from(startDate);
        YearMonth end = YearMonth.from(endDate);
        for (YearMonth month = start; !month.isAfter(end); month = month.plusMonths(1)) {
            String monthKey = month.format(monthFormatter);
            monthlyData.putIfAbsent(monthKey, 0);
        }
        return monthlyData;
    }
    @Override
    public Map<String, Object> selectAllInfo() {
        Map<String, Object> result = new LinkedHashMap<>();
        //营收金额
        LocalDate today = LocalDate.now();
        LocalDate yesterday = today.minusDays(1);
        BigDecimal revenueAmount;
        List<SalesRecord> salesRecords = salesRecordMapper.selectList(null);
        revenueAmount = salesRecords.stream()
                .map(SalesRecord::getTotalAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        //今日营收
        BigDecimal todayRevenue = salesRecords.stream()
                .filter(record -> today.equals(record.getSaleDate()))
                .map(SalesRecord::getTotalAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        //昨日营收
        BigDecimal yesterdayRevenue = salesRecords.stream()
                .filter(record -> yesterday.equals(record.getSaleDate()))
                .map(SalesRecord::getTotalAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        // 计算增长百分比
        BigDecimal increases = BigDecimal.ZERO;
        String trend = "+";
        if (yesterdayRevenue.compareTo(BigDecimal.ZERO) != 0) {
            increases = todayRevenue.subtract(yesterdayRevenue)
                    .divide(yesterdayRevenue, 4, RoundingMode.HALF_UP)
                    .multiply(new BigDecimal(100));
            if (increases.compareTo(BigDecimal.ZERO) > 0) {
                trend = "+";
            } else if (increases.compareTo(BigDecimal.ZERO) < 0) {
                trend = "—";
            }
        }
        // 6. 格式化百分比显示
        DecimalFormat formatNo = new DecimalFormat("0.00%");
        String changeRate = formatNo.format(increases.divide(new BigDecimal(100), 4, RoundingMode.HALF_UP));
        //供应量
        BigDecimal saleQuantity = salesRecords.stream()
                .map(SalesRecord::getSaleQuantity)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        //今日供应量
        BigDecimal todaySaleQuantity = salesRecords.stream()
                .filter(record -> today.equals(record.getSaleDate()))
                .map(SalesRecord::getSaleQuantity)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        //昨日供应量
        BigDecimal yesterdaySaleQuantity = salesRecords.stream()
                .filter(record -> yesterday.equals(record.getSaleDate()))
                .map(SalesRecord::getSaleQuantity)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        // 计算增长百分比
        BigDecimal increasesQuantity = BigDecimal.ZERO;
        String trendQuantity = "+";
        if (yesterdaySaleQuantity.compareTo(BigDecimal.ZERO) != 0) {
            increasesQuantity = todaySaleQuantity.subtract(yesterdaySaleQuantity)
                    .divide(yesterdaySaleQuantity, 4, RoundingMode.HALF_UP)
                    .multiply(new BigDecimal(100));
            if (increasesQuantity.compareTo(BigDecimal.ZERO) > 0) {
                trendQuantity = "+";
            } else if (increasesQuantity.compareTo(BigDecimal.ZERO) < 0) {
                trendQuantity = "—";
            }
        }
        // 格式化数量百分比显示
        DecimalFormat formatNoQuantity = new DecimalFormat("0.00%");
        String saleQuantityRate = formatNoQuantity.format(increasesQuantity.divide(new BigDecimal(100), 4, RoundingMode.HALF_UP));
        //营收分布
        //获取所有煤种信息(id和名称的映射)
        Map<Long, String> coalIdToNameMap = coalInfoMapper.selectList(null).stream()
                .collect(Collectors.toMap(
                        CoalInfo::getId,
                        CoalInfo::getCoal
                ));
        // 3. 按煤种ID统计总金额
        Map<Long, BigDecimal> revenueByCoalId = salesRecords.stream()
                .collect(Collectors.groupingBy(
                        SalesRecord::getCoalId,
                        Collectors.reducing(
                                BigDecimal.ZERO,
                                SalesRecord::getTotalAmount,
                                BigDecimal::add
                        )
                ));
        // 煤种销售分布(单独作为一个子Map)
        Map<String, BigDecimal> revenueDistribution = new LinkedHashMap<>();
        coalIdToNameMap.forEach((id, name) -> {
            BigDecimal amount = revenueByCoalId.getOrDefault(id, BigDecimal.ZERO)
                    .setScale(2, RoundingMode.HALF_UP);
            if (amount.compareTo(BigDecimal.ZERO) > 0) {
                revenueDistribution.put(name, amount);
            }
        });
        //销售数据
        // 按coalId分组并合并数量和金额
        Map<Long, Map<String, Object>> resultMap = new LinkedHashMap<>();
        for (SalesRecord record : salesRecords) {
            Long coalId = record.getCoalId();
            // 将数量和金额转换为BigDecimal
            BigDecimal quantity = record.getInventoryQuantity();
            BigDecimal amount = record.getTotalAmount();
            if (resultMap.containsKey(coalId)) {
                Map<String, Object> existing = resultMap.get(coalId);
                // 获取现有的BigDecimal值
                BigDecimal existingQuantity = (BigDecimal) existing.get("inventoryQuantity");
                BigDecimal existingAmount = (BigDecimal) existing.get("totalAmount");
                // 使用BigDecimal进行加法运算
                existing.put("inventoryQuantity", existingQuantity.add(quantity));
                existing.put("totalAmount", existingAmount.add(amount));
            } else {
                Map<String, Object> newRecord = new HashMap<>();
                newRecord.put("coalId", coalId);
                newRecord.put("inventoryQuantity", quantity);
                newRecord.put("totalAmount", amount);
                resultMap.put(coalId, newRecord);
            }
        }
        // 3. 获取所有涉及的coalId
        List<Long> coalIds = new ArrayList<>(resultMap.keySet());
        // 4. 批量查询煤种信息并填充到结果中
        if (!coalIds.isEmpty()) {
            List<CoalInfo> coalInfos = coalInfoMapper.selectByIds(coalIds);
            for (CoalInfo coalInfo : coalInfos) {
                Map<String, Object> record = resultMap.get(coalInfo.getId());
                if (record != null) {
                    record.put("coalName", coalInfo.getCoal());
                }
            }
        }
        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);
        result.put("saleQuantity", saleQuantity);
        result.put("saleQuantityRate", saleQuantityRate);
        result.put("trendQuantity", trendQuantity);
        result.put("revenueDistribution", revenueDistribution);
        result.put("salesResults", results);
        result.put("resultMouth", resultMouthMap);
        return result;
    }
}