feat(vat): oss momskod keeps unionsordningen sales out of the momsdeklaration (#1797)

A Fortnox user with OSS sales hit the SIE import mapping step and found no
way to map OSS accounts: the momskod picker had no OSS option, 3106-style
labels ("Försäljning varor till annat EU-land, momspliktig") were suggested
as EU-varor (ruta 35), and an OSS revenue account with a sats set leaked into
ruta 05. Skatteverket: "Den försäljning som du redovisar i OSS ska du inte
redovisa i den vanliga momsdeklarationen."

- add the 'oss' revenue treatment: allowed for class 3 only, mapped to no
  ruta, default rate null (destination-country rate is not a Swedish sats);
  explicit 'oss' also overrides static BAS mappings such as 3001
- REVENUE_RUTA becomes a partial map where null = allowed but off the
  declaration, so the class gate no longer conflates "no ruta" with
  "purchase-only"
- SIE label suggestion: OSS/unionsordningen labels suggest 'oss';
  momspliktig EU-varor labels are left for review instead of ruta 35
- AccountVatTreatmentSchema derives from ACCOUNT_VAT_TREATMENTS instead of a
  second literal list
- migration widens the class-aware CHECK with 'oss' for class 3 (superset;
  NOT VALID + VALIDATE like its predecessor); pg test extended
- sv/en labels; unit tests for resolver, suggestion, declaration exclusion

Per-country VAT rates on invoices and the quarterly EUR/ECB OSS underlag
remain unbuilt (DECISIONS.md).


Claude-Session: https://claude.ai/code/session_01E3QB8GxJ9tS217agHjLRk7

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-08-22 10:49:07 +02:00
committed by GitHub
co-authored by Jakob Wennberg Claude Fable 5
parent 1f7acbf144
commit 51c815254a
11 changed files with 156 additions and 12 deletions
+4 -6
View File
@@ -1,6 +1,7 @@
import { z } from 'zod'
import { normaliseSwish, isValidSwish } from '@/lib/payments/swish'
import { normalizeVatNumber } from '@/lib/vat/vat-number'
import { ACCOUNT_VAT_TREATMENTS } from '@/lib/vat/account-vat-treatment'
import {
accountNumberSchema,
isoDateSchema,
@@ -2307,12 +2308,9 @@ const defaultVatRate = z
.nullable()
.optional()
export const AccountVatTreatmentSchema = z.enum([
'standard_25', 'reduced_12', 'reduced_6', 'exempt',
'reverse_charge_domestic', 'reverse_charge_eu_goods',
'reverse_charge_eu_services', 'reverse_charge_non_eu_services',
'export_goods', 'export_services', 'vmb', 'rental_voluntary',
])
// Single source of truth for treatments is lib/vat/account-vat-treatment.ts;
// the DB CHECK on chart_of_accounts.default_vat_treatment mirrors it per class.
export const AccountVatTreatmentSchema = z.enum(ACCOUNT_VAT_TREATMENTS)
const defaultVatTreatment = AccountVatTreatmentSchema.nullable().optional()