fix(admin-service): 个人/团队认种数量改为统计棵数而非订单数

【问题】
getUserList 及推荐树节点中「个人认种」「团队认种」显示的是订单条数
(_count: { id }),而非实际认种棵数。一笔订单可认种多棵,导致多棵
合并下单的用户数量严重偏低。

【修复】
将以下方法中的所有 count(orders) 改为 sum(treeCount):
- getPersonalAdoptionCount   — 用户详情页个人认种数
- getTeamStats               — 用户详情页团队认种数
- getBatchUserStats          — 用户列表批量统计(个人/团队/省/市认种数)
- getAncestors               — 推荐树祖先节点认种数
- getDirectReferrals         — 推荐树直推节点认种数

【影响范围】
仅影响 admin-web 管理后台的展示数据,不涉及业务逻辑和数据存储。
省市认种百分比计算基准同步修正(teamAdoptions 也改为棵数),
比例结果不变,但基数更准确。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-03 22:29:32 -08:00
parent f8f37a2e33
commit 728728bee3
1 changed files with 39 additions and 22 deletions

View File

@ -86,14 +86,16 @@ export class UserDetailQueryRepositoryImpl implements IUserDetailQueryRepository
// 注意:优先从 referrals 获取 accountSequences因为用户可能不存在于 user_query_view // 注意:优先从 referrals 获取 accountSequences因为用户可能不存在于 user_query_view
const referralAccountSequences = referrals.map(r => r.accountSequence); const referralAccountSequences = referrals.map(r => r.accountSequence);
const [adoptionCounts, directReferralCounts, teamStats] = await Promise.all([ const [adoptionCounts, directReferralCounts, teamStats] = await Promise.all([
// 统计每个用户的认种订单数量(状态为 MINING_ENABLED // 统计每个用户的认种棵数(状态为 MINING_ENABLED
// 注意:使用 _sum.treeCount 而非 _count.id因为一笔订单可以认种多棵
// 显示的是棵数(认种数量),不是订单条数。
this.prisma.plantingOrderQueryView.groupBy({ this.prisma.plantingOrderQueryView.groupBy({
by: ['accountSequence'], by: ['accountSequence'],
where: { where: {
accountSequence: { in: referralAccountSequences }, accountSequence: { in: referralAccountSequences },
status: 'MINING_ENABLED', status: 'MINING_ENABLED',
}, },
_count: { id: true }, _sum: { treeCount: true },
}), }),
// 统计每个用户的直推数量 // 统计每个用户的直推数量
this.prisma.referralQueryView.groupBy({ this.prisma.referralQueryView.groupBy({
@ -105,7 +107,7 @@ export class UserDetailQueryRepositoryImpl implements IUserDetailQueryRepository
this.getBatchUserStats(referralAccountSequences), this.getBatchUserStats(referralAccountSequences),
]); ]);
const adoptionCountMap = new Map(adoptionCounts.map(a => [a.accountSequence, a._count.id])); const adoptionCountMap = new Map(adoptionCounts.map(a => [a.accountSequence, a._sum.treeCount ?? 0]));
const directCountMap = new Map( const directCountMap = new Map(
directReferralCounts directReferralCounts
.filter(d => d.referrerId !== null) .filter(d => d.referrerId !== null)
@ -169,14 +171,16 @@ export class UserDetailQueryRepositoryImpl implements IUserDetailQueryRepository
// 实时统计:获取每个用户的认种数量、团队认种量和直推数量 // 实时统计:获取每个用户的认种数量、团队认种量和直推数量
const userAccountSequences = directReferrals.map(r => r.accountSequence); const userAccountSequences = directReferrals.map(r => r.accountSequence);
const [adoptionCounts, directReferralCounts, teamStats] = await Promise.all([ const [adoptionCounts, directReferralCounts, teamStats] = await Promise.all([
// 统计每个用户的认种订单数量(状态为 MINING_ENABLED // 统计每个用户的认种棵数(状态为 MINING_ENABLED
// 注意:使用 _sum.treeCount 而非 _count.id因为一笔订单可以认种多棵
// 显示的是棵数(认种数量),不是订单条数。
this.prisma.plantingOrderQueryView.groupBy({ this.prisma.plantingOrderQueryView.groupBy({
by: ['accountSequence'], by: ['accountSequence'],
where: { where: {
accountSequence: { in: userAccountSequences }, accountSequence: { in: userAccountSequences },
status: 'MINING_ENABLED', status: 'MINING_ENABLED',
}, },
_count: { id: true }, _sum: { treeCount: true },
}), }),
// 统计每个用户的直推数量 // 统计每个用户的直推数量
this.prisma.referralQueryView.groupBy({ this.prisma.referralQueryView.groupBy({
@ -188,7 +192,7 @@ export class UserDetailQueryRepositoryImpl implements IUserDetailQueryRepository
this.getBatchUserStats(userAccountSequences), this.getBatchUserStats(userAccountSequences),
]); ]);
const adoptionCountMap = new Map(adoptionCounts.map(a => [a.accountSequence, a._count.id])); const adoptionCountMap = new Map(adoptionCounts.map(a => [a.accountSequence, a._sum.treeCount ?? 0]));
const directCountMap = new Map( const directCountMap = new Map(
directReferralCounts directReferralCounts
.filter(d => d.referrerId !== null) .filter(d => d.referrerId !== null)
@ -528,15 +532,18 @@ export class UserDetailQueryRepositoryImpl implements IUserDetailQueryRepository
} }
async getPersonalAdoptionCount(accountSequence: string): Promise<number> { async getPersonalAdoptionCount(accountSequence: string): Promise<number> {
// 统计用户的认种订单数量(状态为 MINING_ENABLED // 统计用户的认种棵数(状态为 MINING_ENABLED
const count = await this.prisma.plantingOrderQueryView.count({ // 注意:使用 aggregate._sum.treeCount 而非 count(),因为一笔订单可以认种多棵,
// 返回的是总棵数(认种数量),不是订单条数。
const result = await this.prisma.plantingOrderQueryView.aggregate({
where: { where: {
accountSequence, accountSequence,
status: 'MINING_ENABLED', status: 'MINING_ENABLED',
}, },
_sum: { treeCount: true },
}); });
return count; return result._sum.treeCount ?? 0;
} }
async getDirectReferralCount(accountSequence: string): Promise<number> { async getDirectReferralCount(accountSequence: string): Promise<number> {
@ -578,17 +585,19 @@ export class UserDetailQueryRepositoryImpl implements IUserDetailQueryRepository
const teamAddressCount = teamMembers.length; const teamAddressCount = teamMembers.length;
// 2. 获取团队认种量:汇总所有团队成员的有效认种订单数 // 2. 获取团队认种量:汇总所有团队成员的有效认种棵数
// 注意:使用 aggregate._sum.treeCount 而非 count(),因为一笔订单可以认种多棵。
let teamAdoptionCount = 0; let teamAdoptionCount = 0;
if (teamMembers.length > 0) { if (teamMembers.length > 0) {
const count = await this.prisma.plantingOrderQueryView.count({ const result = await this.prisma.plantingOrderQueryView.aggregate({
where: { where: {
accountSequence: { in: teamMembers.map((m) => m.accountSequence) }, accountSequence: { in: teamMembers.map((m) => m.accountSequence) },
status: 'MINING_ENABLED', status: 'MINING_ENABLED',
}, },
_sum: { treeCount: true },
}); });
teamAdoptionCount = count; teamAdoptionCount = result._sum.treeCount ?? 0;
} }
return { teamAddressCount, teamAdoptionCount }; return { teamAddressCount, teamAdoptionCount };
@ -611,17 +620,19 @@ export class UserDetailQueryRepositoryImpl implements IUserDetailQueryRepository
if (accountSequences.length === 0) return result; if (accountSequences.length === 0) return result;
// 1. 批量获取个人认种量 // 1. 批量获取个人认种棵数
// 注意:使用 _sum.treeCount 而非 _count.id因为一笔订单可以认种多棵
// 显示的是棵数(认种数量),不是订单条数。
const personalAdoptionCounts = await this.prisma.plantingOrderQueryView.groupBy({ const personalAdoptionCounts = await this.prisma.plantingOrderQueryView.groupBy({
by: ['accountSequence'], by: ['accountSequence'],
where: { where: {
accountSequence: { in: accountSequences }, accountSequence: { in: accountSequences },
status: 'MINING_ENABLED', status: 'MINING_ENABLED',
}, },
_count: { id: true }, _sum: { treeCount: true },
}); });
const personalAdoptionMap = new Map( const personalAdoptionMap = new Map(
personalAdoptionCounts.map(p => [p.accountSequence, p._count.id]) personalAdoptionCounts.map(p => [p.accountSequence, p._sum.treeCount ?? 0])
); );
// 2. 批量获取用户的省市信息(从认种订单中获取第一个订单的省市) // 2. 批量获取用户的省市信息(从认种订单中获取第一个订单的省市)
@ -674,35 +685,41 @@ export class UserDetailQueryRepositoryImpl implements IUserDetailQueryRepository
if (teamMembers.length > 0) { if (teamMembers.length > 0) {
const teamAccountSequences = teamMembers.map(m => m.accountSequence); const teamAccountSequences = teamMembers.map(m => m.accountSequence);
// 团队总认种 // 团队总认种棵数(使用 sum treeCount不是 count orders
teamAdoptionCount = await this.prisma.plantingOrderQueryView.count({ const teamResult = await this.prisma.plantingOrderQueryView.aggregate({
where: { where: {
accountSequence: { in: teamAccountSequences }, accountSequence: { in: teamAccountSequences },
status: 'MINING_ENABLED', status: 'MINING_ENABLED',
}, },
_sum: { treeCount: true },
}); });
teamAdoptionCount = teamResult._sum.treeCount ?? 0;
// 如果用户有省市信息,统计同省同市的认种 // 如果用户有省市信息,统计同省同市的认种棵数
if (userLocation?.province) { if (userLocation?.province) {
// 同省认种 // 同省认种棵数
provinceAdoptionCount = await this.prisma.plantingOrderQueryView.count({ const provinceResult = await this.prisma.plantingOrderQueryView.aggregate({
where: { where: {
accountSequence: { in: teamAccountSequences }, accountSequence: { in: teamAccountSequences },
status: 'MINING_ENABLED', status: 'MINING_ENABLED',
selectedProvince: userLocation.province, selectedProvince: userLocation.province,
}, },
_sum: { treeCount: true },
}); });
provinceAdoptionCount = provinceResult._sum.treeCount ?? 0;
// 同市认种 // 同市认种棵数
if (userLocation.city) { if (userLocation.city) {
cityAdoptionCount = await this.prisma.plantingOrderQueryView.count({ const cityResult = await this.prisma.plantingOrderQueryView.aggregate({
where: { where: {
accountSequence: { in: teamAccountSequences }, accountSequence: { in: teamAccountSequences },
status: 'MINING_ENABLED', status: 'MINING_ENABLED',
selectedProvince: userLocation.province, selectedProvince: userLocation.province,
selectedCity: userLocation.city, selectedCity: userLocation.city,
}, },
_sum: { treeCount: true },
}); });
cityAdoptionCount = cityResult._sum.treeCount ?? 0;
} }
} }
} }