feat: complete phase2 baseline cdr quality rbac
This commit is contained in:
@@ -11,10 +11,19 @@ export interface CdrQuery {
|
||||
vendorGatewayId?: string;
|
||||
cityCode?: string;
|
||||
carrier?: CdrCarrier;
|
||||
startedFrom?: Date;
|
||||
startedTo?: Date;
|
||||
take: number;
|
||||
skip: number;
|
||||
}
|
||||
|
||||
export interface CdrPageMeta {
|
||||
total: number;
|
||||
take: number;
|
||||
skip: number;
|
||||
hasMore: boolean;
|
||||
}
|
||||
|
||||
export interface CdrListItem {
|
||||
id: string;
|
||||
eventId: string;
|
||||
@@ -22,6 +31,9 @@ export interface CdrListItem {
|
||||
sourceIp: string | null;
|
||||
caller: string;
|
||||
callee: string;
|
||||
rawCallee: string | null;
|
||||
businessPrefixId: string | null;
|
||||
businessPrefix: string | null;
|
||||
calleeCityCode: string | null;
|
||||
calleeCityName: string | null;
|
||||
calleeProvinceName: string | null;
|
||||
@@ -39,6 +51,8 @@ export interface CdrListItem {
|
||||
vendorGatewayPort: number | null;
|
||||
lineGroupId: string | null;
|
||||
lineGroupName: string | null;
|
||||
landingCaller: string | null;
|
||||
landingCallee: string | null;
|
||||
startedAt: Date;
|
||||
answeredAt: Date | null;
|
||||
endedAt: Date;
|
||||
@@ -52,11 +66,73 @@ export interface CdrListItem {
|
||||
vendorCost: string | null;
|
||||
grossProfit: string | null;
|
||||
billSec: number | null;
|
||||
hasRecording: boolean;
|
||||
recordingId: string | null;
|
||||
recordingStatus: string | null;
|
||||
}
|
||||
|
||||
export interface CdrEntityRef {
|
||||
id: string;
|
||||
name: string;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export interface CdrGatewayRef extends CdrEntityRef {
|
||||
authMode?: string;
|
||||
sourceIp?: string | null;
|
||||
host?: string;
|
||||
port?: number;
|
||||
transport?: string;
|
||||
}
|
||||
|
||||
export interface CdrPolicyRef extends CdrEntityRef {
|
||||
priority: number;
|
||||
callerMode: string;
|
||||
callerValue: string | null;
|
||||
calleeMode: string;
|
||||
calleeValue: string | null;
|
||||
}
|
||||
|
||||
export interface CdrRatedDetail {
|
||||
id: string;
|
||||
billSec: number;
|
||||
customerFee: string;
|
||||
vendorCost: string;
|
||||
grossProfit: string;
|
||||
customerRate: Prisma.JsonValue | null;
|
||||
vendorRate: Prisma.JsonValue | null;
|
||||
ratedAt: Date;
|
||||
}
|
||||
|
||||
export interface CdrRecordingDetail {
|
||||
id: string;
|
||||
storageKey: string;
|
||||
sha256: string | null;
|
||||
bytes: string;
|
||||
durationSec: number;
|
||||
status: string;
|
||||
movedAt: Date | null;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface CdrDetail extends CdrListItem {
|
||||
customer: CdrEntityRef | null;
|
||||
customerGateway: CdrGatewayRef | null;
|
||||
customerGatewayPolicy: CdrPolicyRef | null;
|
||||
businessPrefixRef: CdrEntityRef & { prefix: string; priority: number } | null;
|
||||
vendor: CdrEntityRef | null;
|
||||
vendorGateway: CdrGatewayRef | null;
|
||||
lineGroup: CdrEntityRef | null;
|
||||
rated: CdrRatedDetail | null;
|
||||
recording: CdrRecordingDetail | null;
|
||||
payload: Prisma.JsonValue | null;
|
||||
receivedAt: Date;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface CdrsRepository {
|
||||
list(query: CdrQuery): Promise<{ items: CdrListItem[]; total: number }>;
|
||||
get(id: string): Promise<CdrListItem>;
|
||||
list(query: CdrQuery): Promise<{ items: CdrListItem[]; meta: CdrPageMeta }>;
|
||||
get(id: string): Promise<CdrDetail>;
|
||||
}
|
||||
|
||||
export const CDRS_REPOSITORY = Symbol('CDRS_REPOSITORY');
|
||||
@@ -69,6 +145,54 @@ type RawCdrRecord = Prisma.RawCdrGetPayload<{
|
||||
vendorGateway: { select: { name: true; host: true; port: true } };
|
||||
lineGroup: { select: { name: true } };
|
||||
ratedCdr: { select: { customerFee: true; vendorCost: true; grossProfit: true; billSec: true } };
|
||||
recording: { select: { id: true; status: true } };
|
||||
};
|
||||
}>;
|
||||
|
||||
type RawCdrDetailRecord = Prisma.RawCdrGetPayload<{
|
||||
include: {
|
||||
customer: { select: { id: true; name: true; status: true } };
|
||||
customerGateway: { select: { id: true; name: true; status: true; authMode: true; sourceIp: true } };
|
||||
customerGatewayPolicy: {
|
||||
select: {
|
||||
id: true;
|
||||
name: true;
|
||||
status: true;
|
||||
priority: true;
|
||||
callerMode: true;
|
||||
callerValue: true;
|
||||
calleeMode: true;
|
||||
calleeValue: true;
|
||||
};
|
||||
};
|
||||
businessPrefixRef: { select: { id: true; name: true; prefix: true; priority: true; status: true } };
|
||||
vendor: { select: { id: true; name: true; status: true } };
|
||||
vendorGateway: { select: { id: true; name: true; status: true; authMode: true; host: true; port: true; transport: true } };
|
||||
lineGroup: { select: { id: true; name: true; status: true } };
|
||||
ratedCdr: {
|
||||
select: {
|
||||
id: true;
|
||||
billSec: true;
|
||||
customerFee: true;
|
||||
vendorCost: true;
|
||||
grossProfit: true;
|
||||
customerRate: true;
|
||||
vendorRate: true;
|
||||
ratedAt: true;
|
||||
};
|
||||
};
|
||||
recording: {
|
||||
select: {
|
||||
id: true;
|
||||
storageKey: true;
|
||||
sha256: true;
|
||||
bytes: true;
|
||||
durationSec: true;
|
||||
status: true;
|
||||
movedAt: true;
|
||||
createdAt: true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}>;
|
||||
|
||||
@@ -76,7 +200,7 @@ type RawCdrRecord = Prisma.RawCdrGetPayload<{
|
||||
export class PrismaCdrsRepository implements CdrsRepository {
|
||||
constructor(@Inject(PrismaService) private readonly prisma: PrismaService) {}
|
||||
|
||||
async list(query: CdrQuery): Promise<{ items: CdrListItem[]; total: number }> {
|
||||
async list(query: CdrQuery): Promise<{ items: CdrListItem[]; meta: CdrPageMeta }> {
|
||||
const where = this.where(query);
|
||||
const [items, total] = await this.prisma.$transaction([
|
||||
this.prisma.rawCdr.findMany({
|
||||
@@ -88,18 +212,26 @@ export class PrismaCdrsRepository implements CdrsRepository {
|
||||
}),
|
||||
this.prisma.rawCdr.count({ where })
|
||||
]);
|
||||
return { items: items.map((item) => this.toItem(item)), total };
|
||||
return {
|
||||
items: items.map((item) => this.toItem(item)),
|
||||
meta: {
|
||||
total,
|
||||
take: query.take,
|
||||
skip: query.skip,
|
||||
hasMore: query.skip + items.length < total
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async get(id: string): Promise<CdrListItem> {
|
||||
async get(id: string): Promise<CdrDetail> {
|
||||
const item = await this.prisma.rawCdr.findUnique({
|
||||
where: { id },
|
||||
include: this.includeCdr()
|
||||
include: this.includeDetail()
|
||||
});
|
||||
if (!item) {
|
||||
throw new NotFoundException({ code: 'CDR_NOT_FOUND', message: 'CDR not found.' });
|
||||
}
|
||||
return this.toItem(item);
|
||||
return this.toDetail(item);
|
||||
}
|
||||
|
||||
private where(query: CdrQuery): Prisma.RawCdrWhereInput {
|
||||
@@ -109,7 +241,8 @@ export class PrismaCdrsRepository implements CdrsRepository {
|
||||
customerGatewayId: query.customerGatewayId,
|
||||
vendorGatewayId: query.vendorGatewayId,
|
||||
calleeCityCode: query.cityCode,
|
||||
calleeOperator: query.carrier
|
||||
calleeOperator: query.carrier,
|
||||
startedAt: query.startedFrom || query.startedTo ? { gte: query.startedFrom, lte: query.startedTo } : undefined
|
||||
};
|
||||
}
|
||||
|
||||
@@ -120,7 +253,55 @@ export class PrismaCdrsRepository implements CdrsRepository {
|
||||
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 } }
|
||||
ratedCdr: { select: { customerFee: true, vendorCost: true, grossProfit: true, billSec: true } },
|
||||
recording: { select: { id: true, status: true } }
|
||||
} satisfies Prisma.RawCdrInclude;
|
||||
}
|
||||
|
||||
private includeDetail() {
|
||||
return {
|
||||
customer: { select: { id: true, name: true, status: true } },
|
||||
customerGateway: { select: { id: true, name: true, status: true, authMode: true, sourceIp: true } },
|
||||
customerGatewayPolicy: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
status: true,
|
||||
priority: true,
|
||||
callerMode: true,
|
||||
callerValue: true,
|
||||
calleeMode: true,
|
||||
calleeValue: true
|
||||
}
|
||||
},
|
||||
businessPrefixRef: { select: { id: true, name: true, prefix: true, priority: true, status: true } },
|
||||
vendor: { select: { id: true, name: true, status: true } },
|
||||
vendorGateway: { select: { id: true, name: true, status: true, authMode: true, host: true, port: true, transport: true } },
|
||||
lineGroup: { select: { id: true, name: true, status: true } },
|
||||
ratedCdr: {
|
||||
select: {
|
||||
id: true,
|
||||
billSec: true,
|
||||
customerFee: true,
|
||||
vendorCost: true,
|
||||
grossProfit: true,
|
||||
customerRate: true,
|
||||
vendorRate: true,
|
||||
ratedAt: true
|
||||
}
|
||||
},
|
||||
recording: {
|
||||
select: {
|
||||
id: true,
|
||||
storageKey: true,
|
||||
sha256: true,
|
||||
bytes: true,
|
||||
durationSec: true,
|
||||
status: true,
|
||||
movedAt: true,
|
||||
createdAt: true
|
||||
}
|
||||
}
|
||||
} satisfies Prisma.RawCdrInclude;
|
||||
}
|
||||
|
||||
@@ -132,6 +313,9 @@ export class PrismaCdrsRepository implements CdrsRepository {
|
||||
sourceIp: item.sourceIp,
|
||||
caller: item.caller,
|
||||
callee: item.callee,
|
||||
rawCallee: item.rawCallee,
|
||||
businessPrefixId: item.businessPrefixId,
|
||||
businessPrefix: item.businessPrefix,
|
||||
calleeCityCode: item.calleeCityCode,
|
||||
calleeCityName: item.calleeCityName,
|
||||
calleeProvinceName: item.calleeProvinceName,
|
||||
@@ -149,6 +333,8 @@ export class PrismaCdrsRepository implements CdrsRepository {
|
||||
vendorGatewayPort: item.vendorGateway?.port ?? null,
|
||||
lineGroupId: item.lineGroupId,
|
||||
lineGroupName: item.lineGroup?.name ?? null,
|
||||
landingCaller: item.landingCaller,
|
||||
landingCallee: item.landingCallee,
|
||||
startedAt: item.startedAt,
|
||||
answeredAt: item.answeredAt,
|
||||
endedAt: item.endedAt,
|
||||
@@ -161,7 +347,88 @@ export class PrismaCdrsRepository implements CdrsRepository {
|
||||
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
|
||||
billSec: item.ratedCdr?.billSec ?? null,
|
||||
hasRecording: Boolean(item.recording),
|
||||
recordingId: item.recording?.id ?? null,
|
||||
recordingStatus: item.recording?.status ?? null
|
||||
};
|
||||
}
|
||||
|
||||
private toDetail(item: RawCdrDetailRecord): CdrDetail {
|
||||
const base = this.toItem(item);
|
||||
return {
|
||||
...base,
|
||||
customer: item.customer ? { id: item.customer.id, name: item.customer.name, status: item.customer.status } : null,
|
||||
customerGateway: item.customerGateway
|
||||
? {
|
||||
id: item.customerGateway.id,
|
||||
name: item.customerGateway.name,
|
||||
status: item.customerGateway.status,
|
||||
authMode: item.customerGateway.authMode,
|
||||
sourceIp: item.customerGateway.sourceIp
|
||||
}
|
||||
: null,
|
||||
customerGatewayPolicy: item.customerGatewayPolicy
|
||||
? {
|
||||
id: item.customerGatewayPolicy.id,
|
||||
name: item.customerGatewayPolicy.name,
|
||||
status: item.customerGatewayPolicy.status,
|
||||
priority: item.customerGatewayPolicy.priority,
|
||||
callerMode: item.customerGatewayPolicy.callerMode,
|
||||
callerValue: item.customerGatewayPolicy.callerValue,
|
||||
calleeMode: item.customerGatewayPolicy.calleeMode,
|
||||
calleeValue: item.customerGatewayPolicy.calleeValue
|
||||
}
|
||||
: null,
|
||||
businessPrefixRef: item.businessPrefixRef
|
||||
? {
|
||||
id: item.businessPrefixRef.id,
|
||||
name: item.businessPrefixRef.name,
|
||||
prefix: item.businessPrefixRef.prefix,
|
||||
priority: item.businessPrefixRef.priority,
|
||||
status: item.businessPrefixRef.status
|
||||
}
|
||||
: null,
|
||||
vendor: item.vendor ? { id: item.vendor.id, name: item.vendor.name, status: item.vendor.status } : null,
|
||||
vendorGateway: item.vendorGateway
|
||||
? {
|
||||
id: item.vendorGateway.id,
|
||||
name: item.vendorGateway.name,
|
||||
status: item.vendorGateway.status,
|
||||
authMode: item.vendorGateway.authMode,
|
||||
host: item.vendorGateway.host,
|
||||
port: item.vendorGateway.port,
|
||||
transport: item.vendorGateway.transport
|
||||
}
|
||||
: null,
|
||||
lineGroup: item.lineGroup ? { id: item.lineGroup.id, name: item.lineGroup.name, status: item.lineGroup.status } : null,
|
||||
rated: item.ratedCdr
|
||||
? {
|
||||
id: item.ratedCdr.id,
|
||||
billSec: item.ratedCdr.billSec,
|
||||
customerFee: item.ratedCdr.customerFee.toFixed(6),
|
||||
vendorCost: item.ratedCdr.vendorCost.toFixed(6),
|
||||
grossProfit: item.ratedCdr.grossProfit.toFixed(6),
|
||||
customerRate: item.ratedCdr.customerRate,
|
||||
vendorRate: item.ratedCdr.vendorRate,
|
||||
ratedAt: item.ratedCdr.ratedAt
|
||||
}
|
||||
: null,
|
||||
recording: item.recording
|
||||
? {
|
||||
id: item.recording.id,
|
||||
storageKey: item.recording.storageKey,
|
||||
sha256: item.recording.sha256,
|
||||
bytes: item.recording.bytes.toString(),
|
||||
durationSec: item.recording.durationSec,
|
||||
status: item.recording.status,
|
||||
movedAt: item.recording.movedAt,
|
||||
createdAt: item.recording.createdAt
|
||||
}
|
||||
: null,
|
||||
payload: item.payload,
|
||||
receivedAt: item.receivedAt,
|
||||
createdAt: item.createdAt
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user