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.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.*;
/**
*
* 销售记录表 服务实现类
*
*
* @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<>();
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 coalQueryWrapper = new LambdaQueryWrapper<>();
coalQueryWrapper.like(CoalInfo::getCoal, searchValue);
List coalInfos = coalInfoMapper.selectList(coalQueryWrapper);
if (!coalInfos.isEmpty()) {
// 提取所有匹配的煤种ID
List 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);
// 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.selectByIds(coalIds);
for (CoalInfo coalInfo : coalInfos) {
Map record = resultMap.get(coalInfo.getId());
if (record != null) {
record.put("coalName", coalInfo.getCoal());
}
}
}
List