feat(arcim-migration): import the Fortnox asset register during migration (#1999)

The Fortnox migration now imports the asset register (GET /3/assets + /3/assets/types) as local register rows via createAsset: category from the type's anskaffningskonto BAS class, useful life from the source's depreciation window (K2 schablon fallback), never any journal entries (values arrived via SIE; the source's depreciated-to date is recorded in notes for review of the first proposal). Sold/scrapped/voided assets are skipped, re-runs dedupe, one bad asset counts as skipped. Gated behind FORTNOX_ASSET_SCOPES_APPROVED=false until the portal registration for integration 39254 carries the Assets scope, so hosted consents are unchanged and the wizard shows an honest skipped row.

Co-authored-by: pgronberg <pgronberg@users.noreply.github.com>
This commit is contained in:
Pierre Grönberg
2026-08-31 09:25:55 +01:00
committed by GitHub
co-authored by pgronberg
parent 6ac9679fb5
commit 8b0aa80ea0
11 changed files with 1054 additions and 60 deletions
@@ -149,6 +149,52 @@ describe('POST /migrate: SIE-import-required guard', () => {
it('allows a company-info-only run with no SIE import (writes no ledger data)', async () => {
;(getConsent as Mock).mockResolvedValue({ id: 'consent-1', status: 1, provider: 'fortnox' })
const res = await handler(
migrateRequest({
consentId: 'consent-1',
importCompanyInfo: true,
importCustomers: false,
importSuppliers: false,
importSalesInvoices: false,
importSupplierInvoices: false,
importAssets: false,
}),
buildCtx(0),
)
expect(res.status).toBe(200)
expect(executeMigration).toHaveBeenCalledTimes(1)
})
// The asset register is entity data too: its rows carry BAS account triples
// and depreciation plans that only mean something against an imported chart
// of accounts, so an assets-only run is gated like any other entity import.
it('blocks an assets-only run with no SIE import', async () => {
;(getConsent as Mock).mockResolvedValue({ id: 'consent-1', status: 1, provider: 'fortnox' })
const res = await handler(
migrateRequest({
consentId: 'consent-1',
importCompanyInfo: false,
importCustomers: false,
importSuppliers: false,
importSalesInvoices: false,
importSupplierInvoices: false,
importAssets: true,
}),
buildCtx(0),
)
expect(res.status).toBe(409)
expect(executeMigration).not.toHaveBeenCalled()
})
// Backward compatibility: a client written before the asset option existed
// omits the field entirely. That request must behave as it did then, so the
// omitted option neither imports assets nor trips this guard.
it('allows a company-info-only run that omits importAssets entirely', async () => {
;(getConsent as Mock).mockResolvedValue({ id: 'consent-1', status: 1, provider: 'fortnox' })
const res = await handler(
migrateRequest({
consentId: 'consent-1',