部门交易金额
This commit is contained in:
@@ -144,6 +144,29 @@ public class LbDepartmentUserController {
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
@GetMapping("/treeSumTotalMoney")
|
||||
@Operation(summary = "部门用户树形结构(累计交易金额)")
|
||||
public ResponseEntity<Map<String, Object>> treeSumTotalMoney(
|
||||
@Parameter(description = "租户id", required = true) @RequestParam String tenantId,
|
||||
@Parameter(description = "是否活跃:1活跃 0不活跃") @RequestParam(name = "is_active", required = false) Integer isActive,
|
||||
@Parameter(description = "加入日期,查询 join_date 大于该日期的数据")
|
||||
@RequestParam(name = "join_date", required = false)
|
||||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate joinDate,
|
||||
@Parameter(description = "最后成交日期,查询 last_trade_date 大于该日期的数据")
|
||||
@RequestParam(name = "last_trade_date", required = false)
|
||||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate lastTradeDate,
|
||||
@Parameter(description = "交易开始时间(buy_time),格式:yyyy-MM-dd HH:mm:ss")
|
||||
@RequestParam(name = "buy_start_time", required = false) String buyStartTime,
|
||||
@Parameter(description = "交易结束时间(buy_time),格式:yyyy-MM-dd HH:mm:ss")
|
||||
@RequestParam(name = "buy_end_time", required = false) String buyEndTime) {
|
||||
Map<String, Object> result = lbDepartmentUserService.getDepartmentUserTreeCopy(tenantId, isActive, joinDate, lastTradeDate, buyStartTime, buyEndTime);
|
||||
Boolean success = (Boolean) result.get("success");
|
||||
if (success != null && success) {
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
@PostMapping("/exportUsers")
|
||||
@Operation(summary = "导出指定用户Excel",
|
||||
description = "按租户ID与用户ID列表导出:用户ID、用户名、手机号、加入时间、最早/最晚buy_time、累计交易金额")
|
||||
|
||||
@@ -7,7 +7,9 @@ import com.rj.entity.LbOrderRow;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mapper
|
||||
public interface LbOrderRowMapper extends BaseMapper<LbOrderRow> {
|
||||
@@ -23,4 +25,20 @@ public interface LbOrderRowMapper extends BaseMapper<LbOrderRow> {
|
||||
*/
|
||||
List<LbBuyerTradeStats> selectLatestPaidStatsByTenantId(@Param("tenantId") String tenantId,
|
||||
@Param("dataType") String dataType);
|
||||
|
||||
/**
|
||||
* 按租户、买家ID列表和时间范围查询订单总金额。
|
||||
*/
|
||||
BigDecimal selectSumTotalMoneyByBuyerIdsAndTimeRange(@Param("tenantId") String tenantId,
|
||||
@Param("buyerIds") List<String> buyerIds,
|
||||
@Param("buyStartTime") String buyStartTime,
|
||||
@Param("buyEndTime") String buyEndTime);
|
||||
|
||||
/**
|
||||
* 按租户、买家ID列表和时间范围分组查询每个买家的订单总金额。
|
||||
*/
|
||||
List<Map<String, Object>> selectSumTotalMoneyGroupByBuyerIds(@Param("tenantId") String tenantId,
|
||||
@Param("buyerIds") List<String> buyerIds,
|
||||
@Param("buyStartTime") String buyStartTime,
|
||||
@Param("buyEndTime") String buyEndTime);
|
||||
}
|
||||
|
||||
@@ -46,8 +46,17 @@ public interface ILbDepartmentUserService extends IService<LbDepartmentUser> {
|
||||
*/
|
||||
Map<String, Object> getDepartmentUserTree(String tenantId, Integer isActive, LocalDate joinDate, LocalDate lastTradeDate);
|
||||
|
||||
/**
|
||||
* 按租户从 lb_user 构建父子树(克隆)
|
||||
*/
|
||||
Map<String, Object> getDepartmentUserTreeCopy(String tenantId, Integer isActive, LocalDate joinDate, LocalDate lastTradeDate,
|
||||
String buyStartTime, String buyEndTime);
|
||||
|
||||
/**
|
||||
* 按租户与用户 ID 列表导出部门用户及订单汇总 Excel。
|
||||
*/
|
||||
void exportUsersExcel(String tenantId, List<String> userIds, HttpServletResponse response);
|
||||
|
||||
Map<String, Object> getDepartmentUserTreeSumTotalMoney(String tenantId, Integer isActive, LocalDate joinDate,
|
||||
LocalDate lastTradeDate, String buyStartTime, String buyEndTime);
|
||||
}
|
||||
|
||||
@@ -761,6 +761,170 @@ public class LbDepartmentUserServiceImpl extends ServiceImpl<LbDepartmentUserMap
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getDepartmentUserTreeCopy(String tenantId, Integer isActive, LocalDate joinDate, LocalDate lastTradeDate,
|
||||
String buyStartTime, String buyEndTime) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
if (tenantId == null || tenantId.trim().isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "租户id不能为空");
|
||||
return result;
|
||||
}
|
||||
String tenantIdTrim = tenantId.trim();
|
||||
|
||||
List<LbUser> lbUsers = lbUserMapper.selectList(
|
||||
new LambdaQueryWrapper<LbUser>().eq(LbUser::getTenantId, tenantIdTrim));
|
||||
if (lbUsers == null || lbUsers.isEmpty()) {
|
||||
result.put("success", true);
|
||||
result.put("message", "查询成功");
|
||||
result.put("data", new ArrayList<>());
|
||||
result.put("total", 0);
|
||||
result.put("tenantId", tenantIdTrim);
|
||||
return result;
|
||||
}
|
||||
|
||||
Map<String, LbUser> userById = new LinkedHashMap<>();
|
||||
for (LbUser lbUser : lbUsers) {
|
||||
if (lbUser.getId() != null) {
|
||||
userById.put(String.valueOf(lbUser.getId()), lbUser);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, LbBuyerTradeStats> tradeStatsByUserId = loadBuyerTradeStatsFromOrderRow(tenantIdTrim);
|
||||
boolean hasFilter = isActive != null || joinDate != null || lastTradeDate != null;
|
||||
|
||||
Set<String> matchedUserIds = new HashSet<>();
|
||||
Map<String, LocalDate> joinDateByUserId = new HashMap<>();
|
||||
Map<String, LocalDate> lastTradeDateByUserId = new HashMap<>();
|
||||
Map<String, BigDecimal> lastBuyAmtByUserId = new HashMap<>();
|
||||
Map<String, Integer> isActiveByUserId = new HashMap<>();
|
||||
for (LbUser lbUser : lbUsers) {
|
||||
if (lbUser.getId() == null) {
|
||||
continue;
|
||||
}
|
||||
String userId = String.valueOf(lbUser.getId());
|
||||
LocalDate userJoinDate = lbUser.getJoinTime() != null
|
||||
? lbUser.getJoinTime().toLocalDate() : null;
|
||||
LbBuyerTradeStats tradeStats = tradeStatsByUserId.get(userId);
|
||||
LocalDate userLastTradeDate = tradeStats == null
|
||||
? null : parseOrderTimeToDate(tradeStats.getPayTime());
|
||||
BigDecimal userLastBuyAmt = tradeStats == null ? null : tradeStats.getTotalMoney();
|
||||
int userIsActive = tradeStats == null ? 0 : 1;
|
||||
|
||||
joinDateByUserId.put(userId, userJoinDate);
|
||||
lastTradeDateByUserId.put(userId, userLastTradeDate);
|
||||
lastBuyAmtByUserId.put(userId, userLastBuyAmt);
|
||||
isActiveByUserId.put(userId, userIsActive);
|
||||
|
||||
if (!hasFilter || matchesTreeFilter(
|
||||
userIsActive, userJoinDate, userLastTradeDate, isActive, joinDate, lastTradeDate)) {
|
||||
matchedUserIds.add(userId);
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> visibleUserIds = hasFilter
|
||||
? includeAncestorUserIds(matchedUserIds, userById)
|
||||
: new HashSet<>(userById.keySet());
|
||||
|
||||
Map<String, Map<String, Object>> nodeByUserId = new LinkedHashMap<>();
|
||||
Map<String, String> parentByUserId = new HashMap<>();
|
||||
Map<String, List<String>> childrenByUserId = new HashMap<>();
|
||||
|
||||
for (String userId : visibleUserIds) {
|
||||
LbUser lbUser = userById.get(userId);
|
||||
if (lbUser == null) {
|
||||
continue;
|
||||
}
|
||||
nodeByUserId.put(userId, toLbUserTreeNode(
|
||||
lbUser,
|
||||
joinDateByUserId.get(userId),
|
||||
lastTradeDateByUserId.get(userId),
|
||||
lastBuyAmtByUserId.get(userId),
|
||||
isActiveByUserId.getOrDefault(userId, 0)));
|
||||
|
||||
String parentUserId = resolveLbUserParentId(lbUser, userById);
|
||||
if (parentUserId != null && visibleUserIds.contains(parentUserId)) {
|
||||
parentByUserId.put(userId, parentUserId);
|
||||
childrenByUserId.computeIfAbsent(parentUserId, k -> new ArrayList<>()).add(userId);
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasTradeTimeFilter = (buyStartTime != null && !buyStartTime.trim().isEmpty())
|
||||
|| (buyEndTime != null && !buyEndTime.trim().isEmpty());
|
||||
|
||||
Map<String, BigDecimal> tradeMoneySumByUserId = new HashMap<>();
|
||||
Map<String, BigDecimal> userTradeMoney = new HashMap<>();
|
||||
if (hasTradeTimeFilter) {
|
||||
if (!visibleUserIds.isEmpty()) {
|
||||
List<Map<String, Object>> tradeResults = lbOrderRowMapper.selectSumTotalMoneyGroupByBuyerIds(
|
||||
tenantIdTrim, new ArrayList<>(visibleUserIds), buyStartTime, buyEndTime);
|
||||
|
||||
for (Map<String, Object> row : tradeResults) {
|
||||
String buyerId = String.valueOf(row.get("buyerId"));
|
||||
BigDecimal totalMoney = (BigDecimal) row.get("totalMoney");
|
||||
userTradeMoney.put(buyerId, totalMoney != null ? totalMoney : BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
for (String userId : visibleUserIds) {
|
||||
BigDecimal sumAmount = calculateRecursiveTradeSumOptimized(userId, childrenByUserId, userTradeMoney);
|
||||
tradeMoneySumByUserId.put(userId, sumAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Map<String, Object>> treeRoots = new ArrayList<>();
|
||||
for (Map.Entry<String, Map<String, Object>> entry : nodeByUserId.entrySet()) {
|
||||
String userId = entry.getKey();
|
||||
Map<String, Object> node = entry.getValue();
|
||||
|
||||
if (hasTradeTimeFilter) {
|
||||
node.put("selfTradeMoney", userTradeMoney.getOrDefault(userId, BigDecimal.ZERO));
|
||||
node.put("tradeMoneySum", tradeMoneySumByUserId.getOrDefault(userId, BigDecimal.ZERO));
|
||||
}
|
||||
|
||||
String parentUserId = parentByUserId.get(userId);
|
||||
Map<String, Object> parentNode = parentUserId == null ? null : nodeByUserId.get(parentUserId);
|
||||
if (parentNode == null) {
|
||||
treeRoots.add(node);
|
||||
continue;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> children = (List<Map<String, Object>>) parentNode.get("children");
|
||||
children.add(node);
|
||||
}
|
||||
sortDepartmentUserTreeByName(treeRoots);
|
||||
List<Map<String, Object>> treeData = skipFirstTreeLevel(treeRoots);
|
||||
|
||||
result.put("success", true);
|
||||
result.put("message", "查询成功");
|
||||
result.put("data", treeData);
|
||||
result.put("total", countTreeNodes(treeData));
|
||||
result.put("tenantId", tenantIdTrim);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
result.put("success", false);
|
||||
result.put("message", "查询树形结构异常:" + e.getMessage());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化后的递归计算:从内存 Map 中获取数据,避免重复数据库查询
|
||||
*/
|
||||
private BigDecimal calculateRecursiveTradeSumOptimized(String userId, Map<String, List<String>> childrenByUserId,
|
||||
Map<String, BigDecimal> userTradeMoney) {
|
||||
BigDecimal sum = userTradeMoney.getOrDefault(userId, BigDecimal.ZERO);
|
||||
|
||||
List<String> children = childrenByUserId.get(userId);
|
||||
if (children != null && !children.isEmpty()) {
|
||||
for (String childId : children) {
|
||||
sum = sum.add(calculateRecursiveTradeSumOptimized(childId, childrenByUserId, userTradeMoney));
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* 去掉第 1 级根节点,将其 children 提升为返回给前端的顶层。
|
||||
*/
|
||||
@@ -1135,6 +1299,153 @@ public class LbDepartmentUserServiceImpl extends ServiceImpl<LbDepartmentUserMap
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getDepartmentUserTreeSumTotalMoney(String tenantId, Integer isActive, LocalDate joinDate, LocalDate lastTradeDate, String buyStartTime, String buyEndTime) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
if (tenantId == null || tenantId.trim().isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "租户id不能为空");
|
||||
return result;
|
||||
}
|
||||
String tenantIdTrim = tenantId.trim();
|
||||
|
||||
List<LbUser> lbUsers = lbUserMapper.selectList(
|
||||
new LambdaQueryWrapper<LbUser>().eq(LbUser::getTenantId, tenantIdTrim));
|
||||
if (lbUsers == null || lbUsers.isEmpty()) {
|
||||
result.put("success", true);
|
||||
result.put("message", "查询成功");
|
||||
result.put("data", new ArrayList<>());
|
||||
result.put("total", 0);
|
||||
result.put("tenantId", tenantIdTrim);
|
||||
return result;
|
||||
}
|
||||
|
||||
Map<String, LbUser> userById = new LinkedHashMap<>();
|
||||
for (LbUser lbUser : lbUsers) {
|
||||
if (lbUser.getId() != null) {
|
||||
userById.put(String.valueOf(lbUser.getId()), lbUser);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, LbBuyerTradeStats> tradeStatsByUserId = loadBuyerTradeStatsFromOrderRow(tenantIdTrim);
|
||||
boolean hasFilter = isActive != null || joinDate != null || lastTradeDate != null;
|
||||
|
||||
Set<String> matchedUserIds = new HashSet<>();
|
||||
Map<String, LocalDate> joinDateByUserId = new HashMap<>();
|
||||
Map<String, LocalDate> lastTradeDateByUserId = new HashMap<>();
|
||||
Map<String, BigDecimal> lastBuyAmtByUserId = new HashMap<>();
|
||||
Map<String, Integer> isActiveByUserId = new HashMap<>();
|
||||
for (LbUser lbUser : lbUsers) {
|
||||
if (lbUser.getId() == null) {
|
||||
continue;
|
||||
}
|
||||
String userId = String.valueOf(lbUser.getId());
|
||||
LocalDate userJoinDate = lbUser.getJoinTime() != null
|
||||
? lbUser.getJoinTime().toLocalDate() : null;
|
||||
LbBuyerTradeStats tradeStats = tradeStatsByUserId.get(userId);
|
||||
LocalDate userLastTradeDate = tradeStats == null
|
||||
? null : parseOrderTimeToDate(tradeStats.getPayTime());
|
||||
BigDecimal userLastBuyAmt = tradeStats == null ? null : tradeStats.getTotalMoney();
|
||||
int userIsActive = tradeStats == null ? 0 : 1;
|
||||
|
||||
joinDateByUserId.put(userId, userJoinDate);
|
||||
lastTradeDateByUserId.put(userId, userLastTradeDate);
|
||||
lastBuyAmtByUserId.put(userId, userLastBuyAmt);
|
||||
isActiveByUserId.put(userId, userIsActive);
|
||||
|
||||
if (!hasFilter || matchesTreeFilter(
|
||||
userIsActive, userJoinDate, userLastTradeDate, isActive, joinDate, lastTradeDate)) {
|
||||
matchedUserIds.add(userId);
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> visibleUserIds = hasFilter
|
||||
? includeAncestorUserIds(matchedUserIds, userById)
|
||||
: new HashSet<>(userById.keySet());
|
||||
|
||||
Map<String, Map<String, Object>> nodeByUserId = new LinkedHashMap<>();
|
||||
Map<String, String> parentByUserId = new HashMap<>();
|
||||
Map<String, List<String>> childrenByUserId = new HashMap<>();
|
||||
|
||||
for (String userId : visibleUserIds) {
|
||||
LbUser lbUser = userById.get(userId);
|
||||
if (lbUser == null) {
|
||||
continue;
|
||||
}
|
||||
nodeByUserId.put(userId, toLbUserTreeNode(
|
||||
lbUser,
|
||||
joinDateByUserId.get(userId),
|
||||
lastTradeDateByUserId.get(userId),
|
||||
lastBuyAmtByUserId.get(userId),
|
||||
isActiveByUserId.getOrDefault(userId, 0)));
|
||||
|
||||
String parentUserId = resolveLbUserParentId(lbUser, userById);
|
||||
if (parentUserId != null && visibleUserIds.contains(parentUserId)) {
|
||||
parentByUserId.put(userId, parentUserId);
|
||||
childrenByUserId.computeIfAbsent(parentUserId, k -> new ArrayList<>()).add(userId);
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasTradeTimeFilter = (buyStartTime != null && !buyStartTime.trim().isEmpty())
|
||||
|| (buyEndTime != null && !buyEndTime.trim().isEmpty());
|
||||
|
||||
Map<String, BigDecimal> tradeMoneySumByUserId = new HashMap<>();
|
||||
Map<String, BigDecimal> userTradeMoney = new HashMap<>();
|
||||
if (hasTradeTimeFilter) {
|
||||
if (!visibleUserIds.isEmpty()) {
|
||||
List<Map<String, Object>> tradeResults = lbOrderRowMapper.selectSumTotalMoneyGroupByBuyerIds(
|
||||
tenantIdTrim, new ArrayList<>(visibleUserIds), buyStartTime, buyEndTime);
|
||||
|
||||
for (Map<String, Object> row : tradeResults) {
|
||||
String buyerId = String.valueOf(row.get("buyerId"));
|
||||
BigDecimal totalMoney = (BigDecimal) row.get("totalMoney");
|
||||
userTradeMoney.put(buyerId, totalMoney != null ? totalMoney : BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
for (String userId : visibleUserIds) {
|
||||
BigDecimal sumAmount = calculateRecursiveTradeSumOptimized(userId, childrenByUserId, userTradeMoney);
|
||||
tradeMoneySumByUserId.put(userId, sumAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Map<String, Object>> treeRoots = new ArrayList<>();
|
||||
for (Map.Entry<String, Map<String, Object>> entry : nodeByUserId.entrySet()) {
|
||||
String userId = entry.getKey();
|
||||
Map<String, Object> node = entry.getValue();
|
||||
|
||||
if (hasTradeTimeFilter) {
|
||||
node.put("selfTradeMoney", userTradeMoney.getOrDefault(userId, BigDecimal.ZERO));
|
||||
node.put("tradeMoneySum", tradeMoneySumByUserId.getOrDefault(userId, BigDecimal.ZERO));
|
||||
}
|
||||
|
||||
String parentUserId = parentByUserId.get(userId);
|
||||
Map<String, Object> parentNode = parentUserId == null ? null : nodeByUserId.get(parentUserId);
|
||||
if (parentNode == null) {
|
||||
treeRoots.add(node);
|
||||
continue;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> children = (List<Map<String, Object>>) parentNode.get("children");
|
||||
children.add(node);
|
||||
}
|
||||
sortDepartmentUserTreeByName(treeRoots);
|
||||
List<Map<String, Object>> treeData = skipFirstTreeLevel(treeRoots);
|
||||
|
||||
result.put("success", true);
|
||||
result.put("message", "查询成功");
|
||||
result.put("data", treeData);
|
||||
result.put("total", countTreeNodes(treeData));
|
||||
result.put("tenantId", tenantIdTrim);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
result.put("success", false);
|
||||
result.put("message", "查询树形结构异常:" + e.getMessage());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class OrderBuyStats {
|
||||
private String earliestBuyTime;
|
||||
private LocalDateTime earliestBuyDateTime;
|
||||
|
||||
@@ -85,4 +85,37 @@
|
||||
)
|
||||
</select>
|
||||
|
||||
<select id="selectSumTotalMoneyByBuyerIdsAndTimeRange" resultType="java.math.BigDecimal">
|
||||
SELECT COALESCE(SUM(o.total_money), 0)
|
||||
FROM lb_order_row o
|
||||
WHERE o.tenant_id = #{tenantId}
|
||||
AND o.buyer_id IN
|
||||
<foreach collection="buyerIds" item="buyerId" open="(" separator="," close=")">
|
||||
#{buyerId}
|
||||
</foreach>
|
||||
<if test="buyStartTime != null and buyStartTime != ''">
|
||||
AND o.buy_time >= #{buyStartTime}
|
||||
</if>
|
||||
<if test="buyEndTime != null and buyEndTime != ''">
|
||||
AND o.buy_time <= #{buyEndTime}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<select id="selectSumTotalMoneyGroupByBuyerIds" resultType="map">
|
||||
SELECT o.buyer_id AS buyerId, COALESCE(SUM(o.total_money), 0) AS totalMoney
|
||||
FROM lb_order_row o
|
||||
WHERE o.tenant_id = #{tenantId}
|
||||
AND o.buyer_id IN
|
||||
<foreach collection="buyerIds" item="buyerId" open="(" separator="," close=")">
|
||||
#{buyerId}
|
||||
</foreach>
|
||||
<if test="buyStartTime != null and buyStartTime != ''">
|
||||
AND o.buy_time >= #{buyStartTime}
|
||||
</if>
|
||||
<if test="buyEndTime != null and buyEndTime != ''">
|
||||
AND o.buy_time <= #{buyEndTime}
|
||||
</if>
|
||||
GROUP BY o.buyer_id
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
Reference in New Issue
Block a user