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:
co-authored by
pgronberg
parent
6ac9679fb5
commit
8b0aa80ea0
@@ -2241,9 +2241,9 @@ const PROVIDER_MIGRATION: Record<string, StructuredErrorEntry> = {
|
||||
PROVIDER_SIE_IMPORT_REQUIRED: {
|
||||
httpStatus: 409,
|
||||
message_sv:
|
||||
'Bokföringsdata (SIE) måste importeras först. Ladda upp en SIE-fil med kontoplan, ingående balanser och verifikationer innan du hämtar kunder, leverantörer och fakturor från den här leverantören.',
|
||||
'Bokföringsdata (SIE) måste importeras först. Ladda upp en SIE-fil med kontoplan, ingående balanser och verifikationer innan du hämtar kunder, leverantörer, fakturor och anläggningstillgångar från den här leverantören.',
|
||||
message_en:
|
||||
'A completed SIE import is required first. Import the SIE file (chart of accounts, opening balances and verifications) before importing customers, suppliers and invoices from this provider.',
|
||||
'A completed SIE import is required first. Import the SIE file (chart of accounts, opening balances and verifications) before importing customers, suppliers, invoices and fixed assets from this provider.',
|
||||
},
|
||||
PROVIDER_MIGRATE_FAILED: {
|
||||
httpStatus: 500,
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
fortnoxConsentScopes,
|
||||
FORTNOX_DOCUMENT_SCOPES,
|
||||
FORTNOX_DOCUMENT_SCOPES_APPROVED,
|
||||
FORTNOX_ASSET_SCOPES,
|
||||
FORTNOX_ASSET_SCOPES_APPROVED,
|
||||
} from '../oauth';
|
||||
|
||||
describe('Fortnox OAuth scopes', () => {
|
||||
@@ -47,6 +49,21 @@ describe('Fortnox OAuth scopes', () => {
|
||||
expect(fortnoxConsentScopes()).not.toContain('connectfile');
|
||||
});
|
||||
|
||||
// The asset register scope is gated on its own portal approval. While the
|
||||
// flag is false, no consent may request it: an unapproved scope in the
|
||||
// authorize request is rejected with invalid_scope BEFORE login (the same
|
||||
// outage mode the document flag above guards against).
|
||||
it('keeps the asset scope out of every consent until the portal approves it', () => {
|
||||
expect(FORTNOX_ASSET_SCOPES).toEqual(['assets']);
|
||||
if (FORTNOX_ASSET_SCOPES_APPROVED) {
|
||||
expect(fortnoxConsentScopes()).toContain('assets');
|
||||
expect(fortnoxConsentScopes({ documents: true })).toContain('assets');
|
||||
} else {
|
||||
expect(fortnoxConsentScopes()).not.toContain('assets');
|
||||
expect(fortnoxConsentScopes({ documents: true })).not.toContain('assets');
|
||||
}
|
||||
});
|
||||
|
||||
// Even once the portal registration lands, opting in must never cost the
|
||||
// consent its ledger access: the callback overwrites its tokens in place.
|
||||
it('keeps a document consent a superset of an ordinary one', () => {
|
||||
|
||||
@@ -35,6 +35,24 @@ export const FORTNOX_DOCUMENT_SCOPES = ['archive', 'connectfile'];
|
||||
*/
|
||||
export const FORTNOX_DOCUMENT_SCOPES_APPROVED: boolean = true;
|
||||
|
||||
/** The asset register (anläggningsregistret): what the asset import reads. */
|
||||
export const FORTNOX_ASSET_SCOPES = ['assets'];
|
||||
|
||||
/**
|
||||
* Whether the registered Fortnox app has the Assets scope (Anläggningsregister)
|
||||
* enabled in the Fortnox Developer Portal. Ships false until the portal
|
||||
* registration is confirmed to carry it: requesting a scope the app lacks
|
||||
* makes the authorize endpoint reject with invalid_scope BEFORE login, the
|
||||
* same failure mode the document scopes guard against above.
|
||||
*
|
||||
* When true, the ordinary connect requests the scope. Unlike Arkivplats and
|
||||
* Koppla filer, the asset register carries no separate Fortnox customer
|
||||
* licence, so no per-user opt-in is needed. A consent minted without the
|
||||
* scope degrades gracefully: the migration reports assets as skipped instead
|
||||
* of failing (see arcim-migration import-assets).
|
||||
*/
|
||||
export const FORTNOX_ASSET_SCOPES_APPROVED: boolean = false;
|
||||
|
||||
/**
|
||||
* The scopes a Fortnox consent is minted with. The document scopes are opt-in
|
||||
* per authorize call, because Fortnox derives its customer licence
|
||||
@@ -48,9 +66,14 @@ export const FORTNOX_DOCUMENT_SCOPES_APPROVED: boolean = true;
|
||||
export function fortnoxConsentScopes(options?: { documents?: boolean }): string[] {
|
||||
const withDocuments =
|
||||
options?.documents === true && FORTNOX_DOCUMENT_SCOPES_APPROVED;
|
||||
return withDocuments
|
||||
const scopes = withDocuments
|
||||
? [...BASE_SCOPES, ...FORTNOX_DOCUMENT_SCOPES]
|
||||
: [...BASE_SCOPES];
|
||||
// The asset register rides along on every consent once the portal
|
||||
// registration carries the scope: it needs no extra customer licence, so
|
||||
// there is nothing to opt in to.
|
||||
if (FORTNOX_ASSET_SCOPES_APPROVED) scopes.push(...FORTNOX_ASSET_SCOPES);
|
||||
return scopes;
|
||||
}
|
||||
|
||||
export function buildFortnoxAuthUrl(
|
||||
|
||||
Reference in New Issue
Block a user