Files
accounted/extensions/general/mcp-server/skills/payroll-monthly.ts
T
Jakob WennbergandClaude Opus 4.8 c74b19df1b Accounted rebrand + swarm-skill cleanup + bank-reconciliation fixes (#643)
* feat(reconciliation): close the bank-feed loop on voucher links and re-tag mis-typed opening balances

Two related fixes to bank reconciliation correctness:

1. Auto-reconcile on voucher link. Linking an invoice or supplier invoice to
   an existing voucher previously advanced only the invoice — the bank
   transaction that paid it kept sitting in the Transactions inbox with a null
   journal_entry_id. linkInvoiceToVoucher / linkSupplierInvoiceToVoucher now
   call autoReconcileTransactionForLinkedVoucher (lib/reconciliation), which
   links the bank transaction to the same verifikat when exactly one unbooked
   line matches it. Best-effort and post-commit: a failure here never fails the
   link. The result surfaces reconciledTransactionId; the inbox row leaves the
   list and the UI shows link_success_tx_reconciled.

2. Re-tag mis-typed opening balances. getReconciliationStatus and the GL-line
   matching RPCs identify a cash account's ingående balans solely by
   journal_entries.source_type='opening_balance'. Companies migrated from other
   systems often booked the bank IB as an ordinary voucher (source_type
   'import' or 'manual'), so it was never excluded and surfaced as a phantom
   reconciliation difference equal to the opening balance. Adds:
   - migration mark_entry_as_opening_balance: a GUC-gated carve-out in the
     immutability trigger plus a SECURITY DEFINER RPC that validates the entry
     (balance-sheet lines only, dated on a fiscal-period boundary), flips the
     source_type, and writes an audit row — no blanket data sweep.
   - POST /api/reconciliation/bank/mark-opening-balance + MarkOpeningBalanceSchema.
   - BankReconciliationView action to trigger it from the IB diff.

The gnubok_create_voucher executor now accepts a typed is_opening_balance flag
and derives source_type='opening_balance' only after validating class 1/2 lines
on the period start, so new IBs land correctly typed.

Covered by lib/reconciliation auto-reconcile tests, voucher-executors tests,
and a mark-entry-as-opening-balance pg-real test.

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

* chore: rebrand gnubok → Accounted and prune swarm agent skills

Product rebrand and skills housekeeping. No runtime behaviour change.

Rebrand: replace user-visible "gnubok" with "Accounted" across docs, READMEs,
in-code comments, doc-site content, MCP skill/resource prose, and the
gnubok-mcp package description. The MCP resource URI scheme is moved gnubok://
→ Accounted:// consistently across resource registrations, the event-type
comment, and the resource/skill tests. Deliberately preserved as stable
identifiers (NOT rebranded): the gnubok-company-id cookie, gnubok_sk_ / gnubok_inv_
token prefixes, the gnubok-mcp npm bridge name, and the AGI <gem:Programnamn>
value (kept 'gnubok' per its source comment — it is the software identifier sent
to Skatteverket and must not churn across visual rebrands).

Skills: remove the 27 swarm-* agent SKILL.md atoms (no longer used; already
absent from the agent_atom_registry in prod), refresh the remaining skill docs,
add the .claude/rules/ path-scoped rule set, and regenerate the
seed_agent_atom_bodies migration + .skill-body-manifest.json via
`npm run skills:generate` so the DB-backed skill bodies match the trimmed set.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:52:01 +02:00

123 lines
6.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Skill } from './types'
const body = `# Monthly Payroll — Accounted
Salary run + AGI filing for one calendar month.
## When to use
- "Run payroll for [month]"
- "Lönekörning [månad]"
- "Generate AGI"
- Once per month, **before payment_date**
## Statutory deadlines
- **AGI (arbetsgivardeklaration):** 12th of the **next month** (17th in January and August). E.g. payroll for March → file AGI by 12 April.
- **Skatt + sociala avgifter payment:** same deadline as AGI.
- Skattekontot must be in funds by deadline (SFL 62 kap. 3 §).
## Workflow
### Step 1 — Verify employees are set up
\`gnubok_list_employees\` returns all active employees. For each, the system needs:
- **Personnummer** (last 4 stored, full encrypted)
- **monthly_salary** (or hourly_rate + estimated hours)
- **employment_degree** (1–100 %)
- **tax_table_number** + **tax_column** (skattetabell + kolumn from Skatteverket)
- **employment_type** (\`tjänsteman\`, \`arbetare\`, etc.) — drives BAS account choice (7210 vs 7010)
If anything is missing, the user fixes it in the web UI before running payroll.
### Step 2 — Create the salary run
\`gnubok_create_salary_run({ period_year, period_month, payment_date })\`
- Creates a \`salary_runs\` row with status \`draft\`
- Adds **all active employees** with their base salary line (item_type \`monthly_salary\` or \`hourly_salary\`)
- Returns the run ID + employee count
- Idempotent on \`(company_id, period_year, period_month)\` — re-calling errors with "Salary run already exists for this period"
### Step 3 — Add OB-tillägg, traktamente, förmåner (if any)
Variable lines (overtime, weekend supplement, milage, traktamente, förmåner) are added in the web UI per-employee. There's no MCP tool yet for these — guide the user there.
### Step 4 — Calculate
\`gnubok_calculate_salary_run({ salary_run_id })\`. Computes per employee:
- **Bruttolön** (gross): sum of taxable salary lines
- **Skatteavdrag**: tax-table lookup (skattetabell + kolumn → table column for the gross level)
- **Nettolön** (net): bruttolön − skatteavdrag
- **Sociala avgifter (arbetsgivaravgifter)**: 31.42 % of bruttolön (standard 2025). Reduced rates apply to specific age groups — always check the current statutory rates before relying on these:
- **Born 1937 or earlier**: **0 %** — no avgifter at all (oldest cohort, never paid into the modern pension system). Easy to miss; the BAS journal entries for 7510/2730 simply don't apply for these employees.
- **Age 66+ on 1 January of the income year (67+ from income year 2026)**: 10.21 % (only ålderspensionsavgift). The threshold rises with the riktålder; verify the cohort year for the current run rather than hard-coding a birth year.
- **växa-stöd / temporary youth reduction**: ages 19–23, salary ≤ 25 000 SEK/month, capped duration. The exact rate and window vary year-over-year (e.g. 20.81 % during 1 Apr 2026 – 30 Sep 2027 per Prop. 2025/26:34) — confirm against Skatteverket's current published table before applying.
- **Semesterlöneskuld** (vacation accrual): 12 % of bruttolön (default). Booked monthly to 2920.
- **Förmåner** (benefits): employer-paid taxable amounts (bilförmån, kostförmån, etc.) — added to skattegrundande lön but not to nettolön payment.
Errors at this stage usually mean missing tax-table data — fall back to \`getDefaultTaxColumn(personnummer, year)\` heuristics or prompt user.
### Step 5 — Review
\`gnubok_get_salary_run({ salary_run_id })\` — full breakdown including \`calculation_breakdown\` showing step-by-step formulas. The user reviews per-employee in web UI.
\`gnubok_get_salary_journal({ year })\` — annual rollup for sanity check.
### Step 6 — Approve & book (web UI)
The user marks the run \`approved\` → \`paid\` → \`booked\` in the web UI. Booking creates the JE:
- Debit **7210** (lön tjänstemän) or **7010** (lön arbetare): bruttolön
- Debit **7510** (sociala avgifter): \`avgift_base × applicable_rate\` — **per employee**, using the rate from Step 4 (default 31.42 %, or a reduced rate when applicable: 10.21 % for 66+, växa-stöd, etc.)
- Credit **2710** (källskatt): skatteavdrag
- Credit **2730** (lagstadgade arbetsgivaravgifter): same amount as the 7510 debit (the avgift cost is the same number as the avgift liability)
- Credit **2920** (semesterlöneskuld): 12 % × bruttolön (debit 7290 to balance)
- Credit **1930** (bank): nettolön (when paid)
When a run mixes full-rate and reduced-rate employees, the 7510/2730 lines are summed across all employees — the *total* avgift line equals \`Σ(per-employee avgift_base × per-employee rate)\`, **not** \`Σ bruttolön × 31.42 %\`. \`gnubok_calculate_salary_run\` already does this aggregation.
### Step 7 — Generate AGI
\`gnubok_generate_agi({ salary_run_id })\`. Run must be in \`review\`/\`approved\`/\`paid\`/\`booked\` status (past draft).
Returns \`{ message, period, employee_count, download_url }\`. The XML conforms to Skatteverket's AGI format and is stored 7 years per BFL. Download from \`/api/salary/runs/{id}/agi/xml\` and upload to Skatteverket e-tjänst.
## Critical rules
- **Skatteavdrag is mandatory.** Never pay gross. Skatteverket charges 100% penalty for missing avdrag.
- **Sociala avgifter are 31.42 % even if salary is in EUR.** Convert to SEK at payment date for the avgift base.
- **Semesterlöneskuld** must be reserved monthly, not at year-end. 2920 grows by 12 % of every month's bruttolön.
- **Förmånsbeskattning** (benefit tax) is required even if not in cash. Bilförmån, kostförmån, sjukvårdsförsäkring all count.
- **Karensavdrag** (sick day deduction): first day of sickness is generally without pay; 80 % from day 2. Specific rules — fall through to \`swedish-payroll\` reference if unsure.
## Common errors
- **Run already exists**: idempotency on (company, year, month). Find the existing run with \`gnubok_get_salary_run\`.
- **Tax table column wrong**: defaults to column 1 if not set, which is too high for most employees. Fix on employee record.
- **AGI before booking**: works (status check is past-draft, not booked) — but you should book first so the JE matches what AGI reports.
## Tools
- \`gnubok_list_employees\` — verify setup
- \`gnubok_create_salary_run\` — stage new monthly run
- \`gnubok_calculate_salary_run\` — compute tax + avgifter + accrual
- \`gnubok_get_salary_run\` — review breakdown
- \`gnubok_get_salary_journal\` — annual rollup
- \`gnubok_generate_agi\` — produce AGI XML for filing
`
export const payrollMonthlySkill: Skill = {
slug: 'payroll-monthly',
name: 'Monthly Payroll',
summary: 'Monthly salary run + AGI: employee setup, calculation, sociala avgifter, semesterlöneskuld, booking, AGI XML.',
tags: ['monthly', 'payroll', 'agi', 'compliance'],
body,
tier: 'workflow',
// Only relevant when the company actually has employees. EF without payroll
// (most sole traders) shouldn't see this in the discovery list.
applicability: { entity_type: 'both', requires: ['employees'] },
}