feat(TrainingCancellation): enhance cancellation functionality and localization support
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 44s

- Updated the training cancellation controller to accept training group IDs, improving the cancellation process.
- Modified the database schema to include a JSON field for training group IDs in the training cancellations table.
- Enhanced the TrainingCancellation model to support the new training group IDs field.
- Updated the training cancellation service to normalize and handle training group IDs effectively.
- Added localization support for training cancellation features across multiple languages, improving user experience.
This commit is contained in:
Torsten Schulz (local)
2026-05-13 10:57:23 +02:00
parent 004801b1a6
commit 7981371136
22 changed files with 2306 additions and 275 deletions

View File

@@ -19,13 +19,14 @@ export const upsertTrainingCancellation = async (req, res) => {
try {
const { authcode: userToken } = req.headers;
const { clubId } = req.params;
const { date, startDate, endDate, reason } = req.body;
const { date, startDate, endDate, reason, trainingGroupIds } = req.body;
const result = await trainingCancellationService.upsertTrainingCancellation(
userToken,
clubId,
startDate || date,
reason,
endDate || date || startDate
endDate || date || startDate,
trainingGroupIds
);
res.status(200).json(result);
} catch (error) {

View File

@@ -0,0 +1,2 @@
ALTER TABLE training_cancellations
ADD COLUMN IF NOT EXISTS training_group_ids JSON NULL;

View File

@@ -5,6 +5,7 @@ CREATE TABLE IF NOT EXISTS training_cancellations (
end_date DATE NOT NULL,
date DATE NULL,
reason VARCHAR(255) NULL,
training_group_ids JSON NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uniq_training_cancellation_club_range (club_id, start_date, end_date),

View File

@@ -36,6 +36,11 @@ const TrainingCancellation = sequelize.define('TrainingCancellation', {
type: DataTypes.STRING(255),
allowNull: true,
},
trainingGroupIds: {
type: DataTypes.JSON,
allowNull: true,
field: 'training_group_ids',
},
}, {
tableName: 'training_cancellations',
underscored: true,

View File

@@ -24,10 +24,11 @@ class TrainingCancellationService {
});
}
async upsertTrainingCancellation(userToken, clubId, date, reason, endDate = null) {
async upsertTrainingCancellation(userToken, clubId, date, reason, endDate = null, trainingGroupIds = []) {
await checkAccess(userToken, clubId);
const normalizedStartDate = this.normalizeDate(date);
const normalizedEndDate = this.normalizeDate(endDate || date);
const normalizedTrainingGroupIds = this.normalizeTrainingGroupIds(trainingGroupIds);
if (!normalizedStartDate || !normalizedEndDate) {
throw new HttpError('Ungültiges Datum', 400);
}
@@ -41,6 +42,7 @@ class TrainingCancellationService {
endDate: normalizedEndDate,
date: normalizedStartDate,
reason: String(reason || '').trim() || null,
trainingGroupIds: normalizedTrainingGroupIds,
});
return cancellation || await TrainingCancellation.findOne({
where: { clubId, startDate: normalizedStartDate, endDate: normalizedEndDate },
@@ -71,6 +73,30 @@ class TrainingCancellationService {
const text = String(date || '').slice(0, 10);
return /^\d{4}-\d{2}-\d{2}$/.test(text) ? text : null;
}
normalizeTrainingGroupIds(trainingGroupIds) {
const values = this.parseTrainingGroupIdsValue(trainingGroupIds);
return [...new Set(
values
.map(id => Number.parseInt(id, 10))
.filter(id => Number.isInteger(id) && id > 0)
)];
}
parseTrainingGroupIdsValue(trainingGroupIds) {
if (Array.isArray(trainingGroupIds)) return trainingGroupIds;
if (trainingGroupIds === null || trainingGroupIds === undefined || trainingGroupIds === '') return [];
if (typeof trainingGroupIds === 'string') {
try {
const parsed = JSON.parse(trainingGroupIds);
if (Array.isArray(parsed)) return parsed;
if (parsed !== null && parsed !== undefined) return [parsed];
} catch (error) {
return trainingGroupIds.split(',');
}
}
return [];
}
}
export default new TrainingCancellationService();