main
7 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e113e9c099 |
fix(vat,documents): EU reverse-charge packs feed ruta 20/21; daily reanchor cron for floating supplier-invoice underlag (#2095)
Two user reports (Anders, 2026-08-25 + 2026-08-29): 1. The seeded standardmallar "Inkop EU-varor/-tjanster, omvand moms 25%" booked the cost on 4010/6540, which no momsdeklaration ruta reads, so the fiktiv moms filled ruta 30/48 while ruta 20/21 (inkopsvarde) stayed 0; Skatteverket rejects that (FK004, ML 13 kap). The packs now book directly on the basis accounts 4515/4535 (ACCOUNT_RUTA -> ruta 20/21); the transaction-picker path already skips its own basis emission for basis debit accounts, so no double counting. Regression test pins every reverse-charge pack to a 44xx/45xx business debit. Prod rows update via the existing pack sync cron (upsert on pack_slug). 2. A kontantmetod payment verifikat stayed "Underlag saknas" although the invoice PDF was attached and eligible on every static condition: the inline anchorSupplierInvoiceDocument silently did nothing (prod case 2026-08-28, verified in audit_log: no document_attachments update between the payment booking and the user's manual re-upload). The helper now verifies the guarded update actually matched a row instead of claiming success on zero rows, logs its silent bail branches, and a new daily cron (/api/documents/reanchor/cron) re-runs the anchor for any floating retained document with a posted verifikat, replacing the pattern of one-off repair migrations (20260727180000, 20260824150000). The sweep names the FK in its embed and is idempotent; locked/closed periods are skipped as before. Claude-Session: https://claude.ai/code/session_01Jj6Rg1ViyFRej55gbxLVgj Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
436cbf5304 |
fix(skattekonto): route AGI draw back to 2731 to match salary module (#1905)
* fix(skattekonto): route AGI draw back to 2731 to match salary module (#1870) Migration 20260519160000 moved the skattekonto AGI seed to 2730 while the salary module kept crediting 2731, splitting the employer-contribution liability across two accounts that never net at account level (both carry SRU 7231, so only huvudbok reconciliation exposes the drift). Revert the system seed to 2731: BAS 2026 defines 2731 as the reported-but-unpaid arbetsgivaravgift liability (the accrual account is 2940), and the salary ore-residual logic is built around 2731. Historical 2730 debits since 2026-05-19 are left for per-company reclass verifikat; the migration touches the system seed only. Fixes #1870 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(skattekonto): bump migration version to avoid collision with 20260825120000_create_company_for_user Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(payroll): align remaining 2730 guidance surfaces on 2731 (#1870) Skeptic regression finding: companies booking salary manually were taught 7510/2730 by in-product guidance, so the seed revert alone would re-create the #1870 split mirrored for them. Align every guidance surface on 2731: - packs/loneutbetalning.yaml legal_note - MCP payroll-monthly skill (booking recipe and rate notes) - swedish-payroll SKILL.md + references/bas-7xxx.md (2731 convention, 2730 group-account alternative, never mixed; accrual is 2940) + regenerated agent atom seed (skills:generate -> 20260825180001) - public/docs/systemdokumentation-mall.md Also addresses the compliance review finding that the swedish-payroll skill contradicted the migration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
fe1ff8649b |
fix(bookkeeping): drop non-standard account 2012, book EF F-skatt on 2013 (#1409) (#1489)
Primary-source check against bas.se (BAS 2026 v2): the official kontoplan has no account 2012; the enskild firma equity block is 2010, 2011, 2013, 2017, 2018, 2019. 2012 'Avräkning för skatter och avgifter' is a program convention (Visma, Bokio, Björn Lundén), not standard BAS, and a non-standard account in BAS_REFERENCE leaks via the backfill into charts, SIE export and SRU filing. - remove 2012 from class-2-equity-liabilities.ts, with a tombstone comment - migration retargets 'Preliminär F-skatt (EF)' lines 2012 -> 2013 (system row plus any clones still carrying the seeded shape) - pin 2012's absence in bas-ef-equity-accounts.test.ts (2113 precedent) - correct the swedish-year-end-closing references that motivated #1388, regenerate atom seed migration Companies whose charts already got 2012 backfilled keep it: existing history stays valid; only future template use books 2013. Closes #1409 Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3ff12faa77 |
fix(bookkeeping): reverse-charge VAT in booking templates is 25% of the base, not 20% (#1494)
* fix(bookkeeping): reverse-charge VAT in booking templates is 25% of the base, not 20%
applyTemplate() extracted VAT out of the total (rate/(1+rate)) for every
vat line, including the fiktiv-moms pair of reverse-charge templates.
Under omvand skattskyldighet the supplier charges no VAT, so the total IS
the beskattningsunderlag: on 807.99 kr the seeded EU-purchase template
booked 161.60 kr (20%) on 2614/2645 instead of 202.00 kr (25%),
understating Ruta 30-32 and Ruta 48 on the momsdeklaration.
Fiktiv-moms lines (2614/2624/2634 output, 2615/2625/2635 import,
2645/2647 input) now compute amount x rate on top of the base.
deriveTemplateLinesFromBooking ("Spara som mall") gets the mirror fix:
RC legs no longer inflate the derived total (they net to zero), and RC
rates snap against the base, so a correct RC booking round-trips.
Counterparty/SIE learned patterns already strip RC legs and regenerate
them via generateReverseChargeLines with the gross base; those paths
were correct and are unchanged.
User-reported: "Er automatiska utrakning ar pa 20%, inte 25%".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(bookkeeping): use roundOre for template VAT rounding (guard ratchet)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(bookkeeping): clarify fiktiv-moms comment: total is the base, booked amount is the VAT
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
a3d8fe733c |
fix(packs): representation template named itself avdragsgill while booking 6072 (#1396)
Raised by the Swedish compliance bot on #1395. The template called itself "Representation (avdragsgill, 25% moms)" and labelled its cost line "Representation avdragsgill", but booked to 6072, which BAS 2026 defines as Representation, EJ avdragsgill. The swedish-vat skill lists that exact confusion under Representation errors: "Not separating avdragsgill/ej avdragsgill on correct accounts (6071 vs 6072)". The account was right and the words were wrong. Meal representation stopped being income-tax deductible in 2017, so 6072 is where the cost belongs; only enklare fortaring (max 60 kr per person) is still deductible, on 6071. Renamed to "Representation, maltid (ej avdragsgill)" and relabelled to match. VAT moves from 25% to the 12% restaurang-och-catering rate our own static representation_external template in booking-templates.ts already used, so the two template systems stop contradicting each other, with the paired net ratio 1/1.12. A legal_note now carries the 300 kr per person VAT cap, which this format cannot compute because a template sees only a total and never a participant count, plus the reminder that the rate follows the underlag rather than the template. Correcting the record on one point: the seeded ratio 0.8 was NOT a phantom 80% deductibility rule, as the bot suggested and as I first repeated. 1/1.25 = 0.8, so it was the net-of-VAT fraction pairing with the 25% rate, and the arithmetic balanced. Only the naming and the missing cap were wrong. The ratio is now commented in the file so the next reader does not make the same misreading. Slug deliberately unchanged though it still reads "avdragsgill": a slug is an identifier, not a label (packs/README.md). Renaming it would retire this template and insert a new one, breaking every company's booking_template_usage history. The drift guard caught the rename, which exposed that the test joined packs to seeded rows by NAME. That is the same fragility that made pack_slug the sync's key, so INTENTIONAL_DIVERGENCES now records each entry's seededName explicitly. Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5b9605d8e9 |
fix(packs): repair the four broken system templates the validator found (#1388)
Phase 2a quarantined four defects rather than guessing at Swedish accounting. Each is now resolved against a domain source. KNOWN_BROKEN is empty. Löneutbetalning could never post. It debited 2710 @0.3 + 2920 @0.12 + 7010 @1.0 against a single 1.0 credit, totalling 1.42x the amount, so the balance trigger would reject every entry built from it. Rebuilt per the swedish-payroll skill: Debit 7010 gross, Credit 2710 tax, Credit 1930 net. The 2920 semesterlöneskuld line is gone because vacation accrual is its own verifikat (7290/2920), and a legal_note now says the 30% split is schablon and must be adjusted to the actual skatteavdrag. Periodiseringsfond avsättning/återföring referenced account 2113. Per swedish-year-end-closing the year-tagged block is 2120-2129 (2126 = tax year 2026), so 2113 was the fund for tax year 2013: long since reversed and absent from BAS 2026. Both now use 2110 Periodiseringsfonder, which does not rot annually, with a legal_note pointing at the year-tagged accounts for a company that tracks funds per year. Preliminär F-skatt (EF) turned out to be RIGHT, and the reference was wrong. Account 2012 "Avräkning för skatter och avgifter" was simply missing from lib/bookkeeping/bas-data (the file jumps 2011 -> 2013), while the swedish-year-end-closing skill uses it in two places as an enskild firma equity sub-account. That is not cosmetic: account-backfill.ts only seeds accounts present in BAS_REFERENCE, so any entry touching 2012 failed with AccountsNotInChartError. Added it with the equity SRU code its siblings share, and a description separating it from 1630, which carries a confusingly similar name on the asset side. The port test now distinguishes deliberate divergence from accidental drift: a pack not listed in INTENTIONAL_DIVERGENCES must still match the seeded JSONB exactly, and a listed pack must actually differ, so neither an unnoticed edit nor a stale entry can survive. Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
df34cae9bf |
feat(packs): konteringspaket as validated data files (phase 2a) (#1386)
* feat(packs): konteringspaket as validated data files, ported losslessly The 26 system booking templates lived inside migration 20260413160000. Under the never-modify-a-shipped-migration rule that froze them: correcting a wrong BAS account or a Swedish typo needed a whole new migration, and nothing checked that a seeded account existed in the chart or that a template balanced. #1321 was exactly that failure with seeded chart names. They are now one YAML file per pattern under packs/, with a Zod contract and a CI gate. A correction becomes a one-line edit plus a green run. The port is proven lossless, not asserted. The test fixture was read out of a Postgres with all 548 migrations applied, so it is the exact JSONB production holds; lib/packs/__tests__/port-is-lossless.test.ts asserts the YAML reproduces it by value. Phase 2b can swap the seeded rows for the loader as a no-op. The gate checks what makes a pack CORRECT, not just well-formed, because #1321 was structurally valid and still wrong: every account must exist in BAS 2026, and every pack must balance at five probe amounts through the real applyTemplate() rather than a reimplementation. Account numbers validate through lib/invariants, so a pack cannot disagree with the API or the SIE importer about what an account number is. Doing that immediately found four pre-existing breakages in the shipped templates: loneutbetalning debits total 1.42x the amount against a 1.0 credit: it can never post periodiseringsfond-avsattning-ab account 2113 is not in BAS 2026 and is not periodiseringsfond-aterforing-ab seeded into any company chart preliminar-f-skatt-ef account 2012, same problem These are quarantined in KNOWN_BROKEN, not fixed and not hidden: a quarantined pack's findings are warnings, any NEW finding fails the build, and the validator fails if a quarantined pack turns out to be clean, so the list may only shrink. Each is a Swedish accounting content change to a user-facing template, which deserves its own review rather than riding along inside a file-format change. Five shipped descriptions contain em dashes, preserved verbatim and pinned by a test: a lossless port must not silently rewrite user-visible strings. js-yaml is promoted from a transitive dependency to a declared one (MIT, already in node_modules), so the catalogue does not depend on it by accident. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(deps): regenerate package-lock.json with npm 10 to match CI `npm ci` failed on every job with "Missing: @swc/helpers@0.5.23 from lock file". The lockfile was written by local npm 11.6.0; CI runs npm 10.8.2 on node 20, and npm 11 emits a tree npm 10 reads as out of sync. Regenerated with `npx npm@10 install --package-lock-only`, which cuts the diff from a sprawling rewrite down to the three entries this branch actually adds (js-yaml, @types/js-yaml, and the @swc/helpers entry npm 11 had dropped). Verified with `npx npm@10 ci --dry-run`. This is the documented gotcha for this repo: regenerate lockfiles with npx npm@10, never with a local npm 11. 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> |