fix(reports): localize latest voucher label (#1365)

Closes #1267
This commit is contained in:
Mattsson
2026-08-03 15:18:21 +02:00
committed by GitHub
parent 9e54a8e400
commit d9fb5da16d
6 changed files with 23 additions and 6 deletions
+1
View File
@@ -741,3 +741,4 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. 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.
+10 -5
View File
@@ -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<ResultatrapportReport | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(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 && (
<p className="text-sm text-muted-foreground">
{LATEST_VOUCHERS_LABEL}: {formatLatestVouchers(data.latest_vouchers)}
{t('latest_posted_vouchers')}: {latestVouchers}
</p>
)}
@@ -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<BalansrapportReport | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(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 && (
<p className="text-sm text-muted-foreground">
{LATEST_VOUCHERS_LABEL}: {formatLatestVouchers(data.latest_vouchers)}
{t('latest_posted_vouchers')}: {latestVouchers}
</p>
)}
@@ -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')
})
})
+3 -1
View File
@@ -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
+1
View File
@@ -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.",
+1
View File
@@ -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.",