Initial LisgloSIPS V2 implementation
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Prisma } from '@lisglosips/database';
|
||||
import { PrismaService } from '../database/prisma.service.js';
|
||||
|
||||
export interface DashboardCdrRow {
|
||||
startedAt: Date;
|
||||
durationSec: number;
|
||||
sipCode: number;
|
||||
}
|
||||
|
||||
export interface DashboardRatedRow {
|
||||
startedAt: Date;
|
||||
customerFee: string;
|
||||
vendorCost: string;
|
||||
grossProfit: string;
|
||||
}
|
||||
|
||||
export interface DashboardFailureCodeRow {
|
||||
sipCode: number;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface DashboardGatewayFailureRow {
|
||||
vendorGatewayId: string;
|
||||
vendorGatewayName: string;
|
||||
vendorName: string | null;
|
||||
host: string;
|
||||
port: number;
|
||||
status: string;
|
||||
failedCalls: number;
|
||||
totalCalls: number;
|
||||
}
|
||||
|
||||
export interface DashboardSummarySnapshot {
|
||||
cdrs: DashboardCdrRow[];
|
||||
ratedCdrs: DashboardRatedRow[];
|
||||
failureCodes: DashboardFailureCodeRow[];
|
||||
abnormalGateways: DashboardGatewayFailureRow[];
|
||||
activeCustomers: number;
|
||||
activeCustomerGateways: number;
|
||||
activeVendorGateways: number;
|
||||
pendingQuality: number;
|
||||
}
|
||||
|
||||
export const DASHBOARD_REPOSITORY = Symbol('DASHBOARD_REPOSITORY');
|
||||
|
||||
export interface DashboardRepository {
|
||||
summaryWindow(start: Date, end: Date): Promise<DashboardSummarySnapshot>;
|
||||
trendWindow(start: Date, end: Date): Promise<{ cdrs: DashboardCdrRow[]; ratedCdrs: DashboardRatedRow[] }>;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class PrismaDashboardRepository implements DashboardRepository {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async summaryWindow(start: Date, end: Date): Promise<DashboardSummarySnapshot> {
|
||||
const [cdrs, ratedCdrs, activeCustomers, activeCustomerGateways, activeVendorGateways, pendingQuality] = await Promise.all([
|
||||
this.prisma.rawCdr.findMany({
|
||||
where: { startedAt: { gte: start, lt: end } },
|
||||
select: { startedAt: true, durationSec: true, sipCode: true }
|
||||
}),
|
||||
this.prisma.ratedCdr.findMany({
|
||||
where: { rawCdr: { startedAt: { gte: start, lt: end } } },
|
||||
select: {
|
||||
customerFee: true,
|
||||
vendorCost: true,
|
||||
grossProfit: true,
|
||||
rawCdr: { select: { startedAt: true } }
|
||||
}
|
||||
}),
|
||||
this.prisma.customer.count({ where: { status: 'ENABLED', deletedAt: null } }),
|
||||
this.prisma.customerGateway.count({ where: { status: 'ENABLED', deletedAt: null } }),
|
||||
this.prisma.vendorGateway.count({ where: { status: 'ENABLED', deletedAt: null } }),
|
||||
this.prisma.recording.count({ where: { status: 'READY', reviews: { none: {} } } })
|
||||
]);
|
||||
|
||||
const failureCodes = failureCodesFrom(cdrs);
|
||||
const abnormalGateways = await this.gatewayFailures(start, end);
|
||||
|
||||
return {
|
||||
cdrs,
|
||||
ratedCdrs: ratedCdrs.map((row) => ({
|
||||
startedAt: row.rawCdr.startedAt,
|
||||
customerFee: decimalString(row.customerFee),
|
||||
vendorCost: decimalString(row.vendorCost),
|
||||
grossProfit: decimalString(row.grossProfit)
|
||||
})),
|
||||
failureCodes,
|
||||
abnormalGateways,
|
||||
activeCustomers,
|
||||
activeCustomerGateways,
|
||||
activeVendorGateways,
|
||||
pendingQuality
|
||||
};
|
||||
}
|
||||
|
||||
async trendWindow(start: Date, end: Date): Promise<{ cdrs: DashboardCdrRow[]; ratedCdrs: DashboardRatedRow[] }> {
|
||||
const [cdrs, ratedCdrs] = await Promise.all([
|
||||
this.prisma.rawCdr.findMany({
|
||||
where: { startedAt: { gte: start, lt: end } },
|
||||
select: { startedAt: true, durationSec: true, sipCode: true }
|
||||
}),
|
||||
this.prisma.ratedCdr.findMany({
|
||||
where: { rawCdr: { startedAt: { gte: start, lt: end } } },
|
||||
select: {
|
||||
customerFee: true,
|
||||
vendorCost: true,
|
||||
grossProfit: true,
|
||||
rawCdr: { select: { startedAt: true } }
|
||||
}
|
||||
})
|
||||
]);
|
||||
|
||||
return {
|
||||
cdrs,
|
||||
ratedCdrs: ratedCdrs.map((row) => ({
|
||||
startedAt: row.rawCdr.startedAt,
|
||||
customerFee: decimalString(row.customerFee),
|
||||
vendorCost: decimalString(row.vendorCost),
|
||||
grossProfit: decimalString(row.grossProfit)
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
private async gatewayFailures(start: Date, end: Date): Promise<DashboardGatewayFailureRow[]> {
|
||||
const cdrs = await this.prisma.rawCdr.findMany({
|
||||
where: { startedAt: { gte: start, lt: end }, vendorGatewayId: { not: null } },
|
||||
select: {
|
||||
vendorGatewayId: true,
|
||||
sipCode: true,
|
||||
vendorGateway: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
host: true,
|
||||
port: true,
|
||||
status: true,
|
||||
vendor: { select: { name: true } }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const grouped = new Map<string, { failedCalls: number; totalCalls: number; gateway: NonNullable<(typeof cdrs)[number]['vendorGateway']> }>();
|
||||
for (const cdr of cdrs) {
|
||||
if (!cdr.vendorGatewayId || !cdr.vendorGateway) {
|
||||
continue;
|
||||
}
|
||||
const current = grouped.get(cdr.vendorGatewayId) ?? { failedCalls: 0, totalCalls: 0, gateway: cdr.vendorGateway };
|
||||
current.totalCalls += 1;
|
||||
if (!isAnswered(cdr.sipCode)) {
|
||||
current.failedCalls += 1;
|
||||
}
|
||||
grouped.set(cdr.vendorGatewayId, current);
|
||||
}
|
||||
|
||||
return [...grouped.entries()]
|
||||
.map(([vendorGatewayId, item]) => ({
|
||||
vendorGatewayId,
|
||||
vendorGatewayName: item.gateway.name,
|
||||
vendorName: item.gateway.vendor?.name ?? null,
|
||||
host: item.gateway.host,
|
||||
port: item.gateway.port,
|
||||
status: item.gateway.status,
|
||||
failedCalls: item.failedCalls,
|
||||
totalCalls: item.totalCalls
|
||||
}))
|
||||
.filter((item) => item.failedCalls > 0)
|
||||
.sort((left, right) => right.failedCalls - left.failedCalls || right.totalCalls - left.totalCalls)
|
||||
.slice(0, 10);
|
||||
}
|
||||
}
|
||||
|
||||
function failureCodesFrom(cdrs: DashboardCdrRow[]): DashboardFailureCodeRow[] {
|
||||
const counts = new Map<number, number>();
|
||||
for (const cdr of cdrs) {
|
||||
if (isAnswered(cdr.sipCode)) {
|
||||
continue;
|
||||
}
|
||||
counts.set(cdr.sipCode, (counts.get(cdr.sipCode) ?? 0) + 1);
|
||||
}
|
||||
return [...counts.entries()]
|
||||
.map(([sipCode, count]) => ({ sipCode, count }))
|
||||
.sort((left, right) => right.count - left.count || left.sipCode - right.sipCode)
|
||||
.slice(0, 10);
|
||||
}
|
||||
|
||||
function decimalString(value: Prisma.Decimal): string {
|
||||
return value.toFixed(6);
|
||||
}
|
||||
|
||||
function isAnswered(sipCode: number): boolean {
|
||||
return sipCode >= 200 && sipCode < 300;
|
||||
}
|
||||
Reference in New Issue
Block a user