* feat(reconciliation): scope bank reconciliation per cash account via transactions.cash_account_id A company with two same-currency cash accounts (e.g. checking 1930 + a savings account) saw every SEK transaction on every account, and the status card summed across both — reconciliation filtered transactions by CURRENCY while filtering GL lines by ACCOUNT (issue #604). Bind each bank transaction to the cash_accounts row it settled on: - New nullable transactions.cash_account_id FK (ON DELETE SET NULL — a bank transaction is räkenskapsinformation, BFL 7 kap, and must survive cash-account deletion) + a best-effort 4-pass backfill. - All reconciliation/transaction queries scope to the selected account with a NULL->currency fallback, so legacy/un-backfilled rows never disappear mid-backfill. - ingestTransactions stamps cash_account_id from the batch's settlementAccount; categorize + manualLink resolve and use it. - Bank leg now books to the transaction's actual settlement account via applySettlementAccount (no-op for 1930), so interest/fees on a savings/EUR account reconcile instead of mis-booking to 1930. - manualLink cross-checks the transaction's account and requires a voucher line on the selected account (no silent cross-account links). - BankReconciliationView: quick-book menu for any settlement account, in-flight request abort on account/date switch, 500-row truncation notice, per-account state reset. - pg-real coverage for the FK, all backfill passes, account-scoped query isolation, and cross-company isolation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(supplier-invoices): stop marking paid invoices and credit notes as overdue update_overdue_supplier_invoices() (the daily pg_cron job) flipped every past-due 'registered'/'approved' row to 'overdue' without looking at the outstanding balance. Credit notes — created 'registered', remaining 0, due today — got flipped the next day, surfacing as "Förfallen" with "kvar att betala 0 kr"; so did any fully-paid invoice left in 'registered'/'approved'. Guard the cron on remaining_amount > 0.005 (the "fully paid" threshold used by the payment/match paths) and is_credit_note = false, and backfill the rows already mis-flagged (credit notes -> 'registered', paid -> 'paid' with paid_at stamped only when missing). pg-real coverage for the guarded function and the one-off backfill. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(invoice-inbox): refresh dokumentinkorg on realtime row changes The InvoiceInboxWorkspace only refetched on mount and on explicit in-component actions. When an inbox item was resolved out of band — the in-app agent sheet committing a staged create_supplier_invoice_from_inbox / book-direct op, the /pending page approving one, or another tab booking it — none of those paths called fetchItems(), so the booked underlag stayed in "Att göra" until a manual reload (issue #600). Add invoice_inbox_items to the supabase_realtime publication (mirrors the /pending fix in 20260520120100) and subscribe in the workspace, refetching the whole list on any change so derived status/counts/ordering stay authoritative. RLS scopes the channel to the user's company. fetchItems now preserves optimistic upload placeholders so a refetch firing mid-upload can't drop an in-flight row. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(privacy): disclose EU AI inference via Amazon Bedrock (eu-north-1) Update the privacy policy and DPA to state that AI inference, when AI features are enabled, runs inside the EU via Amazon Bedrock (eu-north-1, Stockholm) using Anthropic's Claude models — no transfer to a third country, prompts not retained after the call or used for model training. Add AWS as a subprocessor row and refresh the "last updated" dates. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(migrations): rename invoice_inbox_realtime to avoid version collision main's #617 shipped 20260605120000_transactions_original_description.sql — the same version this branch used for the inbox-realtime publication. The Supabase migration tracker keys on the numeric version, not the filename, so the preview branch failed with a duplicate-key error on supabase_migrations.schema_migrations (version 20260605120000 already exists). Rename to the unique version 20260605120500; the body (ALTER PUBLICATION) is order-independent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(reconciliation): align run guard with status; harden filter interpolation Addresses PR review (greptile + compliance swarm): - The v1 and core bank/run routes rejected an unknown account uniformly, including the default '1930', while the status routes were lenient for '1930'. A company reconciling its primary SEK account without a cash_accounts row got 200 from status but 400 from run. Make run match status: '1930' falls back to currency-only scoping (cashAccountId undefined); non-default unknown accounts are still rejected. Adds a test. - /api/transactions accepts a user-supplied `currency` query param that was interpolated raw into a PostgREST .or() filter. Reject anything that isn't a 3-letter ISO code — RLS already scopes to the company, but an unsanitized value could otherwise malform/widen the filter. Assert currency/cashAccountId shape in scopeTransactionsToAccount as well. - categorize: log (instead of silently swallowing) a cash_accounts settlement-account lookup error, so a fall-back-to-1930 mis-booking is observable in the audit log. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(migrations): correct backfill UPDATE..FROM join; idempotent realtime publication Two SQL errors that only surface on real Postgres (CI pg-real + Supabase preview) — the unit suite mocks Supabase, so neither was caught locally. - Backfill pass (a): `UPDATE transactions t ... FROM journal_entry_lines jel JOIN cash_accounts ca ON ca.company_id = t.company_id` referenced the UPDATE target `t` inside the FROM join's ON clause, which Postgres rejects ("invalid reference to FROM-clause entry for table t"). Move the company match to WHERE; the JOIN now relates jel<->ca only. Semantics unchanged. - invoice_inbox_realtime: `ALTER PUBLICATION ... ADD TABLE` is not idempotent (SQLSTATE 42710 if the table is already a member). The earlier version-collision push partially applied it on the Supabase preview branch, so the re-apply errored. Guard with a pg_publication_tables existence check. Both statements validated against a real Postgres: the single-line tx binds, the two-bank-line transfer stays NULL, and the publication add runs twice cleanly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(migrations): backfill pass (c) uses array_agg, not min(uuid) Postgres has no min() aggregate for uuid, so pass (c)'s min(id) raised "function min(uuid) does not exist" on apply (CI pg-real + Supabase). The HAVING count(*) = 1 already guarantees one row per group, so (array_agg(id))[1] returns that single id. Validated the full backfill (all four passes) and the overdue migration against a real Postgres: every pass binds / falls through as intended, and the overdue guard + backfill produce the right statuses. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(compliance): add RoPA entry for Amazon Bedrock AI inference (GDPR Art.30) The privacy policy now discloses AI inference (transaction categorization + document/receipt OCR) via Amazon Bedrock as a processing activity, but .compliance/ropa.yaml had no matching Art.30 record. Add it: opt-in consent basis, EU-region (eu-north-1) inference with no third-country transfer, prompts not retained or used for model training. Mirrors the privacy-page disclosure shipped in this PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
280 lines
10 KiB
YAML
280 lines
10 KiB
YAML
# Record of Processing Activities (GDPR Art. 30)
|
||
#
|
||
# Each entry documents one processing activity. Field shape follows EDPB
|
||
# Guidelines 9/2022 §3 (controller-side RoPA): purposes, lawful basis,
|
||
# data subject + data category, recipients, transfer mechanism, retention,
|
||
# security measures. Add an entry whenever a new flow ships that touches
|
||
# personal data — onboarding, integrations, support, or regulator filings.
|
||
|
||
processing_activities:
|
||
|
||
- id: agi.submit
|
||
name: AGI inlämning till Skatteverket
|
||
purpose: >-
|
||
Lämna in lagstadgad arbetsgivardeklaration på individnivå (AGI) till
|
||
Skatteverket varje månad (Skatteförfarandelagen 26 kap.). Innefattar
|
||
huvuduppgift (HU) och individuppgifter (IU) per anställd.
|
||
lawful_basis: art_6_1_c # legal obligation
|
||
special_category_basis: null
|
||
controller: gnubok-tenant
|
||
processor: anthropic-na # software supplier; Skatteverket is recipient, not processor
|
||
data_subjects:
|
||
- employee
|
||
data_categories:
|
||
- user.government_id # personnummer
|
||
- user.name
|
||
- user.financial.employment # gross salary, tax withheld, benefits
|
||
- user.financial.tax # avgifter, sjuklönekostnad
|
||
recipients:
|
||
- name: Skatteverket
|
||
country: SE
|
||
role: legal_recipient
|
||
international_transfers:
|
||
applicable: false
|
||
mechanism: null
|
||
note: >-
|
||
Sweden-to-Sweden processor-to-public-authority flow. Adequacy decision
|
||
not applicable; no third-country transfer.
|
||
retention:
|
||
duration: 7y
|
||
basis: bfl_7_kap # BFL 7 kap. — räkenskapsinformation
|
||
stored_in:
|
||
- agi_declarations.xml_content
|
||
- agi_declarations.individuppgifter
|
||
- skatteverket_api_audit_log
|
||
security_measures:
|
||
- encryption_at_rest_supabase
|
||
- rls_company_scoped
|
||
- tls_1_3_to_skatteverket
|
||
- bankid_signing_required_for_filing
|
||
- immutable_audit_log
|
||
- personnummer_at_rest_encryption
|
||
|
||
- id: agi.kontrollera.preflight
|
||
name: AGI pre-flight kontrollera (HU/IU)
|
||
purpose: >-
|
||
Skicka enskild HU eller IU som JSON till Skatteverkets kontrollera-
|
||
ändpunkt (v1.7 §7/§8) för dry-run-validering av felmeddelanden och
|
||
STOPP-/AVVISANDE-status innan ett fullständigt underlag laddas upp.
|
||
Skatteverket lagrar inget från denna kontroll; svaret driver vår UI
|
||
som låter användaren rätta felaktigheter i lönekörningen lokalt.
|
||
lawful_basis: art_6_1_c # legal obligation (ancillary to AGI filing)
|
||
special_category_basis: null
|
||
controller: gnubok-tenant
|
||
processor: anthropic-na
|
||
data_subjects:
|
||
- employee
|
||
data_categories:
|
||
- user.government_id # personnummer (BetalningsmottagarId)
|
||
- user.financial.employment # gross salary, tax withheld, benefits
|
||
recipients:
|
||
- name: Skatteverket
|
||
country: SE
|
||
role: legal_recipient
|
||
international_transfers:
|
||
applicable: false
|
||
mechanism: null
|
||
note: Sweden-to-Sweden flow; no third-country transfer.
|
||
retention:
|
||
# Skatteverket retains nothing for kontrollera calls. Our outbound
|
||
# audit row in skatteverket_api_audit_log records who-called-what-when
|
||
# but NOT the payload body, minimising at-rest footprint.
|
||
duration: 7y
|
||
basis: bfl_7_kap
|
||
stored_in:
|
||
- skatteverket_api_audit_log
|
||
security_measures:
|
||
- strict_zod_allow_list_input_validation
|
||
- request_size_cap_64kb
|
||
- rbac_company_member_role_check
|
||
- rls_company_scoped
|
||
- tls_1_3_to_skatteverket
|
||
- immutable_audit_log
|
||
- no_payload_body_persisted
|
||
|
||
- id: moms.declaration
|
||
name: Momsdeklaration till Skatteverket
|
||
purpose: >-
|
||
Lämna in lagstadgad momsdeklaration (utkast, lås och signering) till
|
||
Skatteverkets momsdeklaration-API (Mervärdesskattelagen 17 kap. 24 §).
|
||
lawful_basis: art_6_1_c
|
||
special_category_basis: null
|
||
controller: gnubok-tenant
|
||
processor: anthropic-na
|
||
data_subjects:
|
||
- business_owner
|
||
data_categories:
|
||
- user.government_id # orgnummer (juridiska personer) eller pnr
|
||
- user.financial.tax # rutor 05–62
|
||
recipients:
|
||
- name: Skatteverket
|
||
country: SE
|
||
role: legal_recipient
|
||
international_transfers:
|
||
applicable: false
|
||
mechanism: null
|
||
note: Sweden-to-Sweden flow; no third-country transfer.
|
||
retention:
|
||
duration: 7y
|
||
basis: bfl_7_kap
|
||
stored_in:
|
||
- extension_data (utkast metadata)
|
||
- skatteverket_api_audit_log
|
||
security_measures:
|
||
- rls_company_scoped
|
||
- tls_1_3_to_skatteverket
|
||
- bankid_signing_required_for_filing
|
||
- immutable_audit_log
|
||
|
||
- id: psd2.cash_account_mirror
|
||
name: PSD2-konton speglas till cash_accounts
|
||
purpose: >-
|
||
När en bank-anslutning via Enable Banking returnerar kontolista efter
|
||
lyckad PSD2-consent kopieras kontometadata (IBAN, valuta, kontonamn,
|
||
external_uid) till cash_accounts så att avstämning, motkontoresolver
|
||
och __PRIMARY_SEK__-sentinel kan rutta verifikat utan att läsa JSONB
|
||
från bank_connections.accounts_data vid varje fråga.
|
||
lawful_basis: art_6_1_b # contract (PSD2 consent + bookkeeping service)
|
||
special_category_basis: null
|
||
controller: gnubok-tenant
|
||
processor: anthropic-na
|
||
data_subjects:
|
||
- business_owner
|
||
data_categories:
|
||
- user.financial.bank_account # IBAN
|
||
- user.contact # account name (kontotitel)
|
||
recipients:
|
||
- name: Supabase
|
||
country: EU
|
||
role: processor
|
||
international_transfers:
|
||
applicable: false
|
||
mechanism: null
|
||
note: EU-only processor; no third-country transfer.
|
||
retention:
|
||
duration: consent_lifetime
|
||
basis: psd2_consent
|
||
stored_in:
|
||
- cash_accounts
|
||
- bank_connections.accounts_data
|
||
security_measures:
|
||
- rls_company_scoped
|
||
- data_minimization_no_balance_on_callback
|
||
- failure_emitted_to_event_log
|
||
|
||
- id: transactions.counterparty_iban
|
||
name: Motpartens IBAN på transaktioner
|
||
purpose: >-
|
||
Spara motpartens IBAN på transaktionsraden så att own-account-detector
|
||
kan identifiera överföringar mellan företagets egna konton (1930 → 1932
|
||
etc.) och bokföra båda benen automatiskt istället för att felbokföra
|
||
utflödet som extern kostnad. Krävs även för payment-matchning mot
|
||
leverantörsfakturor.
|
||
lawful_basis: art_6_1_b # contract (bookkeeping service)
|
||
special_category_basis: null
|
||
controller: gnubok-tenant
|
||
processor: anthropic-na
|
||
data_subjects:
|
||
- business_owner
|
||
- counterparty
|
||
data_categories:
|
||
- user.financial.bank_account # counterparty IBAN
|
||
recipients:
|
||
- name: Supabase
|
||
country: EU
|
||
role: processor
|
||
international_transfers:
|
||
applicable: false
|
||
mechanism: null
|
||
note: EU-only processor.
|
||
retention:
|
||
duration: 7y
|
||
basis: bfl_7_kap
|
||
stored_in:
|
||
- transactions.counterparty_iban
|
||
security_measures:
|
||
- rls_company_scoped
|
||
- immutable_after_post
|
||
|
||
- id: skattekonto.drift_alert
|
||
name: Skattekonto-drift via e-post
|
||
purpose: >-
|
||
Underrätta företagets kontaktadress när det cachade Skatteverket-saldot
|
||
avviker från GL 1630 utöver konfigurerad tolerans (> 1 SEK), så
|
||
bokföraren kan granska skattekonto-raderna. E-postmeddelandet
|
||
innehåller ingen finansiell siffra utan en länk till autentiserad
|
||
dashboard; mottagaren valideras mot company_members innan utskick.
|
||
lawful_basis: art_6_1_f # legitimate interest (bookkeeping accuracy)
|
||
special_category_basis: null
|
||
controller: gnubok-tenant
|
||
processor: resend
|
||
data_subjects:
|
||
- business_owner
|
||
- company_member
|
||
data_categories:
|
||
- user.contact.email
|
||
recipients:
|
||
- name: Resend
|
||
country: US
|
||
role: processor
|
||
international_transfers:
|
||
applicable: true
|
||
mechanism: scc_2021_c2p
|
||
note: >-
|
||
Resend (US) — SCC Module 2 (controller-to-processor). Outbound
|
||
payload limited to ett notifieringsmail utan finansiella belopp;
|
||
TIA dokumenterad i .compliance/tia/resend.md.
|
||
retention:
|
||
duration: 30d
|
||
basis: event_log_ttl
|
||
stored_in:
|
||
- event_log
|
||
security_measures:
|
||
- recipient_membership_check_before_send
|
||
- no_financial_figures_in_body
|
||
- tls_to_resend
|
||
- rls_company_scoped
|
||
|
||
- id: ai.inference
|
||
name: AI-inferens (kategorisering + dokumenttolkning) via Amazon Bedrock
|
||
purpose: >-
|
||
Föreslå bokföringskategori för banktransaktioner och tolka/extrahera
|
||
uppladdade underlag (kvitton, leverantörsfakturor) med Anthropic
|
||
Claude-modeller körda i Amazon Bedrock. Endast aktiv när tenanten
|
||
uttryckligen aktiverat AI-funktioner — kärntjänsten (bokföring, fakturor,
|
||
moms, rapporter) fungerar fullt ut utan AI.
|
||
lawful_basis: art_6_1_a # consent — opt-in, AI features off by default
|
||
special_category_basis: null
|
||
controller: gnubok-tenant
|
||
processor: aws-bedrock
|
||
data_subjects:
|
||
- business_owner
|
||
- counterparty # namn/belopp på uppladdade underlag
|
||
data_categories:
|
||
- user.financial # transaktionsdata skickad för kategorisering
|
||
- user.document # uppladdade kvitton/fakturor för OCR/extraktion
|
||
recipients:
|
||
- name: Amazon Web Services (Amazon Bedrock)
|
||
country: EU
|
||
role: processor
|
||
international_transfers:
|
||
applicable: false
|
||
mechanism: null
|
||
note: >-
|
||
Inferens körs i AWS-regionen eu-north-1 (Stockholm) — ingen överföring
|
||
till tredje land. AWS DPA + SCC + DPF-certifiering finns som
|
||
skyddsmekanism. Prompter lagras ej hos Bedrock efter anropet och används
|
||
ej till modellträning.
|
||
retention:
|
||
duration: none_at_processor
|
||
basis: no_retention_bedrock # prompt not persisted by Bedrock post-inference
|
||
stored_in:
|
||
- ai_usage_tracking # usage metadata only (tokens/cost), no payload body
|
||
security_measures:
|
||
- opt_in_consent_required
|
||
- eu_region_inference_eu_north_1
|
||
- prompts_not_retained_after_inference
|
||
- not_used_for_model_training
|
||
- rls_company_scoped
|
||
- tls_to_bedrock
|