feat(bookkeeping): verifikationsserie dropdown, wider Ny verifikat modal, balancing amount on focus (#2120)
Three fixes to the Ny verifikation modal. 1. Verifikationsserie is a closed dropdown, not a one-letter free-text field. Ships Fortnox's table verbatim from their Systemdokumentation (A Redovisning, B Kundfakturor, C Inbetalningar från kunder, D Leverantörsfakturor, E Utbetalningar till leverantörer, F Kassa, G Avskrivning, H Periodisering, I Bokslut, J Revisor, K Lön, L Kontantfaktura, M Momsrapport). A is deliberately Redovisning, not Kundfakturor: it is the general series manual entries land in, and migration 20260526120700 ships every source_type defaulting to 'A', so every existing company's A series already holds everything. Any letter the company already configured is appended so no existing value can fall out of the picker. 2. The modal goes from sm:max-w-3xl to sm:max-w-5xl lg:max-w-6xl. 3. Tabbing or clicking into an untouched amount proposes the outstanding difference, pre-selected so typing replaces it. Only fires when the row has an account, both amounts are empty, and the difference belongs on that side. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DnoyZCbfm2geRbwtUpi6XN
This commit is contained in:
co-authored by
Claude Opus 5
parent
0406e628e1
commit
e5fba9471e
@@ -4,6 +4,8 @@ import {
|
||||
formatVoucher,
|
||||
parseVoucher,
|
||||
resolveDefaultSeriesForSource,
|
||||
voucherSeriesLabel,
|
||||
VOUCHER_SERIES_PRESETS,
|
||||
} from '../voucher-series-resolver'
|
||||
|
||||
describe('resolveDefaultSeriesForSource', () => {
|
||||
@@ -192,3 +194,42 @@ describe('parseVoucher', () => {
|
||||
expect(parseVoucher('A 1')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('VOUCHER_SERIES_PRESETS', () => {
|
||||
it('offers the conventional Swedish series in a stable order', () => {
|
||||
expect(VOUCHER_SERIES_PRESETS.map((p) => p.letter)).toEqual(
|
||||
'ABCDEFGHIJKLM'.split(''),
|
||||
)
|
||||
})
|
||||
|
||||
it('gives every preset a non-empty label', () => {
|
||||
for (const preset of VOUCHER_SERIES_PRESETS) {
|
||||
expect(preset.label.length).toBeGreaterThan(0)
|
||||
}
|
||||
})
|
||||
|
||||
it('uses letters the resolver accepts as a series', () => {
|
||||
for (const preset of VOUCHER_SERIES_PRESETS) {
|
||||
expect(
|
||||
resolveDefaultSeriesForSource({ manual: preset.letter }, 'manual'),
|
||||
).toBe(preset.letter)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('voucherSeriesLabel', () => {
|
||||
it('describes a preset letter', () => {
|
||||
// A is the general series manual entries land in, not kundfakturor: it is
|
||||
// the shipped default for every source_type. Guards against a relabelling
|
||||
// that would mislabel every existing company's history.
|
||||
expect(voucherSeriesLabel('A')).toBe('Redovisning')
|
||||
expect(voucherSeriesLabel('B')).toBe('Kundfakturor')
|
||||
expect(voucherSeriesLabel('K')).toBe('Lön')
|
||||
})
|
||||
|
||||
it('returns an empty string for a letter with no preset meaning', () => {
|
||||
expect(voucherSeriesLabel('N')).toBe('')
|
||||
expect(voucherSeriesLabel('Z')).toBe('')
|
||||
expect(voucherSeriesLabel('')).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -21,6 +21,50 @@ export type VoucherSeriesMap = Partial<Record<JournalEntrySourceType, string>> &
|
||||
|
||||
const SERIES_LETTER_RE = /^[A-Z]$/
|
||||
|
||||
/**
|
||||
* The conventional Swedish verifikationsserier and what each one is for.
|
||||
*
|
||||
* The column accepts any A-Z letter, but a free-text field invites typos and
|
||||
* lets the same letter mean different things across a company's ledger, so the
|
||||
* entry form offers this fixed set (plus whatever series the company has
|
||||
* already configured or used).
|
||||
*
|
||||
* The letters are NOT prescribed by law: BFL 5 kap. 7 § only requires an
|
||||
* unbroken, systematically ordered numbering within each series. This list is
|
||||
* Fortnox's, taken verbatim from their Systemdokumentation (Fortnox Lön,
|
||||
* section 4 Behandlingsregler), because Fortnox is the system most companies
|
||||
* migrate here from and an imported ledger should keep its meaning. Note that
|
||||
* the incumbents disagree with each other: Björn Lundén uses F for
|
||||
* kundfakturor, L for leverantörsfakturor and N for löner. The one point
|
||||
* they agree on is that A is the general series you post manual entries into,
|
||||
* which also matches this codebase: every source_type in
|
||||
* company_settings.default_voucher_series_per_source_type ships as 'A'.
|
||||
*
|
||||
* The labels are bookkeeping-domain terms that stay Swedish in both locales,
|
||||
* same convention as VoucherSeriesPerSourceTypeForm.
|
||||
*/
|
||||
export const VOUCHER_SERIES_PRESETS: ReadonlyArray<{ letter: string; label: string }> = [
|
||||
{ letter: 'A', label: 'Redovisning' },
|
||||
{ letter: 'B', label: 'Kundfakturor' },
|
||||
{ letter: 'C', label: 'Inbetalningar från kunder' },
|
||||
{ letter: 'D', label: 'Leverantörsfakturor' },
|
||||
{ letter: 'E', label: 'Utbetalningar till leverantörer' },
|
||||
{ letter: 'F', label: 'Kassa' },
|
||||
{ letter: 'G', label: 'Avskrivning' },
|
||||
{ letter: 'H', label: 'Periodisering' },
|
||||
{ letter: 'I', label: 'Bokslut' },
|
||||
{ letter: 'J', label: 'Revisor' },
|
||||
{ letter: 'K', label: 'Lön' },
|
||||
{ letter: 'L', label: 'Kontantfaktura' },
|
||||
{ letter: 'M', label: 'Momsrapport' },
|
||||
]
|
||||
|
||||
/** Swedish description for a preset series letter; empty for unknown letters. */
|
||||
export function voucherSeriesLabel(letter: string): string {
|
||||
const match = VOUCHER_SERIES_PRESETS.find((p) => p.letter === letter)
|
||||
return match ? match.label : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the default voucher_series letter for a given source_type from a
|
||||
* company_settings row. Returns 'A' as a safe fallback when no mapping is
|
||||
|
||||
Reference in New Issue
Block a user