'use server'; import { z } from 'zod'; import { authActionClient } from '@kit/next/safe-action'; import { getLogger } from '@kit/shared/logger'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { CreateMemberSchema, UpdateMemberSchema, RejectApplicationSchema, CreateDuesCategorySchema, CreateDepartmentSchema, CreateMemberRoleSchema, CreateMemberHonorSchema, CreateSepaMandateSchema, UpdateDuesCategorySchema, UpdateMandateSchema, ExportMembersSchema, AssignDepartmentSchema, } from '../../schema/member.schema'; import { createMemberManagementApi } from '../api'; export const createMember = authActionClient .inputSchema(CreateMemberSchema) .action(async ({ parsedInput: input, ctx }) => { const client = getSupabaseServerClient(); const logger = await getLogger(); const api = createMemberManagementApi(client); const userId = ctx.user.id; // Check for duplicates before creating const duplicates = await api.checkDuplicate( input.accountId, input.firstName, input.lastName, input.dateOfBirth, ); if (duplicates.length > 0) { return { success: false, error: 'Mögliche Duplikate gefunden', validationErrors: duplicates.map((d: Record) => ({ field: 'name', message: `${d.first_name} ${d.last_name}${d.member_number ? ` (Nr. ${d.member_number})` : ''}`, id: String(d.id), })), }; } logger.info({ name: 'member.create' }, 'Creating member...'); const result = await api.createMember(input, userId); logger.info({ name: 'member.create' }, 'Member created'); return { success: true, data: result }; }); export const updateMember = authActionClient .inputSchema(UpdateMemberSchema) .action(async ({ parsedInput: input, ctx }) => { const client = getSupabaseServerClient(); const logger = await getLogger(); const api = createMemberManagementApi(client); const userId = ctx.user.id; logger.info({ name: 'member.update' }, 'Updating member...'); const result = await api.updateMember(input, userId); logger.info({ name: 'member.update' }, 'Member updated'); return { success: true, data: result }; }); export const deleteMember = authActionClient .inputSchema( z.object({ memberId: z.string().uuid(), accountId: z.string().uuid(), }), ) .action(async ({ parsedInput: input, ctx }) => { const client = getSupabaseServerClient(); const logger = await getLogger(); const api = createMemberManagementApi(client); logger.info({ name: 'member.delete' }, 'Deleting member...'); const result = await api.deleteMember(input.memberId); logger.info({ name: 'member.delete' }, 'Member deleted'); return { success: true, data: result }; }); export const approveApplication = authActionClient .inputSchema( z.object({ applicationId: z.string().uuid(), accountId: z.string().uuid(), }), ) .action(async ({ parsedInput: input, ctx }) => { const client = getSupabaseServerClient(); const logger = await getLogger(); const api = createMemberManagementApi(client); const userId = ctx.user.id; logger.info( { name: 'member.approveApplication' }, 'Approving application...', ); const result = await api.approveApplication(input.applicationId, userId); logger.info({ name: 'member.approveApplication' }, 'Application approved'); return { success: true, data: result }; }); export const rejectApplication = authActionClient .inputSchema(RejectApplicationSchema) .action(async ({ parsedInput: input, ctx }) => { const client = getSupabaseServerClient(); const logger = await getLogger(); const api = createMemberManagementApi(client); logger.info( { name: 'members.reject-application' }, 'Rejecting application...', ); await api.rejectApplication( input.applicationId, ctx.user.id, input.reviewNotes, ); return { success: true }; }); export const createDuesCategory = authActionClient .inputSchema(CreateDuesCategorySchema) .action(async ({ parsedInput: input }) => { const client = getSupabaseServerClient(); const api = createMemberManagementApi(client); const data = await api.createDuesCategory(input); return { success: true, data }; }); export const deleteDuesCategory = authActionClient .inputSchema(z.object({ categoryId: z.string().uuid() })) .action(async ({ parsedInput: input }) => { const client = getSupabaseServerClient(); const api = createMemberManagementApi(client); await api.deleteDuesCategory(input.categoryId); return { success: true }; }); export const createDepartment = authActionClient .inputSchema(CreateDepartmentSchema) .action(async ({ parsedInput: input }) => { const client = getSupabaseServerClient(); const api = createMemberManagementApi(client); const data = await api.createDepartment(input); return { success: true, data }; }); export const createMemberRole = authActionClient .inputSchema(CreateMemberRoleSchema) .action(async ({ parsedInput: input }) => { const client = getSupabaseServerClient(); const api = createMemberManagementApi(client); const data = await api.createMemberRole(input); return { success: true, data }; }); export const deleteMemberRole = authActionClient .inputSchema(z.object({ roleId: z.string().uuid() })) .action(async ({ parsedInput: input }) => { const client = getSupabaseServerClient(); const api = createMemberManagementApi(client); await api.deleteMemberRole(input.roleId); return { success: true }; }); export const createMemberHonor = authActionClient .inputSchema(CreateMemberHonorSchema) .action(async ({ parsedInput: input }) => { const client = getSupabaseServerClient(); const api = createMemberManagementApi(client); const data = await api.createMemberHonor(input); return { success: true, data }; }); export const deleteMemberHonor = authActionClient .inputSchema(z.object({ honorId: z.string().uuid() })) .action(async ({ parsedInput: input }) => { const client = getSupabaseServerClient(); const api = createMemberManagementApi(client); await api.deleteMemberHonor(input.honorId); return { success: true }; }); export const createMandate = authActionClient .inputSchema(CreateSepaMandateSchema) .action(async ({ parsedInput: input }) => { const client = getSupabaseServerClient(); const api = createMemberManagementApi(client); const data = await api.createMandate(input); return { success: true, data }; }); export const revokeMandate = authActionClient .inputSchema(z.object({ mandateId: z.string().uuid() })) .action(async ({ parsedInput: input }) => { const client = getSupabaseServerClient(); const api = createMemberManagementApi(client); await api.revokeMandate(input.mandateId); return { success: true }; }); // Gap 1: Update operations export const updateDuesCategory = authActionClient .inputSchema(UpdateDuesCategorySchema) .action(async ({ parsedInput: input }) => { const client = getSupabaseServerClient(); const api = createMemberManagementApi(client); const data = await api.updateDuesCategory(input); return { success: true, data }; }); export const updateMandate = authActionClient .inputSchema(UpdateMandateSchema) .action(async ({ parsedInput: input }) => { const client = getSupabaseServerClient(); const api = createMemberManagementApi(client); const data = await api.updateMandate(input); return { success: true, data }; }); // Gap 2: Export export const exportMembers = authActionClient .inputSchema(ExportMembersSchema) .action(async ({ parsedInput: input }) => { const client = getSupabaseServerClient(); const api = createMemberManagementApi(client); const csv = await api.exportMembersCsv(input.accountId, { status: input.status, }); return { success: true, data: { content: csv, filename: `mitglieder_${new Date().toISOString().split('T')[0]}.csv`, mimeType: 'text/csv', }, }; }); // Gap 5: Department assignments export const assignDepartment = authActionClient .inputSchema(AssignDepartmentSchema) .action(async ({ parsedInput: input }) => { const client = getSupabaseServerClient(); const api = createMemberManagementApi(client); await api.assignDepartment(input.memberId, input.departmentId); return { success: true }; }); export const removeDepartment = authActionClient .inputSchema(AssignDepartmentSchema) .action(async ({ parsedInput: input }) => { const client = getSupabaseServerClient(); const api = createMemberManagementApi(client); await api.removeDepartment(input.memberId, input.departmentId); return { success: true }; }); // Gap 2: Excel export export const exportMembersExcel = authActionClient .inputSchema(ExportMembersSchema) .action(async ({ parsedInput: input }) => { const client = getSupabaseServerClient(); const api = createMemberManagementApi(client); const buffer = await api.exportMembersExcel(input.accountId, { status: input.status, }); return { success: true, data: { content: buffer.toString('base64'), filename: `mitglieder_${new Date().toISOString().split('T')[0]}.xlsx`, mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', }, }; }); // Gap 6: Member card PDF generation export const generateMemberCards = authActionClient .inputSchema( z.object({ accountId: z.string().uuid(), memberIds: z.array(z.string().uuid()).optional(), orgName: z.string().default('Verein'), }), ) .action(async ({ parsedInput: input }) => { const client = getSupabaseServerClient(); const api = createMemberManagementApi(client); let query = client .from('members') .select('id, first_name, last_name, member_number, entry_date, status') .eq('account_id', input.accountId) .eq('status', 'active'); if (input.memberIds && input.memberIds.length > 0) { query = query.in('id', input.memberIds); } const { data: members, error } = await query; if (error) throw error; const { generateMemberCardsPdf } = await import('../services/member-card-generator'); const buffer = await generateMemberCardsPdf( input.orgName, (members ?? []).map((m: any) => ({ firstName: m.first_name, lastName: m.last_name, memberNumber: m.member_number ?? '', entryDate: m.entry_date ?? '', status: m.status, })), ); return { success: true, data: { content: buffer.toString('base64'), filename: `mitgliedsausweise_${new Date().toISOString().split('T')[0]}.pdf`, mimeType: 'application/pdf', }, }; }); // Portal Invitations export const inviteMemberToPortal = authActionClient .inputSchema( z.object({ memberId: z.string().uuid(), accountId: z.string().uuid(), email: z.string().email(), }), ) .action(async ({ parsedInput: input, ctx }) => { const client = getSupabaseServerClient(); const logger = await getLogger(); const api = createMemberManagementApi(client); logger.info( { name: 'portal.invite', memberId: input.memberId }, 'Sending portal invitation...', ); const invitation = await api.inviteMemberToPortal(input, ctx.user.id); // Create auth user for the member if not exists // In production: send invitation email with the token link // For now: create the user directly via admin API logger.info( { name: 'portal.invite', token: invitation.invite_token }, 'Invitation created', ); return { success: true, data: invitation }; }); export const revokePortalInvitation = authActionClient .inputSchema(z.object({ invitationId: z.string().uuid() })) .action(async ({ parsedInput: input }) => { const client = getSupabaseServerClient(); const api = createMemberManagementApi(client); await api.revokePortalInvitation(input.invitationId); return { success: true }; });