feat: Enhance club payment claims and task automation
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 1m0s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 1m0s
- Added `paid_amount_cents` column to `club_payment_claims` for better tracking of payments. - Implemented compatibility checks for the new column in `clubPaymentClaimService` and `clubTaskAutomationService`. - Updated various views to handle read-only states when editing is not allowed. - Refactored forms in `ClubAccountsView`, `ClubInvoicesView`, `ClubTasksView`, and `ClubCommunicationView` to use factory functions for cleaner code. - Introduced a new migration script to add the `paid_amount_cents` column if it doesn't exist and initialize it for existing paid claims. - Created a detailed plan for enhancing club features and ensuring stability in existing modules.
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
TrainingGroup,
|
||||
} from '../models/index.js';
|
||||
import clubArchiveService from '../services/clubArchiveService.js';
|
||||
import { hasClubPaymentClaimPaidAmountCentsColumn } from '../services/clubPaymentClaimCompatibility.js';
|
||||
import { getSafeErrorMessage } from '../utils/errorUtils.js';
|
||||
|
||||
function formatRequestWorkflowStage(stage) {
|
||||
@@ -249,6 +250,7 @@ export const getClubDashboard = async (req, res) => {
|
||||
today.setHours(0, 0, 0, 0);
|
||||
const todayIso = today.toISOString().slice(0, 10);
|
||||
const availableTables = await loadAvailableTables();
|
||||
const hasPaidAmountCentsColumn = await hasClubPaymentClaimPaidAmountCentsColumn();
|
||||
|
||||
const [
|
||||
requests,
|
||||
@@ -293,14 +295,16 @@ export const getClubDashboard = async (req, res) => {
|
||||
},
|
||||
attributes: ['memberId'],
|
||||
})),
|
||||
loadOptionalTableData(availableTables, 'club_payment_claims', () => ClubPaymentClaim.findAll({
|
||||
where: {
|
||||
clubId,
|
||||
status: { [Op.in]: ['open', 'partially_paid'] },
|
||||
archivedAt: null,
|
||||
},
|
||||
order: [['dueOn', 'ASC']],
|
||||
})),
|
||||
hasPaidAmountCentsColumn
|
||||
? loadOptionalTableData(availableTables, 'club_payment_claims', () => ClubPaymentClaim.findAll({
|
||||
where: {
|
||||
clubId,
|
||||
status: { [Op.in]: ['open', 'partially_paid'] },
|
||||
archivedAt: null,
|
||||
},
|
||||
order: [['dueOn', 'ASC']],
|
||||
}))
|
||||
: Promise.resolve([]),
|
||||
loadOptionalTableData(availableTables, 'calendar_events', () => CalendarEvent.findAll({
|
||||
where: {
|
||||
clubId,
|
||||
|
||||
@@ -10,6 +10,7 @@ import Season from '../models/Season.js';
|
||||
import User from '../models/User.js';
|
||||
import HttpError from '../exceptions/HttpError.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
import { hasUserClubAccess } from '../utils/userUtils.js';
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
const teamDataFetchJobs = new Map();
|
||||
@@ -635,15 +636,34 @@ class MyTischtennisUrlController {
|
||||
/**
|
||||
* Configure league from myTischtennis table URL
|
||||
* POST /api/mytischtennis/configure-league
|
||||
* Body: { url: string, createSeason?: boolean }
|
||||
* Body: { url: string, clubId: number, createSeason?: boolean }
|
||||
*/
|
||||
async configureLeague(req, res, next) {
|
||||
try {
|
||||
const { url, createSeason } = req.body;
|
||||
const { url, createSeason, clubId } = req.body;
|
||||
const userIdOrEmail = req.headers.userid;
|
||||
|
||||
if (!url) {
|
||||
throw new HttpError('URL is required', 400);
|
||||
if (!url || !clubId) {
|
||||
throw new HttpError('URL and clubId are required', 400);
|
||||
}
|
||||
|
||||
let userId = userIdOrEmail;
|
||||
if (isNaN(userIdOrEmail)) {
|
||||
const user = await User.findOne({ where: { email: userIdOrEmail } });
|
||||
if (!user) {
|
||||
throw new HttpError('User not found', 404);
|
||||
}
|
||||
userId = user.id;
|
||||
}
|
||||
|
||||
const normalizedClubId = Number.parseInt(clubId, 10);
|
||||
if (!Number.isInteger(normalizedClubId) || normalizedClubId <= 0) {
|
||||
throw new HttpError('clubId must be a valid number', 400);
|
||||
}
|
||||
|
||||
const hasAccess = await hasUserClubAccess(userId, normalizedClubId);
|
||||
if (!hasAccess) {
|
||||
throw new HttpError('Keine Berechtigung für diesen Verein', 403);
|
||||
}
|
||||
|
||||
// Parse URL
|
||||
@@ -669,6 +689,7 @@ class MyTischtennisUrlController {
|
||||
// Find or create league
|
||||
let league = await League.findOne({
|
||||
where: {
|
||||
clubId: normalizedClubId,
|
||||
myTischtennisGroupId: parsedData.groupId,
|
||||
association: parsedData.association
|
||||
}
|
||||
@@ -677,6 +698,7 @@ class MyTischtennisUrlController {
|
||||
if (!league) {
|
||||
league = await League.create({
|
||||
name: parsedData.groupnameOriginal, // Verwende die originale URL-kodierte Version
|
||||
clubId: normalizedClubId,
|
||||
myTischtennisGroupId: parsedData.groupId,
|
||||
association: parsedData.association,
|
||||
groupname: parsedData.groupnameOriginal, // Verwende die originale URL-kodierte Version
|
||||
|
||||
Reference in New Issue
Block a user