stadtwerke/innungsapp/apps/admin/server/routers/termine.ts

181 lines
5.1 KiB
TypeScript

import { z } from 'zod'
import { router, memberProcedure, adminProcedure } from '../trpc'
const TerminInput = z.object({
titel: z.string().min(3),
datum: z.string(), // ISO date string "YYYY-MM-DD"
uhrzeit: z.string().optional(),
endeDatum: z.string().optional().nullable(),
endeUhrzeit: z.string().optional().nullable(),
ort: z.string().optional(),
adresse: z.string().optional(),
typ: z.enum(['Pruefung', 'Versammlung', 'Kurs', 'Event', 'Sonstiges']),
beschreibung: z.string().optional(),
maxTeilnehmer: z.number().int().positive().optional().nullable(),
})
export const termineRouter = router({
/**
* List all termine for org
*/
list: memberProcedure
.input(
z.object({
upcoming: z.boolean().optional(),
nurAngemeldet: z.boolean().optional(),
})
)
.query(async ({ ctx, input }) => {
const member = await ctx.prisma.member.findFirst({
where: { userId: ctx.session.user.id, orgId: ctx.orgId },
})
const today = new Date()
today.setHours(0, 0, 0, 0)
const termine = await ctx.prisma.termin.findMany({
where: {
orgId: ctx.orgId,
...(input.upcoming && { datum: { gte: today } }),
...(!input.upcoming &&
input.upcoming !== undefined && { datum: { lt: today } }),
...(input.nurAngemeldet &&
member && {
anmeldungen: { some: { memberId: member.id } },
}),
},
include: {
anmeldungen: {
select: { memberId: true },
},
},
orderBy: { datum: input.upcoming ? 'asc' : 'desc' },
})
return termine.map((t) => ({
...t,
isAngemeldet: member
? t.anmeldungen.some((a) => a.memberId === member.id)
: false,
teilnehmerAnzahl: t.anmeldungen.length,
anmeldungen: undefined,
}))
}),
/**
* Get single Termin
*/
byId: memberProcedure
.input(z.object({ id: z.string() }))
.query(async ({ ctx, input }) => {
const member = await ctx.prisma.member.findFirst({
where: { userId: ctx.session.user.id, orgId: ctx.orgId },
})
const termin = await ctx.prisma.termin.findFirstOrThrow({
where: { id: input.id, orgId: ctx.orgId },
include: {
anmeldungen: {
include: {
member: { select: { name: true, betrieb: true } },
},
},
},
})
const isAngemeldet = member
? termin.anmeldungen.some((a) => a.memberId === member.id)
: false
return {
...termin,
isAngemeldet,
teilnehmerAnzahl: termin.anmeldungen.length,
// Only expose member list to admins
anmeldungen: ctx.role === 'admin' ? termin.anmeldungen : [],
}
}),
/**
* Anmelden / Abmelden
*/
toggleAnmeldung: memberProcedure
.input(z.object({ terminId: z.string() }))
.mutation(async ({ ctx, input }) => {
const member = await ctx.prisma.member.findFirstOrThrow({
where: { userId: ctx.session.user.id, orgId: ctx.orgId },
})
const termin = await ctx.prisma.termin.findFirstOrThrow({
where: { id: input.terminId, orgId: ctx.orgId },
include: { anmeldungen: true },
})
const existing = termin.anmeldungen.find(
(a) => a.memberId === member.id
)
if (existing) {
// Abmelden
await ctx.prisma.terminAnmeldung.delete({ where: { id: existing.id } })
return { angemeldet: false }
} else {
// Check capacity
if (
termin.maxTeilnehmer &&
termin.anmeldungen.length >= termin.maxTeilnehmer
) {
throw new Error('Maximale Teilnehmerzahl erreicht')
}
await ctx.prisma.terminAnmeldung.create({
data: { terminId: input.terminId, memberId: member.id },
})
return { angemeldet: true }
}
}),
/**
* Create Termin (admin only)
*/
create: adminProcedure.input(TerminInput).mutation(async ({ ctx, input }) => {
const termin = await ctx.prisma.termin.create({
data: {
orgId: ctx.orgId,
...input,
datum: new Date(input.datum),
endeDatum: input.endeDatum ? new Date(input.endeDatum) : null,
},
})
return termin
}),
/**
* Update Termin (admin only)
*/
update: adminProcedure
.input(z.object({ id: z.string(), data: TerminInput.partial() }))
.mutation(async ({ ctx, input }) => {
await ctx.prisma.termin.updateMany({
where: { id: input.id, orgId: ctx.orgId },
data: {
...input.data,
...(input.data.datum && { datum: new Date(input.data.datum) }),
...(input.data.endeDatum && { endeDatum: new Date(input.data.endeDatum) }),
},
})
return { success: true }
}),
/**
* Delete Termin (admin only)
*/
delete: adminProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ ctx, input }) => {
await ctx.prisma.termin.deleteMany({
where: { id: input.id, orgId: ctx.orgId },
})
return { success: true }
}),
})