Skip to main content
Petanque Life
← Back to all features
08

Finance & Billing

318 features · 24 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
Shipped
How it works
  • 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
Shipped
How it works
  • 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
Shipped
How it works
  • 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
Planned

**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
Planned
How it works
  • 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
Planned
How it works
  • 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
Planned

> **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
Planned
How it works
  • 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
Shipped
How it works
  • 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
Shipped

> **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
Planned
How it works
  • 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
Planned
How it works
  • 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
Planned

## 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.11.10 Shipped

    ✅ Betalgrinden — admin och arrangörsappen läser tenantens eget avtal (D-ADMINAPP m-2, 94f74ebf.m-2) — den första kod i huset som LÄSER abonnemangsstatus för att grinda något. Domen räknas i servern (internal/billing.AdminAccessDecision) ur tenantens eget platform_subscription + platform_invoice + dunning_case och bärs som fältet admin_access i GET /me/capabilities (decision granted|blocked, basis subscription_active|subscription_past_due|payment_pending|no_subscription|subscription_ended|not_a_customer|not_enforced, enforced, policy_decision/policy_basis och SERVERNS next_steps[]). Hela admin grindas — admin-webben (ShellHost) och arrangörsappen (SessionGate) — utom avtals-/onboardingvägen (login, auth/complete, kom-igang*, abonnemang); en stängd tenant får grindkortet (admin-access-gate, stegen ur next_steps, *Kontrollera igen* som gör exakt en ny orakelläsning) och ingen vy monteras, ingen vyläsning sker. past_due är ÖPPET med indrivningsbannern (admin-dunning-banner); plattforms- och sandbox-tenanter är not_a_customer. Fail-closed: oklar dom ⇒ admin-access-gate-unknown, aldrig en vy. Verkställighetens omfång är bootkonfig (PL_ADMIN_ACCESS_GATE enforce|selected|off + PL_ADMIN_ACCESS_GATE_TENANTS; ogiltig kombination = bootfel). Arrangörsappen fick dessutom /abonnemang i telefonform (hopfällbara sektioner, fakturorna som kort, fakturamottagaren med If-Match + Idempotency-Key, betal-/dokumentlänkar i systemets webbläsare — ingen betal-SDK). Fixtur: seed-admin-access (sex club-tenanter, en per avtalsläge, drivna genom BILL:s riktiga sömmar). Bevis: dadminapp-m2-e2e.mjs (descriptor 916-dadminapp-m2-betalgrind, arrangörsappen i mobil viewport). Icke-mål: ingen servergrind på domän-API:t, ingen självbetjänad teckning, ingen ändring i BILL:s statusmaskin. Specar: specs/api/endpoints/authz-capabilities.md §admin_access, specs/admin/views/betalgrind.md, specs/arrangor/views/betalgrind.md, specs/arrangor/views/abonnemang.md; domän: docs/domain/subscription-lifecycle.md §0c.

    ✅ D-ADMINAPP m-2
  • 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
Planned

_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
Planned
  • 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
Planned
  • 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.20
Planned

Bindande 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.

Kioskkatalogen: buvettens produkter, paket och lager (D-KIOSK)

F08.21
Planned

Initiativ D-KIOSK, milstolpe `ae37902c.m-1` (`milestones/ae37902c.m-1/spec.md`).

  • F08.21.01 Planned

    Kioskkatalogen (ae37902c.m-1.t-1) — kiosk_products per OrgNode (migration 00324): namn per språk, sluten kategori, BRUTTOPRIS i minsta enhet med tenantens valuta, momssats ur landets meny i BILL:s katalog (getKioskCatalogOptions, 422 vat_rate_not_in_catalog), valfri SKU (unik per nod bland icke-arkiverade), symbol, alkoholflagga, sortering, aktiv/vilande och mjuk radering. listKioskProducts/createKioskProduct/getKioskProduct/updateKioskProduct/archiveKioskProduct med If-Match/ETag/Idempotency-Key, mandaten kiosk:operate|close_z|manage och skopet mandat → nod (404 byte-identiskt utanför skop). Admin-ytan /kiosk levereras i ae37902c.m-1.t-2.

  • F08.21.02 Planned

    Paket med buntpris (ae37902c.m-1.t-1) — kiosk_bundles: 1—10 rader mot aktiva produkter i samma nod och valuta, buntpris ≤ radernas summa (422 bundle_price_exceeds_sum med max_price_minor), raderna expanderade med produktens aktuella pris och sats, vat_display (<bps>|mixed). kiosk.AllocateBundle fördelar buntpriset exakt för m-2:s momsuppdelning.

  • F08.21.03 Planned

    Lätt lagerföring (ae37902c.m-1.t-1) — hela enheter, saldot materialiserat ur den append-only liggaren kiosk_stock_adjustments (opening_balance|restock|waste|breakage|theft|correction|sale), adjustKioskProductStock med obligatorisk Idempotency-Key och radlås, listKioskStockAdjustments, lågt lager som serverns dom (low_stock, getKioskStockSummary). Liggarens aktör anonymiseras vid radering och ingår i DSAR-kategorin *Kioskadministration*.

  • F08.21.04 Planned

    Admin /kiosk, fliken *Produkter & lager* (ae37902c.m-1.t-2) — nav-posten *Kiosk* 🏪 i *Ekonomi & handel* (kiosk:manage), nodvalet ur listKioskNodes (?nod=, väljare bara vid ≥ 2 noder, ärligt tomläge utan mandat), rubriken *»Buvette för <nod> · valuta <CUR>«*, flikraden (i m-1 med *Kassa* ⊘ ae37902c.m-2 och *Dagsavslut* ⊘ ae37902c.m-5; sedan m-2 LEVANDE — se F08.21.08), lågtröskelbannern ur getKioskStockSummary, filterchips som serverfrågor, produkttabellen (pris via formatMoney, moms 12 %, — för otrackat lager, *Lågt*-märket ur serverns low_stock), produktformuläret (pris i minsta enhet som skrivet, moms och kategori ur catalog-options, If-Match), lagerjusteringen med idempotensnyckel och liggaren, paketen med vat_display/*blandad* och serverns max_price_minor vid fältet, ⊘ *Importera startkatalog* ae37902c.m-6. Deny-by-default: utan kiosk:manage på noden renderas inga mutationsverb. Vyn bor i packages/shared/src/views/kiosk/katalog/ (web levande, native skapad men omonterad). Vy-spec specs/admin/views/kiosk.md, L4 tools/gate/dkiosk-m1-e2e.mjs.

  • F08.21.05 Planned

    Kassa-sessionen (ae37902c.m-2.t-1) — kassakonfigurationen per nod (getKioskSettings/updateKioskSettings: kassaplatser, betalsätt — i m-2 kontant —, sidfot, ålderskontroll, diff-tröskel; If-Match), optionslistan (getKioskSessionOptions: kassaplatser, tävlingar och volontärpass), att öppna en kassa på en kassaplats med växelkassa och valfri tävling/pass (openKioskSession, högst en öppen per kassaplats ⇒ 409 register_busy), listKioskSessions/getKioskSession, X-rapporten (getKioskSessionXReport) och stängningen med kontanträkning (closeKioskSession, kiosk:close_z, notering över tröskeln). Migration 00325.

  • F08.21.06 Planned

    Försäljningen och kvittot (ae37902c.m-2.t-1) — createKioskSale med produkter, paket, manuella rader, kvittorabatt och kontant med växel; frysta priser och exakt moms per rad, kvittonummer per session, lagret dras genom liggaren (och nekar aldrig), ålderskontroll, obligatorisk Idempotency-Key med dedup även bortom husets retention (replayed). listKioskSales/getKioskSale, kvittots tryckform getKioskSaleReceipt och den anonyma, PII-fria kvittolänken getPublicKioskReceipt (IP-takad, PL_KIOSK_RECEIPT_RATE_MAX).

  • F08.21.07 Planned

    Returen (ae37902c.m-2.t-1) — refundKioskSale (mandatet kiosk:refund, Kassör och adminmallarna): hel eller delretur mot köpets rader med skäl och valfri återläggning i lager, eget kvittonummer i en öppen session, exakt återbetalning efter rabatt, köpets härledda status completed|partially_refunded|refunded. Kontrakt specs/api/endpoints/kiosk-till.md.

  • F08.21.08 Planned

    Admin /kiosk, fliken *Kassa* och stängningen på *Dagsavslut* (ae37902c.m-2.t-2) — flikraden LEVANDE med ?flik=kassa|produkter|dagsavslut (utan flik ⇒ *Produkter & lager*) och sidhuvudets *Öppna kassa*; sessionskort per öppen session (*● Öppen sedan HH:MM*, kvitton, försäljning, växelkassa, offline-synkade kvitton, kassör/pass/tävling) med *X-rapport* (serverns aggregat i ett blad) och *Stäng kassa → Z-rapport*; offline-kortet ur captured_offline_count (*Synka nu* ⊘ ae37902c.m-4); kassakonfigurationen (kassaplatser, kontant låst på, sidfot, valuta läs-only, ålderskontroll, diff-tröskel i minsta enhet; If-Match, Swish ⊘ ae37902c.m-3, kortterminal ⊘ ägarbeslut); öppna kassa (kassaplats, växelkassa, tävling, volontärpass; 409 register_busy vid fältet); kvittolistan för de öppna passen (-sold_at, 50 åt gången, status med ikon + text + färg, *Offline*-märket) med kvittobladet (den publika länken + *Kopiera*; QR ⊘ m-4, e-post/SMS ⊘ D-COMM) och returformuläret (kiosk:refund: antal per rad, skäl, notering, återläggning, *Kontant ut* förifyllt, en Idempotency-Key per inskick); stängningen på *Dagsavslut* (?session=: förväntad kassa ur X-rapporten, räknad, härledd diff mot nodens tröskel, 422 cash_diff_requires_note vid noteringen, kort/Swish ⊘ m-3, konto ⊘ m-6, stängaren = den inloggade, ConfirmDialog; Z-tabellen ⊘ ae37902c.m-5). Deny-by-default per verb och VALD nod (kiosk:operate|close_z|refund|manage — nodlistan bär nu även kiosk:refund). Vy-spec specs/admin/views/kiosk.md; bevis admin/__acceptance__/dkiosk-m2-kassa.acceptance.mjs + tools/gate/dkiosk-m2-e2e.mjs (deskriptor 999-dkiosk-m2-kassa).

  • F08.21.09 Planned

    Betalalternativen och konfigurationen (ae37902c.m-3.t-1) — getKioskPaymentOptions: PAY-2:s resolution för kassans nod projicerad (mottagarkontot som *eget | förälderns | förbundets*, aldrig ett provider_account_id), per online-ord (kort, Swish, MobilePay, iDEAL, Bancontact) om det är tillgängligt och varför inte, available_methods med kontant alltid först; ett saknat konto är ett tillstånd (resolution: null). updateKioskSettings tar bara tillgängliga betalsätt (422 method_unsupported_for_account med skälet). Kontrakt specs/api/endpoints/kiosk-payments.md.

  • F08.21.10 Planned

    Kort-/wallet-betalning i kassan (ae37902c.m-3.t-1) — createKioskSale med en kort-/wallet-rad (köpet väntar på betalning, lagret orört), payKioskSale (:pay: intenten genom F1-11:s sömmar med subject_type='kiosk_sale', redirect_url/client_secret, förnya, byt metod, byt till kontant), cancelKioskSale (:cancel med skäl, kontantdel tillbaka över disk) och OnSuccess-/refund-hooken RegisterKioskPaymentHandlers som settlar köpet idempotent via F1-11:s centrala webhook (dubbel leverans ⇒ en effekt; avvikande belopp och strandade betalningar flaggas). X-rapporten räknar settlade köp och visar väntande/annullerade separat; kassan stängs inte med köp i luften (409 sales_awaiting_payment). Migration 00327. Regler BR39.13—.16, .18.

  • F08.21.11 Planned

    Retur till kort/wallet (ae37902c.m-3.t-1) — refundKioskSale med en online-rad går tillbaka till köpets kort/Swish genom F1-11:s enda återbetalningssöm executeRefundSeam (en commit; returens rad bär refund_id, köpets rad refunded_minor); en återbetalning gjord utanför kassan bokförs med settlement: external (external_refund_pending släcks). Mandatet kiosk:refund. Regel BR39.17.

  • F08.21.12 Planned

    Admin /kiosk: betalningarna på *Kassa* och *Dagsavslut* (ae37902c.m-3.t-2) — konfigurationens betalsätt som en toggle per online-ord ur getKioskPaymentOptions (*Kort · Swish · MobilePay · iDEAL · Bancontact*; ett otillgängligt ord avstängt med serverns skäl som hjälptext), kontoraden *»Mottagarkonto: Klubbens eget | <nod> (förälder) | Förbundets · <provider>«* ur PAY-2:s resolution, kvittolistans *Väntar på betalning*/*Annullerat* och kolumnen *Betalsätt*, på ett väntande köp *Betallänk* (bladet med länken, *Kopiera*, intentens status, *Förnya*), *Betala kontant i stället* (*Mottaget belopp*) och *Avbryt köp* (med *Skäl*; kontant att lämna tillbaka visas efteråt), omhämtning var 5:e sekund medan ett köp väntar (KIOSK_AWAITING_POLL_MS, pausad i dold flik), varningsmärkena *Betalning efter avbrott* och *Återbetald utanför kassan* (med *Bokför retur*), returens *Pengarna tillbaka* (*Kontant ut* / *Tillbaka till <metod>*), offline-raden *»Offline-köp är alltid kontant«*, och på *Dagsavslut* de läs-only raderna *Kort/Swish enligt kassan* ur X-rapporten och listan *»N köp väntar på betalning«* när stängningen nekas. Ingen QR (⊘ m-4), ingen terminal (⊘ ägarbeslut), bankavstämningen i Bokföring (BILL m-10). Vy-spec specs/admin/views/kiosk.md §Betalningarna; bevis node tools/gate/dkiosk-m3-e2e.mjs (med worker).

  • F08.21.13 Planned

    Kassan i spelarappen: Öppna kassa och Sälj (ae37902c.m-4.t-2) — ingången är raden *Kassa (buvette)* i Inställningar, bara med kiosk:operate (annars finns raden inte, och djuplänken /kassa visar det nekade läget utan ett enda kiosk-anrop). /kassa öppnar en kassaplats med växelkassa eller ansluter till en öppen kassa. Vid 409 register_busy visas *Fortsätt i den öppna kassan*. Därefter visas *Sälj*: kategorichips, produkt- och paketrutnät ur katalogens ögonblicksbild på enheten, manuell rad med katalogens momssatser, kvittorabatt (procent eller belopp med skäl), varukorgen med +/−, och summan med moms per sats. Alla tal räknas av prisspegeln @petanque-life/shared/kiosk, så kassan säljer vidare utan nät. Klockskev mot sessionen varnas, och en kassa i fel tenant säljer inte. /kassa/pass visar kassan, de osynkade posterna, katalogens hämttid, X-rapporten med nät och *Lämna kassan*. Stängningen är ⊘ ae37902c.m-5 och görs i admin. Ingen mockup finns för appens kassa (ägarbeslut); ytan bygger på formspråket och arkivets form. Vy-spec specs/app/views/kassa.md; bevis node tools/gate/dkiosk-m4-e2e.mjs app/dist 3110 http://localhost:3100 (med worker).

  • F08.21.14 Planned

    Offline-kön på D-OFFLINE:s motor (ae37902c.m-4.t-1) — collectionen kiosk-sale (klassen critical, *»pengar som fysiskt bytt ägare«*). Scope är sessionen, och tupeln <sessionId>::<deviceId>::<deviceSeq> ger nyckeln kiosk-sale::<tupel>, samma online och offline. Kroppen fryses vid commit. Ett köp utan nät, med nätfel eller med 5xx köas och replayas per post när nätet är tillbaka, aldrig som batch. Servern dedupar på nyckeln, så en replay ger exakt en rad. Kioskcachen pl.offline.kiosk.v1.<identitet> bär kassan, katalogen, räknaren och de synkade kvittonumren, men ingen person och ingen token. Den töms vid bekräftad utloggning. Kö-räknaren och *Synka nu* finns på *Sälj*, posterna syns i passöversikten och i synkcentret under *Kioskförsäljningar*. En stängd kassa (409 session_not_open) ger *Flytta till det öppna passet*: en ny post med säljtiden i noteringen. Övriga fel tas bort med en bekräftelse, och en post sänds aldrig i fel tenant. Registrerat i offline-persistence.md §3; regler BR39.19—.21.

  • F08.21.15 Planned

    Betala och Kvitto i appen (ae37902c.m-4.t-2) — /kassa/betala erbjuder kontant med växelräknare och snabbbelopp, och med nät också kassans online-ord (getKioskPaymentOptions ∩ kassans betalsätt). En åldersgränsad vara kräver bekräftad legitimation när kassan har ålderskontroll. Kort och wallet går genom :pay, och betallänken (redirect_url) visas som QR-kod ritad på enheten, med *Kopiera* och *Öppna*. Köpet pollas tills hooken settlat det, och den väntande betalningen kan förnyas, bytas till kontant eller avbrytas med skäl. Utan nät är online-orden spärrade med *»Kräver nät — ta kontant«*. Ingen betal-SDK och inget client_secret. /kassa/kvitto visar serverns kvitto (#<n>, kassaplats, rader, rabatt, moms per sats, betalsätt med växel, sidfot, QR-koden för den publika länken och *Dela kvitto*) eller, för en osynkad post, det preliminära kvittot utan nummer och QR. Kvitto per e-post/SMS är ⊘ D-COMM, kortterminal och retur i appen är ⊘ ägarbeslut.

  • F08.21.16 Planned

    Dagsavslutet: Z-rapporten (ae37902c.m-5.t-1) — closeKioskSession skapar Z-rapporten i samma transaktion som stängningen (nodens Z-nummer, fryst X-aggregat med moms per sats för köp och retur, kontantavstämning, varukostnad och marginal ur produkternas unit_cost) och svarar {session, summary, z_report}; sessionen är därefter frusen. Läsning listKioskZReports (filter status, kassaplats, tävling, period, nummer) och getKioskZReport med kiosk:manage eller kiosk:close_z; PDF:en getKioskZReportPdf renderad EN gång efter commit genom blob-porten (byte-identisk, ETag = sha256, 304, 409 z_report_pdf_pending innan den finns). Migration 00330. Regler BR39.22—.24, .27. Kontrakt kiosk-z-reports.md.

  • F08.21.17 Planned

    Void och omstängning (ae37902c.m-5.t-1) — voidKioskZReport (:void med skäl, kiosk:manage, Idempotency-Key) makulerar nodens senaste Z-rapport, släpper köpen och gör sessionen ofrusen; omstängningen ger nästa nummer med supersedes_z_report_id. Sena offline-kontantköp landar i en ofrusen stängd session (härdningen D-10); en frusen svarar 409 session_not_open med frozen. Regler BR39.25, .28.

  • F08.21.18 Planned

    RevenueEvent till BILL med riktning och avräkning (ae37902c.m-5.t-1) — Z-rapportens köp och returer (och vid void deras speglar) emitteras efter commit som BILL-händelser kiosk:z:<id>:sales|:refunds|:void:sales|:void:refunds genom EmitRevenueEvent, idempotent på BILL:s nyckel, med svepet kiosk_z_effects_tick som nät. BILL fick två additiva fält: direction (revenue|reversal — reverseringen bokförs speglad) och settlement_lines (kontant ⇒ 1910, provider ⇒ 1580); varje befintlig producents wire-form och verifikat är oförändrade. Migration 00331. Regel BR39.26. Kontrakt accounting-feed.md §10.

  • F08.21.19 Planned

    Admin /kiosk, fliken *Dagsavslut* komplett (ae37902c.m-5.t-2) — Z-tabellen LEVANDE (listKioskZReports: Z-nr, datum, kassaplats, kassör, kvitton, försäljning, kontant/kort/Swish, diff med ikon + text + ton, status *Stängd*/*Makulerad*, *Öppna* och *PDF*; filtren status/kassaplats/period/tävling som serverfrågor, *Visa fler*, tomläge och fel med *Försök igen*), Z-detaljen ?flik=dagsavslut&z=<id> (getKioskZReport: KPI, per betalsätt, per momssats köp/retur/netto, kontantavstämningen med noteringen, varukostnad och bruttomarginal med täckningen, bokföringshändelserna med BILL:s läge), PDF:en genom transporten i klickögonblicket till porten saveFile (getKioskZReportPdf, spärrad medan den renderas), *Makulera* med obligatoriskt skäl (voidKioskZReport, Idempotency-Key, bara kiosk:manage och bara nodens senaste, serverns 409 i klartext) och stängningens resultatpanel *»Z-rapport Z-<n> skapad«* med *Visa*/*PDF*; en stängd ofrusen session väljs som *»Stängd — Z-rapport makulerad, stäng om«* och stängs om till nästa nummer. Kassören (kiosk:close_z) ser tabellen men ingen void. Vy-spec: specs/admin/views/kiosk.md §Dagsavslut — Z-rapporter.

  • F08.21.20 Planned

    Stäng kassa i appen (ae37902c.m-5.t-3) — /kassa/stang är en stepper ett steg i taget, bara med nät och kiosk:close_z: *Sammanställning* (X-rapporten läst färskt: transaktioner, returer, netto, per betalsätt, per momssats, offlineköp; köp som väntar på betalning listas med kvittonummer och låser *Nästa* tills de betalats kontant, tagits om eller avbrutits), *Räkna kontanter* (växelkassa, kontant in och ut och förväntat ur serverns tal; räknat belopp i minsta enhet, diffen och noteringen — tröskeln ur kassans konfiguration, servern är domaren), *Bekräfta* (closeKioskSession; dubbeltryck ger ett dagsavslut) och resultatet *Z-rapport Z-<n>* med netto, per betalsätt, diff, varukostnad, bruttomarginal, bokföringens läge och PDF:en (Expo Web laddar ner; telefonappen ⊘ D-MOBILDIST). Raden *Stäng kassa* i passöversikten är spärrad utan nät och medan sessionens köp väntar på synk — ett köp kan aldrig frysas ute ur sin egen Z-rapport. Sena offline-kontantköp i en stängd men ofrusen session synkas på nytt i stället för att bli konflikter (frozen). Vy-spec: specs/app/views/kassa.md §/kassa/stang.

  • F08.21.21 Planned

    Bokföringsläget och kontomappningen (ae37902c.m-6.t-1) — blocket bookkeeping i getKioskSettings/updateKioskSettings (kiosk:manage, If-Match; ett PUT utan blocket lämnar det orört): läget z_report (default, samlat per dagsavslut) | realtime (per kvitto), intäkts-, varukostnads-, lager- och tre justeringskonton som hintar mot BILL:s kontoplan (422 kiosk_bookkeeping_account_invalid vid fel typ/okänt/inaktivt konto), effective räknad vid läsningen med source: hint|default, bokens läge och kioskens feed-toggle; alternativen i getKioskBookkeepingOptions (lägena, aktiva konton per typ — aldrig saldon —, standardkontona, chart_available). Läget fryses på passet (booking_mode på session och Z-rapport). Kontrakt: kiosk-till.md §m-6 · regler BR39.29, BR39.33.

  • F08.21.22 Planned

    Realtidsemissionen (ae37902c.m-6.t-1) — i ett realtime-pass går varje settlat köp (kiosk:sale:<id>) och varje retur (kiosk:refund:<id>) till BILL direkt efter sin commit (kontantköpet, F1-11:s OnSuccess-hook, returen) genom EN söm, med intäktskontot ur kontomappningen; stämpeln på köpet, svepet kiosk_z_effects_tick som nät (plikt 3), Z:ns köp/returer skipped_realtime och en void som aldrig reverserar kvittona. Regel BR39.30.

  • F08.21.23 Planned

    Kostnadsbokningen vid dagsavslutet (ae37902c.m-6.t-1) — stängningen gör i samma transaktion anspråk på nodens svinn, kassation och stöld (liggarrader efter regelns införande — äldre rader backfyllda posting_excluded) och värderar dem till inköpskostnaden (z_report.adjustments med täckning); varukostnaden (kiosk:z:<id>:cogs) och justeringarna (kiosk:z:<id>:adjustments, en kostnadsrad per skäl) emitteras i båda lägena, void reverserar dem och släpper anspråket (released_adjustments). Kontrakt: kiosk-z-reports.md §m-6 · regler BR39.31—.32.

  • F08.21.24 Planned

    BILL:s kostnadsklass (ae37902c.m-6.t-1) — källorden kiosk_cogs/kiosk_stock_adjustment (klassen cost, källans egenskap), wire-fälten cost_lines/contra_account_hint (omitempty), ingest-regeln (ErrFeedCostShapeInvalid: inga moms-/avräkningsrader, Σ = brutto), verifikatet debet kostnadskonto per rad (hint hedrad mot expense, annars 4010/7960) · kredit lager (hint mot asset, annars 1410), reversal speglat; feed-ytans läsfält class, cost_lines, contra_account_hint; revenue-by-source räknar aldrig kostnadsklassen; kontoplanens läs-only projektion för producenter. Kontrakt: accounting-feed.md §11.

  • F08.21.25 Planned

    Intäkt per tävling (ae37902c.m-6.t-2) — getKioskRevenueSummary (kiosk:manage ∨ kiosk:close_z, no-store) summerar nodens STÄNGDA Z-rapporter för en säsong (season[eq], default innevarande år) XOR ett closing_at-intervall, valfritt competition_id[eq]: totals och by_competition[] (tävlingen läst ur D-COMP vid läsningen, raden utan tävling sist) med brutto, returer, netto, moms, netto exkl. moms, per betalsätt, varukostnad, justeringar, marginal, snittkvitto och täckningsflaggan; Σ tävlingarna = totalen, Σ betalsätten = nettot. exportKioskRevenueSummary?format=csv ger samma siffror som byte-deterministisk CSV med maskinnycklar och en total-rad, utan personer. Kontrakt: kiosk-revenue-summary.md.

  • F08.21.26 Planned

    Startkatalogen (ae37902c.m-6.t-2) — getKioskCatalogOptions.starter[]: vanliga buvettevaror (nyckel, namn sv/en/fr, kategori, symbol) utan tal; importKioskStarterCatalog (kiosk:manage, Idempotency-Key obligatorisk) skapar 1—20 produkter med radens eget pris och sats i EN transaktion, SKU starter-<nyckel>, befintliga i skipped_existing; okänd nyckel ⇒ 422 kiosk_starter_key_unknown och noll produkter. Kontrakt: kiosk-catalog.md §m-6.

  • F08.21.27 Planned

    Lagervarningen (ae37902c.m-6.t-2) — svepet kiosk_low_stock_tick (20 * * * *) skickar EN kiosk_low_stock-notis (in_app) per nod med produkterna under sin tröskel till de kiosk:manage-bärare vars skop täcker noden, stämplar low_stock_notified_at (single-fire) och liggaren nollar stämpeln när saldot når tröskeln igen. Jobbet: api/docs/jobs-kiosk.md.

  • F08.21.28 Planned

    Operatörens kioskbild (ae37902c.m-6.t-2) — sysKioskGetOverview (GET /v1/sys/kiosk/overview?month=, sys.kiosk.read på sys_finance + sys_support): per tenant volym per valuta, offline-andel och synkfördröjning, betalhälsa, BILL-flödets läge och bokföringslägen, plus de stora kassadifferenserna (warning|critical mot plattformens trösklar PL_SYS_KIOSK_CASH_DIFF_*). Endast aggregat — inga kvittonummer, kassörer eller betalnings-id. Kontrakt: sys-kiosk.md.

  • F08.21.29 Planned

    Buvettens uppföljning /buvette (ae37902c.m-6.t-3) — nav-posten *Buvette* (🍺, kiosk:manage, direkt efter *Kiosk*). Fliken *Intäkt per tävling* (default): säsongsväljaren, KPI-korten *Försäljning · Kvitton · Snittköp · Bruttovinst efter varukostnad* (ur getKioskRevenueSummary.totals, märket *ofullständig täckning*), tabellen per tävling med betalsätten och raden *Utan tävling* sist, *Detaljer* till Z-tabellen i Kiosk och CSV per rad/hela perioden (exportKioskRevenueSummary → nedladdning). Fliken *Bokföring*: bokföringsläget (*Samlat verifikat per dagsavslut|Ett verifikat per kvitto*) och de sex kontona ur förbundets kontoplan med BILL:s standard (getKioskBookkeepingOptions, *Spara* med If-Match, skrivbart bara med kiosk:manage), och *Så bokfördes det senaste dagsavslutet* med BILL:s läge per händelse. Lagerkortet visar produkterna under tröskeln med länk till katalogens filter. *Tillstånd* (ägarbeslut) och *Volontärpass* (D-EVENTS) är ⊘. Kiosken fick samtidigt startkatalogens blad, den läs-only raden *Bokförs på …* i stängningen och Z-detaljens *Lagerjusteringar*. Vy-spec: specs/admin/views/buvette.md

  • F08.21.30 Planned

    Från tävlingen till buvettens intäkt (ae37902c.m-6.t-3) — tävlingsdetaljens rad *Tävlingens ytor* har en fjärde länk *Buvette — intäkt för tävlingen* (tavling-yta-buvette) till /buvette?flik=event&tavling=<id>: tabellen filtrerad på tävlingen och noden vald som tävlingens arrangör när aktören har den. Ingen mandatprövning i länken — /buvette prövar själv; i arrangörsappen står rutten som ej flyttad.

  • F08.21.31 Planned

    Kiosk-hälsa /halsa/kiosk (ae37902c.m-6.t-4) — navposten *Kiosk-hälsa* (🏪, bara med konsolåtkomst OCH sys.kiosk.read — sys_finance + sys_support; sys_engineer möter sys-kiosk-inget-mandat och servern nekar med 403). EN läsning (sysKioskGetOverview) per månad (?manad=ÅÅÅÅ-MM, default innevarande): KPI-raden *Tenanter med kiosk · Dagsavslut · Köp · Andel offline* (bara antal), tabellen med EN rad per tenant × valuta (volym netto/brutto/returer, kontant och via leverantör, dagsavslut och makulerade, betalhälsa och bokföringsflödet mot BILL som märken, synkfördröjning p50/max) — volymen summeras aldrig över valutor — och *Kassadiff-anomalier* (serverns severity som *Varning*/*Kritisk* med ikon + text, *»<n> % av kontantunderlaget«* ur ratio_bps i heltal, plattformens trösklar ur thresholds). Aggregat-only: inga köp, inga personer, ingen länk in i en tenants dagsavslut, inget skrivverb. Tre lägen + nekat; axe ljust + mörkt. Vy-spec specs/sys/views/kiosk-halsa.md; L4 999-dkiosk-m6-uppfoljning avsnitt F.

Tenant Billing Profile

F08.13
Planned

_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
Planned

_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
Planned

_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
In progress

_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
Planned

_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
Planned

`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=&quote=&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