Updates, overview extended, club view implemented

This commit is contained in:
Torsten Schulz (local)
2026-06-23 16:17:07 +02:00
parent dd91c07694
commit 90e6c2f9f6
82 changed files with 11226 additions and 201 deletions

View File

@@ -19,7 +19,7 @@ export const createClubCalendarEvent = async (req, res) => {
try {
const { authcode: userToken } = req.headers;
const { clubId } = req.params;
const event = await calendarEventService.createClubEvent(userToken, clubId, req.body);
const event = await calendarEventService.createClubEvent(userToken, clubId, req.body, req.user?.id || null);
res.status(201).json(event);
} catch (error) {
console.error('[createClubCalendarEvent] - Error:', error);
@@ -28,6 +28,19 @@ export const createClubCalendarEvent = async (req, res) => {
}
};
export const updateClubCalendarEvent = async (req, res) => {
try {
const { authcode: userToken } = req.headers;
const { clubId, eventId } = req.params;
const event = await calendarEventService.updateClubEvent(userToken, clubId, eventId, req.body, req.user?.id || null);
res.status(200).json(event);
} catch (error) {
console.error('[updateClubCalendarEvent] - Error:', error);
const msg = getSafeErrorMessage(error, 'Fehler beim Speichern des Kalender-Events');
res.status(error.statusCode || 500).json({ error: msg });
}
};
export const deleteClubCalendarEvent = async (req, res) => {
try {
const { authcode: userToken } = req.headers;

View File

@@ -4,8 +4,8 @@ class ClubAccountController {
async listClubAccounts(req, res) {
try {
const { clubId } = req.params;
const accounts = await clubAccountService.listClubAccounts(Number(clubId));
res.json({ accounts });
const payload = await clubAccountService.listClubAccounts(Number(clubId));
res.json(payload);
} catch (error) {
console.error('[listClubAccounts] - Error:', error);
res.status(error?.status || 500).json({ error: error?.message || 'Konten konnten nicht geladen werden.' });
@@ -55,6 +55,39 @@ class ClubAccountController {
res.status(error?.status || 500).json({ error: error?.message || 'Konto konnte nicht gelöscht werden.' });
}
}
async createTransaction(req, res) {
try {
const { clubId } = req.params;
const transaction = await clubAccountService.createTransaction(Number(clubId), req.user?.id || null, req.body || {});
res.status(201).json({ transaction });
} catch (error) {
console.error('[createClubAccountTransaction] - Error:', error);
res.status(error?.status || 500).json({ error: error?.message || 'Kontenbewegung konnte nicht gespeichert werden.' });
}
}
async updateTransaction(req, res) {
try {
const { clubId, transactionId } = req.params;
const transaction = await clubAccountService.updateTransaction(Number(clubId), Number(transactionId), req.body || {});
res.json({ transaction });
} catch (error) {
console.error('[updateClubAccountTransaction] - Error:', error);
res.status(error?.status || 500).json({ error: error?.message || 'Kontenbewegung konnte nicht gespeichert werden.' });
}
}
async deleteTransaction(req, res) {
try {
const { clubId, transactionId } = req.params;
await clubAccountService.deleteTransaction(Number(clubId), Number(transactionId));
res.json({ success: true });
} catch (error) {
console.error('[deleteClubAccountTransaction] - Error:', error);
res.status(error?.status || 500).json({ error: error?.message || 'Kontenbewegung konnte nicht gelöscht werden.' });
}
}
}
export default new ClubAccountController();

View File

@@ -0,0 +1,132 @@
import clubCommunicationService from '../services/clubCommunicationService.js';
function handleError(res, scope, error) {
console.error(`[${scope}] - Error:`, error);
res.status(error?.status || 500).json({
error: error?.message || 'internalerror',
});
}
const clubCommunicationController = {
async list(req, res) {
try {
const { clubId } = req.params;
const payload = await clubCommunicationService.listClubCommunication(Number(clubId));
res.json(payload);
} catch (error) {
handleError(res, 'listClubCommunication', error);
}
},
async createThread(req, res) {
try {
const { clubId } = req.params;
const thread = await clubCommunicationService.createThread(Number(clubId), req.user?.id || null, req.body || {});
res.status(201).json({ thread });
} catch (error) {
handleError(res, 'createClubCommunicationThread', error);
}
},
async updateThread(req, res) {
try {
const { clubId, threadId } = req.params;
const thread = await clubCommunicationService.updateThread(Number(clubId), Number(threadId), req.body || {});
res.json({ thread });
} catch (error) {
handleError(res, 'updateClubCommunicationThread', error);
}
},
async deleteThread(req, res) {
try {
const { clubId, threadId } = req.params;
await clubCommunicationService.deleteThread(Number(clubId), Number(threadId));
res.status(204).send();
} catch (error) {
handleError(res, 'deleteClubCommunicationThread', error);
}
},
async addMessage(req, res) {
try {
const { clubId, threadId } = req.params;
const message = await clubCommunicationService.addMessage(Number(clubId), Number(threadId), req.user?.id || null, req.body || {});
res.status(201).json({ message });
} catch (error) {
handleError(res, 'addClubCommunicationMessage', error);
}
},
async sendThread(req, res) {
try {
const { clubId, threadId } = req.params;
const thread = await clubCommunicationService.sendThread(Number(clubId), Number(threadId), req.user?.id || null);
res.json({ thread });
} catch (error) {
handleError(res, 'sendClubCommunicationThread', error);
}
},
async createGroup(req, res) {
try {
const { clubId } = req.params;
const group = await clubCommunicationService.createGroup(Number(clubId), req.body || {});
res.status(201).json({ group });
} catch (error) {
handleError(res, 'createClubDistributionGroup', error);
}
},
async updateGroup(req, res) {
try {
const { clubId, groupId } = req.params;
const group = await clubCommunicationService.updateGroup(Number(clubId), Number(groupId), req.body || {});
res.json({ group });
} catch (error) {
handleError(res, 'updateClubDistributionGroup', error);
}
},
async deleteGroup(req, res) {
try {
const { clubId, groupId } = req.params;
await clubCommunicationService.deleteGroup(Number(clubId), Number(groupId));
res.status(204).send();
} catch (error) {
handleError(res, 'deleteClubDistributionGroup', error);
}
},
async createTemplate(req, res) {
try {
const { clubId } = req.params;
const template = await clubCommunicationService.createTemplate(Number(clubId), req.body || {});
res.status(201).json({ template });
} catch (error) {
handleError(res, 'createClubCommunicationTemplate', error);
}
},
async updateTemplate(req, res) {
try {
const { clubId, templateId } = req.params;
const template = await clubCommunicationService.updateTemplate(Number(clubId), Number(templateId), req.body || {});
res.json({ template });
} catch (error) {
handleError(res, 'updateClubCommunicationTemplate', error);
}
},
async deleteTemplate(req, res) {
try {
const { clubId, templateId } = req.params;
await clubCommunicationService.deleteTemplate(Number(clubId), Number(templateId));
res.status(204).send();
} catch (error) {
handleError(res, 'deleteClubCommunicationTemplate', error);
}
},
};
export default clubCommunicationController;

View File

@@ -2,6 +2,8 @@ import { Op } from 'sequelize';
import sequelize from '../database.js';
import {
CalendarEvent,
ClubCommunicationThread,
ClubInvoice,
ClubPaymentClaim,
ClubRequest,
ClubSepaMandate,
@@ -10,6 +12,7 @@ import {
Member,
TrainingGroup,
} from '../models/index.js';
import clubArchiveService from '../services/clubArchiveService.js';
import { getSafeErrorMessage } from '../utils/errorUtils.js';
function formatRequestWorkflowStage(stage) {
@@ -23,6 +26,8 @@ function formatRequestWorkflowStage(stage) {
sepa_pending: 'SEPA ausstehend',
onboarding_completed: 'Onboarding abgeschlossen',
sponsoring_contacted: 'Sponsoring kontaktiert',
sponsoring_offer_prepared: 'Sponsoringangebot vorbereitet',
sponsoring_followed_up: 'Sponsoring nachgefasst',
}[stage] || stage;
}
@@ -37,6 +42,10 @@ function formatTaskType(taskType) {
membership_collect_sepa_mandate: 'SEPA organisieren',
membership_assign_fee: 'Beitrag zuordnen',
request_sponsoring_reply: 'Sponsoring nachfassen',
sponsoring_prepare_offer: 'Sponsoringangebot vorbereiten',
sponsoring_follow_up: 'Sponsoring nachfassen',
document_review_required: 'Dokument prüfen',
sponsor_contract_renewal: 'Sponsoring verlängern',
member_missing_email: 'E-Mail ergänzen',
member_missing_birthdate: 'Geburtsdatum ergänzen',
member_missing_sepa_mandate: 'SEPA-Mandat einholen',
@@ -48,6 +57,26 @@ function formatTaskType(taskType) {
}[taskType] || taskType || 'Freie Aufgabe';
}
function formatCommunicationStatus(status) {
return {
draft: 'Entwurf',
scheduled: 'Geplant',
sent: 'Versendet',
archived: 'Archiviert',
}[status] || status || 'Status';
}
function formatInvoiceStatus(status) {
return {
draft: 'Entwurf',
issued: 'Gestellt',
partially_paid: 'Teilbezahlt',
paid: 'Bezahlt',
cancelled: 'Storniert',
archived: 'Archiviert',
}[status] || status || 'Status';
}
function formatEventDateRange(event) {
if (!event?.startDate) {
return null;
@@ -230,6 +259,9 @@ export const getClubDashboard = async (req, res) => {
upcomingEvents,
trainingGroups,
upcomingMatches,
communicationThreads,
invoices,
archive,
] = await Promise.all([
loadOptionalTableData(availableTables, 'club_requests', () => ClubRequest.findAll({
where: {
@@ -302,6 +334,22 @@ export const getClubDashboard = async (req, res) => {
order: [['date', 'ASC'], ['time', 'ASC']],
limit: 5,
})),
loadOptionalTableData(availableTables, 'club_communication_threads', () => ClubCommunicationThread.findAll({
where: { clubId },
attributes: ['id', 'subject', 'status', 'threadType', 'scheduledAt', 'sentAt', 'updatedAt'],
order: [['updatedAt', 'DESC'], ['createdAt', 'DESC']],
limit: 8,
})),
loadOptionalTableData(availableTables, 'club_invoices', () => ClubInvoice.findAll({
where: {
clubId,
archivedAt: null,
},
attributes: ['id', 'invoiceNumber', 'invoiceDirection', 'invoiceType', 'status', 'dueOn', 'grossAmountCents', 'currencyCode', 'updatedAt'],
order: [['updatedAt', 'DESC'], ['dueOn', 'ASC']],
limit: 8,
})),
clubArchiveService.getClubArchive(clubId),
]);
const visibleDashboardTasks = tasks.filter((task) => !task.assignedUserId || Number(task.assignedUserId) === currentUserId);
@@ -335,6 +383,12 @@ export const getClubDashboard = async (req, res) => {
(workflowStageCounts.sepa_pending || 0);
const duePaymentCount = paymentClaims.filter((claim) => claim.status === 'open').length;
const reminderCount = paymentClaims.filter((claim) => Number(claim.reminderLevel || 0) > 0).length;
const openInvoices = invoices.filter((invoice) => ['draft', 'issued', 'partially_paid'].includes(invoice.status));
const outgoingOpenInvoices = openInvoices.filter((invoice) => invoice.invoiceDirection === 'outgoing');
const incomingOpenInvoices = openInvoices.filter((invoice) => invoice.invoiceDirection === 'incoming');
const communicationDraftCount = communicationThreads.filter((thread) => thread.status === 'draft').length;
const communicationScheduledCount = communicationThreads.filter((thread) => thread.status === 'scheduled').length;
const communicationSentCount = communicationThreads.filter((thread) => thread.status === 'sent').length;
const recentMembers = members.slice(0, 4);
const upcomingTrainings = buildUpcomingTrainingSlots(trainingGroups);
const paidRatio = paymentClaims.length === 0
@@ -382,6 +436,7 @@ export const getClubDashboard = async (req, res) => {
value: `${openRequestCount + inProgressRequestCount}`,
meta: trialTrainingCount > 0 ? `${trialTrainingCount} Probetrainings` : null,
to: '/club-requests',
accent: 'amber',
items: [
createDashboardItem(`${openRequestCount} offen`, '/club-requests'),
createDashboardItem(`${inProgressRequestCount} in Bearbeitung`, '/club-requests'),
@@ -392,6 +447,7 @@ export const getClubDashboard = async (req, res) => {
value: `${onboardingCount}`,
meta: onboardingCount > 0 ? 'im Aufnahme- und Onboardingprozess' : 'Keine aktiven Onboarding-Fälle',
to: '/club-requests',
accent: 'green',
items: [
createDashboardItem(`${workflowStageCounts.trial_training_scheduled || 0} Probetrainings terminiert`, '/club-requests'),
createDashboardItem(`${workflowStageCounts.membership_reviewed || 0} Mitgliedsanfragen geprüft`, '/club-requests'),
@@ -401,13 +457,17 @@ export const getClubDashboard = async (req, res) => {
{
title: 'Offene Zahlungen',
value: `${paymentClaims.length}`,
meta: reminderCount > 0 ? `${reminderCount} mit Mahnstufe` : 'Keine Mahnungen aktiv',
to: '/club-tasks',
meta: [
duePaymentCount > 0 ? `${duePaymentCount} fällig offen` : null,
reminderCount > 0 ? `${reminderCount} mit Mahnstufe` : null,
].filter(Boolean).join(' · ') || 'Keine Mahnungen aktiv',
to: '/club-payments',
accent: 'red',
items: paymentClaims.slice(0, 3).map((claim) => {
const amount = `${(Number(claim.amountCents) / 100).toFixed(2)} ${claim.currencyCode || 'EUR'}`;
return createDashboardItem(
`${amount} fällig am ${claim.dueOn}`,
claim.memberId ? buildMemberRoute(claim.memberId, 'active') : '/club-tasks'
`${amount} · ${claim.dueOn ? `fällig ${claim.dueOn}` : 'ohne Fälligkeit'}`,
claim.memberId ? buildMemberRoute(claim.memberId, 'active') : '/club-payments'
);
}),
},
@@ -415,6 +475,7 @@ export const getClubDashboard = async (req, res) => {
title: 'Fehlende Daten',
value: `${missingFields.email + missingFields.birthDate + missingMandateCount}`,
to: '/members',
accent: 'blue',
items: [
createDashboardItem(`${missingFields.email} Mitglieder ohne E-Mail`, { path: '/members', query: { scope: 'dataIncomplete' } }),
createDashboardItem(`${missingFields.birthDate} Mitglieder ohne Geburtsdatum`, { path: '/members', query: { scope: 'dataIncomplete' } }),
@@ -426,6 +487,7 @@ export const getClubDashboard = async (req, res) => {
value: `${openTasks.length + inProgressTasks.length}`,
meta: overdueTaskCount > 0 ? `${overdueTaskCount} überfällig` : 'Keine überfälligen Aufgaben',
to: '/club-tasks',
accent: overdueTaskCount > 0 ? 'red' : 'green',
items: [
createDashboardItem(`${automatedOpenTasks.length} automatisch erzeugte Schritte`, '/club-tasks'),
...visibleDashboardTasks.slice(0, 3).map((task) => taskDashboardItem(task, task.title)),
@@ -433,6 +495,46 @@ export const getClubDashboard = async (req, res) => {
},
],
},
{
id: 'operations-room',
title: 'Kommunikation, Finanzen und Archiv',
cards: [
{
title: 'Kommunikation',
value: `${communicationThreads.length}`,
meta: `${communicationDraftCount} Entwürfe, ${communicationScheduledCount} geplant, ${communicationSentCount} versendet`,
to: '/club-communication',
accent: 'blue',
items: communicationThreads.slice(0, 3).map((thread) => createDashboardItem(
`${thread.subject || 'Ohne Betreff'} · ${formatCommunicationStatus(thread.status)}`,
'/club-communication'
)),
},
{
title: 'Rechnungen',
value: `${openInvoices.length}`,
meta: `${outgoingOpenInvoices.length} ausgehend, ${incomingOpenInvoices.length} eingehend`,
to: '/club-invoices',
accent: 'amber',
items: openInvoices.slice(0, 3).map((invoice) => createDashboardItem(
`${invoice.invoiceNumber || `Rechnung #${invoice.id}`} · ${formatInvoiceStatus(invoice.status)}`,
'/club-invoices'
)),
},
{
title: 'Archiv',
value: `${archive.summary.archivedRequests + archive.summary.archivedTasks + archive.summary.archivedClaims}`,
meta: `${archive.summary.inactiveMembers} inaktive Mitglieder`,
to: '/club-archive',
accent: 'neutral',
items: [
createDashboardItem(`${archive.summary.archivedRequests} archivierte Anfragen`, '/club-archive'),
createDashboardItem(`${archive.summary.archivedTasks} archivierte Aufgaben`, '/club-archive'),
createDashboardItem(`${archive.summary.archivedClaims} archivierte Beitragsfälle`, '/club-archive'),
],
},
],
},
{
id: 'appointments',
title: 'Aktuelle Termine',
@@ -537,9 +639,17 @@ export const getClubDashboard = async (req, res) => {
? 'Anfrage'
: task.automationSource === 'club_payment_claims'
? 'Zahlung'
: task.automationSource === 'calendar_events'
? 'Termin'
: 'Workflow';
: task.automationSource === 'club_documents'
? 'Dokument'
: task.automationSource === 'club_invoice_parties'
? 'Sponsor'
: task.automationSource === 'club_invoices'
? 'Rechnung'
: task.automationSource === 'club_communication'
? 'Kommunikation'
: task.automationSource === 'calendar_events'
? 'Termin'
: 'Workflow';
return taskDashboardItem(task, `${formatTaskType(task.taskType)} · ${sourceLabel}`);
}),
},

View File

@@ -0,0 +1,111 @@
import multer from 'multer';
import fs from 'fs';
import path from 'path';
import clubDocumentService from '../services/clubDocumentService.js';
const storage = multer.diskStorage({
destination: (req, file, cb) => {
fs.mkdirSync('uploads/temp', { recursive: true });
cb(null, 'uploads/temp/');
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
cb(null, `${file.fieldname}-${uniqueSuffix}${path.extname(file.originalname)}`);
},
});
const upload = multer({
storage,
limits: {
fileSize: 20 * 1024 * 1024,
},
fileFilter: (req, file, cb) => {
const allowedExtensions = ['.pdf', '.doc', '.docx', '.txt', '.csv', '.odt', '.xlsx'];
const allowedMimePatterns = ['pdf', 'msword', 'wordprocessingml.document', 'text/plain', 'csv', 'excel', 'opendocument.text'];
const extensionValid = allowedExtensions.includes(path.extname(file.originalname).toLowerCase());
const mimetypeValid = allowedMimePatterns.some((pattern) => file.mimetype && file.mimetype.toLowerCase().includes(pattern));
if (extensionValid && mimetypeValid) {
return cb(null, true);
}
cb(new Error('Nur PDF, DOC, DOCX, TXT, CSV, ODT und XLSX Dateien sind erlaubt!'));
},
});
export const uploadMiddleware = upload.single('document');
function cleanupTempFile(file) {
if (file?.path && fs.existsSync(file.path)) {
fs.unlinkSync(file.path);
}
}
export const listClubDocuments = async (req, res) => {
try {
const documents = await clubDocumentService.listClubDocuments(Number(req.params.clubId), req.query || {});
res.status(200).json({ documents });
} catch (error) {
res.status(error.status || 500).json({ error: error.message || 'internalerror' });
}
};
export const createClubDocument = async (req, res) => {
try {
const document = await clubDocumentService.createClubDocument(Number(req.params.clubId), req.user?.id || null, req.body || {}, req.file || null);
res.status(201).json({ document });
} catch (error) {
cleanupTempFile(req.file);
res.status(error.status || 500).json({ error: error.message || 'internalerror' });
}
};
export const updateClubDocument = async (req, res) => {
try {
const document = await clubDocumentService.updateClubDocument(Number(req.params.clubId), Number(req.params.documentId), req.user?.id || null, req.body || {}, req.file || null);
res.status(200).json({ document });
} catch (error) {
cleanupTempFile(req.file);
res.status(error.status || 500).json({ error: error.message || 'internalerror' });
}
};
export const archiveClubDocument = async (req, res) => {
try {
const document = await clubDocumentService.archiveClubDocument(Number(req.params.clubId), Number(req.params.documentId));
res.status(200).json({ document });
} catch (error) {
res.status(error.status || 500).json({ error: error.message || 'internalerror' });
}
};
export const deleteClubDocument = async (req, res) => {
try {
await clubDocumentService.deleteClubDocument(Number(req.params.clubId), Number(req.params.documentId));
res.status(204).end();
} catch (error) {
res.status(error.status || 500).json({ error: error.message || 'internalerror' });
}
};
export const downloadClubDocument = async (req, res) => {
try {
const versionNo = req.query.versionNo ? Number(req.query.versionNo) : null;
const { document, version } = await clubDocumentService.getDocumentDownload(Number(req.params.clubId), Number(req.params.documentId), versionNo);
if (!fs.existsSync(version.storagePath)) {
return res.status(404).json({ error: 'filenotfound' });
}
res.setHeader('Content-Disposition', `inline; filename="${document.title || version.fileName}"`);
res.setHeader('Content-Type', version.mimeType || 'application/octet-stream');
res.sendFile(version.storagePath);
} catch (error) {
res.status(error.status || 500).json({ error: error.message || 'internalerror' });
}
};
export default {
listClubDocuments,
createClubDocument,
updateClubDocument,
archiveClubDocument,
deleteClubDocument,
downloadClubDocument,
};

View File

@@ -0,0 +1,73 @@
import clubPaymentClaimService from '../services/clubPaymentClaimService.js';
class ClubPaymentClaimController {
async list(req, res) {
try {
const payload = await clubPaymentClaimService.listClaims(Number(req.params.clubId));
res.json(payload);
} catch (error) {
console.error('[listClubPaymentClaims] - Error:', error);
res.status(error?.status || 500).json({ error: error?.message || 'Zahlungsforderungen konnten nicht geladen werden.' });
}
}
async create(req, res) {
try {
const claim = await clubPaymentClaimService.createClaim(Number(req.params.clubId), req.body || {});
res.status(201).json({ claim });
} catch (error) {
console.error('[createClubPaymentClaim] - Error:', error);
res.status(error?.status || 500).json({ error: error?.message || 'Zahlungsforderung konnte nicht gespeichert werden.' });
}
}
async update(req, res) {
try {
const claim = await clubPaymentClaimService.updateClaim(Number(req.params.clubId), Number(req.params.claimId), req.body || {});
res.json({ claim });
} catch (error) {
console.error('[updateClubPaymentClaim] - Error:', error);
res.status(error?.status || 500).json({ error: error?.message || 'Zahlungsforderung konnte nicht gespeichert werden.' });
}
}
async updateStatus(req, res) {
try {
const claim = await clubPaymentClaimService.updateClaimStatus(
Number(req.params.clubId),
Number(req.params.claimId),
String(req.body?.status || '')
);
res.json({ claim });
} catch (error) {
console.error('[updateClubPaymentClaimStatus] - Error:', error);
res.status(error?.status || 500).json({ error: error?.message || 'Status konnte nicht gespeichert werden.' });
}
}
async registerPayment(req, res) {
try {
const claim = await clubPaymentClaimService.registerPayment(
Number(req.params.clubId),
Number(req.params.claimId),
req.body || {}
);
res.json({ claim });
} catch (error) {
console.error('[registerClubPaymentClaimPayment] - Error:', error);
res.status(error?.status || 500).json({ error: error?.message || 'Teilzahlung konnte nicht gespeichert werden.' });
}
}
async delete(req, res) {
try {
await clubPaymentClaimService.deleteClaim(Number(req.params.clubId), Number(req.params.claimId));
res.json({ success: true });
} catch (error) {
console.error('[deleteClubPaymentClaim] - Error:', error);
res.status(error?.status || 500).json({ error: error?.message || 'Zahlungsforderung konnte nicht gelöscht werden.' });
}
}
}
export default new ClubPaymentClaimController();

View File

@@ -119,6 +119,7 @@ export const listClubTasks = async (req, res) => {
res.status(200).json({
tasks,
taskDefinitions: automationOverview.definitions,
workflowSources: automationOverview.workflowSources || [],
taskSuggestions: automationOverview.suggestions,
assignableUsers,
});

View File

@@ -68,6 +68,7 @@ export const updateClubSettings = async (req, res) => {
countryCode,
stateCode,
memberDataQualityRequirements,
feeRules,
outgoingInvoicePrefix,
outgoingInvoiceNextNumber,
incomingInvoicePrefix,
@@ -81,6 +82,7 @@ export const updateClubSettings = async (req, res) => {
countryCode,
stateCode,
memberDataQualityRequirements,
feeRules,
outgoingInvoicePrefix,
outgoingInvoiceNextNumber,
incomingInvoicePrefix,

View File

@@ -29,11 +29,12 @@ const getWaitingApprovals = async(req, res) => {
const setClubMembers = async (req, res) => {
try {
const { id: memberId, firstname: firstName, lastname: lastName, street, city, postalCode, birthdate, phone, email, active,
testMembership, picsInInternetAllowed, gender, ttr, qttr, memberFormHandedOver, adultReleaseApproved, adultReserveApproved, contacts } = req.body;
testMembership, picsInInternetAllowed, gender, ttr, qttr, memberFormHandedOver, adultReleaseApproved, adultReserveApproved,
contributionGroupCode, contacts } = req.body;
const { id: clubId } = req.params;
const { authcode: userToken } = req.headers;
const addResult = await MemberService.setClubMember(userToken, clubId, memberId, firstName, lastName, street, city, postalCode, birthdate,
phone, email, active, testMembership, picsInInternetAllowed, gender, ttr, qttr, memberFormHandedOver, adultReleaseApproved, adultReserveApproved, contacts);
phone, email, active, testMembership, picsInInternetAllowed, gender, ttr, qttr, memberFormHandedOver, adultReleaseApproved, adultReserveApproved, contributionGroupCode, contacts);
// Emit Socket-Event wenn Member erfolgreich erstellt/aktualisiert wurde
if (addResult.status === 200) {