/opt/mawid/apps/api/src/patients
NameSizeModeActions
patients.controller.ts14620644editdlrm
patients.gdpr.spec.ts41390644editdlrm
patients.module.ts2690644editdlrm
patients.service.ts42660644editdlrm
Edit: /opt/mawid/apps/api/src/patients/patients.service.ts (4266B)
import { Injectable, NotFoundException } from '@nestjs/common'; import type { UpdatePatientInput } from '@mawid/shared'; import { PrismaService } from '../prisma/prisma.service'; @Injectable() export class PatientsService { constructor(private readonly prisma: PrismaService) {} list(clinicId: string, search?: string) { return this.prisma.patient.findMany({ where: { clinicId, ...(search ? { OR: [ { name: { contains: search, mode: 'insensitive' } }, { waPhone: { contains: search } }, ], } : {}), }, orderBy: [{ lastVisitAt: { sort: 'desc', nulls: 'last' } }, { createdAt: 'desc' }], take: 100, }); } async get(clinicId: string, id: string) { const patient = await this.prisma.patient.findFirst({ where: { id, clinicId }, include: { appointments: { include: { service: { select: { id: true, name: true } }, staff: { select: { id: true, name: true } }, }, orderBy: { startsAt: 'desc' }, take: 50, }, waitlistEntries: { where: { status: { in: ['active', 'notified'] } } }, }, }); if (!patient) throw new NotFoundException('Patient not found'); return patient; } async update(clinicId: string, id: string, input: UpdatePatientInput) { await this.assertExists(clinicId, id); return this.prisma.patient.update({ where: { id }, data: input }); } /** KVKK/GDPR data export: every record we hold about the patient (Phase 7 task 4). */ async exportData(clinicId: string, id: string) { const patient = await this.prisma.patient.findFirst({ where: { id, clinicId }, include: { appointments: { include: { service: true, staff: true } }, waitlistEntries: true, conversations: { include: { messages: { orderBy: { createdAt: 'asc' } } } }, }, }); if (!patient) throw new NotFoundException('Patient not found'); return { exportedAt: new Date().toISOString(), patient: { id: patient.id, waPhone: patient.waPhone, name: patient.name, language: patient.language, notes: patient.notes, tags: patient.tags, createdAt: patient.createdAt, }, appointments: patient.appointments, waitlistEntries: patient.waitlistEntries, conversations: patient.conversations, }; } /** * KVKK/GDPR delete-on-request: hard-deletes the patient and everything keyed * to them (messages, conversations, waitlist entries + holds, reminder jobs, * appointments). Audited without PII. */ async deleteData(clinicId: string, id: string, actor: string) { await this.assertExists(clinicId, id); await this.prisma.$transaction(async (tx) => { const conversations = await tx.conversation.findMany({ where: { clinicId, patientId: id }, select: { id: true }, }); await tx.message.deleteMany({ where: { conversationId: { in: conversations.map((c) => c.id) } }, }); await tx.conversation.deleteMany({ where: { clinicId, patientId: id } }); await tx.slotHold.deleteMany({ where: { clinicId, waitlistEntry: { patientId: id } } }); await tx.waitlistEntry.deleteMany({ where: { clinicId, patientId: id } }); const appointments = await tx.appointment.findMany({ where: { clinicId, patientId: id }, select: { id: true }, }); await tx.reminderJob.deleteMany({ where: { appointmentId: { in: appointments.map((a) => a.id) } }, }); await tx.appointment.deleteMany({ where: { clinicId, patientId: id } }); await tx.patient.delete({ where: { id } }); await tx.auditLog.create({ data: { clinicId, actor, action: 'patient:data_deleted', meta: { patientId: id }, // id only — no PII in audit logs }, }); }); return { deleted: true }; } private async assertExists(clinicId: string, id: string) { const patient = await this.prisma.patient.findFirst({ where: { id, clinicId } }); if (!patient) throw new NotFoundException('Patient not found'); } }