* feat(salary): update_salary_run MCP tool and editable draft payment date
payment_date drives the booking entry date but was only editable via the
v1 PATCH. Close the gap on both remaining surfaces:
- New staged MCP write tool gnubok_update_salary_run (search-only
catalog; tools/list budget is at zero headroom) accepting the exact
v1 PATCH field set: payment_date, voucher_series, notes. Draft-only
with the same optimistic lock semantics, via a new shared service
lib/salary/update-run.ts used by both the staging preflight and the
commit executor.
- Run header UI: payment date on a draft run is now an inline date
input (prefilled, committed on blur/Enter, snaps back on failure),
saved through the existing internal PATCH. Read-only once not draft.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zGah8Yy49esAwpnKGxiGy
* fix(salary): op-type migration, calc invalidation on date change, scanner compliance
Consolidated CI + review fix pass for #2041:
- pg-real: add 'update_salary_run' to pending_operations_operation_type_check
(wholesale re-create, NOT VALID + VALIDATE pair, mirroring 20260828160000/1).
- Swedish accounting review: a payment_date change on a draft run now clears
every roster row's calculation_breakdown (shared service and internal PATCH
alike), so both book preflights refuse the run until a recalculation has run
against the new date; skatteavdrag and the AGI redovisningsperiod follow the
payment month. Staging preview exposes invalidates_calculation and the next
hint states the clearing.
- no-phantom-columns: literal select strings in update-run.ts; ceiling +1 with
a documented reason for the inherent patch-shaped UPDATE payload.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zGah8Yy49esAwpnKGxiGy
* fix(salary): close skeptic findings on payment_date editing
Skeptic round 1 refuted two paths; both closed:
- Retry idempotency (correctness): the calculation_breakdown clear was
gated on new-date-differs-from-stored, so a retry after a partial
failure (header committed, clear failed) compared against the already
updated date and skipped the clear forever, leaving a stale
calculation bookable. The clear is now gated on payment_date being
SUPPLIED, on all three surfaces (shared service, internal PATCH, v1
PATCH: the v1 route previously had no clear at all and bypassed the
invariant).
- Kontantprincipen (compliance): AGI derives its redovisningsperiod
from period_year/period_month while the verifikat books on
payment_date, so a cross-month payment_date change could book salary
in one month and declare it in another. All three edit surfaces now
refuse a payment_date outside the run's period month with the new
structured error SALARY_RUN_PAYMENT_DATE_OUTSIDE_PERIOD; the UI date
input is min/max-bounded to the period month.
- The internal PATCH update is now optimistic-locked on status='draft'
(races return 400 instead of silently writing), matching the v1 PATCH
and the shared service, and the clear cannot fire for a run that left
draft.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zGah8Yy49esAwpnKGxiGy
* fix(salary): carry book_skattekonto op types through the constraint re-create
The sibling migration 20260830130000 (merged from main) re-created
pending_operations_operation_type_check with book_skattekonto_row and
book_skattekonto_rows. This branch's 20260830150000 sorts after it and
re-creates the constraint wholesale, so its list must be that migration's
superset or the two values would be silently revoked at apply time.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zGah8Yy49esAwpnKGxiGy
* fix(salary): value-validate internal PATCH and grandfather out-of-period dates
Two skeptic follow-ups:
- The internal PATCH now validates values, not just keys: JSON body must
be an object, payment_date must be ISO (shared ISO_DATE_RE),
voucher_series a single A-Z letter, notes a string of max 2000 chars
or null: the same rules as the v1 UpdateSalaryRunSchema, so nothing
unvalidated can reach the DB through the whitelist.
- Creation does not (yet) couple payment_date to the period month, so a
legally created out-of-period date must stay correctable. All three
edit surfaces now allow day adjustments within the run's CURRENT
payment month as well as the period month (grandfather clause); no
move can introduce a new wrong month.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zGah8Yy49esAwpnKGxiGy
* fix(salary): resolve migration version collision with delete_draft_invoice
Main's delete_draft_invoice PR landed on the same 20260830150000/150001
versions and also re-creates pending_operations_operation_type_check.
Rename this branch's pair to 20260830160000/160001 (applies last) and
carry delete_draft_invoice through the wholesale re-create so nothing is
silently revoked. Final list = sibling's list + update_salary_run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zGah8Yy49esAwpnKGxiGy
* docs(salary): regenerate accounted-api skill for the new PATCH pitfalls
apiskill:check byte-compares the generated skill against the registry;
the two pitfalls added to the v1 salary-runs PATCH endpoint made
references/salary-runs.md stale and failed Core Build.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zGah8Yy49esAwpnKGxiGy
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>