feat(migration): link migrated invoices to their registration voucher (#1463) (#2024)

Visma and Fortnox migrations now carry each invoice's source voucher reference, and after the invoice steps a core linker resolves it against the SIE-imported ledger (voucher-ref resolver by date, corroborated by the 244x credit / 151x debit amount, posted only, unreferenced only) and writes registration_journal_entry_id / journal_entry_id. Anything ambiguous, mismatched or unresolved is reported and left NULL; journal entries are never written. The arcim-migration /reconcile endpoint can relink already-migrated companies. Payment vouchers are PR B. Refs #1463
This commit is contained in:
Jakob Wennberg
2026-08-30 11:52:14 +02:00
committed by GitHub
parent 683314b439
commit 521f437072
22 changed files with 2411 additions and 21 deletions
@@ -227,17 +227,30 @@ interface SkipReasons {
}
interface MigrationStepError {
step: 'companyInfo' | 'customers' | 'suppliers' | 'salesInvoices' | 'supplierInvoices' | 'reconciliation'
step: 'companyInfo' | 'customers' | 'suppliers' | 'salesInvoices' | 'supplierInvoices' | 'registrationLinks' | 'reconciliation'
code: string | null
message: string
}
/** Mirrors MigrationResults.registrationLinks in extensions/general/arcim-migration/types.ts. */
interface RegistrationLinkCounts {
scanned: number
linked: number
noRef: number
refNotFetched: number
unresolved: number
ambiguous: number
amountMismatch: number
alreadyLinked: number
}
interface MigrationResults {
companyInfo?: { imported: boolean }
customers?: { total: number; imported: number; updated?: number; skipped: number; skipReasons?: SkipReasons; errorSample?: string }
suppliers?: { total: number; imported: number; skipped: number; skipReasons?: SkipReasons; errorSample?: string }
salesInvoices?: { total: number; imported: number; skipped: number; skipReasons?: SkipReasons; errorSample?: string }
supplierInvoices?: { total: number; imported: number; skipped: number; skipReasons?: SkipReasons; errorSample?: string }
registrationLinks?: RegistrationLinkCounts
stepErrors?: MigrationStepError[]
}
import AccountMappingStep from '@/components/import/AccountMappingStep'
@@ -1711,6 +1724,7 @@ function ResultStep({
onDismissDocuments: () => void
onReconnectDocuments: () => void
}) {
const t = useTranslations('extensions')
if (error) {
return (
<div className="stagger-enter space-y-8">
@@ -1835,6 +1849,24 @@ function ResultStep({
failed: entityRowStatus(results.supplierInvoices.imported, results.supplierInvoices.skipReasons) === 'error',
})
}
if (results.registrationLinks && results.registrationLinks.scanned > 0) {
const links = results.registrationLinks
const unlinked = links.scanned - links.linked - links.alreadyLinked
entityLines.push({
label: t('ext_arcim_registration_links_label'),
value: t('ext_arcim_registration_links_value', { linked: links.linked, scanned: links.scanned }),
detail: unlinked > 0
? t('ext_arcim_registration_links_detail', {
unlinked,
noRef: links.noRef,
refNotFetched: links.refNotFetched ?? 0,
unresolved: links.unresolved + links.ambiguous,
amountMismatch: links.amountMismatch,
})
: undefined,
failed: false,
})
}
}
return (
@@ -1974,6 +2006,7 @@ const STEP_ERROR_LABELS: Record<MigrationStepError['step'], string> = {
suppliers: 'Leverantörer',
salesInvoices: 'Kundfakturor',
supplierInvoices: 'Leverantörsfakturor',
registrationLinks: 'Koppling till verifikationer',
reconciliation: 'Avstämning av betalningar',
}