secondary email
This commit is contained in:
@@ -932,16 +932,48 @@ router.get('/:id/html', async (req, res) => {
|
||||
res.status(500).json({ error: 'Error generating HTML' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/:id/send-email', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { recipientEmail, customText } = req.body;
|
||||
// Akzeptiert entweder recipientEmail (Legacy, String) oder recipientEmails (Array).
|
||||
const { recipientEmail, recipientEmails, customText } = req.body;
|
||||
|
||||
if (!recipientEmail) {
|
||||
return res.status(400).json({ error: 'Recipient email is required.' });
|
||||
// Normalisiere zu einem Array. Strings werden gesplittet und gefiltert.
|
||||
let recipients = [];
|
||||
if (Array.isArray(recipientEmails)) {
|
||||
recipients = recipientEmails;
|
||||
} else if (recipientEmail) {
|
||||
recipients = [recipientEmail];
|
||||
}
|
||||
|
||||
recipients = recipients
|
||||
.map(e => (e || '').trim())
|
||||
.filter(Boolean);
|
||||
|
||||
// Dedupe, case-insensitive
|
||||
const seen = new Set();
|
||||
recipients = recipients.filter(e => {
|
||||
const key = e.toLowerCase();
|
||||
if (seen.has(key)) return false;
|
||||
seen.add(key);
|
||||
return true;
|
||||
});
|
||||
|
||||
if (recipients.length === 0) {
|
||||
return res.status(400).json({ error: 'At least one recipient email is required.' });
|
||||
}
|
||||
|
||||
// Einfache E-Mail-Validierung pro Empfänger
|
||||
const emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
const invalid = recipients.filter(e => !emailRe.test(e));
|
||||
if (invalid.length > 0) {
|
||||
return res.status(400).json({
|
||||
error: `Invalid email address(es): ${invalid.join(', ')}`
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Rechnungsdaten und Items laden (analog zu deiner PDF-Route)
|
||||
// 1. Rechnungsdaten und Items laden
|
||||
const invoiceResult = await pool.query(`
|
||||
SELECT i.*, c.name as customer_name, c.line1, c.line2, c.line3, c.line4, c.city, c.state, c.zip_code, c.account_number,
|
||||
COALESCE((SELECT SUM(pi.amount) FROM payment_invoices pi WHERE pi.invoice_id = i.id), 0) as amount_paid
|
||||
@@ -949,16 +981,16 @@ router.post('/:id/send-email', async (req, res) => {
|
||||
LEFT JOIN customers c ON i.customer_id = c.id
|
||||
WHERE i.id = $1
|
||||
`, [id]);
|
||||
|
||||
|
||||
if (invoiceResult.rows.length === 0) return res.status(404).json({ error: 'Invoice not found' });
|
||||
const invoice = invoiceResult.rows[0];
|
||||
|
||||
const itemsResult = await pool.query('SELECT * FROM invoice_items WHERE invoice_id = $1 ORDER BY item_order', [id]);
|
||||
|
||||
// 2. PDF generieren, aber nur im Speicher halten
|
||||
// 2. PDF generieren
|
||||
const templatePath = path.join(__dirname, '..', '..', 'templates', 'invoice-template.html');
|
||||
let html = await fs.readFile(templatePath, 'utf-8');
|
||||
|
||||
|
||||
const logoHTML = await getLogoHtml();
|
||||
const itemsHTML = renderInvoiceItems(itemsResult.rows, invoice);
|
||||
const authHTML = invoice.auth_code ? `<p style="margin-bottom: 20px; font-size: 13px;"><strong>Authorization:</strong> ${invoice.auth_code}</p>` : '';
|
||||
@@ -978,30 +1010,37 @@ router.post('/:id/send-email', async (req, res) => {
|
||||
.replace('{{AUTHORIZATION}}', authHTML)
|
||||
.replace('{{ITEMS}}', itemsHTML)
|
||||
.replace('{{PAYMENT_LINK}}', buildPaymentLinkHtml(invoice));
|
||||
|
||||
|
||||
const pdfBuffer = await generatePdfFromHtml(html);
|
||||
|
||||
// 3. E-Mail über SES versenden
|
||||
// 3. E-Mail über SES versenden — alle Empfänger im To-Feld
|
||||
const stripeLink = invoice.stripe_payment_link_url || null;
|
||||
const info = await sendInvoiceEmail(invoice, recipientEmail, customText, stripeLink, pdfBuffer);
|
||||
const info = await sendInvoiceEmail(invoice, recipients, customText, stripeLink, pdfBuffer);
|
||||
|
||||
// 4. Status in der DB aktualisieren
|
||||
await pool.query(
|
||||
`UPDATE invoices
|
||||
SET email_status = 'sent',
|
||||
`UPDATE invoices
|
||||
SET email_status = 'sent',
|
||||
sent_dates = array_append(COALESCE(sent_dates, '{}'), CURRENT_DATE),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $1`,
|
||||
[id]
|
||||
);
|
||||
|
||||
res.json({ success: true, messageId: info.messageId });
|
||||
console.log(`✉️ Invoice #${invoice.invoice_number} sent to: ${recipients.join(', ')}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
messageId: info.messageId,
|
||||
recipients
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error sending invoice email:', error);
|
||||
res.status(500).json({ error: 'Failed to send email: ' + error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// POST create Stripe Payment Link
|
||||
router.post('/:id/create-payment-link', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
Reference in New Issue
Block a user