15af0cd7eadcecafd6d17618d04bf67b4f736fd2
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
32d9978f1b |
Fix/chrome pdf preview csp (#572)
* feat: add option to exclude year-end closing entries in SIE export and related reports * delete docs * fix: allow Chrome's PDF viewer in verifikat document preview The /api/documents/:id/inline route shipped with `object-src 'none'` in its CSP, which blocked Chrome's built-in PDF viewer (it renders inline PDFs via an internal <embed>). Users on Chrome saw "Det här innehållet har blockerats" when expanding a PDF attachment in the bookkeeping view; Firefox (PDF.js) and Edge (own viewer) were unaffected, and JPGs worked because <img> isn't subject to object-src. Drops the CSP for this route to the minimum needed for embeddability: `frame-ancestors 'self'`. X-Content-Type-Options: nosniff plus the fixed Content-Type from the handler already block MIME confusion; X-Frame-Options: SAMEORIGIN + frame-ancestors still block clickjacking. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(auth): add webmail deep link to email confirmation screens Mirrors Stripe's signup UX: after asking the user to verify their email, detect their webmail provider from the domain and show a button that opens the inbox in a new tab. Gmail gets a from:<sender> search pre-populated; Outlook/Yahoo/iCloud/Proton open the inbox directly. Unknown / custom domains fall back to the existing copy. Sender address is configurable via NEXT_PUBLIC_BRANDING_AUTH_EMAIL_FROM (default noreply@gnubok.se) so white-label installs can match their Supabase Auth SMTP config. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(auth): unblock first-time password set for BankID users with MFA Supabase rejects updateUser({password}) and mfa.unenroll with "AAL2 session is required" whenever a TOTP factor is enrolled. BankID magic-link logins produce AAL1, and middleware skips MFA enforcement for bankid_linked users, so they had no path to AAL2 — leaving them unable to set a backup password or disable MFA without going through the email-recovery escape hatch. - /api/account/password: branch on app_metadata.has_password. First-time set writes via service.auth.admin.updateUserById (no existing credential to protect, AAL2 guard does not apply). Change-password keeps the user-session updateUser so AAL2 still fires for credential rotation. - /mfa/verify: accept a safeReturnTo query param and route there after successful verify, so step-up flows can land back where they came from. - SecuritySettings: detect the AAL2 error from both change-password and mfa.unenroll and redirect through /mfa/verify?returnTo=/settings/account instead of toasting a dead-end error. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add tests and rounding utility for öre precision in bokslut calculations - Implemented `roundOre` function for rounding SEK amounts to two decimal places, ensuring consistent monetary calculations. - Introduced `ORE_TOLERANCE` constant for comparing rounded amounts, facilitating invariant checks in financial entries. - Created comprehensive tests for `roundOre`, covering typical cases, edge cases, and idempotency. - Added year-end invariants tests to verify database-level guarantees for closing entries, ensuring they balance to the öre and reject discrepancies. - Developed end-to-end tests for the dispositions chain, validating the correctness of calculations across various scenarios. * fix: update PDF rendering to remove Swish QR code generation and set default to disable Swish visibility * fix: enhance security by rejecting data URIs in safeReturnTo function tests * fix: improve rounding logic in roundOre function and add customer_type migration * fix: add customer_type column to customers and enforce CHECK constraint --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
fd4274787c |
feat(bokslut): Phases 4-7 — accruals + årsredovisning + EF + MCP tools (#509)
* feat(bokslut): Phases 4-7 — accruals + årsredovisning + EF + MCP tools Builds on the Phase 1-3 PR (#508). Same K2-AB scope decisions hold (iXBRL deferred, K3 deferred, koncernredovisning out of scope) — what ships: PHASE 4 — Periodiseringar - lib/bokslut/accruals/accrual-detector.ts: auto-proposes the vacation- liability change (delta on 2920 against 7090 + 31,42 % avgifter on 7519/2940) by reading lib/reports/vacation-liability.ts. Manual prepaid (17xx) / accrued (29xx) / audit-fee builders for entries the heuristic can't derive — supplier-invoice service_period detection is deferred until the data model grows the field. - /api/bookkeeping/fiscal-periods/[id]/accruals (GET + POST) — posts each accrual as a separate manual-source journal entry with the next-day reverse date embedded in the description. - New AccrualsStep wizard step between Preflight and Dispositions. - Auto-reversal cron is follow-up infra; for now reverses_on is metadata + a visible UI badge. PHASE 5 — Årsredovisning PDF + signing - lib/bokslut/arsredovisning/{types,build-data,arsredovisning-pdf,signature -service}: pre-fills flerårsöversikt from prior 3 fiscal periods, eget- kapital-förändring from journal data, K2-minimum noter with avskrivningstider auto-derived from the asset register and medelantal anställda from the employees table. PDF via @react-pdf/renderer mirroring the income-statement/pdf pattern. - Migration 20260516170000: arsredovisning_signature_requests with RLS, signed-immutability trigger, and DELETE policy that blocks signed rows. Signature-service exposes list/create/markSigned/isFullySignedOff. - BankID call itself is not wired here — the table + service make the request layer available so a follow-up can hook lib/auth/bankid.ts to the sign action without rework. - /api/.../arsredovisning (data + pdf + signatures) endpoints. - /bookkeeping/year-end/arsredovisning page with editable narrative, flerårsöversikt table, signer slots, PDF download, and Bolagsverket Mina Sidor link. Explicit warning about the FY2026 iXBRL mandate. PHASE 6 — Enskild firma NE-bilaga UI - lib/bokslut/enskild-firma/{egenavgifter,rantefordelning,periodiseringsfond -ef,expansionsfond}-calculator.ts. All declaration-only — never produce a journal entry. Egenavgifter 28,97 % / pensionärssats 10,21 % / passive SLP 24,26 %, schablonavdrag 25/10/20 %. Räntefördelning SLR+6 / SLR+1 with the -500 000 negative threshold. P-fond EF cap 30 % (vs 25 % för AB). Expansionsfond 125,94 % av kapitalunderlag, 20,6 % skatt. - EfDeclarationSection mounted inside DispositionsStep when entity_type is enskild_firma — live recompute as the user adjusts kapitalunderlag, prior-year amounts, p-fond desired, expansionsfond change. Each card shows the NE-bilaga ruta the number lands in. NE-bilaga preview link. PHASE 7 — Agent-native MCP tools - gnubok_propose_dispositioner: read-only AB dispositions proposal - gnubok_propose_accruals: read-only accruals proposal - gnubok_propose_annual_depreciation: read-only depreciation per asset - gnubok_post_annual_depreciation: stages depreciation commit (high-risk) - gnubok_preview_arsredovisning: structured K2 ÅR preview - gnubok_preview_ef_declaration: EF skattemässiga justeringar preview - All ≤280-char descriptions, additionalProperties:false, conform to STAGED_OPERATION_SCHEMA for write tools. Read tools return the same shapes as their HTTP counterparts so agents and the UI share a contract. - The existing GET /bokslutsdispositioner endpoint now calls the shared buildDispositionsProposal helper that the MCP tool also uses, removing the duplicate logic that lived in both. Verification - 149 unit tests pass (was 125 on Phase 1-3; +24 across accruals, EF calculators, expansionsfond, periodiseringsfond-ef) - Zero lint or typecheck errors on any new file - Migration 20260516170000 applied to remote Supabase via MCP Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(bokslut): address PR #509 round-1 — CI blocker + 3 P1s + compliance bundle CI BLOCKER (output-schema test) - The 5 new read-only MCP tools (gnubok_propose_dispositioner / _accruals / _annual_depreciation, _preview_arsredovisning / _ef_declaration) were missing outputSchema, which the existing strict-schemas guard reads as a hard failure on core-only CI. Added a permissive { type: 'object', additionalProperties: true } outputSchema to each — the return shapes are the same as their HTTP counterparts and trying to mirror them inline would duplicate the type tree across two boundaries. P1 — vacation accrual: two real bugs in one entry - Delta was anchored on the OPENING balance of 2920, so any mid-year postings (partial accruals, reversals) were ignored. Now anchors on the current closing balance via tb.rows.find(2920).closing_credit - closing_debit. Updated the computation field label to current_2920. - More importantly: 2920 is a balance-sheet carry-forward (semesterlöneskuld persists until the actual vacation is paid). The original implementation set reverses_on to Jan 1 of the next year, which would zero the liability on day 1 of the new year — a known Swedish bookkeeping error. The vacation proposal now ships with reverses_on = '' to suppress the reversal badge, the API route emits a "Bokslutsjustering" description instead of "Periodisering (vänds …)", and AccrualsStep renders "Rullas vidare (ingen vändning)" so the user knows the liability carries forward. P1 — signature_requests immutability gap (security) - The existing trigger only guarded role / signer_name / signed_at / status on signed rows, leaving bankid_signature_data, signer_personnummer_encrypted, and signer_personnummer_hash mutable. An UPDATE on a signed row could silently alter the BankID proof. New migration 20260517090000 replaces the trigger function to cover the full audit-critical column set (plus fiscal_period_id and company_id as belt-and-braces). P1 — narrative edits never reached the PDF - The /bookkeeping/year-end/arsredovisning page let the user edit description / important_events / resultatdisposition but the download link pointed at a plain GET that regenerated boilerplate. Wired the PDF endpoint to accept description / events / disposition as query params (length-capped) and the page now constructs the download URL with the current narrative state — only fields the user actually changed are included, keeping the URL short for the unchanged-defaults case. Compliance quick wins - Added period_lock check to gnubok_post_annual_depreciation MCP tool (matches the existing accruals POST guard). - Added explicit fiscal-period ownership pre-check to the signatures POST route (RLS would reject anyway; the route layer just makes the 404 envelope cleaner). - Replaced free-text role on the signatures schema with an enum allowing only Styrelseledamot / Styrelseordförande / VD / Verkställande direktör. - Added Cache-Control: no-store + Pragma: no-cache to the ÅR PDF response so the document (officer names + financials = personal data) isn't cached by any intermediary. - Sanitized period_end in the PDF Content-Disposition header to dodge header-injection via stray chars (defensive — period_end is a date, but the cost is one regex). - Softened the iXBRL warning text on the ÅR page: digital filing is proposed by Bolagsverket but not yet enacted; PDF is still valid today. False positives I'm intentionally not chasing on this round - Greptile P2 `sate` typo in the address city field — the rename would touch the type and every consumer; defer. - Greptile P2 "computation field label" — already addressed as part of the P1 vacation fix above. - Compliance V2.2 "silent skip" in accruals POST — the silent skip is in the UI's empty-row filter, not in the server route. Server validation already returns 400 via Zod for any invalid item. Verification - 154 unit tests pass (was 149; +5 from re-running the MCP strict-schemas + output-schema suites that now include the new tools) - Zero typecheck errors on any touched file - Zero lint errors on any touched file - Migration 20260517090000 applied to remote Supabase via MCP Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(bokslut): address PR #509 round-2 — 3 real bugs + 5-item polish 3 real bugs from the round-1 Swedish review + Compliance Swarm re-eval: - Soliditet inflated by obeskattade reserver. build-data.ts filtered equity with startsWith('20') || startsWith('21') — but 21xx (periodiseringsfonder, överavskrivningar) are partially deferred tax, not eget kapital. Splitting them out per K2 / ÅRL. Filter is now startsWith('20') only. - Resultaträkning omitted bokslutsdispositioner + skatt rows. K2 RR must include 88xx (dispositioner) and 89xx (skatt) before "Årets resultat" per ÅRL 3:2 — without them, the printed RR doesn't reconcile to BS 2099 and the document is non-compliant for any AB that posted bolagsskatt or periodiseringsfond. flattenIncomeStatement now splits the financial sections on title (Bokslutsdispositioner / Skatter och årets resultat) and emits the K2-required intermediate subtotals: "Resultat efter finansiella poster" → dispositioner → "Resultat före skatt" → skatt → "Årets resultat". - Accruals POST had no idempotency. Re-running the wizard (or a retried POST after a flaky network) would create duplicate accrual entries that distort both the balance sheet and trial balance. New findExistingAccrualEntry helper queries the period for an existing posted entry whose description matches the kind's stable prefix (or for manual prepaid/accrued, the user-supplied description). Duplicates land in a new `skipped` array in the response with a reference to the existing entry id, rather than producing a second posting. 5-item compliance polish: - signer_name on the signatures schema now has .max(200) per GDPR Art.25.2 data-minimization. Swedish personal names are well under that — the bound is a defense against an unbounded-string injection. - Audit-fee accrual: 6420 is BAS-specific to lagstadgad revision. Bokslut fees for a non-revisionspliktigt bolag (liability_account = 2991) now debit 6590 (övriga externa tjänster) instead — Skatteverket may query a 6420 debit when there is no revisor i bolaget. - Räntefördelning ne_ruta label: was 'R30 / INK1 kapital' (confusing — INK1 is a separate form). Positive now reads 'R30 (avdrag i näringsverksamhet)' with the INK1 T4 cross-reference moved to the description; negative reads 'R30 (tillägg till resultat)'. - gnubok_post_annual_depreciation MCP tool now checks the caller's company_members role and throws on viewer. RLS would reject the underlying INSERT anyway; failing fast here produces a cleaner error than the cascaded RLS rejection. Mirrors the HTTP route's { requireWrite: true } guard. - Signature DELETE policy now blocks both 'signed' AND 'declined' rows. A declined signature is auditable evidence (board member refused) and is material under ABL 8 kap. New migration 20260517100000. Bot-flagged items I'm deliberately not chasing on this round: - V8.2.1 × 2 cross-tenant findings on ÅR routes — same false-positive class I've responded to repeatedly: buildArsredovisningData internally filters by company_id, bot can't see past the route handler. - V4.5 / V2.2 MCP arg Zod redundancy — MCP server's central handler validates against each tool's inputSchema. - Narrative-in-URL GDPR concerns — proper fix is POST + body or server-side persistence; tracked as follow-up. - `sate` typo (Greptile P2) — type-wide rename, deferred. - Vacation avgifter age-tier rate split — inherited from upstream generateVacationLiability which doesn't expose age; needs upstream work. - Medelantal anställda proper monthly average — needs salary-run aggregation across the year, follow-up. - K2 noter aktiekapital + fastställelseintyg blocks — real K2 gaps, tracked as follow-up. Verification - 94 tests pass (subset for bokslut + MCP suites; full Phase 1-7 suite unchanged in scope) - Zero typecheck errors on any touched file - Zero new lint errors on any touched file (the 2 server.ts warnings are pre-existing) - Migration 20260517100000 applied to remote Supabase via MCP Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |