diff --git a/backend/services/billingService.js b/backend/services/billingService.js index 8700f324..6229a000 100644 --- a/backend/services/billingService.js +++ b/backend/services/billingService.js @@ -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' }