Add/stripe connect transactions (#1139)
* fix(mcp-oauth): allow ChatGPT connector callbacks and resume OAuth after login Add chatgpt.com/connector/oauth/* (per-instance) and the legacy chatgpt.com/connector_platform_oauth_redirect to the built-in OAuth redirect allowlist so ChatGPT MCP connectors can register and authorize. Fix the login page dropping the ?next= destination: an OAuth-initiated visit that required login previously ended on the dashboard and the connection flow silently died. Login now resumes to the sanitized next path (hard navigation, since the consent page is route-handler HTML), carries it through the MFA step-up as returnTo, and /mfa/verify hard-navigates for /api/ destinations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(transactions): dedup incoming feed rows against booked hand-entered twins Users who bookkeep via MCP/chat first and connect their bank afterwards got the same movement twice: the synced row's external_id lives in a different namespace, the free-form manual title never text-bridges the bank's raw string, and the cross-channel mirror deliberately excluded manual/mcp rows. Extend the mirror with a booked-hand-entered track: an incoming feed row is skipped when a BOOKED manual/mcp row shares its (date, ore) bucket count- symmetrically. Gates beyond the feed-vs-feed mirror: stored row must be booked (staged rows never consume an import), currencies must not contradict (bucket key is date+ore only), the cash-account guard applies to the count exactly as to consumption, and symmetry uses the Layer-1-unmatched incoming count so an already-stored row cannot inflate it. Consumption stamps the batch cash_account_id onto an account-unbound hand row, so one hand row can never consume feed rows on other accounts in later syncs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(bookkeeping): inline verifikat rattelse (strike lines + text/date edit) Second sanctioned correction track under BFL 5 kap 5/9 pp, Fortnox-style: strike lines inside a posted verifikat with replacements in the same voucher, and correct description/entry_date without an andringsverifikat. Envelope: posted entries, open unlocked periods, company lock date, same-period date moves, structural/FX/doc-linked lines excluded, and a reconciliation guard preserving per-account net on bank/reskontra sides of externally linked entries. Every rattelse writes an immutable who/when row (journal_entry_rattelse_log, WORM, archived as rakenskapsinformation) and struck originals render struck-through in the verifikat; list rows and the detail header carry a Rattad marker. CLAUDE.md hard rule 1 and the swedish-accounting-compliance skill are amended to state the two-track rule. Staging carries the DDL; prod gets it on merge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: live saldo in booking form, prior-year window comparison, hideable assistant FAB - Manual journal entry: saldo column now shows before -> after computed from the typed debit/credit amounts (direction feedback while booking) - Resultatrapport: a narrowed date range now compares against the same window shifted one year back (#862), merged across fiscal periods for brutet rakenskapsar; P&L rows report window activity instead of rolled-forward YTD closing - Assistant FAB: per-user hide toggle (user_preferences.hide_assistant_fab, settings > assistant), sidebar entry unaffected; collapsed sessions keep their reopen handle Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(stripe): sync balance transactions as a bank feed on 1686 Import the connected Stripe balance into the transactions inbox, opt-in per connection (transaction_sync_enabled on stripe_connections): - Balance transactions map to feed rows with the two-row gross+fee split and frozen external_id formats (stripe_{acct}_{txn} / _fee), dated on created, bound to a provisioned "Stripe-saldo" cash account on 1686 so booking settles against the clearing account by construction. - Double-booking protection: settled payment-link charges import pre-linked to their settlement entry; payout rows import pre-linked to the payout entry; processPayoutPaidEvent claims the payout's fee rows at booking time (linkPayoutFeedRows, idempotent from both directions). - Cursor last_balance_txn_synced_at with 24h overlap; first run backfills 90 days floored at the day after the company lock date. - Nightly cron /api/extensions/stripe/transactions/cron (03:30), transaction-sync toggle route, "Synka nu" covers both feeds, settings panel toggle with last-synced/backfill note, sv+en strings. - Migration 20260723200000 (applied to staging). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(transactions): offer match-to-voucher on unbooked history rows Unbooked transactions with is_business already set (e.g. left behind when a voucher was removed without a full uncategorize) land in the history list instead of the inbox, where the match-against-existing-voucher action did not exist, leaving them with no path back to voucher matching. Add the same menu item to the history list for unbooked rows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(transactions): enhance ownership checks and error handling in journal entry routes --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
be9d630347
commit
d840257c0c
+10
-1
@@ -331,6 +331,15 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
|
||||
[2026-07-23] Removed the accountingMethod parameter from calculateVatDeclaration (and the dead company_settings.accounting_method reads in the xlsx/pdf/eskd routes) instead of restoring the settings read: the value was verifiably unused (declared _accountingMethod, zero body references) because the method is baked into journal entry timing, and keeping an ignored parameter invites a future branch that silently sees the caller's hard-coded 'accrual'; the v1 API still accepts accounting_method for wire compat but its docs now state it has no effect.
|
||||
[2026-07-23] Closed the mark_entry_as_opening_balance TOCTOU (link committing between the RPC's EXISTS check and commit) with a transactions-side trigger (20260723190000, FOR KEY SHARE on journal_entries) instead of a shared advisory lock in every linking code path: the trigger enforces the invariant from both directions in one place, needs no app-code changes, and FOR KEY SHARE conflicts with exactly the RPC's FOR UPDATE and nothing weaker; prod verified to have zero pre-existing violating rows.
|
||||
[2026-07-23] Declined the suggested composite index (company_id, is_active, account_class, sort_order, id) on chart_of_accounts for list_company_accounts: the RPC exists to eliminate cross-region HTTP round trips, per-company row counts (p95 ~1250) make the filter+sort a few ms via the existing company_id index, and a 5-column index taxes every account write for no user-visible gain.
|
||||
[2026-07-23] Stripe transaction sync provisions a real cash_accounts row on ledger 1686 ("Stripe-saldo") instead of importing balance transactions as accountless inbox rows: booking/categorize/match routes resolve the settlement leg from cash_account_id -> ledger_account, so binding to 1686 makes user booking settle against the clearing account by construction, gives the dedup account guard a scope, and lets the feed reconcile against the actual Stripe balance.
|
||||
[2026-07-23] Stripe feed rows use the two-row gross+fee split with fee rows claimed by the payout booking (linkPayoutFeedRows at processPayoutPaidEvent time AND from the sync for already-booked payouts) rather than holding fee rows back until payout: the feed stays complete intra-week; the short window where a user could manually book a fee row before its payout is accepted and idempotently guarded (only journal_entry_id IS NULL rows are claimed). Payout rows import pre-linked instead of being skipped so the feed sums to Stripe's real balance movements.
|
||||
[2026-07-23] Stripe feed row dates use balance_transaction.created, not available_on: created is the economic event (payment date) that booking, invoice matching, and month boundaries need; available_on is Stripe's internal settlement schedule days later. First-run backfill is 90 days floored at the day after bookkeeping_locked_through: rows behind the lock can never be booked and would be permanent inbox noise.
|
||||
[2026-07-23] The transaction-sync toggle lives on stripe_connections (transaction_sync_enabled), not company_settings: the cursor lives there, the crons iterate connections, and a future second connected account wants independent toggles.
|
||||
[2026-07-23] Import dedup extended with a booked-hand-entered mirror (lib/transactions/ingest.ts): an incoming feed row is skipped when a BOOKED manual/mcp row shares its (date, ore) bucket count-symmetrically, gated on currency + cash-account compatibility; consumption stamps the batch cash_account_id onto a null-account hand row (one-consume-ever across accounts). Chosen over (a) strict account match, which misses the real MCP-then-connect-bank case (mcp rows have null cash_account_id), and (b) external_id adoption, because agents supply their own external_ids (prod: TRANSFER-... on mcp rows) that must not be overwritten. Unbooked hand rows stay excluded: staged intent is not ledger evidence. Symmetry uses the Layer-1-unmatched incoming count and the count map applies the same guards as consumption (adversarial-review findings).
|
||||
[2026-07-23] Inline verifikat rattelse (founder-approved both cards): built Fortnox-style strike-and-replace inside posted verifikat + metadata (text/date) edit WITHOUT andringsverifikat, as a second sanctioned correction track under BFL 5 kap 5/9 pp. One SECURITY DEFINER RPC per operation (correct_entry_metadata, correct_entry_lines_inline), transaction-local GUC carve-outs in the immutability triggers (precedent: notes edit, source_type retag, dimension retag), append-only WORM journal_entry_rattelse_log holding full struck-line snapshots (classified as rakenskapsinformation in full-archive-export, NOT excluded like dimension_retag_log). Envelope: posted entries, open+unlocked periods, company lock date, same-fiscal-period date moves only, structural source types excluded (storno/opening_balance/year_end/vat_settlement), FX lines and doc-attached lines excluded (storno path), reconciliation guard = per-account net on 19xx/cash_accounts (bank-linked), 15xx (invoice_payments-linked), 24xx (supplier-linked) must be preserved. CLAUDE.md hard rule 1 + swedish-accounting-compliance skill amended in the same change to state the two-track rule (adversarial reviewer: feature was otherwise condemned by its own repo docs).
|
||||
[2026-07-23] Inline rattelse deliberate accepts from the adversarial pass: (a) direct INSERT into journal_entry_lines on posted entries is unguarded platform-wide (pre-existing; blocking it blind risks breaking match_batch_allocate/bulk_book SQL RPC booking flows, needs its own audited change) - FLAGGED FOLLOW-UP; (b) strike racing an in-flight multi-statement storno can produce a stale-mirror storno (storno-service holds no row lock until its final CAS) - rare, pre-existing race class, follow-up = FOR UPDATE in storno-service step 0; (c) RPCs granted to authenticated bypass the withRouteContext MFA gate like every existing RPC (retag precedent) - platform-level posture; (d) replacement lines never carry tax_code (nothing reads journal_entry_lines.tax_code in reports today); (e) VAT-declared months are gated by dialog copy + the existing period-lock culture, same as the storno flow - follow-up: banner on VAT report for post-declaration rattelser; (f) rattelse-log WORM trigger blocks cascaded company hard-deletes (shared latent defect with dimension_retag_log, test-company cleanup only).
|
||||
[2026-07-23] Resultatrapport signedAmount moved from closing_* to period_*: trial balance rolls pre-window activity into opening, so closing on P&L rows silently showed YTD for month/quarter windows; period activity is the correct windowed value and equals closing in the full-period case. Enables same-window prior-year comparison (#862).
|
||||
[2026-07-23] Assistant FAB is now user-hideable (user_preferences.hide_assistant_fab, toggle under Installningar > Assistenten): deliberately overrides the keep-visible-as-conversion-surface design for users who opt out; a collapsed chat session keeps its reopen handle even when hidden.
|
||||
[2026-07-21] Floden & agenter concept (dev_docs/flows_agents_concept.md + interactive artifact): founder locked hybrid builder (chat builds, linear flow view edits; no free node canvas), trust-ladder autonomy on pending_operations risk tiers, per-company before byra, ships alongside Assistenten. Engine recommendation is a hand-rolled Postgres state machine in lib/flows/ generalizing the webhook-dispatcher pattern; every external engine (Inngest/Trigger/Temporal/Vercel Workflow/Restate/DBOS/pg-boss) fails zero-extra-services self-host, Supabase-only durability, AGPL compatibility, or free days-long approval pauses. Flows compile deterministically at build time; LLM steps are explicit, marked step types. Awaiting founder visual sign-off before any implementation PR.
|
||||
[2026-07-21] Official Claude Code plugin ships in-tree (claude-plugin/ + root .claude-plugin/marketplace.json), not in the claude-for-swedish-small-business marketplace repo: the wrapper-reference sync test must live next to the MCP server source, and the official plugin belongs with the product. Wrappers are thin by design: regulatory knowledge stays server-side in agent_atom_registry and is loaded via gnubok_load_skill, never bundled. Plugin licensed MIT matching the gnubok-mcp precedent.
|
||||
[2026-07-22] Swish inflows via Swishrapport file import (#1114), not Swish API: the Commerce API cannot list incoming payments (only API-created ones) and prod data shows banks strip payer/message from PSD2 remittance info.
|
||||
@@ -339,4 +348,4 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
|
||||
[2026-07-23] Frame layout (UI-migration PR 1) is md:-gated and the panel owns desktop scroll: mobile keeps document flow + bottom nav (concept is desktop-first), and since Next's window scroll-to-top never fires for an inner scroll container, MainContainer resets panel scroll on route change. Button default size drops fixed h-10 for natural pill height (7px/16px padding per locked convention 3); sm/lg/icon keep their heights.
|
||||
[2026-07-23] Nav PR 2: ui_state persisted as one jsonb bag on user_preferences (founder-approved migration 20260723120000) rather than per-preference columns: cosmetic, never load-bearing, grows with PR 3/4 split-button modes. Sidebar width driven by an inline --nav-w CSS variable on #dash-shell instead of a [data-nav-collapsed] attribute rule: the Tailwind 4/Lightning CSS pipeline silently dropped the top-level attribute-selector rule from compiled output, and the inline variable is pipeline-proof. Register/Bokslut folds default closed (concept tidiness), forced open by an active child route. Discord-community row skipped: no invite URL exists in the repo; add when one lands.
|
||||
[2026-07-23] PR 3 primitives: new --attn token pair (38 50% 34% light / 38 45% 62% dark) for the one-sentence AttnLine instead of reusing --warning: the warning tone fails WCAG AA as 12.5px body text on the page background; chips/charts keep --warning. Stagger applied via className on the five target pages' DataList/TableBody containers (plan item 7), not baked into the DataList primitive: remaining pages adopt it in their own migration PRs where their skeletons are aligned at the same time.
|
||||
[2026-07-23] PR 4 template booking books directly through the normal journal-entries endpoint without the editor's account-activation retry flow: template accounts are standard BAS accounts that are active in practice, and an inactive-account error surfaces as a normal error toast pointing at the full editor. Voucher preview in the commit ConfirmDialog is indicative (voucher-sequences/next for today's period/default series); the atomic number assigned at commit is what the success toast shows.
|
||||
[2026-07-23] PR 4 template booking books directly through the normal journal-entries endpoint without the editor's account-activation retry flow: template accounts are standard BAS accounts that are active in practice, and an inactive-account error surfaces as a normal error toast pointing at the full editor. Voucher preview in the commit ConfirmDialog is indicative (voucher-sequences/next for today's period/default series); the atomic number assigned at commit is what the success toast shows.
|
||||
Reference in New Issue
Block a user