diff --git a/apps/edr-freight-api/src/app.module.ts b/apps/edr-freight-api/src/app.module.ts index ec1664f15..7bd38c935 100644 --- a/apps/edr-freight-api/src/app.module.ts +++ b/apps/edr-freight-api/src/app.module.ts @@ -24,14 +24,13 @@ import { TrainSetsModule } from "./modules/train-sets/train-sets.module"; import { TrainSchedulesModule } from "./modules/train-schedules/train-schedules.module"; import { TrainSchedulingModule } from "./modules/train-scheduling/train-scheduling.module"; import { SchedulingRescheduleModule } from "./modules/scheduling-reschedule/scheduling-reschedule.module"; -import { CustomersModule } from "./modules/customers/customers.module"; import { CompaniesModule } from "./modules/companies/companies.module"; import { TrackingModule } from "./modules/tracking/tracking.module"; import { BillingModule } from "./modules/billing/billing.module"; import { NotificationsModule } from "./modules/notifications/notifications.module"; import { FileUploadSettingsModule } from "./modules/file-upload-settings/file-upload-settings.module"; import { DropdownSettingsModule } from "./modules/dropdown-settings/dropdown-settings.module"; -import { OtpModule } from './modules/otp/otp.module'; +import { OtpModule } from "./modules/otp/otp.module"; import { RuleEngineModule } from "./modules/rule-engine/rule-engine.module"; import { BackofficeModule } from "./modules/backoffice/backoffice.module"; import { DemoPermissionsModule } from "./modules/demo-permissions/demo-permissions.module"; @@ -53,13 +52,13 @@ import { FreightPermissionKeyMigrationSeeder } from "./seed/freight-permission-k import { DemoFreightDataSeeder } from "./seed/demo-freight-data.seeder"; //New Trains, Wagons, Container and Cargo management modules import { TrainsModule } from "./modules/trains/trains.module"; -import { WagonsModule } from './modules/wagons/wagons.module'; -import { ContainersModule } from './modules/container-management/containers.module'; -import { CargoesModule } from './modules/cargoes/cargoes.module'; -import { RoutesModule } from './modules/routes/routes.module'; -import { WarehousesModule } from './modules/warehouses/warehouses.module'; -import { FacilitiesModule } from './modules/facilities/facilities.module'; -import { OverviewModule } from './modules/overview/overview.module'; +import { WagonsModule } from "./modules/wagons/wagons.module"; +import { ContainersModule } from "./modules/container-management/containers.module"; +import { CargoesModule } from "./modules/cargoes/cargoes.module"; +import { RoutesModule } from "./modules/routes/routes.module"; +import { WarehousesModule } from "./modules/warehouses/warehouses.module"; +import { FacilitiesModule } from "./modules/facilities/facilities.module"; +import { OverviewModule } from "./modules/overview/overview.module"; @Module({ imports: [ @@ -97,7 +96,6 @@ import { OverviewModule } from './modules/overview/overview.module'; TrainSchedulesModule, TrainSchedulingModule, SchedulingRescheduleModule, - CustomersModule, CompaniesModule, TrackingModule, BillingModule, diff --git a/apps/edr-freight-api/src/modules/customers/customers.controller.ts b/apps/edr-freight-api/src/modules/customers/customers.controller.ts deleted file mode 100644 index 7451bd6b6..000000000 --- a/apps/edr-freight-api/src/modules/customers/customers.controller.ts +++ /dev/null @@ -1,86 +0,0 @@ -// src/modules/customers/customers.controller.ts - -import { - Controller, - Delete, - Get, - HttpCode, - HttpStatus, - Param, - ParseUUIDPipe, - Patch, - Post, - Body, - Query, -} from "@nestjs/common"; - -import { ApiOperation } from "@nestjs/swagger"; - -import { FreightAdmin } from "../../common/booking-guards"; -import { CustomersService } from "./customers.service"; -import { CreateCustomerDto } from "./dto/create-customer.dto"; -import { UpdateCustomerDto } from "./dto/update-customer.dto"; -import { Customer } from "./entities/customer.entity"; - -@Controller("customers") -@FreightAdmin() -export class CustomersController { - constructor(private readonly customersService: CustomersService) {} - - @Post() - create(@Body() createCustomerDto: CreateCustomerDto): Promise { - return this.customersService.create(createCustomerDto); - } - - @Get() - findAll(): Promise { - return this.customersService.findAll(); - } - - @Get("stats") - @ApiOperation({ summary: "Get customer statistics" }) - getStats(): Promise<{ total: number; withVatNumber: number }> { - return this.customersService.getStats(); - } - - @Get("search") - searchByName(@Query("name") name: string): Promise { - return this.customersService.searchByName(name); - } - - @Get("email/:email") - findByEmail(@Param("email") email: string): Promise { - return this.customersService.findByEmail(email); - } - - @Get("vat/:vatNumber") - findByVatNumber(@Param("vatNumber") vatNumber: string): Promise { - return this.customersService.findByVatNumber(vatNumber); - } - - @Get(":id") - findById(@Param("id", ParseUUIDPipe) id: string): Promise { - return this.customersService.findById(id); - } - - // @Get("user/:userId") - // findByUserId(@Param("userId", ParseUUIDPipe) userId: string): Promise { - // return this.customersService.findByUserId(userId); - // } - - @Patch(":id") - @ApiOperation({ summary: "Update a customer" }) - update( - @Param("id", ParseUUIDPipe) id: string, - @Body() dto: UpdateCustomerDto, - ): Promise { - return this.customersService.update(id, dto); - } - - @Delete(":id") - @ApiOperation({ summary: "Soft-delete a customer" }) - @HttpCode(HttpStatus.NO_CONTENT) - remove(@Param("id", ParseUUIDPipe) id: string): Promise { - return this.customersService.delete(id); - } -} \ No newline at end of file diff --git a/apps/edr-freight-api/src/modules/customers/customers.module.ts b/apps/edr-freight-api/src/modules/customers/customers.module.ts deleted file mode 100644 index 28c6b7c89..000000000 --- a/apps/edr-freight-api/src/modules/customers/customers.module.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Module } from "@nestjs/common"; -import { TypeOrmModule } from "@nestjs/typeorm"; - -import { CustomersController } from "./customers.controller"; -import { CustomersRepository } from "./customers.repository"; -import { CustomersService } from "./customers.service"; -import { Customer } from "./entities/customer.entity"; - -@Module({ - imports: [TypeOrmModule.forFeature([Customer])], - controllers: [CustomersController], - providers: [CustomersService, CustomersRepository], - exports: [CustomersService], -}) -export class CustomersModule {} diff --git a/apps/edr-freight-api/src/modules/customers/customers.repository.ts b/apps/edr-freight-api/src/modules/customers/customers.repository.ts deleted file mode 100644 index 5933cef61..000000000 --- a/apps/edr-freight-api/src/modules/customers/customers.repository.ts +++ /dev/null @@ -1,117 +0,0 @@ -// import { BaseRepository } from "@edr/api-common"; -// import { EntityRepository } from "typeorm"; - -// src/modules/customers/customers.repository.ts -import { Injectable } from "@nestjs/common"; -import { InjectRepository } from "@nestjs/typeorm"; -import { Repository, FindManyOptions, FindOptionsWhere } from "typeorm"; -import { Customer } from "./entities/customer.entity"; -import { CreateCustomerDto } from "./dto/create-customer.dto"; -// import { UpdateCustomerDto } from "./dto/update-customer.dto"; - -@Injectable() -export class CustomersRepository { - constructor( - @InjectRepository(Customer) - private readonly repository: Repository, - ) { } - - async create(dto: CreateCustomerDto): Promise { - const customer = this.repository.create(dto); - return await this.repository.save(customer); - } - - async findAll(options?: FindManyOptions): Promise { - return await this.repository.find(options); - } - - async findById(id: string): Promise { - return await this.repository.findOne({ where: { id } as FindOptionsWhere }); - } - - async findByUserId(userId: string): Promise { - return await this.repository.findOne({ where: { userId } as FindOptionsWhere }); - } - - async findByEmail(email: string): Promise { - return await this.repository.findOne({ where: { email } as FindOptionsWhere }); - } - - async findByVatNumber(vatNumber: string): Promise { - return await this.repository.findOne({ where: { vatNumber } as FindOptionsWhere }); - } - - async findByName(name: string): Promise { - return await this.repository - .createQueryBuilder("customer") - .where("customer.companyName ILIKE :name", { name: `%${name}%` }) - .getMany(); - } - - async findOneByEmailOrVat(email?: string, vatNumber?: string): Promise { - if (!email && !vatNumber) return null; - - const queryBuilder = this.repository.createQueryBuilder('customer'); - - if (email && vatNumber) { - queryBuilder.where('customer.email = :email', { email }) - .orWhere('customer.vatNumber = :vatNumber', { vatNumber }); - } else if (email) { - queryBuilder.where('customer.email = :email', { email }); - } else if (vatNumber) { - queryBuilder.where('customer.vatNumber = :vatNumber', { vatNumber }); - } - - return await queryBuilder.getOne(); - } - - async update(id: string, updates: Partial): Promise { - await this.repository.update(id, updates); - return this.findById(id); - } - - async delete(id: string): Promise { - const result = await this.repository.delete(id); - return (result.affected ?? 0) > 0; - } - - async count(where?: any): Promise { - if (where?.createdAt) { - const result = await this.repository - .createQueryBuilder('customer') - .where('customer.createdAt >= :date', { date: where.createdAt }) - .getCount(); - return result; - } - return await this.repository.count(); - } - - async existsByUniqueFields(email: string, vatNumber?: string): Promise { - const queryBuilder = this.repository.createQueryBuilder('customer') - .where('customer.email = :email', { email }); - - if (vatNumber) { - queryBuilder.orWhere('customer.vatNumber = :vatNumber', { vatNumber }); - } - - const count = await queryBuilder.getCount(); - return count > 0; - } - - async countWithVatNumber(): Promise { - const count = await this.repository - .createQueryBuilder('customer') - .where('customer.vatNumber IS NOT NULL') - .andWhere("customer.vatNumber != ''") - .getCount(); - - return count; - } - - getRepository(): Repository { - return this.repository; - } - softDelete(id: string): any { - return id; - } -} \ No newline at end of file diff --git a/apps/edr-freight-api/src/modules/customers/customers.service.ts b/apps/edr-freight-api/src/modules/customers/customers.service.ts deleted file mode 100644 index e3d1f3a82..000000000 --- a/apps/edr-freight-api/src/modules/customers/customers.service.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { - Injectable, - NotFoundException, - ConflictException, - BadRequestException, -} from "@nestjs/common"; - -import { CustomersRepository } from "./customers.repository"; -import { CreateCustomerDto } from "./dto/create-customer.dto"; -import { UpdateCustomerDto } from "./dto/update-customer.dto"; -import { Customer } from "./entities/customer.entity"; - -@Injectable() -export class CustomersService { - constructor(private readonly customersRepository: CustomersRepository) {} - - /** Create a new customer */ - async create(dto: CreateCustomerDto): Promise { - const exists = await this.customersRepository.existsByUniqueFields( - dto.email, - dto.vatNumber, - ); - - if (exists) { - throw new ConflictException( - "Customer with same email or VAT number already exists", - ); - } - - if (dto.vatNumber && dto.vatNumber.length !== 10) { - throw new BadRequestException("VAT number must be exactly 10 digits"); - } - - return this.customersRepository.create(dto); - } - - /** Get all customers */ - findAll(): Promise { - return this.customersRepository.findAll({ - order: { companyName: "ASC" }, - }); - } - - /** Get customer by ID */ - async findById(id: string): Promise { - const customer = await this.customersRepository.findById(id); - - if (!customer) { - throw new NotFoundException(`Customer with ID ${id} not found`); - } - - return customer; - } - - // async findByUserId(userId: string): Promise { - // const customer = await this.customersRepository.findByUserId(userId); - - // if (!customer) { - // throw new NotFoundException(`Customer with ID ${userId} not found`); - // } - - // return customer; - //} - - /** Get customer by email */ - async findByEmail(email: string): Promise { - const customer = await this.customersRepository.findByEmail(email); - - if (!customer) { - throw new NotFoundException(`Customer with email ${email} not found`); - } - - return customer; - } - - /** Get customer by VAT number */ - async findByVatNumber(vatNumber: string): Promise { - const customer = await this.customersRepository.findByVatNumber(vatNumber); - - if (!customer) { - throw new NotFoundException( - `Customer with VAT number ${vatNumber} not found`, - ); - } - - return customer; - } - - /** Search customers by name */ - searchByName(name: string): Promise { - return this.customersRepository.findByName(name); - } - - /** Update customer */ - async update(id: string, dto: UpdateCustomerDto): Promise { - await this.findById(id); - - // Validate VAT number if provided - if (dto.vatNumber && dto.vatNumber.length !== 10) { - throw new BadRequestException("VAT number must be exactly 10 digits"); - } - - // // Check email conflict - // if (dto.email) { - // const existing = await this.customersRepository.findByEmail(dto.email); - - // // if (existing && existing.userId !== id) { - // // throw new ConflictException( - // // `Customer with email "${dto.email}" already exists`, - // // ); - // // } - // } - - const updated = await this.customersRepository.update(id, dto); - - if (!updated) { - throw new NotFoundException(`Customer ${id} not found`); - } - - return updated; - } - - /** Delete customer (soft delete) */ - async remove(id: string): Promise { - await this.findById(id); - await this.customersRepository.softDelete(id); - } - - /** Get customer statistics */ - async getStats(): Promise<{ total: number; withVatNumber: number }> { - const total = await this.customersRepository.count(); - const withVatNumber = await this.customersRepository.countWithVatNumber(); - - return { total, withVatNumber }; - } - - delete(id: string): any { - return id; - } -} \ No newline at end of file diff --git a/apps/edr-freight-api/src/modules/customers/dto/create-customer.dto.ts b/apps/edr-freight-api/src/modules/customers/dto/create-customer.dto.ts deleted file mode 100644 index 39fc16414..000000000 --- a/apps/edr-freight-api/src/modules/customers/dto/create-customer.dto.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { - IsEmail, - IsEnum, - IsOptional, - IsString, - MaxLength, - IsNotEmpty, - Length, - Matches, -} from "class-validator"; - -// Enums -export enum CustomerStatusDto { - Active = "Active", - Pending = "Pending", - Inactive = "Inactive", -} - -export enum CustomerTypeDto { - Importer = "Importer", - Exporter = "Exporter", - Supplier = "Supplier", -} - -// DTO -export class CreateCustomerDto { - // Basic identity - @IsString() - @IsNotEmpty() - userId!: string; - - @IsString() - @IsNotEmpty() - @MaxLength(100) - firstName!: string; - - @IsString() - @IsNotEmpty() - @MaxLength(100) - lastName!: string; - - @IsEmail() - @IsNotEmpty() - email!: string; - - @IsString() - @IsNotEmpty() - @MaxLength(20) - phone!: string; - - // Company info - @IsString() - @IsNotEmpty() - @MaxLength(200) - companyName!: string; - - @IsEmail() - @IsNotEmpty() - companyEmail!: string; - - @IsString() - @IsNotEmpty() - @MaxLength(20) - companyPhone!: string; - - @IsString() - @IsNotEmpty() - @MaxLength(100) - companyLocation!: string; - - @IsString() - @IsNotEmpty() - companyAddress!: string; - - // Classification - @IsOptional() - @IsEnum(CustomerTypeDto) - customerType?: CustomerTypeDto; - - @IsOptional() - @IsEnum(CustomerStatusDto) - status?: CustomerStatusDto; - - // Legal identifiers - @IsString() - @IsNotEmpty() - @Length(10, 10) - @Matches(/^\d+$/, { message: "TIN must contain only digits" }) - tinNumber!: string; - - @IsString() - @IsNotEmpty() - @Length(16, 16) - @Matches(/^\d+$/, { message: "FAN must contain only digits" }) - fanNumber!: string; - - @IsString() - @IsNotEmpty() - @MaxLength(50) - vatNumber!: string; - - // Contact person - @IsString() - @IsNotEmpty() - @MaxLength(100) - contactPersonName!: string; - - @IsString() - @IsNotEmpty() - @MaxLength(20) - contactPersonPhone!: string; - - // Management - @IsString() - @IsNotEmpty() - @MaxLength(100) - generalManagerName!: string; - - @IsEmail() - @IsNotEmpty() - generalManagerEmail!: string; - - @IsString() - @IsNotEmpty() - @MaxLength(20) - generalManagerPhone!: string; - - // POA (Power of Attorney) - @IsOptional() - @IsString() - @MaxLength(100) - poaName?: string; - - @IsOptional() - @IsString() - @MaxLength(20) - poaPhone?: string; - - @IsOptional() - @IsString() - poaAddress?: string; - - @IsOptional() - @IsEmail() - poaEmail?: string; - - @IsOptional() - @IsString() - @MaxLength(100) - poaLocation?: string; - - // Extra - @IsOptional() - @IsString() - notes?: string; -} \ No newline at end of file diff --git a/apps/edr-freight-api/src/modules/customers/dto/response-customer.dto.ts b/apps/edr-freight-api/src/modules/customers/dto/response-customer.dto.ts deleted file mode 100644 index 3d2a086d4..000000000 --- a/apps/edr-freight-api/src/modules/customers/dto/response-customer.dto.ts +++ /dev/null @@ -1,60 +0,0 @@ -// src/modules/customers/dto/response-customer.dto.ts -import { Customer } from '../entities/customer.entity'; - -export class ResponseCustomerDto { - //UserId: string; - firstName: string; - lastName: string; - email: string; - phone: string; - companyName: string; - companyEmail: string; - companyPhone: string; - companyLocation: string; - companyAddress: string; - contactPersonName: string; - contactPersonPhone: string; - tinNumber: string; - vatNumber?: string; - fanNumber: string; - generalManagerName: string; - generalManagerEmail: string; - generalManagerPhone: string; - poaName?: string; - poaPhone?: string; - poaAddress?: string; - poaEmail?: string; - poaLocation?: string; - notes?: string; - createdAt: Date; - updatedAt: Date; - - constructor(customer: Customer) { - //this.UserId = customer.userId; - this.firstName = customer.firstName; - this.lastName = customer.lastName; - this.email = customer.email; - this.phone = customer.phone; - this.companyName = customer.companyName; - this.companyEmail = customer.companyEmail; - this.companyPhone = customer.companyPhone; - this.companyLocation = customer.companyLocation; - this.companyAddress = customer.companyAddress; - this.contactPersonName = customer.contactPersonName; - this.contactPersonPhone = customer.contactPersonPhone; - this.tinNumber = customer.tinNumber; - this.vatNumber = customer.vatNumber ?? undefined; - this.fanNumber = customer.fanNumber; - this.generalManagerName = customer.generalManagerName; - this.generalManagerEmail = customer.generalManagerEmail; - this.generalManagerPhone = customer.generalManagerPhone; - this.poaName = customer.poaName ?? ''; - this.poaPhone = customer.poaPhone ?? ''; - this.poaAddress = customer.poaAddress ?? ''; - this.poaEmail = customer.poaEmail ?? ''; - this.poaLocation = customer.poaLocation ?? ''; - this.notes = customer.notes ?? ''; - this.createdAt = customer.createdAt; - this.updatedAt = customer.updatedAt; - } -} \ No newline at end of file diff --git a/apps/edr-freight-api/src/modules/customers/dto/update-customer.dto.ts b/apps/edr-freight-api/src/modules/customers/dto/update-customer.dto.ts deleted file mode 100644 index f8cefe046..000000000 --- a/apps/edr-freight-api/src/modules/customers/dto/update-customer.dto.ts +++ /dev/null @@ -1,9 +0,0 @@ -// src/modules/customers/dto/update-customer.dto.ts -import { PartialType } from '@nestjs/mapped-types'; -import { CreateCustomerDto } from './create-customer.dto'; - -export class UpdateCustomerDto extends PartialType(CreateCustomerDto) { - email?: string; - vatNumber?: string; - // Add any other properties you need to access directly -} diff --git a/apps/edr-freight-api/src/modules/customers/entities/customer.entity.ts b/apps/edr-freight-api/src/modules/customers/entities/customer.entity.ts deleted file mode 100644 index abccbaef8..000000000 --- a/apps/edr-freight-api/src/modules/customers/entities/customer.entity.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { BaseEntity } from '@edr/api-common'; -import { Column, Entity, Index } from 'typeorm'; - -@Entity({ schema: 'freight', name: 'customers' }) -@Index(['email']) -//@Index(['userId']) -@Index(['tinNumber']) -@Index(['fanNumber']) -export class Customer extends BaseEntity { - //@Column({ name: 'user_id', type: 'uuid' }) - //userId!: string; - - @Column({ name: 'first_name', type: 'varchar', length: 100 }) - firstName!: string; - - @Column({ name: 'last_name', type: 'varchar', length: 100 }) - lastName!: string; - - @Column({ name: 'email', type: 'varchar', length: 150, unique: true }) - email!: string; - - @Column({ name: 'phone', type: 'varchar', length: 20 }) - phone!: string; - - @Column({ name: 'company_name', type: 'varchar', length: 200 }) - companyName!: string; - - @Column({ name: 'company_email', type: 'varchar', length: 150 }) - companyEmail!: string; - - @Column({ name: 'company_phone', type: 'varchar', length: 20 }) - companyPhone!: string; - - @Column({ name: 'company_location', type: 'varchar', length: 100 }) - companyLocation!: string; - - @Column({ name: 'company_address', type: 'text' }) - companyAddress!: string; - - @Column({ name: 'customer_type', type: 'varchar', length: 32, nullable: true }) - customerType?: string | null; - - @Column({ name: 'status', type: 'varchar', length: 32, nullable: true }) - status?: string | null; - - @Column({ name: 'contact_person_name', type: 'varchar', length: 100 }) - contactPersonName!: string; - - @Column({ name: 'contact_person_phone', type: 'varchar', length: 20 }) - contactPersonPhone!: string; - - @Column({ name: 'tin_number', type: 'varchar', length: 10, unique: true }) - tinNumber!: string; - - @Column({ name: 'vat_number', type: 'varchar', length: 50, nullable: true }) - vatNumber?: string | null; - - @Column({ name: 'fan_number', type: 'varchar', length: 16, unique: true }) - fanNumber!: string; - - @Column({ name: 'general_manager_name', type: 'varchar', length: 100 }) - generalManagerName!: string; - - @Column({ name: 'general_manager_email', type: 'varchar', length: 150 }) - generalManagerEmail!: string; - - @Column({ name: 'general_manager_phone', type: 'varchar', length: 20 }) - generalManagerPhone!: string; - - @Column({ name: 'poa_name', type: 'varchar', length: 100, nullable: true }) - poaName?: string | null; - - @Column({ name: 'poa_phone', type: 'varchar', length: 20, nullable: true }) - poaPhone?: string | null; - - @Column({ name: 'poa_address', type: 'text', nullable: true }) - poaAddress?: string | null; - - @Column({ name: 'poa_email', type: 'varchar', length: 150, nullable: true }) - poaEmail?: string | null; - - @Column({ name: 'poa_location', type: 'varchar', length: 100, nullable: true }) - poaLocation?: string | null; - - @Column({ name: 'notes', type: 'text', nullable: true }) - notes?: string | null; -} diff --git a/apps/edr-freight-api/src/modules/overview/overview.module.ts b/apps/edr-freight-api/src/modules/overview/overview.module.ts index 50893b626..87cdc62d5 100644 --- a/apps/edr-freight-api/src/modules/overview/overview.module.ts +++ b/apps/edr-freight-api/src/modules/overview/overview.module.ts @@ -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 { } diff --git a/apps/edr-freight-api/src/modules/overview/overview.repository.ts b/apps/edr-freight-api/src/modules/overview/overview.repository.ts index 2c49ff8da..a57ba24db 100644 --- a/apps/edr-freight-api/src/modules/overview/overview.repository.ts +++ b/apps/edr-freight-api/src/modules/overview/overview.repository.ts @@ -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, @InjectRepository(PaymentEntity) private readonly paymentRepository: Repository, - @InjectRepository(Customer) - private readonly customerRepository: Repository, + @InjectRepository(Company) + private readonly companyRepository: Repository, @InjectRepository(Train) private readonly trainRepository: Repository, @InjectRepository(Wagon) @@ -60,32 +60,32 @@ export class OverviewRepository { private readonly employeeRepository: Repository, @InjectRepository(User) private readonly userRepository: Repository, - ) {} + ) { } async getBookingKpis(): Promise { 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>(); 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>(); 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> { 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 { 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 }, ]; } }