Files
accounted/lib/bookkeeping/bas-catalog-client.ts
T
Mattsson 27383fd02b fix(inbox): surface full BAS catalog in BookDirectlyDialog account picker (#1543)
* fix(inbox): surface full BAS catalog in BookDirectlyDialog account picker

The account combobox in the inbox book-directly flow was fed only the
company's active chart, so typing a prefix like 65 showed just the two
activated 65xx accounts and a search for 6540 found nothing, which reads
as the account not existing. Pass the cached BAS catalogue (same pattern
as JournalEntryForm) so every standard account is searchable; picking a
not-yet-activated account flows through the existing
ActivateAccountsDialog rail at booking.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(bookkeeping): log BAS catalog fetch failures

Compliance swarm finding (SOC 2 CC7.2): loadBasCatalog swallowed fetch
errors silently, leaving catalog-load failures unobservable. Log inside
the client's catch, which is the only place the error actually surfaces:
callers' own .catch handlers are unreachable since the shared promise
already resolves to an empty list on failure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(bookkeeping): unit-cover loadBasCatalog fetch, fallback, and retry

CodeRabbit finding on PR #1543: the catalog client had no focused
coverage. Tests assert the success path with promise caching, empty-list
fallback with logging on non-OK responses, missing data field handling,
and cache clearing after a failure so the next call refetches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 00:55:33 +02:00

41 lines
1.3 KiB
TypeScript

'use client'
import type { SearchableAccount } from '@/lib/bookkeeping/account-search'
/**
* Client-side loader for the full BAS catalogue used by AccountCombobox.
*
* The catalogue is static reference data, identical for every company, so we
* fetch it once per session and share the in-flight promise across every
* combobox instance and form mount. A failed fetch clears the cache so the
* next caller retries rather than being stuck with an empty list.
*/
export interface CatalogAccount extends SearchableAccount {
account_number: string
account_name: string
account_class: number
account_group: string
description: string | null
}
let cache: Promise<CatalogAccount[]> | null = null
export function loadBasCatalog(): Promise<CatalogAccount[]> {
if (!cache) {
cache = fetch('/api/bookkeeping/accounts/bas-catalog')
.then((res) => {
if (!res.ok) throw new Error(`bas-catalog ${res.status}`)
return res.json()
})
.then((body) => (body?.data as CatalogAccount[]) ?? [])
.catch((err) => {
// Callers degrade gracefully (search falls back to the active chart),
// so this log is the only trace a catalog fetch failure leaves.
console.error('[bas-catalog] fetch failed:', err)
cache = null // allow a retry on the next call
return []
})
}
return cache
}