Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 80 additions & 13 deletions src/app/http/patients/patients.repository.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import {
Between,
type FindOptionsWhere,
IsNull,
LessThanOrEqual,
MoreThanOrEqual,
Not,
Repository,
} from 'typeorm';

import { Patient } from '@/domain/entities/patient';
import type { PatientOrderByType, PatientType } from '@/domain/schemas/patient';
import type {
GetPatientsTotalResponseSchema,
PatientsStatisticFieldType,
} from '@/domain/schemas/statistics';
PatientOrderBy,
PatientStatus,
PatientType,
} from '@/domain/schemas/patient';
import type { PatientsStatisticField } from '@/domain/schemas/statistics';

import type { GetPatientsByPeriodDto } from '../statistics/statistics.dtos';
import type { GetPatientsByPeriodQuery } from '../statistics/statistics.dtos';
import { CreatePatientDto, FindAllPatientQueryDto } from './patients.dtos';

@Injectable()
Expand All @@ -35,7 +44,7 @@ export class PatientsRepository {
all,
} = filters;

const ORDER_BY: Record<PatientOrderByType, string> = {
const ORDER_BY: Record<PatientOrderBy, string> = {
name: 'user.name',
email: 'user.email',
status: 'patient.status',
Expand Down Expand Up @@ -165,12 +174,15 @@ export class PatientsRepository {
return this.patientsRepository.save({ id, status: 'inactive' });
}

public async getPatientsTotal(): Promise<
GetPatientsTotalResponseSchema['data']
> {
public async getTotalPatientsByStatus(): Promise<{
total: number;
active: number;
inactive: number;
}> {
const raw = await this.patientsRepository
.createQueryBuilder('patient')
.select('COUNT(*)', 'total')
.select('COUNT(patient.id)', 'total')
.where('patient.status != :status', { status: 'pending' })
.addSelect(
`SUM(CASE WHEN patient.status = 'active' THEN 1 ELSE 0 END)`,
'active',
Expand All @@ -189,10 +201,10 @@ export class PatientsRepository {
}

public async getPatientsStatisticsByPeriod<T>(
field: PatientsStatisticFieldType,
field: PatientsStatisticField,
startDate: Date,
endDate: Date,
query: GetPatientsByPeriodDto,
query: GetPatientsByPeriodQuery,
): Promise<{ items: T[]; total: number }> {
const totalQuery = this.patientsRepository
.createQueryBuilder('patient')
Expand Down Expand Up @@ -228,4 +240,59 @@ export class PatientsRepository {

return { items, total };
}

public async getTotalPatients(
input: {
status?: PatientStatus;
startDate?: Date;
endDate?: Date;
} = {},
): Promise<number> {
const { status, startDate, endDate } = input;

const where: FindOptionsWhere<Patient> = {
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<number> {
const { startDate, endDate } = input;

const where: FindOptionsWhere<Patient> = {
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 });
}
}
2 changes: 1 addition & 1 deletion src/app/http/referrals/referrals.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ import { ReferralsService } from './referrals.service';
imports: [PatientsModule, TypeOrmModule.forFeature([Referral])],
controllers: [ReferralsController],
providers: [ReferralsService, ReferralsRepository],
exports: [ReferralsRepository],
exports: [ReferralsService, ReferralsRepository],
})
export class ReferralsModule {}
42 changes: 39 additions & 3 deletions src/app/http/referrals/referrals.repository.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import {
Between,
type FindOptionsWhere,
LessThanOrEqual,
MoreThanOrEqual,
Repository,
} from 'typeorm';

import { Referral } from '@/domain/entities/referral';
import { ReferralStatusType } from '@/domain/schemas/referral';
import { ReferralStatus } from '@/domain/schemas/referral';

import { CreateReferralDto } from './referrals.dtos';

Expand All @@ -20,7 +26,7 @@ export class ReferralsRepository {

public async create(
createReferralDto: CreateReferralDto & {
status: ReferralStatusType;
status: ReferralStatus;
referred_by: string;
},
): Promise<Referral> {
Expand All @@ -31,4 +37,34 @@ export class ReferralsRepository {
public async cancel(id: string): Promise<Referral> {
return await this.referralsRepository.save({ id, status: 'canceled' });
}

public async getTotalReferrals(
input: {
status?: ReferralStatus;
startDate?: Date;
endDate?: Date;
} = {},
): Promise<number> {
const { status, startDate, endDate } = input;

const where: FindOptionsWhere<Referral> = {};

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 });
}
}
37 changes: 30 additions & 7 deletions src/app/http/statistics/statistics.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ import { Roles } from '@/common/decorators/roles.decorator';
import type {
GetPatientsByCityResponse,
GetPatientsByGenderResponse,
PatientsByCityType,
PatientsByGenderType,
GetTotalReferralsAndReferredPatientsPercentageResponse,
PatientsByCity,
PatientsByGender,
} from '@/domain/schemas/statistics';

import { GetPatientsByPeriodDto } from './statistics.dtos';
import {
GetPatientsByPeriodQuery,
GetTotalReferralsAndReferredPatientsPercentageQuery,
} from './statistics.dtos';
import { StatisticsService } from './statistics.service';

@ApiTags('Estatísticas')
Expand All @@ -34,10 +38,10 @@ export class StatisticsController {
@Roles(['manager', 'nurse'])
@ApiOperation({ summary: 'Estatísticas de pacientes por gênero' })
async getPatientsByGender(
@Query() query: GetPatientsByPeriodDto,
@Query() query: GetPatientsByPeriodQuery,
): Promise<GetPatientsByGenderResponse> {
const { items: genders, total } =
await this.statisticsService.getPatientsByPeriod<PatientsByGenderType>(
await this.statisticsService.getPatientsByPeriod<PatientsByGender>(
'gender',
query,
);
Expand All @@ -53,10 +57,10 @@ export class StatisticsController {
@Roles(['manager', 'nurse'])
@ApiOperation({ summary: 'Estatísticas de pacientes por cidade' })
async getPatientsByCity(
@Query() query: GetPatientsByPeriodDto,
@Query() query: GetPatientsByPeriodQuery,
): Promise<GetPatientsByCityResponse> {
const { items: cities, total } =
await this.statisticsService.getPatientsByPeriod<PatientsByCityType>(
await this.statisticsService.getPatientsByPeriod<PatientsByCity>(
'city',
query,
);
Expand All @@ -67,4 +71,23 @@ export class StatisticsController {
data: { cities, total },
};
}

@Get('referrals/total')
@Roles(['manager', 'nurse'])
@ApiOperation({ summary: 'Estatísticas do total de encaminhamentos' })
async getTotalReferralsAndReferredPatientsPercentage(
@Query() query: GetTotalReferralsAndReferredPatientsPercentageQuery,
): Promise<GetTotalReferralsAndReferredPatientsPercentageResponse> {
const data =
await this.statisticsService.getTotalReferralsAndReferredPatientsPercentage(
query,
);

return {
success: true,
message:
'Estatísticas com total de encaminhamentos retornada com sucesso.',
data,
};
}
}
11 changes: 9 additions & 2 deletions src/app/http/statistics/statistics.dtos.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { createZodDto } from 'nestjs-zod';

import { getPatientsByPeriodSchema } from '@/domain/schemas/statistics';
import {
getPatientsByPeriodSchema,
getTotalReferralsAndReferredPatientsPercentageQuerySchema,
} from '@/domain/schemas/statistics';

export class GetPatientsByPeriodDto extends createZodDto(
export class GetPatientsByPeriodQuery extends createZodDto(
getPatientsByPeriodSchema,
) {}

export class GetTotalReferralsAndReferredPatientsPercentageQuery extends createZodDto(
getTotalReferralsAndReferredPatientsPercentageQuerySchema,
) {}
3 changes: 2 additions & 1 deletion src/app/http/statistics/statistics.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { Module } from '@nestjs/common';
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';

@Module({
imports: [PatientsModule, UtilsModule],
imports: [PatientsModule, UtilsModule, ReferralsModule],
controllers: [StatisticsController],
providers: [StatisticsService],
})
Expand Down
45 changes: 38 additions & 7 deletions src/app/http/statistics/statistics.service.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
import { Injectable } from '@nestjs/common';

import type {
GetPatientsTotalResponseSchema,
PatientsStatisticFieldType,
GetTotalPatientsByStatusResponse,
PatientsStatisticField,
} from '@/domain/schemas/statistics';
import { UtilsService } from '@/utils/utils.service';

import { PatientsRepository } from '../patients/patients.repository';
import type { GetPatientsByPeriodDto } from './statistics.dtos';
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<GetPatientsTotalResponseSchema['data']> {
return await this.patientsRepository.getPatientsTotal();
async getPatientsTotal(): Promise<GetTotalPatientsByStatusResponse['data']> {
return await this.patientsRepository.getTotalPatientsByStatus();
}

async getPatientsByPeriod<T>(
filter: PatientsStatisticFieldType,
query: GetPatientsByPeriodDto,
filter: PatientsStatisticField,
query: GetPatientsByPeriodQuery,
): Promise<{ items: T[]; total: number }> {
const { startDate, endDate } = this.utilsService.getDateRangeForPeriod(
query.period,
Expand All @@ -35,4 +40,30 @@ export class StatisticsService {
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)),
};
}
}
Loading