import { prisma } from '../../config/database'; import { AppError } from '../../middleware/error-handler'; import { logger } from '../../utils/logger'; import type { UpsertNeedsInput } from './participant-needs.schemas'; export const participantNeedsService = { async upsert(data: UpsertNeedsInput, userId?: string, contactId?: string) { if (!userId && !contactId) { throw new AppError(400, 'Either userId or contactId is required', 'MISSING_IDENTIFIER'); } const where = userId ? { userId } : { contactId: contactId! }; const existing = await prisma.participantNeeds.findUnique({ where }); if (existing) { return prisma.participantNeeds.update({ where: { id: existing.id }, data, }); } return prisma.participantNeeds.create({ data: { ...data, ...(userId ? { userId } : { contactId }), }, }); }, async findByUserId(userId: string) { return prisma.participantNeeds.findUnique({ where: { userId } }); }, async findByContactId(contactId: string) { return prisma.participantNeeds.findUnique({ where: { contactId } }); }, async aggregateForVoters(userIds: string[], contactIds: string[]) { const records = await prisma.participantNeeds.findMany({ where: { OR: [ ...(userIds.length > 0 ? [{ userId: { in: userIds } }] : []), ...(contactIds.length > 0 ? [{ contactId: { in: contactIds } }] : []), ], }, }); const summary = { total: records.length, accessibility: { wheelchair: 0, groundFloor: 0, hearingLoop: 0, signLanguage: 0, other: 0, }, dietary: { vegan: 0, vegetarian: 0, glutenFree: 0, halal: 0, kosher: 0, nutAllergy: 0, other: 0, }, childcare: 0, transportation: 0, translation: 0, languages: {} as Record, }; for (const r of records) { if (r.needsWheelchair) summary.accessibility.wheelchair++; if (r.needsGroundFloor) summary.accessibility.groundFloor++; if (r.needsHearingLoop) summary.accessibility.hearingLoop++; if (r.needsSignLanguage) summary.accessibility.signLanguage++; if (r.otherAccessibility) summary.accessibility.other++; if (r.isVegan) summary.dietary.vegan++; if (r.isVegetarian) summary.dietary.vegetarian++; if (r.isGlutenFree) summary.dietary.glutenFree++; if (r.isHalal) summary.dietary.halal++; if (r.isKosher) summary.dietary.kosher++; if (r.hasNutAllergy) summary.dietary.nutAllergy++; if (r.otherDietary) summary.dietary.other++; if (r.needsChildcare) summary.childcare++; if (r.needsTransportation) summary.transportation++; if (r.needsTranslation) summary.translation++; if (r.translationLanguage) { summary.languages[r.translationLanguage] = (summary.languages[r.translationLanguage] || 0) + 1; } } return summary; }, async deleteById(id: string) { const existing = await prisma.participantNeeds.findUnique({ where: { id } }); if (!existing) { throw new AppError(404, 'Participant needs record not found', 'NOT_FOUND'); } await prisma.participantNeeds.delete({ where: { id } }); }, async purgeExpired() { const cutoff = new Date(); cutoff.setDate(cutoff.getDate() - 7); const result = await prisma.participantNeeds.deleteMany({ where: { updatedAt: { lt: cutoff }, userId: null, contactId: null, }, }); if (result.count > 0) { logger.info(`Purged ${result.count} orphaned participant needs records older than 7 days`); } return { purged: result.count }; }, };