fix: align signature review and admin operations

This commit is contained in:
hectorzhao
2026-07-27 22:07:51 +08:00
parent 9891ffee23
commit df70b336a0
22 changed files with 326 additions and 57 deletions
+25 -3
View File
@@ -226,7 +226,7 @@ describe('OperationsService', () => {
tenant: true,
application: true,
channel: true,
submitRecords: { include: { channel: true } },
submitRecords: { include: { channel: true, channelGroup: true } },
receiptRecords: { include: { channel: true } },
downstreamDeliveries: {
where: { deliveryType: 'receipt' },
@@ -341,6 +341,13 @@ describe('OperationsService', () => {
it('builds dashboard and statistics aggregates', async () => {
const prisma = createPrismaMock();
prisma.$queryRaw.mockResolvedValueOnce([{
tenantId: 'tenant-1',
tenantName: '租户A',
todaySpendCents: 24000n,
balanceCents: 1000000n,
creditCents: 50000n,
}]);
prisma.cmppDownstreamDelivery.count = jest.fn()
.mockResolvedValueOnce(3)
.mockResolvedValueOnce(2)
@@ -365,6 +372,13 @@ describe('OperationsService', () => {
total: 5,
},
today: expect.objectContaining({ returnedCents: 10 }),
enterpriseSpendRanks: [{
tenantId: 'tenant-1',
tenantName: '租户A',
todaySpendCents: 24000,
balanceCents: 1000000,
creditCents: 50000,
}],
gatewayConnections: [{ status: 'connected', _count: { _all: 1 }, _sum: { currentConnections: 2, desiredConnections: 2 } }],
downstreamDeliverySummary: expect.objectContaining({
pending: 3,
@@ -445,6 +459,8 @@ describe('OperationsService', () => {
tenantName: '租户A',
hasDrainage: false,
total: 5,
acceptedCount: 4,
submitFailureCount: 1,
successCount: 3,
unknownCount: 1,
failureCount: 1,
@@ -488,7 +504,13 @@ describe('OperationsService', () => {
submitFailureRate: 20,
successRate: 60,
})],
signatures: [expect.objectContaining({ signatureId: 'signature-1', signatureName: '【测试签名】', hasDrainage: false })],
signatures: [expect.objectContaining({
signatureId: 'signature-1',
signatureName: '【测试签名】',
hasDrainage: false,
acceptedCount: 4,
submitFailureCount: 1,
})],
applications: [expect.objectContaining({ applicationId: 'app-1', applicationName: '通知应用', tenantName: '租户A', total: 5 })],
});
expect(prisma.$queryRaw).toHaveBeenCalledTimes(4);
@@ -868,7 +890,7 @@ describe('OperationsService', () => {
messageRecord: undefined,
},
include: { channel: true, submitRecord: true },
orderBy: [{ submitId: 'asc' }, { segmentIndex: 'asc' }],
orderBy: [{ createdAt: 'asc' }, { segmentIndex: 'asc' }, { id: 'asc' }],
});
});
});
+62 -9
View File
@@ -102,7 +102,7 @@ export class OperationsService {
tenant: true,
application: true,
channel: true,
submitRecords: { include: { channel: true } },
submitRecords: { include: { channel: true, channelGroup: true } },
receiptRecords: { include: { channel: true } },
downstreamDeliveries: {
where: { deliveryType: 'receipt' },
@@ -179,6 +179,7 @@ export class OperationsService {
async dashboard(query: { tenantId?: string }) {
const sinceToday = startOfToday();
const businessDay = qualityBusinessDay();
const downstreamAlertWindow = downstreamAlertWindows();
const messageWhereClause = messageWhere({ tenantId: query.tenantId });
const todayMessageWhereClause = { ...messageWhereClause, queuedAt: { gte: sinceToday } };
@@ -194,6 +195,7 @@ export class OperationsService {
tenantAccounts,
recentTasks,
recentRecharges,
enterpriseSpendRows,
downstreamPendingCount,
downstreamFailedCount,
downstreamDeliveredCount,
@@ -253,6 +255,29 @@ export class OperationsService {
orderBy: { createdAt: 'desc' },
take: 10,
}),
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",
COALESCE(SUM(billing."amountCents") FILTER (WHERE billing."billingStatus" = 'charged'), 0)::bigint AS "todaySpendCents",
account."balanceCents" AS "balanceCents",
account."creditCents" AS "creditCents"
FROM "TenantAccount" account
JOIN "Tenant" tenant ON tenant.id = account."tenantId"
LEFT JOIN "SmsBillingRecord" billing
ON billing."tenantId" = tenant.id
AND billing."createdAt" >= ${businessDay.startAt}
AND billing."createdAt" < ${businessDay.endAt}
WHERE (${query.tenantId ?? null}::text IS NULL OR tenant.id = ${query.tenantId ?? null})
GROUP BY tenant.id, tenant.name, account."balanceCents", account."creditCents"
ORDER BY "todaySpendCents" DESC, tenant.name ASC
`),
this.prisma.cmppDownstreamDelivery.count({
where: { tenantId: query.tenantId, status: 'pending' },
}),
@@ -314,6 +339,13 @@ export class OperationsService {
alertCount: downstreamAlertCount,
},
accounts: tenantAccounts,
enterpriseSpendRanks: enterpriseSpendRows.map((row) => ({
tenantId: row.tenantId,
tenantName: row.tenantName,
todaySpendCents: moneyToNumber(row.todaySpendCents),
balanceCents: moneyToNumber(row.balanceCents),
creditCents: moneyToNumber(row.creditCents),
})),
recentTasks,
recentRecharges,
};
@@ -333,6 +365,7 @@ export class OperationsService {
pendingAudits: dashboard.pendingAudits,
downstreamDeliverySummary: dashboard.downstreamDeliverySummary,
accounts: dashboard.accounts.map(clientAccountView),
enterpriseSpendRanks: dashboard.enterpriseSpendRanks,
recentTasks: dashboard.recentTasks.map(clientBatchTaskView),
recentRecharges: dashboard.recentRecharges.map(clientRechargeView),
};
@@ -468,6 +501,8 @@ export class OperationsService {
tenantName: string;
hasDrainage: boolean;
total: number;
acceptedCount: number;
submitFailureCount: number;
successCount: number;
unknownCount: number;
failureCount: number;
@@ -479,6 +514,7 @@ export class OperationsService {
message."signatureId" AS signature_id,
(message."drainageInfoId" IS NOT NULL) AS has_drainage,
message.status,
message."submitStatus" AS submit_status,
message."receiptStatus" AS receipt_status,
CASE
WHEN (message.status = 'delivered' OR message."receiptStatus" = 'delivered')
@@ -498,23 +534,40 @@ export class OperationsService {
tenant.id AS "tenantId",
tenant.name AS "tenantName",
base.has_drainage AS "hasDrainage",
COUNT(*) FILTER (WHERE COALESCE(base.status, '') <> 'rejected')::integer AS total,
COUNT(*)::integer AS total,
COUNT(*) FILTER (
WHERE COALESCE(base.status, '') <> 'submit_failed'
AND COALESCE(base.submit_status, '') NOT IN ('rejected', 'timeout')
)::integer AS "acceptedCount",
COUNT(*) FILTER (
WHERE base.status = 'submit_failed'
OR base.submit_status IN ('rejected', 'timeout')
)::integer AS "submitFailureCount",
COUNT(*) FILTER (WHERE base.status = 'delivered' OR base.receipt_status = 'delivered')::integer AS "successCount",
COUNT(*) FILTER (
WHERE COALESCE(base.status, '') <> 'rejected'
WHERE COALESCE(base.status, '') <> 'submit_failed'
AND COALESCE(base.submit_status, '') NOT IN ('rejected', 'timeout')
AND NOT (COALESCE(base.status = 'delivered', false) OR COALESCE(base.receipt_status = 'delivered', false))
AND NOT (COALESCE(base.status IN ('submit_failed', 'failed', 'timeout'), false) OR COALESCE(base.receipt_status = 'undelivered', false))
AND NOT (COALESCE(base.status IN ('failed', 'timeout'), false) OR COALESCE(base.receipt_status = 'undelivered', false))
)::integer AS "unknownCount",
COUNT(*) FILTER (
WHERE NOT (COALESCE(base.status = 'delivered', false) OR COALESCE(base.receipt_status = 'delivered', false))
AND (COALESCE(base.status IN ('submit_failed', 'failed', 'timeout'), false) OR COALESCE(base.receipt_status = 'undelivered', false))
WHERE COALESCE(base.status, '') <> 'submit_failed'
AND COALESCE(base.submit_status, '') NOT IN ('rejected', 'timeout')
AND NOT (COALESCE(base.status = 'delivered', false) OR COALESCE(base.receipt_status = 'delivered', false))
AND (COALESCE(base.status IN ('failed', 'timeout'), false) OR COALESCE(base.receipt_status = 'undelivered', false))
)::integer AS "failureCount",
CASE
WHEN COUNT(*) FILTER (WHERE COALESCE(base.status, '') <> 'rejected') = 0 THEN 0
WHEN COUNT(*) FILTER (
WHERE COALESCE(base.status, '') <> 'submit_failed'
AND COALESCE(base.submit_status, '') NOT IN ('rejected', 'timeout')
) = 0 THEN 0
ELSE ROUND(
COUNT(*) FILTER (WHERE base.status = 'delivered' OR base.receipt_status = 'delivered')
* 100.0
/ COUNT(*) FILTER (WHERE COALESCE(base.status, '') <> 'rejected'),
/ COUNT(*) FILTER (
WHERE COALESCE(base.status, '') <> 'submit_failed'
AND COALESCE(base.submit_status, '') NOT IN ('rejected', 'timeout')
),
1
)::double precision
END AS "successRate",
@@ -1016,7 +1069,7 @@ export class OperationsService {
messageRecord: query.messageId ? { messageId: query.messageId } : undefined,
},
include: { channel: true, submitRecord: true },
orderBy: [{ submitId: 'asc' }, { segmentIndex: 'asc' }],
orderBy: [{ createdAt: 'asc' }, { segmentIndex: 'asc' }, { id: 'asc' }],
});
}