From d6c4e786326f39850975f0743dc7cf651518a6b4 Mon Sep 17 00:00:00 2001 From: Juliano Sill Date: Sun, 7 Dec 2025 16:36:00 -0300 Subject: [PATCH 1/8] feat(referrals): add endpoint to return referrals list --- .../migrations/1765133594521-Update.ts | 16 +++ infra/database/seed-dev.ts | 12 ++- .../http/referrals/referrals.controller.ts | 34 +++++- src/app/http/referrals/referrals.dtos.ts | 7 +- src/app/http/referrals/referrals.module.ts | 3 +- .../http/referrals/referrals.repository.ts | 2 +- src/app/http/referrals/referrals.service.ts | 3 + .../use-cases/get-referrals-use-case.ts | 100 ++++++++++++++++++ src/domain/entities/referral.ts | 12 +-- src/domain/enums/referrals.ts | 29 +++++ src/domain/schemas/referral.ts | 77 ++++++++++---- src/domain/schemas/statistics.ts | 2 +- 12 files changed, 258 insertions(+), 39 deletions(-) create mode 100644 infra/database/migrations/1765133594521-Update.ts create mode 100644 src/app/http/referrals/use-cases/get-referrals-use-case.ts create mode 100644 src/domain/enums/referrals.ts diff --git a/infra/database/migrations/1765133594521-Update.ts b/infra/database/migrations/1765133594521-Update.ts new file mode 100644 index 0000000..d7ccf38 --- /dev/null +++ b/infra/database/migrations/1765133594521-Update.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Update1765133594521 implements MigrationInterface { + name = 'Update1765133594521' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`referrals\` DROP COLUMN \`date\``); + await queryRunner.query(`ALTER TABLE \`referrals\` ADD \`date\` timestamp NOT NULL`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`referrals\` DROP COLUMN \`date\``); + await queryRunner.query(`ALTER TABLE \`referrals\` ADD \`date\` date NOT NULL`); + } + +} diff --git a/infra/database/seed-dev.ts b/infra/database/seed-dev.ts index 16461e3..6035b53 100644 --- a/infra/database/seed-dev.ts +++ b/infra/database/seed-dev.ts @@ -3,6 +3,10 @@ import { hash } from 'bcryptjs'; import * as fs from 'fs'; import * as path from 'path'; +import { + REFERRAL_CATEGORIES, + REFERRAL_STATUSES, +} from '@/domain/enums/referrals'; import { APPOINTMENT_CONDITION, APPOINTMENT_STATUS, @@ -16,10 +20,6 @@ import { PATIENT_REQUIREMENT_STATUS, PATIENT_REQUIREMENT_TYPE, } from '@/domain/schemas/patient-requirement'; -import { - REFERRAL_CATEGORIES, - REFERRAL_STATUSES, -} from '@/domain/schemas/referral'; import { SPECIALIST_STATUS } from '@/domain/schemas/specialist'; import { USER_ROLES } from '@/domain/schemas/user'; @@ -258,6 +258,10 @@ async function main() { annotation: faker.datatype.boolean() ? faker.lorem.sentence() : null, referred_to: faker.person.fullName(), referred_by: faker.string.uuid(), + created_at: faker.date.between({ + from: fourMonthsAgo, + to: new Date(), + }), }); await referralRepository.save(referral); } diff --git a/src/app/http/referrals/referrals.controller.ts b/src/app/http/referrals/referrals.controller.ts index 87db5aa..1726dee 100644 --- a/src/app/http/referrals/referrals.controller.ts +++ b/src/app/http/referrals/referrals.controller.ts @@ -1,18 +1,46 @@ -import { Body, Controller, Param, Patch, Post } from '@nestjs/common'; +import { + Body, + Controller, + Get, + Param, + Patch, + Post, + Query, +} from '@nestjs/common'; import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { CurrentUser } from '@/common/decorators/current-user.decorator'; import { Roles } from '@/common/decorators/roles.decorator'; import { BaseResponseSchema } from '@/domain/schemas/base'; +import type { GetReferralsResponseSchema } from '@/domain/schemas/referral'; import { UserSchema } from '@/domain/schemas/user'; -import { CreateReferralDto } from './referrals.dtos'; +import { CreateReferralDto, GetReferralsQuery } from './referrals.dtos'; import { ReferralsService } from './referrals.service'; +import { GetReferralsUseCase } from './use-cases/get-referrals-use-case'; @ApiTags('Encaminhamentos') @Controller('referrals') export class ReferralsController { - constructor(private readonly referralsService: ReferralsService) {} + constructor( + private readonly getReferrals: GetReferralsUseCase, + private readonly referralsService: ReferralsService, + ) {} + + @Get() + @Roles(['manager', 'nurse']) + @ApiOperation({ summary: 'Lista encaminhamentos cadastrados no sistema' }) + async handleGetReferrals( + @Query() query: GetReferralsQuery, + ): Promise { + const data = await this.getReferrals.execute({ query }); + + return { + success: true, + message: 'Lista de encaminhamentos retornada com sucesso.', + data, + }; + } @Post() @Roles(['manager', 'nurse']) diff --git a/src/app/http/referrals/referrals.dtos.ts b/src/app/http/referrals/referrals.dtos.ts index 4440849..e68bf20 100644 --- a/src/app/http/referrals/referrals.dtos.ts +++ b/src/app/http/referrals/referrals.dtos.ts @@ -1,5 +1,10 @@ import { createZodDto } from 'nestjs-zod'; -import { createReferralSchema } from '@/domain/schemas/referral'; +import { + createReferralSchema, + getReferralsQuerySchema, +} from '@/domain/schemas/referral'; export class CreateReferralDto extends createZodDto(createReferralSchema) {} + +export class GetReferralsQuery extends createZodDto(getReferralsQuerySchema) {} diff --git a/src/app/http/referrals/referrals.module.ts b/src/app/http/referrals/referrals.module.ts index 77c9b8d..9d7058f 100644 --- a/src/app/http/referrals/referrals.module.ts +++ b/src/app/http/referrals/referrals.module.ts @@ -7,11 +7,12 @@ import { PatientsModule } from '../patients/patients.module'; import { ReferralsController } from './referrals.controller'; import { ReferralsRepository } from './referrals.repository'; import { ReferralsService } from './referrals.service'; +import { GetReferralsUseCase } from './use-cases/get-referrals-use-case'; @Module({ imports: [PatientsModule, TypeOrmModule.forFeature([Referral])], controllers: [ReferralsController], - providers: [ReferralsService, ReferralsRepository], + providers: [ReferralsService, ReferralsRepository, GetReferralsUseCase], exports: [ReferralsService, ReferralsRepository], }) export class ReferralsModule {} diff --git a/src/app/http/referrals/referrals.repository.ts b/src/app/http/referrals/referrals.repository.ts index 61ee69d..924cbc5 100644 --- a/src/app/http/referrals/referrals.repository.ts +++ b/src/app/http/referrals/referrals.repository.ts @@ -10,7 +10,7 @@ import { } from 'typeorm'; import { Referral } from '@/domain/entities/referral'; -import { ReferralStatus } from '@/domain/schemas/referral'; +import type { ReferralStatus } from '@/domain/enums/referrals'; import type { CategoryTotalReferrals } from '@/domain/schemas/statistics'; import { CreateReferralDto } from './referrals.dtos'; diff --git a/src/app/http/referrals/referrals.service.ts b/src/app/http/referrals/referrals.service.ts index 65f97d7..0fc384b 100644 --- a/src/app/http/referrals/referrals.service.ts +++ b/src/app/http/referrals/referrals.service.ts @@ -4,7 +4,9 @@ import { Logger, NotFoundException, } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Referral } from '@/domain/entities/referral'; import { UserSchema } from '@/domain/schemas/user'; import { PatientsRepository } from '../patients/patients.repository'; @@ -16,6 +18,7 @@ export class ReferralsService { private readonly logger = new Logger(ReferralsService.name); constructor( + @InjectRepository(Referral) private readonly referralsRepository: ReferralsRepository, private readonly patientsRepository: PatientsRepository, ) {} diff --git a/src/app/http/referrals/use-cases/get-referrals-use-case.ts b/src/app/http/referrals/use-cases/get-referrals-use-case.ts new file mode 100644 index 0000000..741fa34 --- /dev/null +++ b/src/app/http/referrals/use-cases/get-referrals-use-case.ts @@ -0,0 +1,100 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { + Between, + type FindOptionsWhere, + ILike, + LessThanOrEqual, + MoreThanOrEqual, + type Repository, +} from 'typeorm'; + +import { Referral } from '@/domain/entities/referral'; +import type { GetReferralsResponseSchema } from '@/domain/schemas/referral'; + +import { GetReferralsQuery } from '../referrals.dtos'; + +interface GetReferralsUseCaseRequest { + query: GetReferralsQuery; +} + +type GetReferralsUseCaseRequestResponse = Promise< + GetReferralsResponseSchema['data'] +>; + +@Injectable() +export class GetReferralsUseCase { + constructor( + @InjectRepository(Referral) + private readonly referralsRepository: Repository, + ) {} + + async execute({ + query, + }: GetReferralsUseCaseRequest): GetReferralsUseCaseRequestResponse { + const { orderBy, page, perPage, category, condition, order, search } = + query; + + const where: FindOptionsWhere = {}; + const startDate = query.startDate ? new Date(query.startDate) : null; + const endDate = query.endDate ? new Date(query.endDate) : null; + + if (condition) { + where.condition = condition; + } + + if (category) { + where.category = category; + } + + if (startDate && !endDate) { + where.date = MoreThanOrEqual(startDate); + } + + if (endDate && !startDate) { + where.date = LessThanOrEqual(endDate); + } + + if (startDate && endDate) { + where.date = Between(startDate, endDate); + } + + if (search) { + where.patient = { user: { name: ILike(`%${search}%`) } }; + } + + const totalQuery = await this.referralsRepository.count({ where }); + + const referralsQuery = await this.referralsRepository.find({ + relations: { patient: { user: true } }, + select: { + patient: { + id: true, + user: { name: true, avatar_url: true }, + }, + }, + order: { [orderBy]: order }, + take: perPage, + skip: (page - 1) * perPage, + where, + }); + + const referrals = referralsQuery.map((referral) => ({ + id: referral.id, + date: referral.date, + category: referral.category, + condition: referral.condition, + annotation: referral.annotation, + status: referral.status, + referred_to: referral.referred_to, + created_at: referral.created_at, + patient: { + id: referral.patient.id, + name: referral.patient.user.name, + avatar_url: referral.patient.user.avatar_url, + }, + })); + + return { referrals, total: totalQuery }; + } +} diff --git a/src/domain/entities/referral.ts b/src/domain/entities/referral.ts index d8ea942..6479321 100644 --- a/src/domain/entities/referral.ts +++ b/src/domain/entities/referral.ts @@ -8,14 +8,14 @@ import { UpdateDateColumn, } from 'typeorm'; -import { PATIENT_CONDITIONS, PatientCondition } from '../schemas/patient'; import { REFERRAL_CATEGORIES, REFERRAL_STATUSES, - ReferralCategory, - ReferralSchema, - ReferralStatus, -} from '../schemas/referral'; + type ReferralCategory, + type ReferralStatus, +} from '../enums/referrals'; +import { PATIENT_CONDITIONS, PatientCondition } from '../schemas/patient'; +import { ReferralSchema } from '../schemas/referral'; import { Patient } from './patient'; @Entity('referrals') @@ -26,7 +26,7 @@ export class Referral implements ReferralSchema { @Column('uuid') patient_id: string; - @Column({ type: 'date' }) + @Column({ type: 'timestamp' }) date: Date; @Column({ type: 'enum', enum: REFERRAL_CATEGORIES }) diff --git a/src/domain/enums/referrals.ts b/src/domain/enums/referrals.ts new file mode 100644 index 0000000..5bc8897 --- /dev/null +++ b/src/domain/enums/referrals.ts @@ -0,0 +1,29 @@ +export const REFERRAL_STATUSES = [ + 'scheduled', + 'canceled', + 'completed', + 'no_show', +] as const; +export type ReferralStatus = (typeof REFERRAL_STATUSES)[number]; + +export const REFERRAL_CATEGORIES = [ + 'medical_care', + 'legal', + 'nursing', + 'psychology', + 'nutrition', + 'physical_training', + 'social_work', + 'psychiatry', + 'neurology', + 'ophthalmology', +] as const; +export type ReferralCategory = (typeof REFERRAL_CATEGORIES)[number]; + +export const REFERRAL_ORDER_BY = [ + 'name', + 'condition', + 'category', + 'date', +] as const; +export type ReferralOrderBy = (typeof REFERRAL_ORDER_BY)[number]; diff --git a/src/domain/schemas/referral.ts b/src/domain/schemas/referral.ts index ac9aeca..cfb1cea 100644 --- a/src/domain/schemas/referral.ts +++ b/src/domain/schemas/referral.ts @@ -1,28 +1,13 @@ import { z } from 'zod'; +import { + REFERRAL_CATEGORIES, + REFERRAL_ORDER_BY, + REFERRAL_STATUSES, +} from '../enums/referrals'; +import { baseResponseSchema } from './base'; import { PATIENT_CONDITIONS } from './patient'; - -export const REFERRAL_STATUSES = [ - 'scheduled', - 'canceled', - 'completed', - 'no_show', -] as const; -export type ReferralStatus = (typeof REFERRAL_STATUSES)[number]; - -export const REFERRAL_CATEGORIES = [ - 'medical_care', - 'legal', - 'nursing', - 'psychology', - 'nutrition', - 'physical_training', - 'social_work', - 'psychiatry', - 'neurology', - 'ophthalmology', -] as const; -export type ReferralCategory = (typeof REFERRAL_CATEGORIES)[number]; +import { baseQuerySchema, QUERY_ORDER } from './query'; export const referralSchema = z .object({ @@ -50,3 +35,51 @@ export const createReferralSchema = referralSchema.pick({ referred_to: true, }); export type CreateReferralSchema = z.infer; + +export const getReferralsQuerySchema = baseQuerySchema + .pick({ + search: true, + startDate: true, + endDate: true, + page: true, + perPage: true, + }) + .extend({ + category: z.enum(REFERRAL_CATEGORIES).optional(), + condition: z.enum(PATIENT_CONDITIONS).optional(), + order: z.enum(QUERY_ORDER).optional().default('DESC'), + orderBy: z.enum(REFERRAL_ORDER_BY).optional().default('date'), + }) + .refine( + (data) => { + if (data.startDate && data.endDate) { + return data.startDate < data.endDate; + } + return true; + }, + { + message: 'It should be greater than `startDate`', + path: ['endDate'], + }, + ); +export type GetReferralsQuerySchema = z.infer; + +export const getReferralsResponseSchema = baseResponseSchema.extend({ + data: z.object({ + referrals: z.array( + referralSchema + .omit({ patient_id: true, updated_at: true, referred_by: true }) + .extend({ + patient: z.object({ + id: z.string(), + name: z.string(), + avatar_url: z.string().nullable(), + }), + }), + ), + total: z.number(), + }), +}); +export type GetReferralsResponseSchema = z.infer< + typeof getReferralsResponseSchema +>; diff --git a/src/domain/schemas/statistics.ts b/src/domain/schemas/statistics.ts index 1a35938..4e01e1b 100644 --- a/src/domain/schemas/statistics.ts +++ b/src/domain/schemas/statistics.ts @@ -2,10 +2,10 @@ import { z } from 'zod'; import { BRAZILIAN_STATES } from '@/constants/brazilian-states'; +import { REFERRAL_CATEGORIES } from '../enums/referrals'; import { baseResponseSchema } from './base'; import { GENDERS } from './patient'; import { baseQuerySchema } from './query'; -import { REFERRAL_CATEGORIES } from './referral'; // Patients From 1df1a5ad1732ba20d29e5926cd38dc56b89738a8 Mon Sep 17 00:00:00 2001 From: Juliano Sill Date: Sun, 7 Dec 2025 18:06:38 -0300 Subject: [PATCH 2/8] refactor(referrals): apply use case pattern to create referral method --- .../http/referrals/referrals.controller.ts | 17 +++--- src/app/http/referrals/referrals.module.ts | 15 +++++- .../http/referrals/referrals.repository.ts | 12 ----- src/app/http/referrals/referrals.service.ts | 27 ---------- .../use-cases/create-referrals-use-case.ts | 54 +++++++++++++++++++ .../use-cases/get-referrals-use-case.ts | 6 +-- src/domain/schemas/referral.ts | 2 +- 7 files changed, 81 insertions(+), 52 deletions(-) create mode 100644 src/app/http/referrals/use-cases/create-referrals-use-case.ts diff --git a/src/app/http/referrals/referrals.controller.ts b/src/app/http/referrals/referrals.controller.ts index 1726dee..afe42b5 100644 --- a/src/app/http/referrals/referrals.controller.ts +++ b/src/app/http/referrals/referrals.controller.ts @@ -17,23 +17,25 @@ import { UserSchema } from '@/domain/schemas/user'; import { CreateReferralDto, GetReferralsQuery } from './referrals.dtos'; import { ReferralsService } from './referrals.service'; +import { CreateReferralUseCase } from './use-cases/create-referrals-use-case'; import { GetReferralsUseCase } from './use-cases/get-referrals-use-case'; @ApiTags('Encaminhamentos') @Controller('referrals') export class ReferralsController { constructor( - private readonly getReferrals: GetReferralsUseCase, + private readonly getReferralsUseCase: GetReferralsUseCase, + private readonly createReferralUseCase: CreateReferralUseCase, private readonly referralsService: ReferralsService, ) {} @Get() @Roles(['manager', 'nurse']) @ApiOperation({ summary: 'Lista encaminhamentos cadastrados no sistema' }) - async handleGetReferrals( + async getReferrals( @Query() query: GetReferralsQuery, ): Promise { - const data = await this.getReferrals.execute({ query }); + const data = await this.getReferralsUseCase.execute({ query }); return { success: true, @@ -44,19 +46,22 @@ export class ReferralsController { @Post() @Roles(['manager', 'nurse']) - @ApiOperation({ summary: 'Cadastra novo encaminhamento.' }) + @ApiOperation({ summary: 'Cadastra um novo encaminhamento' }) async create( @Body() createReferralDto: CreateReferralDto, @CurrentUser() currentUser: UserSchema, ): Promise { - await this.referralsService.create(createReferralDto, currentUser.id); + await this.createReferralUseCase.execute({ + createReferralDto, + userId: currentUser.id, + }); return { success: true, message: 'Encaminhamento cadastrado com sucesso.' }; } @Patch(':id/cancel') @Roles(['nurse', 'manager', 'specialist']) - @ApiOperation({ summary: 'Cancela um encaminhamento.' }) + @ApiOperation({ summary: 'Cancela um encaminhamento' }) async cancel( @Param('id') id: string, @CurrentUser() user: UserSchema, diff --git a/src/app/http/referrals/referrals.module.ts b/src/app/http/referrals/referrals.module.ts index 9d7058f..1475313 100644 --- a/src/app/http/referrals/referrals.module.ts +++ b/src/app/http/referrals/referrals.module.ts @@ -1,18 +1,29 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { Patient } from '@/domain/entities/patient'; import { Referral } from '@/domain/entities/referral'; import { PatientsModule } from '../patients/patients.module'; import { ReferralsController } from './referrals.controller'; import { ReferralsRepository } from './referrals.repository'; import { ReferralsService } from './referrals.service'; +import { CreateReferralUseCase } from './use-cases/create-referrals-use-case'; import { GetReferralsUseCase } from './use-cases/get-referrals-use-case'; @Module({ - imports: [PatientsModule, TypeOrmModule.forFeature([Referral])], + imports: [ + PatientsModule, + TypeOrmModule.forFeature([Referral]), + TypeOrmModule.forFeature([Patient]), + ], controllers: [ReferralsController], - providers: [ReferralsService, ReferralsRepository, GetReferralsUseCase], + providers: [ + ReferralsService, + ReferralsRepository, + GetReferralsUseCase, + CreateReferralUseCase, + ], exports: [ReferralsService, ReferralsRepository], }) export class ReferralsModule {} diff --git a/src/app/http/referrals/referrals.repository.ts b/src/app/http/referrals/referrals.repository.ts index 924cbc5..ce417f5 100644 --- a/src/app/http/referrals/referrals.repository.ts +++ b/src/app/http/referrals/referrals.repository.ts @@ -13,8 +13,6 @@ import { Referral } from '@/domain/entities/referral'; import type { ReferralStatus } from '@/domain/enums/referrals'; import type { CategoryTotalReferrals } from '@/domain/schemas/statistics'; -import { CreateReferralDto } from './referrals.dtos'; - @Injectable() export class ReferralsRepository { constructor( @@ -26,16 +24,6 @@ export class ReferralsRepository { return await this.referralsRepository.findOne({ where: { id } }); } - public async create( - createReferralDto: CreateReferralDto & { - status: ReferralStatus; - referred_by: string; - }, - ): Promise { - const referrals = this.referralsRepository.create(createReferralDto); - return await this.referralsRepository.save(referrals); - } - public async cancel(id: string): Promise { return await this.referralsRepository.save({ id, status: 'canceled' }); } diff --git a/src/app/http/referrals/referrals.service.ts b/src/app/http/referrals/referrals.service.ts index 0fc384b..98cdfc1 100644 --- a/src/app/http/referrals/referrals.service.ts +++ b/src/app/http/referrals/referrals.service.ts @@ -9,8 +9,6 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Referral } from '@/domain/entities/referral'; import { UserSchema } from '@/domain/schemas/user'; -import { PatientsRepository } from '../patients/patients.repository'; -import { CreateReferralDto } from './referrals.dtos'; import { ReferralsRepository } from './referrals.repository'; @Injectable() @@ -20,33 +18,8 @@ export class ReferralsService { constructor( @InjectRepository(Referral) private readonly referralsRepository: ReferralsRepository, - private readonly patientsRepository: PatientsRepository, ) {} - public async create( - createReferralDto: CreateReferralDto, - userId: string, - ): Promise { - const { patient_id } = createReferralDto; - - const patient = await this.patientsRepository.findById(patient_id); - - if (!patient) { - throw new NotFoundException('Paciente não encontrado.'); - } - - await this.referralsRepository.create({ - ...createReferralDto, - status: 'scheduled', - referred_by: userId, - }); - - this.logger.log( - { patientId: patient_id, referredBy: userId }, - 'Referral created successfully', - ); - } - async cancel(id: string, user: UserSchema): Promise { const referral = await this.referralsRepository.findById(id); diff --git a/src/app/http/referrals/use-cases/create-referrals-use-case.ts b/src/app/http/referrals/use-cases/create-referrals-use-case.ts new file mode 100644 index 0000000..c60a49c --- /dev/null +++ b/src/app/http/referrals/use-cases/create-referrals-use-case.ts @@ -0,0 +1,54 @@ +import { Injectable, Logger, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { type Repository } from 'typeorm'; + +import { Patient } from '@/domain/entities/patient'; +import { Referral } from '@/domain/entities/referral'; + +import { CreateReferralDto } from '../referrals.dtos'; + +interface CreateReferralUseCaseRequest { + createReferralDto: CreateReferralDto; + userId: string; +} + +type CreateReferralUseCaseResponse = Promise; + +@Injectable() +export class CreateReferralUseCase { + private readonly logger = new Logger(CreateReferralUseCase.name); + + constructor( + @InjectRepository(Patient) + private readonly patientsRepository: Repository, + + @InjectRepository(Referral) + private readonly referralsRepository: Repository, + ) {} + async execute({ + createReferralDto, + userId, + }: CreateReferralUseCaseRequest): CreateReferralUseCaseResponse { + const { patient_id } = createReferralDto; + + const patient = await this.patientsRepository.findOne({ + where: { id: patient_id }, + select: { id: true }, + }); + + if (!patient) { + throw new NotFoundException('Paciente não encontrado.'); + } + + await this.referralsRepository.save({ + ...createReferralDto, + status: 'scheduled', + referred_by: userId, + }); + + this.logger.log( + { patientId: patient_id, referredBy: userId }, + 'Referral created successfully', + ); + } +} diff --git a/src/app/http/referrals/use-cases/get-referrals-use-case.ts b/src/app/http/referrals/use-cases/get-referrals-use-case.ts index 741fa34..459bcf1 100644 --- a/src/app/http/referrals/use-cases/get-referrals-use-case.ts +++ b/src/app/http/referrals/use-cases/get-referrals-use-case.ts @@ -18,9 +18,7 @@ interface GetReferralsUseCaseRequest { query: GetReferralsQuery; } -type GetReferralsUseCaseRequestResponse = Promise< - GetReferralsResponseSchema['data'] ->; +type GetReferralsUseCaseResponse = Promise; @Injectable() export class GetReferralsUseCase { @@ -31,7 +29,7 @@ export class GetReferralsUseCase { async execute({ query, - }: GetReferralsUseCaseRequest): GetReferralsUseCaseRequestResponse { + }: GetReferralsUseCaseRequest): GetReferralsUseCaseResponse { const { orderBy, page, perPage, category, condition, order, search } = query; diff --git a/src/domain/schemas/referral.ts b/src/domain/schemas/referral.ts index cfb1cea..d8afe13 100644 --- a/src/domain/schemas/referral.ts +++ b/src/domain/schemas/referral.ts @@ -16,7 +16,7 @@ export const referralSchema = z date: z.coerce.date(), category: z.enum(REFERRAL_CATEGORIES), condition: z.enum(PATIENT_CONDITIONS), - annotation: z.string().max(500).nullable(), + annotation: z.string().max(2000).nullable(), status: z.enum(REFERRAL_STATUSES).default('scheduled'), referred_to: z.string().nullable(), referred_by: z.string().uuid().nullable(), From f0854c4b626a01227c9b125ecb450358e17b3a73 Mon Sep 17 00:00:00 2001 From: Juliano Sill Date: Sun, 7 Dec 2025 18:18:11 -0300 Subject: [PATCH 3/8] refactor(referrals): apply use case pattern to cancel referral method --- .../http/referrals/referrals.controller.ts | 6 +-- src/app/http/referrals/referrals.module.ts | 8 ++- src/app/http/referrals/referrals.service.ts | 41 ---------------- .../use-cases/cancel-referral-use-case.ts | 49 +++++++++++++++++++ 4 files changed, 55 insertions(+), 49 deletions(-) delete mode 100644 src/app/http/referrals/referrals.service.ts create mode 100644 src/app/http/referrals/use-cases/cancel-referral-use-case.ts diff --git a/src/app/http/referrals/referrals.controller.ts b/src/app/http/referrals/referrals.controller.ts index afe42b5..66c24a1 100644 --- a/src/app/http/referrals/referrals.controller.ts +++ b/src/app/http/referrals/referrals.controller.ts @@ -16,7 +16,7 @@ import type { GetReferralsResponseSchema } from '@/domain/schemas/referral'; import { UserSchema } from '@/domain/schemas/user'; import { CreateReferralDto, GetReferralsQuery } from './referrals.dtos'; -import { ReferralsService } from './referrals.service'; +import { CancelReferralUseCase } from './use-cases/cancel-referral-use-case'; import { CreateReferralUseCase } from './use-cases/create-referrals-use-case'; import { GetReferralsUseCase } from './use-cases/get-referrals-use-case'; @@ -26,7 +26,7 @@ export class ReferralsController { constructor( private readonly getReferralsUseCase: GetReferralsUseCase, private readonly createReferralUseCase: CreateReferralUseCase, - private readonly referralsService: ReferralsService, + private readonly cancelReferralUseCase: CancelReferralUseCase, ) {} @Get() @@ -66,7 +66,7 @@ export class ReferralsController { @Param('id') id: string, @CurrentUser() user: UserSchema, ): Promise { - await this.referralsService.cancel(id, user); + await this.cancelReferralUseCase.execute({ id, userId: user.id }); return { success: true, diff --git a/src/app/http/referrals/referrals.module.ts b/src/app/http/referrals/referrals.module.ts index 1475313..3e003ee 100644 --- a/src/app/http/referrals/referrals.module.ts +++ b/src/app/http/referrals/referrals.module.ts @@ -4,26 +4,24 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { Patient } from '@/domain/entities/patient'; import { Referral } from '@/domain/entities/referral'; -import { PatientsModule } from '../patients/patients.module'; import { ReferralsController } from './referrals.controller'; import { ReferralsRepository } from './referrals.repository'; -import { ReferralsService } from './referrals.service'; +import { CancelReferralUseCase } from './use-cases/cancel-referral-use-case'; import { CreateReferralUseCase } from './use-cases/create-referrals-use-case'; import { GetReferralsUseCase } from './use-cases/get-referrals-use-case'; @Module({ imports: [ - PatientsModule, TypeOrmModule.forFeature([Referral]), TypeOrmModule.forFeature([Patient]), ], controllers: [ReferralsController], providers: [ - ReferralsService, ReferralsRepository, GetReferralsUseCase, CreateReferralUseCase, + CancelReferralUseCase, ], - exports: [ReferralsService, ReferralsRepository], + exports: [ReferralsRepository], }) export class ReferralsModule {} diff --git a/src/app/http/referrals/referrals.service.ts b/src/app/http/referrals/referrals.service.ts deleted file mode 100644 index 98cdfc1..0000000 --- a/src/app/http/referrals/referrals.service.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { - BadRequestException, - Injectable, - Logger, - NotFoundException, -} from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; - -import { Referral } from '@/domain/entities/referral'; -import { UserSchema } from '@/domain/schemas/user'; - -import { ReferralsRepository } from './referrals.repository'; - -@Injectable() -export class ReferralsService { - private readonly logger = new Logger(ReferralsService.name); - - constructor( - @InjectRepository(Referral) - private readonly referralsRepository: ReferralsRepository, - ) {} - - async cancel(id: string, user: UserSchema): Promise { - const referral = await this.referralsRepository.findById(id); - - if (!referral) { - throw new NotFoundException('Encaminhamento não encontrado.'); - } - - if (referral.status === 'canceled') { - throw new BadRequestException('Este encaminhamento já está cancelado.'); - } - - await this.referralsRepository.cancel(referral.id); - - this.logger.log( - { id: referral.id, userId: user.id }, - 'Referral canceled successfully.', - ); - } -} diff --git a/src/app/http/referrals/use-cases/cancel-referral-use-case.ts b/src/app/http/referrals/use-cases/cancel-referral-use-case.ts new file mode 100644 index 0000000..8309736 --- /dev/null +++ b/src/app/http/referrals/use-cases/cancel-referral-use-case.ts @@ -0,0 +1,49 @@ +import { + BadRequestException, + Injectable, + Logger, + NotFoundException, +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { type Repository } from 'typeorm'; + +import { Referral } from '@/domain/entities/referral'; + +interface CancelReferralUseCaseRequest { + id: string; + userId: string; +} + +type CancelReferralUseCaseResponse = Promise; + +@Injectable() +export class CancelReferralUseCase { + private readonly logger = new Logger(CancelReferralUseCase.name); + + constructor( + @InjectRepository(Referral) + private readonly referralsRepository: Repository, + ) {} + + async execute({ + id, + userId, + }: CancelReferralUseCaseRequest): CancelReferralUseCaseResponse { + const referral = await this.referralsRepository.findOne({ + select: { id: true, status: true }, + where: { id }, + }); + + if (!referral) { + throw new NotFoundException('Encaminhamento não encontrado.'); + } + + if (referral.status === 'canceled') { + throw new BadRequestException('Este encaminhamento já está cancelado.'); + } + + await this.referralsRepository.save({ id, status: 'canceled' }); + + this.logger.log({ id, userId }, 'Referral canceled successfully.'); + } +} From 891f127fa07230af69f1728496c7aef8d6bf9ba8 Mon Sep 17 00:00:00 2001 From: Juliano Sill Date: Sun, 7 Dec 2025 21:25:26 -0300 Subject: [PATCH 4/8] refactor(statistics): apply use case pattern to referred patients by state method --- src/app/http/patients/patients.module.ts | 2 +- src/app/http/patients/patients.repository.ts | 56 +------------ .../http/referrals/referrals.controller.ts | 6 +- src/app/http/referrals/referrals.module.ts | 6 +- ...se-case.ts => cancel-referral.use-case.ts} | 2 +- ...e-case.ts => create-referrals.use-case.ts} | 0 ...-use-case.ts => get-referrals.use-case.ts} | 0 .../http/statistics/statistics.controller.ts | 8 +- src/app/http/statistics/statistics.module.ts | 3 +- src/app/http/statistics/statistics.service.ts | 15 ---- ...get-referred-patients-by-state.use-case.ts | 79 +++++++++++++++++++ src/domain/schemas/statistics.ts | 1 + 12 files changed, 97 insertions(+), 81 deletions(-) rename src/app/http/referrals/use-cases/{cancel-referral-use-case.ts => cancel-referral.use-case.ts} (96%) rename src/app/http/referrals/use-cases/{create-referrals-use-case.ts => create-referrals.use-case.ts} (100%) rename src/app/http/referrals/use-cases/{get-referrals-use-case.ts => get-referrals.use-case.ts} (100%) create mode 100644 src/app/http/statistics/use-cases/get-referred-patients-by-state.use-case.ts diff --git a/src/app/http/patients/patients.module.ts b/src/app/http/patients/patients.module.ts index 9355686..929d363 100644 --- a/src/app/http/patients/patients.module.ts +++ b/src/app/http/patients/patients.module.ts @@ -19,6 +19,6 @@ import { PatientsService } from './patients.service'; ], controllers: [PatientsController], providers: [PatientsService, PatientsRepository], - exports: [PatientsRepository], + exports: [PatientsRepository, TypeOrmModule.forFeature([Patient])], }) export class PatientsModule {} diff --git a/src/app/http/patients/patients.repository.ts b/src/app/http/patients/patients.repository.ts index fffcd60..0f61af1 100644 --- a/src/app/http/patients/patients.repository.ts +++ b/src/app/http/patients/patients.repository.ts @@ -8,7 +8,6 @@ import { MoreThanOrEqual, Not, Repository, - type SelectQueryBuilder, } from 'typeorm'; import { Patient } from '@/domain/entities/patient'; @@ -17,10 +16,7 @@ import type { PatientStatus, PatientType, } from '@/domain/schemas/patient'; -import type { - PatientsStatisticField, - StateReferredPatients, -} from '@/domain/schemas/statistics'; +import type { PatientsStatisticField } from '@/domain/schemas/statistics'; import type { GetPatientsByPeriodQuery } from '../statistics/statistics.dtos'; import { CreatePatientDto, FindAllPatientQueryDto } from './patients.dtos'; @@ -292,54 +288,4 @@ export class PatientsRepository { return await this.patientsRepository.count({ where }); } - - public async getReferredPatientsByState( - input: { startDate?: Date; endDate?: Date; limit?: number } = {}, - ): Promise<{ states: StateReferredPatients[]; total: number }> { - const { startDate, endDate, limit = 10 } = input; - - const createQueryBuilder = (): SelectQueryBuilder => { - return this.patientsRepository - .createQueryBuilder('patient') - .innerJoin('patient.referrals', 'referral') - .where('referral.referred_to IS NOT NULL') - .andWhere('referral.referred_to != :empty', { empty: '' }); - }; - - function getQueryBuilderWithFilters( - queryBuilder: SelectQueryBuilder, - ) { - if (startDate && endDate) { - queryBuilder.andWhere('referral.date BETWEEN :start AND :end', { - start: startDate, - end: endDate, - }); - } - - return queryBuilder; - } - - const stateListQuery = getQueryBuilderWithFilters( - createQueryBuilder() - .select('patient.state', 'state') - .addSelect('COUNT(DISTINCT patient.id)', 'total') - .groupBy('patient.state') - .orderBy('COUNT(DISTINCT patient.id)', 'DESC') - .limit(limit), - ); - - const totalStatesQuery = getQueryBuilderWithFilters( - createQueryBuilder().select('COUNT(DISTINCT patient.state)', 'total'), - ); - - const [states, totalResult] = await Promise.all([ - stateListQuery.getRawMany(), - totalStatesQuery.getRawOne<{ total: string }>(), - ]); - - return { - states, - total: Number(totalResult?.total || 0), - }; - } } diff --git a/src/app/http/referrals/referrals.controller.ts b/src/app/http/referrals/referrals.controller.ts index 66c24a1..4a0dc8a 100644 --- a/src/app/http/referrals/referrals.controller.ts +++ b/src/app/http/referrals/referrals.controller.ts @@ -16,9 +16,9 @@ import type { GetReferralsResponseSchema } from '@/domain/schemas/referral'; import { UserSchema } from '@/domain/schemas/user'; import { CreateReferralDto, GetReferralsQuery } from './referrals.dtos'; -import { CancelReferralUseCase } from './use-cases/cancel-referral-use-case'; -import { CreateReferralUseCase } from './use-cases/create-referrals-use-case'; -import { GetReferralsUseCase } from './use-cases/get-referrals-use-case'; +import { CancelReferralUseCase } from './use-cases/cancel-referral.use-case'; +import { CreateReferralUseCase } from './use-cases/create-referrals.use-case'; +import { GetReferralsUseCase } from './use-cases/get-referrals.use-case'; @ApiTags('Encaminhamentos') @Controller('referrals') diff --git a/src/app/http/referrals/referrals.module.ts b/src/app/http/referrals/referrals.module.ts index 3e003ee..fe79ab3 100644 --- a/src/app/http/referrals/referrals.module.ts +++ b/src/app/http/referrals/referrals.module.ts @@ -6,9 +6,9 @@ import { Referral } from '@/domain/entities/referral'; import { ReferralsController } from './referrals.controller'; import { ReferralsRepository } from './referrals.repository'; -import { CancelReferralUseCase } from './use-cases/cancel-referral-use-case'; -import { CreateReferralUseCase } from './use-cases/create-referrals-use-case'; -import { GetReferralsUseCase } from './use-cases/get-referrals-use-case'; +import { CancelReferralUseCase } from './use-cases/cancel-referral.use-case'; +import { CreateReferralUseCase } from './use-cases/create-referrals.use-case'; +import { GetReferralsUseCase } from './use-cases/get-referrals.use-case'; @Module({ imports: [ diff --git a/src/app/http/referrals/use-cases/cancel-referral-use-case.ts b/src/app/http/referrals/use-cases/cancel-referral.use-case.ts similarity index 96% rename from src/app/http/referrals/use-cases/cancel-referral-use-case.ts rename to src/app/http/referrals/use-cases/cancel-referral.use-case.ts index 8309736..ee3c29e 100644 --- a/src/app/http/referrals/use-cases/cancel-referral-use-case.ts +++ b/src/app/http/referrals/use-cases/cancel-referral.use-case.ts @@ -5,7 +5,7 @@ import { NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { type Repository } from 'typeorm'; +import type { Repository } from 'typeorm'; import { Referral } from '@/domain/entities/referral'; diff --git a/src/app/http/referrals/use-cases/create-referrals-use-case.ts b/src/app/http/referrals/use-cases/create-referrals.use-case.ts similarity index 100% rename from src/app/http/referrals/use-cases/create-referrals-use-case.ts rename to src/app/http/referrals/use-cases/create-referrals.use-case.ts diff --git a/src/app/http/referrals/use-cases/get-referrals-use-case.ts b/src/app/http/referrals/use-cases/get-referrals.use-case.ts similarity index 100% rename from src/app/http/referrals/use-cases/get-referrals-use-case.ts rename to src/app/http/referrals/use-cases/get-referrals.use-case.ts diff --git a/src/app/http/statistics/statistics.controller.ts b/src/app/http/statistics/statistics.controller.ts index 271f910..44b4c56 100644 --- a/src/app/http/statistics/statistics.controller.ts +++ b/src/app/http/statistics/statistics.controller.ts @@ -19,11 +19,15 @@ import { GetTotalReferralsByCategoryQuery, } from './statistics.dtos'; import { StatisticsService } from './statistics.service'; +import { GetReferredPatientsByStateUseCase } from './use-cases/get-referred-patients-by-state.use-case'; @ApiTags('Estatísticas') @Controller('statistics') export class StatisticsController { - constructor(private readonly statisticsService: StatisticsService) {} + constructor( + private readonly statisticsService: StatisticsService, + private readonly getReferredPatientsByStateUseCase: GetReferredPatientsByStateUseCase, + ) {} @Get('patients/total') @Roles(['manager', 'nurse']) @@ -123,7 +127,7 @@ export class StatisticsController { @Query() query: GetReferredPatientsByStateQuery, ): Promise { const { states, total } = - await this.statisticsService.getReferredPatientsByState(query); + await this.getReferredPatientsByStateUseCase.execute({ query }); return { success: true, diff --git a/src/app/http/statistics/statistics.module.ts b/src/app/http/statistics/statistics.module.ts index 2c9cd33..1c0811d 100644 --- a/src/app/http/statistics/statistics.module.ts +++ b/src/app/http/statistics/statistics.module.ts @@ -6,10 +6,11 @@ import { PatientsModule } from '../patients/patients.module'; import { ReferralsModule } from '../referrals/referrals.module'; import { StatisticsController } from './statistics.controller'; import { StatisticsService } from './statistics.service'; +import { GetReferredPatientsByStateUseCase } from './use-cases/get-referred-patients-by-state.use-case'; @Module({ imports: [PatientsModule, UtilsModule, ReferralsModule], controllers: [StatisticsController], - providers: [StatisticsService], + providers: [StatisticsService, GetReferredPatientsByStateUseCase], }) export class StatisticsModule {} diff --git a/src/app/http/statistics/statistics.service.ts b/src/app/http/statistics/statistics.service.ts index 7fa5ffd..bcf4a0a 100644 --- a/src/app/http/statistics/statistics.service.ts +++ b/src/app/http/statistics/statistics.service.ts @@ -4,7 +4,6 @@ import type { CategoryTotalReferrals, GetTotalPatientsByStatusResponse, PatientsStatisticField, - StateReferredPatients, } from '@/domain/schemas/statistics'; import { UtilsService } from '@/utils/utils.service'; @@ -12,7 +11,6 @@ import { PatientsRepository } from '../patients/patients.repository'; import { ReferralsRepository } from '../referrals/referrals.repository'; import type { GetPatientsByPeriodQuery, - GetReferredPatientsByStateQuery, GetTotalReferralsAndReferredPatientsPercentageQuery, GetTotalReferralsByCategoryQuery, } from './statistics.dtos'; @@ -83,17 +81,4 @@ export class StatisticsService { endDate, }); } - - async getReferredPatientsByState( - query: GetReferredPatientsByStateQuery, - ): Promise<{ states: StateReferredPatients[]; total: number }> { - const { startDate, endDate } = this.utilsService.getDateRangeForPeriod( - query.period, - ); - - return await this.patientsRepository.getReferredPatientsByState({ - startDate, - endDate, - }); - } } diff --git a/src/app/http/statistics/use-cases/get-referred-patients-by-state.use-case.ts b/src/app/http/statistics/use-cases/get-referred-patients-by-state.use-case.ts new file mode 100644 index 0000000..ff58dd6 --- /dev/null +++ b/src/app/http/statistics/use-cases/get-referred-patients-by-state.use-case.ts @@ -0,0 +1,79 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import type { Repository, SelectQueryBuilder } from 'typeorm'; + +import { Patient } from '@/domain/entities/patient'; +import type { StateReferredPatients } from '@/domain/schemas/statistics'; +import { UtilsService } from '@/utils/utils.service'; + +import type { GetReferredPatientsByStateQuery } from '../statistics.dtos'; + +interface GetReferredPatientsByStateUseCaseRequest { + query: GetReferredPatientsByStateQuery; +} + +type GetReferredPatientsByStateUseCaseResponse = Promise<{ + states: StateReferredPatients[]; + total: number; +}>; + +@Injectable() +export class GetReferredPatientsByStateUseCase { + constructor( + @InjectRepository(Patient) + private readonly patientsRepository: Repository, + private readonly utilsService: UtilsService, + ) {} + + async execute({ + query, + }: GetReferredPatientsByStateUseCaseRequest): GetReferredPatientsByStateUseCaseResponse { + const { startDate, endDate } = this.utilsService.getDateRangeForPeriod( + query.period, + ); + + const createQueryBuilder = (): SelectQueryBuilder => { + return this.patientsRepository + .createQueryBuilder('patient') + .innerJoin('patient.referrals', 'referral') + .where('referral.referred_to IS NOT NULL') + .andWhere('referral.referred_to != :empty', { empty: '' }); + }; + + function getQueryBuilderWithFilters( + queryBuilder: SelectQueryBuilder, + ) { + if (startDate && endDate) { + queryBuilder.andWhere('referral.date BETWEEN :start AND :end', { + start: startDate, + end: endDate, + }); + } + + return queryBuilder; + } + + const stateListQuery = getQueryBuilderWithFilters( + createQueryBuilder() + .select('patient.state', 'state') + .addSelect('COUNT(DISTINCT patient.id)', 'total') + .groupBy('patient.state') + .orderBy('COUNT(DISTINCT patient.id)', 'DESC') + .limit(query.limit), + ); + + const totalStatesQuery = getQueryBuilderWithFilters( + createQueryBuilder().select('COUNT(DISTINCT patient.state)', 'total'), + ); + + const [states, totalResult] = await Promise.all([ + stateListQuery.getRawMany(), + totalStatesQuery.getRawOne<{ total: string }>(), + ]); + + return { + states, + total: Number(totalResult?.total || 0), + }; + } +} diff --git a/src/domain/schemas/statistics.ts b/src/domain/schemas/statistics.ts index 4e01e1b..9ed48c0 100644 --- a/src/domain/schemas/statistics.ts +++ b/src/domain/schemas/statistics.ts @@ -87,6 +87,7 @@ export type GetTotalReferralsAndReferredPatientsPercentageResponse = z.infer< export const getReferredPatientsByStateQuerySchema = baseQuerySchema.pick({ period: true, + limit: true, }); export const stateReferredPatientsSchema = z.object({ From 91bff9a736c47e31218e538197c3f67047aa1271 Mon Sep 17 00:00:00 2001 From: Juliano Sill Date: Sun, 7 Dec 2025 21:32:58 -0300 Subject: [PATCH 5/8] refactor(statistics): apply use case pattern to referred patients by category method --- .../http/statistics/statistics.controller.ts | 4 +- src/app/http/statistics/statistics.module.ts | 16 +++- src/app/http/statistics/statistics.service.ts | 15 ---- ...et-total-referrals-by-category.use-case.ts | 75 +++++++++++++++++++ src/domain/schemas/statistics.ts | 1 + 5 files changed, 93 insertions(+), 18 deletions(-) create mode 100644 src/app/http/statistics/use-cases/get-total-referrals-by-category.use-case.ts diff --git a/src/app/http/statistics/statistics.controller.ts b/src/app/http/statistics/statistics.controller.ts index 44b4c56..070956a 100644 --- a/src/app/http/statistics/statistics.controller.ts +++ b/src/app/http/statistics/statistics.controller.ts @@ -20,6 +20,7 @@ import { } from './statistics.dtos'; import { StatisticsService } from './statistics.service'; import { GetReferredPatientsByStateUseCase } from './use-cases/get-referred-patients-by-state.use-case'; +import { GetTotalReferralsByCategoryUseCase } from './use-cases/get-total-referrals-by-category.use-case'; @ApiTags('Estatísticas') @Controller('statistics') @@ -27,6 +28,7 @@ export class StatisticsController { constructor( private readonly statisticsService: StatisticsService, private readonly getReferredPatientsByStateUseCase: GetReferredPatientsByStateUseCase, + private readonly getTotalReferralsByCategoryUseCase: GetTotalReferralsByCategoryUseCase, ) {} @Get('patients/total') @@ -108,7 +110,7 @@ export class StatisticsController { @Query() query: GetTotalReferralsByCategoryQuery, ): Promise { const { categories, total } = - await this.statisticsService.getTotalReferralsByCategory(query); + await this.getTotalReferralsByCategoryUseCase.execute({ query }); return { success: true, diff --git a/src/app/http/statistics/statistics.module.ts b/src/app/http/statistics/statistics.module.ts index 1c0811d..11d57a5 100644 --- a/src/app/http/statistics/statistics.module.ts +++ b/src/app/http/statistics/statistics.module.ts @@ -1,5 +1,7 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Referral } from '@/domain/entities/referral'; import { UtilsModule } from '@/utils/utils.module'; import { PatientsModule } from '../patients/patients.module'; @@ -7,10 +9,20 @@ import { ReferralsModule } from '../referrals/referrals.module'; import { StatisticsController } from './statistics.controller'; import { StatisticsService } from './statistics.service'; import { GetReferredPatientsByStateUseCase } from './use-cases/get-referred-patients-by-state.use-case'; +import { GetTotalReferralsByCategoryUseCase } from './use-cases/get-total-referrals-by-category.use-case'; @Module({ - imports: [PatientsModule, UtilsModule, ReferralsModule], + imports: [ + PatientsModule, + UtilsModule, + ReferralsModule, + TypeOrmModule.forFeature([Referral]), + ], controllers: [StatisticsController], - providers: [StatisticsService, GetReferredPatientsByStateUseCase], + providers: [ + StatisticsService, + GetReferredPatientsByStateUseCase, + GetTotalReferralsByCategoryUseCase, + ], }) export class StatisticsModule {} diff --git a/src/app/http/statistics/statistics.service.ts b/src/app/http/statistics/statistics.service.ts index bcf4a0a..85d1a9e 100644 --- a/src/app/http/statistics/statistics.service.ts +++ b/src/app/http/statistics/statistics.service.ts @@ -1,7 +1,6 @@ import { Injectable } from '@nestjs/common'; import type { - CategoryTotalReferrals, GetTotalPatientsByStatusResponse, PatientsStatisticField, } from '@/domain/schemas/statistics'; @@ -12,7 +11,6 @@ import { ReferralsRepository } from '../referrals/referrals.repository'; import type { GetPatientsByPeriodQuery, GetTotalReferralsAndReferredPatientsPercentageQuery, - GetTotalReferralsByCategoryQuery, } from './statistics.dtos'; @Injectable() @@ -68,17 +66,4 @@ export class StatisticsService { referredPatientsPercentage: Number(percentage.toFixed(2)), }; } - - async getTotalReferralsByCategory( - query: GetTotalReferralsByCategoryQuery, - ): Promise<{ categories: CategoryTotalReferrals[]; total: number }> { - const { startDate, endDate } = this.utilsService.getDateRangeForPeriod( - query.period, - ); - - return await this.referralsRepository.getTotalReferralsByCategory({ - startDate, - endDate, - }); - } } diff --git a/src/app/http/statistics/use-cases/get-total-referrals-by-category.use-case.ts b/src/app/http/statistics/use-cases/get-total-referrals-by-category.use-case.ts new file mode 100644 index 0000000..c6e9534 --- /dev/null +++ b/src/app/http/statistics/use-cases/get-total-referrals-by-category.use-case.ts @@ -0,0 +1,75 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import type { Repository, SelectQueryBuilder } from 'typeorm'; + +import { Referral } from '@/domain/entities/referral'; +import type { CategoryTotalReferrals } from '@/domain/schemas/statistics'; +import { UtilsService } from '@/utils/utils.service'; + +import type { GetTotalReferralsByCategoryQuery } from '../statistics.dtos'; + +interface GetTotalReferralsByCategoryUseCaseRequest { + query: GetTotalReferralsByCategoryQuery; +} + +type GetTotalReferralsByCategoryUseCaseResponse = Promise<{ + categories: CategoryTotalReferrals[]; + total: number; +}>; + +@Injectable() +export class GetTotalReferralsByCategoryUseCase { + constructor( + @InjectRepository(Referral) + private readonly referralsRepository: Repository, + private readonly utilsService: UtilsService, + ) {} + + async execute({ + query, + }: GetTotalReferralsByCategoryUseCaseRequest): GetTotalReferralsByCategoryUseCaseResponse { + const { startDate, endDate } = this.utilsService.getDateRangeForPeriod( + query.period, + ); + + const createQueryBuilder = (): SelectQueryBuilder => { + return this.referralsRepository.createQueryBuilder('referral'); + }; + + function getQueryBuilderWithFilters( + queryBuilder: SelectQueryBuilder, + ) { + if (startDate && endDate) { + queryBuilder.andWhere('referral.date BETWEEN :start AND :end', { + start: startDate, + end: endDate, + }); + } + + return queryBuilder; + } + + const categoryListQuery = getQueryBuilderWithFilters( + createQueryBuilder() + .select('referral.category', 'category') + .addSelect('COUNT(referral.id)', 'total') + .groupBy('referral.category') + .orderBy('COUNT(referral.id)', 'DESC') + .limit(query.limit), + ); + + const totalCategoriesQuery = getQueryBuilderWithFilters( + createQueryBuilder().select('COUNT(DISTINCT referral.category)', 'total'), + ); + + const [categories, totalResult] = await Promise.all([ + categoryListQuery.getRawMany(), + totalCategoriesQuery.getRawOne<{ total: string }>(), + ]); + + return { + categories, + total: Number(totalResult?.total || 0), + }; + } +} diff --git a/src/domain/schemas/statistics.ts b/src/domain/schemas/statistics.ts index 9ed48c0..10c0b7c 100644 --- a/src/domain/schemas/statistics.ts +++ b/src/domain/schemas/statistics.ts @@ -109,6 +109,7 @@ export type GetReferredPatientsByStateResponse = z.infer< export const getTotalReferralsByCategoryQuerySchema = baseQuerySchema.pick({ period: true, + limit: true, }); export const categoryTotalReferralsSchema = z.object({ From d489e0545b4fa5bd88435d37161e016a68d63ee6 Mon Sep 17 00:00:00 2001 From: Juliano Sill Date: Mon, 8 Dec 2025 00:17:38 -0300 Subject: [PATCH 6/8] refactor(statistics): apply use case to the entire module --- .../http/statistics/statistics.controller.ts | 55 ++++++------ src/app/http/statistics/statistics.dtos.ts | 8 +- src/app/http/statistics/statistics.module.ts | 28 ++++--- src/app/http/statistics/statistics.service.ts | 69 --------------- .../get-total-patients-by-field.use-case.ts | 79 ++++++++++++++++++ .../get-total-patients-by-status.use-case.ts | 40 +++++++++ .../use-cases/get-total-patients.use-case.ts | 63 ++++++++++++++ ...d-referred-patients-percentage.use-case.ts | 44 ++++++++++ ...et-total-referrals-by-category.use-case.ts | 6 +- .../use-cases/get-total-referrals.use-case.ts | 83 +++++++++++++++++++ ...al-referred-patients-by-state.use-case.ts} | 14 ++-- .../get-total-referred-patients.use-case.ts | 61 ++++++++++++++ src/domain/enums/statistics.ts | 2 + src/domain/schemas/statistics/requests.ts | 22 +++++ .../responses.ts} | 60 ++++---------- 15 files changed, 470 insertions(+), 164 deletions(-) delete mode 100644 src/app/http/statistics/statistics.service.ts create mode 100644 src/app/http/statistics/use-cases/get-total-patients-by-field.use-case.ts create mode 100644 src/app/http/statistics/use-cases/get-total-patients-by-status.use-case.ts create mode 100644 src/app/http/statistics/use-cases/get-total-patients.use-case.ts create mode 100644 src/app/http/statistics/use-cases/get-total-referrals-and-referred-patients-percentage.use-case.ts create mode 100644 src/app/http/statistics/use-cases/get-total-referrals.use-case.ts rename src/app/http/statistics/use-cases/{get-referred-patients-by-state.use-case.ts => get-total-referred-patients-by-state.use-case.ts} (80%) create mode 100644 src/app/http/statistics/use-cases/get-total-referred-patients.use-case.ts create mode 100644 src/domain/enums/statistics.ts create mode 100644 src/domain/schemas/statistics/requests.ts rename src/domain/schemas/{statistics.ts => statistics/responses.ts} (56%) diff --git a/src/app/http/statistics/statistics.controller.ts b/src/app/http/statistics/statistics.controller.ts index 070956a..fbdcced 100644 --- a/src/app/http/statistics/statistics.controller.ts +++ b/src/app/http/statistics/statistics.controller.ts @@ -8,34 +8,38 @@ import type { GetReferredPatientsByStateResponse, GetTotalReferralsAndReferredPatientsPercentageResponse, GetTotalReferralsByCategoryResponse, - PatientsByCity, - PatientsByGender, -} from '@/domain/schemas/statistics'; + TotalPatientsByCity, + TotalPatientsByGender, +} from '@/domain/schemas/statistics/responses'; import { - GetPatientsByPeriodQuery, GetReferredPatientsByStateQuery, + GetTotalPatientsByFieldQuery, GetTotalReferralsAndReferredPatientsPercentageQuery, GetTotalReferralsByCategoryQuery, } from './statistics.dtos'; -import { StatisticsService } from './statistics.service'; -import { GetReferredPatientsByStateUseCase } from './use-cases/get-referred-patients-by-state.use-case'; +import { GetTotalPatientsByFieldUseCase } from './use-cases/get-total-patients-by-field.use-case'; +import { GetTotalPatientsByStatusUseCase } from './use-cases/get-total-patients-by-status.use-case'; +import { GetTotalReferralsAndReferredPatientsPercentageUseCase } from './use-cases/get-total-referrals-and-referred-patients-percentage.use-case'; import { GetTotalReferralsByCategoryUseCase } from './use-cases/get-total-referrals-by-category.use-case'; +import { GetTotalReferredPatientsByStateUseCase } from './use-cases/get-total-referred-patients-by-state.use-case'; @ApiTags('Estatísticas') +@Roles(['manager', 'nurse']) @Controller('statistics') export class StatisticsController { constructor( - private readonly statisticsService: StatisticsService, - private readonly getReferredPatientsByStateUseCase: GetReferredPatientsByStateUseCase, + private readonly getTotalPatientsByStatusUseCase: GetTotalPatientsByStatusUseCase, + private readonly getTotalPatientsByPeriodUseCase: GetTotalPatientsByFieldUseCase, + private readonly getTotalReferredPatientsByStateUseCase: GetTotalReferredPatientsByStateUseCase, private readonly getTotalReferralsByCategoryUseCase: GetTotalReferralsByCategoryUseCase, + private readonly getTotalReferralsAndReferredPatientsPercentageUseCase: GetTotalReferralsAndReferredPatientsPercentageUseCase, ) {} - @Get('patients/total') - @Roles(['manager', 'nurse']) + @Get('patients-total') @ApiOperation({ summary: 'Estatísticas totais de pacientes' }) async getPatientsTotal() { - const data = await this.statisticsService.getPatientsTotal(); + const data = await this.getTotalPatientsByStatusUseCase.execute(); return { success: true, @@ -45,15 +49,16 @@ export class StatisticsController { } @Get('patients-by-gender') - @Roles(['manager', 'nurse']) @ApiOperation({ summary: 'Estatísticas de pacientes por gênero' }) async getPatientsByGender( - @Query() query: GetPatientsByPeriodQuery, + @Query() query: GetTotalPatientsByFieldQuery, ): Promise { const { items: genders, total } = - await this.statisticsService.getPatientsByPeriod( - 'gender', - query, + await this.getTotalPatientsByPeriodUseCase.execute( + { + field: 'gender', + query, + }, ); return { @@ -64,16 +69,15 @@ export class StatisticsController { } @Get('patients-by-city') - @Roles(['manager', 'nurse']) @ApiOperation({ summary: 'Estatísticas de pacientes por cidade' }) async getPatientsByCity( - @Query() query: GetPatientsByPeriodQuery, + @Query() query: GetTotalPatientsByFieldQuery, ): Promise { const { items: cities, total } = - await this.statisticsService.getPatientsByPeriod( - 'city', + await this.getTotalPatientsByPeriodUseCase.execute({ + field: 'city', query, - ); + }); return { success: true, @@ -83,15 +87,14 @@ export class StatisticsController { } @Get('referrals-total') - @Roles(['manager', 'nurse']) @ApiOperation({ summary: 'Estatísticas do total de encaminhamentos' }) async getTotalReferralsAndReferredPatientsPercentage( @Query() query: GetTotalReferralsAndReferredPatientsPercentageQuery, ): Promise { const data = - await this.statisticsService.getTotalReferralsAndReferredPatientsPercentage( + await this.getTotalReferralsAndReferredPatientsPercentageUseCase.execute({ query, - ); + }); return { success: true, @@ -102,7 +105,6 @@ export class StatisticsController { } @Get('referrals-by-category') - @Roles(['manager', 'nurse']) @ApiOperation({ summary: 'Lista com o total de encaminhamentos por categoria', }) @@ -121,7 +123,6 @@ export class StatisticsController { } @Get('referrals-by-state') - @Roles(['manager', 'nurse']) @ApiOperation({ summary: 'Lista com o total de pacientes encaminhados por estado', }) @@ -129,7 +130,7 @@ export class StatisticsController { @Query() query: GetReferredPatientsByStateQuery, ): Promise { const { states, total } = - await this.getReferredPatientsByStateUseCase.execute({ query }); + await this.getTotalReferredPatientsByStateUseCase.execute({ query }); return { success: true, diff --git a/src/app/http/statistics/statistics.dtos.ts b/src/app/http/statistics/statistics.dtos.ts index 9004d2d..312c8e5 100644 --- a/src/app/http/statistics/statistics.dtos.ts +++ b/src/app/http/statistics/statistics.dtos.ts @@ -1,14 +1,14 @@ import { createZodDto } from 'nestjs-zod'; import { - getPatientsByPeriodQuerySchema, getReferredPatientsByStateQuerySchema, + getTotalPatientsByFieldQuerySchema, getTotalReferralsAndReferredPatientsPercentageQuerySchema, getTotalReferralsByCategoryQuerySchema, -} from '@/domain/schemas/statistics'; +} from '@/domain/schemas/statistics/requests'; -export class GetPatientsByPeriodQuery extends createZodDto( - getPatientsByPeriodQuerySchema, +export class GetTotalPatientsByFieldQuery extends createZodDto( + getTotalPatientsByFieldQuerySchema, ) {} export class GetTotalReferralsAndReferredPatientsPercentageQuery extends createZodDto( diff --git a/src/app/http/statistics/statistics.module.ts b/src/app/http/statistics/statistics.module.ts index 11d57a5..99f4338 100644 --- a/src/app/http/statistics/statistics.module.ts +++ b/src/app/http/statistics/statistics.module.ts @@ -1,28 +1,32 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { Patient } from '@/domain/entities/patient'; import { Referral } from '@/domain/entities/referral'; import { UtilsModule } from '@/utils/utils.module'; -import { PatientsModule } from '../patients/patients.module'; -import { ReferralsModule } from '../referrals/referrals.module'; import { StatisticsController } from './statistics.controller'; -import { StatisticsService } from './statistics.service'; -import { GetReferredPatientsByStateUseCase } from './use-cases/get-referred-patients-by-state.use-case'; +import { GetTotalPatientsUseCase } from './use-cases/get-total-patients.use-case'; +import { GetTotalPatientsByFieldUseCase } from './use-cases/get-total-patients-by-field.use-case'; +import { GetTotalPatientsByStatusUseCase } from './use-cases/get-total-patients-by-status.use-case'; +import { GetTotalReferralsUseCase } from './use-cases/get-total-referrals.use-case'; +import { GetTotalReferralsAndReferredPatientsPercentageUseCase } from './use-cases/get-total-referrals-and-referred-patients-percentage.use-case'; import { GetTotalReferralsByCategoryUseCase } from './use-cases/get-total-referrals-by-category.use-case'; +import { GetTotalReferredPatientsUseCase } from './use-cases/get-total-referred-patients.use-case'; +import { GetTotalReferredPatientsByStateUseCase } from './use-cases/get-total-referred-patients-by-state.use-case'; @Module({ - imports: [ - PatientsModule, - UtilsModule, - ReferralsModule, - TypeOrmModule.forFeature([Referral]), - ], + imports: [TypeOrmModule.forFeature([Patient, Referral]), UtilsModule], controllers: [StatisticsController], providers: [ - StatisticsService, - GetReferredPatientsByStateUseCase, + GetTotalPatientsUseCase, + GetTotalPatientsByFieldUseCase, + GetTotalPatientsByStatusUseCase, + GetTotalReferralsUseCase, GetTotalReferralsByCategoryUseCase, + GetTotalReferralsAndReferredPatientsPercentageUseCase, + GetTotalReferredPatientsUseCase, + GetTotalReferredPatientsByStateUseCase, ], }) export class StatisticsModule {} diff --git a/src/app/http/statistics/statistics.service.ts b/src/app/http/statistics/statistics.service.ts deleted file mode 100644 index 85d1a9e..0000000 --- a/src/app/http/statistics/statistics.service.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import type { - GetTotalPatientsByStatusResponse, - PatientsStatisticField, -} from '@/domain/schemas/statistics'; -import { UtilsService } from '@/utils/utils.service'; - -import { PatientsRepository } from '../patients/patients.repository'; -import { ReferralsRepository } from '../referrals/referrals.repository'; -import type { - GetPatientsByPeriodQuery, - GetTotalReferralsAndReferredPatientsPercentageQuery, -} from './statistics.dtos'; - -@Injectable() -export class StatisticsService { - constructor( - private readonly patientsRepository: PatientsRepository, - private readonly utilsService: UtilsService, - private readonly referralsRepository: ReferralsRepository, - ) {} - - async getPatientsTotal(): Promise { - return await this.patientsRepository.getTotalPatientsByStatus(); - } - - async getPatientsByPeriod( - filter: PatientsStatisticField, - query: GetPatientsByPeriodQuery, - ): Promise<{ items: T[]; total: number }> { - const { startDate, endDate } = this.utilsService.getDateRangeForPeriod( - query.period, - ); - - return await this.patientsRepository.getPatientsStatisticsByPeriod( - filter, - startDate, - endDate, - query, - ); - } - - async getTotalReferralsAndReferredPatientsPercentage( - query: GetTotalReferralsAndReferredPatientsPercentageQuery, - ): Promise<{ totalReferrals: number; referredPatientsPercentage: number }> { - const { startDate, endDate } = this.utilsService.getDateRangeForPeriod( - query.period, - ); - - const [totalPatients, totalReferrals, totalReferredPatients] = - await Promise.all([ - this.patientsRepository.getTotalPatients({ startDate, endDate }), - this.referralsRepository.getTotalReferrals({ startDate, endDate }), - this.patientsRepository.getTotalReferredPatients({ - startDate, - endDate, - }), - ]); - - const percentage = - Number((totalReferredPatients / totalPatients) * 100) || 0; - - return { - totalReferrals, - referredPatientsPercentage: Number(percentage.toFixed(2)), - }; - } -} diff --git a/src/app/http/statistics/use-cases/get-total-patients-by-field.use-case.ts b/src/app/http/statistics/use-cases/get-total-patients-by-field.use-case.ts new file mode 100644 index 0000000..03c043a --- /dev/null +++ b/src/app/http/statistics/use-cases/get-total-patients-by-field.use-case.ts @@ -0,0 +1,79 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import type { Repository, SelectQueryBuilder } from 'typeorm'; + +import { Patient } from '@/domain/entities/patient'; +import type { PatientsStatisticField } from '@/domain/enums/statistics'; +import { UtilsService } from '@/utils/utils.service'; + +import type { GetTotalPatientsByFieldQuery } from '../statistics.dtos'; +import { GetTotalPatientsUseCase } from './get-total-patients.use-case'; + +interface GetTotalPatientsByFieldUseCaseRequest { + field: PatientsStatisticField; + query: GetTotalPatientsByFieldQuery; +} + +type GetTotalPatientsByFieldUseCaseResponse = Promise<{ + items: T[]; + total: number; +}>; + +@Injectable() +export class GetTotalPatientsByFieldUseCase { + constructor( + @InjectRepository(Patient) + private readonly patientsRepository: Repository, + private readonly getTotalPatientsUseCase: GetTotalPatientsUseCase, + private readonly utilsService: UtilsService, + ) {} + + async execute({ + field, + query, + }: GetTotalPatientsByFieldUseCaseRequest): GetTotalPatientsByFieldUseCaseResponse { + const { startDate, endDate } = this.utilsService.getDateRangeForPeriod( + query.period, + ); + + const totalPatients = await this.getTotalPatientsUseCase.execute({ + startDate, + endDate, + }); + + const createBaseQuery = (): SelectQueryBuilder => { + return this.patientsRepository + .createQueryBuilder('patient') + .where('patient.created_at BETWEEN :start AND :end', { + start: startDate, + end: endDate, + }); + }; + + const totalFieldQuery = createBaseQuery().select( + `COUNT(DISTINCT patient.${field})`, + 'total', + ); + + const fieldQuery = createBaseQuery() + .select(`patient.${field}`, field) + .addSelect('COUNT(patient.id)', 'total') + .groupBy(`patient.${field}`) + .orderBy('total', query.order) + .limit(query.limit); + + if (query.withPercentage) { + fieldQuery.addSelect( + `ROUND((COUNT(patient.id) * 100.0 / ${totalPatients}), 1)`, + 'percentage', + ); + } + + const [items, totalResult] = await Promise.all([ + fieldQuery.getRawMany(), + totalFieldQuery.getRawOne<{ total: string }>(), + ]); + + return { items, total: Number(totalResult?.total || 0) }; + } +} diff --git a/src/app/http/statistics/use-cases/get-total-patients-by-status.use-case.ts b/src/app/http/statistics/use-cases/get-total-patients-by-status.use-case.ts new file mode 100644 index 0000000..2048bf8 --- /dev/null +++ b/src/app/http/statistics/use-cases/get-total-patients-by-status.use-case.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import type { Repository } from 'typeorm'; + +import { Patient } from '@/domain/entities/patient'; +import type { GetTotalPatientsByStatusResponse } from '@/domain/schemas/statistics/responses'; + +type GetTotalPatientsByStatusUseCaseResponse = Promise< + GetTotalPatientsByStatusResponse['data'] +>; + +@Injectable() +export class GetTotalPatientsByStatusUseCase { + constructor( + @InjectRepository(Patient) + private readonly patientsRepository: Repository, + ) {} + + async execute(): GetTotalPatientsByStatusUseCaseResponse { + const queryBuilder = await this.patientsRepository + .createQueryBuilder('patient') + .select('COUNT(patient.id)', 'total') + .where('patient.status != :status', { status: 'pending' }) + .addSelect( + `SUM(CASE WHEN patient.status = 'active' THEN 1 ELSE 0 END)`, + 'active', + ) + .addSelect( + `SUM(CASE WHEN patient.status = 'inactive' THEN 1 ELSE 0 END)`, + 'inactive', + ) + .getRawOne<{ total: string; active: string; inactive: string }>(); + + return { + total: Number(queryBuilder?.total ?? 0), + active: Number(queryBuilder?.active ?? 0), + inactive: Number(queryBuilder?.inactive ?? 0), + }; + } +} diff --git a/src/app/http/statistics/use-cases/get-total-patients.use-case.ts b/src/app/http/statistics/use-cases/get-total-patients.use-case.ts new file mode 100644 index 0000000..be41b60 --- /dev/null +++ b/src/app/http/statistics/use-cases/get-total-patients.use-case.ts @@ -0,0 +1,63 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { + Between, + type FindOptionsWhere, + LessThanOrEqual, + MoreThanOrEqual, + Not, + type Repository, +} from 'typeorm'; + +import { Patient } from '@/domain/entities/patient'; +import type { PatientStatus } from '@/domain/schemas/patient'; +import type { QueryPeriod } from '@/domain/schemas/query'; +import { UtilsService } from '@/utils/utils.service'; + +interface GetTotalPatientsUseCaseRequest { + status?: PatientStatus; + period?: QueryPeriod; + startDate?: Date; + endDate?: Date; +} + +type GetTotalPatientsUseCaseResponse = Promise; + +@Injectable() +export class GetTotalPatientsUseCase { + constructor( + @InjectRepository(Patient) + private readonly patientsRepository: Repository, + private readonly utilsService: UtilsService, + ) {} + + async execute({ + status, + period, + startDate, + endDate, + }: GetTotalPatientsUseCaseRequest = {}): GetTotalPatientsUseCaseResponse { + const where: FindOptionsWhere = { + status: status ?? Not('pending'), + }; + + if (period) { + const dateRange = this.utilsService.getDateRangeForPeriod(period); + where.created_at = Between(dateRange.startDate, dateRange.endDate); + } + + if (startDate && !endDate) { + where.created_at = MoreThanOrEqual(startDate); + } + + if (endDate && !startDate) { + where.created_at = LessThanOrEqual(endDate); + } + + if (startDate && endDate) { + where.created_at = Between(startDate, endDate); + } + + return await this.patientsRepository.count({ select: { id: true }, where }); + } +} diff --git a/src/app/http/statistics/use-cases/get-total-referrals-and-referred-patients-percentage.use-case.ts b/src/app/http/statistics/use-cases/get-total-referrals-and-referred-patients-percentage.use-case.ts new file mode 100644 index 0000000..7491320 --- /dev/null +++ b/src/app/http/statistics/use-cases/get-total-referrals-and-referred-patients-percentage.use-case.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@nestjs/common'; + +import type { GetTotalReferralsAndReferredPatientsPercentageQuery } from '../statistics.dtos'; +import { GetTotalPatientsUseCase } from './get-total-patients.use-case'; +import { GetTotalReferralsUseCase } from './get-total-referrals.use-case'; +import { GetTotalReferredPatientsUseCase } from './get-total-referred-patients.use-case'; + +interface GetTotalReferralsAndReferredPatientsPercentageUseCaseRequest { + query: GetTotalReferralsAndReferredPatientsPercentageQuery; +} + +type GetTotalReferralsAndReferredPatientsPercentageUseCaseResponse = Promise<{ + totalReferrals: number; + referredPatientsPercentage: number; +}>; + +@Injectable() +export class GetTotalReferralsAndReferredPatientsPercentageUseCase { + constructor( + private readonly getTotalPatientsUseCase: GetTotalPatientsUseCase, + private readonly getTotalReferralsUseCase: GetTotalReferralsUseCase, + private readonly getTotalReferredPatientsUseCase: GetTotalReferredPatientsUseCase, + ) {} + + async execute({ + query, + }: GetTotalReferralsAndReferredPatientsPercentageUseCaseRequest): GetTotalReferralsAndReferredPatientsPercentageUseCaseResponse { + const { period } = query; + + const [totalPatients, totalReferrals, totalReferredPatients] = + await Promise.all([ + this.getTotalPatientsUseCase.execute({ period }), + this.getTotalReferralsUseCase.execute({ period }), + this.getTotalReferredPatientsUseCase.execute({ period }), + ]); + + const percentage = Number((totalReferredPatients / totalPatients) * 100); + + return { + totalReferrals, + referredPatientsPercentage: Number(percentage.toFixed(2)), + }; + } +} diff --git a/src/app/http/statistics/use-cases/get-total-referrals-by-category.use-case.ts b/src/app/http/statistics/use-cases/get-total-referrals-by-category.use-case.ts index c6e9534..6db9d54 100644 --- a/src/app/http/statistics/use-cases/get-total-referrals-by-category.use-case.ts +++ b/src/app/http/statistics/use-cases/get-total-referrals-by-category.use-case.ts @@ -3,7 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import type { Repository, SelectQueryBuilder } from 'typeorm'; import { Referral } from '@/domain/entities/referral'; -import type { CategoryTotalReferrals } from '@/domain/schemas/statistics'; +import type { TotalReferralsByCategory } from '@/domain/schemas/statistics/responses'; import { UtilsService } from '@/utils/utils.service'; import type { GetTotalReferralsByCategoryQuery } from '../statistics.dtos'; @@ -13,7 +13,7 @@ interface GetTotalReferralsByCategoryUseCaseRequest { } type GetTotalReferralsByCategoryUseCaseResponse = Promise<{ - categories: CategoryTotalReferrals[]; + categories: TotalReferralsByCategory[]; total: number; }>; @@ -63,7 +63,7 @@ export class GetTotalReferralsByCategoryUseCase { ); const [categories, totalResult] = await Promise.all([ - categoryListQuery.getRawMany(), + categoryListQuery.getRawMany(), totalCategoriesQuery.getRawOne<{ total: string }>(), ]); diff --git a/src/app/http/statistics/use-cases/get-total-referrals.use-case.ts b/src/app/http/statistics/use-cases/get-total-referrals.use-case.ts new file mode 100644 index 0000000..83fea04 --- /dev/null +++ b/src/app/http/statistics/use-cases/get-total-referrals.use-case.ts @@ -0,0 +1,83 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { + Between, + type FindOptionsWhere, + LessThanOrEqual, + MoreThanOrEqual, + type Repository, +} from 'typeorm'; + +import { Referral } from '@/domain/entities/referral'; +import type { + ReferralCategory, + ReferralStatus, +} from '@/domain/enums/referrals'; +import type { PatientCondition } from '@/domain/schemas/patient'; +import type { QueryPeriod } from '@/domain/schemas/query'; +import { UtilsService } from '@/utils/utils.service'; + +interface GetTotalReferralsUseCaseRequest { + status?: ReferralStatus; + category?: ReferralCategory; + condition?: PatientCondition; + period?: QueryPeriod; + startDate?: Date; + endDate?: Date; +} + +type GetTotalReferralsUseCaseResponse = Promise; + +@Injectable() +export class GetTotalReferralsUseCase { + constructor( + @InjectRepository(Referral) + private readonly referralsRepository: Repository, + private readonly utilsService: UtilsService, + ) {} + + async execute({ + status, + category, + condition, + period, + startDate, + endDate, + }: GetTotalReferralsUseCaseRequest = {}): GetTotalReferralsUseCaseResponse { + const where: FindOptionsWhere = {}; + + if (period) { + const dateRange = this.utilsService.getDateRangeForPeriod(period); + where.created_at = Between(dateRange.startDate, dateRange.endDate); + } + + if (startDate && !endDate) { + where.created_at = MoreThanOrEqual(startDate); + } + + if (endDate && !startDate) { + where.created_at = LessThanOrEqual(endDate); + } + + if (startDate && endDate) { + where.created_at = Between(startDate, endDate); + } + + if (status) { + where.status = status; + } + + if (category) { + where.category = category; + } + + if (condition) { + where.condition = condition; + } + + return await this.referralsRepository.count({ + select: { id: true }, + where, + }); + } +} diff --git a/src/app/http/statistics/use-cases/get-referred-patients-by-state.use-case.ts b/src/app/http/statistics/use-cases/get-total-referred-patients-by-state.use-case.ts similarity index 80% rename from src/app/http/statistics/use-cases/get-referred-patients-by-state.use-case.ts rename to src/app/http/statistics/use-cases/get-total-referred-patients-by-state.use-case.ts index ff58dd6..f1cc30b 100644 --- a/src/app/http/statistics/use-cases/get-referred-patients-by-state.use-case.ts +++ b/src/app/http/statistics/use-cases/get-total-referred-patients-by-state.use-case.ts @@ -3,22 +3,22 @@ import { InjectRepository } from '@nestjs/typeorm'; import type { Repository, SelectQueryBuilder } from 'typeorm'; import { Patient } from '@/domain/entities/patient'; -import type { StateReferredPatients } from '@/domain/schemas/statistics'; +import type { TotalReferredPatientsByStateSchema } from '@/domain/schemas/statistics/responses'; import { UtilsService } from '@/utils/utils.service'; import type { GetReferredPatientsByStateQuery } from '../statistics.dtos'; -interface GetReferredPatientsByStateUseCaseRequest { +interface GetTotalReferredPatientsByStateUseCaseRequest { query: GetReferredPatientsByStateQuery; } -type GetReferredPatientsByStateUseCaseResponse = Promise<{ - states: StateReferredPatients[]; +type GetTotalReferredPatientsByStateUseCaseResponse = Promise<{ + states: TotalReferredPatientsByStateSchema[]; total: number; }>; @Injectable() -export class GetReferredPatientsByStateUseCase { +export class GetTotalReferredPatientsByStateUseCase { constructor( @InjectRepository(Patient) private readonly patientsRepository: Repository, @@ -27,7 +27,7 @@ export class GetReferredPatientsByStateUseCase { async execute({ query, - }: GetReferredPatientsByStateUseCaseRequest): GetReferredPatientsByStateUseCaseResponse { + }: GetTotalReferredPatientsByStateUseCaseRequest): GetTotalReferredPatientsByStateUseCaseResponse { const { startDate, endDate } = this.utilsService.getDateRangeForPeriod( query.period, ); @@ -67,7 +67,7 @@ export class GetReferredPatientsByStateUseCase { ); const [states, totalResult] = await Promise.all([ - stateListQuery.getRawMany(), + stateListQuery.getRawMany(), totalStatesQuery.getRawOne<{ total: string }>(), ]); diff --git a/src/app/http/statistics/use-cases/get-total-referred-patients.use-case.ts b/src/app/http/statistics/use-cases/get-total-referred-patients.use-case.ts new file mode 100644 index 0000000..d64888f --- /dev/null +++ b/src/app/http/statistics/use-cases/get-total-referred-patients.use-case.ts @@ -0,0 +1,61 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { + Between, + type FindOptionsWhere, + IsNull, + LessThanOrEqual, + MoreThanOrEqual, + Not, + type Repository, +} from 'typeorm'; + +import { Patient } from '@/domain/entities/patient'; +import type { QueryPeriod } from '@/domain/schemas/query'; +import { UtilsService } from '@/utils/utils.service'; + +interface GetTotalReferredPatientsUseCaseRequest { + period?: QueryPeriod; + startDate?: Date; + endDate?: Date; +} + +type GetTotalReferredPatientsUseCaseResponse = Promise; + +@Injectable() +export class GetTotalReferredPatientsUseCase { + constructor( + @InjectRepository(Patient) + private readonly patientsRepository: Repository, + private readonly utilsService: UtilsService, + ) {} + + async execute({ + period, + startDate, + endDate, + }: GetTotalReferredPatientsUseCaseRequest = {}): GetTotalReferredPatientsUseCaseResponse { + const where: FindOptionsWhere = { + referrals: { id: Not(IsNull()) }, + }; + + if (period) { + const dateRange = this.utilsService.getDateRangeForPeriod(period); + where.created_at = Between(dateRange.startDate, dateRange.endDate); + } + + if (startDate && !endDate) { + where.created_at = MoreThanOrEqual(startDate); + } + + if (endDate && !startDate) { + where.created_at = LessThanOrEqual(endDate); + } + + if (startDate && endDate) { + where.created_at = Between(startDate, endDate); + } + + return await this.patientsRepository.count({ where }); + } +} diff --git a/src/domain/enums/statistics.ts b/src/domain/enums/statistics.ts new file mode 100644 index 0000000..d2705d9 --- /dev/null +++ b/src/domain/enums/statistics.ts @@ -0,0 +1,2 @@ +export const PATIENTS_STATISTIC_FIELDS = ['gender', 'city', 'state'] as const; +export type PatientsStatisticField = (typeof PATIENTS_STATISTIC_FIELDS)[number]; diff --git a/src/domain/schemas/statistics/requests.ts b/src/domain/schemas/statistics/requests.ts new file mode 100644 index 0000000..b197cdc --- /dev/null +++ b/src/domain/schemas/statistics/requests.ts @@ -0,0 +1,22 @@ +import { baseQuerySchema } from '../query'; + +// Patients + +export const getTotalPatientsByFieldQuerySchema = baseQuerySchema + .pick({ period: true, limit: true, order: true, withPercentage: true }) + .extend({ order: baseQuerySchema.shape.order.default('DESC') }); + +// Referrals + +export const getTotalReferralsAndReferredPatientsPercentageQuerySchema = + baseQuerySchema.pick({ period: true }); + +export const getReferredPatientsByStateQuerySchema = baseQuerySchema.pick({ + period: true, + limit: true, +}); + +export const getTotalReferralsByCategoryQuerySchema = baseQuerySchema.pick({ + period: true, + limit: true, +}); diff --git a/src/domain/schemas/statistics.ts b/src/domain/schemas/statistics/responses.ts similarity index 56% rename from src/domain/schemas/statistics.ts rename to src/domain/schemas/statistics/responses.ts index 10c0b7c..0d196e3 100644 --- a/src/domain/schemas/statistics.ts +++ b/src/domain/schemas/statistics/responses.ts @@ -1,17 +1,13 @@ import { z } from 'zod'; import { BRAZILIAN_STATES } from '@/constants/brazilian-states'; +import { REFERRAL_CATEGORIES } from '@/domain/enums/referrals'; -import { REFERRAL_CATEGORIES } from '../enums/referrals'; -import { baseResponseSchema } from './base'; -import { GENDERS } from './patient'; -import { baseQuerySchema } from './query'; +import { baseResponseSchema } from '../base'; +import { GENDERS } from '../patient'; // Patients -export const PATIENTS_STATISTIC_FIELDS = ['gender', 'city', 'state'] as const; -export type PatientsStatisticField = (typeof PATIENTS_STATISTIC_FIELDS)[number]; - export const getTotalPatientsByStatusResponseSchema = baseResponseSchema.extend( { data: z.object({ @@ -25,24 +21,15 @@ export type GetTotalPatientsByStatusResponse = z.infer< typeof getTotalPatientsByStatusResponseSchema >; -export const getPatientsByPeriodQuerySchema = baseQuerySchema - .pick({ - period: true, - limit: true, - order: true, - withPercentage: true, - }) - .extend({ order: baseQuerySchema.shape.order.default('DESC') }); - -export const patientsByGenderSchema = z.object({ +export const totalPatientsByGenderSchema = z.object({ gender: z.enum(GENDERS), total: z.number(), }); -export type PatientsByGender = z.infer; +export type TotalPatientsByGender = z.infer; export const getPatientsByGenderResponseSchema = baseResponseSchema.extend({ data: z.object({ - genders: z.array(patientsByGenderSchema), + genders: z.array(totalPatientsByGenderSchema), total: z.number(), }), }); @@ -50,18 +37,18 @@ export type GetPatientsByGenderResponse = z.infer< typeof getPatientsByGenderResponseSchema >; -export const patientsByCitySchema = z +export const totalPatientsByCitySchema = z .object({ city: z.string(), total: z.number(), percentage: z.number(), }) .strict(); -export type PatientsByCity = z.infer; +export type TotalPatientsByCity = z.infer; export const getPatientsByCityResponseSchema = baseResponseSchema.extend({ data: z.object({ - cities: z.array(patientsByCitySchema), + cities: z.array(totalPatientsByCitySchema), total: z.number(), }), }); @@ -71,9 +58,6 @@ export type GetPatientsByCityResponse = z.infer< // Referrals -export const getTotalReferralsAndReferredPatientsPercentageQuerySchema = - baseQuerySchema.pick({ period: true }); - export const getTotalReferralsAndReferredPatientsPercentageResponseSchema = baseResponseSchema.extend({ data: z.object({ @@ -85,21 +69,18 @@ export type GetTotalReferralsAndReferredPatientsPercentageResponse = z.infer< typeof getTotalReferralsAndReferredPatientsPercentageResponseSchema >; -export const getReferredPatientsByStateQuerySchema = baseQuerySchema.pick({ - period: true, - limit: true, -}); - -export const stateReferredPatientsSchema = z.object({ +export const totalReferredPatientsByStateSchema = z.object({ state: z.enum(BRAZILIAN_STATES), total: z.number(), }); -export type StateReferredPatients = z.infer; +export type TotalReferredPatientsByStateSchema = z.infer< + typeof totalReferredPatientsByStateSchema +>; export const getReferredPatientsByStateResponseSchema = baseResponseSchema.extend({ data: z.object({ - states: z.array(stateReferredPatientsSchema), + states: z.array(totalReferredPatientsByStateSchema), total: z.number(), }), }); @@ -107,23 +88,18 @@ export type GetReferredPatientsByStateResponse = z.infer< typeof getReferredPatientsByStateResponseSchema >; -export const getTotalReferralsByCategoryQuerySchema = baseQuerySchema.pick({ - period: true, - limit: true, -}); - -export const categoryTotalReferralsSchema = z.object({ +export const totalReferralsByCategorySchema = z.object({ category: z.enum(REFERRAL_CATEGORIES), total: z.number(), }); -export type CategoryTotalReferrals = z.infer< - typeof categoryTotalReferralsSchema +export type TotalReferralsByCategory = z.infer< + typeof totalReferralsByCategorySchema >; export const getTotalReferralsByCategoryResponseSchema = baseResponseSchema.extend({ data: z.object({ - categories: z.array(categoryTotalReferralsSchema), + categories: z.array(totalReferralsByCategorySchema), total: z.number(), }), }); From af3b1f9deba416c462f33d913bbfc88d28cecbeb Mon Sep 17 00:00:00 2001 From: Juliano Sill Date: Mon, 8 Dec 2025 00:18:12 -0300 Subject: [PATCH 7/8] chore(referrals): remove unused repository file --- src/app/http/referrals/referrals.module.ts | 3 - .../http/referrals/referrals.repository.ts | 106 ------------------ 2 files changed, 109 deletions(-) delete mode 100644 src/app/http/referrals/referrals.repository.ts diff --git a/src/app/http/referrals/referrals.module.ts b/src/app/http/referrals/referrals.module.ts index fe79ab3..c3f96c9 100644 --- a/src/app/http/referrals/referrals.module.ts +++ b/src/app/http/referrals/referrals.module.ts @@ -5,7 +5,6 @@ import { Patient } from '@/domain/entities/patient'; import { Referral } from '@/domain/entities/referral'; import { ReferralsController } from './referrals.controller'; -import { ReferralsRepository } from './referrals.repository'; import { CancelReferralUseCase } from './use-cases/cancel-referral.use-case'; import { CreateReferralUseCase } from './use-cases/create-referrals.use-case'; import { GetReferralsUseCase } from './use-cases/get-referrals.use-case'; @@ -17,11 +16,9 @@ import { GetReferralsUseCase } from './use-cases/get-referrals.use-case'; ], controllers: [ReferralsController], providers: [ - ReferralsRepository, GetReferralsUseCase, CreateReferralUseCase, CancelReferralUseCase, ], - exports: [ReferralsRepository], }) export class ReferralsModule {} diff --git a/src/app/http/referrals/referrals.repository.ts b/src/app/http/referrals/referrals.repository.ts deleted file mode 100644 index ce417f5..0000000 --- a/src/app/http/referrals/referrals.repository.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { - Between, - type FindOptionsWhere, - LessThanOrEqual, - MoreThanOrEqual, - Repository, - type SelectQueryBuilder, -} from 'typeorm'; - -import { Referral } from '@/domain/entities/referral'; -import type { ReferralStatus } from '@/domain/enums/referrals'; -import type { CategoryTotalReferrals } from '@/domain/schemas/statistics'; - -@Injectable() -export class ReferralsRepository { - constructor( - @InjectRepository(Referral) - private readonly referralsRepository: Repository, - ) {} - - public async findById(id: string): Promise { - return await this.referralsRepository.findOne({ where: { id } }); - } - - public async cancel(id: string): Promise { - return await this.referralsRepository.save({ id, status: 'canceled' }); - } - - public async getTotalReferrals( - input: { - status?: ReferralStatus; - startDate?: Date; - endDate?: Date; - } = {}, - ): Promise { - const { status, startDate, endDate } = input; - - const where: FindOptionsWhere = {}; - - if (status) { - where.status = status; - } - - if (startDate && !endDate) { - where.date = MoreThanOrEqual(startDate); - } - - if (endDate && !startDate) { - where.date = LessThanOrEqual(endDate); - } - - if (startDate && endDate) { - where.date = Between(startDate, endDate); - } - - return await this.referralsRepository.count({ where }); - } - - public async getTotalReferralsByCategory( - input: { startDate?: Date; endDate?: Date; limit?: number } = {}, - ): Promise<{ categories: CategoryTotalReferrals[]; total: number }> { - const { startDate, endDate, limit = 10 } = input; - - function getQueryBuilderWithFilters( - queryBuilder: SelectQueryBuilder, - ) { - if (startDate && endDate) { - queryBuilder.andWhere('referral.date BETWEEN :start AND :end', { - start: startDate, - end: endDate, - }); - } - - return queryBuilder; - } - - const createQueryBuilder = (): SelectQueryBuilder => { - return this.referralsRepository.createQueryBuilder('referral'); - }; - - const categoryListQuery = getQueryBuilderWithFilters( - createQueryBuilder() - .select('referral.category', 'category') - .addSelect('COUNT(referral.id)', 'total') - .groupBy('referral.category') - .orderBy('COUNT(referral.id)', 'DESC') - .limit(limit), - ); - - const totalCategoriesQuery = getQueryBuilderWithFilters( - createQueryBuilder().select('COUNT(DISTINCT referral.category)', 'total'), - ); - - const [categories, totalResult] = await Promise.all([ - categoryListQuery.getRawMany(), - totalCategoriesQuery.getRawOne<{ total: string }>(), - ]); - - return { - categories, - total: Number(totalResult?.total || 0), - }; - } -} From 935ed0ce6c53bcc7701a9e6307a796009f5ac307 Mon Sep 17 00:00:00 2001 From: Juliano Sill Date: Mon, 8 Dec 2025 00:18:43 -0300 Subject: [PATCH 8/8] chore(patients): remove unused methods from repository --- src/app/http/patients/patients.repository.ts | 133 +------------------ 1 file changed, 2 insertions(+), 131 deletions(-) diff --git a/src/app/http/patients/patients.repository.ts b/src/app/http/patients/patients.repository.ts index 0f61af1..7d1bf0b 100644 --- a/src/app/http/patients/patients.repository.ts +++ b/src/app/http/patients/patients.repository.ts @@ -1,24 +1,10 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { - Between, - type FindOptionsWhere, - IsNull, - LessThanOrEqual, - MoreThanOrEqual, - Not, - Repository, -} from 'typeorm'; +import { Repository } from 'typeorm'; import { Patient } from '@/domain/entities/patient'; -import type { - PatientOrderBy, - PatientStatus, - PatientType, -} from '@/domain/schemas/patient'; -import type { PatientsStatisticField } from '@/domain/schemas/statistics'; +import type { PatientOrderBy, PatientType } from '@/domain/schemas/patient'; -import type { GetPatientsByPeriodQuery } from '../statistics/statistics.dtos'; import { CreatePatientDto, FindAllPatientQueryDto } from './patients.dtos'; @Injectable() @@ -173,119 +159,4 @@ export class PatientsRepository { public async deactivate(id: string): Promise { return this.patientsRepository.save({ id, status: 'inactive' }); } - - public async getTotalPatientsByStatus(): Promise<{ - total: number; - active: number; - inactive: number; - }> { - const queryBuilder = await this.patientsRepository - .createQueryBuilder('patient') - .select('COUNT(patient.id)', 'total') - .where('patient.status != :status', { status: 'pending' }) - .addSelect( - `SUM(CASE WHEN patient.status = 'active' THEN 1 ELSE 0 END)`, - 'active', - ) - .addSelect( - `SUM(CASE WHEN patient.status = 'inactive' THEN 1 ELSE 0 END)`, - 'inactive', - ) - .getRawOne<{ total: string; active: string; inactive: string }>(); - - return { - total: Number(queryBuilder?.total ?? 0), - active: Number(queryBuilder?.active ?? 0), - inactive: Number(queryBuilder?.inactive ?? 0), - }; - } - - public async getPatientsStatisticsByPeriod( - field: PatientsStatisticField, - startDate: Date, - endDate: Date, - query: GetPatientsByPeriodQuery, - ): Promise<{ items: T[]; total: number }> { - const totalQuery = this.patientsRepository - .createQueryBuilder('patient') - .select(`COUNT(DISTINCT patient.${field})`, 'total') - .where('patient.created_at BETWEEN :start AND :end', { - start: startDate, - end: endDate, - }); - - const totalResult = await totalQuery.getRawOne<{ total: string }>(); - const total = Number(totalResult?.total ?? 0); - - const queryBuilder = this.patientsRepository - .createQueryBuilder('patient') - .select(`patient.${field}`, field) - .addSelect('COUNT(patient.id)', 'total') - .where('patient.created_at BETWEEN :start AND :end', { - start: startDate, - end: endDate, - }) - .groupBy(`patient.${field}`) - .orderBy('total', query.order) - .limit(query.limit); - - if (query.withPercentage) { - queryBuilder.addSelect( - 'ROUND((COUNT(*) * 100.0 / SUM(COUNT(*)) OVER()), 1)', - 'percentage', - ); - } - - const items = await queryBuilder.getRawMany(); - - return { items, total }; - } - - public async getTotalPatients( - input: { status?: PatientStatus; startDate?: Date; endDate?: Date } = {}, - ): Promise { - const { status, startDate, endDate } = input; - - const where: FindOptionsWhere = { - status: status ?? Not('pending'), - }; - - if (startDate && !endDate) { - where.created_at = MoreThanOrEqual(startDate); - } - - if (endDate && !startDate) { - where.created_at = LessThanOrEqual(endDate); - } - - if (startDate && endDate) { - where.created_at = Between(startDate, endDate); - } - - return await this.patientsRepository.count({ where }); - } - - public async getTotalReferredPatients( - input: { startDate?: Date; endDate?: Date } = {}, - ): Promise { - const { startDate, endDate } = input; - - const where: FindOptionsWhere = { - referrals: { id: Not(IsNull()) }, - }; - - if (startDate && !endDate) { - where.created_at = MoreThanOrEqual(startDate); - } - - if (endDate && !startDate) { - where.created_at = LessThanOrEqual(endDate); - } - - if (startDate && endDate) { - where.created_at = Between(startDate, endDate); - } - - return await this.patientsRepository.count({ where }); - } }