fix: correct operational statistics and form interactions
This commit is contained in:
@@ -634,7 +634,7 @@ describe('OperationsService', () => {
|
||||
},
|
||||
today: expect.objectContaining({
|
||||
returnedCents: 10,
|
||||
segmentCount: 20,
|
||||
segmentCount: 2,
|
||||
deliveredSegmentCount: 18,
|
||||
arrivalRate: 90,
|
||||
billedCents: 360,
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { moneyToNumber } from '../../common/money';
|
||||
import { PrismaService } from '../../prisma/prisma.service';
|
||||
import type { MessageQuery, TraceQuery, OperationLogQuery, GatewaySubmitDeadLetterQuery, DownstreamDeliveryQuery, DownstreamDeliveryDashboardQuery, DownstreamRecoveryStatusQuery, MessageSegmentAuditQuery, SignatureQualityQuery } from '../operations.contracts';
|
||||
import { messageWhere, recognizedCarrierValues, carrierWhere, startOfShanghaiDay, endOfShanghaiDay, qualityBusinessDay, shanghaiDateKey, normalizeGroupBy, returnedTransactionWhere, createdAtRange, downstreamAlertPendingMinutes, downstreamAlertRecentFailedHours, downstreamAlertWindows, downstreamAlertWhere, stalledPendingWhere, downstreamDeliveryScopedWhere, parseDateBoundary, downstreamRecoveryStatusWhere, escapeCsvCell, formatCsvDate, formatExportTimestamp, clientApplicationView, clientReceiptView, clientMessageView, clientBatchTaskView, clientUplinkView, clientAccountView, clientRechargeView, summarizeMessageGroups, groupDownstreamByType, groupDownstreamByApplication, positiveInteger, operationLogLevelWhere, normalizeOperationLog, sanitizeGatewaySubmitException, redactGatewayCommandValue } from '../operations.helpers';
|
||||
|
||||
import {
|
||||
messageWhere,
|
||||
qualityBusinessDay,
|
||||
returnedTransactionWhere,
|
||||
downstreamAlertWindows,
|
||||
stalledPendingWhere,
|
||||
clientBatchTaskView,
|
||||
clientAccountView,
|
||||
clientRechargeView,
|
||||
summarizeMessageGroups,
|
||||
} from '../operations.helpers';
|
||||
|
||||
// R2 dashboard query domain. Method bodies are preserved byte-for-byte from the facade baseline.
|
||||
export class OperationsDashboardQueries {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async dashboard(query: { tenantId?: string }) {
|
||||
async dashboard(query: { tenantId?: string }) {
|
||||
const businessDay = qualityBusinessDay();
|
||||
const sinceToday = businessDay.startAt;
|
||||
const downstreamAlertWindow = downstreamAlertWindows();
|
||||
@@ -94,13 +102,15 @@ async dashboard(query: { tenantId?: string }) {
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 10,
|
||||
}),
|
||||
this.prisma.$queryRaw<Array<{
|
||||
tenantId: string;
|
||||
tenantName: string;
|
||||
todaySpendCents: bigint;
|
||||
balanceCents: bigint;
|
||||
creditCents: bigint;
|
||||
}>>(Prisma.sql`
|
||||
this.prisma.$queryRaw<
|
||||
Array<{
|
||||
tenantId: string;
|
||||
tenantName: string;
|
||||
todaySpendCents: bigint;
|
||||
balanceCents: bigint;
|
||||
creditCents: bigint;
|
||||
}>
|
||||
>(Prisma.sql`
|
||||
SELECT
|
||||
tenant.id AS "tenantId",
|
||||
tenant.name AS "tenantName",
|
||||
@@ -118,12 +128,14 @@ 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`
|
||||
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",
|
||||
@@ -208,11 +220,13 @@ async dashboard(query: { tenantId?: string }) {
|
||||
updatedAt: { gte: downstreamAlertWindow.recentFailedAt },
|
||||
},
|
||||
}),
|
||||
this.prisma.$queryRaw<Array<{
|
||||
hour: number;
|
||||
submittedCount: bigint;
|
||||
successCount: bigint;
|
||||
}>>(Prisma.sql`
|
||||
this.prisma.$queryRaw<
|
||||
Array<{
|
||||
hour: number;
|
||||
submittedCount: bigint;
|
||||
successCount: bigint;
|
||||
}>
|
||||
>(Prisma.sql`
|
||||
SELECT
|
||||
EXTRACT(
|
||||
HOUR FROM (message."queuedAt" AT TIME ZONE 'UTC') AT TIME ZONE 'Asia/Shanghai'
|
||||
@@ -227,11 +241,13 @@ async dashboard(query: { tenantId?: string }) {
|
||||
ORDER BY 1
|
||||
`),
|
||||
// Signature/template tables have no review timestamps, so their latest pending audit is paired with the review audit.
|
||||
this.prisma.$queryRaw<Array<{
|
||||
category: string;
|
||||
count: bigint;
|
||||
averageProcessingMs: bigint | null;
|
||||
}>>(Prisma.sql`
|
||||
this.prisma.$queryRaw<
|
||||
Array<{
|
||||
category: string;
|
||||
count: bigint;
|
||||
averageProcessingMs: bigint | null;
|
||||
}>
|
||||
>(Prisma.sql`
|
||||
WITH review_samples AS (
|
||||
SELECT
|
||||
'enterpriseCertifications'::text AS category,
|
||||
@@ -302,7 +318,8 @@ async dashboard(query: { tenantId?: string }) {
|
||||
]);
|
||||
const todayTotals = summarizeMessageGroups(todayMessageGroups);
|
||||
const todayBusinessMetrics = todayBusinessMetricsRows[0];
|
||||
const segmentCount = Number(todayBusinessMetrics?.segmentCount ?? 0);
|
||||
const supplierSegmentCount = Number(todayBusinessMetrics?.segmentCount ?? 0);
|
||||
const segmentCount = todayTotals.billingUnits;
|
||||
const deliveredSegmentCount = Number(todayBusinessMetrics?.deliveredSegmentCount ?? 0);
|
||||
const billedCents = moneyToNumber(todayBusinessMetrics?.billedCents);
|
||||
const costCents = moneyToNumber(todayBusinessMetrics?.costCents);
|
||||
@@ -334,7 +351,8 @@ async dashboard(query: { tenantId?: string }) {
|
||||
averageProcessingMs: row?.averageProcessingMs == null ? null : Number(row.averageProcessingMs),
|
||||
};
|
||||
});
|
||||
const downstreamAlertCount = downstreamStalledPendingCount + downstreamStalledAckCount + downstreamRecentFailedCount;
|
||||
const downstreamAlertCount =
|
||||
downstreamStalledPendingCount + downstreamStalledAckCount + downstreamRecentFailedCount;
|
||||
return {
|
||||
taskCount,
|
||||
messageStatus: messageGroups,
|
||||
@@ -349,7 +367,8 @@ async dashboard(query: { tenantId?: string }) {
|
||||
billingUnits: todayTotals.billingUnits,
|
||||
segmentCount,
|
||||
deliveredSegmentCount,
|
||||
arrivalRate: segmentCount > 0 ? Number(((deliveredSegmentCount / segmentCount) * 100).toFixed(1)) : 0,
|
||||
arrivalRate:
|
||||
supplierSegmentCount > 0 ? Number(((deliveredSegmentCount / supplierSegmentCount) * 100).toFixed(1)) : 0,
|
||||
billedCents,
|
||||
profitCents,
|
||||
profitRate: billedCents > 0 ? Number(((profitCents / billedCents) * 100).toFixed(1)) : 0,
|
||||
@@ -383,7 +402,7 @@ async dashboard(query: { tenantId?: string }) {
|
||||
recentRecharges,
|
||||
};
|
||||
}
|
||||
async clientDashboard(query: { tenantId?: string }) {
|
||||
async clientDashboard(query: { tenantId?: string }) {
|
||||
const tenantId = query.tenantId;
|
||||
const [dashboard, tenant, approvedCertification, signatureCount, pendingBatchTaskCount] = await Promise.all([
|
||||
this.dashboard(query),
|
||||
@@ -432,7 +451,7 @@ async clientDashboard(query: { tenantId?: string }) {
|
||||
},
|
||||
};
|
||||
}
|
||||
pendingAudits(tenantId?: string) {
|
||||
pendingAudits(tenantId?: string) {
|
||||
return Promise.all([
|
||||
this.prisma.smsTemplate.count({ where: { tenantId, auditStatus: 'pending' } }),
|
||||
this.prisma.smsSignature.count({ where: { tenantId, auditStatus: 'pending' } }),
|
||||
|
||||
Reference in New Issue
Block a user