mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 06:00:55 +00:00
refactor: cut off the old customre entity
This commit is contained in:
@@ -1,25 +1,25 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Employee } from '@tria-plc/iamapi-common';
|
||||
import { User } from '@tria-plc/iamapi-common/entities/iam/user/user.entity';
|
||||
import { Module } from "@nestjs/common";
|
||||
import { TypeOrmModule } from "@nestjs/typeorm";
|
||||
import { Employee } from "@tria-plc/iamapi-common";
|
||||
import { User } from "@tria-plc/iamapi-common/entities/iam/user/user.entity";
|
||||
|
||||
import { Booking } from '../bookings/entities/booking.entity';
|
||||
import { Cargo } from '../cargoes/entities/cargoes.entity';
|
||||
import { Container } from '../container-management/entities/container.entity';
|
||||
import { Customer } from '../customers/entities/customer.entity';
|
||||
import { PaymentEntity } from '../payment/entities/payment.entity';
|
||||
import { Train } from '../trains/entities/train.entity';
|
||||
import { Wagon } from '../wagons/entities/wagon.entity';
|
||||
import { OverviewController } from './overview.controller';
|
||||
import { OverviewRepository } from './overview.repository';
|
||||
import { OverviewService } from './overview.service';
|
||||
import { Booking } from "../bookings/entities/booking.entity";
|
||||
import { Cargo } from "../cargoes/entities/cargoes.entity";
|
||||
import { Container } from "../container-management/entities/container.entity";
|
||||
import { Company } from "../companies/entities/company.entity";
|
||||
import { PaymentEntity } from "../payment/entities/payment.entity";
|
||||
import { Train } from "../trains/entities/train.entity";
|
||||
import { Wagon } from "../wagons/entities/wagon.entity";
|
||||
import { OverviewController } from "./overview.controller";
|
||||
import { OverviewRepository } from "./overview.repository";
|
||||
import { OverviewService } from "./overview.service";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
Booking,
|
||||
PaymentEntity,
|
||||
Customer,
|
||||
Company,
|
||||
Train,
|
||||
Wagon,
|
||||
Container,
|
||||
@@ -31,4 +31,4 @@ import { OverviewService } from './overview.service';
|
||||
controllers: [OverviewController],
|
||||
providers: [OverviewService, OverviewRepository],
|
||||
})
|
||||
export class OverviewModule {}
|
||||
export class OverviewModule { }
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { EUserStatus } from '@tria-plc/api-common/utils/enums/user.enum';
|
||||
import { Employee } from '@tria-plc/iamapi-common';
|
||||
import { User } from '@tria-plc/iamapi-common/entities/iam/user/user.entity';
|
||||
import { Freight } from '@edr/types';
|
||||
import { Repository, ObjectLiteral } from 'typeorm';
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { EUserStatus } from "@tria-plc/api-common/utils/enums/user.enum";
|
||||
import { Employee } from "@tria-plc/iamapi-common";
|
||||
import { User } from "@tria-plc/iamapi-common/entities/iam/user/user.entity";
|
||||
import { Freight } from "@edr/types";
|
||||
import { Repository, ObjectLiteral } from "typeorm";
|
||||
|
||||
import { Booking } from '../bookings/entities/booking.entity';
|
||||
import { Cargo } from '../cargoes/entities/cargoes.entity';
|
||||
import { Container } from '../container-management/entities/container.entity';
|
||||
import { Customer } from '../customers/entities/customer.entity';
|
||||
import { PaymentEntity } from '../payment/entities/payment.entity';
|
||||
import { Train } from '../trains/entities/train.entity';
|
||||
import { Wagon } from '../wagons/entities/wagon.entity';
|
||||
import { Booking } from "../bookings/entities/booking.entity";
|
||||
import { Cargo } from "../cargoes/entities/cargoes.entity";
|
||||
import { Container } from "../container-management/entities/container.entity";
|
||||
import { PaymentEntity } from "../payment/entities/payment.entity";
|
||||
import { Train } from "../trains/entities/train.entity";
|
||||
import { Wagon } from "../wagons/entities/wagon.entity";
|
||||
import {
|
||||
OVERVIEW_CLOSED_STATUSES,
|
||||
OVERVIEW_IN_APPROVAL_STATUSES,
|
||||
OVERVIEW_NEEDS_ACTION_STATUSES,
|
||||
OVERVIEW_URGENT_PRIORITY_THRESHOLD,
|
||||
} from './overview.constants';
|
||||
} from "./overview.constants";
|
||||
import { Company } from "../companies/entities/company.entity";
|
||||
|
||||
export type OverviewBookingKpisRow = {
|
||||
totalActive: number;
|
||||
@@ -46,8 +46,8 @@ export class OverviewRepository {
|
||||
private readonly bookingRepository: Repository<Booking>,
|
||||
@InjectRepository(PaymentEntity)
|
||||
private readonly paymentRepository: Repository<PaymentEntity>,
|
||||
@InjectRepository(Customer)
|
||||
private readonly customerRepository: Repository<Customer>,
|
||||
@InjectRepository(Company)
|
||||
private readonly companyRepository: Repository<Company>,
|
||||
@InjectRepository(Train)
|
||||
private readonly trainRepository: Repository<Train>,
|
||||
@InjectRepository(Wagon)
|
||||
@@ -60,32 +60,32 @@ export class OverviewRepository {
|
||||
private readonly employeeRepository: Repository<Employee>,
|
||||
@InjectRepository(User)
|
||||
private readonly userRepository: Repository<User>,
|
||||
) {}
|
||||
) { }
|
||||
|
||||
async getBookingKpis(): Promise<OverviewBookingKpisRow> {
|
||||
const row = await this.bookingRepository
|
||||
.createQueryBuilder('booking')
|
||||
.createQueryBuilder("booking")
|
||||
.select(
|
||||
`COUNT(*) FILTER (WHERE booking.status NOT IN (:...closedStatuses) AND booking.status != 'DRAFT')::int`,
|
||||
'totalActive',
|
||||
"totalActive",
|
||||
)
|
||||
.addSelect(
|
||||
`COUNT(*) FILTER (WHERE booking.status IN (:...needsActionStatuses))::int`,
|
||||
'needsAction',
|
||||
"needsAction",
|
||||
)
|
||||
.addSelect(
|
||||
`COUNT(*) FILTER (WHERE booking.priority_score >= :urgentThreshold)::int`,
|
||||
'urgent',
|
||||
"urgent",
|
||||
)
|
||||
.addSelect(
|
||||
`COUNT(*) FILTER (WHERE booking.status IN (:...inApprovalStatuses))::int`,
|
||||
'inApproval',
|
||||
"inApproval",
|
||||
)
|
||||
.addSelect(
|
||||
`COUNT(*) FILTER (WHERE booking.created_at >= CURRENT_DATE AND booking.status != 'DRAFT')::int`,
|
||||
'submittedToday',
|
||||
"submittedToday",
|
||||
)
|
||||
.where('booking.deleted_at IS NULL')
|
||||
.where("booking.deleted_at IS NULL")
|
||||
.setParameters({
|
||||
closedStatuses: [...OVERVIEW_CLOSED_STATUSES],
|
||||
needsActionStatuses: [...OVERVIEW_NEEDS_ACTION_STATUSES],
|
||||
@@ -112,9 +112,9 @@ export class OverviewRepository {
|
||||
const [trainsActive, wagonsAvailable, containersInTransit, cargoesLoaded] =
|
||||
await Promise.all([
|
||||
this.trainRepository
|
||||
.createQueryBuilder('train')
|
||||
.where('train.deleted_at IS NULL')
|
||||
.andWhere('train.status IN (:...statuses)', {
|
||||
.createQueryBuilder("train")
|
||||
.where("train.deleted_at IS NULL")
|
||||
.andWhere("train.status IN (:...statuses)", {
|
||||
statuses: [
|
||||
Freight.TrainStatus.InService,
|
||||
Freight.TrainStatus.Scheduled,
|
||||
@@ -122,39 +122,46 @@ export class OverviewRepository {
|
||||
})
|
||||
.getCount(),
|
||||
this.wagonRepository
|
||||
.createQueryBuilder('wagon')
|
||||
.where('wagon.deleted_at IS NULL')
|
||||
.andWhere('wagon.status = :status', { status: Freight.WagonStatus.Available })
|
||||
.createQueryBuilder("wagon")
|
||||
.where("wagon.deleted_at IS NULL")
|
||||
.andWhere("wagon.status = :status", {
|
||||
status: Freight.WagonStatus.Available,
|
||||
})
|
||||
.getCount(),
|
||||
this.containerRepository
|
||||
.createQueryBuilder('container')
|
||||
.where('container.deleted_at IS NULL')
|
||||
.andWhere('container.status = :status', { status: 'IN_TRANSIT' })
|
||||
.createQueryBuilder("container")
|
||||
.where("container.deleted_at IS NULL")
|
||||
.andWhere("container.status = :status", { status: "IN_TRANSIT" })
|
||||
.getCount(),
|
||||
this.cargoRepository
|
||||
.createQueryBuilder('cargo')
|
||||
.where('cargo.deleted_at IS NULL')
|
||||
.andWhere('cargo.status IN (:...statuses)', {
|
||||
statuses: ['LOADED', 'IN_TRANSIT'],
|
||||
.createQueryBuilder("cargo")
|
||||
.where("cargo.deleted_at IS NULL")
|
||||
.andWhere("cargo.status IN (:...statuses)", {
|
||||
statuses: ["LOADED", "IN_TRANSIT"],
|
||||
})
|
||||
.getCount(),
|
||||
]);
|
||||
|
||||
return { trainsActive, wagonsAvailable, containersInTransit, cargoesLoaded };
|
||||
return {
|
||||
trainsActive,
|
||||
wagonsAvailable,
|
||||
containersInTransit,
|
||||
cargoesLoaded,
|
||||
};
|
||||
}
|
||||
|
||||
async getCustomerKpis(): Promise<{
|
||||
totalCustomers: number;
|
||||
newCustomersThisMonth: number;
|
||||
}> {
|
||||
const row = await this.customerRepository
|
||||
.createQueryBuilder('customer')
|
||||
.select('COUNT(*)::int', 'totalCustomers')
|
||||
const row = await this.companyRepository
|
||||
.createQueryBuilder("customer")
|
||||
.select("COUNT(*)::int", "totalCustomers")
|
||||
.addSelect(
|
||||
`COUNT(*) FILTER (WHERE customer.created_at >= date_trunc('month', CURRENT_DATE))::int`,
|
||||
'newCustomersThisMonth',
|
||||
"newCustomersThisMonth",
|
||||
)
|
||||
.where('customer.deleted_at IS NULL')
|
||||
.where("customer.deleted_at IS NULL")
|
||||
.getRawOne<Record<string, string>>();
|
||||
|
||||
return {
|
||||
@@ -170,26 +177,26 @@ export class OverviewRepository {
|
||||
successfulPaymentsMtd: number;
|
||||
}> {
|
||||
const revenueRow = await this.paymentRepository
|
||||
.createQueryBuilder('payment')
|
||||
.createQueryBuilder("payment")
|
||||
.select(
|
||||
`COALESCE(SUM(payment.amount) FILTER (WHERE payment.currency = 'ETB'), 0)`,
|
||||
'revenueMtdEtb',
|
||||
"revenueMtdEtb",
|
||||
)
|
||||
.addSelect(
|
||||
`COALESCE(SUM(payment.amount) FILTER (WHERE payment.currency = 'USD'), 0)`,
|
||||
'revenueMtdUsd',
|
||||
"revenueMtdUsd",
|
||||
)
|
||||
.addSelect(`COUNT(*)::int`, 'successfulPaymentsMtd')
|
||||
.where('payment.status = :status', { status: 'success' })
|
||||
.addSelect(`COUNT(*)::int`, "successfulPaymentsMtd")
|
||||
.where("payment.status = :status", { status: "success" })
|
||||
.andWhere(
|
||||
`COALESCE(payment.paid_at, payment.created_at) >= date_trunc('month', CURRENT_DATE)`,
|
||||
)
|
||||
.getRawOne<Record<string, string>>();
|
||||
|
||||
const pendingPayments = await this.paymentRepository
|
||||
.createQueryBuilder('payment')
|
||||
.where('payment.status IN (:...statuses)', {
|
||||
statuses: ['action-required', 'processing'],
|
||||
.createQueryBuilder("payment")
|
||||
.where("payment.status IN (:...statuses)", {
|
||||
statuses: ["action-required", "processing"],
|
||||
})
|
||||
.getCount();
|
||||
|
||||
@@ -201,7 +208,10 @@ export class OverviewRepository {
|
||||
};
|
||||
}
|
||||
|
||||
async getStaffKpis(): Promise<{ activeEmployees: number; activeUsers: number }> {
|
||||
async getStaffKpis(): Promise<{
|
||||
activeEmployees: number;
|
||||
activeUsers: number;
|
||||
}> {
|
||||
const [activeEmployees, activeUsers] = await Promise.all([
|
||||
this.employeeRepository.count({
|
||||
where: { isCurrent: true },
|
||||
@@ -217,15 +227,17 @@ export class OverviewRepository {
|
||||
return { activeEmployees, activeUsers };
|
||||
}
|
||||
|
||||
async getBookingTrend(days: number): Promise<{ date: string; count: number }[]> {
|
||||
async getBookingTrend(
|
||||
days: number,
|
||||
): Promise<{ date: string; count: number }[]> {
|
||||
const rows = await this.bookingRepository
|
||||
.createQueryBuilder('booking')
|
||||
.select(`to_char(booking.created_at::date, 'YYYY-MM-DD')`, 'date')
|
||||
.addSelect('COUNT(*)::int', 'count')
|
||||
.where('booking.deleted_at IS NULL')
|
||||
.createQueryBuilder("booking")
|
||||
.select(`to_char(booking.created_at::date, 'YYYY-MM-DD')`, "date")
|
||||
.addSelect("COUNT(*)::int", "count")
|
||||
.where("booking.deleted_at IS NULL")
|
||||
.andWhere(`booking.created_at >= CURRENT_DATE - :days::int + 1`, { days })
|
||||
.groupBy('booking.created_at::date')
|
||||
.orderBy('booking.created_at::date', 'ASC')
|
||||
.groupBy("booking.created_at::date")
|
||||
.orderBy("booking.created_at::date", "ASC")
|
||||
.getRawMany<{ date: string; count: string }>();
|
||||
|
||||
return rows.map((row) => ({
|
||||
@@ -236,11 +248,11 @@ export class OverviewRepository {
|
||||
|
||||
async getStatusCounts(): Promise<Record<string, number>> {
|
||||
const rows = await this.bookingRepository
|
||||
.createQueryBuilder('booking')
|
||||
.select('booking.status', 'status')
|
||||
.addSelect('COUNT(*)::int', 'count')
|
||||
.where('booking.deleted_at IS NULL')
|
||||
.groupBy('booking.status')
|
||||
.createQueryBuilder("booking")
|
||||
.select("booking.status", "status")
|
||||
.addSelect("COUNT(*)::int", "count")
|
||||
.where("booking.deleted_at IS NULL")
|
||||
.groupBy("booking.status")
|
||||
.getRawMany<{ status: string; count: string }>();
|
||||
|
||||
return Object.fromEntries(
|
||||
@@ -252,26 +264,26 @@ export class OverviewRepository {
|
||||
days: number,
|
||||
): Promise<{ date: string; amountEtb: number; amountUsd: number }[]> {
|
||||
const rows = await this.paymentRepository
|
||||
.createQueryBuilder('payment')
|
||||
.createQueryBuilder("payment")
|
||||
.select(
|
||||
`to_char(COALESCE(payment.paid_at, payment.created_at)::date, 'YYYY-MM-DD')`,
|
||||
'date',
|
||||
"date",
|
||||
)
|
||||
.addSelect(
|
||||
`COALESCE(SUM(payment.amount) FILTER (WHERE payment.currency = 'ETB'), 0)`,
|
||||
'amountEtb',
|
||||
"amountEtb",
|
||||
)
|
||||
.addSelect(
|
||||
`COALESCE(SUM(payment.amount) FILTER (WHERE payment.currency = 'USD'), 0)`,
|
||||
'amountUsd',
|
||||
"amountUsd",
|
||||
)
|
||||
.where('payment.status = :status', { status: 'success' })
|
||||
.where("payment.status = :status", { status: "success" })
|
||||
.andWhere(
|
||||
`COALESCE(payment.paid_at, payment.created_at) >= CURRENT_DATE - :days::int + 1`,
|
||||
{ days },
|
||||
)
|
||||
.groupBy(`COALESCE(payment.paid_at, payment.created_at)::date`)
|
||||
.orderBy(`COALESCE(payment.paid_at, payment.created_at)::date`, 'ASC')
|
||||
.orderBy(`COALESCE(payment.paid_at, payment.created_at)::date`, "ASC")
|
||||
.getRawMany<{ date: string; amountEtb: string; amountUsd: string }>();
|
||||
|
||||
return rows.map((row) => ({
|
||||
@@ -283,18 +295,18 @@ export class OverviewRepository {
|
||||
|
||||
async getRecentBookings(limit: number): Promise<OverviewRecentBookingRow[]> {
|
||||
const rows = await this.bookingRepository
|
||||
.createQueryBuilder('booking')
|
||||
.leftJoin('booking.company', 'company')
|
||||
.select('booking.id', 'id')
|
||||
.addSelect('booking.reference', 'reference')
|
||||
.addSelect('COALESCE(company.name, \'—\')', 'customerLabel')
|
||||
.addSelect('booking.status', 'status')
|
||||
.addSelect('booking.priority_score', 'priorityScore')
|
||||
.addSelect('booking.total_amount', 'totalAmount')
|
||||
.addSelect('booking.payment_currency', 'paymentCurrency')
|
||||
.addSelect('booking.created_at', 'createdAt')
|
||||
.where('booking.deleted_at IS NULL')
|
||||
.orderBy('booking.created_at', 'DESC')
|
||||
.createQueryBuilder("booking")
|
||||
.leftJoin("booking.company", "company")
|
||||
.select("booking.id", "id")
|
||||
.addSelect("booking.reference", "reference")
|
||||
.addSelect("COALESCE(company.name, '—')", "customerLabel")
|
||||
.addSelect("booking.status", "status")
|
||||
.addSelect("booking.priority_score", "priorityScore")
|
||||
.addSelect("booking.total_amount", "totalAmount")
|
||||
.addSelect("booking.payment_currency", "paymentCurrency")
|
||||
.addSelect("booking.created_at", "createdAt")
|
||||
.where("booking.deleted_at IS NULL")
|
||||
.orderBy("booking.created_at", "DESC")
|
||||
.limit(limit)
|
||||
.getRawMany<{
|
||||
id: string;
|
||||
@@ -319,15 +331,17 @@ export class OverviewRepository {
|
||||
}));
|
||||
}
|
||||
|
||||
async getBookingsByFreightType(): Promise<{ label: string; count: number }[]> {
|
||||
async getBookingsByFreightType(): Promise<
|
||||
{ label: string; count: number }[]
|
||||
> {
|
||||
const rows = await this.bookingRepository
|
||||
.createQueryBuilder('booking')
|
||||
.select('booking.freight_type', 'label')
|
||||
.addSelect('COUNT(*)::int', 'count')
|
||||
.where('booking.deleted_at IS NULL')
|
||||
.createQueryBuilder("booking")
|
||||
.select("booking.freight_type", "label")
|
||||
.addSelect("COUNT(*)::int", "count")
|
||||
.where("booking.deleted_at IS NULL")
|
||||
.andWhere("booking.status != 'DRAFT'")
|
||||
.groupBy('booking.freight_type')
|
||||
.orderBy('count', 'DESC')
|
||||
.groupBy("booking.freight_type")
|
||||
.orderBy("count", "DESC")
|
||||
.getRawMany<{ label: string; count: string }>();
|
||||
|
||||
return rows.map((row) => ({
|
||||
@@ -338,13 +352,13 @@ export class OverviewRepository {
|
||||
|
||||
async getBookingsByCurrency(): Promise<{ label: string; count: number }[]> {
|
||||
const rows = await this.bookingRepository
|
||||
.createQueryBuilder('booking')
|
||||
.select('booking.payment_currency', 'label')
|
||||
.addSelect('COUNT(*)::int', 'count')
|
||||
.where('booking.deleted_at IS NULL')
|
||||
.createQueryBuilder("booking")
|
||||
.select("booking.payment_currency", "label")
|
||||
.addSelect("COUNT(*)::int", "count")
|
||||
.where("booking.deleted_at IS NULL")
|
||||
.andWhere("booking.status != 'DRAFT'")
|
||||
.groupBy('booking.payment_currency')
|
||||
.orderBy('count', 'DESC')
|
||||
.groupBy("booking.payment_currency")
|
||||
.orderBy("count", "DESC")
|
||||
.getRawMany<{ label: string; count: string }>();
|
||||
|
||||
return rows.map((row) => ({
|
||||
@@ -355,11 +369,11 @@ export class OverviewRepository {
|
||||
|
||||
async getPaymentsByStatus(): Promise<{ status: string; count: number }[]> {
|
||||
const rows = await this.paymentRepository
|
||||
.createQueryBuilder('payment')
|
||||
.select('payment.status', 'status')
|
||||
.addSelect('COUNT(*)::int', 'count')
|
||||
.groupBy('payment.status')
|
||||
.orderBy('count', 'DESC')
|
||||
.createQueryBuilder("payment")
|
||||
.select("payment.status", "status")
|
||||
.addSelect("COUNT(*)::int", "count")
|
||||
.groupBy("payment.status")
|
||||
.orderBy("count", "DESC")
|
||||
.getRawMany<{ status: string; count: string }>();
|
||||
|
||||
return rows.map((row) => ({
|
||||
@@ -372,20 +386,25 @@ export class OverviewRepository {
|
||||
{ method: string; count: number; amountEtb: number; amountUsd: number }[]
|
||||
> {
|
||||
const rows = await this.paymentRepository
|
||||
.createQueryBuilder('payment')
|
||||
.select('payment.method', 'method')
|
||||
.addSelect('COUNT(*)::int', 'count')
|
||||
.createQueryBuilder("payment")
|
||||
.select("payment.method", "method")
|
||||
.addSelect("COUNT(*)::int", "count")
|
||||
.addSelect(
|
||||
`COALESCE(SUM(payment.amount) FILTER (WHERE payment.currency = 'ETB' AND payment.status = 'success'), 0)`,
|
||||
'amountEtb',
|
||||
"amountEtb",
|
||||
)
|
||||
.addSelect(
|
||||
`COALESCE(SUM(payment.amount) FILTER (WHERE payment.currency = 'USD' AND payment.status = 'success'), 0)`,
|
||||
'amountUsd',
|
||||
"amountUsd",
|
||||
)
|
||||
.groupBy('payment.method')
|
||||
.orderBy('count', 'DESC')
|
||||
.getRawMany<{ method: string; count: string; amountEtb: string; amountUsd: string }>();
|
||||
.groupBy("payment.method")
|
||||
.orderBy("count", "DESC")
|
||||
.getRawMany<{
|
||||
method: string;
|
||||
count: string;
|
||||
amountEtb: string;
|
||||
amountUsd: string;
|
||||
}>();
|
||||
|
||||
return rows.map((row) => ({
|
||||
method: row.method,
|
||||
@@ -395,16 +414,18 @@ export class OverviewRepository {
|
||||
}));
|
||||
}
|
||||
|
||||
async getRevenueByCurrency(): Promise<{ currency: string; amount: number }[]> {
|
||||
async getRevenueByCurrency(): Promise<
|
||||
{ currency: string; amount: number }[]
|
||||
> {
|
||||
const rows = await this.paymentRepository
|
||||
.createQueryBuilder('payment')
|
||||
.select('payment.currency', 'currency')
|
||||
.addSelect('COALESCE(SUM(payment.amount), 0)', 'amount')
|
||||
.where('payment.status = :status', { status: 'success' })
|
||||
.createQueryBuilder("payment")
|
||||
.select("payment.currency", "currency")
|
||||
.addSelect("COALESCE(SUM(payment.amount), 0)", "amount")
|
||||
.where("payment.status = :status", { status: "success" })
|
||||
.andWhere(
|
||||
`COALESCE(payment.paid_at, payment.created_at) >= date_trunc('month', CURRENT_DATE)`,
|
||||
)
|
||||
.groupBy('payment.currency')
|
||||
.groupBy("payment.currency")
|
||||
.getRawMany<{ currency: string; amount: string }>();
|
||||
|
||||
return rows.map((row) => ({
|
||||
@@ -413,20 +434,28 @@ export class OverviewRepository {
|
||||
}));
|
||||
}
|
||||
|
||||
async getTrainStatusBreakdown(): Promise<{ status: string; count: number }[]> {
|
||||
return this.statusBreakdown(this.trainRepository, 'train');
|
||||
async getTrainStatusBreakdown(): Promise<
|
||||
{ status: string; count: number }[]
|
||||
> {
|
||||
return this.statusBreakdown(this.trainRepository, "train");
|
||||
}
|
||||
|
||||
async getWagonStatusBreakdown(): Promise<{ status: string; count: number }[]> {
|
||||
return this.statusBreakdown(this.wagonRepository, 'wagon');
|
||||
async getWagonStatusBreakdown(): Promise<
|
||||
{ status: string; count: number }[]
|
||||
> {
|
||||
return this.statusBreakdown(this.wagonRepository, "wagon");
|
||||
}
|
||||
|
||||
async getContainerStatusBreakdown(): Promise<{ status: string; count: number }[]> {
|
||||
return this.statusBreakdown(this.containerRepository, 'container');
|
||||
async getContainerStatusBreakdown(): Promise<
|
||||
{ status: string; count: number }[]
|
||||
> {
|
||||
return this.statusBreakdown(this.containerRepository, "container");
|
||||
}
|
||||
|
||||
async getCargoStatusBreakdown(): Promise<{ status: string; count: number }[]> {
|
||||
return this.statusBreakdown(this.cargoRepository, 'cargo');
|
||||
async getCargoStatusBreakdown(): Promise<
|
||||
{ status: string; count: number }[]
|
||||
> {
|
||||
return this.statusBreakdown(this.cargoRepository, "cargo");
|
||||
}
|
||||
|
||||
private async statusBreakdown(
|
||||
@@ -435,11 +464,11 @@ export class OverviewRepository {
|
||||
): Promise<{ status: string; count: number }[]> {
|
||||
const rows = await repository
|
||||
.createQueryBuilder(alias)
|
||||
.select(`${alias}.status`, 'status')
|
||||
.addSelect('COUNT(*)::int', 'count')
|
||||
.select(`${alias}.status`, "status")
|
||||
.addSelect("COUNT(*)::int", "count")
|
||||
.where(`${alias}.deleted_at IS NULL`)
|
||||
.groupBy(`${alias}.status`)
|
||||
.orderBy('count', 'DESC')
|
||||
.orderBy("count", "DESC")
|
||||
.getRawMany<{ status: string; count: string }>();
|
||||
|
||||
return rows.map((row) => ({
|
||||
@@ -448,15 +477,19 @@ export class OverviewRepository {
|
||||
}));
|
||||
}
|
||||
|
||||
async getCustomerGrowthTrend(days: number): Promise<{ date: string; count: number }[]> {
|
||||
const rows = await this.customerRepository
|
||||
.createQueryBuilder('customer')
|
||||
.select(`to_char(customer.created_at::date, 'YYYY-MM-DD')`, 'date')
|
||||
.addSelect('COUNT(*)::int', 'count')
|
||||
.where('customer.deleted_at IS NULL')
|
||||
.andWhere(`customer.created_at >= CURRENT_DATE - :days::int + 1`, { days })
|
||||
.groupBy('customer.created_at::date')
|
||||
.orderBy('customer.created_at::date', 'ASC')
|
||||
async getCustomerGrowthTrend(
|
||||
days: number,
|
||||
): Promise<{ date: string; count: number }[]> {
|
||||
const rows = await this.companyRepository
|
||||
.createQueryBuilder("customer")
|
||||
.select(`to_char(customer.created_at::date, 'YYYY-MM-DD')`, "date")
|
||||
.addSelect("COUNT(*)::int", "count")
|
||||
.where("customer.deleted_at IS NULL")
|
||||
.andWhere(`customer.created_at >= CURRENT_DATE - :days::int + 1`, {
|
||||
days,
|
||||
})
|
||||
.groupBy("customer.created_at::date")
|
||||
.orderBy("customer.created_at::date", "ASC")
|
||||
.getRawMany<{ date: string; count: string }>();
|
||||
|
||||
return rows.map((row) => ({
|
||||
@@ -466,13 +499,16 @@ export class OverviewRepository {
|
||||
}
|
||||
|
||||
async getCustomersByType(): Promise<{ label: string; count: number }[]> {
|
||||
const rows = await this.customerRepository
|
||||
.createQueryBuilder('customer')
|
||||
.select(`COALESCE(NULLIF(customer.customer_type, ''), 'Unknown')`, 'label')
|
||||
.addSelect('COUNT(*)::int', 'count')
|
||||
.where('customer.deleted_at IS NULL')
|
||||
.groupBy('customer.customer_type')
|
||||
.orderBy('count', 'DESC')
|
||||
const rows = await this.companyRepository
|
||||
.createQueryBuilder("customer")
|
||||
.select(
|
||||
`COALESCE(NULLIF(customer.type, ''), 'Unknown')`,
|
||||
"label",
|
||||
)
|
||||
.addSelect("COUNT(*)::int", "count")
|
||||
.where("customer.deleted_at IS NULL")
|
||||
.groupBy("customer.type")
|
||||
.orderBy("count", "DESC")
|
||||
.getRawMany<{ label: string; count: string }>();
|
||||
|
||||
return rows.map((row) => ({
|
||||
@@ -481,16 +517,18 @@ export class OverviewRepository {
|
||||
}));
|
||||
}
|
||||
|
||||
async getTopCustomersByBookings(limit: number): Promise<{ label: string; count: number }[]> {
|
||||
async getTopCustomersByBookings(
|
||||
limit: number,
|
||||
): Promise<{ label: string; count: number }[]> {
|
||||
const rows = await this.bookingRepository
|
||||
.createQueryBuilder('booking')
|
||||
.leftJoin('booking.company', 'company')
|
||||
.select(`COALESCE(company.name, 'Unknown')`, 'label')
|
||||
.addSelect('COUNT(*)::int', 'count')
|
||||
.where('booking.deleted_at IS NULL')
|
||||
.createQueryBuilder("booking")
|
||||
.leftJoin("booking.company", "company")
|
||||
.select(`COALESCE(company.name, 'Unknown')`, "label")
|
||||
.addSelect("COUNT(*)::int", "count")
|
||||
.where("booking.deleted_at IS NULL")
|
||||
.andWhere("booking.status != 'DRAFT'")
|
||||
.groupBy('company.name')
|
||||
.orderBy('count', 'DESC')
|
||||
.groupBy("company.name")
|
||||
.orderBy("count", "DESC")
|
||||
.limit(limit)
|
||||
.getRawMany<{ label: string; count: string }>();
|
||||
|
||||
@@ -502,11 +540,11 @@ export class OverviewRepository {
|
||||
|
||||
async getUsersByStatus(): Promise<{ status: string; count: number }[]> {
|
||||
const rows = await this.userRepository
|
||||
.createQueryBuilder('user')
|
||||
.select('user.status', 'status')
|
||||
.addSelect('COUNT(*)::int', 'count')
|
||||
.groupBy('user.status')
|
||||
.orderBy('count', 'DESC')
|
||||
.createQueryBuilder("user")
|
||||
.select("user.status", "status")
|
||||
.addSelect("COUNT(*)::int", "count")
|
||||
.groupBy("user.status")
|
||||
.orderBy("count", "DESC")
|
||||
.getRawMany<{ status: string; count: string }>();
|
||||
|
||||
return rows.map((row) => ({
|
||||
@@ -515,15 +553,19 @@ export class OverviewRepository {
|
||||
}));
|
||||
}
|
||||
|
||||
async getEmployeeGrowthTrend(days: number): Promise<{ date: string; count: number }[]> {
|
||||
async getEmployeeGrowthTrend(
|
||||
days: number,
|
||||
): Promise<{ date: string; count: number }[]> {
|
||||
const rows = await this.employeeRepository
|
||||
.createQueryBuilder('employee')
|
||||
.select(`to_char(employee.created_at::date, 'YYYY-MM-DD')`, 'date')
|
||||
.addSelect('COUNT(*)::int', 'count')
|
||||
.where('employee.is_current = true')
|
||||
.andWhere(`employee.created_at >= CURRENT_DATE - :days::int + 1`, { days })
|
||||
.groupBy('employee.created_at::date')
|
||||
.orderBy('employee.created_at::date', 'ASC')
|
||||
.createQueryBuilder("employee")
|
||||
.select(`to_char(employee.created_at::date, 'YYYY-MM-DD')`, "date")
|
||||
.addSelect("COUNT(*)::int", "count")
|
||||
.where("employee.is_current = true")
|
||||
.andWhere(`employee.created_at >= CURRENT_DATE - :days::int + 1`, {
|
||||
days,
|
||||
})
|
||||
.groupBy("employee.created_at::date")
|
||||
.orderBy("employee.created_at::date", "ASC")
|
||||
.getRawMany<{ date: string; count: string }>();
|
||||
|
||||
return rows.map((row) => ({
|
||||
@@ -538,16 +580,16 @@ export class OverviewRepository {
|
||||
where: { isActive: true, status: EUserStatus.ACCEPTED },
|
||||
}),
|
||||
this.userRepository
|
||||
.createQueryBuilder('user')
|
||||
.where('user.is_active = false OR user.status != :status', {
|
||||
.createQueryBuilder("user")
|
||||
.where("user.is_active = false OR user.status != :status", {
|
||||
status: EUserStatus.ACCEPTED,
|
||||
})
|
||||
.getCount(),
|
||||
]);
|
||||
|
||||
return [
|
||||
{ label: 'Active', count: active },
|
||||
{ label: 'Inactive', count: inactive },
|
||||
{ label: "Active", count: active },
|
||||
{ label: "Inactive", count: inactive },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user