package com.rj.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.rj.entity.LbDailyUserTrade;
import com.rj.entity.LbDailyUserTradeReport;
import com.rj.entity.LbDepartmentUser;
import com.rj.mapper.LbDailyUserTradeMapper;
import com.rj.mapper.LbDailyUserTradeReportMapper;
import com.rj.service.ILbDailyUserTradeReportService;
import com.rj.service.ILbDepartmentUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
*
* 当天用户交易报表 服务实现类
*
*/
@Service
public class LbDailyUserTradeReportServiceImpl extends ServiceImpl
implements ILbDailyUserTradeReportService {
private static final Logger log = LoggerFactory.getLogger(LbDailyUserTradeReportServiceImpl.class);
private static final String DATA_TYPE_REPORT_SUM = "report_sum";
private static final String DATA_TYPE_REPORT_DETAIL = "report_detail";
private static final BigDecimal CROSS_DAY_DIFF_TOLERANCE = new BigDecimal("3");
@Autowired
private ILbDepartmentUserService lbDepartmentUserService;
@Autowired
private LbDailyUserTradeMapper lbDailyUserTradeMapper;
@Override
public Page pageDailyUserTradeByUserIdAndDateRange(
LocalDate startDate, LocalDate endDate, String userId, Integer current, Integer size) {
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(LbDailyUserTrade::getUserId, userId.trim())
.ge(LbDailyUserTrade::getReportDate, startDate)
.le(LbDailyUserTrade::getReportDate, endDate)
.orderByDesc(LbDailyUserTrade::getReportDate)
.orderByDesc(LbDailyUserTrade::getCreatedAt);
return lbDailyUserTradeMapper.selectPage(new Page<>(current, size), queryWrapper);
}
@Override
public Page pageDailyUserTradeByParentIdOfUserAndDateRange(
LocalDate startDate, LocalDate endDate, String userId, Integer current, Integer size) {
LambdaQueryWrapper departmentUserQueryWrapper = new LambdaQueryWrapper<>();
departmentUserQueryWrapper.eq(LbDepartmentUser::getUserId, userId.trim())
.orderByDesc(LbDepartmentUser::getUpdateTime)
.orderByDesc(LbDepartmentUser::getCreateTime)
.last("limit 1");
LbDepartmentUser departmentUser = lbDepartmentUserService.getOne(departmentUserQueryWrapper, false);
if (departmentUser == null || departmentUser.getParentId() == null || departmentUser.getParentId().trim().isEmpty()) {
throw new IllegalArgumentException("未查询到该用户对应的parentId");
}
String parentId = departmentUser.getParentId().trim();
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(LbDailyUserTrade::getUserId, parentId)
.ge(LbDailyUserTrade::getReportDate, startDate)
.le(LbDailyUserTrade::getReportDate, endDate)
.orderByDesc(LbDailyUserTrade::getReportDate)
.orderByDesc(LbDailyUserTrade::getCreatedAt);
return lbDailyUserTradeMapper.selectPage(new Page<>(current, size), queryWrapper);
}
@Override
public Map calculateReportSumByDateAndTenant(LocalDate reportDate, String tenantId) {
Map result = new HashMap<>();
LocalDateTime start = reportDate.atStartOfDay();
LocalDateTime end = reportDate.plusDays(1).atStartOfDay();
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(LbDailyUserTradeReport::getTenantId, tenantId)
.ge(LbDailyUserTradeReport::getReportDate, start)
.lt(LbDailyUserTradeReport::getReportDate, end)
.eq(LbDailyUserTradeReport::getDataType, DATA_TYPE_REPORT_DETAIL);
BigDecimal yestodayBuyAmt = BigDecimal.ZERO;
BigDecimal dailySellAmt = BigDecimal.ZERO;
BigDecimal dailyBuyAmt = BigDecimal.ZERO;
BigDecimal serviceAmt = BigDecimal.ZERO;
BigDecimal diffAmt = BigDecimal.ZERO;
BigDecimal dikouAmt = BigDecimal.ZERO;
for (LbDailyUserTradeReport item : list(queryWrapper)) {
yestodayBuyAmt = yestodayBuyAmt.add(defaultZero(item.getYestodayBuyAmt()));
dailySellAmt = dailySellAmt.add(defaultZero(item.getDailySellAmt()));
dailyBuyAmt = dailyBuyAmt.add(defaultZero(item.getDailyBuyAmt()));
serviceAmt = serviceAmt.add(defaultZero(item.getServiceAmt()));
diffAmt = diffAmt.add(defaultZero(item.getDiffAmt()));
dikouAmt = dikouAmt.add(defaultZero(item.getDikouAmt()));
}
LbDailyUserTradeReport summary = new LbDailyUserTradeReport();
summary.setId(UUID.randomUUID().toString());
summary.setTenantId(tenantId);
summary.setDataType(DATA_TYPE_REPORT_SUM);
summary.setReportDate(start);
summary.setYestodayBuyAmt(yestodayBuyAmt);
summary.setDailySellAmt(dailySellAmt);
summary.setDailyBuyAmt(dailyBuyAmt);
summary.setServiceAmt(serviceAmt);
summary.setDiffAmt(diffAmt);
summary.setDikouAmt(dikouAmt);
summary.setActualReceiptsPayments(diffAmt.subtract(serviceAmt));
summary.setDescContent("系统汇总记录");
summary.setCreatedAt(LocalDateTime.now());
summary.setUpdatedAt(LocalDateTime.now());
// 同日重复汇总时先清理历史汇总,避免 report_sum 产生多条导致读取口径不稳定。
LambdaQueryWrapper sumDeleteWrapper = new LambdaQueryWrapper<>();
sumDeleteWrapper.eq(LbDailyUserTradeReport::getTenantId, tenantId)
.ge(LbDailyUserTradeReport::getReportDate, start)
.lt(LbDailyUserTradeReport::getReportDate, end)
.eq(LbDailyUserTradeReport::getDataType, DATA_TYPE_REPORT_SUM);
remove(sumDeleteWrapper);
boolean success = save(summary);
Map checkInfo = buildCrossDayCheckInfo(reportDate, tenantId, start, end, yestodayBuyAmt);
if (success && refreshSummaryFromDetail(summary, tenantId, start, end)) {
checkInfo.put("summaryRefreshed", true);
}
result.put("success", success);
if (success) {
result.put("message", "汇总成功");
result.put("data", summary);
result.put("checkInfo", checkInfo);
return result;
}
result.put("message", "汇总失败");
return result;
}
private Map buildCrossDayCheckInfo(
LocalDate reportDate,
String tenantId,
LocalDateTime todayStart,
LocalDateTime todayEnd,
BigDecimal todayYestodayBuyAmt) {
Map checkInfo = new LinkedHashMap<>();
LocalDate yesterdayDate = reportDate.minusDays(1);
LocalDateTime yesterdayStart = yesterdayDate.atStartOfDay();
LocalDateTime yesterdayEnd = reportDate.atStartOfDay();
BigDecimal tolerance = CROSS_DAY_DIFF_TOLERANCE;
log.info("cross-day check started, tenantId={}, reportDate={}, yesterdayRange=[{},{}), todayRange=[{},{})",
tenantId, reportDate, yesterdayStart, yesterdayEnd, todayStart, todayEnd);
LbDailyUserTradeReport yesterdaySum = queryYesterdaySummary(tenantId, yesterdayStart, yesterdayEnd);
if (yesterdaySum == null) {
log.warn("cross-day check skipped: yesterday summary not found, tenantId={}, reportDate={}", tenantId, reportDate);
checkInfo.put("yesterdaySumFound", false);
checkInfo.put("needDetailCheck", false);
checkInfo.put("detailCheckPassed", false);
return checkInfo;
}
BigDecimal yesterdaySumBuyTotal = defaultZero(yesterdaySum.getDailyBuyAmt());
BigDecimal sumDiff = yesterdaySumBuyTotal.subtract(defaultZero(todayYestodayBuyAmt)).abs();
checkInfo.put("yesterdaySumFound", true);
checkInfo.put("yesterdaySumBuyTotal", yesterdaySumBuyTotal);
checkInfo.put("todayYestodayBuyAmt", todayYestodayBuyAmt);
checkInfo.put("sumDiff", sumDiff);
checkInfo.put("threshold", tolerance);
log.info("cross-day summary compared, tenantId={}, reportDate={}, yesterdaySumBuyTotal={}, todayYestodayBuyAmt={}, diff={}, threshold={}",
tenantId, reportDate, yesterdaySumBuyTotal, todayYestodayBuyAmt, sumDiff, tolerance);
if (sumDiff.compareTo(tolerance) <= 0) {
log.info("cross-day summary within threshold, skip detail check, tenantId={}, reportDate={}", tenantId, reportDate);
checkInfo.put("needDetailCheck", false);
checkInfo.put("detailCheckPassed", true);
return checkInfo;
}
log.warn("cross-day summary exceeded threshold, start detail repair/check, tenantId={}, reportDate={}, diff={}",
tenantId, reportDate, sumDiff);
List yesterdayDetailList = queryDetailRows(tenantId, yesterdayStart, yesterdayEnd);
List todayDetailList = queryDetailRows(tenantId, todayStart, todayEnd);
log.info("detail rows loaded, tenantId={}, yesterdayCount={}, todayCount={}",
tenantId, yesterdayDetailList.size(), todayDetailList.size());
Map yesterdayAmtByUserId = buildAmountByUserId(yesterdayDetailList, true);
DetailRepairStat repairStat = repairTodayDetailByYesterday(yesterdayDetailList, todayDetailList, tenantId, todayStart);
checkInfo.put("autoRepaired", repairStat.repaired);
checkInfo.put("updatedTodayRows", repairStat.updatedCount);
checkInfo.put("insertedTodayRows", repairStat.insertedCount);
log.info("detail repair finished, tenantId={}, repaired={}, updatedRows={}, insertedRows={}",
tenantId, repairStat.repaired, repairStat.updatedCount, repairStat.insertedCount);
// 修正后重新读取今天明细并复核结果。
List repairedTodayDetailList = queryDetailRows(tenantId, todayStart, todayEnd);
Map repairedTodayAmtByUserId = buildAmountByUserId(repairedTodayDetailList, false);
fillDetailCheckResult(checkInfo, yesterdayAmtByUserId, repairedTodayAmtByUserId, yesterdayDetailList.size(), repairedTodayDetailList.size());
@SuppressWarnings("unchecked")
List