* fix(rot-rut): payment-side booking, reminders and claim completeness
Follow-ups from the 2026-08-17 ROT/RUT audit (dev_docs/rot_rut_audit_2026_08_17.md).
Payment side (fakturamodellen: the customer pays total minus avdraget, the
rest is a 1513 receivable on Skatteverket):
- createInvoicePaymentJournalEntry without an explicit paymentAmount used to
book invoice.total on 1930/1510. Every no-lines mark-paid path (MCP
mark_invoice_as_paid, v1 API, no-body dashboard route, Stripe) settles the
outstanding amount, so on a ROT/RUT invoice 1510 went negative by the
deduction and 1930 was overstated; same defect for any previously part-paid
invoice. It now books the outstanding amount (remaining_amount, else total
minus paid_amount); a fully outstanding invoice keeps the total_sek path.
- proposePaymentLines had no deduction awareness: the payment dialog
pre-filled D1930 total / K1510 total, which the settlement plan rejected as
an overpayment, so a ROT/RUT invoice could not be marked paid from the UI.
Accrual: bank + 1510 carry total minus avdrag; cash method: bank gets the
customer share, 1513 the avdrag, revenue + moms in full. Foreign invoices
without a booking rate refuse (1513 is a kronor receivable). Dialog passes
deduction_total.
- Reminders and dröjsmålsränta were computed on invoice.total: a privatperson
was dunned for the Skatteverket share and charged interest on it. New
reminderPrincipal() = the invoice's "Att betala" (öre-rounded total minus
avdrag) drives the processor's interest base and all three templates.
Claim completeness (HUSFL 2009:194: art av arbete + antal arbetstimmar):
- work_type and labor_hours were optional at creation but hard blockers at
begäran-file time, when the invoice is numbered, booked and paid and cannot
be edited. validateDeductionLines() now requires a same-kind arbetstyp and
hours > 0 (schablontjänster exempt) on every deduction line; wired into
validateInvoice, CreateInvoiceItemSchema (field-level issues) and the
editor schema with inline errors under the ROT/RUT strip. Fixed the
labor_hours register (valueAsNumber overrode setValueAs: an emptied field
became NaN and failed validation with no visible error). The Underlag card
now shows whenever any row is flagged, matching the payload/server predicate.
Yearly ceilings:
- COMBINED_MAX 75 000 kr: ROT + RUT share one ceiling per person (ROT capped
at 50 000 inside it). deductionCapWarnings() carries the per-kind and the
combined check plus optional prior-year totals; validateInvoice forwards
them; the editor uses the same helper and fetches what the customer has
already been granted in the invoice year (per customer, warning only).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(rot-rut): treat remaining_amount left at DEFAULT 0 as unmaintained when booking a payment
Rows written by paths that bypass buildInvoiceWriteData (imports, sandbox
seed, legacy migrations) carry remaining_amount = 0 while unpaid; prod has
~330 such open invoices. Booking 0 would have failed the engine's positive-
amount rule, so the outstanding helper derives total - paid - deduction when
the stored value is not positive. Test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(rot-rut): review follow-ups on #1652
- ROT/RUT completeness moves to the invoice-level schema (CreateInvoiceSchema /
UpdateInvoiceSchema share one refine) so it only applies to real invoices
and skips text rows; the editor gates its mirror on the document type via
a ref. Tests moved accordingly (CodeRabbit).
- Prior-year deduction lookup follows the PAYMENT year (paid_at, else
invoice_date for open invoices), paginates via fetchAllRows, and clears the
total on a failed request instead of leaving a stale one.
- rot-rut-file derives its schablon flags from SCHABLON_WORK_TYPES so the
validator and the generator cannot drift.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(rot-rut): pick the prior-year deductions client-side (phantom-columns ceiling)
The runtime-built .or() filter counted as an unresolvable query expression
for the no-phantom-columns guard. A customer has few deduction invoices, so
fetch them all and select the payment year in code.
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>