diff --git a/controller/invoice/InvoiceController.js b/controller/invoice/InvoiceController.js index b74c99b..d4a7628 100644 --- a/controller/invoice/InvoiceController.js +++ b/controller/invoice/InvoiceController.js @@ -69,31 +69,4 @@ module.exports = { return res.status(400).json({ success: false, message: e.message }); } }, - - async downloadPdf(req, res) { - try { - const stream = await service.getInvoicePdfStream(req.params.id, req.user); - res.setHeader('Content-Type', 'application/pdf'); - res.setHeader('Content-Disposition', `inline; filename="invoice-${req.params.id}.pdf"`); - stream.pipe(res); - } catch (e) { - console.error('[INVOICE DOWNLOAD PDF]', e); - if (e.message?.includes('not found') || e.message?.includes('No PDF')) { - return res.status(404).json({ success: false, message: e.message }); - } - return res.status(400).json({ success: false, message: e.message }); - } - }, - - async sendEmailReport(req, res) { - try { - const { email, from, to } = req.body; - if (!email) return res.status(400).json({ success: false, message: 'email is required' }); - const data = await service.sendEmailReport({ email, from, to }); - return res.json({ success: true, data }); - } catch (e) { - console.error('[INVOICE EMAIL REPORT]', e); - return res.status(400).json({ success: false, message: e.message }); - } - }, }; diff --git a/routes/getRoutes.js b/routes/getRoutes.js index 9f32e0a..436795e 100644 --- a/routes/getRoutes.js +++ b/routes/getRoutes.js @@ -188,7 +188,6 @@ router.get('/news/:slug', NewsController.getPublic); // NEW: Invoice GETs router.get('/invoices/mine', authMiddleware, InvoiceController.listMine); -router.get('/invoices/:id/pdf', authMiddleware, InvoiceController.downloadPdf); router.get('/admin/invoices', authMiddleware, adminOnly, InvoiceController.adminList); router.get('/admin/invoices/:id/detail', authMiddleware, adminOnly, InvoiceController.getDetail); diff --git a/routes/postRoutes.js b/routes/postRoutes.js index dc4b1fd..d04e3ef 100644 --- a/routes/postRoutes.js +++ b/routes/postRoutes.js @@ -187,7 +187,6 @@ router.post('/abonements/referred', authMiddleware, ensureUserFromBody, Abonemme // NEW: Invoice POSTs router.post('/invoices/:id/pay', authMiddleware, adminOnly, InvoiceController.pay); -router.post('/admin/invoices/email-report', authMiddleware, adminOnly, InvoiceController.sendEmailReport); // Existing registration handlers (keep) router.post('/register/personal', (req, res) => { diff --git a/services/invoice/InvoiceService.js b/services/invoice/InvoiceService.js index 78d2609..3592b61 100644 --- a/services/invoice/InvoiceService.js +++ b/services/invoice/InvoiceService.js @@ -778,177 +778,6 @@ class InvoiceService { const payments = await this.repo.getPaymentsByInvoiceId(invoiceId); return { invoice, items, payments }; } - - async getInvoicePdfStream(invoiceId, user) { - const invoice = await this.repo.getById(invoiceId); - if (!invoice) throw new Error(`Invoice ${invoiceId} not found.`); - - // Non-admin users can only access their own invoices - const isAdmin = user?.role === 'admin' || user?.role === 'super_admin'; - if (!isAdmin && String(invoice.user_id) !== String(user?.id)) { - throw new Error('Invoice not found.'); - } - - if (!invoice.pdf_storage_key) { - throw new Error('No PDF available for this invoice.'); - } - - const command = new GetObjectCommand({ - Bucket: process.env.EXOSCALE_BUCKET, - Key: invoice.pdf_storage_key, - }); - const obj = await sharedExoscaleClient.send(command); - return obj.Body; - } - - /** - * Send an email report of all paid invoices to a given email. - * Optionally filter by date range. - * @param {{ email: string, from?: string, to?: string }} opts - * @returns {{ sentCount: number }} - */ - async sendEmailReport({ email, from, to }) { - if (!email) throw new Error('email is required'); - - const allInvoices = await this.repo.listAll({ status: 'paid', limit: 10000, offset: 0 }); - - // Optionally filter by date range - let paidInvoices = allInvoices; - if (from) { - const fromDate = new Date(from); - paidInvoices = paidInvoices.filter((inv) => { - const d = new Date(inv.issued_at || inv.created_at); - return d >= fromDate; - }); - } - if (to) { - const toDate = new Date(to); - toDate.setHours(23, 59, 59, 999); - paidInvoices = paidInvoices.filter((inv) => { - const d = new Date(inv.issued_at || inv.created_at); - return d <= toDate; - }); - } - - if (!paidInvoices.length) { - throw new Error('No paid invoices found matching the criteria.'); - } - - // Collect PDF attachments for each paid invoice - const attachments = []; - for (const inv of paidInvoices) { - if (inv.pdf_storage_key) { - try { - const command = new GetObjectCommand({ - Bucket: process.env.EXOSCALE_BUCKET, - Key: inv.pdf_storage_key, - }); - const obj = await sharedExoscaleClient.send(command); - if (typeof obj.Body.transformToByteArray === 'function') { - const bytes = await obj.Body.transformToByteArray(); - attachments.push({ - name: `${inv.invoice_number || `invoice-${inv.id}`}.pdf`, - content: Buffer.from(bytes).toString('base64'), - }); - } else { - const chunks = []; - for await (const chunk of obj.Body) { - chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); - } - attachments.push({ - name: `${inv.invoice_number || `invoice-${inv.id}`}.pdf`, - content: Buffer.concat(chunks).toString('base64'), - }); - } - } catch (e) { - logger.warn('InvoiceService.sendEmailReport:pdf_download_error', { - invoiceId: inv.id, - storageKey: inv.pdf_storage_key, - message: e?.message, - }); - } - } - } - - // Build email body with a summary table - const totalGross = paidInvoices.reduce((sum, inv) => sum + Number(inv.total_gross || 0), 0); - const currency = paidInvoices[0]?.currency || 'EUR'; - const dateRange = [from, to].filter(Boolean).join(' – ') || 'All time'; - - const invoiceRows = paidInvoices - .map( - (inv) => - ` - ${this._escapeHtml(inv.invoice_number || '')} - ${this._escapeHtml(inv.buyer_name || '-')} - ${inv.issued_at ? new Date(inv.issued_at).toISOString().slice(0, 10) : '-'} - ${this._formatAmount(inv.total_gross, inv.currency)} - `, - ) - .join(''); - - const subject = `ProfitPlanet – Paid Invoices Report (${dateRange})`; - - const html = ` - - - - - -
- - - -
-

Paid Invoices Report

-

${this._escapeHtml(dateRange)}

-
-

This report contains ${paidInvoices.length} paid invoice(s).

- - - - - - - - - - ${invoiceRows} - - - - - - -
Invoice #CustomerDateTotal
Total${this._formatAmount(totalGross, currency)}
- ${attachments.length ? '

The individual invoice PDFs are attached to this email.

' : '

No PDF attachments were available at this time.

'} -
-
- -`; - - const text = paidInvoices - .map((inv) => `${inv.invoice_number} | ${inv.buyer_name || '-'} | ${inv.issued_at ? new Date(inv.issued_at).toISOString().slice(0, 10) : '-'} | ${this._formatAmount(inv.total_gross, inv.currency)}`) - .join('\n'); - - await MailService.sendInvoiceEmail({ - email, - subject, - text: `Paid Invoices Report (${dateRange})\n\n${text}\n\nTotal: ${this._formatAmount(totalGross, currency)}`, - html, - lang: 'en', - attachments, - }); - - logger.info('InvoiceService.sendEmailReport:sent', { - recipientEmail: email, - sentCount: paidInvoices.length, - attachmentCount: attachments.length, - dateRange, - }); - - return { sentCount: paidInvoices.length }; - } } module.exports = InvoiceService;