import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; import { PrismaService } from '../../../prisma/prisma.service'; import { isValidMobile, mobileSearchDigits, normalizeMobile } from '../../common/phone'; import { CreatePatientDto } from './dto/create-patient.dto'; import { ListPatientsDto } from './dto/list-patients.dto'; import { UpdatePatientDto } from './dto/update-patient.dto'; @Injectable() export class PatientsService { constructor(private readonly prisma: PrismaService) {} async create(createPatientDto: CreatePatientDto, organizationId: string) { const mobile = this.resolveMobile(createPatientDto.mobile); const existing = await this.prisma.patient.findUnique({ where: { mobile }, }); if (existing) { return { success: true, data: existing, existing: true as const }; } const patient = await this.prisma.patient.create({ data: { firstName: createPatientDto.firstName.trim(), lastName: createPatientDto.lastName.trim(), mobile, email: createPatientDto.email?.trim() || null, notes: createPatientDto.notes?.trim() || null, dateOfBirth: createPatientDto.dateOfBirth ? new Date(createPatientDto.dateOfBirth) : null, createdByOrganizationId: organizationId, }, }); return { success: true, data: patient, existing: false as const }; } async findAll(query: ListPatientsDto) { const { page = 1, limit = 10, q } = query; const skip = (page - 1) * limit; const where = q?.trim() ? this.buildSearchWhere(q.trim()) : {}; const [items, total] = await Promise.all([ this.prisma.patient.findMany({ where, skip, take: limit, orderBy: [{ updatedAt: 'desc' }], }), this.prisma.patient.count({ where }), ]); return { success: true, data: { items, pagination: { page, limit, total, totalPages: Math.max(1, Math.ceil(total / limit)), }, }, }; } async findOne(id: string) { const patient = await this.prisma.patient.findUnique({ where: { id }, }); if (!patient) { throw new NotFoundException('Patient not found'); } return { success: true, data: patient }; } async update(id: string, updatePatientDto: UpdatePatientDto) { await this.ensurePatient(id); const data: { firstName?: string; lastName?: string; mobile?: string; email?: string | null; notes?: string | null; dateOfBirth?: Date | null; } = {}; if (updatePatientDto.firstName !== undefined) { data.firstName = updatePatientDto.firstName.trim(); } if (updatePatientDto.lastName !== undefined) { data.lastName = updatePatientDto.lastName.trim(); } if (updatePatientDto.mobile !== undefined) { data.mobile = this.resolveMobile(updatePatientDto.mobile); } if (updatePatientDto.email !== undefined) { data.email = updatePatientDto.email?.trim() || null; } if (updatePatientDto.notes !== undefined) { data.notes = updatePatientDto.notes?.trim() || null; } if (updatePatientDto.dateOfBirth !== undefined) { data.dateOfBirth = updatePatientDto.dateOfBirth ? new Date(updatePatientDto.dateOfBirth) : null; } const patient = await this.prisma.patient.update({ where: { id }, data, }); return { success: true, data: patient }; } getOrganizationIdFromUser(user: { organizationId?: string }) { if (!user?.organizationId) { throw new BadRequestException('Organization is not selected'); } return user.organizationId; } private buildSearchWhere(q: string) { const orConditions: Array> = [ { firstName: { contains: q, mode: 'insensitive' as const } }, { lastName: { contains: q, mode: 'insensitive' as const } }, { email: { contains: q, mode: 'insensitive' as const } }, ]; const normalized = normalizeMobile(q); if (normalized) { orConditions.push({ mobile: normalized }); } else { const digits = mobileSearchDigits(q); if (digits.length >= 3) { orConditions.push({ mobile: { contains: digits } }); } } return { OR: orConditions }; } private resolveMobile(raw: string): string { const mobile = normalizeMobile(raw); if (!mobile || !isValidMobile(mobile)) { throw new BadRequestException( 'Invalid mobile number. Use a valid Iran mobile (e.g. 09121234567 or +989121234567).', ); } return mobile; } private async ensurePatient(id: string) { const patient = await this.prisma.patient.findUnique({ where: { id }, select: { id: true }, }); if (!patient) { throw new NotFoundException('Patient not found'); } } }