* feat(bookkeeping): verifikationsserie per bankkonto for bank-transaction bookings A company running several bank accounts (main bank on A, company card on M, both imported via CSV) could not route each account's bookings into its own series: every bank_transaction booking took the single company-wide default from default_voucher_series_per_source_type. - cash_accounts.voucher_series (nullable, single letter): per-account override, editable under Inställningar → Bokföring → Verifikationsserier per bankkonto (new PATCH /api/cash-accounts/[id]). - resolveCashAccountVoucherSeries(): step 2 of the resolution order (explicit pick → account override → per-type map → A). Wired into the book route and createTransactionJournalEntry, which covers categorize, the agent, pending operations and the v1 API. - Booking dialog gets the series picker, seeded from the server via /voucher-sequences/next?source_type&cash_account_id so dialog and route can never disagree. An unresolved embedded picker omits voucher_series so a stray 'A' never overrides the account's series. Scope: bank_transaction bookings only. Invoice settlements matched from the bank keep their payment series; bulk-book resolves inside its RPC (see DECISIONS.md). Migration applied to staging as 20260902121420. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JWSLbQc3jgpfqnxWe6nteh * fix(bookkeeping): audit and document the per-bankkonto series, tighten preview and PATCH Consolidated pass over the PR #2160 findings (skeptics, CodeRabbit, Swedish compliance review): - Behandlingshistorik (BFNAR 2013:2 p. 9.16): changing cash_accounts.voucher_series is a behandlingsregel that outranks the audited per-type map. New trigger audit_cash_accounts_voucher_series (UPDATE only, WHEN the series changes, so bank-sync churn never logs), cash_accounts added to AUDITED_TABLES and the audit_log filter, "Bankkonto ... Verifikationsserie: (tomt) -> M" events in the report, pg-real test. Applied to staging as 20260902124513. - Systemdokumentation (p. 9.2-9.15): revision/systemdokumentation.json gains a verifikationsserier_regler block with the resolution order and the two exceptions (invoice settlements, samlingsverifikat); the per-account mapping itself is in data/cash_accounts.json. - Settings picker uses the same closed list as the manual verifikat form (presets plus letters already in use) instead of all 26 letters; strings moved to messages/sv.json and messages/en.json. - /voucher-sequences/next applies the account override only for source_type=bank_transaction (CodeRabbit), so a manual-entry preview cannot show a series the entry will not get. - Book route resolves the series from the account the row ends up on after a stranded-row repoint, not the stale one. - PATCH /api/cash-accounts/[id] answers 404 for a non-UUID id instead of a Postgres cast 500; the series lookup logs a warning when it fails open. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JWSLbQc3jgpfqnxWe6nteh --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
54 lines
1.9 KiB
TypeScript
54 lines
1.9 KiB
TypeScript
import { NextResponse } from 'next/server'
|
|
import { withRouteContext } from '@/lib/api/with-route-context'
|
|
import { validateBody } from '@/lib/api/validate'
|
|
import { UpdateCashAccountVoucherSeriesSchema } from '@/lib/api/schemas'
|
|
import { errorResponse } from '@/lib/errors/get-structured-error'
|
|
import { setVoucherSeries } from '@/lib/cash-accounts/service'
|
|
import { UUID_RE } from '@/lib/invariants/uuid'
|
|
|
|
/** Canonical 404 for an id that is not one of the company's bank accounts. */
|
|
function notFound(): NextResponse {
|
|
return NextResponse.json(
|
|
{
|
|
error: {
|
|
code: 'CASH_ACCOUNT_NOT_FOUND',
|
|
message: 'Bankkontot hittades inte.',
|
|
message_en: 'Bank account not found.',
|
|
},
|
|
},
|
|
{ status: 404 },
|
|
)
|
|
}
|
|
|
|
/**
|
|
* PATCH /api/cash-accounts/[id]
|
|
*
|
|
* Sets or clears the verifikationsserie override on one of the company's
|
|
* bank accounts. Only this one field is editable here: ledger account and
|
|
* primary flag have their own guarded flows (unique constraint, atomic RPC).
|
|
*/
|
|
export const PATCH = withRouteContext<{ params: Promise<{ id: string }> }>(
|
|
'cash_accounts.update',
|
|
async (request, { supabase, companyId, log, requestId }, { params }) => {
|
|
const { id } = await params
|
|
// A non-UUID id can never match a row; answer 404 instead of letting the
|
|
// uuid cast surface as a 500 from Postgres.
|
|
if (!UUID_RE.test(id)) return notFound()
|
|
const validation = await validateBody(request, UpdateCashAccountVoucherSeriesSchema)
|
|
if (!validation.success) return validation.response
|
|
|
|
let updated
|
|
try {
|
|
updated = await setVoucherSeries(supabase, companyId, id, validation.data.voucher_series)
|
|
} catch (err) {
|
|
log.error('cash_accounts voucher_series update failed', err as Error)
|
|
return errorResponse(err, log, { requestId })
|
|
}
|
|
|
|
if (!updated) return notFound()
|
|
|
|
return NextResponse.json({ data: updated })
|
|
},
|
|
{ requireWrite: true },
|
|
)
|