Updates, overview extended, club view implemented
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
132
backend/controllers/clubCommunicationController.js
Normal file
132
backend/controllers/clubCommunicationController.js
Normal 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;
|
||||
@@ -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}`);
|
||||
}),
|
||||
},
|
||||
|
||||
111
backend/controllers/clubDocumentController.js
Normal file
111
backend/controllers/clubDocumentController.js
Normal 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,
|
||||
};
|
||||
73
backend/controllers/clubPaymentClaimController.js
Normal file
73
backend/controllers/clubPaymentClaimController.js
Normal 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();
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user