93 lines
4.3 KiB
TypeScript
93 lines
4.3 KiB
TypeScript
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';
|
|
|
|
// R2 trace query domain. Method bodies are preserved byte-for-byte from the facade baseline.
|
|
export class OperationsTraceQueries {
|
|
constructor(private readonly prisma: PrismaService) {}
|
|
|
|
auditSummary(query: { tenantId?: string }) {
|
|
return this.prisma.operationLog.groupBy({
|
|
by: ['action', 'resource'],
|
|
where: { tenantId: query.tenantId },
|
|
_count: { _all: true },
|
|
orderBy: { _count: { action: 'desc' } },
|
|
take: 100,
|
|
});
|
|
}
|
|
async trace(query: TraceQuery) {
|
|
const messages = await this.prisma.smsMessageRecord.findMany({
|
|
where: {
|
|
...messageWhere(query),
|
|
messageId: query.messageId,
|
|
},
|
|
include: {
|
|
batchTask: { include: { apiRequests: true } },
|
|
submitRecords: { include: { session: true } },
|
|
receiptRecords: true,
|
|
},
|
|
orderBy: { queuedAt: 'desc' },
|
|
take: 100,
|
|
});
|
|
const messageIds = messages.map((message) => message.messageId);
|
|
const [billingRecords, uplinks] = await Promise.all([
|
|
this.prisma.smsBillingRecord.findMany({
|
|
where: {
|
|
tenantId: query.tenantId,
|
|
taskId: query.taskId,
|
|
messageId: messageIds.length > 0 ? { in: messageIds } : undefined,
|
|
},
|
|
orderBy: { createdAt: 'desc' },
|
|
}),
|
|
this.prisma.smsUplinkMessage.findMany({
|
|
where: {
|
|
tenantId: query.tenantId,
|
|
messageId: messageIds.length > 0 ? { in: messageIds } : undefined,
|
|
},
|
|
orderBy: { receivedAt: 'desc' },
|
|
}),
|
|
]);
|
|
return { messages, billingRecords, uplinks };
|
|
}
|
|
async reconciliation(query: { tenantId?: string; taskId?: string }) {
|
|
const [messages, billing, transactions] = await Promise.all([
|
|
this.prisma.smsMessageRecord.aggregate({
|
|
where: messageWhere({ tenantId: query.tenantId, taskId: query.taskId }),
|
|
_count: { _all: true },
|
|
_sum: { amountCents: true, billingUnits: true },
|
|
}),
|
|
this.prisma.smsBillingRecord.aggregate({
|
|
where: { tenantId: query.tenantId, taskId: query.taskId },
|
|
_count: { _all: true },
|
|
_sum: { amountCents: true, billingUnits: true },
|
|
}),
|
|
this.prisma.accountTransaction.aggregate({
|
|
where: {
|
|
tenantId: query.tenantId,
|
|
relatedType: query.taskId ? { in: ['sms_batch_task', 'sms_message_record'] } : undefined,
|
|
relatedId: query.taskId,
|
|
},
|
|
_count: { _all: true },
|
|
_sum: { amountCents: true },
|
|
}),
|
|
]);
|
|
const messageAmount = moneyToNumber(messages._sum.amountCents);
|
|
const billingAmount = moneyToNumber(billing._sum.amountCents);
|
|
const transactionAmount = moneyToNumber(transactions._sum.amountCents);
|
|
return {
|
|
messages,
|
|
billing,
|
|
transactions,
|
|
diff: {
|
|
messageVsBillingAmountCents: messageAmount - billingAmount,
|
|
billingVsTransactionAmountCents: billingAmount + transactionAmount,
|
|
messageVsBillingUnits: (messages._sum.billingUnits ?? 0) - (billing._sum.billingUnits ?? 0),
|
|
},
|
|
};
|
|
}
|
|
}
|