import type { Invoice, Customer, CompanySettings, InvoiceDocumentType } from '@/types' import { formatDate, getCompanyDisplayName, getCompanyPrimaryName } from '@/lib/utils' type EmailLang = 'sv' | 'en' // Customer-facing labels. Statutory chapter references stay intact in both // locales. lib/utils.ts formatCurrency() keeps the Swedish "kr" symbol for // in-app financial UI per the accounting standard; here we want the ISO code // so a non-Swedish recipient understands the unit. const LABELS = { sv: { docInvoice: 'Faktura', docCreditNote: 'Kreditfaktura', docProforma: 'Proformafaktura', docDeliveryNote: 'Följesedel', htmlLang: 'sv', documentFrom: (doc: string, sender: string) => `${doc} från ${sender}`, documentNumber: (doc: string) => `${doc}nummer:`, documentDate: (doc: string) => `${doc}datum:`, dueDate: 'Förfallodatum:', greeting: (firstName: string) => `Hej${firstName ? ` ${firstName}` : ''},`, bodyCreditNote: 'Bifogat hittar du en kreditfaktura som korrigerar en tidigare faktura.', bodyInvoice: 'Tack för ditt förtroende! Bifogat hittar du din faktura.', toPay: 'Att betala:', paymentHeading: 'Betalningsinformation', bank: 'Bank:', account: 'Kontonummer:', iban: 'IBAN:', bic: 'BIC/SWIFT:', message: 'Meddelande:', questions: 'Har du frågor om fakturan? Svara direkt på detta mejl så hjälper vi dig.', sincerely: 'Med vänliga hälsningar,', orgNo: 'Org.nr:', vat: 'VAT:', fSkatt: 'Innehar F-skattsedel', documentSummary: (doc: string) => `${doc.toLowerCase()}sammanfattning:`, subjectFrom: (doc: string, num: string, sender: string) => `${doc} ${num} från ${sender}`, }, en: { docInvoice: 'Invoice', docCreditNote: 'Credit note', docProforma: 'Proforma invoice', docDeliveryNote: 'Delivery note', htmlLang: 'en', documentFrom: (doc: string, sender: string) => `${doc} from ${sender}`, documentNumber: (doc: string) => `${doc} number:`, documentDate: (doc: string) => `${doc} date:`, dueDate: 'Due date:', greeting: (firstName: string) => `Hi${firstName ? ` ${firstName}` : ''},`, bodyCreditNote: 'Attached you will find a credit note that corrects an earlier invoice.', bodyInvoice: 'Thank you for your business. Attached you will find your invoice.', toPay: 'Total due:', paymentHeading: 'Payment information', bank: 'Bank:', account: 'Account number:', iban: 'IBAN:', bic: 'BIC/SWIFT:', message: 'Reference:', questions: 'Questions about the invoice? Reply directly to this email and we will help you.', sincerely: 'Kind regards,', orgNo: 'Reg. no.:', vat: 'VAT:', // Statutory Swedish phrase — kept verbatim in both locales. F-skatt is a // Swedish tax-authority designation; translating it has no legal standing. fSkatt: 'Innehar F-skattsedel', documentSummary: (doc: string) => `${doc} summary:`, subjectFrom: (doc: string, num: string, sender: string) => `${doc} ${num} from ${sender}`, }, } as const function resolveLang(customer: Customer): EmailLang { return customer.language === 'en' ? 'en' : 'sv' } function getDocumentLabel(invoice: Invoice, lang: EmailLang): string { const L = LABELS[lang] if (invoice.credited_invoice_id) return L.docCreditNote const docType = (invoice as Invoice & { document_type?: InvoiceDocumentType }).document_type || 'invoice' if (docType === 'proforma') return L.docProforma if (docType === 'delivery_note') return L.docDeliveryNote return L.docInvoice } // Currency for the customer-facing total — explicit ISO code so a non-Swedish // recipient reads "1 234,56 SEK" instead of the Swedish symbol "kr". Use the // English locale for digit grouping when the email is in English so the comma // thousands separator matches reader expectation. function formatCurrencyForCustomer(amount: number, currency: string, lang: EmailLang): string { const formatted = new Intl.NumberFormat(lang === 'en' ? 'en-US' : 'sv-SE', { style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2, }).format(amount) return `${formatted} ${currency}` } export interface InvoiceEmailData { invoice: Invoice customer: Customer company: CompanySettings } // Minimal hex validator — guards against branding values that bypass the // settings UI and could inject CSS via crafted strings. Anything malformed // falls back to the legacy default. function safeBrandingColor(value: string | null | undefined, fallback: string): string { if (!value) return fallback return /^#[0-9A-Fa-f]{6}$/.test(value) ? value : fallback } /** * Generate HTML email for sending an invoice */ export function generateInvoiceEmailHtml(data: InvoiceEmailData): string { const { invoice, customer, company } = data const lang = resolveLang(customer) const L = LABELS[lang] const documentType = getDocumentLabel(invoice, lang) const isCreditNote = !!invoice.credited_invoice_id const docType = (invoice as Invoice & { document_type?: InvoiceDocumentType }).document_type || 'invoice' const isDeliveryNote = docType === 'delivery_note' const isProforma = docType === 'proforma' const hidePayment = isCreditNote || isDeliveryNote || isProforma const firstName = customer.name ? customer.name.split(' ')[0] : '' // Primary color drives the heading accent and the highlighted total. The // accent is sanitized to a strict hex pattern — anything else falls back // to the legacy dark neutral. Credit notes intentionally use the success // green for the total regardless of branding, because the customer's brain // is wired to expect "money coming back = green". const primaryColor = safeBrandingColor(company.invoice_primary_color, '#111111') return ` ${documentType} ${invoice.invoice_number}

${L.documentFrom(documentType, getCompanyPrimaryName(company))}

${L.documentNumber(documentType)} ${invoice.invoice_number}

${L.greeting(firstName)}

${isCreditNote ? L.bodyCreditNote : L.bodyInvoice}

${L.documentNumber(documentType)} ${invoice.invoice_number}
${L.documentDate(documentType)} ${formatDate(invoice.invoice_date)}
${L.dueDate} ${formatDate(invoice.due_date)}
${L.toPay} ${formatCurrencyForCustomer(invoice.total, invoice.currency, lang)}
${!hidePayment ? `

${L.paymentHeading}

${company.bank_name ? ` ` : ''} ${company.clearing_number && company.account_number ? ` ` : ''} ${company.iban ? ` ` : ''} ${company.bic ? ` ` : ''}
${L.bank} ${company.bank_name}
${L.account} ${company.clearing_number}-${company.account_number}
${L.iban} ${company.iban}
${L.bic} ${company.bic}
${L.message} ${invoice.invoice_number}
` : ''}

${L.questions}

${L.sincerely}
${getCompanyPrimaryName(company)}

${company.org_number ? `

${L.orgNo} ${company.org_number} ${company.vat_number ? ` | ${L.vat} ${company.vat_number}` : ''} ${company.f_skatt ? ` | ${L.fSkatt}` : ''}

` : ''}
` } /** * Generate plain text email for sending an invoice */ export function generateInvoiceEmailText(data: InvoiceEmailData): string { const { invoice, customer, company } = data const lang = resolveLang(customer) const L = LABELS[lang] const documentType = getDocumentLabel(invoice, lang) const isCreditNote = !!invoice.credited_invoice_id const docType = (invoice as Invoice & { document_type?: InvoiceDocumentType }).document_type || 'invoice' const isDeliveryNote = docType === 'delivery_note' const isProforma = docType === 'proforma' const hidePayment = isCreditNote || isDeliveryNote || isProforma const firstName = customer.name ? customer.name.split(' ')[0] : '' let text = `${L.documentFrom(documentType, getCompanyPrimaryName(company))}\n` text += `${L.documentNumber(documentType)} ${invoice.invoice_number}\n\n` text += `${L.greeting(firstName)}\n\n` text += `${isCreditNote ? L.bodyCreditNote : L.bodyInvoice}\n\n` text += `${L.documentSummary(documentType)}\n` text += `---\n` text += `${L.documentNumber(documentType)} ${invoice.invoice_number}\n` text += `${L.documentDate(documentType)} ${formatDate(invoice.invoice_date)}\n` text += `${L.dueDate} ${formatDate(invoice.due_date)}\n` text += `${L.toPay} ${formatCurrencyForCustomer(invoice.total, invoice.currency, lang)}\n` text += `---\n\n` if (!hidePayment) { text += `${L.paymentHeading}:\n` if (company.bank_name) text += `${L.bank} ${company.bank_name}\n` if (company.clearing_number && company.account_number) { text += `${L.account} ${company.clearing_number}-${company.account_number}\n` } if (company.iban) text += `${L.iban} ${company.iban}\n` if (company.bic) text += `${L.bic} ${company.bic}\n` text += `${L.message} ${invoice.invoice_number}\n\n` } text += `${L.questions}\n\n` text += `${L.sincerely}\n` text += `${getCompanyDisplayName(company)}\n` if (company.org_number) { text += `\n${L.orgNo} ${company.org_number}` if (company.vat_number) text += ` | ${L.vat} ${company.vat_number}` if (company.f_skatt) text += ` | ${L.fSkatt}` text += `\n` } return text } /** * Generate email subject for an invoice */ export function generateInvoiceEmailSubject(data: InvoiceEmailData): string { const { invoice, customer, company } = data const lang = resolveLang(customer) const L = LABELS[lang] const documentType = getDocumentLabel(invoice, lang) return L.subjectFrom(documentType, invoice.invoice_number ?? '', getCompanyPrimaryName(company)) }