Files
accounted/lib/skatteverket/declaration-status.ts
T
MattssonandClaude Fable 5 60920ec794 feat(skatteverket): expose filed VAT declarations and decisions via the v1 API (#1773)
* feat(skatteverket): expose filed VAT declarations and decisions via the v1 API

Add GET /api/v1/companies/:companyId/skatteverket/vat-declarations, returning
a period's momsdeklaration as Skatteverket has it on file: the submitted
declaration (SKV /inlamnat) and Skatteverket's beslut (SKV /beslutat), either
individually via ?state= or both.

- Auth: compliance:read scope; member-visibility read model per #1673
  (resolveReadAuth: caller's token, any member's active token, or system
  credentials with a verified ombud grant).
- Architecture: core reaches the Skatteverket extension through the
  registry-resolved services channel (contract in
  lib/skatteverket/declaration-status.ts), so core never imports from
  @/extensions/.
- New structured error SKATTEVERKET_API_ERROR (502) for upstream SKV
  failures; 404 from SKV maps to submitted/decided = null with HTTP 200.
- 19 new tests (route: auth, validation, extension-disabled, happy path;
  extension service: auth resolution, state filtering, SKV error mapping).

Fixes #1663

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

* fix(skatteverket): address review findings on the vat-declarations read API

Consolidated fixes for PR #1773 review round:

- apiskill sync (core-build Checks): map the new skatteverket endpoint
  group into the periods.md reference and regenerate skills/accounted-api
  (124 -> 125 operations).
- CodeRabbit: parse the SKV 2xx body before writing the audit row, so an
  unreadable body is audited as skv_error and returns the structured
  SKATTEVERKET_API_ERROR 502 instead of escaping as an internal 500;
  regression test added.
- Compliance swarm (ISO A.8.12 / SOC2 CC6.1): stop forwarding the raw
  upstream SKV response body to API consumers; the caller now gets the
  status code and a generic Swedish message, the body is logged
  server-side only.
- Compliance swarm (GDPR Art.30): add the moms.declaration_status_read
  processing activity to .compliance/ropa.yaml (live read, no payload
  persisted, audit-log metadata only).

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 12:12:18 +02:00

63 lines
2.1 KiB
TypeScript

/**
* Core <-> Skatteverket-extension read boundary for filed VAT declarations.
*
* `lib/` and `app/api/v1/` cannot import from `@/extensions/` (CI guard,
* core-build.yml), so the v1 REST read endpoint reaches the Skatteverket
* extension only through the registry-resolved `services` channel: same
* pattern as lib/pending-operations/skatteverket-commit.ts. This module
* defines the SHARED shapes so the extension (which may import core freely)
* and the v1 route agree on the contract without core ever importing the
* extension.
*/
import type { VatPeriodType } from '@/types'
/** Which Skatteverket view(s) to fetch. */
export type SkvVatDeclarationState = 'submitted' | 'decided' | 'both'
export interface SkvVatDeclarationStatusInput {
periodType: VatPeriodType
year: number
/** 1-12 for monthly, 1-4 for quarterly, 1 for yearly. */
period: number
/** Defaults to 'both'. */
state?: SkvVatDeclarationState
}
/** Result returned by the extension's fetchVatDeclarationStatus. */
export type SkvVatDeclarationStatusResult =
| {
ok: true
/** 12-digit Skatteverket redovisare identifier for the company. */
redovisare: string
/** Skatteverket period identifier (YYYYMM: the period's last month). */
redovisningsperiod: string
/**
* Skatteverket's /inlamnat body (the declaration as filed), or null when
* nothing has been submitted for the period or state='decided'.
*/
submitted: unknown
/**
* Skatteverket's /beslutat body (the beslut), or null when Skatteverket
* has not decided the period yet or state='submitted'.
*/
decided: unknown
}
| {
ok: false
/** Structured error code (see lib/errors/structured-errors.ts). */
code: string
http_status: number
error: string
}
/** Read services a fully-wired skatteverket extension exposes on `services`. */
export interface SkatteverketReadServices {
fetchVatDeclarationStatus: (
supabase: unknown,
userId: string,
companyId: string,
input: SkvVatDeclarationStatusInput,
) => Promise<SkvVatDeclarationStatusResult>
}