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}`,