@@ -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] 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-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-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.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import React, { useState, useEffect, useCallback } from 'react'
|
import React, { useState, useEffect, useCallback } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
import { useTranslations } from 'next-intl'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
@@ -19,7 +20,7 @@ import { ContextPicker } from '@/components/common/ContextPicker'
|
|||||||
import { cn, formatDate } from '@/lib/utils'
|
import { cn, formatDate } from '@/lib/utils'
|
||||||
import { getErrorMessage } from '@/lib/errors/get-error-message'
|
import { getErrorMessage } from '@/lib/errors/get-error-message'
|
||||||
import { roundOre } from '@/lib/money'
|
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 { formatVoucher } from '@/lib/bookkeeping/voucher-series-resolver'
|
||||||
import { AccountNumber } from '@/components/ui/account-number'
|
import { AccountNumber } from '@/components/ui/account-number'
|
||||||
import { ReportExportMenu } from '@/components/reports/ReportExportMenu'
|
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 }) {
|
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 [data, setData] = useState<ResultatrapportReport | null>(null)
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
const reportQs = reportQuery(periodId, dateRange, dimensionFilter)
|
const reportQs = reportQuery(periodId, dateRange, dimensionFilter)
|
||||||
|
const latestVouchers = formatLatestVouchers(data?.latest_vouchers)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@@ -786,9 +789,9 @@ export function ResultatrapportView({ periodId, dateRange, dimensionFilter = nul
|
|||||||
{ format: 'xlsx', href: `/api/reports/resultatrapport/xlsx?${reportQs}` },
|
{ format: 'xlsx', href: `/api/reports/resultatrapport/xlsx?${reportQs}` },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
{formatLatestVouchers(data.latest_vouchers) && (
|
{latestVouchers && (
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{LATEST_VOUCHERS_LABEL}: {formatLatestVouchers(data.latest_vouchers)}
|
{t('latest_posted_vouchers')}: {latestVouchers}
|
||||||
</p>
|
</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 }) {
|
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 [data, setData] = useState<BalansrapportReport | null>(null)
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
const reportQs = reportQuery(periodId, dateRange)
|
const reportQs = reportQuery(periodId, dateRange)
|
||||||
|
const latestVouchers = formatLatestVouchers(data?.latest_vouchers)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@@ -939,9 +944,9 @@ export function BalansrapportView({ periodId, dateRange, onNavigateToAccount }:
|
|||||||
{ format: 'xlsx', href: `/api/reports/balansrapport/xlsx?${reportQs}` },
|
{ format: 'xlsx', href: `/api/reports/balansrapport/xlsx?${reportQs}` },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
{formatLatestVouchers(data.latest_vouchers) && (
|
{latestVouchers && (
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{LATEST_VOUCHERS_LABEL}: {formatLatestVouchers(data.latest_vouchers)}
|
{t('latest_posted_vouchers')}: {latestVouchers}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|||||||
import { getLatestPostedVouchers } from '../latest-vouchers'
|
import { getLatestPostedVouchers } from '../latest-vouchers'
|
||||||
import { formatLatestVouchers, LATEST_VOUCHERS_LABEL } from '../latest-vouchers-format'
|
import { formatLatestVouchers, LATEST_VOUCHERS_LABEL } from '../latest-vouchers-format'
|
||||||
import { createQueuedMockSupabase } from '@/tests/helpers'
|
import { createQueuedMockSupabase } from '@/tests/helpers'
|
||||||
|
import enMessages from '../../../messages/en.json'
|
||||||
|
import svMessages from '../../../messages/sv.json'
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
@@ -184,4 +186,9 @@ describe('formatLatestVouchers', () => {
|
|||||||
// the books, and a reconciler told the wrong one chases a phantom gap.
|
// the books, and a reconciler told the wrong one chases a phantom gap.
|
||||||
expect(LATEST_VOUCHERS_LABEL).toBe('Senaste bokförda verifikat')
|
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')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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
|
* 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
|
* number the sequence counter handed out. A reconciler who assumes the other
|
||||||
|
|||||||
@@ -5381,6 +5381,7 @@
|
|||||||
"download_excel": "Download Excel",
|
"download_excel": "Download Excel",
|
||||||
"download_csv": "Download CSV",
|
"download_csv": "Download CSV",
|
||||||
"download_xml": "Download XML file",
|
"download_xml": "Download XML file",
|
||||||
|
"latest_posted_vouchers": "Latest posted vouchers",
|
||||||
"pdf_download_failed_title": "The PDF could not be generated",
|
"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_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.",
|
"pdf_download_network": "No connection to the server. No PDF was saved. Check your internet connection and try again.",
|
||||||
|
|||||||
@@ -5381,6 +5381,7 @@
|
|||||||
"download_excel": "Ladda ner Excel",
|
"download_excel": "Ladda ner Excel",
|
||||||
"download_csv": "Ladda ner CSV",
|
"download_csv": "Ladda ner CSV",
|
||||||
"download_xml": "Ladda ner XML-fil",
|
"download_xml": "Ladda ner XML-fil",
|
||||||
|
"latest_posted_vouchers": "Senaste bokförda verifikat",
|
||||||
"pdf_download_failed_title": "PDF:en kunde inte skapas",
|
"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_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.",
|
"pdf_download_network": "Ingen kontakt med servern. Ingen PDF sparades. Kontrollera din internetanslutning och försök igen.",
|
||||||
|
|||||||
Reference in New Issue
Block a user