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>
123 lines
6.8 KiB
TypeScript
123 lines
6.8 KiB
TypeScript
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'] },
|
||
}
|