feat(salary): agent path to set this month's per-run salary (#2015)
* feat(salary): agent path to set this month's per-run salary
Agents could not do variable owner pay: the only per-run edit tool,
gnubok_update_payslip_line, edits the display-only Grundlon line that
every recalculation rebuilds from salary_run_employees.monthly_salary,
so the fixed employee salary silently won (user-reported).
- lib/salary/run-employees.ts: setRunEmployeeSalary() shared service
(draft gate, roundOre, 0 = nollkorning, display-line refresh); the
cookie route PATCH now delegates to it (behavior unchanged)
- MCP: gnubok_set_run_salary staged tool (search catalog: tools/list
budget at zero headroom), op type set_run_salary (medium risk),
commitSetRunSalary executor, payroll:write scope, payroll_month
loadout + payroll-monthly skill step; update_payslip_line description
now warns that recalc rebuilds base salary lines
- v1 REST: PATCH /salary-runs/{id}/employees/{employeeId} accepting
monthly_salary (draft only, dry-run, idempotency key)
- Migration pair (NOT VALID + VALIDATE) adds set_run_salary to the
pending_operations op-type CHECK; base list verified against prod live
- Tests: service, staged tool, executor, cookie route, v1 route; spec
snapshot updated
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MP37pE3zk667nP6S766iJG
* fix(salary): harden set_run_salary per skeptic + CI findings
- Clear calculation_breakdown when the per-run salary changes so the
existing book preflights force a recalculation: a run can no longer
be booked with gross/tax derived from the old salary (skeptic R1)
- Enforce SALARY_OVERRIDE_MAX (10 MSEK) in the shared service and the
v1 body schema: closes the unbounded/1e307-overflow path that wrote
Infinity -> NULL -> 500 (skeptic R2)
- Promote gnubok_set_run_salary to the default catalog: a search-only
WRITE is uncallable on Claude.ai (update_customer lesson) while three
surfaces pointed agents at it; payload ceiling bumped 63.8K -> 64.4K
with a ledger entry, read-demotion left as its own change (skeptic R3)
- Granskning label type_set_run_salary in vocabulary.ts + sv/en (R4)
- Display-line refresh is fire-and-forget again (write already
committed; matches pre-refactor route behavior) and DB error details
carry the SQLSTATE code for Swedish error mapping
- v1 risk metadata aligned to 'medium'; NOT_DRAFT message now covers
salary edits, not just roster changes
- npm run apiskill:generate committed (CI apiskill:check failure)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MP37pE3zk667nP6S766iJG
* chore(migrations): rename set_run_salary pair past main's newest versions
origin/main gained 20260828120000 and 20260828154800 after this branch
staged 20260828110000/1; out-of-order versions are skipped at merge, so
the pair moves to 20260828160000/1 (byte-identical SQL, reference in the
VALIDATE header updated).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MP37pE3zk667nP6S766iJG
* chore: retrigger Supabase preview after migration-version repair
The preview branch tracked 20260828110000/1 before the rename to
20260828160000/1; the orphan rows are deleted from the preview branch's
schema_migrations (preview only, prod never saw those versions) and this
empty commit re-runs the tasks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MP37pE3zk667nP6S766iJG
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
a4ceaafa4f
commit
e8aa0670ca
@@ -8,7 +8,7 @@ description: >-
|
||||
transactions and reconciliation, payroll (lön), VAT/moms and financial
|
||||
reports, SIE import/export, documents, webhooks. Covers auth with
|
||||
gnubok_sk_ API keys, conventions (dry-run, idempotency, cursor
|
||||
pagination, scopes), and all 138 endpoints.
|
||||
pagination, scopes), and all 139 endpoints.
|
||||
---
|
||||
|
||||
<!-- GENERATED FILE, do not edit. Source: lib/api/v1 registry + scripts/api-skill/overlays. Regenerate with `npm run apiskill:generate`. -->
|
||||
@@ -142,7 +142,7 @@ call can undo it, e.g. invoice credit).
|
||||
|
||||
## Endpoint index
|
||||
|
||||
API version `2026-05-12`, 138 operations. Paths are shown without
|
||||
API version `2026-05-12`, 139 operations. Paths are shown without
|
||||
their `/api/v1` prefix (full base URL: `https://app.gnubok.se/api/v1`).
|
||||
|
||||
### Core (5)
|
||||
@@ -303,7 +303,7 @@ PUT /companies/{companyId}/employees/opening-balances : Bulk-set payroll cutover
|
||||
POST /companies/{companyId}/salary/vacation-year-close : Close a vacation year (semesterberedning + arsavslut) [scope:payroll:write risk:high idempotent dry-run]
|
||||
```
|
||||
|
||||
### Salary runs (18)
|
||||
### Salary runs (19)
|
||||
|
||||
Full detail: [references/salary-runs.md](references/salary-runs.md)
|
||||
|
||||
@@ -319,6 +319,7 @@ POST /companies/{companyId}/salary-runs/{id}/calculate : Calculate a draft salar
|
||||
GET /companies/{companyId}/salary-runs/{id}/employees : List per-employee results of a salary run [scope:payroll:read risk:low idempotent]
|
||||
POST /companies/{companyId}/salary-runs/{id}/employees : Add an employee to a draft salary run [scope:payroll:write risk:low idempotent dry-run reversible]
|
||||
GET /companies/{companyId}/salary-runs/{id}/employees/{employeeId} : Get one employee's payslip in a salary run [scope:payroll:read risk:low idempotent]
|
||||
PATCH /companies/{companyId}/salary-runs/{id}/employees/{employeeId} : Set this run's base salary for one employee [scope:payroll:write risk:medium idempotent dry-run reversible]
|
||||
DELETE /companies/{companyId}/salary-runs/{id}/employees/{employeeId} : Remove an employee from a draft salary run [scope:payroll:write risk:low idempotent dry-run reversible]
|
||||
POST /companies/{companyId}/salary-runs/{id}/employees/{employeeId}/lines : Add a payslip line to an employee in a draft salary run [scope:payroll:write risk:low idempotent dry-run reversible]
|
||||
POST /companies/{companyId}/salary-runs/{id}/generate-agi : Generate the Skatteverket AGI XML for a salary run [scope:payroll:write risk:medium idempotent]
|
||||
|
||||
@@ -809,6 +809,81 @@ Example response `200`:
|
||||
|
||||
---
|
||||
|
||||
### `PATCH /api/v1/companies/{companyId}/salary-runs/{id}/employees/{employeeId}`
|
||||
|
||||
**Set this run's base salary for one employee.**
|
||||
`scope:payroll:write · risk:medium · idempotent · dry-run · reversible`
|
||||
|
||||
Sets the per-run base salary (salary_run_employees.monthly_salary) that the calculation engine reads for this run. The employee master record is untouched, so each month's gross can differ from the employee's standard pay (variable owner salary). Draft-only; 0 is a valid nollkörning.
|
||||
|
||||
**Use when:** The employee's pay this month differs from their configured fixed salary: owners taking salary by need and capacity, one-off adjustments, or a deliberate zero month.
|
||||
**Do not use for:** Changing the employee's standard salary going forward: PATCH /employees/{id}. Editing individual payslip lines (tillägg/avdrag): the lines endpoints. Tax/avgifter overrides in review: not exposed on v1 yet.
|
||||
|
||||
**Pitfalls:**
|
||||
- Draft-only: 400 SALARY_RUN_EMPLOYEES_NOT_DRAFT once the run has advanced.
|
||||
- Run POST /calculate afterwards: gross, tax and totals reflect the new salary only after recalculation.
|
||||
- Do NOT edit the monthly_salary line item instead: recalculation rebuilds base salary lines from this per-run value.
|
||||
- For hourly employees the value is stored but gross derives from hours worked; the salary_type field in the response tells you which applies.
|
||||
|
||||
| Parameter | In | Type | Required | Notes |
|
||||
|---|---|---|---|---|
|
||||
| `companyId` | path | `string` | yes | |
|
||||
| `id` | path | `string` | yes | |
|
||||
| `employeeId` | path | `string` | yes | |
|
||||
|
||||
Request body:
|
||||
```ts
|
||||
{ monthly_salary: number }
|
||||
```
|
||||
|
||||
Example request:
|
||||
```json
|
||||
{
|
||||
"monthly_salary": 45000
|
||||
}
|
||||
```
|
||||
|
||||
Response `200`:
|
||||
```ts
|
||||
{
|
||||
data: {
|
||||
salary_run_employee_id: string,
|
||||
employee_id: string,
|
||||
salary_type: string,
|
||||
employment_degree: number,
|
||||
previous_monthly_salary: number,
|
||||
monthly_salary: number
|
||||
},
|
||||
meta: {
|
||||
request_id: string,
|
||||
api_version: string,
|
||||
next_cursor?: string,
|
||||
audit?: { voucher_number?: string, voucher_url?: string, audit_trail_url?: string, immutable_at?: string },
|
||||
partial_expansions?: string[]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example response `200`:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"salary_run_employee_id": "sre_a8f1…",
|
||||
"employee_id": "emp_77b2…",
|
||||
"salary_type": "monthly",
|
||||
"employment_degree": 100,
|
||||
"previous_monthly_salary": 30000,
|
||||
"monthly_salary": 45000
|
||||
},
|
||||
"meta": {
|
||||
"request_id": "req_…",
|
||||
"api_version": "2026-05-12"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `DELETE /api/v1/companies/{companyId}/salary-runs/{id}/employees/{employeeId}`
|
||||
|
||||
**Remove an employee from a draft salary run.**
|
||||
|
||||
Reference in New Issue
Block a user