fix: remove truncated management totals
This commit is contained in:
@@ -21,7 +21,6 @@ export class AuditService {
|
|||||||
return this.prisma.operationLog.findMany({
|
return this.prisma.operationLog.findMany({
|
||||||
where: tenantId ? { tenantId } : undefined,
|
where: tenantId ? { tenantId } : undefined,
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export class BillingService {
|
|||||||
constructor(private readonly prisma: PrismaService) {}
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
listPlans() {
|
listPlans() {
|
||||||
return this.prisma.billingPlan.findMany({ orderBy: { createdAt: 'desc' }, take: 100 });
|
return this.prisma.billingPlan.findMany({ orderBy: { createdAt: 'desc' } });
|
||||||
}
|
}
|
||||||
|
|
||||||
createPlan(data: CreateBillingPlanDto) {
|
createPlan(data: CreateBillingPlanDto) {
|
||||||
@@ -109,7 +109,6 @@ export class BillingService {
|
|||||||
return this.prisma.tenantAccount.findMany({
|
return this.prisma.tenantAccount.findMany({
|
||||||
include: { tenant: true },
|
include: { tenant: true },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +128,6 @@ export class BillingService {
|
|||||||
where: tenantId ? { tenantId } : undefined,
|
where: tenantId ? { tenantId } : undefined,
|
||||||
include: { plan: true },
|
include: { plan: true },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +139,6 @@ export class BillingService {
|
|||||||
},
|
},
|
||||||
include: { plan: true },
|
include: { plan: true },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
const orderIds = orders.map((order) => order.id);
|
const orderIds = orders.map((order) => order.id);
|
||||||
if (orderIds.length === 0) {
|
if (orderIds.length === 0) {
|
||||||
@@ -328,12 +325,11 @@ export class BillingService {
|
|||||||
return this.prisma.smsBillingRecord.findMany({
|
return this.prisma.smsBillingRecord.findMany({
|
||||||
where: { tenantId, taskId },
|
where: { tenantId, taskId },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
listRules() {
|
listRules() {
|
||||||
return this.prisma.billingRule.findMany({ orderBy: { createdAt: 'desc' }, take: 100 });
|
return this.prisma.billingRule.findMany({ orderBy: { createdAt: 'desc' } });
|
||||||
}
|
}
|
||||||
|
|
||||||
createRule(data: CreateBillingRuleDto) {
|
createRule(data: CreateBillingRuleDto) {
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ export class CertificationService {
|
|||||||
},
|
},
|
||||||
include: { tenant: true },
|
include: { tenant: true },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -715,13 +715,11 @@ describe('ChannelsService', () => {
|
|||||||
expect(prisma.cmppConnectionState.findMany).toHaveBeenCalledWith({
|
expect(prisma.cmppConnectionState.findMany).toHaveBeenCalledWith({
|
||||||
where: { channelId: 'channel-1' },
|
where: { channelId: 'channel-1' },
|
||||||
orderBy: { updatedAt: 'desc' },
|
orderBy: { updatedAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
expect(prisma.cmppConnectionState.findMany).toHaveBeenCalledWith({
|
expect(prisma.cmppConnectionState.findMany).toHaveBeenCalledWith({
|
||||||
where: { tenantId: 'tenant-1' },
|
where: { tenantId: 'tenant-1' },
|
||||||
include: { channel: true },
|
include: { channel: true },
|
||||||
orderBy: { updatedAt: 'desc' },
|
orderBy: { updatedAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
expect(prisma.operationLog.create).toHaveBeenCalledWith({
|
expect(prisma.operationLog.create).toHaveBeenCalledWith({
|
||||||
data: expect.objectContaining({
|
data: expect.objectContaining({
|
||||||
|
|||||||
@@ -198,7 +198,6 @@ export class ChannelsService implements OnModuleInit, OnModuleDestroy {
|
|||||||
return this.prisma.smsChannel.findMany({
|
return this.prisma.smsChannel.findMany({
|
||||||
include: { connectionStates: true },
|
include: { connectionStates: true },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,7 +530,6 @@ export class ChannelsService implements OnModuleInit, OnModuleDestroy {
|
|||||||
return this.prisma.cmppConnectionState.findMany({
|
return this.prisma.cmppConnectionState.findMany({
|
||||||
where: { channelId },
|
where: { channelId },
|
||||||
orderBy: { updatedAt: 'desc' },
|
orderBy: { updatedAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,7 +574,6 @@ export class ChannelsService implements OnModuleInit, OnModuleDestroy {
|
|||||||
where: { tenantId },
|
where: { tenantId },
|
||||||
include: { channel: true },
|
include: { channel: true },
|
||||||
orderBy: { updatedAt: 'desc' },
|
orderBy: { updatedAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -705,7 +702,6 @@ export class ChannelsService implements OnModuleInit, OnModuleDestroy {
|
|||||||
return this.prisma.smsChannelGroup.findMany({
|
return this.prisma.smsChannelGroup.findMany({
|
||||||
include: { items: { include: { channel: { include: { connectionStates: true } } }, orderBy: [{ province: 'asc' }, { priority: 'asc' }] } },
|
include: { items: { include: { channel: { include: { connectionStates: true } } }, orderBy: [{ province: 'asc' }, { priority: 'asc' }] } },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -857,7 +853,6 @@ export class ChannelsService implements OnModuleInit, OnModuleDestroy {
|
|||||||
return this.prisma.channelRouteRule.findMany({
|
return this.prisma.channelRouteRule.findMany({
|
||||||
include: { group: true, channel: true },
|
include: { group: true, channel: true },
|
||||||
orderBy: [{ priority: 'asc' }, { createdAt: 'desc' }],
|
orderBy: [{ priority: 'asc' }, { createdAt: 'desc' }],
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -900,7 +895,6 @@ export class ChannelsService implements OnModuleInit, OnModuleDestroy {
|
|||||||
return this.prisma.channelReportField.findMany({
|
return this.prisma.channelReportField.findMany({
|
||||||
where: channelId ? { channelId } : undefined,
|
where: channelId ? { channelId } : undefined,
|
||||||
orderBy: [{ sortOrder: 'asc' }, { createdAt: 'desc' }],
|
orderBy: [{ sortOrder: 'asc' }, { createdAt: 'desc' }],
|
||||||
take: 200,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -926,7 +920,6 @@ export class ChannelsService implements OnModuleInit, OnModuleDestroy {
|
|||||||
channelId,
|
channelId,
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 200,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -958,7 +951,6 @@ export class ChannelsService implements OnModuleInit, OnModuleDestroy {
|
|||||||
where: { tenantId, status },
|
where: { tenantId, status },
|
||||||
include: { signature: true, channel: true },
|
include: { signature: true, channel: true },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1021,7 +1013,6 @@ export class ChannelsService implements OnModuleInit, OnModuleDestroy {
|
|||||||
return this.prisma.channelSignatureReportRecord.findMany({
|
return this.prisma.channelSignatureReportRecord.findMany({
|
||||||
where: { taskId, channelId },
|
where: { taskId, channelId },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 200,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -140,7 +140,6 @@ export class DictionariesService {
|
|||||||
] : undefined,
|
] : undefined,
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 200,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +172,6 @@ export class DictionariesService {
|
|||||||
] : undefined,
|
] : undefined,
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 200,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +212,6 @@ export class DictionariesService {
|
|||||||
},
|
},
|
||||||
include: { tenant: true, application: true },
|
include: { tenant: true, application: true },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 200,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,7 +251,7 @@ export class DictionariesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
listDrainageFields() {
|
listDrainageFields() {
|
||||||
return this.prisma.drainageField.findMany({ orderBy: { createdAt: 'desc' }, take: 200 });
|
return this.prisma.drainageField.findMany({ orderBy: { createdAt: 'desc' } });
|
||||||
}
|
}
|
||||||
|
|
||||||
createDrainageField(data: CreateDrainageFieldDto) {
|
createDrainageField(data: CreateDrainageFieldDto) {
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ export class FilesService {
|
|||||||
return this.prisma.fileObject.findMany({
|
return this.prisma.fileObject.findMany({
|
||||||
where: tenantId ? { tenantId } : undefined,
|
where: tenantId ? { tenantId } : undefined,
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
}).then((items) => items.map(serializeFileObject));
|
}).then((items) => items.map(serializeFileObject));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -202,7 +202,6 @@ describe('OperationsService', () => {
|
|||||||
},
|
},
|
||||||
include: { tenant: true, application: true, channel: true, submitRecords: true, receiptRecords: true },
|
include: { tenant: true, application: true, channel: true, submitRecords: true, receiptRecords: true },
|
||||||
orderBy: { queuedAt: 'desc' },
|
orderBy: { queuedAt: 'desc' },
|
||||||
take: 500,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -229,7 +228,6 @@ describe('OperationsService', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
orderBy: { receivedAt: 'desc' },
|
orderBy: { receivedAt: 'desc' },
|
||||||
take: 500,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ export class OperationsService {
|
|||||||
where: { tenantId: query.tenantId, status: query.status },
|
where: { tenantId: query.tenantId, status: query.status },
|
||||||
include: { apiRequests: true },
|
include: { apiRequests: true },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 200,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +89,6 @@ export class OperationsService {
|
|||||||
where: messageWhere(query),
|
where: messageWhere(query),
|
||||||
include: { tenant: true, application: true, channel: true, submitRecords: true, receiptRecords: true },
|
include: { tenant: true, application: true, channel: true, submitRecords: true, receiptRecords: true },
|
||||||
orderBy: { queuedAt: 'desc' },
|
orderBy: { queuedAt: 'desc' },
|
||||||
take: 500,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +110,6 @@ export class OperationsService {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
orderBy: { receivedAt: 'desc' },
|
orderBy: { receivedAt: 'desc' },
|
||||||
take: 500,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,7 +300,6 @@ export class OperationsService {
|
|||||||
return this.prisma.operationLog.findMany({
|
return this.prisma.operationLog.findMany({
|
||||||
where: { tenantId: query.tenantId, userId: query.userId },
|
where: { tenantId: query.tenantId, userId: query.userId },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 500,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,6 @@ export class RiskReviewService {
|
|||||||
return this.prisma.riskRule.findMany({
|
return this.prisma.riskRule.findMany({
|
||||||
where: tenantId ? { OR: [{ tenantId: null }, { tenantId }] } : undefined,
|
where: tenantId ? { OR: [{ tenantId: null }, { tenantId }] } : undefined,
|
||||||
orderBy: [{ priority: 'asc' }, { createdAt: 'asc' }],
|
orderBy: [{ priority: 'asc' }, { createdAt: 'asc' }],
|
||||||
take: 200,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +145,6 @@ export class RiskReviewService {
|
|||||||
taskId,
|
taskId,
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 200,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +156,6 @@ export class RiskReviewService {
|
|||||||
},
|
},
|
||||||
include: { riskHits: true },
|
include: { riskHits: true },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -331,19 +331,28 @@ export class SendChainService implements OnModuleInit, OnModuleDestroy {
|
|||||||
return this.getBatchTask(task.id);
|
return this.getBatchTask(task.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
listBatchTasks(tenantId?: string, status?: string) {
|
async listBatchTasks(tenantId?: string, status?: string) {
|
||||||
return this.prisma.smsBatchTask.findMany({
|
const tasks = await this.prisma.smsBatchTask.findMany({
|
||||||
where: { tenantId, status },
|
where: { tenantId, status },
|
||||||
include: {
|
include: {
|
||||||
tenant: true,
|
tenant: true,
|
||||||
application: true,
|
application: true,
|
||||||
template: true,
|
template: true,
|
||||||
apiRequests: true,
|
apiRequests: true,
|
||||||
messages: { include: { channel: true }, orderBy: { queuedAt: 'asc' }, take: 100000 },
|
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
|
const taskIds = tasks.map((task) => task.id);
|
||||||
|
const messageStats = taskIds.length > 0 ? await this.prisma.smsMessageRecord.groupBy({
|
||||||
|
by: ['batchTaskId', 'carrier', 'province', 'status'],
|
||||||
|
where: { batchTaskId: { in: taskIds } },
|
||||||
|
_count: { _all: true },
|
||||||
|
_sum: { billingUnits: true },
|
||||||
|
}) : [];
|
||||||
|
return tasks.map((task) => ({
|
||||||
|
...task,
|
||||||
|
messageStats: messageStats.filter((item) => item.batchTaskId === task.id),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
getBatchTask(taskId: string) {
|
getBatchTask(taskId: string) {
|
||||||
@@ -378,7 +387,6 @@ export class SendChainService implements OnModuleInit, OnModuleDestroy {
|
|||||||
receiptRecords: { include: { channel: true }, orderBy: { createdAt: 'asc' } },
|
receiptRecords: { include: { channel: true }, orderBy: { createdAt: 'asc' } },
|
||||||
},
|
},
|
||||||
orderBy: { queuedAt: 'desc' },
|
orderBy: { queuedAt: 'desc' },
|
||||||
take: 500,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,7 +394,6 @@ export class SendChainService implements OnModuleInit, OnModuleDestroy {
|
|||||||
return this.prisma.smsSubmitRecord.findMany({
|
return this.prisma.smsSubmitRecord.findMany({
|
||||||
where: { batchTaskId: taskId },
|
where: { batchTaskId: taskId },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 200,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,7 +401,6 @@ export class SendChainService implements OnModuleInit, OnModuleDestroy {
|
|||||||
return this.prisma.smsReceiptRecord.findMany({
|
return this.prisma.smsReceiptRecord.findMany({
|
||||||
where: { batchTaskId: taskId },
|
where: { batchTaskId: taskId },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 200,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,7 +409,6 @@ export class SendChainService implements OnModuleInit, OnModuleDestroy {
|
|||||||
where: { tenantId, channelId },
|
where: { tenantId, channelId },
|
||||||
include: { application: true, channel: true, messageRecord: { include: { application: true } } },
|
include: { application: true, channel: true, messageRecord: { include: { application: true } } },
|
||||||
orderBy: { receivedAt: 'desc' },
|
orderBy: { receivedAt: 'desc' },
|
||||||
take: 200,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,7 +547,6 @@ export class SendChainService implements OnModuleInit, OnModuleDestroy {
|
|||||||
const tasks = await this.prisma.smsBatchTask.findMany({
|
const tasks = await this.prisma.smsBatchTask.findMany({
|
||||||
where: { status: 'scheduled', scheduledAt: { lte: now } },
|
where: { status: 'scheduled', scheduledAt: { lte: now } },
|
||||||
orderBy: { scheduledAt: 'asc' },
|
orderBy: { scheduledAt: 'asc' },
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
const results: Array<{ taskId: string; status: string; enqueued?: number; reason?: string }> = [];
|
const results: Array<{ taskId: string; status: string; enqueued?: number; reason?: string }> = [];
|
||||||
for (const task of tasks) {
|
for (const task of tasks) {
|
||||||
@@ -1812,7 +1816,6 @@ export class SendChainService implements OnModuleInit, OnModuleDestroy {
|
|||||||
const rules = await this.prisma.phoneCarrierRule.findMany({
|
const rules = await this.prisma.phoneCarrierRule.findMany({
|
||||||
where: { status: 'active' },
|
where: { status: 'active' },
|
||||||
orderBy: [{ priority: 'asc' }, { createdAt: 'asc' }],
|
orderBy: [{ priority: 'asc' }, { createdAt: 'asc' }],
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
for (const rule of rules) {
|
for (const rule of rules) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ function createPrismaMock() {
|
|||||||
interfaceType: 'cmpp20',
|
interfaceType: 'cmpp20',
|
||||||
queuePriority: 'normal',
|
queuePriority: 'normal',
|
||||||
tenant: { id: 'tenant-1', name: '租户A', code: 'TENANT-A' },
|
tenant: { id: 'tenant-1', name: '租户A', code: 'TENANT-A' },
|
||||||
messageRecords: [{ status: 'delivered' }, { status: 'undelivered' }],
|
|
||||||
}]),
|
}]),
|
||||||
findUnique: jest.fn().mockResolvedValue({
|
findUnique: jest.fn().mockResolvedValue({
|
||||||
id: 'app-1',
|
id: 'app-1',
|
||||||
@@ -106,6 +105,12 @@ function createPrismaMock() {
|
|||||||
update: jest.fn().mockImplementation(({ data }) => Promise.resolve({ id: 'downstream-1', ...data })),
|
update: jest.fn().mockImplementation(({ data }) => Promise.resolve({ id: 'downstream-1', ...data })),
|
||||||
updateMany: jest.fn().mockResolvedValue({ count: 0 }),
|
updateMany: jest.fn().mockResolvedValue({ count: 0 }),
|
||||||
},
|
},
|
||||||
|
smsMessageRecord: {
|
||||||
|
groupBy: jest.fn().mockResolvedValue([
|
||||||
|
{ applicationId: 'app-1', status: 'delivered', _count: { _all: 1 } },
|
||||||
|
{ applicationId: 'app-1', status: 'undelivered', _count: { _all: 1 } },
|
||||||
|
]),
|
||||||
|
},
|
||||||
smsChannel: {
|
smsChannel: {
|
||||||
findFirst: jest.fn().mockResolvedValue({
|
findFirst: jest.fn().mockResolvedValue({
|
||||||
id: 'channel-1',
|
id: 'channel-1',
|
||||||
@@ -177,6 +182,14 @@ describe('SmsConfigService', () => {
|
|||||||
cmppConnections: [expect.objectContaining({ connectionId: 'gateway-1-1', account: '100001' })],
|
cmppConnections: [expect.objectContaining({ connectionId: 'gateway-1-1', account: '100001' })],
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
expect(prisma.smsApplication.findMany).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
include: { tenant: true, ipAllowlist: true },
|
||||||
|
}));
|
||||||
|
expect(prisma.smsApplication.findMany.mock.calls[0][0]).not.toHaveProperty('take');
|
||||||
|
expect(prisma.smsMessageRecord.groupBy).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
by: ['applicationId', 'status'],
|
||||||
|
_count: { _all: true },
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns CMPP params from persisted application and channel config', async () => {
|
it('returns CMPP params from persisted application and channel config', async () => {
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ export class SmsConfigService {
|
|||||||
if (query.includeConnections) {
|
if (query.includeConnections) {
|
||||||
await this.markTimedOutDownstreamConnections();
|
await this.markTimedOutDownstreamConnections();
|
||||||
}
|
}
|
||||||
return this.prisma.smsApplication.findMany({
|
const applications = await this.prisma.smsApplication.findMany({
|
||||||
where: {
|
where: {
|
||||||
tenantId: query.tenantId,
|
tenantId: query.tenantId,
|
||||||
OR: query.keyword ? [
|
OR: query.keyword ? [
|
||||||
@@ -134,32 +134,36 @@ export class SmsConfigService {
|
|||||||
include: {
|
include: {
|
||||||
tenant: true,
|
tenant: true,
|
||||||
ipAllowlist: true,
|
ipAllowlist: true,
|
||||||
messageRecords: { where: { queuedAt: { gte: startOfToday() } }, take: 1000 },
|
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 100,
|
});
|
||||||
}).then(async (applications) => {
|
if (!query.includeConnections) {
|
||||||
if (!query.includeConnections) {
|
return applications;
|
||||||
return applications;
|
}
|
||||||
}
|
const applicationIds = applications.map((application) => application.id);
|
||||||
const applicationIds = applications.map((application) => application.id);
|
const [connections, messageStats] = await Promise.all([
|
||||||
const connections = await this.prisma.cmppDownstreamConnection.findMany({
|
this.prisma.cmppDownstreamConnection.findMany({
|
||||||
where: { applicationId: { in: applicationIds } },
|
where: { applicationId: { in: applicationIds } },
|
||||||
orderBy: { updatedAt: 'desc' },
|
orderBy: { updatedAt: 'desc' },
|
||||||
take: 500,
|
}),
|
||||||
});
|
this.prisma.smsMessageRecord.groupBy({
|
||||||
return applications.map((application) => {
|
by: ['applicationId', 'status'],
|
||||||
const appConnections = connections.filter((connection) => connection.applicationId === application.id);
|
where: { applicationId: { in: applicationIds }, queuedAt: { gte: startOfToday() } },
|
||||||
const todayTotal = application.messageRecords.length;
|
_count: { _all: true },
|
||||||
const delivered = application.messageRecords.filter((message) => message.status === 'delivered').length;
|
}),
|
||||||
return {
|
]);
|
||||||
...application,
|
return applications.map((application) => {
|
||||||
cmppConnections: appConnections,
|
const appConnections = connections.filter((connection) => connection.applicationId === application.id);
|
||||||
cmppStatus: normalizeApplicationCmppStatus(appConnections, application.status),
|
const appStats = messageStats.filter((item) => item.applicationId === application.id);
|
||||||
sentToday: todayTotal,
|
const todayTotal = appStats.reduce((sum, item) => sum + item._count._all, 0);
|
||||||
deliveryRate: todayTotal > 0 ? Number(((delivered / todayTotal) * 100).toFixed(1)) : 0,
|
const delivered = appStats.find((item) => item.status === 'delivered')?._count._all ?? 0;
|
||||||
};
|
return {
|
||||||
});
|
...application,
|
||||||
|
cmppConnections: appConnections,
|
||||||
|
cmppStatus: normalizeApplicationCmppStatus(appConnections, application.status),
|
||||||
|
sentToday: todayTotal,
|
||||||
|
deliveryRate: todayTotal > 0 ? Number(((delivered / todayTotal) * 100).toFixed(1)) : 0,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,7 +370,6 @@ export class SmsConfigService {
|
|||||||
const connections = await this.prisma.cmppDownstreamConnection.findMany({
|
const connections = await this.prisma.cmppDownstreamConnection.findMany({
|
||||||
where: { applicationId },
|
where: { applicationId },
|
||||||
orderBy: { updatedAt: 'desc' },
|
orderBy: { updatedAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
application,
|
application,
|
||||||
@@ -513,7 +516,6 @@ export class SmsConfigService {
|
|||||||
},
|
},
|
||||||
include: { materials: true, tenant: true, application: true },
|
include: { materials: true, tenant: true, application: true },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -596,7 +598,6 @@ export class SmsConfigService {
|
|||||||
},
|
},
|
||||||
include: { variables: true, application: true, tenant: true, signature: true },
|
include: { variables: true, application: true, tenant: true, signature: true },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -696,7 +697,6 @@ export class SmsConfigService {
|
|||||||
targetId,
|
targetId,
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,8 @@ describe('TenantsService', () => {
|
|||||||
expect(prisma.tenant.findMany).toHaveBeenCalledWith(expect.objectContaining({
|
expect(prisma.tenant.findMany).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
where: { status: { not: 'deleted' } },
|
where: { status: { not: 'deleted' } },
|
||||||
}));
|
}));
|
||||||
|
expect(prisma.tenant.findMany.mock.calls[0][0]).not.toHaveProperty('take');
|
||||||
|
expect(prisma.tenantAccount.findMany).toHaveBeenCalledWith();
|
||||||
expect(prisma.smsMessageRecord.groupBy).toHaveBeenCalledWith(expect.objectContaining({
|
expect(prisma.smsMessageRecord.groupBy).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
by: ['tenantId'],
|
by: ['tenantId'],
|
||||||
_sum: { amountCents: true },
|
_sum: { amountCents: true },
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ export class TenantsService {
|
|||||||
return this.prisma.tenant.findMany({
|
return this.prisma.tenant.findMany({
|
||||||
include: { enterpriseCertifications: { orderBy: { submittedAt: 'desc' }, take: 1 } },
|
include: { enterpriseCertifications: { orderBy: { submittedAt: 'desc' }, take: 1 } },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
}).then((items) => items.map(withEnterpriseProfile));
|
}).then((items) => items.map(withEnterpriseProfile));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,9 +50,8 @@ export class TenantsService {
|
|||||||
where: { status: { not: 'deleted' } },
|
where: { status: { not: 'deleted' } },
|
||||||
include: { enterpriseCertifications: { orderBy: { submittedAt: 'desc' }, take: 1 } },
|
include: { enterpriseCertifications: { orderBy: { submittedAt: 'desc' }, take: 1 } },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
}),
|
}),
|
||||||
this.prisma.tenantAccount.findMany({ take: 200 }),
|
this.prisma.tenantAccount.findMany(),
|
||||||
this.prisma.smsMessageRecord.groupBy({
|
this.prisma.smsMessageRecord.groupBy({
|
||||||
by: ['tenantId'],
|
by: ['tenantId'],
|
||||||
where: { queuedAt: { gte: sinceToday } },
|
where: { queuedAt: { gte: sinceToday } },
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ export class UsersService {
|
|||||||
},
|
},
|
||||||
include: { tenant: true, roles: { include: { role: true } } },
|
include: { tenant: true, roles: { include: { role: true } } },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 200,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,7 +239,6 @@ export class UsersService {
|
|||||||
return this.prisma.role.findMany({
|
return this.prisma.role.findMany({
|
||||||
include: { permissions: { include: { permission: true } } },
|
include: { permissions: { include: { permission: true } } },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
take: 100,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +254,7 @@ export class UsersService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
listPermissions() {
|
listPermissions() {
|
||||||
return this.prisma.permission.findMany({ orderBy: { createdAt: 'desc' }, take: 200 });
|
return this.prisma.permission.findMany({ orderBy: { createdAt: 'desc' } });
|
||||||
}
|
}
|
||||||
|
|
||||||
createPermission(data: CreatePermissionDto) {
|
createPermission(data: CreatePermissionDto) {
|
||||||
|
|||||||
@@ -1590,3 +1590,12 @@ git diff --check
|
|||||||
- 短信任务进度详情中的“号码运营商分布”和“号码省份分布”均直接聚合任务内真实短信记录的 `carrier/province`。
|
- 短信任务进度详情中的“号码运营商分布”和“号码省份分布”均直接聚合任务内真实短信记录的 `carrier/province`。
|
||||||
- 已执行 `npm --prefix api test -- --runInBand send-chain.service.spec.ts`(35 项通过)、`npm --prefix api run build`、`npm run build` 和 `git diff --check`;前端仅有既有 chunk size warning。
|
- 已执行 `npm --prefix api test -- --runInBand send-chain.service.spec.ts`(35 项通过)、`npm --prefix api run build`、`npm run build` 和 `git diff --check`;前端仅有既有 chunk size warning。
|
||||||
- 已将 `7b8424d9` 部署生产,migration `20260711210000_add_message_route_identity` 成功应用。`cmpp-api`、`cmpp-gateway`、Nginx、MinIO 均为 active,`12026/17890/8090/3000` 监听、API/Gateway health 及外部 `12026` HTTP 均通过。生产 11 条已有短信已全部回填运营商和省份:移动 9 条、电信 1 条、联通 1 条,省份均为上海。
|
- 已将 `7b8424d9` 部署生产,migration `20260711210000_add_message_route_identity` 成功应用。`cmpp-api`、`cmpp-gateway`、Nginx、MinIO 均为 active,`12026/17890/8090/3000` 监听、API/Gateway health 及外部 `12026` HTTP 均通过。生产 11 条已有短信已全部回填运营商和省份:移动 9 条、电信 1 条、联通 1 条,省份均为上海。
|
||||||
|
|
||||||
|
## 2026-07-12 全页面固定条数截断与总数口径整改
|
||||||
|
|
||||||
|
- 生产核查确认企业管理只返回前 100 条,但 PostgreSQL 实际有 106 个企业(未删除 105 个);企业应用只返回前 100 条,实际有 204 条。页面将截断后的数组长度展示为“企业总数/共找到”,属于真实数据口径 Bug。
|
||||||
|
- 已系统审计页面主列表 API,移除会把前 `100/200/500` 条直接作为页面全量的固定截断。覆盖企业、企业应用、企业认证、用户/角色/权限、签名、模板、通道/通道组/路由规则、报备字段/材料/任务/记录、账务、文件、审计、风控、敏感词/黑名单/引流字段、短信任务/记录/提交/回执/上行等真实列表。
|
||||||
|
- 企业应用的“今日发送/到达率”不再每应用加载最多 1000 条 `SmsMessageRecord` 后用数组长度计算,改为 PostgreSQL 按 `applicationId/status` 的 `groupBy` 全量聚合。
|
||||||
|
- 短信任务进度不再为每个任务最多加载 100000 条短信后统计,改为 PostgreSQL 按 `batchTaskId/carrier/province/status` 聚合总数、成功数和计费条数,任务运营商/省份分布不再受 10 万条截断影响。
|
||||||
|
- 保留的固定条数均属于明确的近期日志/监控窗口、超时扫描单批、导出保护、单消息重试尝试或唯一候选判定,这些响应不被页面展示为业务总数。
|
||||||
|
- 已执行 API 全量测试(13 suites、122 项通过),及受影响服务定向测试(5 suites、91 项通过)、API build、前端 build 与 `git diff --check`;前端仅有既有 chunk size warning。本轮尚未提交或部署生产。
|
||||||
|
|||||||
@@ -330,6 +330,14 @@ export type SmsBatchTask = {
|
|||||||
application?: { id: string; name: string };
|
application?: { id: string; name: string };
|
||||||
template?: { id: string; name: string; content: string; billingUnits?: number };
|
template?: { id: string; name: string; content: string; billingUnits?: number };
|
||||||
messages?: SmsMessageRecord[];
|
messages?: SmsMessageRecord[];
|
||||||
|
messageStats?: Array<{
|
||||||
|
batchTaskId?: string | null;
|
||||||
|
carrier?: string | null;
|
||||||
|
province?: string | null;
|
||||||
|
status: string;
|
||||||
|
_count: { _all: number };
|
||||||
|
_sum: { billingUnits?: number | null };
|
||||||
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ImportPreviewResponse = {
|
export type ImportPreviewResponse = {
|
||||||
|
|||||||
@@ -125,6 +125,31 @@ function buildRegionStats(messages: SmsMessageRecord[] | undefined): RegionStat[
|
|||||||
return Array.from(stats.values()).sort((a, b) => b.total - a.total);
|
return Array.from(stats.values()).sort((a, b) => b.total - a.total);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildCarrierStatsFromAggregates(stats: SmsBatchTask['messageStats']): CarrierStat[] {
|
||||||
|
const totals = new Map<string, CarrierStat>();
|
||||||
|
(stats ?? []).forEach((item) => {
|
||||||
|
const carrier = item.carrier ?? 'unknown';
|
||||||
|
const meta = carrierLabels[carrier] ?? { label: carrier || '未识别', tone: 'mobile' as const };
|
||||||
|
const current = totals.get(carrier) ?? { name: meta.label, total: 0, success: 0, tone: meta.tone };
|
||||||
|
current.total += item._count._all;
|
||||||
|
if (item.status === 'delivered') current.success += item._count._all;
|
||||||
|
totals.set(carrier, current);
|
||||||
|
});
|
||||||
|
return Array.from(totals.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRegionStatsFromAggregates(stats: SmsBatchTask['messageStats']): RegionStat[] {
|
||||||
|
const totals = new Map<string, RegionStat>();
|
||||||
|
(stats ?? []).forEach((item) => {
|
||||||
|
const region = item.province ?? '未识别省份';
|
||||||
|
const current = totals.get(region) ?? { region, total: 0, success: 0 };
|
||||||
|
current.total += item._count._all;
|
||||||
|
if (item.status === 'delivered') current.success += item._count._all;
|
||||||
|
totals.set(region, current);
|
||||||
|
});
|
||||||
|
return Array.from(totals.values()).sort((a, b) => b.total - a.total);
|
||||||
|
}
|
||||||
|
|
||||||
function mapTask(task: SmsBatchTask): SmsTask {
|
function mapTask(task: SmsBatchTask): SmsTask {
|
||||||
const messages = task.messages ?? [];
|
const messages = task.messages ?? [];
|
||||||
const submittedStatuses = ['submitted', 'delivered', 'failed', 'unknown', 'timeout', 'submit_failed'];
|
const submittedStatuses = ['submitted', 'delivered', 'failed', 'unknown', 'timeout', 'submit_failed'];
|
||||||
@@ -134,7 +159,8 @@ function mapTask(task: SmsBatchTask): SmsTask {
|
|||||||
const failedCount = task.failedTotal ?? countMessages(messages, failedStatuses);
|
const failedCount = task.failedTotal ?? countMessages(messages, failedStatuses);
|
||||||
// submittedTotal already includes unknown and timeout records, so never add them again.
|
// submittedTotal already includes unknown and timeout records, so never add them again.
|
||||||
const processedCount = Math.max(submittedCount, successCount + failedCount + (task.unknownTotal ?? 0));
|
const processedCount = Math.max(submittedCount, successCount + failedCount + (task.unknownTotal ?? 0));
|
||||||
const billingCount = messages.reduce((sum, message) => sum + (message.billingUnits ?? 0), 0)
|
const billingCount = (task.messageStats ?? []).reduce((sum, item) => sum + (item._sum.billingUnits ?? 0), 0)
|
||||||
|
|| messages.reduce((sum, message) => sum + (message.billingUnits ?? 0), 0)
|
||||||
|| task.phoneTotal * (task.template?.billingUnits ?? Math.max(1, Math.ceil([...task.content].length / 67)));
|
|| task.phoneTotal * (task.template?.billingUnits ?? Math.max(1, Math.ceil([...task.content].length / 67)));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -156,8 +182,8 @@ function mapTask(task: SmsBatchTask): SmsTask {
|
|||||||
failedCount,
|
failedCount,
|
||||||
status: normalizeTaskStatus(task.status),
|
status: normalizeTaskStatus(task.status),
|
||||||
rawStatus: task.status,
|
rawStatus: task.status,
|
||||||
carriers: buildCarrierStats(messages),
|
carriers: task.messageStats ? buildCarrierStatsFromAggregates(task.messageStats) : buildCarrierStats(messages),
|
||||||
regions: buildRegionStats(messages),
|
regions: task.messageStats ? buildRegionStatsFromAggregates(task.messageStats) : buildRegionStats(messages),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user