c0ecf2fa3bebd46bdfd0169efd73b89653d1dfed
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0743717033 |
fix(salary): keep the payslip's Ackumulerat total from going stale (#1911)
* fix(salary): keep the payslip's Ackumulerat total from going stale
`salary_run_employees.ytd_*` (the "Ackumulerat {år}" block on the
lönespecifikation) was written once at calculation time and never
recomputed, from a query that only counted prior runs already in
`booked`. Preparing next month's run before the current one is booked
(entirely normal) therefore froze a YTD that is permanently missing the
month in between, and the employee's payslip understates the year.
Seen in production: an August run calculated on 2026-07-23, three days
before the July run was booked, shipped a payslip whose Ackumulerat brutto
was 60 000 kr instead of 95 000 kr.
Two fixes, both in the new lib/salary/ytd.ts:
- `computePriorYtd` counts `approved`, `paid` and `booked` prior runs, not
only `booked`. `corrected` stays excluded: its correction run replaces
the whole month, so counting both would double it.
- `refreshRunYtd` recomputes and rewrites the snapshot, and is now called
at approval (the first status lönebesked can be sent from) and at
booking, on both the dashboard and v1 surfaces. Rows already correct are
left untouched; a failure is logged and never blocks an approval or a
booking.
The snapshot stays a snapshot rather than becoming a render-time sum: an
employee re-opening a lönebesked must see the figures it had when it was
issued. YTD is display and reporting only, so nothing here can move a
verifikation: the per-month tax lookup and the avgifter caps never read it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(salary): fail loudly on a YTD read error and paginate the reads
Review follow-up on both counts:
- The opening-balance and prior-run reads discarded their `error`. A failed
read looked exactly like a month with no prior pay, so `refreshRunYtd`
would rewrite the snapshot to the current month alone and still report
success. Both now throw; `refreshRunYtd` turns that into `ok: false` for
its callers to log, and `runSalaryCalculation` returns DATABASE_ERROR the
way it already does for every other query error in that function.
- The prior-run and roster reads now page through `fetchAllRows()` ordered
on the primary key. A full roster times eleven prior months passes
PostgREST's 1000-row cap well before an employer is large by Swedish
standards, and a silent truncation there understates somebody's
Ackumulerat.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor(salary): one paginated loader for cutover opening balances
Review follow-up. `run-calculation` and `ytd` each read
employee_opening_balances with their own unpaginated, error-discarding
query. Both now go through `loadOpeningBalances()`: paged via
fetchAllRows() ordered on the primary key, and throwing on a read error.
The error path matters more than the paging one here. That row carries
`karens_periods_adjustment` as well as the YTD carry-in, and a discarded
error looked exactly like "nobody has a cutover balance" - which would
drop a karensavdrag from sjuklön silently, not just understate a display
figure. runSalaryCalculation now maps it to DATABASE_ERROR.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
4e14182a00 |
fix(salary): declare, book and pay AGI in whole kronor (SKV per-sats computation) (#1611)
* fix(salary): declare, book and pay AGI in whole kronor (SKV per-sats computation) A user's first lönekörning surfaced öre amounts in the AGI payable while Skatteverket deals in whole kronor. Three connected defects: - the AGI XML rounded amounts (Math.round); öretal bortfaller (SFF 2011:1261 22 kap. 1 §) requires truncation, and FK487 must be Skatteverket's own per-sats computation on the whole-krona underlag sums (IK587, kontroll B_006), not a truncation of the öre-exact engine sum - the salary booking credited 2731 with exact öre, leaving a residual after the whole-krona skattekonto draw; 2731 now carries the declared amount with the remainder on 3740 (Öres- och kronutjämning) - the LB payment file and TaxPaymentPanel paid/showed öre; they now use the declared whole-krona totals stored on agi_declarations (which also lets skattekonto auto-settlement match the draw); legacy öre rows keep paying öre-exact so pre-deploy bookings still clear 2731 New lib/salary/declared-avgifter.ts implements the SKV computation (per-IU whole-krona underlag, per-sats sums, youth/växa cap splits, exact integer math) shared by the AGI generator, the booking split and the preview. Review overrides route all legs through the same per-category truncation; basis overrides are inert on money totals (they never reach the filed IUs); the v1 book route gains override parity with book-run; F-skatt rows ignore avgifter overrides on every surface. Booked runs show their posted verifikat instead of a recomputed projection. tax_withheld_override requires whole kronor. Adversarially verified over three /skeptic rounds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: merge origin/main and re-ratchet the öre-round baseline The merge brought #1609 (net-pay öresavrundning) whose two new Math.round(x*100)/100 occurrences are counted against the baseline this branch had tightened from 637 to 629; 631 keeps the net -6 improvement without policing already-merged code. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(salary): address PR review (hybrid override computation, legacy youth cap, robustness) CodeRabbit round on #1611, all findings in one pass: - computeDeclaredAvgifterWithOverrides: one shared hybrid for the AGI generator AND the booking split. Overridden rows contribute their manual amounts per category; colleagues keep the SKV-exact per-sats underlag computation (a FoU override on one employee no longer costs the rest of the roster kronor of declared accuracy) - youth cap keys on the RESOLVED category so legacy null-category rows classified as youth by the rate heuristic still get the 25k split - F-skatt rows zero their avgifter_basis on both booking surfaces and in the preview, matching the AGI's isFSkattRow invariant - preview route: posted-voucher lookup errors return 500 instead of masquerading as a booked run with no vouchers; 400/500 tests added - run page clears stale AGI totals when the tax-payment fetch fails - SalaryOverridePanel truncates the tax override to whole kronor so the schema's .int() cannot bounce a decimal input with a 400 - v1 book route override parity pinned by a lifecycle test - DECISIONS.md format fixes + superseded entry marked; exempt category mapped explicitly; unified truncation-drift band with rationale Declined (recorded): dating the decision entries 2026-08-13 (bot assumed UTC; the decisions were made after midnight local time). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(salary): round-2 review nits (shared F-skatt helper, test hygiene) - isFSkattStatus in declared-avgifter.ts: single source for the F-skatt exclusion, consumed by book-run, the v1 book route, the preview route and the AGI generator, per the Swedish review's drift-risk finding - declared-avgifter test suite gets the standard beforeEach cleanup Declined (recorded for the summary): auto-generated correction voucher for regenerated legacy periods (data-repair follow-up needing Emil's go); SFF 22 kap. 1 par. citation doubt (verified against lagen.nu and already shipped in tax-tables.ts); 3740 scope doubt (BAS generic utjamning account, Visma praxis, matches the user's reference voucher). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c9625fa45c |
fix(salary): book lonevaxling pension provision from frozen run snapshot (#1382)
The book flow never populated pension_contribution/pension_slp, so a gross_deduction_pension line item reduced the salary entry but the 7410/2740 pension provision and 7533/2514 SLP lines were never posted. Derive both at the createSalaryRunEntries boundary from the run's frozen calculation_params.slpRate snapshot so the dashboard, MCP and v1 booking paths all emit the pension verifikat, and reuse the exact 1.058 factor via calculateLoneVaxlingPensionProvision shared with the planning calculator. Fixes #317 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
30771b1619 |
feat(mcp): payroll e2e parity: staged salary-run booking + absence deletion (#1075)
* feat(mcp): payroll e2e parity: staged salary-run booking + absence deletion Close the last MCP-surface gaps for running payroll end-to-end via the connector (the v1 REST API already had the full chain): - gnubok_book_salary_run: stages a high-risk book operation; on approval the executor walks review -> approved -> paid -> booked via the new lib/salary/book-run.ts (extracted from the dashboard book route, which now calls the same core) and posts the immutable salary vouchers. - gnubok_delete_absence: staged inverse of gnubok_register_absence, reusing deleteAbsenceRange with a dry-run day-count preview. - Wire the missing payroll operation types into the Granskning label map (register_absence, update_payslip_line, employee ops, vacation_year_close had translations but fell back to humanized snake_case). - Update stale 'booking happens in the web UI' prose in tool descriptions, the payroll-monthly skill, and the workflow hint; payload-size ceiling 56K -> 57K per the documented bump protocol. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(mcp): widen pending_operations op-type CHECK + roster typing for book_salary_run The op-type audit (pg-real) caught the exact bug class it exists for: book_salary_run and delete_absence were staged in code without the constraint-expansion migration, so every real staging INSERT would have failed with check_violation while dry_run previewed clean. Ships the documented widen (NOT VALID) + validate migration pair. Also fixes the strict-mode cast in book-run.ts that failed the production typecheck. Verified locally against supabase/postgres 15.8.1.060 with all migrations applied: op-type audit green, pg-real 692/693 (the one failure is the pre-existing TZ-sensitive get_unlinked_1930_lines assertion, green under TZ=UTC as in CI). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |