diff --git a/src/main/java/com/rj/controller/LbDailyUserTradeReportController.java b/src/main/java/com/rj/controller/LbDailyUserTradeReportController.java index 5603222..3e1ec4a 100644 --- a/src/main/java/com/rj/controller/LbDailyUserTradeReportController.java +++ b/src/main/java/com/rj/controller/LbDailyUserTradeReportController.java @@ -411,6 +411,64 @@ public class LbDailyUserTradeReportController { } } + @GetMapping("/stats/user-trade-with-team/by-date-range-and-tenant") + @Operation(summary = "按日期范围统计本人及递归下级交易金额", description = "根据开始日期、结束日期、租户ID,基于 lb_department_user 上下级(parent_id 为父用户 user_id)与 lb_daily_user_trade_report,对 yestoday_buy_amt、daily_sell_amt、daily_buy_amt、diff_amt 四类分别汇总 own*/team*/total*(team 为全部下级递归,不含本人);不含 data_type=report_sum") + public ResponseEntity> statsUserTradeWithTeamByDateRangeAndTenant( + @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 tenantId) { + Map result = new HashMap<>(); + try { + if (tenantId == null || tenantId.trim().isEmpty()) { + result.put("success", false); + result.put("message", "tenantId不能为空"); + 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); + } + + Map serviceResult = lbDailyUserTradeReportService + .listUserTradeAmountWithTeamByDateRangeAndTenant(parsedStartDate, parsedEndDate, tenantId.trim()); + Boolean success = (Boolean) serviceResult.get("success"); + if (Boolean.FALSE.equals(success)) { + return ResponseEntity.badRequest().body(serviceResult); + } + return ResponseEntity.ok(serviceResult); + } catch (Exception e) { + result.put("success", false); + result.put("message", "统计异常:" + e.getMessage()); + return ResponseEntity.internalServerError().body(result); + } + } + @PostMapping("/calculate-report-sum/by-date-and-tenant") @Operation(summary = "按日期和租户汇总报表", description = "根据报表日期和租户ID汇总报表字段并新增一条 report_sum 记录") public ResponseEntity> calculateReportSumByDateAndTenant( diff --git a/src/main/java/com/rj/service/ILbDailyUserTradeReportService.java b/src/main/java/com/rj/service/ILbDailyUserTradeReportService.java index 386dc22..e2876ab 100644 --- a/src/main/java/com/rj/service/ILbDailyUserTradeReportService.java +++ b/src/main/java/com/rj/service/ILbDailyUserTradeReportService.java @@ -13,4 +13,10 @@ import java.util.Map; */ public interface ILbDailyUserTradeReportService extends IService { Map calculateReportSumByDateAndTenant(LocalDate reportDate, String tenantId); + + /** + * 按日期范围与租户统计每人四类金额(昨日买入、当日卖出、当日买入、差额):本人汇总与递归下级汇总。 + */ + Map listUserTradeAmountWithTeamByDateRangeAndTenant( + LocalDate startDate, LocalDate endDate, String tenantId); } diff --git a/src/main/java/com/rj/service/impl/LbDailyUserTradeReportServiceImpl.java b/src/main/java/com/rj/service/impl/LbDailyUserTradeReportServiceImpl.java index 4e82766..6542d50 100644 --- a/src/main/java/com/rj/service/impl/LbDailyUserTradeReportServiceImpl.java +++ b/src/main/java/com/rj/service/impl/LbDailyUserTradeReportServiceImpl.java @@ -3,15 +3,24 @@ package com.rj.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.rj.entity.LbDailyUserTradeReport; +import com.rj.entity.LbDepartmentUser; import com.rj.mapper.LbDailyUserTradeReportMapper; import com.rj.service.ILbDailyUserTradeReportService; +import com.rj.service.ILbDepartmentUserService; +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; /** @@ -25,6 +34,9 @@ public class LbDailyUserTradeReportServiceImpl extends ServiceImpl calculateReportSumByDateAndTenant(LocalDate reportDate, String tenantId) { Map result = new HashMap<>(); @@ -81,7 +93,206 @@ public class LbDailyUserTradeReportServiceImpl extends ServiceImpl listUserTradeAmountWithTeamByDateRangeAndTenant( + LocalDate startDate, LocalDate endDate, String tenantId) { + Map result = new HashMap<>(); + if (tenantId == null || tenantId.trim().isEmpty()) { + result.put("success", false); + result.put("message", "tenantId不能为空"); + return result; + } + if (startDate == null || endDate == null) { + result.put("success", false); + result.put("message", "开始日期和结束日期不能为空"); + return result; + } + if (endDate.isBefore(startDate)) { + result.put("success", false); + result.put("message", "结束日期不能早于开始日期"); + return result; + } + + String tenantIdTrim = tenantId.trim(); + LocalDateTime rangeStart = startDate.atStartOfDay(); + LocalDateTime rangeEnd = endDate.atTime(23, 59, 59); + + List deptUsers = lbDepartmentUserService.list( + new LambdaQueryWrapper() + .eq(LbDepartmentUser::getTenantId, tenantIdTrim)); + if (deptUsers == null || deptUsers.isEmpty()) { + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", List.of()); + result.put("total", 0); + result.put("startDate", startDate); + result.put("endDate", endDate); + result.put("tenantId", tenantIdTrim); + return result; + } + + Map userIdToName = new HashMap<>(); + Set tenantUserIds = new HashSet<>(); + Map> childrenByParentUserId = new HashMap<>(); + for (LbDepartmentUser row : deptUsers) { + if (row.getUserId() == null || row.getUserId().trim().isEmpty()) { + continue; + } + String uid = row.getUserId().trim(); + tenantUserIds.add(uid); + if (row.getName() != null && !row.getName().trim().isEmpty()) { + userIdToName.putIfAbsent(uid, row.getName().trim()); + } + String parentUserId = row.getParentId() == null ? null : row.getParentId().trim(); + if (parentUserId != null && !parentUserId.isEmpty()) { + List ch = childrenByParentUserId.computeIfAbsent(parentUserId, k -> new ArrayList<>()); + if (!ch.contains(uid)) { + ch.add(uid); + } + } + } + for (List list : childrenByParentUserId.values()) { + list.sort(String::compareTo); + } + + LambdaQueryWrapper tradeQw = new LambdaQueryWrapper<>(); + tradeQw.eq(LbDailyUserTradeReport::getTenantId, tenantIdTrim) + .ge(LbDailyUserTradeReport::getReportDate, rangeStart) + .le(LbDailyUserTradeReport::getReportDate, rangeEnd) + .ne(LbDailyUserTradeReport::getDataType, DATA_TYPE_REPORT_SUM); + + Map ownAmtByUserId = new HashMap<>(); + for (LbDailyUserTradeReport r : list(tradeQw)) { + if (r.getUserId() == null || r.getUserId().trim().isEmpty()) { + continue; + } + String uid = r.getUserId().trim(); + ownAmtByUserId + .computeIfAbsent(uid, k -> TradeAmtBundle.zeros()) + .addFromReportRow(r); + if (r.getNickname() != null && !r.getNickname().trim().isEmpty()) { + userIdToName.putIfAbsent(uid, r.getNickname().trim()); + } + } + + Map teamAmtMemo = new HashMap<>(); + for (String userId : tenantUserIds) { + teamAmtRecursive(userId, childrenByParentUserId, ownAmtByUserId, teamAmtMemo, new HashSet<>()); + } + + List> rows = new ArrayList<>(); + for (String userId : tenantUserIds) { + TradeAmtBundle own = ownAmt(userId, ownAmtByUserId); + TradeAmtBundle team = teamAmt(userId, teamAmtMemo); + TradeAmtBundle total = own.addCopy(team); + Map row = new LinkedHashMap<>(); + row.put("userId", userId); + row.put("name", userIdToName.getOrDefault(userId, "")); + row.put("ownYestodayBuyAmt", own.yestodayBuyAmt); + row.put("teamYestodayBuyAmt", team.yestodayBuyAmt); + row.put("totalYestodayBuyAmt", total.yestodayBuyAmt); + row.put("ownDailySellAmt", own.dailySellAmt); + row.put("teamDailySellAmt", team.dailySellAmt); + row.put("totalDailySellAmt", total.dailySellAmt); + row.put("ownDailyBuyAmt", own.dailyBuyAmt); + row.put("teamDailyBuyAmt", team.dailyBuyAmt); + row.put("totalDailyBuyAmt", total.dailyBuyAmt); + row.put("ownDiffAmt", own.diffAmt); + row.put("teamDiffAmt", team.diffAmt); + row.put("totalDiffAmt", total.diffAmt); + rows.add(row); + } + rows.sort(Comparator.comparing((Map m) -> (BigDecimal) m.get("totalDiffAmt")).reversed()); + + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", rows); + result.put("total", rows.size()); + result.put("startDate", startDate); + result.put("endDate", endDate); + result.put("tenantId", tenantIdTrim); + return result; + } + + private static TradeAmtBundle ownAmt(String userId, Map ownAmtByUserId) { + TradeAmtBundle b = ownAmtByUserId.get(userId); + return b != null ? b : TradeAmtBundle.zeros(); + } + + private static TradeAmtBundle teamAmt(String userId, Map teamAmtMemo) { + TradeAmtBundle b = teamAmtMemo.get(userId); + return b != null ? b : TradeAmtBundle.zeros(); + } + + /** + * 递归下级(不含本人)在日期范围内四类金额汇总;parent_id 为父用户 user_id。 + */ + private TradeAmtBundle teamAmtRecursive( + String parentUserId, + Map> childrenByParentUserId, + Map ownAmtByUserId, + Map memo, + Set stack) { + TradeAmtBundle cached = memo.get(parentUserId); + if (cached != null) { + return cached; + } + if (!stack.add(parentUserId)) { + return TradeAmtBundle.zeros(); + } + try { + TradeAmtBundle sum = TradeAmtBundle.zeros(); + List children = childrenByParentUserId.get(parentUserId); + if (children != null) { + for (String childUserId : children) { + sum.addInPlace(ownAmt(childUserId, ownAmtByUserId)); + sum.addInPlace(teamAmtRecursive(childUserId, childrenByParentUserId, ownAmtByUserId, memo, stack)); + } + } + memo.put(parentUserId, sum); + return sum; + } finally { + stack.remove(parentUserId); + } + } + + /** 日期范围内按用户汇总的四类金额(与报表字段对应)。 */ + private static final class TradeAmtBundle { + BigDecimal yestodayBuyAmt = BigDecimal.ZERO; + BigDecimal dailySellAmt = BigDecimal.ZERO; + BigDecimal dailyBuyAmt = BigDecimal.ZERO; + BigDecimal diffAmt = BigDecimal.ZERO; + + static TradeAmtBundle zeros() { + return new TradeAmtBundle(); + } + + void addFromReportRow(LbDailyUserTradeReport r) { + yestodayBuyAmt = yestodayBuyAmt.add(defaultZero(r.getYestodayBuyAmt())); + dailySellAmt = dailySellAmt.add(defaultZero(r.getDailySellAmt())); + dailyBuyAmt = dailyBuyAmt.add(defaultZero(r.getDailyBuyAmt())); + diffAmt = diffAmt.add(defaultZero(r.getDiffAmt())); + } + + void addInPlace(TradeAmtBundle o) { + if (o == null) { + return; + } + yestodayBuyAmt = yestodayBuyAmt.add(defaultZero(o.yestodayBuyAmt)); + dailySellAmt = dailySellAmt.add(defaultZero(o.dailySellAmt)); + dailyBuyAmt = dailyBuyAmt.add(defaultZero(o.dailyBuyAmt)); + diffAmt = diffAmt.add(defaultZero(o.diffAmt)); + } + + TradeAmtBundle addCopy(TradeAmtBundle o) { + TradeAmtBundle n = zeros(); + n.addInPlace(this); + n.addInPlace(o != null ? o : zeros()); + return n; + } + } }