fix(bookkeeping): let a backfilled first räkenskapsår start mid-month (#2237) (#2242)

* fix(bookkeeping): let a backfilled first räkenskapsår start mid-month (#2237)

POST /api/bookkeeping/fiscal-periods decided "first period" as "no period
exists at all", so a company that imported 2024+ from Fortnox and then
created its actual first year by hand (2022-07-22, the registration date)
was refused with the 1st-of-month error, while the DB trigger
enforce_first_of_month_for_subsequent_periods would have accepted the row.

The route now mirrors the trigger: first = no existing period starts
earlier. The 1st-of-month rule (BFL 3 kap. 1 §) keeps binding subsequent
years, and its message now says which years it binds and why instead of
only refusing.

Tests: prepend with a mid-month start passes; a mid-month start for a
non-earliest period is still a 400 that names the rule.

Closes #2237

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VnConrmMCxJRQ5kfiPPWyy

* chore: carry the DECISIONS.md line for this PR in #2247 instead (append-only log conflicts on every merge)

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-09-03 18:41:14 +02:00
committed by GitHub
co-authored by Claude Fable 5.1 Jakob Wennberg
parent d69de86b71
commit cc18e9d530
4 changed files with 52 additions and 7 deletions
@@ -49,13 +49,15 @@ describe('validatePeriodDuration', () => {
})
it('returns error when start is not 1st of month (default)', () => {
expect(validatePeriodDuration('2025-01-15', '2025-12-31')).toBe(
'Period start must be the 1st of a month'
)
const result = validatePeriodDuration('2025-01-15', '2025-12-31')
expect(result).toContain('Period start must be the 1st of a month')
// Says what IS allowed and why, not only "no" (issue #2237).
expect(result).toContain('first fiscal year may start mid-month')
expect(result).toContain('BFL 3 kap.')
})
it('returns error when start is not 1st of month (isFirstPeriod: false)', () => {
expect(validatePeriodDuration('2025-03-25', '2025-12-31', { isFirstPeriod: false })).toBe(
expect(validatePeriodDuration('2025-03-25', '2025-12-31', { isFirstPeriod: false })).toContain(
'Period start must be the 1st of a month'
)
})
+4 -2
View File
@@ -47,9 +47,11 @@ export function validatePeriodDuration(start: string, end: string, options?: Val
return 'Period end must be after period start'
}
// start must be 1st of month: unless this is the first fiscal period (BFL 3 kap.)
// start must be 1st of month: unless this is the first fiscal period (BFL 3
// kap. 1 § for subsequent years, 3 kap. 3 § for the first). Say why and what
// is allowed, not only "no" (issue #2237).
if (startParts.day !== 1 && !options?.isFirstPeriod) {
return 'Period start must be the 1st of a month'
return "Period start must be the 1st of a month: only the company's first fiscal year may start mid-month (BFL 3 kap. 1 and 3 §§)"
}
// end must be last day of month