84ba8323b4
* fix(transactions): derive pre-migration marker cutoff from imported voucher dates, not fiscal_year_end A SIE file exported mid-year still declares the full fiscal year in #RAR 0, so sie_imports.fiscal_year_end is a future date for mid-year migrators and the 'fran perioden fore din migrering' marker fired on every new bank transaction until New Year. The cutoff now comes from the latest posted source_type='import' entry_date (excluding the M-series omforingsverifikation, which is deliberately dated at fiscal year end), so it tracks where the imported bokforing actually ends and self-corrects on undo/replace. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UU5QbL3p8xNB3tSZDbyrr3 * fix(transactions): arm pre-migration cutoff on completed SIE import, exclude omforing by description Skeptic findings on the frozen commit: (1) source_type='import' is accepted from v1 API clients, so a never-migrated company with an API-labeled backfill would get a false cutoff; the marker is now armed only when a completed sie_imports row exists. (2) Imported vouchers keep the source file's voucher series, so excluding series M dropped genuine M-series vouchers (6 prod companies); the omforingsverifikation is now excluded by its hardcoded description prefix instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UU5QbL3p8xNB3tSZDbyrr3 * docs(transactions): state the pre-migration cutoff residuals truthfully The ledger duplicate guard only reaches the completed-year single-skip case (7-day window vs a fiscal-year-end-dated aggregate omforing), so it is not a general backstop for the skip-window gap; the gap is accepted on rarity. Also documents the rattelse-rename fragility of the description-keyed exclusion. Skeptic re-review condition, no code change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UU5QbL3p8xNB3tSZDbyrr3 --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
72 lines
3.4 KiB
TypeScript
72 lines
3.4 KiB
TypeScript
import type { SupabaseClient } from '@supabase/supabase-js'
|
|
|
|
/**
|
|
* End of the company's SIE-migration data coverage: the latest entry_date
|
|
* among posted imported verifikat. Drives the quiet "från perioden före din
|
|
* migrering" marker on inbox rows.
|
|
*
|
|
* Derived from journal_entries, NOT from sie_imports.fiscal_year_end: a SIE
|
|
* file exported mid-year still declares the full fiscal year in #RAR 0, so
|
|
* for a mid-year migrator fiscal_year_end is a FUTURE date and every new
|
|
* bank transaction satisfied `date <= cutoff` until New Year (the 2026-08-30
|
|
* user report). The imported vouchers themselves end where the old system's
|
|
* data ends, which is the boundary the marker is about.
|
|
*
|
|
* Armed only when a completed sie_imports row exists: source_type='import'
|
|
* is accepted from API clients too (CreateJournalEntrySchema), so without
|
|
* the gate a third-party backfill labeled 'import' would paint a false
|
|
* pre-migration marker across a company that never migrated. With the gate,
|
|
* such entries can still stretch a real migrator's cutoff, but a company
|
|
* labeling API entries 'import' post-migration is doing exactly what the
|
|
* label says.
|
|
*
|
|
* The importer's omföringsverifikation (skipped-voucher adjustment) is
|
|
* excluded by its hardcoded description prefix: it is deliberately dated at
|
|
* fiscal year end (sie-import.ts) and would reintroduce the future-date bug
|
|
* for any import with skipped vouchers. Excluding by description rather
|
|
* than by its 'M' voucher series keeps genuine series-M vouchers from the
|
|
* source file in the max (prod has companies whose files use series M for
|
|
* ordinary vouchers, e.g. moms). Two accepted residuals: (1) bank movement
|
|
* covered only by the omföring (skipped vouchers dated after the last
|
|
* cleanly imported one) falls outside the cutoff and is mostly unmitigated:
|
|
* the ledger duplicate guard only catches a single skipped movement within
|
|
* 7 days of the omföring's fiscal-year-end date, never the mid-year or
|
|
* aggregated-skip variants. Accepted because it needs a conjunction of
|
|
* rare conditions, against the systematic all-year over-marking it
|
|
* replaces. Exact closure needs skipped-voucher dates persisted at import
|
|
* (skippedDetails in sie-import.ts has them; candidate follow-up: a
|
|
* coverage_end column on sie_imports). (2) The exclusion keys on
|
|
* description, which inline rättelse can edit: renaming the omföring
|
|
* re-admits its fiscal-year-end date and degrades that one company to the
|
|
* pre-fix over-marking, nothing worse.
|
|
*
|
|
* Undo/replace of an import deletes or recreates these entries, so the
|
|
* cutoff self-corrects with no stored state to maintain. Returns null when
|
|
* the company has no completed migration: callers render no marker.
|
|
*/
|
|
export async function fetchMigrationCoverageEnd(
|
|
supabase: SupabaseClient,
|
|
companyId: string,
|
|
): Promise<string | null> {
|
|
const { data: completedImport } = await supabase
|
|
.from('sie_imports')
|
|
.select('id')
|
|
.eq('company_id', companyId)
|
|
.eq('status', 'completed')
|
|
.limit(1)
|
|
.maybeSingle()
|
|
if (!completedImport) return null
|
|
|
|
const { data } = await supabase
|
|
.from('journal_entries')
|
|
.select('entry_date')
|
|
.eq('company_id', companyId)
|
|
.eq('status', 'posted')
|
|
.eq('source_type', 'import')
|
|
.not('description', 'like', 'Omföringsverifikation:%')
|
|
.order('entry_date', { ascending: false })
|
|
.limit(1)
|
|
.maybeSingle()
|
|
return (data as { entry_date?: string } | null)?.entry_date ?? null
|
|
}
|