feat: add number library routing and cdr location support

This commit is contained in:
hectorzhao
2026-06-24 11:39:32 +08:00
parent 5fa1bd35e9
commit 7057fd3c42
63 changed files with 6361 additions and 566 deletions
@@ -0,0 +1,167 @@
import { Inject, Injectable, NotFoundException } from '@nestjs/common';
import { Prisma } from '@lisglosips/database';
import { PrismaService } from '../database/prisma.service.js';
export type CdrCarrier = 'MOBILE' | 'UNICOM' | 'TELECOM' | 'BROADCAST' | 'MVNO' | 'UNKNOWN';
export interface CdrQuery {
caller?: string;
callee?: string;
customerGatewayId?: string;
vendorGatewayId?: string;
cityCode?: string;
carrier?: CdrCarrier;
take: number;
skip: number;
}
export interface CdrListItem {
id: string;
eventId: string;
callId: string;
sourceIp: string | null;
caller: string;
callee: string;
calleeCityCode: string | null;
calleeCityName: string | null;
calleeProvinceName: string | null;
calleeOperator: CdrCarrier;
calleeNumberType: string;
customerId: string | null;
customerName: string | null;
customerGatewayId: string | null;
customerGatewayName: string | null;
vendorId: string | null;
vendorName: string | null;
vendorGatewayId: string | null;
vendorGatewayName: string | null;
vendorGatewayHost: string | null;
vendorGatewayPort: number | null;
lineGroupId: string | null;
lineGroupName: string | null;
startedAt: Date;
answeredAt: Date | null;
endedAt: Date;
durationSec: number;
sipCode: number;
hangupReason: string | null;
recordingKey: string | null;
configVersion: number | null;
ratingStatus: string;
customerFee: string | null;
vendorCost: string | null;
grossProfit: string | null;
billSec: number | null;
}
export interface CdrsRepository {
list(query: CdrQuery): Promise<{ items: CdrListItem[]; total: number }>;
get(id: string): Promise<CdrListItem>;
}
export const CDRS_REPOSITORY = Symbol('CDRS_REPOSITORY');
type RawCdrRecord = Prisma.RawCdrGetPayload<{
include: {
customer: { select: { name: true } };
customerGateway: { select: { name: true } };
vendor: { select: { name: true } };
vendorGateway: { select: { name: true; host: true; port: true } };
lineGroup: { select: { name: true } };
ratedCdr: { select: { customerFee: true; vendorCost: true; grossProfit: true; billSec: true } };
};
}>;
@Injectable()
export class PrismaCdrsRepository implements CdrsRepository {
constructor(@Inject(PrismaService) private readonly prisma: PrismaService) {}
async list(query: CdrQuery): Promise<{ items: CdrListItem[]; total: number }> {
const where = this.where(query);
const [items, total] = await this.prisma.$transaction([
this.prisma.rawCdr.findMany({
where,
orderBy: [{ startedAt: 'desc' }],
take: query.take,
skip: query.skip,
include: this.includeCdr()
}),
this.prisma.rawCdr.count({ where })
]);
return { items: items.map((item) => this.toItem(item)), total };
}
async get(id: string): Promise<CdrListItem> {
const item = await this.prisma.rawCdr.findUnique({
where: { id },
include: this.includeCdr()
});
if (!item) {
throw new NotFoundException({ code: 'CDR_NOT_FOUND', message: 'CDR not found.' });
}
return this.toItem(item);
}
private where(query: CdrQuery): Prisma.RawCdrWhereInput {
return {
caller: query.caller ? { contains: query.caller } : undefined,
callee: query.callee ? { contains: query.callee } : undefined,
customerGatewayId: query.customerGatewayId,
vendorGatewayId: query.vendorGatewayId,
calleeCityCode: query.cityCode,
calleeOperator: query.carrier
};
}
private includeCdr() {
return {
customer: { select: { name: true } },
customerGateway: { select: { name: true } },
vendor: { select: { name: true } },
vendorGateway: { select: { name: true, host: true, port: true } },
lineGroup: { select: { name: true } },
ratedCdr: { select: { customerFee: true, vendorCost: true, grossProfit: true, billSec: true } }
} satisfies Prisma.RawCdrInclude;
}
private toItem(item: RawCdrRecord): CdrListItem {
return {
id: item.id,
eventId: item.eventId,
callId: item.callId,
sourceIp: item.sourceIp,
caller: item.caller,
callee: item.callee,
calleeCityCode: item.calleeCityCode,
calleeCityName: item.calleeCityName,
calleeProvinceName: item.calleeProvinceName,
calleeOperator: item.calleeOperator,
calleeNumberType: item.calleeNumberType,
customerId: item.customerId,
customerName: item.customer?.name ?? null,
customerGatewayId: item.customerGatewayId,
customerGatewayName: item.customerGateway?.name ?? null,
vendorId: item.vendorId,
vendorName: item.vendor?.name ?? null,
vendorGatewayId: item.vendorGatewayId,
vendorGatewayName: item.vendorGateway?.name ?? null,
vendorGatewayHost: item.vendorGateway?.host ?? null,
vendorGatewayPort: item.vendorGateway?.port ?? null,
lineGroupId: item.lineGroupId,
lineGroupName: item.lineGroup?.name ?? null,
startedAt: item.startedAt,
answeredAt: item.answeredAt,
endedAt: item.endedAt,
durationSec: item.durationSec,
sipCode: item.sipCode,
hangupReason: item.hangupReason,
recordingKey: item.recordingKey,
configVersion: item.configVersion,
ratingStatus: item.ratingStatus,
customerFee: item.ratedCdr?.customerFee.toFixed(6) ?? null,
vendorCost: item.ratedCdr?.vendorCost.toFixed(6) ?? null,
grossProfit: item.ratedCdr?.grossProfit.toFixed(6) ?? null,
billSec: item.ratedCdr?.billSec ?? null
};
}
}