dev #23
@ -11,7 +11,6 @@ const fs = require('fs/promises');
|
||||
const path = require('path');
|
||||
const pool = require('../../database/database');
|
||||
|
||||
const CompanySettingsRepository = require('../../repositories/settings/CompanySettingsRepository');
|
||||
const CoffeeShippingFeeService = require('../subscriptions/CoffeeShippingFeeService');
|
||||
|
||||
class InvoiceService {
|
||||
@ -19,15 +18,6 @@ class InvoiceService {
|
||||
this.repo = new InvoiceRepository();
|
||||
}
|
||||
|
||||
_inferImageMimeFromBase64(base64) {
|
||||
const s = String(base64 || '').trim();
|
||||
if (!s) return 'image/png';
|
||||
if (s.startsWith('iVBORw0KGgo')) return 'image/png';
|
||||
if (s.startsWith('/9j/')) return 'image/jpeg';
|
||||
if (s.startsWith('R0lGOD')) return 'image/gif';
|
||||
return 'image/png';
|
||||
}
|
||||
|
||||
_templateHasVars(template, varNames) {
|
||||
if (!template) return false;
|
||||
return varNames.every((name) => {
|
||||
@ -47,15 +37,19 @@ class InvoiceService {
|
||||
}
|
||||
|
||||
_resolvePieceCountForQr(abonement) {
|
||||
const breakdown = Array.isArray(abonement?.pack_breakdown) ? abonement.pack_breakdown : [];
|
||||
const totalPacks = breakdown.reduce((sum, item) => sum + Number(item?.packs || item?.quantity || 0), 0);
|
||||
const piecesByPack = totalPacks ? totalPacks * 10 : null;
|
||||
if (piecesByPack != null) {
|
||||
if (piecesByPack >= 120) return 120;
|
||||
if (piecesByPack >= 60) return 60;
|
||||
return null;
|
||||
}
|
||||
|
||||
const packGroup = String(abonement?.pack_group || '').toLowerCase();
|
||||
if (packGroup.includes('120')) return 120;
|
||||
if (packGroup.includes('60')) return 60;
|
||||
|
||||
const breakdown = Array.isArray(abonement?.pack_breakdown) ? abonement.pack_breakdown : [];
|
||||
const totalPacks = breakdown.reduce((sum, item) => sum + Number(item?.packs || 0), 0);
|
||||
const piecesByPack = totalPacks ? totalPacks * 10 : null;
|
||||
if (piecesByPack === 60 || piecesByPack === 120) return piecesByPack;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -107,35 +101,8 @@ class InvoiceService {
|
||||
return items;
|
||||
}
|
||||
|
||||
async _getCompanySettingsQrDataUri(pieceCount) {
|
||||
const safePieceCount = pieceCount === 120 ? 120 : 60;
|
||||
try {
|
||||
const repo = new CompanySettingsRepository();
|
||||
const row = await repo.get();
|
||||
if (!row) return null;
|
||||
const raw = safePieceCount === 120 ? row?.qr_code_120_base64 : row?.qr_code_60_base64;
|
||||
const value = (raw == null) ? '' : String(raw).trim();
|
||||
if (!value) return null;
|
||||
if (value.startsWith('data:image/')) return value;
|
||||
const mime = this._inferImageMimeFromBase64(value);
|
||||
return `data:${mime};base64,${value}`;
|
||||
} catch (e) {
|
||||
logger.warn('InvoiceService._getCompanySettingsQrDataUri:error', {
|
||||
pieceCount: safePieceCount,
|
||||
message: e?.message,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async _buildQrCodeImageTag({ abonement }) {
|
||||
const pieceCount = this._resolvePieceCountForQr(abonement);
|
||||
if (!pieceCount) return '';
|
||||
|
||||
const dataUri = await this._getCompanySettingsQrDataUri(pieceCount);
|
||||
if (!dataUri) return '';
|
||||
|
||||
return `<img alt="QR Code" src="${this._escapeHtml(dataUri)}" />`;
|
||||
return '';
|
||||
}
|
||||
|
||||
_escapeHtml(value) {
|
||||
@ -312,11 +279,10 @@ class InvoiceService {
|
||||
|
||||
_prepareVariablesForTemplate(templateHtml, variables) {
|
||||
// Ensure backwards compatibility with older templates that only contain {{paymentInfoText}}
|
||||
// by injecting the Profit Planet bank block (and optionally QR) into paymentInfoText.
|
||||
// by injecting the Profit Planet bank block into paymentInfoText.
|
||||
if (!templateHtml) return variables;
|
||||
|
||||
const supportsBankVars = this._templateHasVars(templateHtml, ['bankAccountHolder', 'bankIban', 'bankBic']);
|
||||
const supportsQrVar = this._templateHasVars(templateHtml, ['qrCodeImage']);
|
||||
|
||||
const bankBlock = this._getProfitPlanetBankBlockHtml({
|
||||
bankAccountHolder: variables.bankAccountHolder || 'Profit Planet GmbH',
|
||||
@ -330,11 +296,6 @@ class InvoiceService {
|
||||
next.paymentInfoText = bankBlock;
|
||||
}
|
||||
|
||||
if (!supportsQrVar && variables.qrCodeImage) {
|
||||
// Append QR under payment info text when there's no dedicated placeholder
|
||||
next.paymentInfoText = `${next.paymentInfoText || ''}<br><br>${variables.qrCodeImage}`;
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
@ -559,29 +520,14 @@ class InvoiceService {
|
||||
|
||||
if (templateHtml) {
|
||||
const supportsBankVars = this._templateHasVars(templateHtml, ['bankAccountHolder', 'bankIban', 'bankBic']);
|
||||
const supportsQrVar = this._templateHasVars(templateHtml, ['qrCodeImage']);
|
||||
const pieceCountForQr = this._resolvePieceCountForQr(abonement);
|
||||
logger.info('InvoiceService._sendInvoiceEmail:template_compat', {
|
||||
invoiceId: invoice?.id,
|
||||
lang,
|
||||
supportsBankVars,
|
||||
supportsQrVar,
|
||||
pieceCountForQr,
|
||||
hasQrImage: Boolean(variables?.qrCodeImage),
|
||||
});
|
||||
|
||||
const varsForTemplate = this._prepareVariablesForTemplate(templateHtml, variables);
|
||||
html = this._renderTemplate(templateHtml, varsForTemplate);
|
||||
|
||||
// Final guard: if we still didn't embed QR but we expected one, force local template
|
||||
const missingQr = variables.qrCodeImage && !html.includes('data:image/png;base64,');
|
||||
if (missingQr) {
|
||||
const localTemplate = await this._loadLocalInvoiceTemplateHtml();
|
||||
if (localTemplate) {
|
||||
const varsForLocal = this._prepareVariablesForTemplate(localTemplate, variables);
|
||||
html = this._renderTemplate(localTemplate, varsForLocal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const htmlForPdf = html || await this._buildFallbackInvoiceHtml({ invoice, items, abonement, lang });
|
||||
|
||||
Loading…
Reference in New Issue
Block a user