release: prepare RealeseV2.3

This commit is contained in:
hectorzhao
2026-08-06 10:48:36 +08:00
parent 57b58f1c40
commit 8ad8e61793
37 changed files with 997 additions and 64 deletions
@@ -3,7 +3,7 @@ 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 type { MessageQuery, TraceQuery, OperationLogQuery, GatewaySubmitDeadLetterQuery, ReceiptAnomalyQuery, DownstreamDeliveryQuery, DownstreamDeliveryDashboardQuery, DownstreamRecoveryStatusQuery, MessageSegmentAuditQuery, SignatureQualityQuery } from '../operations.contracts';
import { 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 downstream query domain. Method bodies are preserved byte-for-byte from the facade baseline.
@@ -73,6 +73,67 @@ async listGatewaySubmitDeadLetters(query: GatewaySubmitDeadLetterQuery) {
},
};
}
async listReceiptAnomalies(query: ReceiptAnomalyQuery) {
const page = Math.max(1, Number(query.page ?? 1));
const pageSize = Math.min(100, Math.max(1, Number(query.pageSize ?? 10)));
const baseWhere: Prisma.SmsReceiptAnomalyWhereInput = {
tenantId: query.tenantId,
applicationId: query.applicationId,
channelId: query.channelId,
anomalyType: query.anomalyType && query.anomalyType !== 'all' ? query.anomalyType : undefined,
OR: query.keyword ? [
{ anomalyKey: { contains: query.keyword } },
{ rawStatus: { contains: query.keyword } },
{ errorCode: { contains: query.keyword } },
{ messageRecord: { messageId: { contains: query.keyword } } },
{ submitRecord: { submitId: { contains: query.keyword } } },
] : undefined,
};
const where: Prisma.SmsReceiptAnomalyWhereInput = {
...baseWhere,
status: query.status && query.status !== 'all' ? query.status : undefined,
};
const [items, total, statusGroups, oldestPending] = await Promise.all([
this.prisma.smsReceiptAnomaly.findMany({
where,
include: {
tenant: { select: { id: true, name: true } },
application: { select: { id: true, name: true } },
channel: { select: { id: true, code: true, name: true, status: true } },
messageRecord: { select: { messageId: true, phoneNumber: true, status: true } },
submitRecord: { select: { submitId: true, submitStatus: true } },
receiptRecord: { select: { gatewayMessageId: true, receiptStatus: true, rawStatus: true, deliveredAt: true } },
},
orderBy: [{ lastOccurredAt: 'desc' }, { id: 'desc' }],
skip: (page - 1) * pageSize,
take: pageSize,
}),
this.prisma.smsReceiptAnomaly.count({ where }),
this.prisma.smsReceiptAnomaly.groupBy({
by: ['status'],
where: baseWhere,
_count: { _all: true },
}),
this.prisma.smsReceiptAnomaly.findFirst({
where: { ...baseWhere, status: 'pending' },
orderBy: { firstOccurredAt: 'asc' },
select: { firstOccurredAt: true },
}),
]);
const statusCounts = new Map(statusGroups.map((item) => [item.status, item._count._all]));
return {
items,
total,
page,
pageSize,
summary: {
pending: statusCounts.get('pending') ?? 0,
resolved: statusCounts.get('resolved') ?? 0,
ignored: statusCounts.get('ignored') ?? 0,
oldestPendingAt: oldestPending?.firstOccurredAt ?? null,
},
};
}
async listDownstreamDeliveries(query: DownstreamDeliveryQuery) {
const page = Math.max(1, Number(query.page ?? 1));
const pageSize = Math.min(100, Math.max(1, Number(query.pageSize ?? 10)));