Files
accounted/lib/providers/wint/config.ts
T
MattssonandClaude Fable 5 93f81f03e8 feat(providers): WINT migration provider behind WINT_MIGRATION_ENABLED (#1446)
* feat(providers): WINT migration provider behind WINT_MIGRATION_ENABLED

Adds WINT (wint.se) as a sixth migration provider, built against the
OpenAPI specs WINT's own API host serves publicly. Tier A scope: only the
partner-facing v1 endpoints are used; the general ledger is fetched as
vouchers/accounts and rendered as SIE 4E by our own sie-builder, with
opening balances for earlier years derived backward from the current-year
Ib anchor. Auth is the user's WINT login exchanged once for a JWT pair;
the password is never stored.

Ships dark: the wizard shows a disabled "Kommer snart" card, and the
server-side /connect gate rejects WINT until WINT_MIGRATION_ENABLED=true.
Live verification against a real WINT account is still outstanding.

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

* fix(providers): harden WINT provider per PR #1446 review findings

Addresses CodeRabbit and Swedish accounting review feedback in one pass:

- Ib anchor selection now uses WINT's unfiltered fiscal-year list, so an
  active year outside the allowed import window can never silently anchor
  the wrong year; the voucher chain is extended through the anchor and a
  per-year fetch failure fails that year loudly instead of sinking the
  whole migration.
- Auth token exchange is strict: only LoginState Success with a complete
  access+refresh pair mints a consent (a pair without a refresh token is
  unrefreshable and would break days later).
- WintApiError no longer retains full response bodies (bounded 300-char
  diagnostic; bodies can carry customer data and errors get logged).
- sie-builder refuses to render structurally invalid vouchers (missing
  account number or booking date) and documents deleted-voucher gaps in a
  #PROSA record per BFL 5 kap 6-7 §.
- Account classification: 20xx is equity, 83xx is financial income.
- SIE validator accepts EUBAS97 as BAS-based (standard kontoplanstyp; it
  previously produced a false non-BAS warning on every WINT/Bollbok file).
- New tests: resolveConsent WINT refresh flow, credential upsert payload
  (no mail/password persisted), WINT fetch failure path, EUBAS97 warning
  regression, builder invalid-data rejection, vi.clearAllMocks hygiene.

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

* fix(import): pin EUBAS97 acceptance to the exact SIE spec value

Review follow-up on PR #1446: match EUBAS97 exactly instead of any
EUBAS* prefix, so the non-BAS kontoplan warning stays pinned to the four
kontoplanstyp values the SIE 4B spec enumerates (BAS95, BAS96, EUBAS97,
NE2007) rather than silently accepting unknown future variants.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 11:07:14 +02:00

57 lines
2.0 KiB
TypeScript

import { ResourceType } from '../dto';
import type { RateLimitConfig, WintResourceConfig } from '../types';
import {
mapWintToSalesInvoice,
mapWintToCustomer,
mapWintToAccountingAccount,
mapWintToCompanyInformation,
} from './mapper';
// WINT has no published developer docs; every endpoint below comes from the
// OpenAPI specs their API host serves itself (https://api.wint.se/index.html,
// spec /swagger/v1/swagger.json, fetched 2026-08-06). We deliberately stay on
// the partner-facing "v1" surface: the SIE export and IncomingInvoice
// endpoints exist only in their Full/Internal specs and are NOT used here
// (Tier A: the general ledger is reconstructed from /api/Voucher + /api/Account
// and rendered as SIE on our side; see sie-builder.ts).
export const WINT_BASE_URL = 'https://api.wint.se';
// Undocumented; start conservative until WINT confirms a real budget.
export const WINT_RATE_LIMIT: RateLimitConfig = { maxRequests: 3, windowMs: 1000 };
export const WINT_RESOURCE_CONFIGS: Partial<Record<ResourceType, WintResourceConfig>> = {
[ResourceType.SalesInvoices]: {
listEndpoint: '/api/Invoice',
detailEndpoint: '/api/Invoice/{id}',
idField: 'Id',
mapper: mapWintToSalesInvoice,
paginated: true,
modifiedParam: 'LastUpdated',
},
[ResourceType.Customers]: {
listEndpoint: '/api/Customer',
detailEndpoint: '/api/Customer/{id}',
idField: 'Id',
mapper: mapWintToCustomer,
paginated: true,
modifiedParam: 'UpdatedAfter',
},
[ResourceType.AccountingAccounts]: {
listEndpoint: '/api/Account',
detailEndpoint: '/api/Account',
idField: 'Id',
mapper: mapWintToAccountingAccount,
paginated: true,
},
[ResourceType.CompanyInformation]: {
// GET /api/Auth describes the company the current token is scoped to
// (Id, Name, Org, NoVat, FinancialYears, ...): WINT has no separate
// company-information endpoint on the v1 surface.
listEndpoint: '/api/Auth',
detailEndpoint: '/api/Auth',
idField: 'Id',
mapper: mapWintToCompanyInformation,
singleton: true,
},
};