Spaces:
Running
Running
Commit Β·
9037c9f
1
Parent(s): b5a54b6
fix(services): add facility validation and clean up code
Browse filesadd checks to verify patients are linked to a facility across multiple services to prevent runtime null errors
standardize code formatting for imports, indentation, and line breaks across all service files
fix null handling in facilities service to safely access facility data and return accurate linked status
add missing trailing newline in facilities service file
src/anc-visit/anc-visit.service.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
| 1 |
-
import {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
import { CreateAncVisitDto } from "./dto/create-anc-visit.dto";
|
| 3 |
import { PrismaService } from "src/database/prisma.service";
|
| 4 |
import { calculateRisk } from "./utils/calculate-risk";
|
|
@@ -7,190 +11,150 @@ import { calculateRisk } from "./utils/calculate-risk";
|
|
| 7 |
export class AncVisitService {
|
| 8 |
constructor(private readonly prisma: PrismaService) {}
|
| 9 |
|
| 10 |
-
async create(
|
| 11 |
-
|
| 12 |
-
healthcareWorkerId: string,
|
| 13 |
-
) {
|
| 14 |
-
const healthcareWorker =
|
| 15 |
-
await this.prisma.healthcareWorker.findUnique({
|
| 16 |
where: {
|
| 17 |
id: healthcareWorkerId,
|
| 18 |
},
|
| 19 |
});
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
);
|
| 25 |
-
}
|
| 26 |
|
| 27 |
-
|
| 28 |
-
await this.prisma.patient.findFirst({
|
| 29 |
where: {
|
| 30 |
id: dto.patientId,
|
| 31 |
-
facilityId:
|
| 32 |
-
healthcareWorker.facilityId,
|
| 33 |
},
|
| 34 |
});
|
| 35 |
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
);
|
| 40 |
-
}
|
| 41 |
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
dto.
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
await this.prisma.aNCVisit.create({
|
| 53 |
data: {
|
| 54 |
patientId: patient.id,
|
| 55 |
-
facilityId:
|
| 56 |
-
patient.facilityId,
|
| 57 |
healthcareWorkerId,
|
| 58 |
|
| 59 |
-
visitDate: dto.visitDate
|
| 60 |
-
? new Date(dto.visitDate)
|
| 61 |
-
: new Date(),
|
| 62 |
|
| 63 |
-
gestationalAge:
|
| 64 |
-
dto.gestationalAge,
|
| 65 |
weight: dto.weight,
|
| 66 |
|
| 67 |
-
bloodPressureSystolic:
|
| 68 |
-
dto.bloodPressureSystolic,
|
| 69 |
|
| 70 |
-
bloodPressureDiastolic:
|
| 71 |
-
dto.bloodPressureDiastolic,
|
| 72 |
|
| 73 |
-
urineProtein:
|
| 74 |
-
dto.urineProtein,
|
| 75 |
|
| 76 |
-
fundalHeight:
|
| 77 |
-
dto.fundalHeight,
|
| 78 |
|
| 79 |
-
fetalHeartRate:
|
| 80 |
-
dto.fetalHeartRate,
|
| 81 |
|
| 82 |
-
fetalMovement:
|
| 83 |
-
dto.fetalMovement,
|
| 84 |
|
| 85 |
-
haemoglobin:
|
| 86 |
-
dto.haemoglobin,
|
| 87 |
|
| 88 |
-
temperature:
|
| 89 |
-
dto.temperature,
|
| 90 |
|
| 91 |
-
remarks:
|
| 92 |
-
dto.remarks,
|
| 93 |
|
| 94 |
-
nextAppointmentDate:
|
| 95 |
-
dto.nextAppointmentDate
|
| 96 |
-
|
| 97 |
-
dto.nextAppointmentDate,
|
| 98 |
-
)
|
| 99 |
-
: null,
|
| 100 |
|
| 101 |
riskLevel,
|
| 102 |
},
|
| 103 |
});
|
| 104 |
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
where: {
|
| 110 |
-
id: patient.id,
|
| 111 |
-
},
|
| 112 |
-
data: {
|
| 113 |
-
latestRiskLevel: riskLevel,
|
| 114 |
-
lastVisitDate:
|
| 115 |
-
visit.visitDate,
|
| 116 |
-
},
|
| 117 |
-
});
|
| 118 |
-
|
| 119 |
-
/**
|
| 120 |
-
* Create appointment
|
| 121 |
-
*/
|
| 122 |
-
if (dto.nextAppointmentDate) {
|
| 123 |
-
await this.prisma.appointment.create({
|
| 124 |
-
data: {
|
| 125 |
-
userId: patient.userId,
|
| 126 |
-
patientId: patient.id,
|
| 127 |
-
facilityId:
|
| 128 |
-
patient.facilityId,
|
| 129 |
-
title:
|
| 130 |
-
"Antenatal Care Visit",
|
| 131 |
-
type: "ANC",
|
| 132 |
-
dateTime: new Date(
|
| 133 |
-
dto.nextAppointmentDate,
|
| 134 |
-
),
|
| 135 |
-
},
|
| 136 |
-
});
|
| 137 |
-
}
|
| 138 |
-
|
| 139 |
-
/**
|
| 140 |
-
* Close old unresolved alerts
|
| 141 |
-
* if patient is no longer high risk.
|
| 142 |
-
*/
|
| 143 |
-
if (
|
| 144 |
-
riskLevel === "LOW" ||
|
| 145 |
-
riskLevel === "MODERATE"
|
| 146 |
-
) {
|
| 147 |
-
await this.prisma.alert.updateMany({
|
| 148 |
where: {
|
| 149 |
-
|
| 150 |
-
isResolved: false,
|
| 151 |
},
|
| 152 |
data: {
|
| 153 |
-
|
| 154 |
-
|
| 155 |
},
|
| 156 |
});
|
| 157 |
-
}
|
| 158 |
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
where: {
|
| 169 |
patientId: patient.id,
|
| 170 |
isResolved: false,
|
| 171 |
-
|
|
|
|
|
|
|
|
|
|
| 172 |
},
|
| 173 |
});
|
|
|
|
| 174 |
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
|
|
|
| 182 |
type: riskLevel,
|
| 183 |
-
message:
|
| 184 |
-
riskLevel === "EMERGENCY"
|
| 185 |
-
? "Severely elevated blood pressure detected."
|
| 186 |
-
: "High-risk pregnancy detected during ANC visit.",
|
| 187 |
},
|
| 188 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
}
|
| 190 |
-
}
|
| 191 |
|
| 192 |
-
|
| 193 |
-
}
|
| 194 |
|
| 195 |
async getPatientVisits(patientId: string) {
|
| 196 |
const patient = await this.prisma.patient.findUnique({
|
|
@@ -298,24 +262,18 @@ async create(
|
|
| 298 |
return defaulters.sort((a, b) => b.daysOverdue - a.daysOverdue);
|
| 299 |
}
|
| 300 |
|
| 301 |
-
async getPatientSummary(
|
| 302 |
-
|
| 303 |
-
) {
|
| 304 |
-
const patient =
|
| 305 |
-
await this.prisma.patient.findUnique({
|
| 306 |
where: {
|
| 307 |
id: patientId,
|
| 308 |
},
|
| 309 |
});
|
| 310 |
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
);
|
| 315 |
-
}
|
| 316 |
|
| 317 |
-
|
| 318 |
-
await this.prisma.aNCVisit.findMany({
|
| 319 |
where: {
|
| 320 |
patientId,
|
| 321 |
},
|
|
@@ -324,76 +282,57 @@ async create(
|
|
| 324 |
},
|
| 325 |
});
|
| 326 |
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
|
| 339 |
-
|
| 340 |
-
visits[visits.length - 1];
|
| 341 |
|
| 342 |
-
|
| 343 |
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
(v) =>
|
| 347 |
-
v.nextAppointmentDate &&
|
| 348 |
-
v.nextAppointmentDate < today,
|
| 349 |
).length;
|
| 350 |
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
) {
|
| 370 |
-
bpTrend = "RISING";
|
| 371 |
-
} else if (
|
| 372 |
-
last.bloodPressureSystolic! <
|
| 373 |
-
previous.bloodPressureSystolic!
|
| 374 |
-
) {
|
| 375 |
-
bpTrend = "FALLING";
|
| 376 |
}
|
| 377 |
-
}
|
| 378 |
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
latestVisit.
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
.bloodPressureSystolic,
|
| 393 |
-
diastolic:
|
| 394 |
-
latestVisit
|
| 395 |
-
.bloodPressureDiastolic,
|
| 396 |
-
},
|
| 397 |
-
};
|
| 398 |
-
}
|
| 399 |
}
|
|
|
|
| 1 |
+
import {
|
| 2 |
+
BadRequestException,
|
| 3 |
+
Injectable,
|
| 4 |
+
NotFoundException,
|
| 5 |
+
} from "@nestjs/common";
|
| 6 |
import { CreateAncVisitDto } from "./dto/create-anc-visit.dto";
|
| 7 |
import { PrismaService } from "src/database/prisma.service";
|
| 8 |
import { calculateRisk } from "./utils/calculate-risk";
|
|
|
|
| 11 |
export class AncVisitService {
|
| 12 |
constructor(private readonly prisma: PrismaService) {}
|
| 13 |
|
| 14 |
+
async create(dto: CreateAncVisitDto, healthcareWorkerId: string) {
|
| 15 |
+
const healthcareWorker = await this.prisma.healthcareWorker.findUnique({
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
where: {
|
| 17 |
id: healthcareWorkerId,
|
| 18 |
},
|
| 19 |
});
|
| 20 |
|
| 21 |
+
if (!healthcareWorker) {
|
| 22 |
+
throw new NotFoundException("Healthcare worker not found");
|
| 23 |
+
}
|
|
|
|
|
|
|
| 24 |
|
| 25 |
+
const patient = await this.prisma.patient.findFirst({
|
|
|
|
| 26 |
where: {
|
| 27 |
id: dto.patientId,
|
| 28 |
+
facilityId: healthcareWorker.facilityId,
|
|
|
|
| 29 |
},
|
| 30 |
});
|
| 31 |
|
| 32 |
+
if (!patient) {
|
| 33 |
+
throw new NotFoundException("Patient not found");
|
| 34 |
+
}
|
|
|
|
|
|
|
| 35 |
|
| 36 |
+
if (!patient.facilityId) {
|
| 37 |
+
throw new BadRequestException("Patient is not linked to a facility");
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
const riskLevel = calculateRisk({
|
| 41 |
+
systolic: dto.bloodPressureSystolic,
|
| 42 |
+
diastolic: dto.bloodPressureDiastolic,
|
| 43 |
+
urineProtein: dto.urineProtein,
|
| 44 |
+
});
|
| 45 |
+
|
| 46 |
+
const visit = await this.prisma.aNCVisit.create({
|
| 47 |
data: {
|
| 48 |
patientId: patient.id,
|
| 49 |
+
facilityId: patient.facilityId,
|
|
|
|
| 50 |
healthcareWorkerId,
|
| 51 |
|
| 52 |
+
visitDate: dto.visitDate ? new Date(dto.visitDate) : new Date(),
|
|
|
|
|
|
|
| 53 |
|
| 54 |
+
gestationalAge: dto.gestationalAge,
|
|
|
|
| 55 |
weight: dto.weight,
|
| 56 |
|
| 57 |
+
bloodPressureSystolic: dto.bloodPressureSystolic,
|
|
|
|
| 58 |
|
| 59 |
+
bloodPressureDiastolic: dto.bloodPressureDiastolic,
|
|
|
|
| 60 |
|
| 61 |
+
urineProtein: dto.urineProtein,
|
|
|
|
| 62 |
|
| 63 |
+
fundalHeight: dto.fundalHeight,
|
|
|
|
| 64 |
|
| 65 |
+
fetalHeartRate: dto.fetalHeartRate,
|
|
|
|
| 66 |
|
| 67 |
+
fetalMovement: dto.fetalMovement,
|
|
|
|
| 68 |
|
| 69 |
+
haemoglobin: dto.haemoglobin,
|
|
|
|
| 70 |
|
| 71 |
+
temperature: dto.temperature,
|
|
|
|
| 72 |
|
| 73 |
+
remarks: dto.remarks,
|
|
|
|
| 74 |
|
| 75 |
+
nextAppointmentDate: dto.nextAppointmentDate
|
| 76 |
+
? new Date(dto.nextAppointmentDate)
|
| 77 |
+
: null,
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
riskLevel,
|
| 80 |
},
|
| 81 |
});
|
| 82 |
|
| 83 |
+
/**
|
| 84 |
+
* Update patient's latest risk snapshot
|
| 85 |
+
*/
|
| 86 |
+
await this.prisma.patient.update({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
where: {
|
| 88 |
+
id: patient.id,
|
|
|
|
| 89 |
},
|
| 90 |
data: {
|
| 91 |
+
latestRiskLevel: riskLevel,
|
| 92 |
+
lastVisitDate: visit.visitDate,
|
| 93 |
},
|
| 94 |
});
|
|
|
|
| 95 |
|
| 96 |
+
/**
|
| 97 |
+
* Create appointment
|
| 98 |
+
*/
|
| 99 |
+
if (dto.nextAppointmentDate) {
|
| 100 |
+
await this.prisma.appointment.create({
|
| 101 |
+
data: {
|
| 102 |
+
userId: patient.userId,
|
| 103 |
+
patientId: patient.id,
|
| 104 |
+
facilityId: patient.facilityId,
|
| 105 |
+
title: "Antenatal Care Visit",
|
| 106 |
+
type: "ANC",
|
| 107 |
+
dateTime: new Date(dto.nextAppointmentDate),
|
| 108 |
+
},
|
| 109 |
+
});
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
/**
|
| 113 |
+
* Close old unresolved alerts
|
| 114 |
+
* if patient is no longer high risk.
|
| 115 |
+
*/
|
| 116 |
+
if (riskLevel === "LOW" || riskLevel === "MODERATE") {
|
| 117 |
+
await this.prisma.alert.updateMany({
|
| 118 |
where: {
|
| 119 |
patientId: patient.id,
|
| 120 |
isResolved: false,
|
| 121 |
+
},
|
| 122 |
+
data: {
|
| 123 |
+
isResolved: true,
|
| 124 |
+
resolvedAt: new Date(),
|
| 125 |
},
|
| 126 |
});
|
| 127 |
+
}
|
| 128 |
|
| 129 |
+
/**
|
| 130 |
+
* Create new alert
|
| 131 |
+
*/
|
| 132 |
+
if (riskLevel === "HIGH" || riskLevel === "EMERGENCY") {
|
| 133 |
+
const existingAlert = await this.prisma.alert.findFirst({
|
| 134 |
+
where: {
|
| 135 |
+
patientId: patient.id,
|
| 136 |
+
isResolved: false,
|
| 137 |
type: riskLevel,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
},
|
| 139 |
});
|
| 140 |
+
|
| 141 |
+
if (!existingAlert) {
|
| 142 |
+
await this.prisma.alert.create({
|
| 143 |
+
data: {
|
| 144 |
+
facilityId: patient.facilityId,
|
| 145 |
+
patientId: patient.id,
|
| 146 |
+
type: riskLevel,
|
| 147 |
+
message:
|
| 148 |
+
riskLevel === "EMERGENCY"
|
| 149 |
+
? "Severely elevated blood pressure detected."
|
| 150 |
+
: "High-risk pregnancy detected during ANC visit.",
|
| 151 |
+
},
|
| 152 |
+
});
|
| 153 |
+
}
|
| 154 |
}
|
|
|
|
| 155 |
|
| 156 |
+
return visit;
|
| 157 |
+
}
|
| 158 |
|
| 159 |
async getPatientVisits(patientId: string) {
|
| 160 |
const patient = await this.prisma.patient.findUnique({
|
|
|
|
| 262 |
return defaulters.sort((a, b) => b.daysOverdue - a.daysOverdue);
|
| 263 |
}
|
| 264 |
|
| 265 |
+
async getPatientSummary(patientId: string) {
|
| 266 |
+
const patient = await this.prisma.patient.findUnique({
|
|
|
|
|
|
|
|
|
|
| 267 |
where: {
|
| 268 |
id: patientId,
|
| 269 |
},
|
| 270 |
});
|
| 271 |
|
| 272 |
+
if (!patient) {
|
| 273 |
+
throw new NotFoundException("Patient not found");
|
| 274 |
+
}
|
|
|
|
|
|
|
| 275 |
|
| 276 |
+
const visits = await this.prisma.aNCVisit.findMany({
|
|
|
|
| 277 |
where: {
|
| 278 |
patientId,
|
| 279 |
},
|
|
|
|
| 282 |
},
|
| 283 |
});
|
| 284 |
|
| 285 |
+
if (!visits.length) {
|
| 286 |
+
return {
|
| 287 |
+
totalVisits: 0,
|
| 288 |
+
lastVisitDate: null,
|
| 289 |
+
nextAppointmentDate: null,
|
| 290 |
+
riskLevel: "LOW",
|
| 291 |
+
missedAppointments: 0,
|
| 292 |
+
bpTrend: "UNKNOWN",
|
| 293 |
+
lastBloodPressure: null,
|
| 294 |
+
};
|
| 295 |
+
}
|
| 296 |
|
| 297 |
+
const latestVisit = visits[visits.length - 1];
|
|
|
|
| 298 |
|
| 299 |
+
const today = new Date();
|
| 300 |
|
| 301 |
+
const missedAppointments = visits.filter(
|
| 302 |
+
(v) => v.nextAppointmentDate && v.nextAppointmentDate < today,
|
|
|
|
|
|
|
|
|
|
| 303 |
).length;
|
| 304 |
|
| 305 |
+
let bpTrend = "STABLE";
|
| 306 |
+
|
| 307 |
+
const bpVisits = visits.filter(
|
| 308 |
+
(v) => v.bloodPressureSystolic && v.bloodPressureDiastolic,
|
| 309 |
+
);
|
| 310 |
+
|
| 311 |
+
if (bpVisits.length >= 2) {
|
| 312 |
+
const last = bpVisits[bpVisits.length - 1];
|
| 313 |
+
|
| 314 |
+
const previous = bpVisits[bpVisits.length - 2];
|
| 315 |
+
|
| 316 |
+
if (last.bloodPressureSystolic! > previous.bloodPressureSystolic!) {
|
| 317 |
+
bpTrend = "RISING";
|
| 318 |
+
} else if (
|
| 319 |
+
last.bloodPressureSystolic! < previous.bloodPressureSystolic!
|
| 320 |
+
) {
|
| 321 |
+
bpTrend = "FALLING";
|
| 322 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 323 |
}
|
|
|
|
| 324 |
|
| 325 |
+
return {
|
| 326 |
+
totalVisits: visits.length,
|
| 327 |
+
lastVisitDate: latestVisit.visitDate,
|
| 328 |
+
nextAppointmentDate: latestVisit.nextAppointmentDate,
|
| 329 |
+
riskLevel: latestVisit.riskLevel,
|
| 330 |
+
missedAppointments,
|
| 331 |
+
bpTrend,
|
| 332 |
+
lastBloodPressure: {
|
| 333 |
+
systolic: latestVisit.bloodPressureSystolic,
|
| 334 |
+
diastolic: latestVisit.bloodPressureDiastolic,
|
| 335 |
+
},
|
| 336 |
+
};
|
| 337 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
}
|
src/appointments/appointments.service.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
| 1 |
-
import {
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
@Injectable()
|
| 8 |
export class AppointmentsService {
|
|
@@ -12,30 +16,28 @@ export class AppointmentsService {
|
|
| 12 |
healthcareWorkerId: string,
|
| 13 |
dto: CreateAppointmentDto,
|
| 14 |
): Promise<Appointment> {
|
| 15 |
-
const worker =
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
});
|
| 21 |
|
| 22 |
if (!worker) {
|
| 23 |
-
throw new NotFoundException(
|
| 24 |
-
"Healthcare worker not found",
|
| 25 |
-
);
|
| 26 |
}
|
| 27 |
|
| 28 |
-
const patient =
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
});
|
| 34 |
|
| 35 |
if (!patient) {
|
| 36 |
-
throw new NotFoundException(
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
| 39 |
}
|
| 40 |
|
| 41 |
return this.prisma.appointment.create({
|
|
@@ -52,20 +54,15 @@ export class AppointmentsService {
|
|
| 52 |
});
|
| 53 |
}
|
| 54 |
|
| 55 |
-
async findAll(
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
id: healthcareWorkerId,
|
| 62 |
-
},
|
| 63 |
-
});
|
| 64 |
|
| 65 |
if (!worker) {
|
| 66 |
-
throw new NotFoundException(
|
| 67 |
-
"Healthcare worker not found",
|
| 68 |
-
);
|
| 69 |
}
|
| 70 |
|
| 71 |
return this.prisma.appointment.findMany({
|
|
@@ -82,33 +79,26 @@ export class AppointmentsService {
|
|
| 82 |
});
|
| 83 |
}
|
| 84 |
|
| 85 |
-
async findOne(
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
include: {
|
| 103 |
-
patient: true,
|
| 104 |
-
facility: true,
|
| 105 |
-
},
|
| 106 |
-
});
|
| 107 |
|
| 108 |
if (!appointment) {
|
| 109 |
-
throw new NotFoundException(
|
| 110 |
-
"Appointment not found",
|
| 111 |
-
);
|
| 112 |
}
|
| 113 |
|
| 114 |
return appointment;
|
|
@@ -119,10 +109,7 @@ export class AppointmentsService {
|
|
| 119 |
healthcareWorkerId: string,
|
| 120 |
dto: UpdateAppointmentDto,
|
| 121 |
) {
|
| 122 |
-
await this.findOne(
|
| 123 |
-
id,
|
| 124 |
-
healthcareWorkerId,
|
| 125 |
-
);
|
| 126 |
|
| 127 |
return this.prisma.appointment.update({
|
| 128 |
where: {
|
|
@@ -137,14 +124,8 @@ export class AppointmentsService {
|
|
| 137 |
});
|
| 138 |
}
|
| 139 |
|
| 140 |
-
async remove(
|
| 141 |
-
|
| 142 |
-
healthcareWorkerId: string,
|
| 143 |
-
) {
|
| 144 |
-
await this.findOne(
|
| 145 |
-
id,
|
| 146 |
-
healthcareWorkerId,
|
| 147 |
-
);
|
| 148 |
|
| 149 |
await this.prisma.appointment.delete({
|
| 150 |
where: {
|
|
|
|
| 1 |
+
import {
|
| 2 |
+
BadRequestException,
|
| 3 |
+
Injectable,
|
| 4 |
+
NotFoundException,
|
| 5 |
+
} from "@nestjs/common";
|
| 6 |
+
import { PrismaService } from "../database/prisma.service";
|
| 7 |
+
import { CreateAppointmentDto } from "./dto/create-appointment.dto";
|
| 8 |
+
import { UpdateAppointmentDto } from "./dto/update-appointment.dto";
|
| 9 |
+
import { Appointment } from "@prisma/client";
|
| 10 |
|
| 11 |
@Injectable()
|
| 12 |
export class AppointmentsService {
|
|
|
|
| 16 |
healthcareWorkerId: string,
|
| 17 |
dto: CreateAppointmentDto,
|
| 18 |
): Promise<Appointment> {
|
| 19 |
+
const worker = await this.prisma.healthcareWorker.findUnique({
|
| 20 |
+
where: {
|
| 21 |
+
id: healthcareWorkerId,
|
| 22 |
+
},
|
| 23 |
+
});
|
|
|
|
| 24 |
|
| 25 |
if (!worker) {
|
| 26 |
+
throw new NotFoundException("Healthcare worker not found");
|
|
|
|
|
|
|
| 27 |
}
|
| 28 |
|
| 29 |
+
const patient = await this.prisma.patient.findUnique({
|
| 30 |
+
where: {
|
| 31 |
+
id: dto.patientId,
|
| 32 |
+
},
|
| 33 |
+
});
|
|
|
|
| 34 |
|
| 35 |
if (!patient) {
|
| 36 |
+
throw new NotFoundException("Patient not found");
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
if (!patient.facilityId) {
|
| 40 |
+
throw new BadRequestException("Patient is not linked to a facility");
|
| 41 |
}
|
| 42 |
|
| 43 |
return this.prisma.appointment.create({
|
|
|
|
| 54 |
});
|
| 55 |
}
|
| 56 |
|
| 57 |
+
async findAll(healthcareWorkerId: string): Promise<Appointment[]> {
|
| 58 |
+
const worker = await this.prisma.healthcareWorker.findUnique({
|
| 59 |
+
where: {
|
| 60 |
+
id: healthcareWorkerId,
|
| 61 |
+
},
|
| 62 |
+
});
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
if (!worker) {
|
| 65 |
+
throw new NotFoundException("Healthcare worker not found");
|
|
|
|
|
|
|
| 66 |
}
|
| 67 |
|
| 68 |
return this.prisma.appointment.findMany({
|
|
|
|
| 79 |
});
|
| 80 |
}
|
| 81 |
|
| 82 |
+
async findOne(id: string, healthcareWorkerId: string) {
|
| 83 |
+
const worker = await this.prisma.healthcareWorker.findUnique({
|
| 84 |
+
where: {
|
| 85 |
+
id: healthcareWorkerId,
|
| 86 |
+
},
|
| 87 |
+
});
|
| 88 |
+
|
| 89 |
+
const appointment = await this.prisma.appointment.findFirst({
|
| 90 |
+
where: {
|
| 91 |
+
id,
|
| 92 |
+
facilityId: worker?.facilityId,
|
| 93 |
+
},
|
| 94 |
+
include: {
|
| 95 |
+
patient: true,
|
| 96 |
+
facility: true,
|
| 97 |
+
},
|
| 98 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
|
| 100 |
if (!appointment) {
|
| 101 |
+
throw new NotFoundException("Appointment not found");
|
|
|
|
|
|
|
| 102 |
}
|
| 103 |
|
| 104 |
return appointment;
|
|
|
|
| 109 |
healthcareWorkerId: string,
|
| 110 |
dto: UpdateAppointmentDto,
|
| 111 |
) {
|
| 112 |
+
await this.findOne(id, healthcareWorkerId);
|
|
|
|
|
|
|
|
|
|
| 113 |
|
| 114 |
return this.prisma.appointment.update({
|
| 115 |
where: {
|
|
|
|
| 124 |
});
|
| 125 |
}
|
| 126 |
|
| 127 |
+
async remove(id: string, healthcareWorkerId: string) {
|
| 128 |
+
await this.findOne(id, healthcareWorkerId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
await this.prisma.appointment.delete({
|
| 131 |
where: {
|
src/facilities/facilities.service.ts
CHANGED
|
@@ -56,7 +56,12 @@ export class FacilitiesService {
|
|
| 56 |
|
| 57 |
async update(
|
| 58 |
id: string,
|
| 59 |
-
data: {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
) {
|
| 61 |
return this.prisma.facility.update({ where: { id }, data });
|
| 62 |
}
|
|
@@ -230,14 +235,18 @@ export class FacilitiesService {
|
|
| 230 |
},
|
| 231 |
});
|
| 232 |
|
| 233 |
-
|
| 234 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
return {
|
| 236 |
-
linked:
|
| 237 |
facilityId: patient.facilityId,
|
| 238 |
-
facilityName: patient.facility.name,
|
| 239 |
-
facilityAddress: patient.facility.address,
|
| 240 |
-
facilityPhone: patient.facility.phone,
|
| 241 |
patientCode: patient.patientId,
|
| 242 |
linkedAt: patient.createdAt,
|
| 243 |
};
|
|
@@ -262,4 +271,4 @@ export class FacilitiesService {
|
|
| 262 |
`Patient ${userId} unlinked from facility ${patient.facilityId}`,
|
| 263 |
);
|
| 264 |
}
|
| 265 |
-
}
|
|
|
|
| 56 |
|
| 57 |
async update(
|
| 58 |
id: string,
|
| 59 |
+
data: {
|
| 60 |
+
name?: string;
|
| 61 |
+
address?: string;
|
| 62 |
+
phone?: string;
|
| 63 |
+
isActive?: boolean;
|
| 64 |
+
},
|
| 65 |
) {
|
| 66 |
return this.prisma.facility.update({ where: { id }, data });
|
| 67 |
}
|
|
|
|
| 235 |
},
|
| 236 |
});
|
| 237 |
|
| 238 |
+
|
| 239 |
+
if (!patient || !patient.facility) {
|
| 240 |
+
return {
|
| 241 |
+
linked: false,
|
| 242 |
+
};
|
| 243 |
+
}
|
| 244 |
return {
|
| 245 |
+
linked: !!patient.facility,
|
| 246 |
facilityId: patient.facilityId,
|
| 247 |
+
facilityName: patient.facility?.name ?? null,
|
| 248 |
+
facilityAddress: patient.facility?.address ?? null,
|
| 249 |
+
facilityPhone: patient.facility?.phone ?? null,
|
| 250 |
patientCode: patient.patientId,
|
| 251 |
linkedAt: patient.createdAt,
|
| 252 |
};
|
|
|
|
| 271 |
`Patient ${userId} unlinked from facility ${patient.facilityId}`,
|
| 272 |
);
|
| 273 |
}
|
| 274 |
+
}
|
src/patients/patients.service.ts
CHANGED
|
@@ -200,6 +200,11 @@ export class PatientsService {
|
|
| 200 |
}
|
| 201 |
|
| 202 |
async getDefaulters(facilityId: string) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
const sevenDaysAgo = new Date();
|
| 204 |
|
| 205 |
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
|
|
|
| 200 |
}
|
| 201 |
|
| 202 |
async getDefaulters(facilityId: string) {
|
| 203 |
+
|
| 204 |
+
if (!facilityId) {
|
| 205 |
+
return [];
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
const sevenDaysAgo = new Date();
|
| 209 |
|
| 210 |
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
src/referral/referral.service.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
| 1 |
// βββ referrals.service.ts βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 2 |
|
| 3 |
-
import {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
import { PrismaService } from "../database/prisma.service";
|
| 5 |
|
| 6 |
import { CreateReferralDto } from "./dto/create-referral-dto";
|
| 7 |
|
| 8 |
-
|
| 9 |
-
|
| 10 |
export interface UpdateReferralDto {
|
| 11 |
status: "PENDING" | "ACCEPTED" | "COMPLETED" | "CANCELLED";
|
| 12 |
}
|
|
@@ -15,63 +17,56 @@ export interface UpdateReferralDto {
|
|
| 15 |
export class ReferralsService {
|
| 16 |
constructor(private readonly prisma: PrismaService) {}
|
| 17 |
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
facilityId: string,
|
| 21 |
-
) {
|
| 22 |
-
const patient =
|
| 23 |
-
await this.prisma.patient.findFirst({
|
| 24 |
where: {
|
| 25 |
id: dto.patientId,
|
| 26 |
facilityId,
|
| 27 |
},
|
| 28 |
});
|
| 29 |
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
}
|
| 35 |
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
}
|
| 47 |
-
|
| 48 |
-
async accept(
|
| 49 |
-
referralId: string,
|
| 50 |
-
) {
|
| 51 |
-
return this.prisma.referral.update({
|
| 52 |
-
where: {
|
| 53 |
-
id: referralId,
|
| 54 |
-
},
|
| 55 |
-
data: {
|
| 56 |
-
status: 'ACCEPTED',
|
| 57 |
-
acceptedAt: new Date(),
|
| 58 |
-
},
|
| 59 |
-
});
|
| 60 |
-
}
|
| 61 |
|
| 62 |
-
async complete(
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
});
|
| 74 |
-
}
|
| 75 |
|
| 76 |
async findAll(facilityId?: string) {
|
| 77 |
return this.prisma.referral.findMany({
|
|
@@ -151,5 +146,3 @@ async complete(
|
|
| 151 |
return { total, pending, accepted, completed, cancelled };
|
| 152 |
}
|
| 153 |
}
|
| 154 |
-
|
| 155 |
-
|
|
|
|
| 1 |
// βββ referrals.service.ts βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 2 |
|
| 3 |
+
import {
|
| 4 |
+
BadRequestException,
|
| 5 |
+
Injectable,
|
| 6 |
+
NotFoundException,
|
| 7 |
+
} from "@nestjs/common";
|
| 8 |
import { PrismaService } from "../database/prisma.service";
|
| 9 |
|
| 10 |
import { CreateReferralDto } from "./dto/create-referral-dto";
|
| 11 |
|
|
|
|
|
|
|
| 12 |
export interface UpdateReferralDto {
|
| 13 |
status: "PENDING" | "ACCEPTED" | "COMPLETED" | "CANCELLED";
|
| 14 |
}
|
|
|
|
| 17 |
export class ReferralsService {
|
| 18 |
constructor(private readonly prisma: PrismaService) {}
|
| 19 |
|
| 20 |
+
async create(dto: CreateReferralDto, facilityId: string) {
|
| 21 |
+
const patient = await this.prisma.patient.findFirst({
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
where: {
|
| 23 |
id: dto.patientId,
|
| 24 |
facilityId,
|
| 25 |
},
|
| 26 |
});
|
| 27 |
|
| 28 |
+
if (!patient) {
|
| 29 |
+
throw new NotFoundException("Patient not found");
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
if (!patient.facilityId) {
|
| 33 |
+
throw new BadRequestException("Patient is not linked to a facility");
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
return this.prisma.referral.create({
|
| 37 |
+
data: {
|
| 38 |
+
patientId: patient.id,
|
| 39 |
+
fromFacilityId: facilityId,
|
| 40 |
+
toFacilityId: dto.toFacilityId,
|
| 41 |
+
reason: dto.reason,
|
| 42 |
+
notes: dto.notes,
|
| 43 |
+
},
|
| 44 |
+
});
|
| 45 |
}
|
| 46 |
|
| 47 |
+
async accept(referralId: string) {
|
| 48 |
+
return this.prisma.referral.update({
|
| 49 |
+
where: {
|
| 50 |
+
id: referralId,
|
| 51 |
+
},
|
| 52 |
+
data: {
|
| 53 |
+
status: "ACCEPTED",
|
| 54 |
+
acceptedAt: new Date(),
|
| 55 |
+
},
|
| 56 |
+
});
|
| 57 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
+
async complete(referralId: string) {
|
| 60 |
+
return this.prisma.referral.update({
|
| 61 |
+
where: {
|
| 62 |
+
id: referralId,
|
| 63 |
+
},
|
| 64 |
+
data: {
|
| 65 |
+
status: "COMPLETED",
|
| 66 |
+
completedAt: new Date(),
|
| 67 |
+
},
|
| 68 |
+
});
|
| 69 |
+
}
|
|
|
|
|
|
|
| 70 |
|
| 71 |
async findAll(facilityId?: string) {
|
| 72 |
return this.prisma.referral.findMany({
|
|
|
|
| 146 |
return { total, pending, accepted, completed, cancelled };
|
| 147 |
}
|
| 148 |
}
|
|
|
|
|
|