const InvoiceRepository = require('../../repositories/invoice/InvoiceRepository'); const UnitOfWork = require('../../database/UnitOfWork'); // NEW const TaxRepository = require('../../repositories/tax/taxRepository'); // NEW class InvoiceService { constructor() { this.repo = new InvoiceRepository(); } // NEW: resolve current standard VAT rate for a buyer country code async resolveVatRateForCountry(countryCode) { if (!countryCode) return null; const uow = new UnitOfWork(); await uow.start(); const taxRepo = new TaxRepository(uow); try { const country = await taxRepo.getCountryByCode(String(countryCode).toUpperCase()); if (!country) { await uow.commit(); return null; } // get current vat row for this country const [rows] = await taxRepo.conn.query( `SELECT standard_rate FROM vat_rates WHERE country_id = ? AND effective_to IS NULL LIMIT 1`, [country.id] ); await uow.commit(); const rate = rows?.[0]?.standard_rate; return rate == null ? null : Number(rate); } catch (e) { await uow.rollback(); throw e; } } // Issue invoice for a subscription period, with items from pack_breakdown async issueForAbonement(abonement, periodStart, periodEnd, { actorUserId } = {}) { console.log('[INVOICE ISSUE] Inputs:', { abonement_id: abonement?.id, abonement_user_id: abonement?.user_id, abonement_purchaser_user_id: abonement?.purchaser_user_id, actorUserId, periodStart, periodEnd, }); const buyerName = [abonement.first_name, abonement.last_name].filter(Boolean).join(' ') || null; const buyerEmail = abonement.email || null; const addr = { street: abonement.street || null, postal_code: abonement.postal_code || null, city: abonement.city || null, country: abonement.country || null, }; const currency = abonement.currency || 'EUR'; // 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: `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 context = { source: 'abonement', pack_group: abonement.pack_group || null, period_start: periodStart, period_end: periodEnd, referred_by: abonement.referred_by || null, }; // CHANGED: prioritize token user id for invoice ownership const userIdForInvoice = actorUserId ?? abonement.user_id ?? abonement.purchaser_user_id ?? null; console.log('[INVOICE ISSUE] Resolved user_id for invoice:', userIdForInvoice); const invoice = await this.repo.createInvoiceWithItems({ source_type: 'subscription', source_id: abonement.id, user_id: userIdForInvoice, buyer_name: buyerName, buyer_email: buyerEmail, buyer_street: addr.street, buyer_postal_code: addr.postal_code, buyer_city: addr.city, buyer_country: addr.country, currency, items, status: 'issued', issued_at: new Date(), due_at: periodEnd, context, vat_rate, // NEW: persist on invoice }); console.log('[INVOICE ISSUE] Created invoice:', { id: invoice?.id, user_id: invoice?.user_id, source_type: invoice?.source_type, source_id: invoice?.source_id, total_net: invoice?.total_net, total_tax: invoice?.total_tax, total_gross: invoice?.total_gross, }); return invoice; } async markPaid(invoiceId, { payment_method, transaction_id, amount, paid_at = new Date(), details } = {}) { return this.repo.markPaid(invoiceId, { payment_method, transaction_id, amount, paid_at, details }); } async listMine(userId, { status, limit = 50, offset = 0 } = {}) { return this.repo.listByUser(userId, { status, limit, offset }); } async adminList({ status, limit = 200, offset = 0 } = {}) { return this.repo.listAll({ status, limit, offset }); } } module.exports = InvoiceService;