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
77 changes: 62 additions & 15 deletions src/app/http/patients/patients.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
MoreThanOrEqual,
Not,
Repository,
type SelectQueryBuilder,
} from 'typeorm';

import { Patient } from '@/domain/entities/patient';
Expand All @@ -16,7 +17,10 @@ import type {
PatientStatus,
PatientType,
} from '@/domain/schemas/patient';
import type { PatientsStatisticField } from '@/domain/schemas/statistics';
import type {
PatientsStatisticField,
StateReferredPatients,
} from '@/domain/schemas/statistics';

import type { GetPatientsByPeriodQuery } from '../statistics/statistics.dtos';
import { CreatePatientDto, FindAllPatientQueryDto } from './patients.dtos';
Expand Down Expand Up @@ -179,7 +183,7 @@ export class PatientsRepository {
active: number;
inactive: number;
}> {
const raw = await this.patientsRepository
const queryBuilder = await this.patientsRepository
.createQueryBuilder('patient')
.select('COUNT(patient.id)', 'total')
.where('patient.status != :status', { status: 'pending' })
Expand All @@ -194,9 +198,9 @@ export class PatientsRepository {
.getRawOne<{ total: string; active: string; inactive: string }>();

return {
total: Number(raw?.total ?? 0),
active: Number(raw?.active ?? 0),
inactive: Number(raw?.inactive ?? 0),
total: Number(queryBuilder?.total ?? 0),
active: Number(queryBuilder?.active ?? 0),
inactive: Number(queryBuilder?.inactive ?? 0),
};
}

Expand All @@ -220,7 +224,7 @@ export class PatientsRepository {
const queryBuilder = this.patientsRepository
.createQueryBuilder('patient')
.select(`patient.${field}`, field)
.addSelect('COUNT(*)', 'total')
.addSelect('COUNT(patient.id)', 'total')
.where('patient.created_at BETWEEN :start AND :end', {
start: startDate,
end: endDate,
Expand All @@ -242,11 +246,7 @@ export class PatientsRepository {
}

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

Expand All @@ -270,10 +270,7 @@ export class PatientsRepository {
}

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

Expand All @@ -295,4 +292,54 @@ 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<Patient> => {
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<Patient>,
) {
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<StateReferredPatients>(),
totalStatesQuery.getRawOne<{ total: string }>(),
]);

return {
states,
total: Number(totalResult?.total || 0),
};
}
}
23 changes: 22 additions & 1 deletion src/app/http/statistics/statistics.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import { Roles } from '@/common/decorators/roles.decorator';
import type {
GetPatientsByCityResponse,
GetPatientsByGenderResponse,
GetReferredPatientsByStateResponse,
GetTotalReferralsAndReferredPatientsPercentageResponse,
PatientsByCity,
PatientsByGender,
} from '@/domain/schemas/statistics';

import {
GetPatientsByPeriodQuery,
GetReferredPatientsByStateQuery,
GetTotalReferralsAndReferredPatientsPercentageQuery,
} from './statistics.dtos';
import { StatisticsService } from './statistics.service';
Expand Down Expand Up @@ -72,7 +74,7 @@ export class StatisticsController {
};
}

@Get('referrals/total')
@Get('referrals-total')
@Roles(['manager', 'nurse'])
@ApiOperation({ summary: 'Estatísticas do total de encaminhamentos' })
async getTotalReferralsAndReferredPatientsPercentage(
Expand All @@ -90,4 +92,23 @@ export class StatisticsController {
data,
};
}

@Get('referrals-by-state')
@Roles(['manager', 'nurse'])
@ApiOperation({
summary: 'Lista com o total de pacientes encaminhados por estado',
})
async getReferredPatientsByState(
@Query() query: GetReferredPatientsByStateQuery,
): Promise<GetReferredPatientsByStateResponse> {
const { states, total } =
await this.statisticsService.getReferredPatientsByState(query);

return {
success: true,
message:
'Lista com o total de pacientes encaminhados por estado retornada com sucesso.',
data: { states, total },
};
}
}
9 changes: 7 additions & 2 deletions src/app/http/statistics/statistics.dtos.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { createZodDto } from 'nestjs-zod';

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

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

export class GetTotalReferralsAndReferredPatientsPercentageQuery extends createZodDto(
getTotalReferralsAndReferredPatientsPercentageQuerySchema,
) {}

export class GetReferredPatientsByStateQuery extends createZodDto(
getReferredPatientsByStateQuerySchema,
) {}
15 changes: 15 additions & 0 deletions src/app/http/statistics/statistics.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { Injectable } from '@nestjs/common';
import type {
GetTotalPatientsByStatusResponse,
PatientsStatisticField,
StateReferredPatients,
} 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,
GetReferredPatientsByStateQuery,
GetTotalReferralsAndReferredPatientsPercentageQuery,
} from './statistics.dtos';

Expand Down Expand Up @@ -66,4 +68,17 @@ export class StatisticsService {
referredPatientsPercentage: Number(percentage.toFixed(2)),
};
}

async getReferredPatientsByState(
query: GetReferredPatientsByStateQuery,
): Promise<{ states: StateReferredPatients[]; total: number }> {
const { startDate, endDate } = this.utilsService.getDateRangeForPeriod(
query.period,
);

return await this.patientsRepository.getReferredPatientsByState({
startDate,
endDate,
});
}
}
2 changes: 1 addition & 1 deletion src/constants/brazilian-states.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ export const BRAZILIAN_STATES = [
'SE',
'TO',
] as const;
export type BrazilianStateType = (typeof BRAZILIAN_STATES)[number];
export type BrazilianState = (typeof BRAZILIAN_STATES)[number];
4 changes: 2 additions & 2 deletions src/domain/entities/patient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {

import {
BRAZILIAN_STATES,
type BrazilianStateType,
type BrazilianState,
} from '@/constants/brazilian-states';
import { User } from '@/domain/entities/user';

Expand Down Expand Up @@ -48,7 +48,7 @@ export class Patient implements PatientSchema {
cpf: string;

@Column({ type: 'enum', enum: BRAZILIAN_STATES })
state: BrazilianStateType;
state: BrazilianState;

@Column({ type: 'varchar', length: 50 })
city: string;
Expand Down
12 changes: 6 additions & 6 deletions src/domain/schemas/query.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { z } from 'zod';

export const ORDER = ['ASC', 'DESC'] as const;
export type OrderType = (typeof ORDER)[number];
export const QUERY_ORDER = ['ASC', 'DESC'] as const;
export type QueryOrder = (typeof QUERY_ORDER)[number];

export const PERIOD = [
export const QUERY_PERIOD = [
'today',
'last-year',
'last-month',
'last-week',
] as const;
export type PeriodType = (typeof PERIOD)[number];
export type QueryPeriod = (typeof QUERY_PERIOD)[number];

export const baseQuerySchema = z.object({
search: z.string().optional(),
order: z.enum(ORDER).optional(),
period: z.enum(PERIOD).optional().default('today'),
order: z.enum(QUERY_ORDER).optional(),
period: z.enum(QUERY_PERIOD).optional().default('today'),
page: z.coerce.number().min(1).optional().default(1),
perPage: z.coerce.number().min(1).max(50).optional().default(10),
limit: z.coerce.number().min(1).optional().default(10),
Expand Down
33 changes: 28 additions & 5 deletions src/domain/schemas/statistics.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { z } from 'zod';

import { BRAZILIAN_STATES } from '@/constants/brazilian-states';

import { baseResponseSchema } from './base';
import { GENDERS } from './patient';
import { baseQuerySchema } from './query';

// Patients

export const PATIENTS_STATISTIC_FIELDS = ['gender', 'city'] as const;
export const PATIENTS_STATISTIC_FIELDS = ['gender', 'city', 'state'] as const;
export type PatientsStatisticField = (typeof PATIENTS_STATISTIC_FIELDS)[number];

export const getTotalPatientsByStatusResponseSchema = baseResponseSchema.extend(
Expand All @@ -22,7 +24,7 @@ export type GetTotalPatientsByStatusResponse = z.infer<
typeof getTotalPatientsByStatusResponseSchema
>;

export const getPatientsByPeriodSchema = baseQuerySchema
export const getPatientsByPeriodQuerySchema = baseQuerySchema
.pick({
period: true,
limit: true,
Expand Down Expand Up @@ -66,10 +68,10 @@ export type GetPatientsByCityResponse = z.infer<
typeof getPatientsByCityResponseSchema
>;

// Referrals

export const getTotalReferralsAndReferredPatientsPercentageQuerySchema =
baseQuerySchema.pick({
period: true,
});
baseQuerySchema.pick({ period: true });

export const getTotalReferralsAndReferredPatientsPercentageResponseSchema =
baseResponseSchema.extend({
Expand All @@ -81,3 +83,24 @@ export const getTotalReferralsAndReferredPatientsPercentageResponseSchema =
export type GetTotalReferralsAndReferredPatientsPercentageResponse = z.infer<
typeof getTotalReferralsAndReferredPatientsPercentageResponseSchema
>;

export const getReferredPatientsByStateQuerySchema = baseQuerySchema.pick({
period: true,
});

export const stateReferredPatientsSchema = z.object({
state: z.enum(BRAZILIAN_STATES),
total: z.number(),
});
export type StateReferredPatients = z.infer<typeof stateReferredPatientsSchema>;

export const getReferredPatientsByStateResponseSchema =
baseResponseSchema.extend({
data: z.object({
states: z.array(stateReferredPatientsSchema),
total: z.number(),
}),
});
export type GetReferredPatientsByStateResponse = z.infer<
typeof getReferredPatientsByStateResponseSchema
>;
4 changes: 2 additions & 2 deletions src/utils/utils.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from 'date-fns';
import { type CookieOptions, Response } from 'express';

import type { PeriodType } from '@/domain/schemas/query';
import type { QueryPeriod } from '@/domain/schemas/query';
import { EnvService } from '@/env/env.service';

type SetCookieOptions = CookieOptions & {
Expand Down Expand Up @@ -56,7 +56,7 @@ export class UtilsService {
});
}

getDateRangeForPeriod(period: PeriodType): {
getDateRangeForPeriod(period: QueryPeriod): {
startDate: Date;
endDate: Date;
} {
Expand Down