feat: 套餐使用统计增加MRR与ARR

This commit is contained in:
2025-12-15 21:29:54 +08:00
parent e39a42b74c
commit 7ff66cd8e7
2 changed files with 36 additions and 12 deletions

View File

@@ -24,5 +24,14 @@ public sealed class TenantPackageUsageDto
/// 历史总订阅记录数量(不含软删)。 /// 历史总订阅记录数量(不含软删)。
/// </summary> /// </summary>
public int TotalSubscriptionCount { get; init; } public int TotalSubscriptionCount { get; init; }
}
/// <summary>
/// MRRMonthly Recurring Revenue粗看按“当前有效订阅数 × 套餐月付等效价”估算。
/// </summary>
public decimal Mrr { get; init; }
/// <summary>
/// ARRAnnual Recurring Revenue粗看按“当前有效订阅数 × 套餐年付等效价”估算。
/// </summary>
public decimal Arr { get; init; }
}

View File

@@ -45,7 +45,9 @@ public sealed class GetTenantPackageUsagesQueryHandler(IDapperExecutor dapperExe
TenantPackageId = reader.GetInt64(0), TenantPackageId = reader.GetInt64(0),
ActiveSubscriptionCount = reader.GetInt32(1), ActiveSubscriptionCount = reader.GetInt32(1),
ActiveTenantCount = reader.GetInt32(2), ActiveTenantCount = reader.GetInt32(2),
TotalSubscriptionCount = reader.GetInt32(3) TotalSubscriptionCount = reader.GetInt32(3),
Mrr = reader.IsDBNull(4) ? 0m : reader.GetDecimal(4),
Arr = reader.IsDBNull(5) ? 0m : reader.GetDecimal(5)
}); });
} }
@@ -56,16 +58,17 @@ public sealed class GetTenantPackageUsagesQueryHandler(IDapperExecutor dapperExe
private static string BuildSql(long[]? ids, out (string Name, object? Value)[] parameters, DateTime now) private static string BuildSql(long[]? ids, out (string Name, object? Value)[] parameters, DateTime now)
{ {
// 1. 基础查询 // 1. 基础查询:先按订阅表聚合,再回连套餐表计算 MRR/ARR
var builder = new System.Text.StringBuilder(); var builder = new System.Text.StringBuilder();
builder.AppendLine(""" builder.AppendLine("""
select with stats as (
"TenantPackageId" as "TenantPackageId", select
count(*) filter (where "Status" = 1 and "EffectiveFrom" <= @now and "EffectiveTo" >= @now) as "ActiveSubscriptionCount", "TenantPackageId" as "TenantPackageId",
count(distinct "TenantId") filter (where "Status" = 1 and "EffectiveFrom" <= @now and "EffectiveTo" >= @now) as "ActiveTenantCount", count(*) filter (where "Status" = 1 and "EffectiveFrom" <= @now and "EffectiveTo" >= @now) as "ActiveSubscriptionCount",
count(*) as "TotalSubscriptionCount" count(distinct "TenantId") filter (where "Status" = 1 and "EffectiveFrom" <= @now and "EffectiveTo" >= @now) as "ActiveTenantCount",
from public.tenant_subscriptions count(*) as "TotalSubscriptionCount"
where "DeletedAt" is null from public.tenant_subscriptions
where "DeletedAt" is null
"""); """);
var list = new List<(string Name, object? Value)> var list = new List<(string Name, object? Value)>
@@ -92,8 +95,20 @@ public sealed class GetTenantPackageUsagesQueryHandler(IDapperExecutor dapperExe
builder.AppendLine(")"); builder.AppendLine(")");
} }
// 3. (空行后) 分组 // 3. (空行后) 分组与回连套餐表
builder.AppendLine("group by \"TenantPackageId\";"); builder.AppendLine("""
group by "TenantPackageId"
)
select
s."TenantPackageId" as "TenantPackageId",
s."ActiveSubscriptionCount" as "ActiveSubscriptionCount",
s."ActiveTenantCount" as "ActiveTenantCount",
s."TotalSubscriptionCount" as "TotalSubscriptionCount",
(s."ActiveSubscriptionCount"::numeric * coalesce(p."MonthlyPrice", (p."YearlyPrice" / 12.0), 0))::numeric(18, 2) as "Mrr",
(s."ActiveSubscriptionCount"::numeric * coalesce(p."YearlyPrice", (p."MonthlyPrice" * 12), 0))::numeric(18, 2) as "Arr"
from stats s
left join public.tenant_packages p on p."Id" = s."TenantPackageId" and p."DeletedAt" is null;
""");
parameters = list.ToArray(); parameters = list.ToArray();
return builder.ToString(); return builder.ToString();