feat(BillingService): add file path resolution for PDF templates
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 43s

- Implemented a new method to resolve existing file paths for PDF templates, enhancing file handling robustness.
- Updated PDF storage path handling to ensure correct resolution of paths before file operations, improving error handling and reliability.
- Refactored related logic to utilize the new path resolution method across various template operations.
This commit is contained in:
Torsten Schulz (local)
2026-04-25 09:46:59 +02:00
parent 630c202fd2
commit 5f07a3e3d6

View File

@@ -30,6 +30,30 @@ const toMonthDate = (value, endOfMonth = false) => {
};
class BillingService {
_resolveExistingFilePath(storedPath) {
const rawPath = String(storedPath || '').trim();
if (!rawPath) return null;
const candidates = [];
if (path.isAbsolute(rawPath)) {
candidates.push(rawPath);
} else {
const normalizedRawPath = rawPath.replace(/^\.?[\\/]/, '');
candidates.push(path.resolve(rawPath));
candidates.push(path.resolve(process.cwd(), rawPath));
candidates.push(path.resolve(process.cwd(), normalizedRawPath));
candidates.push(path.resolve(process.cwd(), 'backend', normalizedRawPath));
candidates.push(path.resolve(process.cwd(), '..', normalizedRawPath));
}
for (const candidate of candidates) {
if (candidate && fs.existsSync(candidate)) {
return candidate;
}
}
return null;
}
_normalizeIban(raw, withoutCountry = false) {
const cleaned = String(raw || '').replace(/[^a-zA-Z0-9]/g, '').toUpperCase();
if (!cleaned) return '';
@@ -347,7 +371,7 @@ class BillingService {
clubId,
name,
description: payload?.description ? String(payload.description).trim() : null,
pdfStoragePath: file.path,
pdfStoragePath: path.resolve(file.path),
pdfFilename: file.originalname,
pdfMimeType: file.mimetype || 'application/pdf',
isActive: true,
@@ -386,9 +410,10 @@ class BillingService {
throw error;
}
if (template.pdfStoragePath && fs.existsSync(template.pdfStoragePath)) {
const storedTemplatePath = this._resolveExistingFilePath(template.pdfStoragePath);
if (storedTemplatePath) {
try {
fs.unlinkSync(template.pdfStoragePath);
fs.unlinkSync(storedTemplatePath);
} catch (error) {
// Ignore file cleanup errors, DB deletion already succeeded.
}
@@ -568,7 +593,8 @@ class BillingService {
return { status: 404, response: { success: false, error: 'Abrechnungslauf nicht gefunden' } };
}
await checkAccess(userToken, run.clubId);
if (!run.template?.pdfStoragePath || !fs.existsSync(run.template.pdfStoragePath)) {
const resolvedTemplatePath = this._resolveExistingFilePath(run.template?.pdfStoragePath);
if (!resolvedTemplatePath) {
return { status: 404, response: { success: false, error: 'Vorlagen-PDF nicht gefunden' } };
}
const computed = await this._collectTrainingSessions(run.clubId, run.periodStart, run.periodEnd);
@@ -583,7 +609,7 @@ class BillingService {
const generatedPath = path.resolve('uploads/billing-generated', generatedFilename);
await this._renderBillingPdfFromTemplate(
run.template.pdfStoragePath,
resolvedTemplatePath,
generatedPath,
run,
sessionsWithLabel,
@@ -656,13 +682,14 @@ class BillingService {
const template = await BillingTemplate.findByPk(templateId);
if (!template) return { status: 404, response: { success: false, error: 'Vorlage nicht gefunden' } };
await checkAccess(userToken, template.clubId);
if (!template.pdfStoragePath || !fs.existsSync(template.pdfStoragePath)) {
const resolvedTemplatePath = this._resolveExistingFilePath(template.pdfStoragePath);
if (!resolvedTemplatePath) {
return { status: 404, response: { success: false, error: 'Datei nicht gefunden' } };
}
return {
status: 200,
file: {
path: path.resolve(template.pdfStoragePath),
path: resolvedTemplatePath,
name: template.pdfFilename,
mimeType: template.pdfMimeType || 'application/pdf'
}