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.*;
|
|
/**
|
* <p>
|
* 销售记录表 服务实现类
|
* </p>
|
*
|
* @author ruoyi
|
* @since 2025-06-11
|
*/
|
@Service
|
@RequiredArgsConstructor
|
public class SalesRecordServiceImpl extends ServiceImpl<SalesRecordMapper, SalesRecord> 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<SalesRecordDto> selectSalesRecordList(Page<SalesRecord> page, SalesRecordDto salesRecordDto) {
|
// 1. 创建查询条件,按创建时间倒序排序
|
LambdaQueryWrapper<SalesRecord> queryWrapper = new LambdaQueryWrapper<>();
|
queryWrapper.orderByDesc(SalesRecord::getCreateTime);
|
|
// 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)
|
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<SalesRecord> updateWrapper = new UpdateWrapper<>();
|
updateWrapper.in("id", ids)
|
.set("deleted", 1); // 设置 deleted 为 1 表示已删除
|
// 执行批量逻辑删除
|
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.selectBatchIds(coalIds);
|
for (CoalInfo coalInfo : coalInfos) {
|
Map<String, Object> record = resultMap.get(coalInfo.getId());
|
if (record != null) {
|
record.put("coalName", coalInfo.getCoal());
|
}
|
}
|
}
|
|
// 最终结果是一个List<Map>,每个Map包含合并后的销售数据和对应的煤种信息
|
List<Map<String, Object>> 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;
|
}
|
}
|