diff --git a/DECISIONS.md b/DECISIONS.md index 38d1da84..479a17d8 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -741,3 +741,4 @@ One line per decision: `[YYYY-MM-DD] : `. Appended by agents and [2026-08-01] Out-of-order SIE IB activity is bounded by the target fiscal-period end, not its start: this excludes later-first imports while preserving same-period continuation suppression; successor IB resync checks the current error state plus a real target-period entry because result.success is finalized later, keeping replacement on a new engine voucher plus storno without letting a no-op import succeed through resync alone. [2026-08-01] Successor SIE IB replacement uses a specialized engine RPC instead of loosening the owner-only generic relink RPC: non-viewer members and scoped service-role imports are supported, while one period-row lock and expected-pointer CAS make the replacement voucher, storno, reversal status, pointer swap, and voucher sequence increments commit or roll back together. [2026-08-02] Out-of-order SIE IB resync requires exact date adjacency: the nearest later fiscal period can sit beyond a missing middle year, and replacing its authoritative IB with a non-adjacent UB would make that later period temporarily wrong until the gap was imported. +[2026-08-03] Issue #1267 localizes the latest-posted-voucher label in the web views while PDF and spreadsheet exports remain Swedish: translating one export label would create mixed-language files, so full export localization stays a separate surface-wide change. diff --git a/components/reports/views/index.tsx b/components/reports/views/index.tsx index 6604ad5d..1710454a 100644 --- a/components/reports/views/index.tsx +++ b/components/reports/views/index.tsx @@ -6,6 +6,7 @@ import React, { useState, useEffect, useCallback } from 'react' import Link from 'next/link' +import { useTranslations } from 'next-intl' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' @@ -19,7 +20,7 @@ import { ContextPicker } from '@/components/common/ContextPicker' import { cn, formatDate } from '@/lib/utils' import { getErrorMessage } from '@/lib/errors/get-error-message' import { roundOre } from '@/lib/money' -import { formatLatestVouchers, LATEST_VOUCHERS_LABEL } from '@/lib/reports/latest-vouchers-format' +import { formatLatestVouchers } from '@/lib/reports/latest-vouchers-format' import { formatVoucher } from '@/lib/bookkeeping/voucher-series-resolver' import { AccountNumber } from '@/components/ui/account-number' import { ReportExportMenu } from '@/components/reports/ReportExportMenu' @@ -715,10 +716,12 @@ export function BalanceSheetView({ periodId, dateRange, onNavigateToAccount }: { } export function ResultatrapportView({ periodId, dateRange, dimensionFilter = null, onNavigateToAccount }: { periodId: string; dateRange: DateRangeValue; dimensionFilter?: DimensionFilterValue | null; onNavigateToAccount: (account: string) => void }) { + const t = useTranslations('reports') const [data, setData] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const reportQs = reportQuery(periodId, dateRange, dimensionFilter) + const latestVouchers = formatLatestVouchers(data?.latest_vouchers) useEffect(() => { setLoading(true) @@ -786,9 +789,9 @@ export function ResultatrapportView({ periodId, dateRange, dimensionFilter = nul { format: 'xlsx', href: `/api/reports/resultatrapport/xlsx?${reportQs}` }, ]} /> - {formatLatestVouchers(data.latest_vouchers) && ( + {latestVouchers && (

- {LATEST_VOUCHERS_LABEL}: {formatLatestVouchers(data.latest_vouchers)} + {t('latest_posted_vouchers')}: {latestVouchers}

)} @@ -873,10 +876,12 @@ export function ResultatrapportView({ periodId, dateRange, dimensionFilter = nul } export function BalansrapportView({ periodId, dateRange, onNavigateToAccount }: { periodId: string; dateRange: DateRangeValue; onNavigateToAccount: (account: string) => void }) { + const t = useTranslations('reports') const [data, setData] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const reportQs = reportQuery(periodId, dateRange) + const latestVouchers = formatLatestVouchers(data?.latest_vouchers) useEffect(() => { setLoading(true) @@ -939,9 +944,9 @@ export function BalansrapportView({ periodId, dateRange, onNavigateToAccount }: { format: 'xlsx', href: `/api/reports/balansrapport/xlsx?${reportQs}` }, ]} /> - {formatLatestVouchers(data.latest_vouchers) && ( + {latestVouchers && (

- {LATEST_VOUCHERS_LABEL}: {formatLatestVouchers(data.latest_vouchers)} + {t('latest_posted_vouchers')}: {latestVouchers}

)} diff --git a/lib/reports/__tests__/latest-vouchers.test.ts b/lib/reports/__tests__/latest-vouchers.test.ts index 8d92788c..5022f278 100644 --- a/lib/reports/__tests__/latest-vouchers.test.ts +++ b/lib/reports/__tests__/latest-vouchers.test.ts @@ -2,6 +2,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' import { getLatestPostedVouchers } from '../latest-vouchers' import { formatLatestVouchers, LATEST_VOUCHERS_LABEL } from '../latest-vouchers-format' import { createQueuedMockSupabase } from '@/tests/helpers' +import enMessages from '../../../messages/en.json' +import svMessages from '../../../messages/sv.json' beforeEach(() => { vi.clearAllMocks() @@ -184,4 +186,9 @@ describe('formatLatestVouchers', () => { // the books, and a reconciler told the wrong one chases a phantom gap. expect(LATEST_VOUCHERS_LABEL).toBe('Senaste bokförda verifikat') }) + + it('keeps the web label explicit in both supported locales', () => { + expect(svMessages.reports.latest_posted_vouchers).toBe(LATEST_VOUCHERS_LABEL) + expect(enMessages.reports.latest_posted_vouchers).toBe('Latest posted vouchers') + }) }) diff --git a/lib/reports/latest-vouchers-format.ts b/lib/reports/latest-vouchers-format.ts index cdc30e52..aa1839a3 100644 --- a/lib/reports/latest-vouchers-format.ts +++ b/lib/reports/latest-vouchers-format.ts @@ -9,7 +9,9 @@ import type { LatestVoucherPerSeries } from '@/types' */ /** - * Swedish-only report surface (see .claude/rules/i18n.md). + * Swedish label for PDF and spreadsheet exports, whose complete document + * chrome currently renders in Swedish. Client views use the localized + * `reports.latest_posted_vouchers` message instead. * * The wording is load-bearing: this is the last POSTED number, not the last * number the sequence counter handed out. A reconciler who assumes the other diff --git a/messages/en.json b/messages/en.json index 96fcd73e..b5c874e0 100644 --- a/messages/en.json +++ b/messages/en.json @@ -5381,6 +5381,7 @@ "download_excel": "Download Excel", "download_csv": "Download CSV", "download_xml": "Download XML file", + "latest_posted_vouchers": "Latest posted vouchers", "pdf_download_failed_title": "The PDF could not be generated", "pdf_download_timeout": "The server did not respond in time. No PDF was saved. Try again.", "pdf_download_network": "No connection to the server. No PDF was saved. Check your internet connection and try again.", diff --git a/messages/sv.json b/messages/sv.json index 39a0da49..3ca5b2a9 100644 --- a/messages/sv.json +++ b/messages/sv.json @@ -5381,6 +5381,7 @@ "download_excel": "Ladda ner Excel", "download_csv": "Ladda ner CSV", "download_xml": "Ladda ner XML-fil", + "latest_posted_vouchers": "Senaste bokförda verifikat", "pdf_download_failed_title": "PDF:en kunde inte skapas", "pdf_download_timeout": "Servern svarade inte i tid. Ingen PDF sparades. Försök igen.", "pdf_download_network": "Ingen kontakt med servern. Ingen PDF sparades. Kontrollera din internetanslutning och försök igen.",