给树形结构添加查询交易明细的功能
This commit is contained in:
@@ -2,6 +2,7 @@ package com.rj.controller;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.rj.entity.LbDailyUserTrade;
|
||||||
import com.rj.entity.LbDailyUserTradeReport;
|
import com.rj.entity.LbDailyUserTradeReport;
|
||||||
import com.rj.service.ILbDailyUserTradeReportService;
|
import com.rj.service.ILbDailyUserTradeReportService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@@ -224,6 +225,143 @@ public class LbDailyUserTradeReportController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/list/daily-user-trade/by-user-id-and-date-range")
|
||||||
|
@Operation(summary = "按用户ID和日期范围分页查询交易明细", description = "根据开始日期、结束日期和用户ID,从lb_daily_user_trade分页查询交易数据")
|
||||||
|
public ResponseEntity<Map<String, Object>> listDailyUserTradeByUserIdAndDateRange(
|
||||||
|
@Parameter(description = "页码", example = "1") @RequestParam(defaultValue = "1") Integer current,
|
||||||
|
@Parameter(description = "每页大小", example = "10") @RequestParam(defaultValue = "10") Integer size,
|
||||||
|
@Parameter(description = "开始日期,格式:yyyy-MM-dd", required = true) @RequestParam String startDate,
|
||||||
|
@Parameter(description = "结束日期,格式:yyyy-MM-dd", required = true) @RequestParam String endDate,
|
||||||
|
@Parameter(description = "用户ID", required = true) @RequestParam String userId) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
try {
|
||||||
|
if (userId == null || userId.trim().isEmpty()) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", "userId不能为空");
|
||||||
|
return ResponseEntity.badRequest().body(result);
|
||||||
|
}
|
||||||
|
if (startDate == null || startDate.trim().isEmpty()) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", "startDate不能为空");
|
||||||
|
return ResponseEntity.badRequest().body(result);
|
||||||
|
}
|
||||||
|
if (endDate == null || endDate.trim().isEmpty()) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", "endDate不能为空");
|
||||||
|
return ResponseEntity.badRequest().body(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDate parsedStartDate;
|
||||||
|
LocalDate parsedEndDate;
|
||||||
|
try {
|
||||||
|
parsedStartDate = LocalDate.parse(startDate.trim());
|
||||||
|
} catch (Exception e) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", "startDate格式错误,请使用 yyyy-MM-dd");
|
||||||
|
return ResponseEntity.badRequest().body(result);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
parsedEndDate = LocalDate.parse(endDate.trim());
|
||||||
|
} catch (Exception e) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", "endDate格式错误,请使用 yyyy-MM-dd");
|
||||||
|
return ResponseEntity.badRequest().body(result);
|
||||||
|
}
|
||||||
|
if (parsedEndDate.isBefore(parsedStartDate)) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", "endDate不能早于startDate");
|
||||||
|
return ResponseEntity.badRequest().body(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Page<LbDailyUserTrade> page = lbDailyUserTradeReportService.pageDailyUserTradeByUserIdAndDateRange(
|
||||||
|
parsedStartDate, parsedEndDate, userId.trim(), current, size);
|
||||||
|
|
||||||
|
result.put("success", true);
|
||||||
|
result.put("message", "查询成功");
|
||||||
|
result.put("data", page.getRecords());
|
||||||
|
result.put("total", page.getTotal());
|
||||||
|
result.put("current", page.getCurrent());
|
||||||
|
result.put("size", page.getSize());
|
||||||
|
result.put("pages", page.getPages());
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", "查询异常:" + e.getMessage());
|
||||||
|
return ResponseEntity.internalServerError().body(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/list/daily-user-trade/by-parent-id-of-user-and-date-range")
|
||||||
|
@Operation(summary = "按用户ID和日期范围分页查询父节点的交易明细",
|
||||||
|
description = "根据用户ID查询lb_department_user的parent_id,再按user_id=parent_id从lb_daily_user_trade分页查询指定日期范围交易数据")
|
||||||
|
public ResponseEntity<Map<String, Object>> listDailyUserTradeByParentIdOfUserAndDateRange(
|
||||||
|
@Parameter(description = "页码", example = "1") @RequestParam(defaultValue = "1") Integer current,
|
||||||
|
@Parameter(description = "每页大小", example = "10") @RequestParam(defaultValue = "10") Integer size,
|
||||||
|
@Parameter(description = "开始日期,格式:yyyy-MM-dd", required = true) @RequestParam String startDate,
|
||||||
|
@Parameter(description = "结束日期,格式:yyyy-MM-dd", required = true) @RequestParam String endDate,
|
||||||
|
@Parameter(description = "用户ID", required = true) @RequestParam String userId) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
try {
|
||||||
|
if (userId == null || userId.trim().isEmpty()) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", "userId不能为空");
|
||||||
|
return ResponseEntity.badRequest().body(result);
|
||||||
|
}
|
||||||
|
if (startDate == null || startDate.trim().isEmpty()) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", "startDate不能为空");
|
||||||
|
return ResponseEntity.badRequest().body(result);
|
||||||
|
}
|
||||||
|
if (endDate == null || endDate.trim().isEmpty()) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", "endDate不能为空");
|
||||||
|
return ResponseEntity.badRequest().body(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDate parsedStartDate;
|
||||||
|
LocalDate parsedEndDate;
|
||||||
|
try {
|
||||||
|
parsedStartDate = LocalDate.parse(startDate.trim());
|
||||||
|
} catch (Exception e) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", "startDate格式错误,请使用 yyyy-MM-dd");
|
||||||
|
return ResponseEntity.badRequest().body(result);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
parsedEndDate = LocalDate.parse(endDate.trim());
|
||||||
|
} catch (Exception e) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", "endDate格式错误,请使用 yyyy-MM-dd");
|
||||||
|
return ResponseEntity.badRequest().body(result);
|
||||||
|
}
|
||||||
|
if (parsedEndDate.isBefore(parsedStartDate)) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", "endDate不能早于startDate");
|
||||||
|
return ResponseEntity.badRequest().body(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Page<LbDailyUserTrade> page = lbDailyUserTradeReportService.pageDailyUserTradeByParentIdOfUserAndDateRange(
|
||||||
|
parsedStartDate, parsedEndDate, userId.trim(), current, size);
|
||||||
|
|
||||||
|
result.put("success", true);
|
||||||
|
result.put("message", "查询成功");
|
||||||
|
result.put("data", page.getRecords());
|
||||||
|
result.put("total", page.getTotal());
|
||||||
|
result.put("current", page.getCurrent());
|
||||||
|
result.put("size", page.getSize());
|
||||||
|
result.put("pages", page.getPages());
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", e.getMessage());
|
||||||
|
return ResponseEntity.badRequest().body(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", "查询异常:" + e.getMessage());
|
||||||
|
return ResponseEntity.internalServerError().body(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@PutMapping("/update")
|
@PutMapping("/update")
|
||||||
@Operation(summary = "修改报表记录", description = "根据ID修改当天用户交易报表记录")
|
@Operation(summary = "修改报表记录", description = "根据ID修改当天用户交易报表记录")
|
||||||
public ResponseEntity<Map<String, Object>> update(@RequestBody LbDailyUserTradeReport tradeReport) {
|
public ResponseEntity<Map<String, Object>> update(@RequestBody LbDailyUserTradeReport tradeReport) {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.rj.service;
|
package com.rj.service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.rj.entity.LbDailyUserTrade;
|
||||||
import com.rj.entity.LbDailyUserTradeReport;
|
import com.rj.entity.LbDailyUserTradeReport;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -12,6 +14,11 @@ import java.util.Map;
|
|||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public interface ILbDailyUserTradeReportService extends IService<LbDailyUserTradeReport> {
|
public interface ILbDailyUserTradeReportService extends IService<LbDailyUserTradeReport> {
|
||||||
|
Page<LbDailyUserTrade> pageDailyUserTradeByUserIdAndDateRange(
|
||||||
|
LocalDate startDate, LocalDate endDate, String userId, Integer current, Integer size);
|
||||||
|
Page<LbDailyUserTrade> pageDailyUserTradeByParentIdOfUserAndDateRange(
|
||||||
|
LocalDate startDate, LocalDate endDate, String userId, Integer current, Integer size);
|
||||||
|
|
||||||
Map<String, Object> calculateReportSumByDateAndTenant(LocalDate reportDate, String tenantId);
|
Map<String, Object> calculateReportSumByDateAndTenant(LocalDate reportDate, String tenantId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
package com.rj.service.impl;
|
package com.rj.service.impl;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
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.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.rj.entity.LbDailyUserTrade;
|
||||||
import com.rj.entity.LbDailyUserTradeReport;
|
import com.rj.entity.LbDailyUserTradeReport;
|
||||||
import com.rj.entity.LbDepartmentUser;
|
import com.rj.entity.LbDepartmentUser;
|
||||||
|
import com.rj.mapper.LbDailyUserTradeMapper;
|
||||||
import com.rj.mapper.LbDailyUserTradeReportMapper;
|
import com.rj.mapper.LbDailyUserTradeReportMapper;
|
||||||
import com.rj.service.ILbDailyUserTradeReportService;
|
import com.rj.service.ILbDailyUserTradeReportService;
|
||||||
import com.rj.service.ILbDepartmentUserService;
|
import com.rj.service.ILbDepartmentUserService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -32,11 +37,50 @@ import java.util.UUID;
|
|||||||
public class LbDailyUserTradeReportServiceImpl extends ServiceImpl<LbDailyUserTradeReportMapper, LbDailyUserTradeReport>
|
public class LbDailyUserTradeReportServiceImpl extends ServiceImpl<LbDailyUserTradeReportMapper, LbDailyUserTradeReport>
|
||||||
implements ILbDailyUserTradeReportService {
|
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_SUM = "report_sum";
|
||||||
private static final String DATA_TYPE_REPORT_DETAIL = "report_detail";
|
private static final String DATA_TYPE_REPORT_DETAIL = "report_detail";
|
||||||
|
private static final BigDecimal CROSS_DAY_DIFF_TOLERANCE = new BigDecimal("3");
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ILbDepartmentUserService lbDepartmentUserService;
|
private ILbDepartmentUserService lbDepartmentUserService;
|
||||||
|
@Autowired
|
||||||
|
private LbDailyUserTradeMapper lbDailyUserTradeMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<LbDailyUserTrade> pageDailyUserTradeByUserIdAndDateRange(
|
||||||
|
LocalDate startDate, LocalDate endDate, String userId, Integer current, Integer size) {
|
||||||
|
LambdaQueryWrapper<LbDailyUserTrade> 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<LbDailyUserTrade> pageDailyUserTradeByParentIdOfUserAndDateRange(
|
||||||
|
LocalDate startDate, LocalDate endDate, String userId, Integer current, Integer size) {
|
||||||
|
LambdaQueryWrapper<LbDepartmentUser> 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<LbDailyUserTrade> 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
|
@Override
|
||||||
public Map<String, Object> calculateReportSumByDateAndTenant(LocalDate reportDate, String tenantId) {
|
public Map<String, Object> calculateReportSumByDateAndTenant(LocalDate reportDate, String tenantId) {
|
||||||
@@ -84,21 +128,297 @@ public class LbDailyUserTradeReportServiceImpl extends ServiceImpl<LbDailyUserTr
|
|||||||
summary.setUpdatedAt(LocalDateTime.now());
|
summary.setUpdatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
boolean success = save(summary);
|
boolean success = save(summary);
|
||||||
|
Map<String, Object> checkInfo = buildCrossDayCheckInfo(reportDate, tenantId, start, end, yestodayBuyAmt);
|
||||||
|
|
||||||
result.put("success", success);
|
result.put("success", success);
|
||||||
if (success) {
|
if (success) {
|
||||||
result.put("message", "汇总成功");
|
result.put("message", "汇总成功");
|
||||||
result.put("data", summary);
|
result.put("data", summary);
|
||||||
|
result.put("checkInfo", checkInfo);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
result.put("message", "汇总失败");
|
result.put("message", "汇总失败");
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> buildCrossDayCheckInfo(
|
||||||
|
LocalDate reportDate,
|
||||||
|
String tenantId,
|
||||||
|
LocalDateTime todayStart,
|
||||||
|
LocalDateTime todayEnd,
|
||||||
|
BigDecimal todayYestodayBuyAmt) {
|
||||||
|
Map<String, Object> 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<LbDailyUserTradeReport> yesterdayDetailList = queryDetailRows(tenantId, yesterdayStart, yesterdayEnd);
|
||||||
|
List<LbDailyUserTradeReport> todayDetailList = queryDetailRows(tenantId, todayStart, todayEnd);
|
||||||
|
log.info("detail rows loaded, tenantId={}, yesterdayCount={}, todayCount={}",
|
||||||
|
tenantId, yesterdayDetailList.size(), todayDetailList.size());
|
||||||
|
|
||||||
|
Map<String, BigDecimal> 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<LbDailyUserTradeReport> repairedTodayDetailList = queryDetailRows(tenantId, todayStart, todayEnd);
|
||||||
|
Map<String, BigDecimal> repairedTodayAmtByUserId = buildAmountByUserId(repairedTodayDetailList, false);
|
||||||
|
fillDetailCheckResult(checkInfo, yesterdayAmtByUserId, repairedTodayAmtByUserId, yesterdayDetailList.size(), repairedTodayDetailList.size());
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<Map<String, Object>> mismatchUsers = (List<Map<String, Object>>) checkInfo.get("mismatchUsers");
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<String> missingInTodayUsers = (List<String>) checkInfo.get("missingInTodayUsers");
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<String> extraInTodayUsers = (List<String>) checkInfo.get("extraInTodayUsers");
|
||||||
|
|
||||||
|
if (!mismatchUsers.isEmpty() || !missingInTodayUsers.isEmpty() || !extraInTodayUsers.isEmpty()) {
|
||||||
|
log.warn("trade report detail check failed, tenantId={}, reportDate={}, sumDiff={}, mismatchCount={}, missingCount={}, extraCount={}",
|
||||||
|
tenantId, reportDate, sumDiff, mismatchUsers.size(), missingInTodayUsers.size(), extraInTodayUsers.size());
|
||||||
|
} else {
|
||||||
|
log.info("trade report detail check passed after repair, tenantId={}, reportDate={}, sumDiff={}",
|
||||||
|
tenantId, reportDate, sumDiff);
|
||||||
|
}
|
||||||
|
return checkInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LbDailyUserTradeReport queryYesterdaySummary(String tenantId, LocalDateTime yesterdayStart, LocalDateTime yesterdayEnd) {
|
||||||
|
LambdaQueryWrapper<LbDailyUserTradeReport> query = new LambdaQueryWrapper<>();
|
||||||
|
query.eq(LbDailyUserTradeReport::getTenantId, tenantId)
|
||||||
|
.ge(LbDailyUserTradeReport::getReportDate, yesterdayStart)
|
||||||
|
.lt(LbDailyUserTradeReport::getReportDate, yesterdayEnd)
|
||||||
|
.eq(LbDailyUserTradeReport::getDataType, DATA_TYPE_REPORT_SUM);
|
||||||
|
List<LbDailyUserTradeReport> rows = list(query);
|
||||||
|
return rows.isEmpty() ? null : rows.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<LbDailyUserTradeReport> queryDetailRows(String tenantId, LocalDateTime start, LocalDateTime end) {
|
||||||
|
LambdaQueryWrapper<LbDailyUserTradeReport> query = new LambdaQueryWrapper<>();
|
||||||
|
query.eq(LbDailyUserTradeReport::getTenantId, tenantId)
|
||||||
|
.ge(LbDailyUserTradeReport::getReportDate, start)
|
||||||
|
.lt(LbDailyUserTradeReport::getReportDate, end)
|
||||||
|
.eq(LbDailyUserTradeReport::getDataType, DATA_TYPE_REPORT_DETAIL);
|
||||||
|
return list(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, BigDecimal> buildAmountByUserId(List<LbDailyUserTradeReport> detailRows, boolean useDailyBuyAmt) {
|
||||||
|
Map<String, BigDecimal> amountByUserId = new HashMap<>();
|
||||||
|
for (LbDailyUserTradeReport row : detailRows) {
|
||||||
|
if (row.getUserId() == null || row.getUserId().trim().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String userId = row.getUserId().trim();
|
||||||
|
BigDecimal amount = useDailyBuyAmt ? defaultZero(row.getDailyBuyAmt()) : defaultZero(row.getYestodayBuyAmt());
|
||||||
|
amountByUserId.merge(userId, amount, BigDecimal::add);
|
||||||
|
}
|
||||||
|
return amountByUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DetailRepairStat repairTodayDetailByYesterday(
|
||||||
|
List<LbDailyUserTradeReport> yesterdayDetailList,
|
||||||
|
List<LbDailyUserTradeReport> todayDetailList,
|
||||||
|
String tenantId,
|
||||||
|
LocalDateTime todayStart) {
|
||||||
|
// Step1: 先把“昨天每个用户应有金额”聚合好,作为今天修正目标。
|
||||||
|
Map<String, BigDecimal> yesterdayAmtByUserId = new HashMap<>();
|
||||||
|
Map<String, LbDailyUserTradeReport> yesterdaySampleByUserId = new HashMap<>();
|
||||||
|
for (LbDailyUserTradeReport row : yesterdayDetailList) {
|
||||||
|
if (row.getUserId() == null || row.getUserId().trim().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String userId = row.getUserId().trim();
|
||||||
|
yesterdayAmtByUserId.merge(userId, defaultZero(row.getDailyBuyAmt()), BigDecimal::add);
|
||||||
|
yesterdaySampleByUserId.putIfAbsent(userId, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step2: 对今天明细建立 userId -> rows 映射,便于 O(1) 找到待修正记录。
|
||||||
|
Map<String, List<LbDailyUserTradeReport>> todayRowsByUserId = new HashMap<>();
|
||||||
|
for (LbDailyUserTradeReport row : todayDetailList) {
|
||||||
|
if (row.getUserId() == null || row.getUserId().trim().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String userId = row.getUserId().trim();
|
||||||
|
todayRowsByUserId.computeIfAbsent(userId, k -> new ArrayList<>()).add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<LbDailyUserTradeReport> rowsToUpdate = new ArrayList<>();
|
||||||
|
List<LbDailyUserTradeReport> rowsToInsert = new ArrayList<>();
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
int duplicateRowZeroedCount = 0;
|
||||||
|
|
||||||
|
// Step3: 以“昨天聚合金额”为唯一标准回写今天数据。
|
||||||
|
for (Map.Entry<String, BigDecimal> entry : yesterdayAmtByUserId.entrySet()) {
|
||||||
|
String userId = entry.getKey();
|
||||||
|
BigDecimal expectedAmt = defaultZero(entry.getValue());
|
||||||
|
List<LbDailyUserTradeReport> todayRows = todayRowsByUserId.get(userId);
|
||||||
|
|
||||||
|
if (todayRows == null || todayRows.isEmpty()) {
|
||||||
|
LbDailyUserTradeReport source = yesterdaySampleByUserId.get(userId);
|
||||||
|
LbDailyUserTradeReport inserted = new LbDailyUserTradeReport();
|
||||||
|
inserted.setId(UUID.randomUUID().toString());
|
||||||
|
inserted.setTenantId(tenantId);
|
||||||
|
inserted.setUserId(userId);
|
||||||
|
inserted.setNickname(source == null ? null : source.getNickname());
|
||||||
|
inserted.setDataType(DATA_TYPE_REPORT_DETAIL);
|
||||||
|
inserted.setReportDate(todayStart);
|
||||||
|
inserted.setYestodayBuyAmt(expectedAmt);
|
||||||
|
inserted.setDailySellAmt(BigDecimal.ZERO);
|
||||||
|
inserted.setDailyBuyAmt(BigDecimal.ZERO);
|
||||||
|
inserted.setServiceAmt(BigDecimal.ZERO);
|
||||||
|
inserted.setDiffAmt(BigDecimal.ZERO);
|
||||||
|
inserted.setDikouAmt(BigDecimal.ZERO);
|
||||||
|
inserted.setActualReceiptsPayments(BigDecimal.ZERO);
|
||||||
|
inserted.setDescContent("系统按昨日数据自动修正");
|
||||||
|
inserted.setCreatedAt(now);
|
||||||
|
inserted.setUpdatedAt(now);
|
||||||
|
rowsToInsert.add(inserted);
|
||||||
|
log.info("detail repair insert row, tenantId={}, userId={}, expectedAmt={}", tenantId, userId, expectedAmt);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
LbDailyUserTradeReport first = todayRows.get(0);
|
||||||
|
if (defaultZero(first.getYestodayBuyAmt()).compareTo(expectedAmt) != 0) {
|
||||||
|
first.setYestodayBuyAmt(expectedAmt);
|
||||||
|
first.setUpdatedAt(now);
|
||||||
|
rowsToUpdate.add(first);
|
||||||
|
log.info("detail repair update row, tenantId={}, userId={}, rowId={}, expectedAmt={}",
|
||||||
|
tenantId, userId, first.getId(), expectedAmt);
|
||||||
|
}
|
||||||
|
for (int i = 1; i < todayRows.size(); i++) {
|
||||||
|
LbDailyUserTradeReport extra = todayRows.get(i);
|
||||||
|
if (defaultZero(extra.getYestodayBuyAmt()).compareTo(BigDecimal.ZERO) != 0) {
|
||||||
|
extra.setYestodayBuyAmt(BigDecimal.ZERO);
|
||||||
|
extra.setUpdatedAt(now);
|
||||||
|
rowsToUpdate.add(extra);
|
||||||
|
duplicateRowZeroedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean repaired = false;
|
||||||
|
if (!rowsToUpdate.isEmpty()) {
|
||||||
|
saveOrUpdateBatch(rowsToUpdate);
|
||||||
|
repaired = true;
|
||||||
|
}
|
||||||
|
if (!rowsToInsert.isEmpty()) {
|
||||||
|
saveBatch(rowsToInsert);
|
||||||
|
repaired = true;
|
||||||
|
}
|
||||||
|
log.info("detail repair summary, tenantId={}, targetUserCount={}, updatedRows={}, insertedRows={}, duplicateRowsZeroed={}",
|
||||||
|
tenantId, yesterdayAmtByUserId.size(), rowsToUpdate.size(), rowsToInsert.size(), duplicateRowZeroedCount);
|
||||||
|
return new DetailRepairStat(repaired, rowsToUpdate.size(), rowsToInsert.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillDetailCheckResult(
|
||||||
|
Map<String, Object> checkInfo,
|
||||||
|
Map<String, BigDecimal> yesterdayAmtByUserId,
|
||||||
|
Map<String, BigDecimal> todayAmtByUserId,
|
||||||
|
int yesterdayDetailCount,
|
||||||
|
int todayDetailCount) {
|
||||||
|
List<Map<String, Object>> mismatchUsers = new ArrayList<>();
|
||||||
|
List<String> missingInTodayUsers = new ArrayList<>();
|
||||||
|
List<String> extraInTodayUsers = new ArrayList<>();
|
||||||
|
Set<String> allUserIds = new HashSet<>(yesterdayAmtByUserId.keySet());
|
||||||
|
allUserIds.addAll(todayAmtByUserId.keySet());
|
||||||
|
|
||||||
|
for (String userId : allUserIds) {
|
||||||
|
BigDecimal yesterdayUserAmt = yesterdayAmtByUserId.get(userId);
|
||||||
|
BigDecimal todayUserAmt = todayAmtByUserId.get(userId);
|
||||||
|
|
||||||
|
if (yesterdayUserAmt == null && todayUserAmt != null) {
|
||||||
|
extraInTodayUsers.add(userId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (yesterdayUserAmt != null && todayUserAmt == null) {
|
||||||
|
missingInTodayUsers.add(userId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
BigDecimal userDiff = defaultZero(yesterdayUserAmt).subtract(defaultZero(todayUserAmt)).abs();
|
||||||
|
if (userDiff.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
|
Map<String, Object> mismatch = new LinkedHashMap<>();
|
||||||
|
mismatch.put("userId", userId);
|
||||||
|
mismatch.put("yesterdayAmt", yesterdayUserAmt);
|
||||||
|
mismatch.put("todayAmt", todayUserAmt);
|
||||||
|
mismatch.put("diff", userDiff);
|
||||||
|
mismatchUsers.add(mismatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkInfo.put("needDetailCheck", true);
|
||||||
|
checkInfo.put("yesterdayDetailCount", yesterdayDetailCount);
|
||||||
|
checkInfo.put("todayDetailCount", todayDetailCount);
|
||||||
|
checkInfo.put("mismatchUsers", mismatchUsers);
|
||||||
|
checkInfo.put("missingInTodayUsers", missingInTodayUsers);
|
||||||
|
checkInfo.put("extraInTodayUsers", extraInTodayUsers);
|
||||||
|
checkInfo.put("detailCheckPassed", mismatchUsers.isEmpty() && missingInTodayUsers.isEmpty() && extraInTodayUsers.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class DetailRepairStat {
|
||||||
|
final boolean repaired;
|
||||||
|
final int updatedCount;
|
||||||
|
final int insertedCount;
|
||||||
|
|
||||||
|
DetailRepairStat(boolean repaired, int updatedCount, int insertedCount) {
|
||||||
|
this.repaired = repaired;
|
||||||
|
this.updatedCount = updatedCount;
|
||||||
|
this.insertedCount = insertedCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static BigDecimal defaultZero(BigDecimal value) {
|
private static BigDecimal defaultZero(BigDecimal value) {
|
||||||
return value == null ? BigDecimal.ZERO : value;
|
return value == null ? BigDecimal.ZERO : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
/**
|
||||||
|
* 按日期范围统计“本人 + 递归团队”的交易金额。
|
||||||
|
*
|
||||||
|
* 设计说明:
|
||||||
|
* 1) 先用交易报表圈定“本次需要返回”的用户集合(scopeUserIds),并聚合每个用户 own 金额;
|
||||||
|
* 2) 再用部门关系构建 parentUserId -> childrenUserIds 的树;
|
||||||
|
* 3) 对 scope 中每个用户递归汇总 team 金额(不含本人);
|
||||||
|
* 4) 返回 own/team/total 三套字段,便于前端直接展示。
|
||||||
|
*
|
||||||
|
* 注意:
|
||||||
|
* - 交易报表必须带 tenant 过滤,否则会混入其他租户用户导致层级结果异常;
|
||||||
|
* - lb_department_user.parent_id 在历史数据中可能是“父user_id”或“父记录id(UUID)”,此处做兼容解析。
|
||||||
|
*/
|
||||||
public Map<String, Object> listUserTradeAmountWithTeamByDateRangeAndTenant(
|
public Map<String, Object> listUserTradeAmountWithTeamByDateRangeAndTenant(
|
||||||
LocalDate startDate, LocalDate endDate, String tenantId) {
|
LocalDate startDate, LocalDate endDate, String tenantId) {
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
@@ -122,13 +442,19 @@ public class LbDailyUserTradeReportServiceImpl extends ServiceImpl<LbDailyUserTr
|
|||||||
LocalDateTime rangeStart = startDate.atStartOfDay();
|
LocalDateTime rangeStart = startDate.atStartOfDay();
|
||||||
LocalDateTime rangeEnd = endDate.atTime(23, 59, 59);
|
LocalDateTime rangeEnd = endDate.atTime(23, 59, 59);
|
||||||
|
|
||||||
// 1) 先查 lb_daily_user_trade_report:圈定日期内有数据的用户,并汇总本人四类金额
|
// 1) 先查 lb_daily_user_trade_report:
|
||||||
|
// - 仅取当前租户 + 指定日期范围 + report_detail;
|
||||||
|
// - 用于圈定返回范围(scopeUserIds);
|
||||||
|
// - 同时把本人四类金额先聚合好(ownAmtByUserId)。
|
||||||
LambdaQueryWrapper<LbDailyUserTradeReport> tradeQw = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<LbDailyUserTradeReport> tradeQw = new LambdaQueryWrapper<>();
|
||||||
tradeQw.ge(LbDailyUserTradeReport::getReportDate, rangeStart)
|
tradeQw.ge(LbDailyUserTradeReport::getReportDate, rangeStart)
|
||||||
.le(LbDailyUserTradeReport::getReportDate, rangeEnd)
|
.le(LbDailyUserTradeReport::getReportDate, rangeEnd)
|
||||||
|
.eq(LbDailyUserTradeReport::getTenantId, tenantIdTrim)
|
||||||
.eq(LbDailyUserTradeReport::getDataType, DATA_TYPE_REPORT_DETAIL );
|
.eq(LbDailyUserTradeReport::getDataType, DATA_TYPE_REPORT_DETAIL );
|
||||||
|
|
||||||
|
// userId -> 姓名,优先用报表昵称,后续再用部门姓名补齐。
|
||||||
Map<String, String> userIdToName = new HashMap<>();
|
Map<String, String> userIdToName = new HashMap<>();
|
||||||
|
// userId -> 本人金额汇总(own)。
|
||||||
Map<String, TradeAmtBundle> ownAmtByUserId = new HashMap<>();
|
Map<String, TradeAmtBundle> ownAmtByUserId = new HashMap<>();
|
||||||
List<LbDailyUserTradeReport> list1 = list(tradeQw);
|
List<LbDailyUserTradeReport> list1 = list(tradeQw);
|
||||||
|
|
||||||
@@ -156,14 +482,37 @@ public class LbDailyUserTradeReportServiceImpl extends ServiceImpl<LbDailyUserTr
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 最终只返回这些用户(报表范围内“有交易数据”的用户)。
|
||||||
Set<String> scopeUserIds = new HashSet<>(ownAmtByUserId.keySet());
|
Set<String> scopeUserIds = new HashSet<>(ownAmtByUserId.keySet());
|
||||||
|
|
||||||
// 2) 再查 lb_department_user:同一租户下的上下级(parent_id 为父用户 user_id),用于递归下级汇总
|
// 2) 再查 lb_department_user,构建上下级关系:
|
||||||
|
// 目标结构:childrenByParentUserId[parentUserId] = [childUserId...]
|
||||||
|
// 兼容 parent_id 两种格式:
|
||||||
|
// - 直接存父 user_id;
|
||||||
|
// - 存父记录 id(UUID),需先反查成父 user_id。
|
||||||
Map<String, List<String>> childrenByParentUserId = new HashMap<>();
|
Map<String, List<String>> childrenByParentUserId = new HashMap<>();
|
||||||
|
// childUserId -> parentUserId,用于后续把平铺数据组装成树。
|
||||||
|
Map<String, String> parentByUserId = new HashMap<>();
|
||||||
List<LbDepartmentUser> deptUsers = lbDepartmentUserService.list(
|
List<LbDepartmentUser> deptUsers = lbDepartmentUserService.list(
|
||||||
new LambdaQueryWrapper<LbDepartmentUser>()
|
new LambdaQueryWrapper<LbDepartmentUser>()
|
||||||
.eq(LbDepartmentUser::getTenantId, tenantIdTrim));
|
.eq(LbDepartmentUser::getTenantId, tenantIdTrim));
|
||||||
if (deptUsers != null) {
|
if (deptUsers != null) {
|
||||||
|
// 预处理索引:
|
||||||
|
// - userIdByRecordId: 记录id(UUID) -> userId
|
||||||
|
// - allUserIds: 全部 userId(用于判断 parent_id 是否已是 userId)
|
||||||
|
Map<String, String> userIdByRecordId = new HashMap<>();
|
||||||
|
Set<String> allUserIds = new HashSet<>();
|
||||||
|
for (LbDepartmentUser row : deptUsers) {
|
||||||
|
String rowId = row.getId() == null ? null : row.getId().trim();
|
||||||
|
String uid = row.getUserId() == null ? null : row.getUserId().trim();
|
||||||
|
if (uid == null || uid.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
allUserIds.add(uid);
|
||||||
|
if (rowId != null && !rowId.isEmpty()) {
|
||||||
|
userIdByRecordId.put(rowId, uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
for (LbDepartmentUser row : deptUsers) {
|
for (LbDepartmentUser row : deptUsers) {
|
||||||
if (row.getUserId() == null || row.getUserId().trim().isEmpty()) {
|
if (row.getUserId() == null || row.getUserId().trim().isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -172,12 +521,37 @@ public class LbDailyUserTradeReportServiceImpl extends ServiceImpl<LbDailyUserTr
|
|||||||
if (row.getName() != null && !row.getName().trim().isEmpty()) {
|
if (row.getName() != null && !row.getName().trim().isEmpty()) {
|
||||||
userIdToName.putIfAbsent(uid, row.getName().trim());
|
userIdToName.putIfAbsent(uid, row.getName().trim());
|
||||||
}
|
}
|
||||||
String parentUserId = row.getParentId() == null ? null : row.getParentId().trim();
|
// parentRef 可能是“父userId”或“父记录id”,不要直接假设其一定是 userId。
|
||||||
if (parentUserId != null && !parentUserId.isEmpty()) {
|
String parentRef = row.getParentId() == null ? null : row.getParentId().trim();
|
||||||
|
if (parentRef != null && !parentRef.isEmpty()) {
|
||||||
|
String parentUserId = null;
|
||||||
|
String resolvedBy = null;
|
||||||
|
// case A:parent_id 本身就是父 userId
|
||||||
|
if (allUserIds.contains(parentRef)) {
|
||||||
|
parentUserId = parentRef;
|
||||||
|
resolvedBy = "parent_user_id";
|
||||||
|
// case B:parent_id 是父记录id(UUID),反查映射到父 userId
|
||||||
|
} else if (userIdByRecordId.containsKey(parentRef)) {
|
||||||
|
parentUserId = userIdByRecordId.get(parentRef);
|
||||||
|
resolvedBy = "parent_record_id";
|
||||||
|
}
|
||||||
|
// 两种都解析失败:说明关系数据不完整/不一致,记录日志便于排查。
|
||||||
|
if (parentUserId == null || parentUserId.isEmpty()) {
|
||||||
|
log.warn("dept relation parent unresolved, tenantId={}, userId={}, name={}, parentId={}",
|
||||||
|
tenantIdTrim, uid, row.getName(), parentRef);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
List<String> ch = childrenByParentUserId.computeIfAbsent(parentUserId, k -> new ArrayList<>());
|
List<String> ch = childrenByParentUserId.computeIfAbsent(parentUserId, k -> new ArrayList<>());
|
||||||
if (!ch.contains(uid)) {
|
if (!ch.contains(uid)) {
|
||||||
ch.add(uid);
|
ch.add(uid);
|
||||||
}
|
}
|
||||||
|
// 若同一个子节点出现多次,保留第一次解析到的父节点,避免树结构抖动。
|
||||||
|
parentByUserId.putIfAbsent(uid, parentUserId);
|
||||||
|
// 针对已知问题用户打定位日志,便于验证 parent_id 解析是否正确。
|
||||||
|
if ("98155".equals(uid) || "杨渺渺".equals(row.getName())) {
|
||||||
|
log.info("dept relation resolved for focus user, tenantId={}, userId={}, name={}, parentId={}, parentUserId={}, resolvedBy={}",
|
||||||
|
tenantIdTrim, uid, row.getName(), parentRef, parentUserId, resolvedBy);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (List<String> list : childrenByParentUserId.values()) {
|
for (List<String> list : childrenByParentUserId.values()) {
|
||||||
@@ -185,12 +559,15 @@ public class LbDailyUserTradeReportServiceImpl extends ServiceImpl<LbDailyUserTr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) 仅对报表圈定的用户计算递归下级 team(无部门数据时 team 全为 0)
|
// 3) 对 scope 用户计算递归 team 金额:
|
||||||
|
// - team = 所有下级 own + 下级的 team(不含本人);
|
||||||
|
// - 使用 memo 缓存避免重复递归。
|
||||||
Map<String, TradeAmtBundle> teamAmtMemo = new HashMap<>();
|
Map<String, TradeAmtBundle> teamAmtMemo = new HashMap<>();
|
||||||
for (String userId : scopeUserIds) {
|
for (String userId : scopeUserIds) {
|
||||||
teamAmtRecursive(userId, childrenByParentUserId, ownAmtByUserId, teamAmtMemo, new HashSet<>());
|
teamAmtRecursive(userId, childrenByParentUserId, ownAmtByUserId, teamAmtMemo, new HashSet<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4) 先组装平铺结果,保持 own/team/total 三套字段并按 totalDiffAmt 倒序。
|
||||||
List<Map<String, Object>> rows = new ArrayList<>();
|
List<Map<String, Object>> rows = new ArrayList<>();
|
||||||
for (String userId : scopeUserIds) {
|
for (String userId : scopeUserIds) {
|
||||||
TradeAmtBundle own = ownAmt(userId, ownAmtByUserId);
|
TradeAmtBundle own = ownAmt(userId, ownAmtByUserId);
|
||||||
@@ -211,13 +588,39 @@ public class LbDailyUserTradeReportServiceImpl extends ServiceImpl<LbDailyUserTr
|
|||||||
row.put("ownDiffAmt", own.diffAmt);
|
row.put("ownDiffAmt", own.diffAmt);
|
||||||
row.put("teamDiffAmt", team.diffAmt);
|
row.put("teamDiffAmt", team.diffAmt);
|
||||||
row.put("totalDiffAmt", total.diffAmt);
|
row.put("totalDiffAmt", total.diffAmt);
|
||||||
|
// children 用于前端树形展示。
|
||||||
|
row.put("children", new ArrayList<Map<String, Object>>());
|
||||||
rows.add(row);
|
rows.add(row);
|
||||||
}
|
}
|
||||||
rows.sort(Comparator.comparing((Map<String, Object> m) -> (BigDecimal) m.get("totalDiffAmt")).reversed());
|
rows.sort(Comparator.comparing((Map<String, Object> m) -> (BigDecimal) m.get("totalDiffAmt")).reversed());
|
||||||
|
|
||||||
|
// 5) 将平铺数据转为树形结构:
|
||||||
|
// - 仅挂载 scope(报表内有交易数据)节点,避免返回无交易的空节点;
|
||||||
|
// - 若父节点不在 scope,则当前节点作为根节点返回。
|
||||||
|
Map<String, Map<String, Object>> rowByUserId = new HashMap<>();
|
||||||
|
for (Map<String, Object> row : rows) {
|
||||||
|
rowByUserId.put((String) row.get("userId"), row);
|
||||||
|
}
|
||||||
|
List<Map<String, Object>> treeRows = new ArrayList<>();
|
||||||
|
for (Map<String, Object> row : rows) {
|
||||||
|
String userId = (String) row.get("userId");
|
||||||
|
String parentUserId = parentByUserId.get(userId);
|
||||||
|
Map<String, Object> parentRow = parentUserId == null ? null : rowByUserId.get(parentUserId);
|
||||||
|
if (parentRow == null) {
|
||||||
|
treeRows.add(row);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<Map<String, Object>> children = (List<Map<String, Object>>) parentRow.get("children");
|
||||||
|
children.add(row);
|
||||||
|
}
|
||||||
|
sortTreeByTotalDiffDesc(treeRows);
|
||||||
|
|
||||||
result.put("success", true);
|
result.put("success", true);
|
||||||
result.put("message", "查询成功");
|
result.put("message", "查询成功");
|
||||||
result.put("data", rows);
|
// data 改为树形,flatData 保留平铺,便于前端平滑改造。
|
||||||
|
result.put("data", treeRows);
|
||||||
|
result.put("flatData", rows);
|
||||||
result.put("total", rows.size());
|
result.put("total", rows.size());
|
||||||
result.put("startDate", startDate);
|
result.put("startDate", startDate);
|
||||||
result.put("endDate", endDate);
|
result.put("endDate", endDate);
|
||||||
@@ -236,7 +639,26 @@ public class LbDailyUserTradeReportServiceImpl extends ServiceImpl<LbDailyUserTr
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 递归下级(不含本人)在日期范围内四类金额汇总;parent_id 为父用户 user_id。
|
* 递归按 totalDiffAmt 倒序排序树节点,确保每层展示稳定。
|
||||||
|
*/
|
||||||
|
private static void sortTreeByTotalDiffDesc(List<Map<String, Object>> nodes) {
|
||||||
|
nodes.sort(Comparator.comparing((Map<String, Object> m) -> (BigDecimal) m.get("totalDiffAmt")).reversed());
|
||||||
|
for (Map<String, Object> node : nodes) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<Map<String, Object>> children = (List<Map<String, Object>>) node.get("children");
|
||||||
|
if (children != null && !children.isEmpty()) {
|
||||||
|
sortTreeByTotalDiffDesc(children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归计算某个用户的 team 金额(不含本人)。
|
||||||
|
*
|
||||||
|
* 规则:
|
||||||
|
* - sum = Σ(每个child的own + child的team);
|
||||||
|
* - memo 命中即直接返回,避免重复计算;
|
||||||
|
* - stack 用于防环(脏数据导致 A->B->A 时返回 0,避免死循环)。
|
||||||
*/
|
*/
|
||||||
private TradeAmtBundle teamAmtRecursive(
|
private TradeAmtBundle teamAmtRecursive(
|
||||||
String parentUserId,
|
String parentUserId,
|
||||||
@@ -244,10 +666,12 @@ public class LbDailyUserTradeReportServiceImpl extends ServiceImpl<LbDailyUserTr
|
|||||||
Map<String, TradeAmtBundle> ownAmtByUserId,
|
Map<String, TradeAmtBundle> ownAmtByUserId,
|
||||||
Map<String, TradeAmtBundle> memo,
|
Map<String, TradeAmtBundle> memo,
|
||||||
Set<String> stack) {
|
Set<String> stack) {
|
||||||
|
// 动态规划缓存:同一节点只算一次。
|
||||||
TradeAmtBundle cached = memo.get(parentUserId);
|
TradeAmtBundle cached = memo.get(parentUserId);
|
||||||
if (cached != null) {
|
if (cached != null) {
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
// 防止环形关系导致无限递归。
|
||||||
if (!stack.add(parentUserId)) {
|
if (!stack.add(parentUserId)) {
|
||||||
return TradeAmtBundle.zeros();
|
return TradeAmtBundle.zeros();
|
||||||
}
|
}
|
||||||
@@ -267,7 +691,10 @@ public class LbDailyUserTradeReportServiceImpl extends ServiceImpl<LbDailyUserTr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 日期范围内按用户汇总的四类金额(与报表字段对应)。 */
|
/**
|
||||||
|
* 日期范围内按用户汇总的四类金额(与报表字段对应)。
|
||||||
|
* 该对象既可表示 own,也可表示 team 或 total。
|
||||||
|
*/
|
||||||
private static final class TradeAmtBundle {
|
private static final class TradeAmtBundle {
|
||||||
BigDecimal yestodayBuyAmt = BigDecimal.ZERO;
|
BigDecimal yestodayBuyAmt = BigDecimal.ZERO;
|
||||||
BigDecimal dailySellAmt = BigDecimal.ZERO;
|
BigDecimal dailySellAmt = BigDecimal.ZERO;
|
||||||
|
|||||||
@@ -260,6 +260,7 @@ public class LbDepartmentUserServiceImpl extends ServiceImpl<LbDepartmentUserMap
|
|||||||
new LambdaQueryWrapper<LbDailyUserTrade>()
|
new LambdaQueryWrapper<LbDailyUserTrade>()
|
||||||
.ge(LbDailyUserTrade::getReportDate, startDate)
|
.ge(LbDailyUserTrade::getReportDate, startDate)
|
||||||
.le(LbDailyUserTrade::getReportDate, endDate)
|
.le(LbDailyUserTrade::getReportDate, endDate)
|
||||||
|
.ge(LbDailyUserTrade::getDailyBuyAmt, 1)
|
||||||
.isNotNull(LbDailyUserTrade::getNickname)
|
.isNotNull(LbDailyUserTrade::getNickname)
|
||||||
.orderByDesc(LbDailyUserTrade::getUpdatedAt)
|
.orderByDesc(LbDailyUserTrade::getUpdatedAt)
|
||||||
.orderByDesc(LbDailyUserTrade::getCreatedAt)
|
.orderByDesc(LbDailyUserTrade::getCreatedAt)
|
||||||
|
|||||||
Reference in New Issue
Block a user