Files
accounted/extensions/general/enable-banking/lib/date-suggestions.ts
T
Jakob WennbergandClaude Fable 5 11400b4dec fix(banking): base sync date suggestions on booked coverage and the real fiscal period (#956)
* fix(enable-banking): base sync date suggestions on booked coverage and actual fiscal period

The "start after your bookkeeping" suggestion in the account picker now
uses the latest posted verifikat date (journal_entries, status posted)
instead of sie_imports.fiscal_year_end: the fiscal period end can lie
months past the last actually booked transaction, so the old suggestion
made users skip every unbooked transaction in between. Companies with
no posted entries get no suggestion instead of a misleading one.

The suggested start date (day after the last posted verifikat) is
clamped to today (UTC): the PATCH handler rejects non-past
initial_lookback_from_date values, so a company whose latest verifikat
is dated today would otherwise be suggested tomorrow and get a 400 when
saving.

"Sedan raekenskapsaarets boerjan" now resolves from the fiscal_periods
row containing today, falling back to the recurring
fiscal_year_start_month setting only when no period row exists, so an
extended or shortened first fiscal year (e.g. 2025-10-01 to 2026-12-31)
resolves to its real start date instead of the recurring-year date.

Date logic extracted to lib/date-suggestions.ts with regression tests
covering both issue scenarios.

Fixes #917

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

* fix(banking): keep the fiscal-year date masked when the settings fetch fails (CodeRabbit)

A failed company_settings or fiscal_periods query silently fell back to
the calendar-year default, the exact misleading suggestion issue #917
removes. On error the date now stays masked and the request-side
fallback remains the recurring-setting derivation.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 21:20:24 +02:00

64 lines
2.8 KiB
TypeScript

import type { CompanySettings } from '@/types'
import { getCurrentFiscalYearStart } from '@/lib/company/fiscal-year'
export interface BookedCoverage {
/** Entry date of the company's latest posted verifikat. */
lastBookedDate: string
/**
* Day after lastBookedDate (the earliest sync start that cannot overlap
* booked entries), clamped to today (UTC) so the backend accepts it.
*/
suggestedStartDate: string
}
/**
* Turn the latest posted verifikat date into a "start syncing from" suggestion.
*
* Issue #917: this used to be derived from sie_imports.fiscal_year_end, which
* is the fiscal PERIOD end, not how far the bookkeeping actually reaches. For
* a company whose SIE covered an extended first year (2025-10-01 to 2026-12-31)
* but whose entries stopped in May, the old value suggested a start date past
* every unbooked transaction. Returns null when there is nothing booked: no
* suggestion beats a misleading one.
*/
export function resolveBookedCoverage(
lastPostedEntryDate: string | null | undefined,
today: Date = new Date(),
): BookedCoverage | null {
if (!lastPostedEntryDate) return null
// Pin the math to UTC so the day-after arithmetic is timezone-independent.
const d = new Date(lastPostedEntryDate + 'T00:00:00Z')
d.setUTCDate(d.getUTCDate() + 1)
const dayAfter = d.toISOString().split('T')[0]
// The backend PATCH handler (index.ts) rejects initial_lookback_from_date
// unless Date.now() is past the date's UTC midnight, so the newest date it
// accepts is the current UTC date. A company whose latest verifikat is
// dated today (plausible at initial bank activation) would otherwise get
// tomorrow suggested here and a 400 when saving. Clamp to today; ISO date
// strings compare correctly as plain strings.
const todayUtc = today.toISOString().split('T')[0]
return {
lastBookedDate: lastPostedEntryDate,
suggestedStartDate: dayAfter <= todayUtc ? dayAfter : todayUtc,
}
}
/**
* Resolve the start of the current fiscal year, preferring the actual
* fiscal_periods row that contains today over the recurring
* fiscal_year_start_month setting.
*
* Issue #917: the recurring setting cannot represent an extended or shortened
* first fiscal year (e.g. 2025-10-01 to 2026-12-31 for a company that later
* runs calendar years), so deriving from it alone returned 2026-01-01 where
* the real start was 2025-10-01. The period row is authoritative when it
* exists; the setting remains the fallback for companies without period rows.
*/
export function resolveFiscalYearStart(
currentPeriodStart: string | null | undefined,
settings: Pick<CompanySettings, 'fiscal_year_start_month' | 'entity_type'> | null | undefined,
today: Date = new Date(),
): string {
return currentPeriodStart || getCurrentFiscalYearStart(settings, today)
}