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; import com.ruoyi.business.mapper.SalesRecordMapper; 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.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.*; /** *

* 销售记录表 服务实现类 *

* * @author ruoyi * @since 2025-06-11 */ @Service @RequiredArgsConstructor public class SalesRecordServiceImpl extends ServiceImpl implements SalesRecordService { private final SalesRecordMapper salesRecordMapper; private final SysUserMapper userMapper; private final CustomerMapper customerMapper; private final OfficialInventoryMapper officialInventoryMapper; private final CoalInfoMapper coalInfoMapper; @Override public IPage selectSalesRecordList(Page page, SalesRecordDto salesRecordDto) { // 1. 创建查询条件,按创建时间倒序排序 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.orderByDesc(SalesRecord::getCreateTime); // 2. 获取分页的销售记录 IPage salesRecordPage = salesRecordMapper.selectPage(page, queryWrapper); // 3. 批量查询所有CoalInfo List coalIds = salesRecordPage.getRecords().stream() .map(SalesRecord::getCoalId) // 获取所有SalesRecord的coalId .collect(Collectors.toList()); Map coalInfoMap; // 如果有煤炭ID,执行批量查询 if (!coalIds.isEmpty()) { // 使用selectList进行批量查询 LambdaQueryWrapper coalInfoQueryWrapper = new LambdaQueryWrapper<>(); coalInfoQueryWrapper.in(CoalInfo::getId, coalIds); List coalInfos = coalInfoMapper.selectList(coalInfoQueryWrapper); // 将查询结果放入Map中,煤炭ID为键,CoalInfo为值 coalInfoMap = coalInfos.stream() .collect(Collectors.toMap(CoalInfo::getId, Function.identity())); } else { coalInfoMap = new HashMap<>(); } // 4. 创建返回结果页,使用BeanUtils进行属性复制 Page resultPage = new Page<>(); BeanUtils.copyProperties(salesRecordPage, resultPage); // 复制分页信息 // 5. 转换SalesRecord为SalesRecordDto,并设置每条销售记录的煤炭信息 List 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) public int addOrEditSalesRecord(SalesRecordDto salesRecordDto) { // 参数校验 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 salesRecord = buildSalesRecord(salesRecordDto, officialInventory.getCoalId()); // 处理新增/更新逻辑 if (salesRecordDto.getId() == null) { return insertSalesRecord(salesRecord); } else { return updateSalesRecord(salesRecord); } } 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 SalesRecord buildSalesRecord(SalesRecordDto dto, Long coalId) { SalesRecord record = new SalesRecord(); BeanUtils.copyProperties(dto, record); // 设置登记人信息 SysUser registrant = userMapper.selectUserById(dto.getRegistrantId()); if (registrant == null) { throw new BaseException("登记人信息不存在"); } record.setRegistrant(registrant.getUserName()); // 设置客户信息 Customer customer = customerMapper.selectById(dto.getCustomerId()); if (customer == null) { throw new BaseException("客户信息不存在"); } record.setCustomer(customer.getCustomerName()); // 设置日期信息 LocalDate now = LocalDate.now(); if (record.getId() == null) { // 新增时设置日期 record.setSaleDate(now); record.setRegistrationDate(now); } else { // 更新时不覆盖原有日期 SalesRecord existing = salesRecordMapper.selectById(record.getId()); if (existing == null) { throw new BaseException("销售记录不存在"); } record.setSaleDate(existing.getSaleDate()); record.setRegistrationDate(existing.getRegistrationDate()); } // 煤种 record.setCoalId(coalId); return record; } private int insertSalesRecord(SalesRecord record) { int result = salesRecordMapper.insert(record); if (result <= 0) { throw new BaseException("销售记录创建失败"); } return result; } private int updateSalesRecord(SalesRecord record) { int result = salesRecordMapper.updateById(record); if (result <= 0) { throw new BaseException("销售记录更新失败"); } return result; } @Override public int delByIds(Long[] ids) { // 检查参数 if (ids == null || ids.length == 0) { return 0; } // 构造更新条件 UpdateWrapper updateWrapper = new UpdateWrapper<>(); updateWrapper.in("id", ids) .set("deleted", 1); // 设置 deleted 为 1 表示已删除 // 执行批量逻辑删除 return salesRecordMapper.update(null, updateWrapper); } @Override public Map 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 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 buildResponse(String[] timeRange, List records, LocalDate startDate, LocalDate endDate) { Map 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 formatMonthlyData(List records, LocalDate startDate, LocalDate endDate) { // 预定义格式化器 DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyy-MM"); // 使用 TreeMap 自动按键排序 Map 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 selectAllInfo() { Map result = new LinkedHashMap<>(); //营收金额 LocalDate today = LocalDate.now(); LocalDate yesterday = today.minusDays(1); BigDecimal revenueAmount; List 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 coalIdToNameMap = coalInfoMapper.selectList(null).stream() .collect(Collectors.toMap( CoalInfo::getId, CoalInfo::getCoal )); // 3. 按煤种ID统计总金额 Map revenueByCoalId = salesRecords.stream() .collect(Collectors.groupingBy( SalesRecord::getCoalId, Collectors.reducing( BigDecimal.ZERO, SalesRecord::getTotalAmount, BigDecimal::add ) )); // 煤种销售分布(单独作为一个子Map) Map 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> 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 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 newRecord = new HashMap<>(); newRecord.put("coalId", coalId); newRecord.put("inventoryQuantity", quantity); newRecord.put("totalAmount", amount); resultMap.put(coalId, newRecord); } } // 3. 获取所有涉及的coalId List coalIds = new ArrayList<>(resultMap.keySet()); // 4. 批量查询煤种信息并填充到结果中 if (!coalIds.isEmpty()) { List coalInfos = coalInfoMapper.selectBatchIds(coalIds); for (CoalInfo coalInfo : coalInfos) { Map record = resultMap.get(coalInfo.getId()); if (record != null) { record.put("coalName", coalInfo.getCoal()); } } } // 最终结果是一个List,每个Map包含合并后的销售数据和对应的煤种信息 List> results = new ArrayList<>(resultMap.values()); 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); return result; } }