Commit Graph
5 Commits
Author SHA1 Message Date
MattssonandClaude Fable 5 98d0c7f2d0 Add/stripe skv (#1004)
* fix(salary): align pain.001 salary file with the Swedish domestic bank dialect

Verified against the Swedish Common Interpretation of ISO 20022
(Bankforeningen, Common Payment Types in Sweden, Appendix 1 Example 4:
Salaries) and Nordea Corporate Access pain.001 examples v2.6 (2026-06-22),
and XSD-validated against the official pain.001.001.03 schema:

- drop SvcLvl SEPA (SEPA credit transfers are EUR-only; omitting SvcLvl
  gets the domestic NURG default)
- drop RmtInf (not allowed for SALA salary payments; the beneficiary
  statement text comes from the Dataclearing LON code)
- address employees domestically: clearing as CdtrAgt ClrSysMmbId SESBA,
  account WITHOUT clearing as CdtrAcct Othr with SchmeNm BBAN
- share the clearing/account split (Swedbank 5-digit shift, Nordea
  personkonto prefix dedup) between the LB and pain.001 generators via
  splitDomesticBankAccount, fixing pain.001 duplicating the personkonto
  clearing
- clamp MsgId/PmtInfId/InstrId/EndToEndId to Max35Text with the per-tx
  counter surviving truncation; carry the org number on Dbtr
- return 400 from the pain001 route on an invalid clearing instead of
  emitting a broken file

Also includes two unrelated decision-log lines from the parallel
revisor-review session (DECISIONS.md is a shared append-only log).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(nav): surface the year-end chain in the sidebar

Add Periodiseringar, Arsredovisning (aktiebolag only) and
Inkomstdeklaration (INK2 for AB, NE-bilaga for EF) to the Skatt &
bokslut group, in workflow order. Entity gating via a new entityOnly
flag on NavItem; isActive carve-outs extended so exactly one row
lights up for the new routes. Driven by an external revisor review
that concluded these features did not exist because none of them
were reachable from the nav.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(stripe): Stripe Connect integration behind config gate

Connect OAuth per company (only the acct_ id is stored), automatic
single-use Payment Links on invoice send, deterministic payment
settlement against 1686 (BAS moved acquirer receivables 1580 -> 1686),
payout booking with reverse-charge fees (6570 + 4535/4598 + 2645/2614),
and a 15-minute sync cron. Non-deterministic events land as
needs_review, never guessed at.

Fully dark without STRIPE_CONNECT_CLIENT_ID: connect returns 503, the
send hook and cron no-op, and the settings page shows 'Kommer snart'
(hosted) until the Connect platform is verified. Self-hosted keeps the
honest not-configured message.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(deadlines): add shared completeTaxDeadline and fix dead AGI deadline auto-complete

generate-declaration.ts has updated non-existent columns (type/period/
status) since inception, so the arbetsgivardeklaration deadline was
never auto-completed. Replace with a shared helper targeting the real
schema (tax_deadline_type/tax_period/is_completed), also used by the
kvittens crons and moms handlers in the follow-up commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(rot-rut): import Skatteverket beslutsfil and record decisions on payout requests

Parse the beslutsfil JSON from Skatteverkets rot/rut e-tjanst and record
godkant belopp on the matching begaran: matched by stored
skv_referensnummer first, then exact name among active undecided
requests; arenden by fakturanummer then personnummer, exactly-one or the
beslut errors (all-or-nothing). Never auto-settles: recording the beslut
and booking the payout are separate acts. Exposed as an API route and
the gnubok_import_rot_rut_beslut MCP tool.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(skatteverket): system auth for background reads, one-click VAT submit, kvittens notifications

Hybrid auth program: system CCG (org certificate) for background reads
while personal BankID stays for interactive submissions, since SKV
per-flow refresh tokens live 65 min and crons structurally cannot run
on them. All system-auth code sits behind SKATTEVERKET_SYSTEM_AUTH_MODE
(default off) with a stub transport until the Expisoft cert and CCG
avtal land; auth resolution is centralized in resolve-auth.ts.

Also in this change:
- One-click VAT submit chaining kontrollera -> utkast -> las
  server-side with a stage discriminator; step-by-step buttons demoted
  to the overflow menu.
- Kvittens crons (AGI + new VAT schedule) with email-only
  notifications, deduped in notification_log under the new
  skv_kvittens type.
- Ombud grant probe + verification UI in the connect panel, and a
  dashboard promo card for unconnected companies.
- skatteverket_company_connections table with pg-real coverage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(salary): auto-settle AGI tax payment from skattekonto and surface SKV reconnect on the tax card

The "Skatt att betala" card only cleared via the manual mark-paid button
on the run detail page; the promised automatic flip from the Skattekonto
sync was never implemented, so paid periods stayed red.

- settleAgiTaxPayments: during every skattekonto sync, a booked
  "Arbetsgivardeklaration YYYYMM" debit row settles the matching
  agi_declarations.tax_paid_at, but only when the amount equals the
  declared total to the ore and the account is not in deficit
  (deterministic; drift or deficit falls back to manual).
- Salary overview card: reconnect hint when the SKV token needs
  re-consent (link to /settings/tax, silent when the extension is off),
  plus an inline "Markera som betald" button reusing the existing
  endpoint and salary_payments strings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Add cloud backup scheduling and alerting features

- Implement unit tests for scheduling logic in `schedule.test.ts`, covering various scenarios for determining if a backup schedule is due.
- Create a new module `backup-alert.ts` to handle failure alerts for cloud backup auto-sync, including email notifications for reauthentication and repeated failures.
- Introduce `schedule.ts` to manage scheduling logic, including handling local time zones and converting between local and UTC hours.
- Add CSV report generation functions in `archive-csv.ts` for trial balance, income statement, balance sheet, and general ledger, ensuring compatibility with Swedish Excel formats.
- Create a README generator for the archive structure in `archive-readme.ts`, providing clear documentation for users accessing backup files.
- Implement tests for CSV report generation in `archive-csv.test.ts`, ensuring correct formatting and content.
- Establish a full-archive coverage contract test in `full-archive-coverage.pg.test.ts` to ensure all company-scoped tables are properly classified for backup.

* fix(stripe): correct invoice clearing reference and improve type safety in sync logic

* fix(invoices): narrow accountingMethod before resolveInvoicePaymentSourceType

settleInvoicePayment takes accountingMethod as a raw settings string, but
resolveInvoicePaymentSourceType requires the 'accrual' | 'cash' union.
Normalize at the call site (anything but 'cash' books as accrual), matching
the existing useCashEntry semantics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: address CodeRabbit review findings and nitpicks on PR #1004

Review findings:
- backup settings redirect: always force view=export over incoming params
- AGI/VAT kvittens crons: isolate best-effort post-submit calls, check the
  signed-state persist error, guard recovery calls in catch blocks so one
  company cannot abort the rest; surface grant_revoked in the run summary
- kvittens notifications: atomic claim-first dedup with a partial unique
  index; map non-uuid reference keys to deterministic uuids
- grant probe: record the actual 2xx status; mTLS transport: handle
  response-stream errors
- stripe: amount-aware idempotency keys for payment links; emit
  stripe.disconnected on upstream revocations
- ROT/RUT beslut import: mutate in-memory request state after apply, move
  item + header writes into an atomic apply_rot_rut_beslut RPC, add
  rot_rut_payout to JournalEntrySourceTypeSchema
- migrations: use NOT VALID + VALIDATE CONSTRAINT for CHECK constraints on
  journal_entries, notification_log and rot_rut_payout_requests
- cloud backup: hour_utc-only schedule updates clear stale hour_local

Nitpicks:
- stripe sync: enforce the cron time budget inside per-connection event
  processing with idempotent cursor progress; maybeSingle for settings;
  honest partial-customer DTO shared with the settlement boundary
- shared applyPaymentLinkToInvoice helper for both invoice send routes,
  v1 docblock documents step 6b and PAYMENT_LINK_FAILED
- settings panel: drop redundant decodeURIComponent
- cloud backup: document worst-case archive memory headroom

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 19:14:12 +02:00
Jakob WennbergandClaude Sonnet 5 ec27228a8e style: remove em/en dashes repo-wide, add CLAUDE.md rule against them (#890)
Em dashes (—) and en dashes (–) had spread across comments, docs, tests,
and a few UI strings, reading as AI-generated boilerplate rather than
house style. Replaced each with punctuation matching its context: colon
for explanatory clauses, comma for asides, plain hyphen for numeric/legal
ranges (e.g. "21-23§"), "to"/"till" for date ranges, parentheses for
paired-dash asides. messages/en.json and messages/sv.json were fixed by
hand together to keep sv/en in sync.

Left untouched where the dash is the functional subject rather than
decorative punctuation: date-range-parser.ts's separator regex,
charset-repair.ts's CP1252 byte-mapping table (and its test), the SIE
encoding mojibake docs, generic-csv.ts's minus-sign normalizer, the
agent system-prompt files that already instruct against em dashes, and
a golden iXBRL test fixture compared byte-for-byte.

Also fixes two bugs surfaced along the way: an off-by-one in
ApiKeysPanel's scope-label split (a leftover from an earlier partial
pass), and a charset-repair test that had lost the literal en-dash it
exists to verify.

Regenerated the agent atom seed migration (skills:generate) since 27
SKILL.md files changed. Added a CLAUDE.md rule against em/en dashes,
with an explicit carve-out for the functional-dash cases above.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 15:58:06 +02:00
Mattsson a652dcae1a Skv/e2e overview (#515)
* feat(agi): refactor AGI XML generation and data handling

- Remove deprecated AGI field codes from field-codes.ts.
- Update generate-declaration.ts to include new employee fields and handle absence data with stable specification numbers.
- Enhance XML generation in xml-generator.ts to support new flags for housing benefits and adjusted benefits.
- Introduce new database migrations to support:
  - `removed_from_agi` flag for tombstoning individuppgifter.
  - `benefits_adjusted` flag for tracking adjustments to benefits.
  - `franvaro_specifikationsnummer` for stable absence event identification.
  - `housing_benefit_type` to differentiate between housing benefit types.

* feat: Implement strict validation for AGI employee data and introduce pre-flight validation schemas

- Added Zod schemas for validating employee data in AGI declarations to ensure all required fields are present and correctly typed, preventing silent errors during processing.
- Introduced AGI pre-flight validation schemas for Skatteverket endpoints to validate individual and head unit submissions before sending to the API.
- Created a new audit log table for tracking all outbound calls to Skatteverket, ensuring compliance and traceability for AGI and moms submissions.
- Implemented advisory locks in the database to manage concurrent updates to absence specification numbers, enhancing data integrity.
- Added compliance documentation for GDPR processing activities related to AGI and moms submissions, detailing data handling and retention policies.

* feat: Extend DELETE RLS policy to protect 'declined' signatures in årsredovisning

* fix: Update date handling in salary absence migrations to use immutable year-month key

* fix: Update SELECT policy in skatteverket_api_audit_log to use IN clause for company_id

* feat: Add skatteverket_api_audit_log and salary_absence_franvaro_audit tables with RLS policies and immutable triggers
2026-05-17 23:52:49 +02:00
Jakob WennbergandClaude Opus 4.7 432a8b60dc feat(skatteverket): rewrite AGI flow against real Skatteverket RAML (#391)
* feat(skatteverket): rewrite AGI flow against real Skatteverket RAML

The previous AGI client posted JSON to URL paths that don't exist on
Skatteverket's gateway and used invented field names. POST /underlag
actually accepts application/xml, and the lock/kvittenser operations
live on the separate hanteraredovisningsperiod API. Verified against
dev_docs/arbetsgivardeklaration-inlamning(1.7.7) and
arbetsgivardeklaration-hantera-redovisningsperiod(1.2.8) RAMLs.

- Replace fictional types with real schemas (kontrollresultat,
  granskningsunderlag, kvittenser, error envelope)
- Rewrite agi-client into 9 functions matching the documented flow:
  /underlag (XML) -> kontrollresultat -> spara -> skapaGranskningsunderlag
  -> kvittenser, plus las/lasUpp on the hantera API
- Drop agi-mappers entirely; lib/salary/agi/xml-generator.ts already
  produces schema-valid XML, so the extension just feeds
  agi_declarations.xml_content to POST /underlag
- Extend skvRequest with a contentType option so AGI can post XML
- AGIPanel state machine: underlag_submitted -> awaiting_signing ->
  signed, with kontrollresultat polling and normalized findings
- Add the agd OAuth scope (confirmed from SKV's Tjanstebeskrivning
  Arbetsgivardeklaration inlamning v1.7, section 4.1.2.2)
- Add Skatteverket connect step to NewUserChecklist alongside the
  existing SIE/old-system import and bank steps; track
  hasSkatteverketConnected in OnboardingProgress
- Update orchestrator route + tests to point at the new /agi/submit
  endpoint
- Declare new optional base-URL env vars in the manifest

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(skatteverket): address PR review findings on AGI flow

- Surface INCORRECT_DATA felrapport link in AGIPanel
  skapaGranskningsunderlag returns 409 with a felrapport URL when SKV
  rejects the underlag. The link was persisted as `signeringslank` with
  status `underlag_rejected`, but the render condition only fired for
  `awaiting_signing`, leaving the link unreachable. Add a distinct
  destructive-styled block so the user can open the felrapport in Mina
  Sidor.

- /agi/underlag DELETE clears local submission state
  Add optional `period` query param. When supplied, clear
  `agi_submission_{period}` directly. When not, fall back to scanning
  recent agi_submission_* keys for the matching inlamningId. Without
  this, an aborted underlag left a stale `underlag_submitted` entry in
  extension_data and the UI couldn't progress.

- Re-add salary-run status guard inside loadAGIXml
  The orchestrator at app/api/salary/runs/[id]/agi/submit/route.ts has
  this check, but the extension endpoint is also reachable directly
  from AGIPanel and must enforce it itself. Per BFL 5 kap and SFL
  26 kap, AGI must reflect finalised payroll data; submitting from a
  draft/cancelled run would emit incorrect figures.

- Move agi_declarations.status='exported' from /agi/submit to /agi/spara
  Setting status on underlag-ingest was wrong because a DONE_REJECTED
  kontrollresultat would leave the row falsely marked as exported. The
  transition now happens only after the spara call commits the underlag
  to Eget utrymme. /agi/spara accepts salaryRunId in the body for the
  fast path and falls back to scanning agi_submission_* state otherwise.

- Move salary_runs.agi_submitted_at stamp to kvittenser observation
  The orchestrator was stamping at underlag-ingest, but no later code
  updated the column on signing. Removed the orchestrator stamp; the
  /agi/kvittenser handler now stamps salary_runs.agi_submitted_at to
  kvittens.signeradTid (mirroring SKV's own timestamp) when it pins
  the receipt to the matching agi_declarations row.

- Tighten misleading JSDoc in agi-client.ts
  taBortSparadInlamning is on the inlämning API, not hantera; the old
  layout grouped it under a "hantera API" heading and tripped an
  automated reviewer. Restructured into separate "period management"
  and "cleanup" blocks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(skatteverket): address Swedish compliance review on AGI flow

Follow-up to review on https://github.com/erp-mafia/gnubok/pull/391.

- Migration adds 'pending_signature' to agi_declarations.status
  Reusing 'exported' for the spara→kvittens interval misstated the
  filing outcome — Eget utrymme is a staging area, not a filing — which
  conflicts with BFNAR 2013:2 kap 8 / BFL 5 kap 5§ behandlingshistorik
  faithfulness. /agi/spara now sets 'pending_signature'; /agi/kvittenser
  later promotes to 'submitted' when a uuidKvittens is observed.

- AGIPanel auto-polls /agi/kvittenser at 30s, 2 min and 5 min after the
  signing link is created
  Previously the kvittens (and therefore salary_runs.agi_submitted_at)
  was only stamped if the user manually returned to the panel and
  clicked "Hämta kvittens". Without that follow-up the audit trail
  showed a NULL submitted-at for an AGI that had actually been filed.
  Background polls capture the kvittens for the common case where the
  user signs in Mina Sidor and never returns to gnubok. Cleanup on
  unmount via useRef + useEffect.

- Distinct MISSING_SCOPE error code on 403 invalid_scope
  Existing tokens lack the new 'agd' scope and surface as a generic
  ACCESS_DENIED today. The compliance reviewer pointed out that
  operators may interpret this as a data error and submit a corrected
  AGI with altered figures. New SkatteverketAuthError code maps SKV's
  invalid_scope body to a clear "reconnect via Inställningar →
  Skatteverket" message; routes to 401 (token-level remediation).

- Refine deadline copy in AGIPanel
  The standard AGI deadline is the 12th regardless of company size; the
  17th only applies in January and August for employers with turnover
  ≤ 40 MSEK. Surface that nuance instead of saying just "12:e".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(skatteverket): server-side kvittens reconciliation cron

Round 2 of compliance review on https://github.com/erp-mafia/gnubok/pull/391.

- /api/extensions/skatteverket/agi/kvittenser/cron
  Walks every agi_declarations row in 'pending_signature' status, fetches
  kvittenser via the matching token, and on a hit promotes the row to
  'submitted' + stamps salary_runs.agi_submitted_at. Authoritative source
  for the audit trail per BFNAR 2013:2 kap 8 / BFL 5 kap 5§ — the
  AGIPanel client-side timers from the previous round remain as the
  fast-path UX, but no longer carry the audit-trail responsibility on
  their own. Per-row errors are skipped, not abort-the-run. 50s budget.
  Scheduled every 2 hours in vercel.json.

- AGIStatus union now includes 'pending_signature'
  Without this update, downstream code reading the union would have
  rejected the new status as unknown. The migration extending the DB
  CHECK constraint shipped in the previous commit; this brings the type
  layer into sync.

- Stale comment update in AGIPanel.tsx
  Referred to status='exported' from before the rename. Now reads
  'pending_signature', matching the actual handler behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(skatteverket): close audit-trail gaps from compliance round 3

- Cron now writes submitted_by from the token-owning auth.users row
  Previously left NULL with a "system actor" comment. The token row was
  created when the operator authenticated with BankID, and the kvittens'
  signeradAv refers to the same person — so writing the user_id from
  skatteverket_tokens captures actor traceability without inventing a
  system identity. Closes the BFL 5 kap 6§ / BFNAR 2013:2 kap 8 gap on
  cron-reconciled rows.

- /agi/spara monotonicity guard
  Adds .in('status', ['generated', 'exported']) to the row update so a
  delayed /agi/spara call after the cron (or interactive /agi/kvittenser)
  has already promoted the row to 'submitted'/'accepted' won't silently
  regress it back to 'pending_signature'. behandlingshistorik must
  advance only.

- DONE_REJECTED / DONE_FAILED → status='rejected'
  /agi/kontrollresultat handler now flips the matching agi_declarations
  row to 'rejected' on a terminal SKV failure, using the same
  cached-submission-state lookup pattern /agi/spara already uses.
  Without this the row sat at 'generated' indefinitely even though SKV
  considered the underlag failed. Same monotonicity guard prevents
  regressing a successfully-filed row.

- Deadline criterion: lönesumma, not omsättning
  AGIPanel pendingText. SFL 26 kap's relaxed-deadline criterion (17:e
  in Jan/Aug) is the employer's total taxable wages, not turnover.
  Internal reference (.claude/skills/swedish-payroll/references/agi-filing.md)
  used the colloquial "turnover"; statutory wording is "lönesumma".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(skatteverket): close round-4 audit-trail and UX gaps

- agi_submitted_at: NULL when signeradTid absent
  Both /agi/kvittenser handler and the kvittens cron previously
  fell back to new Date().toISOString() if SKV's kvittens lacked
  signeradTid. Substituting wall-clock now() falsifies the filing
  moment in behandlingshistorik (BFNAR 2013:2 kap 8 / BFL 5 kap 6§).
  Now leaves the column NULL and logs a warning. Status flip to
  'submitted' still happens — the audit gap was timing only.

- Proactive missing-agd-scope banner
  SkatteverketConnectPanel and AGIPanel now warn when the stored
  token lacks the agd scope. Tokens issued before the agd rollout
  would otherwise 403 with invalid_scope at submission time, often
  too close to the AGI deadline. SkatteverketConnectPanel mirrors
  the existing "skattekonto saknas" pattern; AGIPanel surfaces a
  banner in the connected state and links to /settings/skatteverket.

- Granskningsunderlag isError keys on tillstand only
  Previous check mixed HTTP 409 with the INCORRECT_DATA tillstand
  string. A future SKV addition like RECEIVING returned with HTTP
  200 would have slipped through as awaiting_signing. Now keys
  solely on tillstand: only LOCKED_FOR_SIGNING / UNLOCKED are
  treated as signable; everything else (INCORRECT_DATA, RECEIVING,
  CALCULATING, SIGNING) routes to underlag_rejected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(skatteverket): close round-5 audit-trail and recovery gaps

- agi_submitted_at: stamp with reconciliation time when signeradTid absent
  Round 4 left the column NULL on missing signeradTid to avoid falsifying
  the signing moment. Round 5 pointed out that NULL hides that the filing
  *occurred* — also a behandlingshistorik integrity violation. Resolution:
  presence of uuidKvittens proves SKV signed and accepted the AGI, so we
  stamp with signeradTid || now() and warn-log when fallback is used.
  Both /agi/kvittenser handler and the kvittens cron.

- Persist signeradAv + full kvittens in agi_declarations.response_data
  submitted_by is the auth.users UUID we have on hand (the polling /
  reconciling user). The legally load-bearing signer identity is
  kvittens.signeradAv (a personnummer) — which the token user_id does
  NOT necessarily match (e.g. bookkeeper vs deklarationsombud). The
  existing response_data jsonb column now holds the full kvittens record,
  preserving signeradAv for the audit trail (BFL 5 kap 6§ / BFNAR 2013:2
  kap 8) without a schema change. Cron path also marks reconciledBy='cron'.

- /agi/spara monotonicity: allow recovery from 'rejected'
  Previously .in('status', ['generated', 'exported']) excluded rejected
  rows, so a successful re-submission after a prior rejection couldn't
  promote the row to pending_signature — it silently stayed rejected.
  The xml-route reuses the same agi_declarations row when re-generating
  XML, so this is the realistic recovery path. Added 'rejected' to the
  allowed-from list. 'submitted'/'accepted' still blocked (no regression
  from filed states).

- Fix misleading agi-client.ts comment
  Claimed users could "fix the errors in Mina Sidor" after a
  DONE_REJECTED save. Mina Sidor doesn't expose in-place editing; the
  correct recovery is to regenerate XML and resubmit. Updated the
  agiSparaUnderlag JSDoc to describe the actual flow.

- Deadline copy: "vars sammanlagda lönesumma understiger 40 MSEK"
  Reads more cleanly than "≤ 40 MSEK" and matches the phrasing the
  compliance reviewer suggested.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(skatteverket): tighten /agi/spara guard and clarify deadline copy (round 6)

- Drop 'exported' from /agi/spara allowed-from states
  Audit confirmed no code path writes status='exported' today; the value
  is preserved in the schema (and union) for the legacy manual-download
  path that no longer has a writer. Allowing the spara handler to flip
  an 'exported' row to 'pending_signature' would conflate two distinct
  filing attempts on a single row, weakening the chain of custody (BFL
  5 kap 6§). Tightened to .in(['generated', 'rejected']) — same recovery
  path for re-submission after rejection, no path for the dormant state.

- Deadline copy: explicit "per år" qualifier
  The 40 MSEK threshold is annual lönesumma, not per-payment. Adding
  "per år" closes the (admittedly thin) misread the compliance reviewer
  flagged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 17:08:29 +02:00
Mattssonandgreptile-apps[bot] bb0db7a588 Salary module improvements (#250)
* feat: implement salary module with personnummer encryption, salary entries, tax tables, and AGI tracking

- Added personnummer encryption and decryption functions for secure storage.
- Created salary entries handling for journal entries including gross salary, tax withholding, and employer contributions.
- Implemented tax table lookup functionality for calculating tax amounts based on monthly income.
- Developed SQL migration for salary module including tables for payroll configuration, tax rates, employees, salary runs, salary run employees, salary line items, and AGI declarations.
- Established row-level security policies for all new tables to ensure company-scoped access.

* feat: add salary calculation modules for 2026

- Implemented engångsskatt calculation for one-time payments with tax brackets.
- Added löneväxling functionality for salary sacrifice to pension, including employer savings and warnings.
- Created pain.001 generator for salary batch payments in compliance with Swedish banking standards.
- Developed PDF template for payslips, including detailed breakdowns and employer costs.
- Generated seed data for Swedish tax tables for 2026, including SQL insert statements.
- Implemented traktamente calculations for per diem and mileage allowances, adhering to Skatteverket regulations.
- Added seed script for populating tax tables in the database.

* feat: Update meal reduction percentages in traktamente calculation

fix: Remove obsolete seed script for 2026 tax tables

feat: Extend SalaryRunStatus type to include 'corrected' status

feat: Implement KU10 XML generation endpoint for annual employee income statements

feat: Add endpoint for creating corrections to booked salary runs

feat: Implement endpoint for sending payslip PDFs to employees

feat: Create KU10 XML generator for annual reporting

feat: Add salary transaction matcher for auto-linking bank transactions to salary entries

chore: Add database migration for salary correction support

* feat: replace select elements with custom Select component for employment and salary types

* feat: enhance salary calculations with pension entry and avgifter category support

* feat: enhance employee management with salary type, tax status, and validation improvements

* feat: Implement AGI submission flow to Skatteverket

- Added AGI submission route to handle the submission process.
- Created AGI client for interacting with Skatteverket's API.
- Introduced AGI mappers to convert salary run data into the required AGI JSON payload format.
- Enhanced API client to support custom base URLs for Skatteverket API requests.
- Added types for AGI submission payload and validation results.
- Implemented tests for AGI mappers to ensure correct payload structure and data handling.

* feat: enhance salary module with Skatteverket integration and update dashboard navigation

* Update app/api/salary/runs/[id]/agi/submit/route.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update app/api/salary/runs/[id]/approve/route.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* feat: integrate write permission check and remove Skatteverket extension

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-04-15 20:55:29 +02:00