diff --git a/src/main/java/com/rj/dto/LbBuyerTradeStats.java b/src/main/java/com/rj/dto/LbBuyerTradeStats.java new file mode 100644 index 0000000..6bd0784 --- /dev/null +++ b/src/main/java/com/rj/dto/LbBuyerTradeStats.java @@ -0,0 +1,18 @@ +package com.rj.dto; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 买家在 lb_order_row 中的最新已支付订单统计。 + */ +@Data +public class LbBuyerTradeStats { + + private Long buyerId; + + private String payTime; + + private BigDecimal totalMoney; +} diff --git a/src/main/java/com/rj/mapper/LbOrderRowMapper.java b/src/main/java/com/rj/mapper/LbOrderRowMapper.java index 00f5762..dd7d779 100644 --- a/src/main/java/com/rj/mapper/LbOrderRowMapper.java +++ b/src/main/java/com/rj/mapper/LbOrderRowMapper.java @@ -2,6 +2,7 @@ package com.rj.mapper; import com.baomidou.mybatisplus.annotation.InterceptorIgnore; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.rj.dto.LbBuyerTradeStats; import com.rj.entity.LbOrderRow; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @@ -16,4 +17,10 @@ public interface LbOrderRowMapper extends BaseMapper { */ @InterceptorIgnore(tenantLine = "true") int upsertBatch(@Param("list") List list); + + /** + * 按租户与买家汇总:每位买家取 pay_time 最新(相同时取 id 最大)的已支付订单。 + */ + List selectLatestPaidStatsByTenantId(@Param("tenantId") String tenantId, + @Param("dataType") String dataType); } diff --git a/src/main/java/com/rj/service/ILbDepartmentUserService.java b/src/main/java/com/rj/service/ILbDepartmentUserService.java index 6f80786..fd86e88 100644 --- a/src/main/java/com/rj/service/ILbDepartmentUserService.java +++ b/src/main/java/com/rj/service/ILbDepartmentUserService.java @@ -41,7 +41,8 @@ public interface ILbDepartmentUserService extends IService { Map syncFromLbUser(String tenantId); /** - * 按租户查询部门用户并构建父子树(parent_id 对应 lb_user.pid)。 + * 按租户从 lb_user 构建父子树(pid 为上级用户 ID); + * is_active / last_trade_date / last_buy_amt 从 lb_order_row 汇总读取。 */ Map getDepartmentUserTree(String tenantId, Integer isActive, LocalDate joinDate, LocalDate lastTradeDate); diff --git a/src/main/java/com/rj/service/impl/LbDepartmentUserServiceImpl.java b/src/main/java/com/rj/service/impl/LbDepartmentUserServiceImpl.java index 51f86ec..82916e9 100644 --- a/src/main/java/com/rj/service/impl/LbDepartmentUserServiceImpl.java +++ b/src/main/java/com/rj/service/impl/LbDepartmentUserServiceImpl.java @@ -3,6 +3,7 @@ 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.dto.LbBuyerTradeStats; import com.rj.entity.LbDailyUserTrade; import com.rj.entity.LbDepartmentUser; import com.rj.entity.LbOrderRow; @@ -492,27 +493,7 @@ public class LbDepartmentUserServiceImpl extends ServiceImpl tradeActiveUserIds = new HashSet<>(); - Map latestOrderByBuyerId = new HashMap<>(); - List orderRows = lbOrderRowMapper.selectList( - new LambdaQueryWrapper() - .eq(LbOrderRow::getTenantId, tenantIdTrim) - .eq(LbOrderRow::getDataType, ORDER_DATA_TYPE_DETAIL) - .isNotNull(LbOrderRow::getBuyerId) - .isNotNull(LbOrderRow::getPayTime)); - if (orderRows != null) { - for (LbOrderRow row : orderRows) { - if (!isPaidOrderRow(row)) { - continue; - } - String buyerUserId = String.valueOf(row.getBuyerId()); - tradeActiveUserIds.add(buyerUserId); - LbOrderRow prev = latestOrderByBuyerId.get(buyerUserId); - if (prev == null || isOrderPayTimeNewer(row, prev)) { - latestOrderByBuyerId.put(buyerUserId, row); - } - } - } + Map tradeStatsByUserId = loadBuyerTradeStatsFromOrderRow(tenantIdTrim); List toInsert = new ArrayList<>(); List toUpdate = new ArrayList<>(); @@ -556,16 +537,17 @@ public class LbDepartmentUserServiceImpl extends ServiceImpl queryWrapper = new LambdaQueryWrapper() - .eq(LbDepartmentUser::getTenantId, tenantIdTrim); - if (isActive != null) { - queryWrapper.eq(LbDepartmentUser::getIsActive, isActive); - } - if (joinDate != null) { - queryWrapper.gt(LbDepartmentUser::getJoinDate, joinDate); - } - if (lastTradeDate != null) { - queryWrapper.gt(LbDepartmentUser::getLastTradeDate, lastTradeDate); - } - List deptUsers = this.list(queryWrapper); - if (deptUsers == null || deptUsers.isEmpty()) { + List lbUsers = lbUserMapper.selectList( + new LambdaQueryWrapper().eq(LbUser::getTenantId, tenantIdTrim)); + if (lbUsers == null || lbUsers.isEmpty()) { result.put("success", true); result.put("message", "查询成功"); result.put("data", new ArrayList<>()); @@ -696,40 +668,67 @@ public class LbDepartmentUserServiceImpl extends ServiceImpl userRecordMap = new LinkedHashMap<>(); - for (LbDepartmentUser row : deptUsers) { - String uid = trimToNull(row.getUserId()); - if (uid == null) { + Map userById = new LinkedHashMap<>(); + for (LbUser lbUser : lbUsers) { + if (lbUser.getId() != null) { + userById.put(String.valueOf(lbUser.getId()), lbUser); + } + } + + Map tradeStatsByUserId = loadBuyerTradeStatsFromOrderRow(tenantIdTrim); + boolean hasFilter = isActive != null || joinDate != null || lastTradeDate != null; + + Set matchedUserIds = new HashSet<>(); + Map joinDateByUserId = new HashMap<>(); + Map lastTradeDateByUserId = new HashMap<>(); + Map lastBuyAmtByUserId = new HashMap<>(); + Map isActiveByUserId = new HashMap<>(); + for (LbUser lbUser : lbUsers) { + if (lbUser.getId() == null) { continue; } - LbDepartmentUser existing = userRecordMap.get(uid); - if (existing == null || isNewerDepartmentUser(row, existing)) { - userRecordMap.put(uid, row); + 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); } } - Map userIdByRecordId = new HashMap<>(); - Set allUserIds = new HashSet<>(userRecordMap.keySet()); - for (LbDepartmentUser row : deptUsers) { - String rowId = trimToNull(row.getId()); - String uid = trimToNull(row.getUserId()); - if (rowId != null && uid != null) { - userIdByRecordId.put(rowId, uid); - } - } - - Map parentByUserId = new HashMap<>(); - for (LbDepartmentUser row : userRecordMap.values()) { - String uid = row.getUserId().trim(); - String parentUserId = resolveParentUserId(row, allUserIds, userIdByRecordId); - if (parentUserId != null) { - parentByUserId.put(uid, parentUserId); - } - } + Set visibleUserIds = hasFilter + ? includeAncestorUserIds(matchedUserIds, userById) + : new HashSet<>(userById.keySet()); Map> nodeByUserId = new LinkedHashMap<>(); - for (LbDepartmentUser row : userRecordMap.values()) { - nodeByUserId.put(row.getUserId().trim(), toDepartmentUserTreeNode(row)); + Map parentByUserId = 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); + } } List> treeRoots = new ArrayList<>(); @@ -747,11 +746,12 @@ public class LbDepartmentUserServiceImpl extends ServiceImpl> treeData = skipFirstTreeLevel(treeRoots); result.put("success", true); result.put("message", "查询成功"); - result.put("data", treeRoots); - result.put("total", nodeByUserId.size()); + result.put("data", treeData); + result.put("total", countTreeNodes(treeData)); result.put("tenantId", tenantIdTrim); return result; } catch (Exception e) { @@ -762,46 +762,122 @@ public class LbDepartmentUserServiceImpl extends ServiceImpl allUserIds, - Map userIdByRecordId) { - String parentRef = trimToNull(row.getParentId()); - if (parentRef == null) { - return null; + private static List> skipFirstTreeLevel(List> treeRoots) { + List> result = new ArrayList<>(); + for (Map root : treeRoots) { + @SuppressWarnings("unchecked") + List> children = (List>) root.get("children"); + if (children != null && !children.isEmpty()) { + result.addAll(children); + } } - if (allUserIds.contains(parentRef)) { - return parentRef; - } - return userIdByRecordId.get(parentRef); + sortDepartmentUserTreeByName(result); + return result; } - private static Map toDepartmentUserTreeNode(LbDepartmentUser row) { - Map node = new LinkedHashMap<>(); - node.put("userId", trimToNull(row.getUserId())); - node.put("name", trimToNull(row.getName())); - node.put("phone", trimToNull(row.getPhone())); - node.put("joinDate", row.getJoinDate()); - node.put("lastTradeDate", row.getLastTradeDate()); - node.put("lastBuyAmt", row.getLastBuyAmt()); - node.put("isActive", row.getIsActive()); - node.put("children", new ArrayList>()); - return node; + private static int countTreeNodes(List> nodes) { + int count = 0; + for (Map node : nodes) { + count++; + @SuppressWarnings("unchecked") + List> children = (List>) node.get("children"); + if (children != null && !children.isEmpty()) { + count += countTreeNodes(children); + } + } + return count; } - private static boolean isNewerDepartmentUser(LbDepartmentUser candidate, LbDepartmentUser existing) { - LocalDateTime candidateTime = candidate.getUpdateTime() != null - ? candidate.getUpdateTime() : candidate.getCreateTime(); - LocalDateTime existingTime = existing.getUpdateTime() != null - ? existing.getUpdateTime() : existing.getCreateTime(); - if (candidateTime == null) { + private static boolean matchesTreeFilter(int userIsActive, + LocalDate userJoinDate, + LocalDate userLastTradeDate, + Integer isActive, + LocalDate joinDate, + LocalDate lastTradeDate) { + if (isActive != null && userIsActive != isActive) { return false; } - if (existingTime == null) { - return true; + if (joinDate != null && (userJoinDate == null || !userJoinDate.isAfter(joinDate))) { + return false; } - return candidateTime.isAfter(existingTime); + return lastTradeDate == null + || (userLastTradeDate != null && userLastTradeDate.isAfter(lastTradeDate)); + } + + /** + * 筛选命中用户时,沿 lb_user.pid 向上补全祖先,避免子节点因上级被筛掉而挂错层级。 + */ + private static Set includeAncestorUserIds(Set userIds, Map userById) { + Set expanded = new HashSet<>(userIds); + for (String userId : userIds) { + LbUser current = userById.get(userId); + Set visited = new HashSet<>(); + while (current != null) { + Long pid = current.getPid(); + if (pid == null || pid <= 0 || !visited.add(pid)) { + break; + } + String parentUserId = String.valueOf(pid); + if (!userById.containsKey(parentUserId)) { + break; + } + expanded.add(parentUserId); + current = userById.get(parentUserId); + } + } + return expanded; + } + + /** + * 从 lb_user.pid 解析上级 userId;pid 必须在同租户用户索引中存在。 + */ + private static String resolveLbUserParentId(LbUser user, Map userById) { + if (user == null || user.getPid() == null || user.getPid() <= 0) { + return null; + } + String parentUserId = String.valueOf(user.getPid()); + return userById.containsKey(parentUserId) ? parentUserId : null; + } + + /** + * 一次 SQL 从 lb_order_row 汇总每位买家的最新已支付订单统计。 + */ + private Map loadBuyerTradeStatsFromOrderRow(String tenantId) { + List rows = lbOrderRowMapper.selectLatestPaidStatsByTenantId( + tenantId, ORDER_DATA_TYPE_DETAIL); + Map statsByUserId = new HashMap<>(); + if (rows == null || rows.isEmpty()) { + return statsByUserId; + } + for (LbBuyerTradeStats row : rows) { + if (row.getBuyerId() == null) { + continue; + } + statsByUserId.put(String.valueOf(row.getBuyerId()), row); + } + return statsByUserId; + } + + private static Map toLbUserTreeNode(LbUser user, + LocalDate joinDate, + LocalDate lastTradeDate, + BigDecimal lastBuyAmt, + int isActive) { + Map node = new LinkedHashMap<>(); + node.put("userId", user.getId() == null ? null : String.valueOf(user.getId())); + node.put("name", trimToNull(resolveDisplayName(user))); + node.put("phone", trimToNull(user.getMobile())); + Long pid = user.getPid(); + node.put("parentId", pid == null || pid <= 0 ? null : String.valueOf(pid)); + node.put("parentName", trimToNull(user.getPname())); + node.put("joinDate", joinDate); + node.put("lastTradeDate", lastTradeDate); + node.put("lastBuyAmt", lastBuyAmt); + node.put("isActive", isActive); + node.put("children", new ArrayList>()); + return node; } private static void sortDepartmentUserTreeByName(List> nodes) { diff --git a/src/main/resources/mapper/LbOrderRowMapper.xml b/src/main/resources/mapper/LbOrderRowMapper.xml index 3e1c35b..1a75c44 100644 --- a/src/main/resources/mapper/LbOrderRowMapper.xml +++ b/src/main/resources/mapper/LbOrderRowMapper.xml @@ -59,4 +59,29 @@ today_order_count = VALUES(today_order_count) + +