feat: enhance operations dashboard and reporting controls
This commit is contained in:
@@ -32,6 +32,7 @@ async dashboard(query: { tenantId?: string }) {
|
||||
recentTasks,
|
||||
recentRecharges,
|
||||
enterpriseSpendRows,
|
||||
todayBusinessMetricsRows,
|
||||
downstreamPendingCount,
|
||||
downstreamFailedCount,
|
||||
downstreamDeliveredCount,
|
||||
@@ -117,6 +118,67 @@ async dashboard(query: { tenantId?: string }) {
|
||||
GROUP BY tenant.id, tenant.name, account."balanceCents", account."creditCents"
|
||||
ORDER BY "todaySpendCents" DESC, tenant.name ASC
|
||||
`),
|
||||
this.prisma.$queryRaw<Array<{
|
||||
segmentCount: bigint;
|
||||
deliveredSegmentCount: bigint;
|
||||
billedCents: bigint;
|
||||
costCents: bigint;
|
||||
}>>(Prisma.sql`
|
||||
WITH segment_metrics AS (
|
||||
SELECT
|
||||
COUNT(segment.id)::bigint AS "segmentCount",
|
||||
COUNT(segment.id) FILTER (WHERE segment."receiptStatus" = 'delivered')::bigint AS "deliveredSegmentCount"
|
||||
FROM "SmsMessageSegmentAudit" segment
|
||||
JOIN "SmsMessageRecord" message ON message.id = segment."messageRecordId"
|
||||
WHERE message."queuedAt" >= ${businessDay.startAt}
|
||||
AND message."queuedAt" < ${businessDay.endAt}
|
||||
AND (${query.tenantId ?? null}::text IS NULL OR message."tenantId" = ${query.tenantId ?? null})
|
||||
),
|
||||
message_revenue AS (
|
||||
SELECT COALESCE(SUM(
|
||||
CASE WHEN message.status = 'delivered' OR message."receiptStatus" = 'delivered'
|
||||
THEN message."billingUnits" * message."unitPrice" ELSE 0 END
|
||||
), 0)::bigint AS "billedCents"
|
||||
FROM "SmsMessageRecord" message
|
||||
WHERE message."queuedAt" >= ${businessDay.startAt}
|
||||
AND message."queuedAt" < ${businessDay.endAt}
|
||||
AND (${query.tenantId ?? null}::text IS NULL OR message."tenantId" = ${query.tenantId ?? null})
|
||||
),
|
||||
submit_cost AS (
|
||||
SELECT COALESCE(SUM(submit."costUnitPrice" * CASE
|
||||
WHEN segment_receipts.audit_count > 0 THEN segment_receipts.delivered_count
|
||||
WHEN legacy_receipt.delivered THEN message."billingUnits"
|
||||
ELSE 0
|
||||
END), 0)::bigint AS "costCents"
|
||||
FROM "SmsSubmitRecord" submit
|
||||
JOIN "SmsMessageRecord" message ON message.id = submit."messageRecordId"
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
COUNT(*)::integer AS audit_count,
|
||||
COUNT(*) FILTER (WHERE audit."receiptStatus" = 'delivered')::integer AS delivered_count
|
||||
FROM "SmsMessageSegmentAudit" audit
|
||||
WHERE audit."submitRecordId" = submit.id
|
||||
) segment_receipts ON TRUE
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM "SmsReceiptRecord" receipt
|
||||
WHERE receipt."gatewayMessageId" = submit."gatewayMessageId"
|
||||
AND receipt."channelId" = submit."channelId"
|
||||
AND receipt."receiptStatus" = 'delivered'
|
||||
) AS delivered
|
||||
) legacy_receipt ON TRUE
|
||||
WHERE submit."submitStatus" = 'accepted'
|
||||
AND message."queuedAt" >= ${businessDay.startAt}
|
||||
AND message."queuedAt" < ${businessDay.endAt}
|
||||
AND (${query.tenantId ?? null}::text IS NULL OR message."tenantId" = ${query.tenantId ?? null})
|
||||
)
|
||||
SELECT
|
||||
segment_metrics."segmentCount",
|
||||
segment_metrics."deliveredSegmentCount",
|
||||
message_revenue."billedCents",
|
||||
submit_cost."costCents"
|
||||
FROM segment_metrics, message_revenue, submit_cost
|
||||
`),
|
||||
this.prisma.cmppDownstreamDelivery.count({
|
||||
where: { tenantId: query.tenantId, status: 'pending' },
|
||||
}),
|
||||
@@ -239,6 +301,12 @@ async dashboard(query: { tenantId?: string }) {
|
||||
`),
|
||||
]);
|
||||
const todayTotals = summarizeMessageGroups(todayMessageGroups);
|
||||
const todayBusinessMetrics = todayBusinessMetricsRows[0];
|
||||
const segmentCount = Number(todayBusinessMetrics?.segmentCount ?? 0);
|
||||
const deliveredSegmentCount = Number(todayBusinessMetrics?.deliveredSegmentCount ?? 0);
|
||||
const billedCents = moneyToNumber(todayBusinessMetrics?.billedCents);
|
||||
const costCents = moneyToNumber(todayBusinessMetrics?.costCents);
|
||||
const profitCents = billedCents - costCents;
|
||||
const hourlyRowsByHour = new Map(hourlySendRows.map((row) => [Number(row.hour), row]));
|
||||
// Always return all 24 Shanghai-time buckets so the line chart does not imply missing hours are missing data.
|
||||
const hourlySendTrend = Array.from({ length: 24 }, (_, hour) => {
|
||||
@@ -279,6 +347,12 @@ async dashboard(query: { tenantId?: string }) {
|
||||
spendCents: todayTotals.amountCents,
|
||||
returnedCents: moneyToNumber(transactionAggregate._sum.amountCents),
|
||||
billingUnits: todayTotals.billingUnits,
|
||||
segmentCount,
|
||||
deliveredSegmentCount,
|
||||
arrivalRate: segmentCount > 0 ? Number(((deliveredSegmentCount / segmentCount) * 100).toFixed(1)) : 0,
|
||||
billedCents,
|
||||
profitCents,
|
||||
profitRate: billedCents > 0 ? Number(((profitCents / billedCents) * 100).toFixed(1)) : 0,
|
||||
},
|
||||
uplinkCount,
|
||||
billing: billingAggregate,
|
||||
|
||||
Reference in New Issue
Block a user