fix(rot-rut): surface drop-out reasons in payout request dialog and keep selectors usable (#1884) (#1891)
* fix(rot-rut): surface drop-out reasons in payout request dialog and keep selectors usable (#1884) Four silent drop paths made a paid RUT invoice invisible in the begaran dialog (neither eligible nor blocked), and the empty list hid the year picker so the dialog looked dead: 1. deduction lines without a header deduction_total: a second line-based candidate query now finds them and they block as DEDUCTION_TOTAL_MISSING (also at file generation: the 1513 receivable was never booked). 2. partially_paid with the customer share settled: remaining_amount = 0 (total - paid_amount - deduction_total, migration 20260817191708) now counts as paid in evaluateInvoiceForFile; a genuine partial blocks as NOT_PAID with the outstanding amount. 3. NO_DEDUCTION_OF_TYPE is no longer filtered out of blocked: the message points at the other type, and the dialog's empty state adds a switch-type hint. 4. invoices held by a generated/submitted begaran block as ALREADY_REQUESTED naming the request; decided requests stay omitted (finished business, visible in the history list). The dialog keeps the year picker rendered when the list is empty (current year as inert fallback) and opens the blocked list by default when nothing is eligible. Fixes #1884 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(rot-rut): skeptic hardening: decided requests vanish on both tabs, customer share derived from header fields (#1884) Two skeptic refutations against the frozen PR head: 1. Regression: the wrong-type branch ran before the active-request lookup, so invoices of the OTHER type whose begaran was already decided resurfaced forever as NO_DEDUCTION_OF_TYPE in the opposite tab's blocked list, and the empty-state hint pointed at a tab where they never appear. The decided-request skip now runs first, on every tab. 2. Correctness: the paid gate and the NOT_PAID message trusted remaining_amount, but payment-sync's storno path recomputes it WITHOUT subtracting deduction_total, so the stored column can carry Skatteverkets 1513 share and the dialog could assert a wrong customer-outstanding figure. The gate now derives the customer share as total - paid_amount - deduction_total (the buildInvoiceWriteData / migration 20260817191708 formula) from fields every settlement path maintains. Tests pin both: decided+wrong-type omitted from both lists, corrupted remaining still classified and reported from the derived share. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(rot-rut): align CANDIDATE_STATUSES comment with the derived-share gate (#1884) The skeptic-hardening commit moved the paid gate off remaining_amount to the derived customer share (total - paid_amount - deduction_total); the comment still named remaining_amount as the signal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(rot-rut): explicit decided-status set + correction-path wording (#1884) Swedish accounting review findings on the candidate list: 1. The decided-begaran skip inferred 'decided' by exclusion (anything not generated/submitted), so a future request status would make an invoice vanish from both lists, exactly the silent drop the module forbids. DECIDED_REQUEST_STATUSES now names paid/partially_paid; any other status held by a request lands in blocked as ALREADY_REQUESTED with a generic message. Test pins it. 2. The DEDUCTION_TOTAL_MISSING message said only 'ratta fakturan', which could read as an invitation to edit a booked invoice directly. The invoice edit route already refuses sent/paid/booked invoices, and the message now names the sanctioned path: drafts edit directly, sent or paid invoices are corrected via credit note + new invoice. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
0bb482bf6e
commit
cbfb2201ff
@@ -1194,6 +1194,7 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
|
||||
[2026-08-24] Single-call chat console (general.help, AskConsole → /api/agent/ask) now carries the thread's earlier turns into every model call, via a new optional `history` on the provider-agnostic GenerateTextRequest (real message turns before the prompt in BOTH adapters: Anthropic-family messages array, OpenAI-compatible via AI SDK `messages`; an absent/empty history leaves the request byte-identical to the single-turn call, so hosted extraction and every other caller are untouched). The 08-20 RIP-3 cutover made each turn stateless (conversationId was only the tool actor id), so a follow-up in a resumed thread was answered blind (user report: "frågar vad jag refererar till"). History is loaded server-side from agent_messages (loadChatHistory: text only, hidden + tool rows dropped, alternation repaired, newest 16 rows / 10k chars) rather than sent by the client, so the client cannot forge earlier turns and old streaming threads replay cleanly. Rejected: inlining a transcript into the prompt (works everywhere but weaker turn semantics and blurs data vs instructions) and loading history in AskConsole (client-trusted history). Separately: the docked assistant panel now remembers its open thread per tab in sessionStorage (lib/agent-panel/session-restore) and reopens it after a full reload (the deploy prompt's "Ladda om" wiped it); sessionStorage, not user_preferences, because this is this-tab-this-session state that must not follow the user to other devices or tabs. And DeployReloadPrompt's full-width wrapper gets pointer-events-none: at z-[60] after the panel in DOM order it swallowed clicks on the panel's composer ("går ej att skriva").
|
||||
[2026-08-25] /reports/bank-reconciliation retired behind a redirect to /reconciliation instead of kept as a "power" page: everything it did (matcher, manual N:1 matching, residual booking, IB tag, move-to-account) lives on the account-keyed page, and two reconciliation surfaces meant two truths. The catalog slug stays so old links, the report library and ?autorun=1 deep links keep working.
|
||||
[2026-08-25] reconciliation_residual staged op tiered 'medium', not create_voucher's 'high': it books one typed verifikat (6570/8410/8310/3740 vs bank) bounded by RESIDUAL_MAX_AMOUNT and is undone by storno + unmatch, i.e. the same blast radius as categorize_transaction. Scope is transactions:write (same as the v1 route) because it writes the ledger.
|
||||
[2026-08-25] Rot/rut candidate list (#1884) treats a partially_paid invoice whose customer share is settled as claimable, instead of only surfacing it as blocked: the share outstanding is DERIVED as total - paid_amount - deduction_total (the buildInvoiceWriteData / migration 20260817191708 formula) rather than read off remaining_amount, because payment-sync's storno path recomputes remaining_amount without subtracting the deduction (skeptic-proven divergence), so the stored column is not a deterministic signal while the three header fields are maintained by every settlement path. Current settlement code flips such invoices to paid at exactly derived-share 0, and a legacy row stuck at partially_paid has NO user repair path (a 0-kr payment is rejected as overpayment), so blocked-with-reason would explain the dead end without opening it. The gate lives in evaluateInvoiceForFile so the list and file generation can never disagree. Decided (paid/partially_paid) begaran items are omitted from BOTH lists before any other classification, wrong-type included (skeptic-caught ordering hole): finished business, visible in the request history, and surfacing them would flood the list forever. DEDUCTION_TOTAL_MISSING (lines claim a deduction the header never recorded) blocks file generation too, not just the list: the 1513 receivable was never booked, so requesting the line amounts would claim money the ledger does not carry.
|
||||
[2026-08-25] Notification recipient lookup is a two-step query (lib/notifications/member-email), not the company_members -> profiles!inner(email) embed and not a new FK: company_members.user_id references auth.users, so PostgREST has no relationship to traverse and the embed 400'd, silently killing all four notification emails (kvittens, drift, backup, connection-expired) since they shipped. Adding an FK to profiles would be a migration on a core tenancy table for zero functional gain. Second lesson recorded: the drift path DID log the failure and nobody read it, so the guard is post-deploy delivery verification, not more logging.
|
||||
[2026-08-25] The skattekonto connection-expired email is deleted, not fixed: SKV's per-flow refresh tokens live 65 minutes, so per-consent-episode dedup means one "your connection expired" mail per connect, arriving an hour after every successful BankID login; that trains users to ignore mail. Rejected alternative (kept for revisit): fix + throttle to one mail per user per 7 days, fired only when a scheduled sync actually failed. Residual accepted knowingly: web-only users now have NO proactive channel for a dead SKV connection (banner needs a visit, briefing needs an agent, drift email needs a working sync); the agent briefing's skatteverket_connection block and the rewritten SKATTEVERKET_NOT_CONNECTED copy are the compensating surfaces. The skattekonto.connection.expired event and needs_reconsent flagging stay.
|
||||
[2026-08-25] SKATTEVERKET_NOT_CONNECTED stays one code for both never-connected and expired: splitting would ripple through every consumer (error-map, v1 routes, MCP dispatch, UI), and the declaration-status path already differentiates in its message. The copy is agent-directive on purpose (only a person can run BankID; do not retry until the user confirms) because the old English copy "Reconnect with BankID before retrying" invited agents to retry something only a human can fix.
|
||||
|
||||
Reference in New Issue
Block a user