From b87b9994557f1a81cc200d66e18bb4db6510d2a4 Mon Sep 17 00:00:00 2001 From: seaznCode Date: Fri, 20 Mar 2026 16:27:37 +0100 Subject: [PATCH] feat: add shipping fee resolution to invoice items and refactor item building logic --- services/invoice/InvoiceService.js | 73 ++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 20 deletions(-) diff --git a/services/invoice/InvoiceService.js b/services/invoice/InvoiceService.js index cfbd226..3592b61 100644 --- a/services/invoice/InvoiceService.js +++ b/services/invoice/InvoiceService.js @@ -12,6 +12,7 @@ const fs = require('fs/promises'); const path = require('path'); const CompanySettingsRepository = require('../../repositories/settings/CompanySettingsRepository'); +const CoffeeShippingFeeService = require('../subscriptions/CoffeeShippingFeeService'); class InvoiceService { constructor() { @@ -58,6 +59,54 @@ class InvoiceService { return null; } + async _resolveShippingFeeItem({ abonement, vatRate, lang }) { + const pieceCount = this._resolvePieceCountForQr(abonement); + if (!pieceCount) return null; + + const shippingFee = await CoffeeShippingFeeService.get(pieceCount); + const unitPrice = Number(shippingFee?.price || 0); + if (!(unitPrice > 0)) return null; + + return { + product_id: null, + sku: `SHIPPING-${pieceCount}`, + description: lang === 'de' ? `Versandkosten (${pieceCount} Stk.)` : `Shipping fee (${pieceCount} pcs)` , + quantity: 1, + unit_price: unitPrice, + tax_rate: vatRate, + }; + } + + async _buildInvoiceItems({ abonement, vatRate, lang }) { + const breakdown = Array.isArray(abonement?.pack_breakdown) ? abonement.pack_breakdown : []; + const items = breakdown.length + ? breakdown.map((b) => ({ + product_id: Number(b.coffee_table_id) || null, + sku: `COFFEE-${b.coffee_table_id || 'N/A'}`, + description: b.coffee_title || `Coffee subscription: ${b.coffee_table_id}`, + quantity: Number(b.packs || 1), + unit_price: Number(b.price_per_pack || 0), + tax_rate: b.tax_rate != null ? Number(b.tax_rate) : vatRate, + })) + : [ + { + product_id: null, + sku: 'SUBSCRIPTION', + description: `Subscription ${abonement?.pack_group || ''}`, + quantity: 1, + unit_price: Number(abonement?.price || 0), + tax_rate: vatRate, + }, + ]; + + const shippingItem = await this._resolveShippingFeeItem({ abonement, vatRate, lang }); + if (shippingItem) { + items.push(shippingItem); + } + + return items; + } + async _getCompanySettingsQrDataUri(pieceCount) { const safePieceCount = pieceCount === 120 ? 120 : 60; try { @@ -598,26 +647,7 @@ class InvoiceService { // NEW: resolve invoice vat_rate (standard) from buyer country const vat_rate = await this.resolveVatRateForCountry(addr.country); - const breakdown = Array.isArray(abonement.pack_breakdown) ? abonement.pack_breakdown : []; - const items = breakdown.length - ? breakdown.map((b) => ({ - product_id: Number(b.coffee_table_id) || null, - sku: `COFFEE-${b.coffee_table_id || 'N/A'}`, - description: b.coffee_title || `Coffee subscription: ${b.coffee_table_id}`, - quantity: Number(b.packs || 1), - unit_price: Number(b.price_per_pack || 0), - tax_rate: b.tax_rate != null ? Number(b.tax_rate) : vat_rate, // CHANGED: default to invoice vat_rate - })) - : [ - { - product_id: null, - sku: 'SUBSCRIPTION', - description: `Subscription ${abonement.pack_group || ''}`, - quantity: 1, - unit_price: Number(abonement.price || 0), - tax_rate: vat_rate, // CHANGED - }, - ]; + const items = await this._buildInvoiceItems({ abonement, vatRate: vat_rate, lang }); const context = { source: 'abonement', @@ -677,6 +707,9 @@ class InvoiceService { logger.error('InvoiceService.issueForAbonement:invoice_email_error', { invoiceId: invoice?.id, message: mailError?.message, + stack: mailError?.stack, + brevoStatus: mailError?.statusCode ?? mailError?.response?.status ?? null, + brevoData: mailError?.body ?? mailError?.response?.data ?? mailError?.response?.text ?? null, }); }