diff --git a/extensions/general/mcp-server/server.ts b/extensions/general/mcp-server/server.ts index 88eb33bd..64897d91 100644 --- a/extensions/general/mcp-server/server.ts +++ b/extensions/general/mcp-server/server.ts @@ -3482,9 +3482,10 @@ export const tools: McpTool[] = [ }) if (dup) { const amountAbs = roundOre(Math.abs(Number(tx.amount))) + const amountUnit = !tx.currency || tx.currency === 'SEK' ? 'kr' : tx.currency const voucher = dup.voucher_label ? `verifikat ${dup.voucher_label}` : 'en befintlig verifikation' throw new Error( - `Möjlig dubblettbokföring: ${voucher} (${dup.entry_date}) bokför redan ${amountAbs} kr på bankkontot. ` + + `Möjlig dubblettbokföring: ${voucher} (${dup.entry_date}) bokför redan ${amountAbs} ${amountUnit} på bankkontot. ` + `Den här affärshändelsen ser redan ut att vara bokförd: länka transaktionen till den befintliga ` + `verifikationen i stället. Anropa igen med allow_duplicate=true först om det är en genuint separat affärshändelse.`, ) diff --git a/extensions/general/mcp-server/widgets/receipt-matcher.ts b/extensions/general/mcp-server/widgets/receipt-matcher.ts index 434ae7ad..74502bba 100644 --- a/extensions/general/mcp-server/widgets/receipt-matcher.ts +++ b/extensions/general/mcp-server/widgets/receipt-matcher.ts @@ -199,7 +199,9 @@ export const RECEIPT_MATCHER_HTML = ` const cls = isBooked ? 'booked' : (hasError ? 'error-row' : ''); const amt = Number(tx.amount); const amtClass = amt < 0 ? 'negative' : 'positive'; - const formatted = amt.toLocaleString('sv-SE', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' kr'; + // The tool handler passes each transaction's own currency through. + const suffix = !tx.currency || tx.currency === 'SEK' ? 'kr' : tx.currency; + const formatted = amt.toLocaleString('sv-SE', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' ' + esc(suffix); html += ''; html += '' + esc(tx.date || '') + ''; diff --git a/extensions/general/push-notifications/__tests__/payload-builders.test.ts b/extensions/general/push-notifications/__tests__/payload-builders.test.ts new file mode 100644 index 00000000..6a7ae8d2 --- /dev/null +++ b/extensions/general/push-notifications/__tests__/payload-builders.test.ts @@ -0,0 +1,22 @@ +import { describe, it, expect } from 'vitest' +import { createInvoiceOverduePayload, createInvoiceDuePayload } from '../payload-builders' + +describe('invoice notification payloads', () => { + // sv-SE grouping uses a non-breaking space: build expectations via the same + // formatter instead of typing literals with a plain space. + it('labels SEK amounts with kr', () => { + const payload = createInvoiceDuePayload('1042', 'Acme AB', 12500, 'SEK', '2026-08-01', 'inv-1') + expect(payload.body).toContain(`${(12500).toLocaleString('sv-SE')} kr`) + }) + + it('labels non-SEK amounts with their ISO code instead of kr', () => { + const payload = createInvoiceOverduePayload('1043', 'Odin Aero GmbH', 9800, 'EUR', '2026-07-01', 'inv-2') + expect(payload.body).toContain(`${(9800).toLocaleString('sv-SE')} EUR`) + expect(payload.body).not.toContain('kr (') + }) + + it('falls back to kr when currency is missing on legacy rows', () => { + const payload = createInvoiceDuePayload('1044', 'Acme AB', 100, '', '2026-08-01', 'inv-3') + expect(payload.body).toContain('100 kr') + }) +}) diff --git a/extensions/general/push-notifications/notification-scheduler.ts b/extensions/general/push-notifications/notification-scheduler.ts index d3d6c127..8184fe68 100644 --- a/extensions/general/push-notifications/notification-scheduler.ts +++ b/extensions/general/push-notifications/notification-scheduler.ts @@ -135,7 +135,7 @@ export async function sendInvoiceNotifications( const { data: invoices } = await supabase .from('invoices') - .select('id, user_id, invoice_number, total, due_date, customer:customers(name)') + .select('id, user_id, invoice_number, total, currency, due_date, customer:customers(name)') .in('status', ['sent', 'overdue']) .in('due_date', [in3DaysStr, todayStr, daysAgo3Str, daysAgo7Str]) @@ -180,6 +180,7 @@ export async function sendInvoiceNotifications( invoice.invoice_number, customerName, invoice.total, + invoice.currency, invoice.due_date, invoice.id ) @@ -187,6 +188,7 @@ export async function sendInvoiceNotifications( invoice.invoice_number, customerName, invoice.total, + invoice.currency, invoice.due_date, invoice.id ) diff --git a/extensions/general/push-notifications/payload-builders.ts b/extensions/general/push-notifications/payload-builders.ts index 865d598f..25d0e059 100644 --- a/extensions/general/push-notifications/payload-builders.ts +++ b/extensions/general/push-notifications/payload-builders.ts @@ -163,16 +163,22 @@ export function createTaxDeadlinePayload( } } +// Invoices carry their own currency; "kr" is only correct for SEK. +function formatInvoiceAmount(amount: number, currency: string): string { + return `${amount.toLocaleString('sv-SE')} ${!currency || currency === 'SEK' ? 'kr' : currency}` +} + export function createInvoiceOverduePayload( invoiceNumber: string, customerName: string, amount: number, + currency: string, dueDate: string, invoiceId: string ): NotificationPayload { return { title: `Obetald faktura #${invoiceNumber}`, - body: `${customerName} - ${amount.toLocaleString('sv-SE')} kr (förföll ${formatDate(dueDate)})`, + body: `${customerName} - ${formatInvoiceAmount(amount, currency)} (förföll ${formatDate(dueDate)})`, icon: '/icons/icon-192.png', badge: '/icons/badge-72.png', tag: `invoice-${invoiceId}`, @@ -189,12 +195,13 @@ export function createInvoiceDuePayload( invoiceNumber: string, customerName: string, amount: number, + currency: string, dueDate: string, invoiceId: string ): NotificationPayload { return { title: `Faktura #${invoiceNumber} förfaller`, - body: `${customerName} - ${amount.toLocaleString('sv-SE')} kr (${formatDate(dueDate)})`, + body: `${customerName} - ${formatInvoiceAmount(amount, currency)} (${formatDate(dueDate)})`, icon: '/icons/icon-192.png', badge: '/icons/badge-72.png', tag: `invoice-${invoiceId}`,