4cf227001d
* fix(skattekonto): scope the sync and the avdragen-skatt rule for enskild firma
Two EF problems on the skattekonto surface:
1. Stuck pre-company rows. The sync never passed datumFrom, so SKV's
~555-day default lookback imported the owner's PERSONAL skattekonto
history from before the company existed. Those rows can never be
booked (no fiscal period covers them), never deleted (external
mirror), and had no ignore path: visible forever.
- syncSkattekonto now bounds the fetch at the company's earliest
fiscal_periods.period_start (new getEarliestFiscalPeriodStart in
period-service; no bound when no period exists yet). Applied
uniformly to EF and AB.
- New skattekonto_transactions.is_ignored column (migration
20260819080000, copies the transactions.is_ignored precedent:
CHECK that an ignored row has no journal_entry_id, partial index;
the existing company-scoped UPDATE policy already covers it) plus
PATCH /skattekonto/transaktioner/:id/ignore (409 on booked rows,
race-guarded on journal_entry_id IS NULL). Ignored rows leave the
default GET buckets; ignored_count is always reported and
include_ignored=1 returns the rows, surfaced as a count line +
"Ignorerade" band on /skattekonto and an Ignorera affordance with
confirm + Ångra on both /skattekonto and the /transactions inbox.
- PERIOD_LOCKED for a date before the first fiscal period now says
the row predates the company's bookkeeping and can be ignored,
instead of "lås upp perioden" (a dead end for those rows).
2. "Avdragen skatt" auto-mapped to 2710 for every entity type. For an
EF without employees that line is almost always A-skatt an outside
employer withheld from the owner's private salary, not the firm's
payroll liability. New data-driven skattekonto_rules.requires_employer
column (migration 20260819080100, set on the avdragen-skatt seed and
its per-company clones); the matcher gates such rules for an
enskild_firma unless company_settings.employer_registered is true
(the existing AGI gate signal, fetched in the same settings query).
Gated rows take the NO_COUNTER_ACCOUNT path with a distinct hint;
AB and employer-registered EF keep 2710 unconditionally. Regression
guard pins EF preliminärskatt to 2013.
The nightly sync upsert excludes is_ignored so it can never silently
un-ignore a row. New pg tests for the CHECK + RLS need a test:pg run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(skattekonto): clamp datumFrom to the SKV window, gate ignored rows, widen the employer signal
Review fixes on the EF-scoping PR:
- sync: clamp datumFrom to max(earliestPeriodStart, today - 555 days); a
bookkeeping start older than SKV's 555-day default is omitted entirely,
since sending it would widen the window past the default and anything
older than ~915 days fails the whole sync with felkod 2. The misleading
"no-op for AB" comment is corrected and boundary tests added.
- booking/match: an ignored row now throws a typed ROW_IGNORED error
(409) before any draft is created or link is written, in both
bokforSkattekontoTransaction and matchSkattekontoToEntry.
- page: the Nasta dragning / shortfall math re-includes ignored upcoming
charges (SKV draws them regardless of our ignore flag) while the
work-list buckets keep excluding them.
- employer gate: treat employer_registered ?? pays_salaries as the
signal (same fallback as lib/tax/deadline-config.ts), so an EF that
attested pays_salaries keeps 2710 for avdragen skatt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(skattekonto): assert the is_ignored RLS toggle inside the rolled-back transaction
withUserContext always rolls back (tests/pg/setup.ts), so the previous test
wrote inside it and read the pre-write value back on the pool connection:
it failed against a correct policy and would have passed against a missing
one only by accident. The assertions now live inside the same transaction,
pin rowCount=1 (an RLS-filtered UPDATE silently matches zero rows), and a
new test pins the negative: a non-member's UPDATE matches zero rows.
Falsification-verified against a real Postgres: dropping the UPDATE policy
makes both tests fail; with the policy they pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
32 lines
1.6 KiB
SQL
32 lines
1.6 KiB
SQL
-- Migration: skattekonto_rules.requires_employer
|
|
--
|
|
-- The 'avdragen skatt,personalskatt,a-skatt' seed rule maps unconditionally
|
|
-- to 2710 (Personalskatt) for every entity type. For an aktiebolag that is
|
|
-- always right: a personalskatt line on the company's skattekonto implies
|
|
-- payroll. For an enskild firma WITHOUT registered employees it is wrong:
|
|
-- the EF's skattekonto is the owner's personal account, and "Avdragen skatt"
|
|
-- there is almost always A-skatt an outside employer withheld from the
|
|
-- owner's personal salary, not an affarshandelse of the firm. Crediting 2710
|
|
-- would fabricate a payroll liability the firm never had.
|
|
--
|
|
-- The distinguishing signal is dynamic per company (does it actually employ
|
|
-- anyone?), which the static counter_account_ef column cannot express. So the
|
|
-- gate is data-driven: rules flagged requires_employer only apply to an
|
|
-- enskild firma when company_settings.employer_registered is true (the same
|
|
-- signal that gates AGI reminders, 20260717151000). An AB is unaffected; an
|
|
-- employer-registered EF keeps 2710. The code gate lives in
|
|
-- extensions/general/skatteverket/lib/skattekonto-booking.ts.
|
|
--
|
|
-- Follows the 20260817120100 precedent: update the NULL-company system seed
|
|
-- row AND any per-company clones of the same pattern.
|
|
|
|
ALTER TABLE public.skattekonto_rules
|
|
ADD COLUMN IF NOT EXISTS requires_employer BOOLEAN NOT NULL DEFAULT false;
|
|
|
|
UPDATE public.skattekonto_rules
|
|
SET requires_employer = true
|
|
WHERE pattern = 'avdragen skatt,personalskatt,a-skatt'
|
|
AND requires_employer IS DISTINCT FROM true;
|
|
|
|
NOTIFY pgrst, 'reload schema';
|