feat(worklist): the next Skatteverket payment with bankgiro, OCR and due date under Att göra on Hem (#2435)

Part (a) of #2187. A twelfth worklist category, skattekonto_payment_due:
the earliest upcoming skattekonto charge whose sum exceeds the last synced
saldo, computed once in lib/worklist (server-side twin of the /skattekonto
page's Nästa dragning math) and rendered as one Betala row on Hem with the
shortfall, bankgiro 5050-1055, the OCR reference and the due date.

The row appears only when money has to move: a saldo that covers the charge
yields nothing, and no upcoming charge yields nothing. Ignored rows take
part, since Skatteverket draws them regardless of our flag. Without a
balance snapshot the full charge is the amount. Without an org number the
row keeps its bankgiro and date and drops the OCR.

No table, route or migration: /api/worklist/counts picks the category up
through getWorklistCounts, and Hem passes the computed row into the same
options wave as the expense payouts.

Part (b), a betalfil for the skattekonto payment, stays a follow-up: the
existing payment-file route is AGI-scoped.


Claude-Session: https://claude.ai/code/session_0179bdetHyofL6ATfQxB5wP5

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-09-08 21:25:01 +02:00
committed by GitHub
co-authored by Jakob Wennberg Claude Fable 5.1
parent a7dcaac6ad
commit 20925f6c65
10 changed files with 353 additions and 9 deletions
+35 -2
View File
@@ -30,7 +30,12 @@ import {
ShieldCheck,
Stamp,
} from 'lucide-react'
import type { ExpensePayoutDue, SuggestedMatch, WorklistCounts } from '@/lib/worklist/types'
import type {
ExpensePayoutDue,
SkattekontoPaymentDue,
SuggestedMatch,
WorklistCounts,
} from '@/lib/worklist/types'
/**
* AttGoraSection: the dashboard's unified worklist ("Att göra").
@@ -57,6 +62,8 @@ interface AttGoraSectionProps {
suggestedMatches: SuggestedMatch[]
/** People owed for registered, unpaid utlägg: one Betala row each. */
expensePayouts?: ExpensePayoutDue[]
/** The next skattekonto charge the balance does not cover: one Betala row. */
skattekontoPayment?: SkattekontoPaymentDue | null
expiringBankConnections?: ExpiringBankConnection[]
/**
* True while the setup checklist is open and the company has zero posted
@@ -119,6 +126,7 @@ export default function AttGoraSection({
worklist,
suggestedMatches,
expensePayouts = [],
skattekontoPayment = null,
expiringBankConnections = [],
emptyLedger = false,
hasActiveBankConnection = true,
@@ -218,7 +226,7 @@ export default function AttGoraSection({
counts.book_skattekonto > 0 ||
showInboxDocuments ||
matches.length > 0
const betalaRows = expensePayouts.length > 0
const betalaRows = expensePayouts.length > 0 || skattekontoPayment !== null
const granskaRows =
counts.supplier_invoice_approval > 0 ||
counts.verifikat_missing_document > 0 ||
@@ -396,6 +404,31 @@ export default function AttGoraSection({
<div>
<BandHeader>{t('band_betala')}</BandHeader>
<div>
{skattekontoPayment && (
<WorklistRow
href="/skattekonto"
icon={Landmark}
label={t('row_skattekonto_payment')}
detail={
skattekontoPayment.ocr
? t('row_skattekonto_payment_detail', {
bankgiro: skattekontoPayment.bankgiro,
ocr: skattekontoPayment.ocr,
date: formatDate(skattekontoPayment.due),
})
: t('row_skattekonto_payment_detail_no_ocr', {
bankgiro: skattekontoPayment.bankgiro,
date: formatDate(skattekontoPayment.due),
})
}
count={1}
badge={
<span className="text-xs tabular-nums text-muted-foreground">
{formatCurrency(skattekontoPayment.amount)}
</span>
}
/>
)}
{expensePayouts.map((p) => (
<WorklistRow
key={p.key}