Finance & Billing
286 features · 23 subsystems
Financial management across all federation levels. Built on the platform's financial primitives (payments, invoicing, billing, bookkeeping) extended with petanque-specific financial workflows.
Fee Management
F08.01- F08.01.01 Shipped
Composite license fee configuration — license fee = sum of configurable components set at different OrgNode levels. Example France: part fédérale (set by FFPJP) + part ligue (set by ligue régionale) + part comité (set by comité départemental) + assurance. Each component configurable per license type, age, category. Supports regional price variation (France, Spain).
✅ PL-F0801a - F08.01.02 Shipped
Club affiliation fee configuration (flat or per-member)
✅ PL-F0801a - F08.01.03 Shipped
Competition entry fee management
✅ PL-F0801a - F08.01.04 Shipped
Fee schedules per season/year
✅ PL-F0801a - F08.01.05 Shipped
Early bird / late fee surcharges
✅ PL-F0801a - F08.01.06 Shipped
Fee waivers and discounts (youth, veteran, disabled, family) — configurable per tenant (e.g., Sweden: no sanction fee for youth classes)
✅ PL-F0801b - F08.01.07 Shipped
Fee splitting with automatic distribution — when a license is purchased, the system automatically splits the payment to the correct recipients (federation account, regional account, district account, insurance). Configurable split ratios per OrgNode level.
✅ PL-F0801b - F08.01.08 Shipped
Fee history and audit trail
✅ PL-F0801b - F08.01.09 Shipped
Sanction fee per competition class — configurable per tenant (e.g., Sweden: 300 SEK per class, paid to district where competition is held). Supports different fee rules per competition level.
✅ PL-F0801b - F08.01.10 Shipped
License suspension administrative fee — configurable per tenant (e.g., Sweden: 2× senior license fee, charged to the club requesting suspension)
✅ PL-F0801b
Payment Processing
F08.02- F08.02.01 Shipped
✅ Payment recording and status tracking
- F08.02.02 Planned
✅ Online payment gateway integration (Stripe, PayPal, local providers)
- F08.02.03 Planned
✅ Bank transfer payment matching
- F08.02.04 Shipped
✅ Payment receipt generation
- F08.02.05 Planned
✅ Installment payment plans (split license fee over months)
- F08.02.06 Shipped
✅ Payment reconciliation
- F08.02.07 Planned
✅ Refund processing
- F08.02.08 Planned
✅ Club bulk payment (club pays all member licenses)
- F08.02.09 Planned
✅ Multi-currency support (international competitions)
- F08.02.10 Shipped
✅ Payment audit trail
- F08.02.11 Shipped
✅ Affiliate hybrid signup med Stripe Payment Link — gated av sys_support-godkännande, 14 d expiry, valuta klassificeras per land (SE→SEK, NO→NOK, DK→DKK, GB→GBP, CH→CHF, övriga→EUR), webhook driver provisionering via PL-T003
✅ PL-T302 - F08.02.13 Shipped
✅ Per-nivå betalkonfiguration — UI-ytor (PAY-2 UI, d49e6f73.m-2) — betalkonfigurationen synlig och användbar i alla fyra apparna, uteslutande mot riktiga endpoints via den genererade klienten (inga stubbar, ingen hårdkodad data): admin /ekonomi (Ekonomi-ytan: konton per nivå, arv/resolved_from, metod-/valuta-togglar, onboarding-status, förbundsträd, koppla-konto med autonomi-grind — ersätter den gamla <Stub>); app /klubbadmin-betalkonto (kassörens tunna konto-status + rå onboarding-länk + status-refresh, capability-gatad finance:view); web Betalmetoder (publik, white-label metodvisning driven av den anonyma projektionen GET /v1/public/tenants/{code}/payment-methods, integrerad på /ansok); sys betalkonfig (/betalkonfig — operatörens läs-endast tvärs-tenant övervakning GET /v1/sys/payment-accounts, payout-paus ⏸, engelska/ljust tema). Två additiva backend-deltan (t-1): kontometadata org_number/contact_email/note + den publika display-projektionen. Ingen plattformsavgift; sys-mockupens "Platform payment parameters"/platform-fee-sektion byggs INTE (mockup-konflikt, se docs/migration/mockup-changelog.md). Specar: specs/admin/views/ekonomi.md, specs/app/views/klubbadmin-betalkonto.md, specs/web/views/betalmetoder.md, specs/sys/views/betalkonfig.md.
✅ PAY-2 UI - F08.02.14 Shipped
✅ Metod-kapabilitetsmodell + metodtillgänglighet & pass-through-avgifter (PAY-3, 8579a2a7.m-1) — v1-metoduppsättningen (card/sepa_debit/swish/mobilepay/ideal/bancontact/vipps/paypal) materialiserad som seedad plattforms-scopad referensdata payment_method_capability (data, inte kod — utökningsbar för PAY-5 utan kodändring) med usage-flaggor (supports_recurring/is_one_off_only/requires_mandate), charge_model/merchant_of_record och land/valuta, plus pass-through-avgiften som live-data payment_method_fee (procent som bp integer + fast *_minor bigint, aldrig float, ingen markup-/kommissionskolumn — PL tar 0 %). Checkout-läsvägen GET /v1/payment-methods returnerar skärningen kapabilitet ∩ PAY-2-aktivering ∩ avgiftsdata med exakt fee_breakdown per metod (percentage_component/fixed_component/fee_total/grand_total som Money, banker's rounding), autentiserad + tenant-bunden utan finance-cap, läcker ingen konto-identitet/PII, 422 validation_error på ogiltig currency/amount_minor. Noll drift mot PAY-2:s katalog. Spec: specs/api/endpoints/payment-methods.md; arkitektur: docs/engineering/architecture/09-payment-infrastructure.md.
✅ PAY-3 - F08.02.15 Shipped
✅ Engångsbetalning med lokala Stripe-wallets + Vipps + pass-through (PAY-3, 8579a2a7.m-2) — den faktiska engångs-charge fyller röret: stripeProvider.CreatePayment parametriseras per metod (Swish SE/SEK, MobilePay DK/FI, Vipps NO/NOK, iDEAL NL/EUR, Bancontact BE/EUR, kort överallt) som en destination charge (on_behalf_of, aldrig application_fee — PL tar 0 %). Vipps är en native Stripe-capability (vipps_payments, private preview — anropet bär vipps_preview=v1-headern; ingen egen modul/webhook; Vipps-recurring är en öppen fråga, engångs i v1). Metod-guard på POST /v1/payments: kapabel för nodens land/valuta (422 method_unsupported_for_account) ∧ aktiverad i PAY-2 (409 method_not_enabled) — konsistent med GET /v1/payment-methods. Svaret bär client_secret + (wallet) redirect_url; status drivs requires_action → succeeded enbart av webhook (klienten pollar GET /v1/payments/{id}). charge.refunded reconcilieras in i refunds (provider-initierad refund syntetiseras; fee_minor nollas aldrig); netto-aggregat (succeeded − refunded) driver subjektets status. Pass-through-avgiften visas exakt innan bekräftelse (0 % markup). Specar: specs/api/endpoints/payments.md, specs/shared/payment-method-picker.md; arkitektur: docs/engineering/architecture/09-payment-infrastructure.md.
✅ PAY-3 - F08.02.16 Shipped
✅ PayPal — egen PaymentProvider-modul, klubb-ägd (PAY-3, 8579a2a7.m-4) — PayPal som den andra äkta PaymentProvider (internal/payments/paypal, provider='paypal') bredvid Stripe, mot PayPals egen plattform (Complete Payments / Multiparty — INTE via Stripe). Klubben äger sitt EGNA PayPal-konto och ÄR merchant-of-record (charge_model=paypal_partner, merchant_of_record=tenant): pengarna settlar direkt på klubbens konto, descriptor visar klubben, och PL drar INGEN partner-/plattformsavgift (application_fee_minor=0, klubben behåller 100 % minus PayPals egen avgift). Provider-routing på POST /v1/payments + refund efter payee-/betalnings-kontots provider (ingen on_behalf_of-gren berör PayPal; Stripe-vägen oförändrad). Egen signaturverifierad webhook POST /v1/payments/webhooks/paypal ovanpå F1-11:s idempotenta webhook_events-rygg (single-fire på (paypal, provider_event_id); osignerad → 400, ingen rad) med handlers PAYMENT.CAPTURE.COMPLETED/DENIED/REFUNDED, MERCHANT.ONBOARDING.COMPLETED, MERCHANT.PARTNER-CONSENT.REVOKED (uppdaterar payments/refunds/payment_accounts; fee_minor nollas aldrig; netto-aggregat driver subjektets status). Engångs i alla målländer (DE/FR/ES/IT/NL/BE + SE/NO/DK/FI). Öppet/go-live (aldrig påstått klart): PayPal-recurring är en öppen fråga som verifieras med PayPal (recurring-metoderna returnerar ErrNotImplemented); partner-/plattformsavtal + per-klubb PayPal-business-konto + permission-grant (Partner Referrals) är go-live-krav (ej kodleverabel); exakta PayPal-avgiftssatser är placeholders (is_verified=false) tills verifierade. Specar: specs/api/endpoints/paypal.md; arkitektur: docs/engineering/architecture/09-payment-infrastructure.md.
✅ PAY-3 - F08.02.17 Shipped
✅ Connected-account-onboarding + provider-capability + 2026 enhanced verification (PAY-3, 8579a2a7.m-5) — onboardingen härdad till ett övervakat flöde så en nollteknisk klubbkassör kan ta sitt connected account hela vägen till "kan ta betalt". Onboarding-läge per KONTOTS land, aldrig ett plattformsval (PL är SE-registrerat → embedded som default; FR-konton → account_tokens/hosted under fransk reglering oavsett att plattformen är svensk). Entitetstyp nonprofit_association (ideell förening) accepteras och passeras till providerns onboarding (MCC-/entitetshantering per land, audit-spårat). Metod-capability-lager: den nya tabellen account_method_capability (per-konto provider-aktiveringsstatus requested → pending → active, skild från PAY-2:s per-nod metod-config) skrivs via POST /v1/payment-accounts/{id}/capabilities/{method}/request (finance:manage, Idempotency-Key, exakt en audit-rad) och synkas ur providerns capability-map via den signaturverifierade account.updated-vägen + refresh-status; checkout (GET /v1/payment-methods) gatas på en TREDJE dimension account_method_capability.status='active'. 2026 enhanced verification: requirements klassas currently_due/past_due/eventually_due med due-dates (UBO/direktör-ID, jun–okt 2026), payout-paus exponeras explicit i klarspråk (payout_paused + requirements_plain med i18n message_key + "Ditt nästa steg" + deadline) och eskaleras idempotent till tenant-admin FÖRE deadline (Omtanke: aldrig en tyst payout-frysning); requirement-tick-jobbet (payment_account_requirement_tick, säkerhetsnät ≥6 mån) är kopplat till klassningen/eskaleringen och körbart via run-job. Deployable ytor: admin /ekonomi (payout-paus-varning + "Ditt nästa steg" + (åter)starta embedded-onboarding + begär metod-aktivering + capability-badge per metod) och sys /betalkonfig (tvärs-tenant kontohälsa: antal restricted/payout-pausade, annalkande requirement-deadlines; aldrig payer-PAN/IBAN), båda mot riktiga endpoints via den genererade TS-klienten (inga stubbar). Ingen ny konto-/betalnings-/refund-/webhook-tabell (enda nya = account_method_capability), ingen fee-/markup-kolumn. Öppet/go-live (aldrig påstått klart): exakta FR-onboarding-krav (account tokens vs Stripe-hostad per entitetstyp) verifieras mot Stripe före lansering. Specar: specs/admin/views/ekonomi.md, specs/sys/views/betalkonfig.md; arkitektur: docs/engineering/architecture/09-payment-infrastructure.md.
✅ PAY-3 - F08.02.18 Shipped
✅ Spelarens betalar-yta /me/payments (PAY-3, 8579a2a7.m-6) — betalarens EGNA self-service-yta, skild från admins scope-filtrerade ledger. Payer-enrichment (ej parallell ledger): den nullbara kolumnen payments.payer_user_id (+ index (payer_user_id, created_at)) materialiserar payer-identiteten på F1-11:s BEFINTLIGA payments-rad — fylld vid betalningsskapande (player-checkout ur bäraren; off-session recurring = källans payer; admin-on-behalf/walk-in = NULL). Payer är den tenant-överskridande auth-identiteten, så historiken är TVÄRS tenants. Nya self-scopade endpoints (selfPattern, payer ALLTID ur bäraren, aldrig query/body — IDOR-säkert, annans rad → 404): POST /v1/me/payments (player-checkout-capture, FR-1), GET /v1/me/payments (egen historik tvärs tenants, filter status/subject_type/from/to + paginering), GET /v1/me/payments/{id} (betalning + refunds + subject-länk + ETag), GET /v1/me/payments/{id}/receipt (nedladdningsbart, tenant-brandat kvitto med riktig data + raden "Petanque Life tar 0,00", Content-Disposition: attachment, ingen klartext-PAN/IBAN), POST /v1/me/payments/{id}/refund-request (payer-initierad refund vid avbokad anmälan). RefundEligibilityPolicy-krok per subject_type (provider-neutralt Go-interface + deny-by-default registry; m-6 äger interfacet/dispatchen, ej domänreglerna) — refund tillåts endast när betalningen är refunderbar OCH policyn medger (subjekt avbokat + belopp), annars 409 refund_not_eligible; skapar EN F1-11 refunds-rad (samma rygg som admin-refund, fee_minor nollas ALDRIG), netto-aggregat flippar status; varje begäran (beviljad + nekad) audit-spårad. Demo/seed-policy för competition_entry (via subject_refund_eligibility) gör flödet lokalt exekverbart tills D-COMP registrerar sin egen. Sparade betalsätt återanvänder m-3:s /me/recurring-sources (bygger ej om). Ingen ny betalnings-/refund-/webhook-tabell (enda schema-tillägg = payer-kolumnen + genererad fee_minor-spegling + demo-seed). App-UI (t-2): app-vyn /betalningar (app/app/betalningar.tsx) med underflikarna History (betalhistorik + mockupens filterchips, per-rad "Kvitto ›"-nedladdning, status-badge färg + IKON + TEXT, refunderad-markering, detaljvy med refund-lista och — endast när eligible — "Begär återbetalning" bakom en bekräftelse-stepper) och Saved methods (återanvänder m-3:s betalmetoder-yta) — delade byggstenar i packages/shared/src/payments/mePayments.ts (wire-typer, filterchips, semantisk status-ton, canRequestRefund-predikat, klarspråks-microcopy), data via den genererade klienten (meListPayments/meGetPayment/meRefundRequest) + autentiserad kvitto-nedladdning; riktiga tom-/laddnings-/fel-tillstånd, inga hårdkodade siffror, WCAG 2.2 AA (ikon + text, ≥48px). Specar: specs/api/endpoints/me-payments.md, specs/app/views/me-payments.md.
✅ PAY-3 - F08.02.19 Shipped
✅ Betal-steget i setup-guiden — data-driven kontostatus (D-SIGNUP-2, f154e12a.m-3) — den adaptiva setup-guidens (kom-igang / kom-igang-klubb / kom-igang-distrikt) steg "Connect payments" kopplat till den riktiga F1-11/PAY-2-betal-ryggen, utan att bygga om något penningflöde. Guidens läsmodell (GET /v1/onboarding/guide) DERIVERAR betal-stegets status server-side ur tenantens/nodens riktiga payment_accounts (aldrig m-1:s hårdkodade literal): payment_status (not_connected = inget konto · action_needed = konto finns men charges_enabled=false, KYC/requirements kvar · active = active + charges_enabled · unknown = ärligt läsfel, aldrig låtsad "klar"), payment_provider (stripe/paypal), payment_charges_enabled och en klarspråks-payment_requirements[]. Samma riktiga status driver utfalls-mätarens förmåga collect_member_fees ("Ta betalt av medlemmar"/"Ta betalt av besökare") — achieved=true enbart när ett konto är active + charges_enabled, aldrig hårdkodat. Betal-steget markeras klart som UTFALL när kontot är aktivt (ingen manuell bock); distriktsvarianten är Required:false/överhoppbart ("bara om distriktet tar emot pengar direkt"). Nod-skopning: en klubb vars pengar settlar till ett eget finansiellt autonomt nod-konto (F1-9) bedöms på nodens konto; en federation utan nod-konto på tenant-nivå. admin-vyn (components/onboarding/PaymentStepStatus.tsx i GuideView) renderar den riktiga kontostatusen inline (leverantör + charges_enabled + klarspråks-kravlista) som ikon+text+färg (WCAG 2.2 AA, ≥48px CTA), med ärliga tom-/fel-tillstånd och deep-link till den byggda djupytan admin/ekonomi (den döda rutten /admin/kom-igang/betalning är borta). Per-variant settlement-kopia ("Money settles straight to your club — PL never holds it", PL tar 0 %). Endast läsning via befintliga /v1/payment-accounts*/guide-kontraktet — inga nya betal-endpoints. Specar: specs/api/endpoints/onboarding-guide.md, specs/views/admin-onboarding-guide-views.md.
✅ D-SIGNUP-2 m-3 - F08.02.12 Shipped
✅ Per-nivå betalkonfiguration med arv nedåt (PAY-2, d49e6f73.m-1) — connected account per Tenant/OrgNode (F1-11:s payment_accounts; OrgNode-konto kräver financial-autonomi, BR06.12) med per-konto aktiverade metoder (usage/display_order/constraints, land×valuta×usage-guards 422 method_unsupported_for_account, onboarding-grind 409 account_not_onboarded) och valutor (payment_currency_config, exakt EN default, 422 currency_not_supported). Kärnan: resolve-before-charge GET /v1/payment-config/resolve (§5d.2) — egen nod → närmaste förälder med kvalificerat konto → tenant-rot, spårbar resolved_from (self/ancestor/tenant), 404 no_effective_payment_account när kedjan är tom; arv är resolution-only (BR06.11). Plus metodkatalog-endpoint (data, inte kod), läs-endast sys-översikt GET /v1/sys/payment-accounts (payout-paus-bevakning), PATCH/disable på konton, tröskelstyrd requirement-tick och seed-payment-config (SE self / FR ancestor / NO tenant). Ingen plattformsavgift — PL tar 0 på tenant-betalningar. Spec: specs/api/endpoints/payment-config.md.
✅ PAY-2
Invoicing
F08.03- F08.03.01 Shipped
Invoice generation (line items, tax, totals)
✅ PL-F0803a - F08.03.02 Shipped
Automatic invoice generation (license renewal, affiliation)
✅ PL-F0803a - F08.03.03 Shipped
Invoice templates per federation
✅ PL-F0803a - F08.03.04 Shipped
Invoice delivery (email, download)
✅ PL-F0803a - F08.03.05 Planned
✅ Credit note management
- F08.03.06 Shipped
✅ Invoice status tracking (draft, sent, paid, overdue)
- F08.03.07 Planned
✅ Payment reminder automation
- F08.03.08 Shipped
✅ Debt collection workflow — involuntary dunning lifecycle: banner → 6 months read-only → +12 months data deletion
✅ PL-T047 - F08.03.09 Shipped
Multi-issuer invoicing — Invoice.issuer_type ∈ {platform, tenant, org_node} + issuer_tenant_id/issuer_org_node_id separerar utställare från gäldenär; cross-tenant-fakturor möjliga (FIPJP → nationsförbund); validatorer enforcerar konsistens; backfill-skript sätter historiska fakturor till platform-utställda.
✅ PL-T091 - F08.03.10 Shipped
Multi-language invoicing + ECB reference rate — Invoice.language (ISO-639-1) + per-språk PDF-templates (templates/invoice/{template_name}/{lang}.html) med en.html-fallback + structlog-warning; plattformsfakturor låsta till engelska; ECB-referenskurs fångad per faktura vid utfärdande (ecb_reference_rate + ecb_reference_date) för audit-spårbarhet i cross-currency-scenarier.
✅ PL-T091 - F08.03.11 Shipped
Strukturerad e-faktura UTGÅENDE (EN 16931) — GET /v1/sys/billing/invoices/{id}/einvoice och GET /v1/tenant/billing/invoices/{id}/einvoice med format=facturx|zugferd|xrechnung (zugferd = alias, samma bytes). Dokumentet är en LÄSNING av den oföränderliga fakturaraden + bokens partsdata — inget lagras. Gaten är KÖPARENS land (buyer_country ∈ {FR, DE}); profilens obligatorier prövas FÖRE första byten (422 einvoice_party_incomplete med fältpekare, regel-id och var uppgiften sätts). Den additiva DTO-nyckeln einvoice {applicable, formats, reason, missing} styr ytans knappar.
✅ BILL 4e608d85.m-14 - F08.03.12 Shipped
Strukturerad e-faktura INKOMMANDE (mottagning + registrering) — POST /v1/accounting/einvoices:receive (råa XML-bytes, CII och UBL), hårdad parser (ingen DTD/entitetsexpansion, tak 5 MiB), BR-CO-10…-16, landgate, kvitto genom m-7:s media.BlobStore-väg och dubblettskydd i databasen; :register går genom RegisterSupplierInvoiceSeam med bilagan einvoice_original och bokför ingenting (skulden bokförs vid attesten); :reject behåller kvittot och släpper dubblettnyckeln.
✅ BILL 4e608d85.m-14 - F08.03.13 Shipped
Partsdatan — legal_address, einvoice_contact, payment_iban (mod-97), payment_bic på accounting_settings; billing_address på abonnemanget (kundens egen väg + operatörens PUT /v1/sys/billing/invoice-settings/{tenant_id} bakom fresh-auth + motivering + WORM); platform_invoice.buyer_address som ögonblicksbild vid :issue, aldrig backfillad.
✅ BILL 4e608d85.m-14 - F08.03.14 Planned
⊘ PDF/A-3-hybriden (Factur-X/ZUGFeRD-PDF med inbäddad XML) och inkommande PDF-extraktion — huset renderar/tolkar ingen PDF i Go.
⊘ PDF-sidecaren (architecture-spec §7) - F08.03.15 Planned
⊘ Transport — FR PPF/PDP (Chorus Pro), DE ZRE/OZG-RE, Peppol-accesspunkt, e-postinkorg, FR e-reporting. m-14 producerar och tar emot filer.
⊘ eget go-live-initiativ - F08.03.16 Planned
⊘ SE-e-faktura (Peppol BIS Billing 3.0, Svefaktura) och XRechnung UBL / Factur-X BASIC+EXTENDED utgående.
⊘ framtida BILL-milstolpe / hardening
Dunning & Payment Enforcement
F08.09**Status:** PL-T047 implemented
How it works- F08.09.01 Shipped
Involuntary dunning lifecycle: banner_only → final_warning → read_only → pre_deletion → deleted
✅ PL-T047 - F08.09.02 Shipped
Persistent payment-overdue banner visible to all tenant users (not just admins)
✅ PL-T047 - F08.09.03 Shipped
Email cascade to all tenant admins at each dunning stage transition
✅ PL-T047 - F08.09.04 Shipped
Stripe webhook integration: invoice.payment_failed opens case, invoice.paid closes (if ≤ final_warning)
✅ PL-T047 - F08.09.05 Shipped
Contract billing integration: 30-day overdue triggers dunning case
✅ PL-T047 - F08.09.06 Shipped
Platform-admin manual waive with mandatory reason + governance audit log
✅ PL-T047 - F08.09.07 Shipped
Anti-scope: no auto-reactivation after read_only — manual waive required
✅ PL-T047 - F08.09.08 Shipped
Voluntary cancellation during active dunning case closes dunning cleanly
✅ PL-T047
Federation Budgeting
F08.04- F08.04.01 Shipped
Annual budget creation per federation — POST /v1/accounting/budgets (en budget per (räkenskapsår, org-nod))
✅ BILL m-13 (4e608d85.m-13) - F08.04.02 Shipped
Budget categories — budget_line med kontointervall ur bokens BAS-kontoplan, aldrig fria etiketter; överlapp avvisas av databasens EXCLUDE-constraint
✅ BILL m-13 (4e608d85.m-13) - F08.04.03 Shipped
Budget vs. actual tracking — GET …/budgets/{id}/variance, en LÄSNING av bokförda verifikat (inget utfall lagras)
✅ BILL m-13 (4e608d85.m-13) - F08.04.04 Shipped
Budget approval workflow — :propose/:approve/:reject/:withdraw/:close genom F1-6 (request_type='budget') med självbeslutsvakt
✅ BILL m-13 (4e608d85.m-13) - F08.04.05 Shipped
Financial report generation (annual, quarterly) — variansens period=year\
q1..q4 ur räkenskapsårets egna datum | ✅ BILL m-13 (4e608d85.m-13) - F08.04.06 Shipped
Bookkeeping / ledger entries
✅ BILL m-1 (4e608d85.m-1)
Sponsorship & Grants
F08.05- F08.05.01 Shipped
Sponsor registry (company, contact, contract period, amount)
✅ PL-F0805 - F08.05.02 Shipped
Sponsorship contract management
✅ PL-F0805 - F08.05.03 Shipped
Sponsor exposure tracking (logo placement, mentions)
✅ PL-F0805 - F08.05.04 Shipped
Grant application tracking (government, sports council)
✅ PL-F0805 - F08.05.05 Shipped
Grant reporting and accountability
✅ PL-F0805 - F08.05.06 Shipped
Sponsorship revenue allocation (competition, federation, club)
✅ PL-F0805 - F08.05.07 Shipped
Grant application workflow — MinistryGrantApplication med 8-state-machine (draft → submitted → under_review → awarded → in_progress → reporting_due → closed; rejected som terminal-gren)
✅ PL-T223 - F08.05.08 Shipped
Multi-stage status — capability-gated transitions via submit/record_decision/mark_in_progress/close med immutabel audit-trail
✅ PL-T223 - F08.05.09 Shipped
Reporting-due reminder — daglig cron-trigg 30/14/7 dagar före closes_at, deduplicerad per bucket, status promoveras till reporting_due vid minsta bucket
✅ PL-T223
Prize Money & Awards
F08.06> **Om-markerad till greenfield 2026-09-11 (D-PRIZE `40ec6f10.m-1`).** ✅-markeringarna
How it works- F08.06.01 Shipped
Prize pool configuration per competition
✅ greenfield 40ec6f10.m-1 — /v1/prize-pools (klassbunden pott, finansieringskällor, livscykel draft→active→closed) - F08.06.02 Shipped
Prize money distribution rules
✅ greenfield 40ec6f10.m-1 — regeln som KONFIG (/v1/prize-distribution-presets, …/distribution-rule), heltalsfördelning med redovisad rest - F08.06.03 Shipped
Winner payment processing
✅ greenfield 40ec6f10.m-2 — raden drivs pending → approved → paid genom F1-6:s motor med tvåpersonsregel (EN approval_request per RAD, request_type=prize_payout), enskilt eller i batch med MÄTT per-rad-kvitto, och varje steg bokförs i BILL genom husets EGNA verifikatsömmar. Bokförd avvikelse: paid är kansliets BOKFÖRDA utbetalning (MarkPrizePayoutPaid är ordets ENDA skrivare), bekräftad av BILL m-10:s bankavstämning — huset har ingen F1-11-port som flyttar pengar UT till en människa, och den porten är ⊘ med ägare PAY/ägarbeslut - F08.06.04 Shipped
Tax reporting for prize money
✅ greenfield 40ec6f10.m-2 — den aggregerade rapporten GET /v1/prize-payouts/tax-report per skatteland × valuta (en LÄSNING vid frågetillfället, aldrig ett lagrat aggregat) + CSV-underlaget …/tax-report/export.csv. BERÄKNINGEN är m-1:s (/v1/withholding-tax-rates, fryst på raden, null med skäl i stället för ett tyst 0). ⊘ KVAR: Skatteverkets KU-XML och utländska motsvarigheter, ägare ägarbeslut — huset bär varken formatspecifikation, personnummer/TIN eller inlämningskanal, så CSV:ns tax_identifier är ALLTID tom med sin kommentarsrad - F08.06.05 Shipped
Trophy and medal inventory tracking
✅ greenfield 40ec6f10.m-3 — medaljer, pokaler, plaketter, diplom och vandringspriser inventerade per tävlingsklass, AUTO-TILLDELADE per placering i SAMMA transaktion som klassens resultat publiceras (m-1:s ankare i PublishCompetitionResult — EXAKT en utlösare), med units MÄTT ur registration_player och bristen (units_short) bokförd PÅ RADEN. Vandringsprisets pending → awarded → returned med förväntat returdatum, svepet trophy_return_reminder och ombindning till nästa upplaga (samma artikel — dess tilldelningslista ÄR gravyrlistan). Artikelns inköpskostnad bokförs i förbundets bok genom BILL:s EGNA sömmar: debet 4010 varukostnad (+ ingående moms ur bookkeeping.VATAccountFor bara när boken är momsregistrerad), kredit 1930 bank på paid_on. Bokförd avvikelse: ingen leverantörsskuld på 2440 utan faktura — attest FÖRE betalning är BILL m-7:s väg, och artikeln bär då bara en MJUK supplier_invoice_ref. ⊘ KVAR: utmärkelser/jury/rekord/hall of fame (ägare D-AWARDS), prisceremonins schemaläggning (D-EVENTS m-7), avskrivning/lagerplats och per-spelare-rader för medaljer (ägarbeslut)
Expense Management
F08.07- F08.07.01 Shipped
Expense claim submission (umpires, officials, delegates)
✅ PL-F0807 - F08.07.02 Shipped
Travel expense calculation (distance, per diem)
✅ PL-F0807 - F08.07.03 Shipped
Expense approval workflow
✅ PL-F0807 - F08.07.04 Shipped
Expense reimbursement processing
✅ PL-F0807 - F08.07.05 Shipped
Expense category reporting
✅ PL-F0807
Financial Reporting
F08.08- F08.08.01 Shipped
Revenue reports (by source) — GET /v1/accounting/reports/revenue-by-source; källan är KONTOT, total_minor byte-identiskt med resultaträkningen
✅ BILL m-13 (4e608d85.m-13) - F08.08.02 Shipped
Expense reports (by category) — …/costs-by-category med BAS-klasser + budget_minor som läsning (null, aldrig 0, när kontot inte är budgeterat)
✅ BILL m-13 (4e608d85.m-13) - F08.08.03 Shipped
Federation financial summary (upward reporting) — …/consolidation: subträdssummor på path-prefix, nivåer ur tenantens EGEN org_node_levels
✅ BILL m-13 (4e608d85.m-13) - F08.08.04 Shipped
Club financial health indicators — …/club-health med kod-deklarerade trösklar; betalningsgrad/förfallet är ⊘ med ägare (huset saknar register över tenantutställda fakturor)
✅ BILL m-13 (4e608d85.m-13) - F08.08.05 Shipped
Multi-year trend analysis — …/trend, högst 10 år, tillväxt i bps och null från noll; ingen prognos (pågående år märks to_date)
✅ BILL m-13 (4e608d85.m-13) - F08.08.06 Shipped
Export to accounting software — SIE-4 (BILL m-2) + CSV och SAF-T 2.0 (GET /v1/accounting/export/{csv,saft}) med historikrad + audit
✅ BILL m-2 + BILL m-13 (4e608d85.m-13)
Bokföring och redovisning
F08.10> **Greenfield-ombyggnad (BILL m-1, `4e608d85.m-1`) — bokföringsmotorns kärna.** Den rena
How it works- F08.10.13 Shipped
✅ Bokföringsmotorn — tenant-isolerad dubbel bokföring (BILL m-1 t-1, 4e608d85.m-1) — migration 00217 med ledger_account (BAS-2024-baslinjen per bok, kontotyp + standardmomssats + SRU), fiscal_year (icke-överlappande per tenant), accounting_entry + accounting_line och period_lock. Invarianterna är hårda, inte hygien: balanskravet SUM(debit_minor) = SUM(credit_minor) per verifikat (422 unbalanced_entry, bokförs aldrig), gapfritt löpnummer per (tenant, fiscal_year, series) tilldelat i transaktion vid bokföring (ett utkast förbrukar inget nummer ⇒ — i Ver.nr), oföränderlighet (ett bokfört verifikat ändras eller raderas aldrig — rättelse uteslutande via motverifikat med reverses/reversed_by-paret) och periodlåset (409 period_locked, överlappande lås 409 period_overlap). Pengar är heltal i minsta enhet (*_minor bigint) + valutakod — aldrig float. Rutter: /v1/accounting/{accounts,fiscal-years,entries,period-locks,cost-centres} + entries/{id}:post / :reverse + rapporterna income-statement/balance-sheet (svaret bär serverns egen balanced-dom + de två summorna, med periodens resultat som härledd rad så balansen går ihop utan bokslutsverifikat). Bracket-ops-filter ur whitelist, ETag/If-Match, Idempotency-Key och audit på varje mutation. Tenant-isoleringen är säkerhetsegenskapen: tenant_id injiceras server-side ur sessionen — ytan skickar aldrig ett tenant-fält, och PL:s böcker (tenant-noll) är samma rutter med plattforms-tenanten som aktiv. Capabilities accounting:read/accounting:post/accounting:admin (deny-by-default). Fixtur: go run . seed-bokforing (två böcker, tre identiteter inkl. läsar-negativet, fyra verifikatlägen per bok, ett periodlås). Kontrakt: specs/api/endpoints/accounting-ledger.md; motorns invarianter: docs/domain/accounting-flows.md §1.
✅ BILL m-1 - F08.10.14 Shipped
✅ Adminytan /bokforing — Verifikationer · Kontoplan · Resultat & balans (BILL m-1 t-2, 4e608d85.m-1) — mockupens (design/src/pages/admin/bokforing.astro) tre första flikar som levande ytor mot motorns egna rutter, uteslutande genom den genererade TS-klienten (ingen rå fetch, ingen egen lagring, inget tenant_id från klienten): *Verifikationer* (periodlåsbanner ur RIKTIGA låsrader, filterpanel mot serverns bracket-ops, tabell med SERVERNS löpnummer, — för utkastet, status som ikon + text + färg per rad, ↩-markering på BÅDA raderna i motverifikatparet, formuläret *Nytt manuellt verifikat* med radtabell + löpande summering som ren visning och *Bokför* / *Spara som utkast* / *Reversera*), *Kontoplan* (tabell, *Lägg till konto* inkl. SRU-fältet, If-Match-låst redigering, serverns 409 account_exists/409 account_in_use i klartext) och *Resultat & balans* (resultat- och balansräkning, balanskontrollen ur serverns balanced-fält, period- och kostnadsställe-väljare — den senare ur GET …/cost-centres, aldrig ur /v1/org-nodes). Ytan dömer aldrig: *Bokför* är alltid tryckbar och 422 unbalanced_entry renderas vid fältet; beloppen matas i minsta enhet (»100 = 1 krona«) utan klientomräkning; läsar-negativet ser listan men får ingen mutationsknapp renderad (aldrig en utgråad attrapp). Vy-spec med komponentträd, ⊘-tabell och den pinnade testid-mängden: specs/admin/views/bokforing.md.
✅ BILL m-1 - F08.10.15 Planned
⊘ Ännu inte byggt i greenfield — med ägare (uppdaterad efter BILL m-2). *Momsrapport/momsdeklaration* (perioder, output − input, omvänd betalningsskyldighet, OSS, periodisk sammanställning) → BILL M5; dit hör också kontoplansflikens *Chart-of-accounts standard* / *Tax residence*, som hänger ihop med momskontomappningen per land (A6). *Provider-export (Fortnox/Visma/Xero), coa_mapping och Bokio direkt-synk (OAuth2)* → BILL M4 (tenantens export-läge) — m-2 levererade SIE-4 och arkivet, inte provider-formaten. *Konsolidering uppåt i OrgNode-hierarkin och jämförelseperioder* → M5/M6. *Bilagor/kvitton på verifikat* → BILL M3. *Resultatdisposition (2098 → 2091) och periodiseringsautomat* → manuella verifikat, ingen automat byggs. Ingen av dem renderas som attrapp i adminytan: de utelämnade elementen är oklickbara aria-disabled-markeringar med sin ägare i klartext.
⏳ BILL M3/M4/M5/M6 - F08.10.16 Shipped
✅ Årsstängning, SIE-4-export och det lagstadgade arkivet — kontraktet (BILL m-2 t-1, 4e608d85.m-2) — migration 00218 med accounting_settings (den bokföringsskyldiges namn + organisationsnummer, A-8 — huset saknade kolumnen), year_end_close (utkast → finaliserat, med *fastställt av*, datum, not, låsflagga och den frysta ögonblicksbilden), accounting_archive (paketets bytes, content_hash, manifest, frist) och accounting_export_log (append-only historik), plus tre oföränderlighetstriggar i databasen och den additivt utvidgade source_type-mängden (year_end_close). Bokslutet är EN transaktion (POST …/year-end-closes/{id}:finalise): ögonblicksbilden fryses ur m-1:s egna rapportfunktioner, resultatöverföringen bokförs på 8999 → 2099 med sitt EGET löpnummer ur serieräknaren, periodlåset skapas, året stängs och carry-in rullar balanskontona (2099 → 2098) till nästa år — förvillkoren (409 draft_entries_exist, 422 org_number_required, 409 period_overlap, 409 year_end_close_already_finalised) prövas i skrivningens transaktion, och carry-ins överhopp bär alltid sin orsak. Fristen är lag, inte konfiguration (internal/bookkeeping/retention.go): varje rad bär sin juridiska hänvisning (bokföringslagen 7 kap. 2 § · HGB § 257 · Code de commerce L123-22), regeln är 31 december det kalenderår räkenskapsåret slutade + antalet år (ett brutet år som slutar 2026-04-30 bevaras t.o.m. 2033-12-31, aldrig 2033-04-30) och okänt land ger katalogens längsta frist (fail-closed). Arkivet skrivs bara av sitt svep (run-job accounting_archive_generate, G-10): paketet är en deterministisk zip med SIE-4-filen, de fyra rapporterna som JSON, verifikatindexet som CSV, bokslutet och systemdokumentationen som Markdown plus ett manifest vars coverage[] aldrig är tomt — det räknar upp vad som INTE ingår, med ägare. Rutter: /v1/accounting/{settings, year-end-closes, export/sie4, exports, archives} + fiscal-years/{id}:carry-in. Kontrakt: specs/api/endpoints/accounting-archive-export.md + specs/api/endpoints/accounting-ledger.md; runbook: docs/engineering/runbooks/bokslut-och-arkivering.md.
✅ BILL m-2 - F08.10.17 Shipped
✅ Adminytan /bokforing — Export (SIE) · Arkiv (lagstadgat) · Bokslut per säsong · verifikatdetaljen (BILL m-2 t-2, 4e608d85.m-2) — mockupens tre ⊘ m-2-element som levande ytor mot t-1:s rutter, plus arkivsektionen (bokfört tillägg mot mockupen, A-13: ett lagstadgat arkiv som inte går att ladda ner eller verifiera är ingen revisor-export). *Export (SIE)*: företagsnamn + organisationsnummer ur GET …/settings med If-Match-låst inline-redigering bakom accounting:admin, exakt två valbara format (SIE4 CP437 · Bokio SIE4 UTF-8 BOM/CRLF — Fortnox/Visma/Xero är ⊘ M4 i väljaren), räkenskapsår med serverns statusmärkning *(öppet)*/*(stängt)*/*(arkiverat)*, serieväljare, *Förhandsgranska* (serverns riktiga #FLAGGA/#SIETYP/#KONTO-rader) och *Exportera ↓* som hämtar serverns bytes genom transportens binärsöm och läser om historiken. Mockupens kryssruta *Posted vouchers only* är statisk text (regeln är serverns — parametern finns inte). *Arkiv (lagstadgat)*: en rad per stängt år med genereringstidpunkt, storlek, hash-prefix, »Bevaras t.o.m. ÅÅÅÅ-12-31« med lagrummet i klartext ur serverns fält (aldrig en klienträknad frist), *Ladda ner*, *Verifiera integritet* (serverns dom som ikon + text + färg) och den ärliga raden *»Arkivpaketet genereras«* för ett nyss stängt år. *Bokslut per säsong* i fliken *Resultat & balans*: mockupens fältmängd, *Spara utkast* och *Finalisera bokslut* med ett bekräftelsesteg som räknar upp konsekvenserna — inklusive att det inte går att ångra (det finns ingen återöppning) — och serverns 409 draft_entries_exist / 422 org_number_required / 409 period_overlap i klartext i ett role="alert", plus det frysta underlaget och carry-ins utfall. *Verifikatdetaljen* (mockupens open) på m-1:s befintliga GET …/entries/{id}: konteringsraderna, summorna, källan, kostnadsstället, motverifikatlänken åt båda håll och bokföringsspåret. Resultaträkningens rad *Periodens resultat* läser nu result_before_transfer_minor, så rapporten läses likadant före och efter en stängning. Läsar-negativet är asymmetriskt (A-9): accounting:read får exportera och läsa arkivet, men ingen inställnings- eller bokslutsknapp renderas. Vy-spec: specs/admin/views/bokforing.md.
✅ BILL m-2 - F08.10.18 Shipped
✅ PL:s abonnemang, plattformsfakturan och den bokförda betalningen — kontraktet (BILL m-3 t-1, 4e608d85.m-3) — migration 00220 med platform_subscription, platform_invoice och platform_invoice_number_counter, två oföränderlighetstriggar och den additivt utvidgade source_type-mängden (pl_invoice_issued, pl_credit_note — pl_invoice_paid fanns sedan 00217 och får sin FÖRSTA skrivare här). Raden har två parter: owner_tenant_id (PL:s tenant-noll, resolvad server-side på tenant_type='platform' och aldrig ur en kropp) och customer_tenant_id — aldrig tenant_id. Fakturametoden, inte kontantmetoden: utfärdandet bokför kundfordran (debet 1510 · kredit 3010\
3030 · kredit 2611), betalningen bokför banken mot fordran (debet 1930 · kredit 1510, ingen momsrad — momsen redovisades vid utfärdandet), kreditnotan speglar utfärdandet och återbetalningens penningben är debet 1510 · kredit 1930. Ingen andra betalrail och ingen egen webhook: pengarna kommer hem genom F1-11:s CENTRALA rail, och m-3 bidrar med två hookar för subject_type='platform_invoice'. Löpnumret (PL-<år>-<0000>) allokeras under radlås vid :issue — ett utkast förbrukar inget nummer (invoice_number: null). En enda momsregel, med lagrummet vid raden: svensk köpare ⇒ 25 % (ML 7 kap. 1 §); varje annat land — och ett saknat land — ⇒ 422 vat_treatment_unresolved med ägaren (m-4) i klartext och NOLL skrivna rader. overdue lagras aldrig: förfallenhet är serverns härledning vid läsning, så m-6:s dunning-maskin blir statusordets enda skrivare. Rutter: /v1/sys/billing/{subscriptions,invoices} bakom husets BEFINTLIGA sys.billing.read + finance.subscription.edit (noll nya capabilities) med grindkedjan mandat → fresh-auth → reason ≥ 10 tecken → resursen → status → If-Match → belopp. Fixtur: seed-bokforings m-3-delta (ingen ny CLI-rad). Kontrakt: specs/api/endpoints/platform-subscriptions-invoices.md; bokföringsmönstren: docs/domain/accounting-flows.md §1c. | ✅ BILL m-3 - F08.10.19 Shipped
✅ Sys-ytan /intakter — abonnemangs- och fakturaregistret + källetiketterna på /bokforing (BILL m-3 t-2, 4e608d85.m-3) — D-SYS b8867909.m-10 lämnade två ⊘-platser med ägare BILL på den befintliga rutten, och m-3 häver dem i samma leverans som källan byts in: fliken *Avgifter & avstämning* får abonnemangstabellen (kund · tier · pricing_version · status som ikon + text + färg · periodslut · utestående — serverns MÄTNING, aldrig en klientsummering) med serverfilter som riktiga bracket-ops och *Nytt abonnemang* / *Säg upp* bakom grindkedjan med bekräftelsesteg och obligatorisk motivering; fliken *Fakturor & betalningar* får fakturatabellen (nr · kund · belopp · förfaller · status · not) där *Förfallen* är serverns härledning och märks som en härledning, plus detaljvyn med raderna, momssatsen per rad, providerdokumentets länk och BOKFÖRINGSSPÅRET (verifikatens serie + löpnummer i klartext) och åtgärderna *Utfärda* · *Kreditera* · *Makulera utkast*. ⊘-korten subscriptions/invoices är borttagna — ett kort som säger *"ägs av BILL"* bredvid en byggd tabell är en lögn i motsatt riktning — medan mrr/arr/churn står kvar med sin nya ägare (BILL m-12: ett abonnemangsREGISTER är inte en MRR-MODELL) och dunning/unmatched_payments/pdf_export/pricing_catalog med sina. På adminytan /bokforing namnger verifikatlistans och detaljvyns *Källa* de tre nya källtyperna i klarspråk med fakturanumret (*"Plattformsfaktura PL-2026-0007 (utfärdad)"*) som en ren regel med fristående test — ingen ny rutt, ingen ny flik, ingen länk över appgränsen. Vy-spec: specs/sys/views/intakter.md; bevis mot körande stack: tools/gate/bill-m3-e2e.mjs.
✅ BILL m-3 - F08.10.20 Shipped
✅ Momsprofilen och det VIES-styrda beslutsträdet — kontraktet (BILL m-4 t-1, 4e608d85.m-4) — migration 00222 med tax_profile (en rad per tenant: skattehemvist, momsregistrering, eget momsnummer, serverägd VIES-status + tidsstämpel + konsultationsreferens, föreningsflaggan, tröskeln och den sys-ägda oss_registered), den tenant-lösa vat_rate-katalogen (EU-standardsatser med källa och giltighetsdatum), ec_sales_line (periodisk sammanställning per köpar-VAT-nr och kalenderkvartal) och den additiva oss_rate_bps på fakturan, med DB-CHECK-vakter för reverse_charge → buyer_vat_number och oss → buyer_country + oss_rate_bps. A9-RESOLVERN ÄR EN REN FUNKTION (billing.ResolveVATTreatment): SE ⇒ standard 2500 bps · annat EU-land med VIES-giltigt köparnummer ⇒ reverse_charge (0 moms, legend, EC-rad) · annat EU-land utan giltigt nummer = B2C ⇒ standard under den statutära tröskeln (99 680 kr, mätt över BÅDA kalenderåren) och oss med köparlandets sats över den · icke-EU eller okänt land ⇒ fail-closed 422 vat_treatment_unresolved. VIES är en leverantörsneutral PORT med deterministisk fejk och dokumenterade markörer; ett portfel är ett FEL, aldrig en dom (statusen lämnas orörd, utfallet redovisas mätt i verification), och resolvern ringer aldrig synkront — den lagrade statusen är auktoriteten vid utfärdandet (A-3). Momskontot kommer ur vat_account_map (SE BAS · FR PCG · DE SKR) och OSS-momsen krediteras ett eget skuldkonto, aldrig 2611. Rutter: /v1/accounting/tax-profile (tenantens egen, accounting:read/:admin) och /v1/sys/billing/tax-profiles/{tenant_id} (PL:s kundregister, sys.billing.read/finance.subscription.edit) — två skrivvägar, EN söm och EN regel — plus GET /v1/sys/billing/ec-sales-lines. :declare byggs INTE (m-11). Fixtur: seed-bokforings m-4-delta. Kontrakt: specs/api/endpoints/tax-profiles.md; beslutsträdet med lagrum: docs/domain/invoicing.md §0b; bokföringsmönstren: docs/domain/accounting-flows.md §1d.
✅ BILL m-4 - F08.10.21 Shipped
✅ Momsytan — behandlingen och momsprofilen blir synliga (BILL m-4 t-2, 4e608d85.m-4) — ett bokfört tillägg utöver mockupen (ingen prototyp ritar en momsskärm; utan panelen kan ingen registrera en fransk kunds momsnummer, och då kan hennes faktura aldrig lösas). *sys /intakter*: fakturadetaljen får momsbehandlingen som ikon + text + färg i klarspråk (*Svensk moms 25 %* · *Omvänd betalningsskyldighet* · *OSS (köparlandets sats)*), där satsen är serverns vat_rate_bps och bara bärs när fakturans rader är entydiga; köparens momsidentitet (numret ur fakturan, VIES-statusen och tidsstämpeln ur kundens profil); RC-legenden ORDAGRANT ur vat_legend — aldrig en klientbyggd lagtext; och köparland + tillämpad oss_rate_bps. Momsprofilpanelen per kund läser GET …/tax-profiles/{tenant_id} och erbjuder redigering + *Verifiera VAT-nr* bakom hela grindkedjan (mandat → fresh-auth → motivering ≥ 10 tecken → If-Match), med VIES-utfallet renderat mätt i TRE lägen — giltigt · ogiltigt · kunde inte verifieras (det tredje är ett fel i försöket, aldrig en dom). De fail-closed-grenarna 409 oss_registration_required, 409 platform_vat_number_missing och 422 vat_treatment_unresolved renderas som åtgärdsbesked vid handlingen, aldrig som ett tomt dataläge. EC-sales förblir ⊘ med ägare BILL m-11 (radunderlaget finns och serveras; deklarationsvyn byggs inte). *admin /bokforing*: tenantens egen momsprofil i inställningsblocket i fliken *Export (SIE)* — land, momsregistrerad, VAT-nr med *Verifiera*, föreningsflaggan — bakom accounting:admin med If-Match, och oss_registered i LÄSLÄGE (en verklighetsuppgift om en registrering hos Skatteverket, sys-ägd). Vy-specar: specs/sys/views/intakter.md §BILL 4e608d85.m-4 + specs/admin/views/bokforing.md §17; bevis mot körande stack: tools/gate/bill-m4-e2e.mjs.
✅ BILL m-4 - F08.10.22 Shipped
✅ Intro-erbjudandet, Price-Lock, HICP-justeringen och förnyelsemotorn — kontraktet (BILL m-5 t-1, 4e608d85.m-5) — migration 00223 med intro-fälten på platform_subscription (intro_until, intro_price_minor, intro_list_price_minor, intro_unconditional), auto_renew, nivåändringens kolumner och tabellen subscription_price_adjustment. Gratisåret är en RABATT till 0 kr, aldrig ett uttag (A8): 0-kr-fakturan får ett riktigt nummer i den obrutna serien, beskattningsunderlag 0 och list_minor/discount_minor som revisorsspår på raden — utan betalrad, utan provider-anrop och utan verifikat, för en rabatt bokförs inte (ML 5 kap. 29 § / mervärdesskattedirektivet art. 26). Ett intro på en icke berättigad tier-grupp avvisas (422 intro_offer_not_eligible), och ett intro utan villkorslöshet likaså (422 intro_offer_requires_unconditional) — med en motprestation är erbjudandet ett byte som ska momsas till marknadsvärde. price_lock_until är en HÄRLEDNING ur signed_at + 36 månader och har därför inget API: kolumnen kan inte sättas, bara läsas. En justering prövas i ordning — avtalet aktivt → effektdatum efter låset (422 price_lock_active) → minst 90 dygns varsel (422 notice_window_too_short) → HICP-taket ur den kod-deklarerade katalogen (422 hicp_cap_exceeded, 422 hicp_reference_unavailable) — och varslet går ut i samma handling med mätt utfall: utan bokfört varsel tillämpas justeringen aldrig (fail-closed). Förnyelsemotorn (run-job platform_subscription_renewal_tick, timkadens) utfärdar förnyelsefakturan 30 dygn före periodslutet genom m-3:s EGEN :issue-söm, omvärderar momsen genom m-4:s A9-resolver vid VARJE förnyelse (ett portfel är ett fel och aldrig en dom), växlar intro → låst pris som en härledning ur intro_until, tillämpar en varslad justering bara med sitt varsel och är expired-ordets enda skrivare (renewal_declined, SYSTEMAKTÖR på audit-raden). Kundens läsfamilj /v1/tenant/billing/{subscription,invoices,invoice-settings} (platform-agreement:read/:manage, tenanten server-side) och sys-mutationerna …:set-auto-renew / :schedule-price-adjustment / :cancel-price-adjustment / :schedule-tier-change / :cancel-tier-change. Kontrakt: specs/api/endpoints/platform-subscriptions-invoices.md §BILL m-5; avtalets livscykel: docs/domain/subscription-lifecycle.md; 0-kr-fakturans form: docs/domain/invoicing.md §0c.
✅ BILL m-5 - F08.10.23 Shipped
✅ Kundens egen vy /abonnemang + prisskyddsdeltat på sys /intakter (BILL m-5 t-2, 4e608d85.m-5) — mockupen design/src/pages/admin/abonnemang.astro som en levande yta: *Aktuell nivå* (tier, pris/år, nästa förnyelse — avtalets FAKTISKA uppgifter, ingen påhittad spelarräkning), Ditt prisskydd med 🎁 *listpris − rabatt = 0* endast där ett intro FINNS, 🔒 price_lock_until ur API:t (ytan räknar aldrig 36 månader själv — låset är serverns härledning) och 📈 HICP-policyn plus den varslade justeringens gamla/nya pris, effektdatum och rätt till avgiftsfri uppsägning, *Betalhistorik och fakturor* med serverns not (A8:s revisorsspår), serverfiltrerat årsval (year[eq]) och providerdokumentets länk där den finns, och *Fakturainställningar* med If-Match, serverns 422 vid fältet och orgnr/momsnummer i LÄSLÄGE med hänvisning till Bokföring (G-14: två skrivfält för samma uppgift blir två sanningar). De fyra riktiga tillstånden: laddar · tomt vid 204 · fel · nekad som BEHÖRIGHETSBESKED (serverns 403 får aldrig se ut som »du har inget avtal«). På sys /intakter får abonnemangstabellen kolumnerna Auto-förnyelse (false = härledningen »avslutas vid periodslut«, aldrig en lagrad status) och Nästa förnyelse plus 🎁/🔒-markeringarna som ikon + text, en prisskyddspanel mot abonnemangets detalj med SERVERNS HICP-post (det finns med flit inget fält för en operatörsinmatad indexsiffra) och de fyra mutationerna bakom hela grindkedjan (mandat → fresh-auth → reason ≥ 10 → If-Match) med affordanser som följer läget; tecknandets intro-block renderas endast för federation/venue — samma grind som serverns — med en villkorslöshets-kryssruta som aldrig är förikryssad. ⊘ med ägare, aldrig döda knappar: PDF-nedladdning/-export → PDF-sidecarn · betalmetod/kort-vault → BILL m-6 · publik prislista, tier-auto-klassificering och proration → pricing-domänen · nyhetsbrevspanelen (F19.17.20) → D-NEWSLETTER · dunning/past_due → BILL m-6. Vy-specar: specs/admin/views/abonnemang.md (ny) + specs/sys/views/intakter.md §BILL 4e608d85.m-5; bevis mot körande stack: tools/gate/bill-m5-e2e.mjs.
✅ BILL m-5 - F08.10.24 Shipped
✅ Kort-valvet, utgångspåminnelsen, indrivningskaskaden och dunning-maskinen — kontraktet (BILL m-6 t-1, 4e608d85.m-6) — migration 00227 med dunning_case (PL-ägd rad OM en annan tenant: owner_tenant_id + customer_tenant_id, aldrig tenant_id), platform_card_expiry_reminder, den additiva länken platform_subscription.default_recurring_source_id och kaskadens bokföring PÅ SKULDEN (platform_invoice.charge_attempts/last_charge_attempt_at — visionens payment_failure_count byggs INTE: en andra räknare om samma försök vore en andra sanning, spec A-2). Ingen platform_card-tabell och ingen andra rail (A-1/NFR-1): ett sparat kort ÄR PAY-3:s recurring_payment_source med PlatformDirect mot PL:s eget merchant-konto, aktiverad av den CENTRALA setup_intent.succeeded-webhooken — kortmetadata kommer enbart ur provider-event, och plattformen ser aldrig mer än märke + last4 (NFR-8). Tre F1-7-svep, alla med injicerad klocka, batch-tak och single-fire i WHERE/DB: platform_card_expiry_scan (30/14/7, högst EN notis per kort och körning — det minsta förfallna fönstret bär den; single-fire är den unika nyckeln (source, exp, milestone), så ett FÖRNYAT kort re-armar påminnelserna av sig självt), platform_invoice_collection_tick (off-session-debitering genom husets ENDA rail med application_fee_minor = 0 och deterministisk nyckel; kaskadens 1h/2h/4h och taket 3 är DEKLARERADE konstanter i internal/billing med källan vid raden — aldrig konfig; ett misslyckande bokförs i en EGEN kort transaktion, för en fallen debitering får aldrig se ut som en tyst no-op) och platform_dunning_stage_tick (trappan, högst EN övergång per tick så en missad körning hinns ikapp steg för steg i stället för att hoppa dit klockan råkar peka). Öppningen är svepets, aldrig en människas: en förfallen faktura ELLER en uttömd kaskad med ett TERMINALT sista försök ⇒ EXAKT ett ärende per gäldenär (garantin är det partiella unika indexet, inte ett if) och abonnemanget flippas active → past_due i samma transaktion. Upplösningen följer anti-scopet: en betalning stänger ärendet automatiskt endast t.o.m. final_warning och först när gäldenärens SAMTLIGA förfallna fakturor är reglerade (A-11); därefter krävs POST …/dunning-cases/{id}:waive genom hela grindkedjan (mandat finance.subscription.edit → fresh-auth → reason ≥ 10 tecken → If-Match) med motiveringen ordagrant i WORM-raden. Rutter: /v1/tenant/billing/payment-method* + …/dunning-banner (medlemskaps-guardad — beloppet endast för platform-agreement:read) och /v1/sys/billing/dunning-cases* + …/cards/expiring. Fixtur: seed-bokforings m-6-delta (ingen ny CLI-rad). Kontrakt: specs/api/endpoints/platform-subscriptions-invoices.md §BILL m-6; domänreglerna med källa: docs/domain/dunning-policy.md.
✅ BILL m-6 - F08.10.25 Shipped
✅ Betalmetoden, indrivningsbannern och sys-fliken *Indrivning* (BILL m-6 t-2, 4e608d85.m-6) — admin /abonnemang: panelen Betalmetod (m-5:s ⊘ *Change payment method* HÄVD i samma leverans som källan) med kortraden *märke · •••• last4 · utgång* ur GET /v1/tenant/billing/payment-method, utgångsmärket (⏰ inom 30 d, rött vid passerad), *Byt betalmetod* som säger att kortet gäller först när leverantören bekräftat det, *Ta bort kort* med bekräftelsesteg och kortets EGEN If-Match (aldrig *), det ärliga tomläget — och aldrig ett PAN. Indrivningens felläge visar *»försök n av 3«* och nästa försök MÄTT ur avtalets collection-block; ytan räknar aldrig kaskaden själv (A-7). Admin-skalets banner (ShellHost, MfaGraceBanner-prejudikatet) är inte avvisbar — ett datum då kontot låses får inte kunna tystas av en enskild inloggad — och dess färg är SERVERNS severity (A-10: gul t.o.m. read_only, röd från pre_deletion_30d), aldrig ensam signal: stadiet står i klartext. Bannern är synlig för alla i organisationen medan beloppet och betalvägen bara skickas till platform-agreement:read-bäraren; 204 ⇒ ingen banner (frånvaro är ett svar), och ett fel på probningen renderar ingenting. sys /intakter får fliken Indrivning (⊘-nyckeln dunning togs ur serverns unavailable[] i samma leverans): stage-chips och lösta-växel som SERVERFILTER (current_stage[eq], is_resolved[eq]), kön *kund · steg · öppnat · trigger · nästa övergång* där »nästa övergång« är SERVERNS härledning, detaljens tidslinje ur stages_history med mätt notisutfall och den HÄRLEDDA fordranslistan, *Efterskänk* som endast renderas för mandatbäraren (frånvaro, aldrig en utgråad knapp) med bekräftelsesteg och serverns 422 vid åtgärden, tomläget *»Inga förbund i indrivning«* och kortlistan »kortet går snart ut« vars påminnelsestatus 30/14/7 är BOKFÖRING ur scanjobbet. Registren får dessutom abonnemangsradens indrivningsläge (*»försök n/3«*) och fakturatabellens Dunning-kolumn med sitt serverfilter dunning_stage[eq] — medan förfallenhet medvetet inte går att filtrera på: den lagras aldrig. ⊘ med ägare, aldrig döda knappar: banner i app/web → respektive yta/D-COMM · manuell ärendeöppning och self-service-efterskänkning → ingen (Icke-mål 9) · hård radering i deleted → D-GDPR/F1-9 · SEPA som sparad metod → PAY-3 · mrr/arr/churn → BILL m-12 · unmatched_payments → BILL m-10. Vy-specar: specs/admin/views/abonnemang.md §8 + specs/sys/views/intakter.md §BILL 4e608d85.m-6; bevis mot körande stack: tools/gate/bill-m6-e2e.mjs (deskriptor tools/gate/e2e.d/999-bill-m6-indrivning.mjs, strukturgrind sys/__acceptance__/bill-m6-indrivning-ui.acceptance.mjs).
✅ BILL m-6 - F08.10.26 Shipped
✅ PL:s utgiftssida (AP) — leverantörsfakturor, företagskort, kvitton och kostnadsimporten: kontraktet (BILL m-7 t-1, 4e608d85.m-7) — migration 00229 med supplier_invoice, card_transaction, cost_import och accounting_receipt, alla tenant-skopade mot samma isolationsgräns som liggaren (NFR-1). Leverantörsfakturan har två grenar: direktgrenen (:approve utan attestkrav) och F1-6-attestgrenen (requires_approval=true ⇒ ett approval-ärende, och :approve driver MOTORNS Approve — ordet approved finns inte som strängliteral i m-7:s handlare eller seed, grep-vaktat i bill_m7_source_guards_test.go). Skulden bokförs vid ATTESTEN (debet kostnadskonto (netto) + debet 2640 (moms) / kredit 2440 (totalt)) och betalningen som ett EGET verifikat (2440 → 1930) — fakturametoden, inte kontantmetoden. Avdragsrätten är en fråga till momsprofilen, aldrig en default: en bok utan momsregistrering får ingen 2640-rad. Förvärvsmomsen (reverse_charge med vat_minor > 0 ⇒ 422 reverse_charge_requires_zero_vat) bokförs som netto-nollparet 2645/2614 ur bookkeeping.VATAccountFor — aldrig en kontoliteral. Företagskortets CSV-import tolkar beloppen med exakt strängaritmetik till öre (både komma- och punktform, aldrig via float, NFR-2) och dedupar på DB-invarianten (tenant_id, external_id); :book på en okategoriserad rad ⇒ 422 uncategorized_transaction. Kvittot går genom husets media.BlobStore-port med MÄTT sha256/mime/size_bytes (ett klientpåstått hash-värde vore ett integritetsbevis ingen räknat) och utan objektlager ⇒ ärligt 503 blob_backend_unavailable, aldrig en rad utan bytes; ett kvitto i efterhand på en bokförd rad uppdaterar RADEN och lämnar verifikatets jsonb orörd (oföränderligheten vinner). Kostnadsimporten går genom den leverantörsneutrala billing.CostImportProvider vars adapter väljs ur MILJÖN (PL_BILL_COST_IMPORT_BACKEND; tom i drift ⇒ ärligt överhopp med logg, fake i local/test, okänt namn ⇒ fel — aldrig en tyst fallback) och bokförs av run-job platform_cost_import med dubbel DB-idempotens (registrets unique (tenant, scope, period) + verifikatets source_event_key); valuta ≠ bokens ⇒ failed:currency_mismatch utan verifikat (aldrig tyst FX). Fixtur: seed-bokforings m-7-delta (ingen ny CLI-rad). Kontrakt: specs/api/endpoints/accounting-payables.md; bokföringsmönstren: docs/domain/accounting-flows.md §1e.
✅ BILL m-7 - F08.10.27 Shipped
✅ Adminfliken *Utgifter* på /bokforing (BILL m-7 t-2, 4e608d85.m-7) — ett bokfört tillägg utöver mockupen: ingen app-prototyp ritar ett AP-element, och design/src/pages/visions/bill/utgifter.astro är ARKIV och därmed fältkontrakt, aldrig skärmkontrakt (dess exempeldata grep-vaktas ur produktionskällan). En flik, aldrig en ny rutt (G-12): leverantörsfakturan, kortköpet och kostnadsimporten bokförs i samma bok, av samma människa, genom samma motor som de manuella verifikaten. Kostnadsimport-panelen visar den MÄTTA summan av FAKTISKT bokförda importer inom det aktiva räkenskapsårets fönster (aldrig ett uppskattat »hittills«, aldrig en failed- eller pending-rad inräknad), senaste period/status per scope, failed-orsaken i klartext (aldrig ett rått enum) och *Importera nu* med 409 cost_import_not_configured/409 cost_import_no_backend renderade ärligt vid åtgärden — de är sanna besked om driftläget, inte fel i ytan. Leverantörsfakturor: tabellen, kedjan *Attestera → Betalmarkera*, F1-6-ärendets läge, två verifikatlänkar per betald rad, Annullera endast i registered bakom ett bekräftelsesteg med motivering, och registreringsformuläret med belopp i minsta enhet (hjälptexten »100 = 1 krona«) där total = netto + moms visas som information — knappen är alltid tryckbar och serverns 422 renderas vid fältet ur kuvertets fältpekare, för en klientgrind hade sett likadan ut ända tills den drev isär från serverns CHECK. Företagskort: CSV-dialogen med serverns MÄTTA räknare {importerade, dubbletter, per-rad-fel}, kvittokolumnen som ikon + text (»Bifogat«/»Saknas«, färg aldrig ensam — och en kvittolös rad går ändå att bokföra: ett hårt kvittokrav hade gjort flödet obrukbart varje dag objektlagret ligger nere) och kategoriseringspanelen med kvittouppladdning och det ärliga 503-läget. Tre riktiga tillstånd per sektion (laddar/tomt/läsfel) med åtgärdsfelen skilda från läsfelen, mandatet fail-closed (utan accounting:post renderas ingen mutationsknapp — aldrig en utgråad attrapp), WCAG 2.2 AA och ≥ 48 px. ⊘ med ägare, aldrig döda knappar: bankavstämning/CAMT/partially_paid → BILL m-10 · momsperioder + :declare → BILL m-11 · kostnadsdashboard/runway → BILL m-12 · regelbaserad auto-kategorisering → framtida delta · riktig Azure-adapter → go-live · sys-yta för AP-/jobbhälsa → D-SYS/BILL m-12 · OCR och e-faktura-mottagning → utanför m-7. Vy-spec: specs/admin/views/bokforing.md §18; bevis mot körande stack: tools/gate/bill-m7-e2e.mjs (deskriptor tools/gate/e2e.d/999-bill-m7-utgifter.mjs, strukturgrind api/__acceptance__/bill-m7-yta-kontrakt.acceptance.mjs).
✅ BILL m-7 - F08.10.28 Shipped
✅ Tenant-bokföringens lägen och RevenueEvent-auto-feeden: kontraktet (BILL m-8 t-1, 4e608d85.m-8) — migration 00230 lägger lägesfälten (mode, feed_toggles) ADDITIVT på m-2:s BEFINTLIGA accounting_settings-rad (ingen andra tabell, ingen andra ETag-serie) och registret revenue_feed_event med sin idempotensnyckel. Ingen extern create-endpoint finns: intäktsverifikat med feed-källtyper skapas ENBART av svepets bokföringssöm, bookkeeping.ManualSourceTypes() förblir {manual, opening_balance, accrual} (grep-vaktat i bill_m8_source_guards_test.go), och mottagningen sker genom RevenueEventIngestor-sinken som installeras på VARJE processor huset konstruerar — en domän skriver aldrig direkt i en annan tenants böcker. Svepet accounting_feed_ingest_tick är garantin (det hittar sitt arbete självt): entitlement-prövning (ej berättigad ⇒ raden LÄMNAS pending, ingen räknare bränns) → total, kod-deklarerad källmappning (ett ord utanför den ⇒ failed:unknown_source_type, aldrig en gissning) → momsdomen genom m-4:s EGEN säljarregel med beloppen ur eventets LÅSTA vat_lines och kontot ur bookkeeping.VATAccountFor (SE 26xx · FR 4457 · DE 1776 — ingen kontoliteral i handlaren) → verifikatet genom m-1:s EGNA sömmar med debet provider-fordran (pengarna står hos betalväxeln tills payout; 1930 direkt vore ett påstående om ett bankkonto som inte fått pengarna — kvittningen är m-10:s). En split ger ETT verifikat PER mottagarnod med egen source_event_key; member_fee momsas aldrig (B3); valuta ≠ bokens ⇒ failed:currency_mismatch (aldrig tyst FX); försökstaket är deklarerat och :requeue är människans väg tillbaka efter en åtgärdad orsak. Plus tröskelsvepen accounting_vat_threshold_scan (A2) och accounting_cash_register_scan (B1), som mäter och flaggar men aldrig beslutar — vat_registered är en verklighetsuppgift och rörs aldrig av ett svep. Fixtur: seed-bokforings m-8-delta (ingen ny CLI-rad; feed-innehållet drivs av en RIKTIG betalkedja, aldrig handskrivna rader). Kontrakt: specs/api/endpoints/accounting-feed.md; mönstret: docs/domain/accounting-flows.md §1f.
✅ BILL m-8 - F08.10.29 Shipped
✅ Adminfliken *Auto-feed* på /bokforing (BILL m-8 t-2, 4e608d85.m-8) — mockupens auto-feed-spår byggt + ett mockuplöst ytdelta, båda bokförda: prototypen ritar källfiltrets *"Auto-feed (revenue events)"* och tomlägestexten *"…wait for the first auto-feed event"* (nu byggda som fältkontrakt respektive riktigt tomläge), men ingen skärm för bokföringsläge och ingen feed-detaljvy — panelen byggs därför på de BYGGDA /bokforing-mönstren. Mockupens kioskrad med kontot 3510 följs inte (3510 är *Fakturerade kostnader* i BAS-2024; kioskintäkten bokförs mot 3610 — riktiga BAS-konton vinner, och literalen grep-vaktas ur produktionskällan). Lägespanelen visar serverns läge, entitlementet i klartext och per-käll-togglarna; växeln är tryckbar även utan tilläggstjänsten och SERVERNS 409 feature_disabled renderas ärligt vid åtgärden — en knapp ytan själv låst hade sett likadan ut ända tills entitlementet ändrades under dess fötter. I plattformskontexten finns ingen växel: läget är härlett och en PUT svarar 409 platform_mode_derived (spec A-5). Feed-spåret visar registrets rader med källa (liggarens ord i klartext + producentens inom parentes), belopp ur heltalet i minsta enhet, mottagarnod, status som ikon + text + färg, skälet i svensk klartext MED serverns kod (momsdom · hint-utfall · failure_reason), parkeringens förklaring (»källan är avslagen« ≠ »väntar på nästa svep«), verifikatlänk till m-1:s detaljvy och :requeue på en felad rad bakom ett bekräftelsesteg — en bokförd rad rättas med motverifikat, aldrig genom en om-bokföring av samma pengar. Filtren är kontraktets bracket-ops och serverfiltrerar. Tre riktiga tillstånd, mandatet fail-closed i affordansen (aldrig en utgråad attrapp), WCAG 2.2 AA och ≥ 48 px. ⊘ med ägare: 409 feature_disabled renderad i webbläsaren → seed-bokforing/t-1 (FRA saknar en inloggningsbar accounting-aktör; koden bevisas i Go mot riktig Postgres, och att panelen renderar serverns dom vid åtgärden bevisas med en riktig 412 stale_version) · payout-avstämning → m-10 · momsperioder → m-11 · export-lägets integrationslager → m-9 · dashboards/runway → m-12. Vy-spec: specs/admin/views/bokforing.md §19; bevis mot körande stack: tools/gate/bill-m8-e2e.mjs (deskriptor tools/gate/e2e.d/999-bill-m8-autofeed.mjs, strukturgrind api/__acceptance__/bill-m8-yta-kontrakt.acceptance.mjs).
✅ BILL m-8 - F08.10.30 Shipped
✅ De externa bokföringsmålen — kontoplansmappningen, filexporterna och Bokio-synken: kontraktet (BILL m-9 t-1, 4e608d85.m-9) — migration 00231 med coa_mapping (per tenant + provider, mjuk radering och unik aktiv nyckel som partiellt index), accounting_integration (en rad per tenant + provider: läget sie4_only|api, company_ref, anslutningsstatus, förseglat tokenpar i credential_sealed och hashat engångs-oauth_state), accounting_sync_run (körningarna med sina MÄTTA räknare) och accounting_entry_sync (registret, unique (tenant_id, entry_id, provider)), plus exportloggens tre nya formatord fortnox_csv|visma_txt|xero_csv i m-2:s BEFINTLIGA CHECK (additivt — SIE-raden bryts aldrig). KONTOPLANSMAPPNINGEN (GET/PUT /v1/accounting/coa-mappings…) med :seed-identity för Fortnox och Visma — ensure-semantik, en rad någon redigerat rörs ALDRIG, svaret är MÄTT {created, skipped} — medan Xero med flit saknar identitetsväg (422 identity_mapping_not_available: Xeros standardkontoplan är inte BAS, och en gissad kod hade fått mottagaren att bokföra fel). DE TRE FILEXPORTERNA (GET /v1/accounting/export/{provider}) är FAIL-CLOSED på en komplett mappning (422 coa_mapping_incomplete med unmapped_accounts[]; preview=true skriver INGENTING) och renderas ur husets deklaration — Fortnox ; + decimalkomma + BOM + CRLF · Visma TAB + decimalkomma + BOM + CRLF · Xero , + decimalpunkt + UTAN BOM + CRLF, endast posted verifikat, beloppen ur heltalet i minsta enhet (12345 öre ⇒ 123,45 respektive 123.45, aldrig via float) — och varje verklig export går genom m-2:s EGEN exportsöm, så exporthistoriken förblir EN sanning om vad som lämnat huset. /export/sie4 behåller sitt företräde: Bokios dialekt är m-2:s ?dialect=bokio, ingen andra SIE-rutt byggs. BOKIO-SYNKEN bakom den leverantörsneutrala porten ExternalLedgerClient med sluten utfallsvokabulär (ok · already_exists · unauthorized · unprocessable · rate_limited · transient · network): anslutningskedjan :configure → :connect → callback → :test-connection med engångs-state (10 min, 422 oauth_state_invalid opakt) och förseglat token som ALDRIG lämnar processen (varken svar, audit-diff — byggd av en EXPLICIT redaktionslista — eller logg bär det; klient-id/hemlighet finns aldrig i databasen, bara den opaka env:-pekaren), och svepet accounting_bokio_sync_tick som hittar sitt arbete självt (*/15 * * * *, claim med for update skip locked, EN token-förnyelse per våg, Idempotency-Key = verifikatets id, kaskaden 1 min · 5 min · 25 min med taket 3, rate_limited avslutar VÅGEN, :requeue är människans väg tillbaka och svarar 409 sync_entry_not_failed på en redan skickad rad). Registret ligger utanför verifikatet (spec A-2): accounting_entry.external_refs HÄRLEDS vid läsning och har INGEN skrivare (grep-vaktat) — liggaren är oföränderlig. Adaptern väljs ur miljön (PL_BILL_BOKIO_BACKEND: fake i local/test · tom eller none ⇒ 409 integration_backend_unavailable respektive skipped_no_backend · okänt namn ⇒ ärligt fel, aldrig en tyst fallback), och den riktiga Bokio-adaptern är go-live-arbete med bokförd ägare — den är en ren tilläggsfil som mappar 2xx/409/401/422/429/5xx/timeout mot vokabulären. Fortnox/Visma/Xero som DIREKTSYNK och import FRÅN ett externt system är uttalade icke-mål. Fixtur: seed-bokforings m-9-delta (ingen ny CLI-rad; mappningar, en medvetet ofullständig Xero-mappning, en riktig Fortnox-export och en ansluten Bokio-koppling med EN fallen registerrad — allt genom sömmarna, tokenet passerar aldrig seeden). Kontrakt: specs/api/endpoints/accounting-integrations.md; mönstret: docs/domain/accounting-flows.md §1g; operatörens runbook: docs/engineering/runbooks/bokio-synk.md. Ytan: adminfliken *Export (SIE)*:s mappnings- och Bokio-panel är byggd i t-2 — se F08.10.31.
✅ BILL m-9 - F08.10.31 Shipped
✅ Adminflikens tre m-9-element på /bokforing *Export (SIE)* (BILL m-9 t-2, 4e608d85.m-9) — mockupen ritar alla tre och de är nu byggda som ett DELTA på den befintliga fliken (G-10: ingen ny rutt — exportens val, dess förutsättning och dess automatiserade motpart hör ihop på samma skärm, så den som får 422 coa_mapping_incomplete kan rätta felet utan att navigera bort). (1) FORMATVÄLJAREN — fem alternativ i mockupens ordning (standard · sie4_bokio · fortnox_csv · visma_txt · xero_csv) där orden är kontraktets enum och etiketterna presenterns (ett nytt format utan svensk etikett gör enhetstestet rött i stället för att visa ett rått maskinord); *Förhandsgranska* VARNAR ur preview.unmapped_accounts[], och *Exportera ↓* — som aldrig gråas ut av ytans egen bedömning (ingen förhandsdöming; en klientgrind hade dolt serverns regel) — renderar serverns 422 coa_mapping_incomplete som feltillståndet *»Exporten avbröts — N konton saknar mappning: …«* byggt ur svarets missing_accounts[], med länken *Öppna kontoplansmappningen →* som filtrerar tabellen till exakt de kontona; m-2:s två ⊘-attrapper är BORTA (en attrapp bredvid en levande sektion är två sanningar). (2) KONTOPLANSMAPPNING — BAS-konto · Namn · Fortnox · Visma eEkonomi · Xero · [redigera] pivoterad per konto ur GET /v1/accounting/coa-mappings (tom cell = omappad som ikon + text, aldrig färg ensam; de konton servern stoppade exporten på finns med som rader även utan en enda mappning), radredigering med If-Match = "<id>:<version>" ur listradens egna fält, och *Fyll med identitet* per BAS-nativ provider som visar serverns MÄTTA {created, skipped} medan Xeros 422 identity_mapping_not_available renderas ärligt (knappen finns, domen visas). (3) BOKIO DIREKTSYNK (API) — statusbadgen ur serverns backend/connection_status där backend: none ⇒ *»Ingen adapter i miljön«* VINNER över anslutningsläget (det är inte samma sak som *Frånkopplad*, och skillnaden avgör om det är någon idé att trycka *Anslut*), synkläge/företags-id/senaste synk, *Anslut* mot serverns egen authorize_url med landningen ?bokio=ansluten kvitterad (ytan bygger aldrig en auktoriserings-URL själv), *Testa anslutning* med MÄTT svar inline, *↻ Synka nu* (202 ⇒ körningen i loggen, 409 sync_already_in_progress visat ärligt), *Koppla från* med bekräftelsesteg, körningsloggen och registrets failed-rader med *Försök igen* (:requeue erbjuds bara i felläge — serverns 409 sync_entry_not_failed speglas, aldrig ersätts, och försökstaket sägs rakt ut). Ytan hanterar ALDRIG ett token-fält (NFR-2; drivrutinens vakt bygger fältnamnen ur fragment så gate-källan själv inte bär dem), skriver aldrig ett statusord (etikettkartor över serverns koder), skickar aldrig ett tenant_id (boken resolvas server-side) och går uteslutande genom den genererade klienten (ingen rå fetch, ingen egen klientlagring). Tre riktiga tillstånd per sektion, mandatet fail-closed i affordansen — men aldrig fail-closed på serverns dom. Bokförd avvikelse: ingen 409 för en dubblettmappning finns att rendera — unikheten är en DB-invariant (partiellt unikt index) och skrivvägen en upsert, så regeln står i klartext i sektionshuvudet i stället för som ett fel som aldrig inträffar. ⊘ med ägare: sys-yta för synk-/jobbhälsa → m-12 (Icke-mål 4) · :requeue-mutationen i drivrutinen → t-1 (fixturens enda failed-rad får inte förbrukas och fejkens felinjektion är process-intern utan env-flagga; affordansen, bekräftelsesteget och serverns icke-muterande domar mäts i stället, och omköningen bevisas i TestBillM9 mot riktig Postgres) · backend: none-copyn renderad i webbläsaren → grindriggen (inbakad NEXT_PUBLIC_API_URL; kontraktets svar mäts mot en riktig adapterlös sidecar och copyns härledning är en ren, enhetstestad regel) · Fortnox/Visma/Xero som direktsynk → framtida BILL-milstolpe (m-9 ger dem filexport). Vy-spec: specs/admin/views/bokforing.md §20; bevis mot körande stack: tools/gate/bill-m9-e2e.mjs (deskriptor tools/gate/e2e.d/999-bill-m9-externa-mal.mjs, strukturgrind api/__acceptance__/bill-m9-yta-kontrakt.acceptance.mjs, presentertester admin/lib/__tests__/bokforing-m9-presenter.test.ts).
✅ BILL m-9 - F08.10.32 Shipped
✅ Adminfliken *Avstämning* på /bokforing + sys /intakter:s två HÄVDA ⊘ som läsvyer (BILL m-10 t-2, 4e608d85.m-10) — mockupens flik *Reconciliation* i design/src/pages/admin/betalningar.astro är byggd, men som en FLIK på /bokforing (A-8: kontoutdraget matchas mot verifikat, skriver i liggaren och låser en avstämning — det hör till bokföringen) medan /betalningar LÄNKAR dit i stället för att dubblera ytan: två ytor för samma beslut har ingen ansvarig. Importpanelen har EXAKT husets två format (CAMT.053 (ISO 20022) · CSV — husets form) — formatet anges, det gissas aldrig, för ett format som gissas fel importerar fel belopp TYST — bankkontona är bokens EGNA aktiva tillgångskonton, SHA-256-dubblettskyddet och CSV:ns kolumnkontrakt står i klartext, och serverns {lines, matched, unmatched, skipped_duplicate, deduplicated, file_retained} renderas ärligt (en om-import är ett 200 deduplicated: true, inte »0 rader« som ser ut som ett fel). De fyra saldokorten är serverns balance_check — utan saldon står varje kort på ett ärligt streck, aldrig en nolla som ser mätt ut — och låsknappen är ALLTID tryckbar: låsets tre domar (409 statement_has_unmatched_lines med antalet, 409 statement_unbalanced med differensen, 422 statement_balances_missing) är SERVERNS och renderas VID åtgärden, för en knapp ytan låste på sin egen tolkning hade dolt VILKEN dom som gällde. Matchningsinboxen bär mockupens färgprick men aldrig som ensam signal: konfidensen står i TEXT och har bara TVÅ mätta nivåer (10000 referens/OCR eller människans beslut · 8500 belopp + datum ±3 d) — en tredje, lägre nivå hade varit ett förslag som ser mätt ut — orsaken står i klartext ur serverns EGEN kod, förslagskolumnen visar bara det servern matchade, och treknappsflödet *Matcha · Bokför · Ignorera* är STEG med bekräftelse där ingen knapp förhandsdöms (422 amount_mismatch, 422 amount_exceeds_remaining, 422 account_inactive och skälets längd är serverns domar). *Ta bort matchningen* renderas endast för en verifikatmatchning — en matchning som SKREV i böckerna rättas med motverifikat, och servern svarar 409 bank_line_wrote_ledger. Payout-registret visar brutto/avgift/netto som serverns heltal, items[] med sin klass per transaktion, booked_with_residual som VARNING med residualen (den göms aldrig) och :requeue bara på en FALLEN rad; summary-tabellen ger provider · providernetto · bokfört · drift · dom med toleransen som SERVERNS deklarerade tal. Verifikatlistan, formuläret och exporten får serien B — bankhändelser. sys /intakter häver de två ⊘-korten unmatched_payments och provider_reconciliation i samma leverans som källan byts in (m-3/m-6:s prejudikat): tabellen *Omatchade bankbetalningar (PL)* med serverns orsak och hänvisningen att matchningen görs av PL:s bokförare på /bokforing, och kortet *Avstämning mot leverantören* med domen som ikon + text + färg — helt utan mutationsaffordans (A-9: visionen gör sys läs-/övervakning, aldrig redigering av någon tenants böcker), och utan en rad för en clearingtjänst huset saknar adapter för. ⊘ med ägare, aldrig döda knappar: regeltabellen (*Custom auto-matching rules*) och split-matchning → egen BILL-uppföljning · bankspecifika CSV/OFX/SIE4/BGMax → go-live · formatgissning och Jaro-Winkler-fuzzy → bokförda avvikelser · sys-matchning → ägarbeslut · den lyckade fakturaregleringen i L4 → t-1 (fixturens enda obetalda faktura är m-3:s och m-5/m-6 vilar på dess läge; kedjan bevisas i TestBillM10BankLineSettlesInvoice och i t-1:s kontraktssvit). Vy-specar: specs/admin/views/bokforing.md §21 + specs/sys/views/intakter.md §BILL 4e608d85.m-10; bevis mot körande stack: tools/gate/bill-m10-e2e.mjs (deskriptor tools/gate/e2e.d/999-bill-m10-avstamning.mjs, strukturgrind api/__acceptance__/bill-m10-yta-kontrakt.acceptance.mjs).
✅ BILL m-10 - F08.10.01 Shipped
Sverige - Fortnox-export — semikolonseparerad CSV med VER/Serie/Vernr/Datum/Text/Konto/Debet/Kredit. Svenskt decimalformat (komma). Exporterar endast bokförda verifikationer. Använder per-tenant kontoplansmappning.
Omtolkad — byggd i F08.10.30 (BILL m-9), men FAIL-CLOSED på komplett mappning ✅ PL-F0810a - F08.10.02 Shipped
International - Xero-export — kommaseparerad CSV med *Date/*Description/*AccountCode/*Debit/*Credit/TaxType/Reference. Internationellt decimalformat (punkt). Per-tenant kontoplansmappning.
Omtolkad — byggd i F08.10.30 (BILL m-9); TaxType-mappningen deklarerad, ingen identitetsväg ✅ PL-F0810a - F08.10.03 Shipped
Sverige - Visma eEkonomi-export — tabbseparerad textfil med VER/Serie/Vernr/Datum/Text/Konto/Debet/Kredit/Projekt/Resultatenhet. Svenskt decimalformat. Stödjer API-synkronisering via integrationsconfig.
Omtolkad — filexporten byggd i F08.10.30 (BILL m-9); Visma som DIREKTSYNK är icke-mål ✅ PL-F0810a - F08.10.04 Shipped
Per-tenant chart of accounts mapping — ChartOfAccountsMapping-samling tenant-scopad. Översätter interna BAS-konton till externa leverantörskonton. En aktiv mappning per provider per tenant. CRUD med duplikatskydd (409).
Omtolkad — byggd i F08.10.30 (BILL m-9) som coa_mapping med mjuk radering + unik aktiv nyckel ✅ PL-F0810a - F08.10.05 Shipped
SIE4-export med IB/UB/RES — utökad SIE4-export (GET /accounting-export/sie4) med #KTYP kontotyper, #IB ingående balanser, #UB utgående balanser, #RES resultaträkningskonton. CP437-encoding, fiscal_year/company_name/org_number-parametrar.
✅ PL-F0810b - F08.10.06 Shipped
Bokslut per säsong — generering av bokslut (financial statements) med resultaträkning- och balansräkningsögonblicksbilder. Status draft→finalized livscykel. Valfri periodlåsning vid finalisering. CRUD /financial-statements/.
✅ PL-F0810b - F08.10.07 Shipped
Revisionsspår och låsning av perioder — periodlåsning förhindrar skapande/bokföring/makulering av verifikationer inom låst datumintervall. CRUD + check + unlock /period-locks/. Överlappande lås avvisas. Komplett audit trail via FinancialAuditLog.
✅ PL-F0810b - F08.10.08 Shipped
Resultaträkning och balansräkning per OrgNode — konsoliderade rapporter per kostnadställe. GET /accounting-reports/consolidated-income-statement och /consolidated-balance-sheet med org_node_id-nedbrytning. Kvartalsfilter.
✅ PL-F0810b - F08.10.09 Shipped
Bokio SIE4-export — Bokio-specifik SIE4-variant med UTF-8 BOM, CRLF-radslut, BAS-2024-validering (1000–8999), obligatoriskt #ORGNR. GET /accounting/bokio/export/sie4.
Omtolkad — Bokio-dialekten bor i m-2:s /export/sie4?dialect=bokio; ingen egen rutt (BILL m-9) ✅ PL-T078 - F08.10.10 Shipped
Bokio direkt-API — OAuth2-onboarding + synkron/batch-POST av verifikationer till Bokio REST API med idempotency-nycklar. Token-refresh 5 min före expiry. Error-hantering: 401 retry, 409 ignore, 422 fail, 429 backoff.
Omtolkad — porten + fejken byggda i F08.10.30 (BILL m-9); riktig adapter = go-live ✅ PL-T078 - F08.10.11 Shipped
Bokio sync-jobb — batch-sync var 15 min (eller on-demand), plockar entries med synced_to_bokio_at is None, POST till Bokio per entry, markerar synced med external_refs.bokio. Max 3 retries med exponentiell backoff.
Omtolkad — run-job accounting_bokio_sync_tick i F08.10.30 (BILL m-9); status utanför verifikatet ✅ PL-T078 - F08.10.12 Shipped
Bokio onboarding-flow — POST /accounting/bokio/configure (company_id, sync_mode api/sie4_only), OAuth-URL för API-läge, test-connection via get_fiscal_years(). Admin-vy med provider-kort, Sync Now-knapp, SIE4-nedladdning.
Omtolkad — :configure/:connect/:test-connection i F08.10.30 (BILL m-9); adminpanelen → t-2 ✅ PL-T078
Per-nationell ekonomi och avgifter
F08.09- F08.09.01 Shipped
Per-tenant license fee with annual revision
✅ PL-F0809a - F08.09.02 Shipped
Per-tenant VAT/MOMS/TVA/IVA configuration
✅ PL-F0809a - F08.09.03 Shipped
Per-tenant invoice number series
✅ PL-F0809a - F08.09.04 Shipped
Per-tenant payment term rules (30/60/90 days)
✅ PL-F0809a - F08.09.05 Shipped
France SEPA Direct Debit for club fees
✅ PL-F0809a - F08.09.06 Shipped
Sverige - Swish som default-betalsätt — per-debtor-type betalningspolicy med min/max-belopp, Swish som default för spelare
✅ PL-F0809b - F08.09.07 Shipped
Deutschland - SEPA-Lastschrift — Basislastschrift (CORE) och Firmenlastschrift (B2B) via SEPA-mandathantering
✅ PL-F0809b - F08.09.08 Shipped
Espana - Bizum för spelare-betalningar — Bizum med beloppstak (max 1 000 EUR), fallback till kort vid överskridande
✅ PL-F0809b - F08.09.09 Shipped
Per-tenant valutahantering — basvaluta, stödda valutor, decimalplatser, avrundningsläge, visningsformat
✅ PL-F0809b - F08.09.10 Shipped
Multi-currency rapportering för FIPJP/CEP — konsoliderade flervaualtarapporter med växelkursupplösning, kategoribrytning, finalisering
✅ PL-F0809b
Pricing Catalog
F08.10- F08.10.01 Shipped
14-SKU pricing catalog data model — TenantBillingType (14 enums), BillingMode (self_service/contract), PricingCatalogVersion (versioned, immutable), PricingTier (per-version, per-billing-type, Decimal EUR price, i18n display/description/includes).
✅ PL-T011 - F08.10.02 Shipped
Seed data v2026.01 — Idempotent seed script skapar initial katalog med exakta priser från market-analysis.md A2. 7 self-service + 7 contract tiers. i18n för en/fr/es/sv.
✅ PL-T011 - F08.10.03 Shipped
Admin pricing catalog API — GET /admin/pricing-catalog/current (14 tiers), /versions (historik), /versions/{id}, /tiers/{billing_type}. Platform-admin auth.
✅ PL-T011 - F08.10.04 Shipped
Public pricing API — GET /public/pricing (7 self-service tiers, no auth, Cache-Control max-age=300).
✅ PL-T011 - F08.10.05 Shipped
Admin pricing catalog UI — Read-only list- och detaljvy i admin under Platform > Pricing Catalog. 14 tiers i tabell, detaljvy med i18n-fält, Stripe-ID:n, license-range.
✅ PL-T011 - F08.10.06 Shipped
National tier auto-classification — resolve_national_tier_from_license_count: 0–999→XS, 1000–2999→S, 3000–9999→M, ≥10000→L.
✅ PL-T011 - F08.10.07 Shipped
Publicera ny version (write-UI i admin)
✅ PL-F0810b - F08.10.08 Shipped
Prishistorik per kund (koppling via Subscription)
✅ PL-F0810b - F08.10.09 Shipped
Tenant-typ-bryggad pricing-katalog (PL-T226) — PricingTier utökas med tenant_type (broad till TenantType) + denormaliserade domain_count / subsystem_count / feature_count från TENANT_TIER_MATRIX. TENANT_TYPE_FOR_BILLING_TYPE ger reverse-lookup för Stripe-webhook + drift-job. Migration tools/migrations/pl-t226-tenant-type-pricing.py är idempotent.
✅ PL-T226 - F08.10.10 Shipped
Public detail-endpoint för tier-jämförelser (PL-T226) — GET /public/pricing-tiers (full 14-rad-katalog med ?include_legacy=true för 15) + GET /public/pricing-tiers/{tenant_type} (deep-link med included_subsystems / excluded_subsystems från matrisen). 5 min cache. Service tier_for_tenant_type() routar FEDERATION mot national_* via license-count.
✅ PL-T226 - F08.10.11 Shipped
Pricing tier sync-check job (PL-T226) — nattlig jämförelse av PricingTier-tabellen mot www/src/data/tenant-tiers.ts. SEV2 platform-event vid divergens.
✅ PL-T226
Subscription Management
F08.11## Implementation Status
How it works- F08.11.01 Shipped
Subscription datamodell med pricing_version — varje Subscription kopplas till en pricing_version (ISO-datum) som identifierar den prislista som gällde vid avtalstillfället. Oförändrat vid förnyelse. Indexerat för rapportqueries.
✅ PL-T042 - F08.11.02 Shipped
Price-Lock Guarantee — formell policy: priset som pricing_version anger gäller så länge Subscription:en är aktiv. Inga automatiska prishöjningar vid förnyelse. Publicerad på petanque.life/price-lock-guarantee (4 språk).
✅ PL-T042 - F08.11.03 Shipped
Subscription CRUD-livscykel (PL-T285) — PUT /subscriptions/{id} uppgraderar tier inom låst pricing_version (downgrades blockas 422). POST /subscriptions/{id}/cancel med immediate=False (default) eller True (sys_finance + fresh-auth). POST /subscriptions/{id}/renew är idempotent på (subscription_id, period_start, period_end). DELETE /subscriptions/{id} soft-deletar (sys_finance + fresh-auth) — blockeras vid obetalda fakturor. Audit via write_finance_audit.
✅ PL-T285 - F08.11.04 Shipped
Pricing-version-rapport (PL-T285) — GET /sys/billing/subscription-report/by-pricing-version?include_cancelled=<bool> aggregerar antal aktiva subscriptions och låst årsintäkt per pricing_version, plus tenant-typ-fördelning. Cache 5 min via Redis. Sys-konsol-vy sys/app/(dashboard)/billing/subscription-report.tsx med trendgraf och tabell.
✅ PL-T285 - F08.11.05 Shipped
Auto-renewal-jobb (PL-T285) — subscription_auto_renewal_job körs varje timme via the job scheduler, plockar auto_renew=True AND status=ACTIVE AND next_renewal_at <= now, skapar Invoice via existerande pipeline med oförändrat pricing_version. Retry 1h/2h/4h, sedan dunning (PL-T047). Drift-detektion subscription_renewal_drift (SEV2) när jobbet inte körts >25h. Audit subscription.auto_renewed / subscription.auto_renewal_failed.
✅ PL-T285 - F08.11.06 Shipped
ECB-kvartalsfixing för lokal valuta — formell policy: fakturering i lokal valuta sker enligt ECB:s kvartalsfixade referensväxelkurser. Fixing första bankdagen i januari, april, juli, oktober. Stödda valutor: EUR, SEK, NOK, DKK, GBP, USD, CHF. Varje faktura sparar ecb_fixing_rate och ecb_fixing_date för historisk reproducerbarhet. Publicerad på petanque.life/currency-policy (4 språk).
✅ PL-T043 - F08.11.07 Shipped
Tenant-typ-tier-byte via Stripe Subscription Schedule (PL-T226) — POST /sys/billing/{tenant_id}/change-tier (sys_finance + fresh-auth) byter TenantConfig.tenant_type, repointar TenantSubscription mot ny TenantBillingType, skapar Stripe Subscription Schedule prorated, invaliderar TenantTierResolver-cachen och skriver tenant.tier.billing_changed + tenant.tier.synced_from_stripe-audit. Companion-endpoint …/change-tier/quote är read-only förhandsbesked. Downgrades från federation-grade SKU blockas (422 tier_downgrade_blocked).
✅ PL-T226 - F08.11.08 Shipped
Tenant-facing upgrade-quote + ROI-räknare (PL-T226) — POST /admin/billing/upgrade-quote returnerar prorated belopp + pre-fylld contact-sales/?topic=tier-upgrade-URL. Admin-vyn views/billing/SubscriptionView.tsx läser GET /me/tenant-tier, listar inkluderade delsystem och låter kunden simulera tier-byte. Faktiska byten ligger hos sys-operator.
✅ PL-T226 - F08.11.09 Shipped
Stripe webhook tenant_type-sync + drift-job (PL-T226) — customer.subscription.updated läser metadata.tenant_type, applicerar om det driftat, skriver tenant.tier.synced_from_stripe-audit + bustar tier-resolver-cachen. Job stripe_subscription_drift_check (varje timme) fångar oöverensstämmelser → billing_health=warning + SEV2 platform-event.
✅ PL-T226 - F08.01.01 Shipped
/license-fee-configs/, /license-fee-overrides/, /license-fees/calculate
✅ PL-204 - F08.01.02 Shipped
/fee-schedules/ (fee_category=club_affiliation, med affiliation_tiers)
✅ PL-801 - F08.01.03 Shipped
/fee-schedules/ (fee_category=competition_entry)
✅ PL-801 - F08.01.04 Shipped
/fee-management/copy-season (säsongskopiering), /fee-management/by-season/{id} (konsoliderad vy)
✅ PL-F0801a - F08.01.05 Shipped
/fee-surcharge-rules/ (CRUD), /fee-surcharges/calculate (early bird/late fee)
✅ PL-F0801a - F08.01.07 Shipped
/revenue-distribution-rules/ + automatisk distribution vid betalning
✅ PL-801 - F08.01.09 Shipped
/fee-schedules/ (fee_category=sanction)
✅ PL-801 - F08.02.01 Shipped
/payments/ (registrera, spåra, återbetala)
✅ PL-801 - F08.03.01 Shipped
/invoices/ (skapa, rader, summor)
✅ PL-801 - F08.03.02 Shipped
Automatisk faktura vid licensgodkännande
✅ PL-801 - F08.03.06 Shipped
Fakturastatus: draft → sent → paid/overdue/cancelled
✅ PL-801 - F08.03.07 Shipped
/invoices/{id}/send-reminder
✅ PL-801 - F08.03.08 Shipped
/debts/check/{debtor_id} — blockerar transfer och licensförnyelse
✅ PL-801 - F08.08.01 Shipped
/financial-reports/revenue, /financial-reports/revenue/by-org-node
✅ PL-801 - F08.02.02 Shipped
/checkout/ (Stripe Checkout Session), /webhooks/stripe
✅ PL-802 - F08.02.02 Shipped
/checkout/ (Swish QR-betalning), /webhooks/swish
✅ PL-802 - F08.02.04 Shipped
/receipts/, /receipts/{id}/pdf (kvittogenerering)
✅ PL-802 - F08.02.06 Shipped
Automatisk avstämning via Stripe/Swish webhooks
✅ PL-802 - F08.02.07 Shipped
/payments/{id}/gateway-refund (återbetalning via gateway)
✅ PL-802 - F08.02.10 Shipped
Betalningsspårning: /payment-history/{debtor_id}
✅ PL-802 - F08.03.01 Shipped
/invoices/ utökad med tax_rate per rad, tax_amount, tax_total
✅ PL-F0803a - F08.03.02 Shipped
/invoices/auto-generate/license-renewal, /invoices/auto-generate/affiliation
✅ PL-F0803a - F08.03.03 Shipped
/invoice-templates/ (CRUD, per-federation, default-hantering)
✅ PL-F0803a - F08.03.04 Shipped
/invoices/{id}/pdf (PDF-nedladdning), /invoices/{id}/deliver (e-postleverans)
✅ PL-F0803a - F08.03.05 Shipped
/credit-notes/ (CRUD, issue, apply, void — kreditnotor mot fakturor)
✅ PL-F0803b - F08.03.06 Shipped
/invoices/{id}/status-history, /invoices/mark-overdue (statusspårning med historik)
✅ PL-F0803b - F08.03.07 Shipped
/reminder-configs/, /invoices/reminder-queue, /invoices/send-due-reminders (påminnelseautomatik)
✅ PL-F0803b - F08.03.08 Shipped
/debt-collection/ (CRUD, escalate, resolve — inkassoworkflow)
✅ PL-F0803b - F08.05.01 Shipped
/sponsors/ (CRUD med kontaktinfo, nivå, sponsrade entiteter)
✅ PL-F0805 - F08.05.02 Shipped
/sponsors/{id}/contracts (kontraktshantering med belopp, period, villkor)
✅ PL-F0805 - F08.05.03 Shipped
/sponsors/{id}/exposure (exponeringsregistrering och sammanfattning)
✅ PL-F0805 - F08.05.04 Shipped
/grants/ (ansökan: draft → submitted → approved/rejected → reporting)
✅ PL-F0805 - F08.05.05 Shipped
/grants/{id}/reports (bidragsrapportering efter godkännande)
✅ PL-F0805 - F08.05.06 Shipped
/sponsor-allocations/ (intäktsallokering per OrgNode med procentsatser)
✅ PL-F0805 - F08.04.01 Shipped
/budgets/ (CRUD med inkomst-/utgiftskategorier per OrgNode), /budgets/copy (kopiera med inflationsjustering)
✅ PL-F0804 - F08.04.02 Shipped
BudgetCategory med type: income/expense, kopplad till kontoplan
✅ PL-F0804 - F08.04.03 Shipped
/budgets/{id}/actualize + /budgets/{id}/variance (budget vs. faktisk)
✅ PL-F0804 - F08.04.04 Shipped
draft → proposed → approved → closed (godkännandeflöde med audit log)
✅ PL-F0804 - F08.04.05 Shipped
/accounting-reports/income-statement (årsvis + kvartalsvis med quarter-param), /accounting-reports/budget-tracking, /accounting-reports/balance-sheet
✅ PL-F0804 - F08.04.06 Shipped
/accounting-entries/ (verifikationer med kontoplan), /accounting-export/sie + /fec + /json
✅ PL-F0804 - F08.01.04 Shipped
/fee-management/copy-season (kopiera avgiftsdata), /fee-management/by-season/{id}
✅ PL-F0801a - F08.01.05 Shipped
/fee-surcharge-rules/ (CRUD), /fee-surcharges/calculate (beräkning)
✅ PL-F0801a - F08.01.06 Shipped
/fee-waiver-rules/ (CRUD), /fee-waivers/calculate (beräkning med ålder/familj/scoping)
✅ PL-F0801b - F08.01.07 Shipped
/fee-splitting/calculate (fördelningsberäkning), /fee-splitting/execute (skapar RevenueEntry)
✅ PL-F0801b - F08.01.08 Shipped
/fee-audit/ (lista), /fee-audit/{id} (detalj), /fee-audit/entity/{type}/{id} (per entitet)
✅ PL-F0801b - F08.01.09 Shipped
/sanction-fee-configs/ (CRUD), /sanction-fee-configs/calculate/{season_id} (beräkning)
✅ PL-F0801b - F08.01.10 Shipped
/suspension-fee-configs/ (CRUD), /suspension-fee-configs/calculate/{season_id} (beräkning)
✅ PL-F0801b - F08.01.06 Shipped
/fee-waiver-rules/ (CRUD), /fee-waivers/calculate
✅ PL-F0801b - F08.01.07 Shipped
/fee-splitting/calculate, /fee-splitting/execute
✅ PL-F0801b - F08.01.08 Shipped
/fee-audit/, /fee-audit/{id}, /fee-audit/entity/{type}/{id}
✅ PL-F0801b - F08.01.09 Shipped
/sanction-fee-configs/ (CRUD), /sanction-fee-configs/calculate/{season_id}
✅ PL-F0801b - F08.01.10 Shipped
/suspension-fee-configs/ (CRUD), /suspension-fee-configs/calculate/{season_id}
✅ PL-F0801b - F08.07.01 Shipped
/expense-claims/ (CRUD med specificerade utgiftsposter, kvitto-URL, käll-referens)
✅ PL-F0807 - F08.07.02 Shipped
/expenses/travel/calculate (km-ersättning + traktamente), embedded TravelExpense i claims
✅ PL-F0807 - F08.07.03 Shipped
/expense-claims/{id}/submit, /expense-claims/{id}/approve, /expense-claims/{id}/reject
✅ PL-F0807 - F08.07.04 Shipped
/expense-claims/{id}/pay (utbetalning med valfri betalningsmetod och referens)
✅ PL-F0807 - F08.07.05 Shipped
/expense-reports/by-category, /expense-reports/by-org-node (rapporter med filter)
✅ PL-F0807 - F08.06.01 Planned
GET/POST /v1/prize-pools, GET/PATCH/DELETE /v1/prize-pools/{id}, POST …:activate
:close, PUT …/funding - F08.06.02 Planned
GET/PUT /v1/prize-pools/{id}/distribution-rule, GET/POST /v1/prize-distribution-presets + GET/PUT/DELETE /v1/prize-distribution-presets/{id}, POST …:generate-payouts
:recompute-distribution - F08.06.03 Planned
m-1:s GET /v1/prize-payouts[/{id}] + PATCH (skatteland/sats) — plus m-2:s verb: POST /v1/prize-payouts/{id}:submit|:withdraw|:approve|:reject|:pay och kollektionsverben POST /v1/prize-payouts:submit|:approve (≤ 200 rader, Idempotency-Key, ett fel i EN rad fäller aldrig de andra).
- F08.06.04 Planned
GET /v1/prize-payouts/tax-report + GET /v1/prize-payouts/tax-report/export.csv; beräkningen är m-1:s GET/PUT /v1/withholding-tax-rates. GET /v1/prize-pools/summary svarar nu MÄTT withheld_this_year per valuta (posten är BORTA ur unavailable[]), och in_review/approved_unpaid bär batchknapparnas N.
- F08.06.05 Planned
GET/POST /v1/trophy-items, GET /v1/trophy-items/catalog, GET/PATCH/DELETE /v1/trophy-items/{id}, POST /v1/trophy-items/{id}:assign|:book-cost, GET /v1/trophy-assignments[/{id}], POST /v1/trophy-assignments/{id}:award|:return och kollektionsverbet POST /v1/trophy-assignments:award (≤ 200 rader, Idempotency-Key, per-rad-utfall). Svepet run-job trophy_return_reminder. Kontraktet: specs/api/endpoints/trophy-inventory.md.
- F08.09.01 Shipped
/license-fee-revisions/ (CRUD, approve, apply — årlig revision av licensavgifter)
✅ PL-F0809a - F08.09.02 Shipped
/tenant-vat-configs/ (CRUD), /tenant-vat-configs/active, /tenant-vat-configs/resolve-rate
✅ PL-F0809a - F08.09.03 Shipped
/invoice-number-series/ (CRUD), /invoice-number-series/preview
✅ PL-F0809a - F08.09.04 Shipped
/payment-term-rules/ (CRUD), /payment-term-rules/resolve
✅ PL-F0809a - F08.09.05 Shipped
/sepa-mandates/ (CRUD, activate, cancel), /sepa-direct-debits/ (CRUD, submit, settle, reject)
✅ PL-F0809a - F08.08.01 Shipped
/financial-reporting/revenue-summary (intäkter per källa: licenser, tävlingar, sponsring, bidrag)
✅ PL-F0808 - F08.08.02 Shipped
/financial-reporting/expense-summary (kostnader per kategori: utlägg + bokföringsposter)
✅ PL-F0808 - F08.08.03 Shipped
/financial-reporting/federation-summary (hierarkisk sammanställning per OrgNode-nivå + budget)
✅ PL-F0808 - F08.08.04 Shipped
/financial-reporting/club-health (hälsoindikatorer: betalningsgrad, kostnadskvot, förfallna fakturor)
✅ PL-F0808 - F08.08.05 Shipped
/financial-reporting/trend-analysis (flerårsanalys med tillväxttakt, max 10 år)
✅ PL-F0808 - F08.08.06 Shipped
/financial-reporting/export/csv (UTF-8 BOM), /financial-reporting/export/saft (SAF-T XML, OECD)
✅ PL-F0808 - F08.10.01 Shipped
GET /accounting-export/fortnox (semikolonseparerad CSV, svenskt decimalformat, per-tenant kontoplansmappning)
✅ PL-F0810a - F08.10.02 Shipped
GET /accounting-export/xero (kommaseparerad CSV, internationellt decimalformat, per-tenant kontoplansmappning)
✅ PL-F0810a - F08.10.03 Shipped
GET /accounting-export/visma (tabbseparerad textfil, svenskt decimalformat, API-synk via integrationsconfig)
✅ PL-F0810a - F08.10.04 Shipped
/chart-of-accounts-mappings/ (CRUD, per provider per tenant, duplikatskydd 409)
✅ PL-F0810a - F08.10.05 Shipped
GET /accounting-export/sie4 (utökad SIE4 med #KTYP, #IB, #UB, #RES, CP437)
✅ PL-F0810b - F08.10.06 Shipped
/financial-statements/ (CRUD, generering bokslut med resultat-/balansögonblicksbild, finalisering)
✅ PL-F0810b - F08.10.07 Shipped
/period-locks/ (CRUD, check, unlock, blockerar entry create/post/void inom låst period)
✅ PL-F0810b - F08.10.08 Shipped
/accounting-reports/consolidated-income-statement, /consolidated-balance-sheet (per OrgNode, kvartal)
✅ PL-F0810b
Bank Reconciliation
F08.12_PL-T076 — import av kontoutdrag, regelbaserad auto-matchning, manuell inbox, AccountingEntry-generering, lock-flöde._
How it works- F08.12.07 Planned
Avstämningen: payout-reconciliation, avgiftens reverse charge och bankavstämning (BILL m-10 t-1, 4e608d85.m-10) — migration 00232 med payout_reconciliation (mottagningens durabla register, unique (provider, provider_payout_id), items[] med klass och skäl per transaktion), bank_statement (unique (tenant_id, sha256)) och bank_statement_line (unique (tenant_id, external_id) + partiellt unikt (tenant_id, matched_entry_id) = A-6:s en-rad-per-verifikat), källordet bank_statement additivt i liggarens vokabulär, platform_invoice.ocr_reference (Luhn mod-10, backfilld) och BAS 2999 i varje bok. Payout-porten PayoutProvider är en valfri, additiv utvidgning (F1-11:s Event-form oförändrad) med sin deterministiska fejk; mottagningen går genom InstallPayoutReconciler på den CENTRALA processorn och persisterar bara (A-3 — ett periodlås får aldrig blockera railen). Svepet accounting_payout_reconcile_tick bokför kvittningen i serie B: tvåklass-regeln läser den bokförda radens debetkonto (1580 ⇒ kvitta, 1930 ⇒ redan på banken, dras AV från bankdebeten), avgiften kommer ur porten aldrig ur en prislista (A-4), och B4:s reverse charge har två verkligt olika grenar (med avdragsrätt: nollparet; utan: momsen är en KOSTNAD på 6570 och bara det utgående benet krediteras) med kontona ur bookkeeping.VATAccountFor — ett land utan mappning ger failed:vat_account_unmapped, aldrig ett svenskt konto i en fransk bok. Residualen göms aldrig: den bokförs synligt på 2999, raden blir booked_with_residual och notisen till accounting:admin-bärarna bär MÄTT utfall på raden. B5 mekaniskt: payouten bokförs i kontots bok — en klubbs payout ger noll rader i PL:s liggare. Bankavstämningen: CAMT.053-/CSV-import med dubbel dedup, husets ENDA matchningsheuristik (samma rena funktion i importen, svepet accounting_bank_match_tick och :auto-match), inboxens knappar med If-Match/Idempotency-Key, låsningen bakom accounting:admin och SettlePlatformInvoice som partially_paid-ordets enda skrivare (beloppet ackumuleras, statusen HÄRLEDS; en bankreglering har ingen payments-rad — bankraden ÄR händelsen). Sys-läsvyerna GET /v1/sys/billing/{bank-lines,payout-reconciliation} häver ⊘-korten unmatched_payments och provider_reconciliation i samma leverans och är LÄS-ONLY (A-9). Fixtur: seed-bokforings m-10-delta (ingen ny CLI-rad; båda payouts drivna genom den riktiga railen och svepets egen söm, båda utdragen genom import-sömmen). Kontrakt: specs/api/endpoints/accounting-reconciliation.md; mönstret: docs/domain/accounting-flows.md §1h. ⊘ med ägare: regelmotor + split-matchning → egen BILL-uppföljning · bankdialekter/OFX/BGMax/SFTP/PSD2 och den riktiga Stripe-payout-adaptern → go-live · PayPal-payouts → PAY-3 · momsperioder/EC-listan → m-11 · dashboards/runway → m-12 · sys-mutationer → ägarbeslut.
- F08.12.01 Shipped
Import av kontoutdrag — stöd för CAMT.053 (ISO 20022), CSV (generisk + bankspecifik: Nordea, SEB, Handelsbanken, Swedbank, BNP Paribas, Crédit Agricole), OFX, SIE4. Formatdetektering automatisk. SHA-256-deduplicering.
✅ PL-T076 - F08.12.02 Shipped
Regelbaserad auto-matchning — klubbkassörens egna regler (motpartsnamn-regex, beloppsintervall, OCR-prefix). Prioritetsordning. Stöder book_to_account, match_to_invoice_by_ocr och ignore.
✅ PL-T076 - F08.12.03 Shipped
Heuristisk matchning — referens/OCR-matchning mot Payment (1.0 conf), fuzzy namn+belopp+datum ±3d (Jaro-Winkler ≥ 0.85). Under 0.6 → unmatched.
✅ PL-T076 - F08.12.04 Shipped
Manuell inbox — obematchade rader med färgkodning (grön/gul/röd). Tre-knapps-flöde: matcha mot faktura, bokför som ny post, ignorera. Split-matchning stöds.
✅ PL-T076 - F08.12.05 Shipped
AccountingEntry-generering — automatisk dubbelbokning vid matchning: debet bank/kredit kundfordran (incoming), debet leverantörsskuld/kredit bank (outgoing), debet bank/kredit intäktskonto (new).
✅ PL-T076 - F08.12.06 Shipped
Saldo-avstämning och lock — closing_balance == opening_balance + sum(lines) OCH alla lines matchade ⇒ status=locked. Balance-check-endpoint returnerar expected/actual/diff.
✅ PL-T076
Momsdeklaration, EC-sammanställning och OSS
F08.18- F08.18.01 Planned
Momsdeklarationen (BILL m-11 t-1, 4e608d85.m-11) — migration 00233 med vat_period (EXCLUDE gist per tenant, snapshot med de frysta rutorna, period_lock_id, settlement_entry_id), vat_period_oss (per (kvartal, land, sats, revision) med partiellt unikt index på den ÖPPNA revisionen), EC-radens revisionsnyckel, accounting_line.vat_base_minor (ADDITIV), tax_profile:s frekvens + begärans-flaggor och vat_period_reminder. Katalogerna bär sitt lagrum vid raden: frekvensen (SFL 26 kap. 10—11, 17 §§ · 1 mkr/40 mkr), förfallokalendern (26 kap. 26—27 §§, 33 a—b §§, 35 kap. 5 §, art. 364) med planerarens A-4-rättelse (helår utan unionshandel = 12:e i SJUNDE månaden — juridiska personers regel), rutkatalogen SKV 4700 (nio mätta rutor, sexton omätta med skäl och ägare, inklusive ruta 38) och redovisningskontot (SE 2650) i A6-katalogen. Basen MÄTS på raden i varje momsradsskrivare (aldrig baklänges ur momsbeloppet); auto-feedens base_minor är additiv med subtraktionsregeln för exakt en momsrad. Svepet accounting_vat_period_tick har fyra plikter (frekvensdomen ur MÄTT omsättning med audit bara vid FAKTISK ändring · materialiseringen där klockan avgör VILKA perioder som finns men aldrig var en period SLUTAR · OSS-projektionen som aldrig rör en deklarerad revision · förfallopåminnelsen 14/3 med single-fire som DB-invariant). :declare prövar sina fem förvillkor i ordning och gör i EN transaktion: rutorna frysta i snapshot, omföringsverifikatet (source_type='vat_settlement', serie A, entry_date = period_end, teckenregeln mot 2650), m-1:s periodlås (vat_declared:<id>) och EN audit-rad. En nollrapport deklareras och låses utan verifikat; betalningen läses ur liggaren, aldrig ur en lagrad status. Sys-läsvyerna GET /v1/sys/billing/{vat-periods,oss-periods} är LÄS-ONLY — det finns ingen :declare i sys-scopet och kommer inte att finnas. Fixtur: seed-bokforings m-11-delta (ingen ny CLI-rad). Kontrakt: specs/api/endpoints/accounting-vat.md; mönstret: docs/domain/accounting-flows.md §1i och docs/domain/invoicing.md §0d. ⊘ med ägare: PDF-rendering → Python-sidecar · FR:s CA3 och DE:s Umsatzsteuer-Voranmeldung → egen milstolpe per land · »rättad deklaration« som statusmaskin → Skatteverkets tjänst (rättelser går i nästa period) · e-inlämning/Peppol → eget initiativ · dashboards/runway → m-12.
- F08.18.02 Planned
Adminfliken *Momsrapport* på /bokforing + Momsprofilens två reglage + sys-panelen *Deklarationsläge (PL)* (BILL m-11 t-2, 4e608d85.m-11) — mockupens sista ⊘-flik (design/src/pages/admin/bokforing.astro, *VAT report*) är byggd, och ⊘-markeringen (agare) samt vy-specens rader 60/77 togs bort i SAMMA leverans som innehållet (G-13). Huvudet visar frekvensen som SERVERNS härledning i läsläge (med länk till Momsprofilen — frekvensen är serverägd, ett klientskrivet värde avvisas med 422), periodväljaren ur GET /v1/accounting/vat-periods med de deklarerade markerade, förfallodatumet och statusen som ikon + text + färg med overdue ur svaret. Rut-tabellen renderar varje ruta i mockupens form med ruta 49 framhävd, och en OMÄTT ruta som »—« MED sitt skäl — aldrig 0: null och 0 är två olika påståenden, och ett hus som inte mäter uttag ska säga det i stället för att påstå att de var noll. Deklarationsknappen gråas ALDRIG ut av ytans egen bedömning — 409 vat_period_not_ended/draft_entries_exist/vat_base_unmeasured/period_locked/already_declared är serverns domar och renderas VID åtgärden med koden i data-felkod och husets åtgärdstext — och det låsta tillståndet visar deklarationsdagen, nettot som serverns heltal, omföringsverifikatets serie/nummer som LÄNK till verifikatdetaljen och betalningsraden som en LÄSNING av liggaren (annars *»Ingen betalning bokförd ännu«*). Mockupens handläggarnamn byggs inte: declared_by är ett aktör-id, och ytan säger *»av en förvaltare med deklarationsmandat«* i stället för att visa ett uuid eller hitta på ett namn. Underlaget hämtas som JSON + CSV (PDF är ⊘ med ägare — en knapp utan renderare är en attrapp), tomläget bär mockupens besked om nollrapporten, och de ärliga lägena finns för en bok som inte är momsregistrerad och för ett land utan frekvenskatalog. Sektionerna *Periodisk sammanställning* (rader per köparens momsnummer, rättelserader markerade, :declare med serverns 409 renderat) och *Unionsordningen (OSS)* (kvartal → destinationsland, sats, underlag, moms, revisioner, korsprovet, :declare bara när boken är OSS-registrerad) ligger i samma flik. Momsprofilen (fliken *Export (SIE)*) får de två reglagen vat_filing_monthly_requested och ec_sales_monthly_requested plus den härledda frekvensen i läsläge med sitt MÄTTA basis. sys /intakter får den mockuplösa läsvyn Deklarationsläge (PL): tre rader (momsdeklaration · OSS · periodisk sammanställning) med nästa period, förfallodatum, status och overdue som serverns härledning (m-11 t-2 lade fältet på sys-OSS- och EC-raderna, som saknade det), hänvisningen till /bokforing och INGEN mutationsaffordans (A-10: sys är läs-/övervakning, aldrig redigering av någon tenants böcker). Läsaren (accounting:read) ser hela fliken utan en enda mutationsknapp och nekas :declare med 403; tenant-isoleringen ger 404 på en annan boks period. ⊘ med ägare: PDF av underlaget → renderar-sidecaren · aktörens NAMN → egen uppföljning (identitetsupplösning av aktörer) · e-inlämning till Skatteverket/OSS-portalen → eget initiativ · en »rättad deklaration«-status → finns inte och ska inte finnas (rättelser går i NÄSTA period) · den LYCKADE deklarationen och reglage-skrivningen i L4 → t-1 (:declare är ENGÅNGS och låser perioden; ett reglagebyte re-materialiserar EC-raderna med nya id:n — grinden ska vara omkörbar mot samma databas). Vy-specar: specs/admin/views/bokforing.md §22 + specs/sys/views/intakter.md §BILL 4e608d85.m-11; bevis mot körande stack: tools/gate/bill-m11-e2e.mjs (deskriptor tools/gate/e2e.d/999-bill-m11-moms.mjs, strukturgrind api/__acceptance__/bill-m11-yta-kontrakt.acceptance.mjs, presentertester admin/lib/__tests__/bokforing-m11-presenter.test.ts + sys/lib/__tests__/intakter-presenter.test.ts).
Ekonomidashboards: runway, ARR/churn och ekonomihälsa
F08.19- F08.19.01 Planned
Ekonomidashboardernas kontrakt (BILL m-12 t-1, 4e608d85.m-12) — INGEN migration: varje storhet är en LÄSNING vid frågetillfället av rader som redan finns, och källvakten (api/internal/http/bill_m12_source_guards_test.go) avvisar varje insert/update/delete i m-12:s källa. Tre tenant-rutter (GET /v1/accounting/dashboards/{result,liquidity,vat-status}, accounting:read i BOKENS tenant, skopet injicerat server-side — ?tenant_id= flyttar aldrig något) och fyra sys-rutter (GET /v1/sys/billing/{arr,churn,fee-report,health}, sys.billing.read som ENDAST sys_finance bär, alla LÄS-ONLY). Likviditeten är ett deklarerat kontointervall (BAS 1900—1999 med sitt skäl vid raden) där 1580 och 1510 står UTANFÖR som »på väg in« — pengar hos betalväxeln eller kunden är inte pengar på kontot, och att räkna in dem hade gett en runway som räckte längre än pengarna. Kassaflödet läses ur kassan, aldrig härlett ur resultatet (periodiseringar är inte kassa), med rullande utgående saldo. Runway är en division av två mätta tal ur SAMMA AccountBalances-läsning som resultaträkningen — m-7:s hostingkostnad ingår därmed per konstruktion (initiativets AC 16) — med basis som gör omräkningen exakt och ett NAMN i stället för ett tal när måttet inte går att mäta (not_burning · no_liquidity · no_entries). Ingen prognos, ingen regression, inget golv (legacy-CFO-vyns framskrivning byggs inte). ARR är abonnemangsREGISTRET, inte betalvolymen: avtal i active/past_due (aldrig pending_payment), intro-året som A8:s rabatt till 0 med listpriset bevarat, mrr = round_half_up(arr/12) i heltal med den bokförda avvikelsen ≤ 6 öre. Aldrig en summa (invarianten *Intäktsseparation (PL)*): talen redovisas per (revenue_category, currency), regeln vaktas på NAMNET (DTO-nyckelvakten) och på TALET (kontraktssvitens djupsökning), och en tom kategori står med 0 som MÄTT. Churn är kohorter härledda ur registret med de två tidsreglerna DEKLARERADE i svarets basis (A-2), churn_rate_bps som null + no_opening_cohort utan kohort (ingen division med noll, ingen NaN) och orsaker ur registrets EGET lifecycle_reason. Avgiftsrapporten LARMAR (policy: zero_by_owner_decision, varje rad ≠ 0 i anomalies[]) — den bokför aldrig en provision som intäkt. Ekonomihälsans nio sektioner med overall = värsta i den deklarerade ordningen ok < unavailable < warning < critical, där unavailable INTE är ett fel (never_run · no_backend · no_scopes) och jobbfamiljen läses ur api/job_registry.go med en mekanisk brygga i BÅDA riktningar (api/bill_m12_job_family_test.go — ett registrerat BILL-jobb som saknas i familjen är rött, för tystnad ser ut som hälsa). ⊘-hävningen i SAMMA leverans som källan (m-3/m-6/m-10-prejudikatet): mrr, arr, churn och fee_report ur sysBillingUnavailable() (kvar: total_eur, pdf_export, pricing_catalog — alla med annan ägare), och D-SYS m-7:s /-rutor mrr/top_mrr blir measured för sys_finance och restricted (additiv status, ägare + mandat, INGET tal) för alla andra operatörer — talet räknas genom porten billing.ARRReader, samma rena funktioner som /v1/sys/billing/arr, så landningssidan och intäktsytan aldrig kan visa olika MRR. Kontrakt: specs/api/endpoints/accounting-dashboards.md + specs/api/endpoints/sys-billing-health.md + sys-billing.md §BILL m-12 + sys-dashboard.md; mönstret: docs/domain/accounting-flows.md §1j; kategorierna: docs/business/finance/revenue-categories.md. ⊘ med ägare: budget/utfall/varians och användarsatta mål → m-13 (goal.source byter från guardrail till budget UTAN formändring) · cross-valuta-totaler/FX → FX-foundationen (FXRate) · handinmatade kassasnapshots → byggs aldrig (likviditeten LÄSES ur liggaren; vägen in är m-10:s bankavstämning) · prognoser/runway_alert/LTV-CAC/board-pack-PDF → eget initiativ / D-DASH / sidecar · churn-orsak som fritext/enkät → D-COMM/produkt · notiser ur dashboards → D-COMM/D-PUSH · per-OrgNod-konsolidering → m-1:s Icke-mål 9 · sys-mutationer → BILL.md §UI (läs-/övervakning) · ytorna (admin-fliken *Översikt*, sys-panelerna, /halsa/ekonomi) → t-2.
Budget: ram, varians, konsolidering och CSV/SAF-T
F08.20Bindande mockup: `design/src/pages/admin/budget.astro`. Ägarskaps-auditen 2026-07-09 fann att
- F08.20.01 Planned
Budgetens kontrakt (BILL m-13 t-1, 4e608d85.m-13) — migration 00234 med budget (två partiella unika index för (år, nod) respektive (år, tenantnivån), composite FK mot org_nodes (tenant_id, id), decision_note, mjuk approval_request_id, last_rejection, copied_from_budget_id, uplift_bps) och budget_line (EXCLUDE USING gist över int4range(account_from, account_to, '[]') per budget ⇒ 409 budget_line_overlap ur DATABASEN, aldrig ur ett if), plus en ADDITIV utvidgning av accounting_export_log_format_check med csv/saft_xml och villkorad Down (m-9:s prejudikat). INGEN kolumn actual*/variance* — budgeten är en RAM BREDVID boken, aldrig i den: utfallet är en LÄSNING av bokförda verifikat vid frågetillfället, och m-13:s källa bär noll DML mot accounting_entry/accounting_line/ledger_account (mekaniskt vaktat). Livscykeln är F1-6:s, inte en egen FSM: draft → proposed → approved → closed är en approval_request med request_type='budget' (en sekventiell steg-rad, accounting:admin i tenant-scope), statusordet skrivs av hooken inuti motorns beslutstransaktion (lägesorden får inte ens förekomma som strängliteraler i handlare/store/seed), och handlern opt-in:ar SelfDecisionGuard — förslagsställaren beslutar aldrig sin egen budget (403 approval_self_decision_denied). Ett avslag för budgeten tillbaka till utkast med motiveringen på raden och nollar ärendereferensen; den raderas aldrig av ett nej. Variansen är en läsning med period=year
q1..q4 ur räkenskapsårets EGNA datum (aldrig kalenderkvartal), consolidated över path-subträdet, gynnsamt-positiv teckenregel (intäkt actual − budget, kostnad budget − actual), flaggan vid BudgetVarianceAlertBps = 500 och variance_bps = null (aldrig 0) när ramen är 0 — plus obudgeterat utfall redovisat för sig. Kopiering med påslag genom ApplyUplift (round-half-up i heltal: 1 000 000 @ 480 bps ⇒ 1 048 000; 105 ⇒ 110; 1 ⇒ 1). De fem rapporterna (revenue-by-source, costs-by-category, consolidation, club-health, trend) är läsningar genom SAMMA AccountBalances-familj som resultaträkningen, med andelar fördelade med största resten (summerar till exakt 10 000 bps) och ⊘ MED ÄGARE där huset saknar register (klubbens betalningsgrad/förfallna belopp). Konsolideringens nodrader listar alla aktiva noder — GET /v1/org-nodes är gatad på tenant-config.manage och därför inte läsbar för en bokförare, så tabellen ÄR nodkatalogen. CSV/SAF-T renderas ur samma ExportBook som SIE-4 (CSV: UTF-8 med BOM, CRLF, nio kolumner, heltal med komma-decimal, tom cell för den tomma sidan; SAF-T 2.0: urn:OECD:StandardAuditFile-Taxation/2.00, fail-closed 422 saft_org_number_missing, tomt urval ⇒ giltig fil med NumberOfEntries = 0) och skriver EN historikrad med MÄTT entry_count/byte_size/content_hash + EN audit-rad. goal.source byter till budget på m-12:s dashboards/result när en approved/closed budget finns — utan formändring — med budget_id och pro rata-fördelad nettoram räknad på räkenskapsårets EGNA dagar. Fixtur: seed-bokforings m-13-delta (ingen ny CLI-rad) — policyn på båda böckerna, beslutsidentiteterna budget-beslut-seed@demo.petanque.life + budget-beslut-pl-seed@… och fyra budgetar i mockupens fyra lägen, konvergent och driven genom API:ts EGNA sömmar. Kontrakt: specs/api/endpoints/accounting-budget.md + specs/api/endpoints/accounting-reports-budget.md; mönstret: docs/domain/accounting-flows.md §1k. ⊘ med ägare: prognos/*forecast* → ingen prognosmotor i huset (pågående år märks to_date) · klubbens betalningsgrad + förfallna belopp → D-MEMBER/BILL (tenantutställda fakturor) · cross-valuta-konsolidering → FX-foundationen (FXRate) · SAF-T-validering mot vendorad XSD → bokförd begränsning (välformning + elementordning testas i stället) · budgetytan i admin → t-2. - F08.20.02 Planned
Budgetytan (BILL m-13 t-2, 4e608d85.m-13.t-2) — den NYA admin-rutten /budget (mockupens EGEN skärm design/src/pages/admin/budget.astro, inte en flik på /bokforing) med nav-posten *Budget & rapporter* i gruppen Ekonomi & handel DIREKT efter *Bokföring* (accounting:read, ⌘K-träff) och de fyra flikarna *Budget · Budget mot utfall · Rapporter · Export* bakom ?flik= (+ ?budget=<id>). Ingen siffra räknas i klienten: *Ram* är serverns totals.net_minor, utfallet/avvikelsen serverns variance-rader, andelarna serverns bps — presentern admin/lib/budget-presenter.ts FORMATERAR bara (formatMinor ur heltal) och konverterar *Påslag (%)* till uplift_bps (percentToBps('4,8') = 480; servern tar aldrig en procentsträng, och ett NaN når aldrig kontraktet). Formuläret skriver POST/PUT med If-Match + Idempotency-Key, *Baserad på* erbjuder båda grunderna per källbudget (*Kopiera <år> · <nod> (budget)* / *(utfall)* ⇒ kontraktets basis), ett utkast går att Redigera och serverns 422 budget_line_no_account/budget_line_type_mismatch/409 budget_line_overlap renderas VID raden i klartext — ytan förhandsdömer aldrig. Beslutsknapparna renderas fail-closed efter status + mandat med obligatorisk motivering; självbeslutet är serverns 403 approval_self_decision_denied i klartext. Nodkatalogen läses ur konsolideringsrapportens kind: "node"-rader — GET /v1/org-nodes är gatad på tenant-config.manage och nekar en bokförare (bokförd korrigering). ⊘ med ägare (aldrig attrapp): *Rapportvaluta* som läsfält (FX-foundationen) · *Multi-currency (CEP/FIPJP)* (FX + publikt cross-tenant-API) · klubbhälsans *Betalningsgrad*/*Förfallet* (D-MEMBER — tenantutställda fakturor; ägartext, aldrig »0 %«) · trendens *(forecast)* ⇒ *»(hittills)«* · breadcrumb-länken *Fees* (ingen /avgifter-rutt) · kvartalen Q1—Q4 i *Rapporter* (rapportrutterna tar bara fiscal_year/org_node_id — disabled med skälet i klartext; *Budget mot utfall* har riktiga kvartal). Vy-spec: specs/admin/views/budget.md; kontraktsraden i docs/migration/design-contract.md. Bevis: node api/__acceptance__/bill-m13-yta-kontrakt.acceptance.mjs + node tools/gate/bill-m13-e2e.mjs http://localhost:3120 http://localhost:3100 mot körande stack + admin/lib/__tests__/budget-presenter.test.ts.
Tenant Billing Profile
F08.13_PL-T092 — tenant-scoped singleton som styr språk, betalningstid, påminnelsekadens, ton, dröjsmålspolicy och utställar-identitet._
How it works- F08.13.01 Shipped
BillingProfile-modell — tenant-scoped singleton med språk/valuta/betalningstid/ton/dunning-stages/dröjsmålsavgift/issuer-identitet. Fyra pydantic-validatorer (språk, dröjsmåls-policy→fält, dunning-kadens sortering/unika, payment-terms-ton).
✅ PL-T092 - F08.13.02 Shipped
Konfigurerbar dunning-kadens — DunningStageConfig med offset_days (0–720), stage_name, template_id, channel (email/letter/both). Stigande sortering + unika offsets/namn enforced.
✅ PL-T092 - F08.13.03 Shipped
Dröjsmålsavgifts-policy — none/fixed/percentage/interest_rate med matchande fält-kravsvalidering och legal_reference för nationell lagstiftning (Code de commerce L. 441-6, BGB § 288 Abs. 2, Inkassolagen 1974:182 § 6).
✅ PL-T092 - F08.13.04 Shipped
Profil-cache — in-process TTL 60 s per tenant; invalidate_billing_profile_cache(tenant_id) vid alla mutationer. ≤ 50 ms p95 på cold read, ≪ 1 ms på cache hit.
✅ PL-T092 - F08.13.05 Shipped
Platform defaults fallback — get_platform_defaults() returnerar hårdkodad konfig (Easy Software System In Europe AB, engelska, EUR, 30d, 14/30/60 dunning). resolve_profile_or_defaults fallback-loggar strukturerad varning (billing_profile.fallback_to_defaults).
✅ PL-T092 - F08.13.06 Shipped
API — GET/PUT /tenants/{id}/billing-profile (tenant-admin RBAC via _assert_tenant_access), POST /tenants/{id}/billing-profile/dunning-stages/preview för dry-run av kadensändringar (antal fakturor + total_outstanding per stadium idag).
✅ PL-T092 - F08.13.07 Shipped
Onboarding-seed — create_default_billing_profile(tenant_id, issuer_*, ...) skapar profilen vid tenant-onboarding; avvisar dubbel-skapelse på service-nivå (singleton-invariant ägd av servicen, ej DB-constraint).
✅ PL-T092
Pro Services Billing
F08.11_PL-T036 — timbaserad fakturering för konsultengagemang (€150/h)._
How it works- F08.11.01 Shipped
ProServiceEngagement — scoped consulting assignment per tenant med låst timpris (€150/h), estimerat timantal och faktureringskontakt
✅ PL-T036 - F08.11.02 Shipped
TimeEntry med kvartstimme-precision — DRAFT→SUBMITTED→INVOICED/VOIDED livscykel; max 24h per konsult per dag
✅ PL-T036 - F08.11.03 Shipped
Månadsvis faktureringsjobb — idempotent jobb skapar ProServiceInvoice per tenant 1:a varje månad; markerar TimeEntries INVOICED
✅ PL-T036 - F08.11.04 Shipped
Finansiell separation — revenue_category = "pro_services_consulting" på alla dokument; aldrig inkluderad i subscription ARR
✅ PL-T036 - F08.11.05 Shipped
Kund-rapport — kunden ser egna SUBMITTED/INVOICED poster; consultant_user_id dold; DRAFT aldrig exponerat
✅ PL-T036 - F08.11.06 Shipped
Faktura-förhandsgranskning — admin kan se nästa månads fakturor innan jobbet körs
✅ PL-T036 - F08.11.07 Shipped
Pro Services revenue-rapport — månadsvis/årsvis rapport separerad från subscription ARR
✅ PL-T036
Bot Protection
F08.14_PL-T072 — Cloudflare Turnstile + honeypot + rate-limit + audit på alla publika POST-endpoints._
How it works- F08.14.01 Shipped
Turnstile-widget — Cloudflare Turnstile på alla publika POST-endpoints (auth, contract-requests, newsletter, contact, feedback, klubbansökan); osynlig 95 % av tiden; GDPR-vänlig (ingen cookie); fail-open med 5 s timeout.
✅ PL-T072 - F08.14.02 Shipped
Honeypot-fält — dolda formulärfält som botar fyller i; serverside-validering avvisar 422 om satta. Kompletterar Turnstile för enklaste botar.
✅ PL-T072 - F08.14.03 Shipped
Rate-limit-stack — IP-baserad rate-limit per endpoint (Redis-counter); 429 efter tröskel; dynamiska gränser per endpoint-känslighet.
✅ PL-T072 - F08.14.04 Shipped
Audit + alert — strukturerad logg på Turnstile-fail/honeypot-trigger/rate-limit-block; Prometheus-counter; alert vid spike (>10× baseline).
✅ PL-T072
Swedbank Bankgirot BGI-inläsning
F08.15_PL-T095 — SFTP-dragen auto-matchning av inkommande Bankgirot-betalningar mot `Invoice.ocr_reference`. Kärnpipelinen (Luhn + parser + matchare + admin-kö + job) är klar; riktig SFTP-hämtning och Azure Key Vault-credentials landar i en uppföljning._
How it works- F08.15.01 Shipped
Luhn mod-10-hjälpare — verify_luhn/generate_luhn_checksum/build_ocr_reference med testvektorer (Wikipedia, MasterCard). Strippar icke-siffror, zero-pad till ≥3, trim till ≤24, sist checksum.
✅ PL-T095 - F08.15.02 Shipped
BGMax-parser (legacy fixed-length) — record 01 (valuta pos 25–27), 05 (OCR pos 3–27, belopp pos 28–45 som öre, datum pos 46–53 YYYYMMDD, motpart pos 54–80). Okända record-typer hoppas över utan krasch.
✅ PL-T095 - F08.15.03 Shipped
ISO 20022 pain.002-parser — namespace-strippad iteration av TxInfAndSts-noder; stödjer pain.002.001.10–.12.
✅ PL-T095 - F08.15.04 Shipped
Auto-detect parser — auto_detect_and_parse väljer XML vs legacy på första non-whitespace-bytet.
✅ PL-T095 - F08.15.05 Shipped
Invoice.ocr_reference — nytt fält (4–25 siffror) + Pydantic-validator som auto-genererar från invoice_number om det saknas; index på (tenant_id, ocr_reference).
✅ PL-T095 - F08.15.06 Shipped
BankImportRun-modell — platform-scoped audit per fil med SHA-256 source_hash för idempotens, filename, fetched_at, format, rows-räknare, status (success/partial/failed).
✅ PL-T095 - F08.15.07 Shipped
UnmatchedBankTransfer-modell — platform-scoped kö med reason (no_ocr/invalid_ocr_checksum/no_matching_invoice/amount_overpaid) och resolution-fält (matched_invoice_id, ignored).
✅ PL-T095 - F08.15.08 Shipped
Matcher — cross-tenant lookup mot aktiva fakturor (sent/overdue/partially_paid), skapar Payment (method bank_transfer, status completed) + BankTransferMatch (status matched, confidence exact), uppdaterar invoice.paid_amount och status.
✅ PL-T095 - F08.15.09 Shipped
Övermatchning — när beloppet överstiger utestående skapas både payment och amount_overpaid unmatched-rad så admin kan hantera återbetalning.
✅ PL-T095 - F08.15.10 Shipped
SHA-256-idempotens — process_file_content returnerar befintlig BankImportRun vid återleverans; inga dubblerade betalningar.
✅ PL-T095 - F08.15.11 Shipped
Schemalagd jobbregistrering — bank-reconciliation-import med injicerbar fetcher: () -> Iterable[(filename, content)]; default-fetcher returnerar []. Tester byter fetcher på modulnivå.
✅ PL-T095 - F08.15.12 Shipped
Admin-kö — GET /admin/bank/unmatched, POST /admin/bank/unmatched/{id}/match?invoice_id=, POST /admin/bank/unmatched/{id}/ignore, GET /admin/bank/import-runs. Alla platform_admin-skyddade.
✅ PL-T095 - F08.15.13 Shipped
SFTP-hämtare — asyncssh-baserad fetcher mot Swedbanks SFTP-inbox med credentials från Azure Key Vault.
✅ PL-T095 - F08.15.14 Shipped
Move-on-processed — flytta fil till processed/ resp. failed/ efter varje försök så idempotens-hashet bara försvarar mot genuina dubletter.
✅ PL-T095 - F08.15.15 Shipped
Tenant-admin-kö — låt federationer hantera sina egna unmatched-rader (nuvarande surface är platform_admin-only eftersom inboxen är platform-vid).
✅ PL-T095 - F08.15.16 Shipped
Parse-failure-alert — Slack/email-notifiering när en BGI-fil misslyckas parsa.
✅ PL-T095
Tenant-admin UI för fakturering
F08.16_PL-T096 — sex vyer i admin-appen under nytt sidomenysektion "Fakturering": tenant-scopade (fakturaprofil, fakturor, fakturadetalj, betalningar) och platform-admin-only (omatchade transfers, batch-jobb)._
How it works- F08.16.01 Shipped
Central API-klient — admin/src/lib/billing-api.ts centraliserar alla anrop mot /tenants/{id}/billing-profile, /invoices, /payments, /admin/bank/* och /jobs/*; exponerar typer (BillingProfile, Invoice, Payment, UnmatchedTransfer, JobRun), hjälpfunktioner (formatMoney, invoiceStatusLabel, INVOICE_STATUSES), multi-status fan-out för listfiltret, och ApiError-genomsläpp.
✅ PL-T096 - F08.16.02 Shipped
BillingProfileView — editerar tenantens singleton-profil via GET/PUT /tenants/{id}/billing-profile. Dunning-stegar som editerbar tabell (add/remove/reorder, auto-sortering på save), språk som chip multi-select (default låst i supported-listan), villkorliga dröjsmålsfält per policy, POST .../dunning-stages/preview för dry-run. Client-side validering speglar backend-pydantic.
✅ PL-T096 - F08.16.03 Shipped
InvoiceList — GET /invoices/?status=... med multi-status-chip-filter (parallel fan-out + de-dup per id när flera valts), "Ladda fler 50"-paginering, sorterbara kolumner (Nummer, Debitor, Utfärdad, Förfaller, Summa, Status), statusfärgade pill-badges.
✅ PL-T096 - F08.16.04 Shipped
InvoiceDetail — full faktura-detalj: header med status-badge, info-kort (issuer, debitor, ECB-referenskurs, OCR), rader, betalningslista (parallell GET /payments/?invoice_id=), status_history-tidslinje, villkorliga actions (PATCH /send, POST /send-reminder, PATCH /cancel), PDF-länk via Linking.openURL.
✅ PL-T096 - F08.16.05 Shipped
PaymentList — GET /payments/?status= med radiofilter (alla/avslutade/pågående/misslyckade/återbetalda/avbrutna), paginering, row-click öppnar faktura. Kolumner: Betald, Belopp, Metod, Referens, Faktura (last-8), Status.
✅ PL-T096 - F08.16.06 Shipped
UnmatchedTransfersView (platform-admin) — GET /admin/bank/unmatched, include-resolved-toggle, row-actions via modal (Matcha via faktura-ID + POST .../match?invoice_id=...; Ignorera med motivering + POST .../ignore). Status-badges OLÖST/MATCHAD/IGNORERAD.
✅ PL-T096 - F08.16.07 Shipped
JobsAdmin (platform-admin) — kort-grid med alla /jobs/registry-jobb (Historik-filter + Kör nu), body-table med senaste 100 /jobs/runs-poster (manuellt-triggad-markör, tid i sekunder, status-färgad badge, felmeddelande 2 rader). POST /jobs/run/{name} med tom parameter-body.
✅ PL-T096 - F08.16.08 Shipped
Navigation — ny sidomenysektion "Fakturering" mellan Ekonomi och Innehåll med 5 länkar (3 tenant-scopade, 2 platform-admin-gatade via nytt requiresPlatformAdmin-flag i NavItem). Filtret appliceras både i visibleSectionsFor() och i ⌘K command palette.
✅ PL-T096 - F08.16.09 Shipped
requirePlatformAdmin-HOC — admin/src/auth/platform-admin.tsx: hämtar GET /session/me, tillåter scope === "platform_admin", role === "platform_admin" eller is_system_user, renderar annars 403-panel. Används av /billing/unmatched-transfers och /platform/jobs. Bara kosmetisk — backend enforceras oberoende.
✅ PL-T096 - F08.16.10 Shipped
usePlatformAdmin-hook — hook-variant för icke-blockerande villkorlig UI (sidomenyns dolda länkar, command palette-index). Läser samma /session/me-resultat.
✅ PL-T096
Multi-currency Foundation (PL-T224)
F08.17`MoneyAmount` value object + manual `FXRate`-table + `FXService.convert`. Hedging hooks och real-time FX-feeds är **explicit out-of-scope** — se docs/business/finance/multi-currency-foundation.md.
- F08.17.01 Shipped
MoneyAmount value object — Pydantic-modell med currency: ISO4217 + amount: Decimal. Wire-format: amount serialiserad som string för float-säker JS-transport. Negativa belopp tillåts (refunds). Likhet currency-aware. Konsumeras av PracticeExam.fee och MentorAssignment.hourly_rate.
✅ PL-T224 - F08.17.02 Shipped
FXRate manual entry + lookup — data model med (base_currency, quote_currency, rate, as_of, source, is_active). POST /finance/fx/rates (capability finance.fx.manage) skriver manuella rates; GET /finance/fx/rates?base="e=&as_of= hämtar senaste matchande. Source-värdena ecb-daily och stripe-fx reserverade för framtida automation.
✅ PL-T224 - F08.17.03 Shipped
FXService.convert — POST /finance/fx/convert med {amount, currency, target_currency, as_of?}. base==quote returnerar input oförändrat utan DB-hit. Saknad rate ger 422 fx_rate_unavailable. Conversion runda till 0.01 ROUND_HALF_EVEN. Returnerar rate_used för audit-spårning.
✅ PL-T224
No features match your filters.