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>
This commit is contained in:
co-authored by
Claude Fable 5
parent
0de766c6a4
commit
60920ec794
@@ -1,6 +1,6 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`v1 spec snapshot > matches the recorded endpoint count > endpoint-count 1`] = `124`;
|
||||
exports[`v1 spec snapshot > matches the recorded endpoint count > endpoint-count 1`] = `125`;
|
||||
|
||||
exports[`v1 spec snapshot > matches the recorded endpoint key set > endpoint-keys 1`] = `
|
||||
[
|
||||
@@ -52,6 +52,7 @@ exports[`v1 spec snapshot > matches the recorded endpoint key set > endpoint-key
|
||||
"GET /api/v1/companies/:companyId/salary-runs/:id/employees",
|
||||
"GET /api/v1/companies/:companyId/salary-runs/:id/employees/:employeeId",
|
||||
"GET /api/v1/companies/:companyId/salary-runs/:id/payslips/:employeeId/pdf",
|
||||
"GET /api/v1/companies/:companyId/skatteverket/vat-declarations",
|
||||
"GET /api/v1/companies/:companyId/supplier-invoices",
|
||||
"GET /api/v1/companies/:companyId/supplier-invoices/:id",
|
||||
"GET /api/v1/companies/:companyId/suppliers",
|
||||
|
||||
@@ -164,4 +164,7 @@ import '@/app/api/v1/companies/[companyId]/articles/route'
|
||||
// #1348: company-settings write (PATCH, MCP-tool-identical field set).
|
||||
import '@/app/api/v1/companies/[companyId]/settings/route'
|
||||
|
||||
// #1663: filed momsdeklaration read (SKV inlamnat/beslutat).
|
||||
import '@/app/api/v1/companies/[companyId]/skatteverket/vat-declarations/route'
|
||||
|
||||
export {}
|
||||
|
||||
@@ -108,6 +108,9 @@ export const V1_ENDPOINT_SCOPES: Record<string, ApiKeyScope> = {
|
||||
'POST /api/v1/companies/:companyId/fiscal-periods/:id/currency-revaluation': 'bookkeeping:write',
|
||||
// Compliance check (Accounted's defensible edge).
|
||||
'GET /api/v1/companies/:companyId/compliance/check': 'compliance:read',
|
||||
// #1663: filed momsdeklaration read (SKV inlamnat/beslutat). Rides
|
||||
// compliance:read, mirroring the MCP gnubok_vat_declaration_status mapping.
|
||||
'GET /api/v1/companies/:companyId/skatteverket/vat-declarations': 'compliance:read',
|
||||
// Phase 4 PR-3: Documents (multipart).
|
||||
'POST /api/v1/companies/:companyId/documents': 'documents:write',
|
||||
'GET /api/v1/companies/:companyId/documents/:id/download': 'documents:read',
|
||||
|
||||
@@ -3342,6 +3342,11 @@ const SKATTEVERKET: Record<string, StructuredErrorEntry> = {
|
||||
message_en: 'Skatteverket rate limit exceeded.',
|
||||
retryable: true,
|
||||
},
|
||||
SKATTEVERKET_API_ERROR: {
|
||||
httpStatus: 502,
|
||||
message_sv: 'Skatteverkets tjänst svarade med ett fel. Se detaljerna och försök igen.',
|
||||
message_en: 'The Skatteverket API returned an error. See details for the upstream message.',
|
||||
},
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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>
|
||||
}
|
||||
Reference in New Issue
Block a user