fix(transactions): resolve bank account from cash_account_id in bulk-book and direct-book dialogs (#770)
Follow-up to #769: applies the same fetch-on-open + resolveAccount pattern to BulkBookDialog (manual tab bank-leg pre-fill) and BookDirectlyDialog (settlement line in buildPrefillLines). Both dialogs now fetch /api/cash-accounts when they open, gate the pre-fill on the fetch resolving, and resolve the correct BAS ledger account per transaction instead of always emitting '1930'. Adds lib/cash-accounts/resolve-account.ts (cherry of the utility from #769) since that PR is not yet merged into main. Signed-off-by: Jonas Flodén <jonas@floden.nu>
This commit is contained in:
@@ -4,6 +4,8 @@ import { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { createClient } from '@/lib/supabase/client'
|
||||
import { useCompany } from '@/contexts/CompanyContext'
|
||||
import { resolveAccount } from '@/lib/cash-accounts/resolve-account'
|
||||
import type { CashAccount } from '@/types'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -81,6 +83,8 @@ export default function BulkBookDialog({
|
||||
const [templates, setTemplates] = useState<BookingTemplateLibrary[]>([])
|
||||
const [loadingTemplates, setLoadingTemplates] = useState(true)
|
||||
const [selectedTemplateId, setSelectedTemplateId] = useState<string | null>(null)
|
||||
// null = fetch pending; array = loaded (may be empty on error — falls back to '1930')
|
||||
const [cashAccounts, setCashAccounts] = useState<CashAccount[] | null>(null)
|
||||
const [mode, setMode] = useState<Mode>('one_line_per_tx')
|
||||
const [description, setDescription] = useState('')
|
||||
const [manualLines, setManualLines] = useState<ManualLine[]>([])
|
||||
@@ -137,6 +141,28 @@ export default function BulkBookDialog({
|
||||
}
|
||||
}, [open, company, supabase])
|
||||
|
||||
// Fetch cash accounts once when the dialog opens so the manual bank-leg
|
||||
// pre-fill can resolve the correct ledger account per transaction.
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
setCashAccounts(null)
|
||||
let cancelled = false
|
||||
fetch('/api/cash-accounts')
|
||||
.then((r) => {
|
||||
if (!r.ok) throw new Error(`cash-accounts fetch failed: ${r.status}`)
|
||||
return r.json()
|
||||
})
|
||||
.then((json) => {
|
||||
if (cancelled) return
|
||||
setCashAccounts((json.data ?? []) as CashAccount[])
|
||||
})
|
||||
.catch(() => {
|
||||
// Fall back to empty list — resolveAccount will return '1930'
|
||||
if (!cancelled) setCashAccounts([])
|
||||
})
|
||||
return () => { cancelled = true }
|
||||
}, [open])
|
||||
|
||||
// Reset state when dialog closes so the next open starts clean.
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
@@ -145,31 +171,43 @@ export default function BulkBookDialog({
|
||||
setMode('one_line_per_tx')
|
||||
setDescription('')
|
||||
setManualLines([])
|
||||
setCashAccounts(null)
|
||||
} else if (sharedDate) {
|
||||
// Pre-fill description with a sensible default the user can edit.
|
||||
setDescription(t('default_description', { date: sharedDate }))
|
||||
}
|
||||
}, [open, sharedDate, t])
|
||||
|
||||
// Pre-fill the bank side from the txs (one line per tx on 1930 with
|
||||
// the correct Dr/Cr direction). We intentionally do NOT pre-fill a
|
||||
// counterpart account: swedish-compliance flagged that a hardcoded
|
||||
// 3001/5800 prefill nudges users into submitting verifikat without a
|
||||
// VAT line (26xx) for momsregistrerade affärshändelser. The bank
|
||||
// side is the unambiguous part the user always wants; the
|
||||
// Pre-fill the bank side from the txs (one line per tx with the resolved
|
||||
// ledger account and correct Dr/Cr direction). We intentionally do NOT
|
||||
// pre-fill a counterpart account: swedish-compliance flagged that a
|
||||
// hardcoded 3001/5800 prefill nudges users into submitting verifikat
|
||||
// without a VAT line (26xx) for momsregistrerade affärshändelser. The
|
||||
// bank side is the unambiguous part the user always wants; the
|
||||
// counterpart (and any VAT split) is the user's responsibility.
|
||||
// Gate on cashAccounts !== null so lines are only built after the account
|
||||
// fetch resolves — this prevents the form from briefly showing '1930' when
|
||||
// the resolved account differs.
|
||||
useEffect(() => {
|
||||
if (tab !== 'manual') return
|
||||
if (manualLines.length > 0) return
|
||||
if (transactions.length === 0) return
|
||||
if (cashAccounts === null) return
|
||||
const isIncome = direction === 'income'
|
||||
const bankLines: ManualLine[] = transactions.map((tx) => ({
|
||||
id: newManualLineId(),
|
||||
account_number: '1930',
|
||||
debit_amount: isIncome ? Math.abs(tx.amount).toFixed(2).replace('.', ',') : '',
|
||||
credit_amount: isIncome ? '' : Math.abs(tx.amount).toFixed(2).replace('.', ','),
|
||||
line_description: (tx.description || '').slice(0, 40).trim(),
|
||||
}))
|
||||
const bankLines: ManualLine[] = transactions.map((tx) => {
|
||||
const { account } = resolveAccount(
|
||||
cashAccounts,
|
||||
tx.cash_account_id ?? null,
|
||||
tx.currency ?? 'SEK',
|
||||
)
|
||||
return {
|
||||
id: newManualLineId(),
|
||||
account_number: account,
|
||||
debit_amount: isIncome ? Math.abs(tx.amount).toFixed(2).replace('.', ',') : '',
|
||||
credit_amount: isIncome ? '' : Math.abs(tx.amount).toFixed(2).replace('.', ','),
|
||||
line_description: (tx.description || '').slice(0, 40).trim(),
|
||||
}
|
||||
})
|
||||
// One empty counterpart row to scaffold the next entry. Account
|
||||
// left blank — user must choose, which avoids the no-VAT trap.
|
||||
const counterpart: ManualLine = {
|
||||
@@ -180,7 +218,7 @@ export default function BulkBookDialog({
|
||||
line_description: '',
|
||||
}
|
||||
setManualLines([...bankLines, counterpart])
|
||||
}, [tab, manualLines.length, transactions, direction])
|
||||
}, [tab, manualLines.length, transactions, direction, cashAccounts])
|
||||
|
||||
// Live line preview — driven by either the template/mode pair (template
|
||||
// tab) or the user-edited manual lines (manual tab). Same downstream
|
||||
|
||||
Reference in New Issue
Block a user