130 lines
3.7 KiB
TypeScript
130 lines
3.7 KiB
TypeScript
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<string, number>,
|
|
};
|
|
|
|
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 };
|
|
},
|
|
};
|