feat: densify sms records and improve uplink matching
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "SmsUplinkMessage"
|
||||
ADD COLUMN "gatewayMessageId" TEXT;
|
||||
+16
-15
@@ -2116,21 +2116,22 @@ model SmsReceiptAnomaly {
|
||||
}
|
||||
|
||||
model SmsUplinkMessage {
|
||||
id String @id @default(cuid())
|
||||
eventId String? @unique
|
||||
tenantId String?
|
||||
applicationId String?
|
||||
channelId String
|
||||
messageRecordId String?
|
||||
messageId String?
|
||||
sequenceId Int?
|
||||
phoneNumber String
|
||||
destId String
|
||||
content String
|
||||
matchStatus String @default("unmatched")
|
||||
matchReason String?
|
||||
receivedAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
id String @id @default(cuid())
|
||||
eventId String? @unique
|
||||
tenantId String?
|
||||
applicationId String?
|
||||
channelId String
|
||||
messageRecordId String?
|
||||
messageId String?
|
||||
gatewayMessageId String?
|
||||
sequenceId Int?
|
||||
phoneNumber String
|
||||
destId String
|
||||
content String
|
||||
matchStatus String @default("unmatched")
|
||||
matchReason String?
|
||||
receivedAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
tenant Tenant? @relation(fields: [tenantId], references: [id])
|
||||
application SmsApplication? @relation(fields: [applicationId], references: [id])
|
||||
|
||||
@@ -303,6 +303,7 @@ export function clientUplinkView(message: Record<string, any>) {
|
||||
applicationId: message.applicationId ?? null,
|
||||
messageRecordId: message.messageRecordId ?? null,
|
||||
messageId: message.messageId ?? null,
|
||||
gatewayMessageId: message.gatewayMessageId ?? null,
|
||||
phoneNumber: message.phoneNumber,
|
||||
destId: message.destId,
|
||||
content: message.content,
|
||||
|
||||
@@ -359,6 +359,50 @@ describe('OperationsService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the matched message record and the distinct uplink gateway message id to the client view', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
prisma.smsUplinkMessage.findMany.mockResolvedValue([{
|
||||
id: 'uplink-1',
|
||||
tenantId: 'tenant-1',
|
||||
applicationId: 'app-1',
|
||||
messageRecordId: 'record-1',
|
||||
messageId: null,
|
||||
gatewayMessageId: '8412634832294102675',
|
||||
phoneNumber: '13800000001',
|
||||
destId: '10690000',
|
||||
content: 'TD',
|
||||
matchStatus: 'matched',
|
||||
matchReason: '手机号 72 小时窗口唯一匹配',
|
||||
receivedAt: new Date('2026-08-26T01:00:00.000Z'),
|
||||
createdAt: new Date('2026-08-26T01:00:00.000Z'),
|
||||
application: { id: 'app-1', name: '应用A' },
|
||||
messageRecord: {
|
||||
id: 'record-1',
|
||||
applicationId: 'app-1',
|
||||
messageId: 'MSG-1',
|
||||
phoneNumber: '13800000001',
|
||||
content: '通知内容',
|
||||
billingUnits: 1,
|
||||
amountCents: 325,
|
||||
status: 'delivered',
|
||||
queuedAt: new Date('2026-08-25T01:00:00.000Z'),
|
||||
application: { id: 'app-1', name: '应用A' },
|
||||
},
|
||||
matchCandidates: [],
|
||||
}]);
|
||||
const service = new OperationsService(prisma as never);
|
||||
|
||||
const [uplink] = await service.listClientUplinkMessages({ tenantId: 'tenant-1' });
|
||||
|
||||
expect(uplink).toMatchObject({
|
||||
messageId: null,
|
||||
gatewayMessageId: '8412634832294102675',
|
||||
messageRecordId: 'record-1',
|
||||
matchStatus: 'matched',
|
||||
messageRecord: { id: 'record-1', messageId: 'MSG-1' },
|
||||
});
|
||||
});
|
||||
|
||||
it('returns client message views without supplier channel, submit, tenant, or gateway internals', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
prisma.smsMessageRecord.findMany.mockResolvedValue([{
|
||||
|
||||
@@ -118,6 +118,7 @@ export interface GatewayUplinkEventDto {
|
||||
eventId?: string;
|
||||
traceId?: string;
|
||||
messageId?: string;
|
||||
gatewayMessageId?: string;
|
||||
channelId: string;
|
||||
sequenceId?: number;
|
||||
phoneNumber: string;
|
||||
|
||||
@@ -3749,6 +3749,7 @@ describe('SendChainService', () => {
|
||||
});
|
||||
await service.handleUplink({
|
||||
messageId: 'MSG-1',
|
||||
gatewayMessageId: '8412634832294102675',
|
||||
channelId: 'channel-1',
|
||||
sequenceId: 8,
|
||||
phoneNumber: '13800000001',
|
||||
@@ -3761,7 +3762,7 @@ describe('SendChainService', () => {
|
||||
data: expect.objectContaining({ receiptStatus: 'delivered', rawStatus: 'DELIVRD', messageRecordId: 'record-1' }),
|
||||
});
|
||||
expect(prisma.smsUplinkMessage.create).toHaveBeenCalledWith({
|
||||
data: expect.objectContaining({ tenantId: 'tenant-1', channelId: 'channel-1', content: 'TD' }),
|
||||
data: expect.objectContaining({ tenantId: 'tenant-1', channelId: 'channel-1', gatewayMessageId: '8412634832294102675', content: 'TD' }),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ export class SendDownstreamDeliveryService {
|
||||
messageRecordId: match.messageRecordId,
|
||||
channelId: data.channelId,
|
||||
messageId: data.messageId,
|
||||
gatewayMessageId: data.gatewayMessageId,
|
||||
sequenceId: data.sequenceId,
|
||||
phoneNumber: data.phoneNumber,
|
||||
destId: data.destId,
|
||||
|
||||
Reference in New Issue
Block a user