fix(worklist): count unbooked skattekonto rows in the Att göra badge and Hem list (#2180) (#2227)

The Transaktioner inbox lists unbooked skattekonto rows next to unbooked
bank rows, but the sidebar badge and the Hem "Att göra" list counted bank
rows only, so a migrated tax account waited in the inbox unseen.

- lib/worklist: new category book_skattekonto with the inbox's own
  predicate (status = 'booked', no verifikat, not ignored); aggregate
  includes it in counts and total.
- Hem: a "Bokföra skattekontohändelser" row under Bokför, deep-linking to
  /transactions?source=skatteverket.
- Sidebar badge hook: the same third head-count query, summed into the
  /transactions badge.
- DashboardNav subscribes to skattekonto_transactions realtime changes;
  migration adds the table to the supabase_realtime publication so a
  booked or ignored row drops the badge without a manual refresh.

Closes #2180


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

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-03 17:17:41 +02:00
committed by GitHub
co-authored by Jakob Wennberg Claude Fable 5.1
parent aeff998b90
commit 1e91c126ff
11 changed files with 136 additions and 9 deletions
+15 -3
View File
@@ -4,7 +4,12 @@ import useSWR from 'swr'
import { createClient } from '@/lib/supabase/client'
export interface WorklistBadges {
/** Unbooked bank transactions: same predicate as lib/worklist countUnbookedTransactions. */
/**
* Rows waiting in the Transaktioner inbox: unbooked bank transactions
* (lib/worklist countUnbookedTransactions) plus unbooked skattekonto rows
* (countUnbookedSkattekontoRows). The badge sits on /transactions, which
* lists both, so the number must cover both (#2180).
*/
uncategorized: number
/** Agent-staged operations awaiting review: same predicate as countPendingOperations. */
pendingOperations: number
@@ -26,13 +31,20 @@ export function useWorklistBadges(companyId: string | null | undefined) {
companyId ? ['worklist-badges', companyId] : null,
async ([, id]: [string, string]) => {
const supabase = createClient()
const [tx, ops] = await Promise.all([
const [tx, skv, ops] = await Promise.all([
supabase
.from('transactions')
.select('id', { count: 'exact', head: true })
.eq('company_id', id)
.is('is_business', null)
.eq('is_ignored', false),
supabase
.from('skattekonto_transactions')
.select('id', { count: 'exact', head: true })
.eq('company_id', id)
.eq('status', 'booked')
.is('journal_entry_id', null)
.eq('is_ignored', false),
supabase
.from('pending_operations')
.select('id', { count: 'exact', head: true })
@@ -40,7 +52,7 @@ export function useWorklistBadges(companyId: string | null | undefined) {
.eq('status', 'pending'),
])
return {
uncategorized: tx.error ? 0 : (tx.count ?? 0),
uncategorized: (tx.error ? 0 : (tx.count ?? 0)) + (skv.error ? 0 : (skv.count ?? 0)),
pendingOperations: ops.error ? 0 : (ops.count ?? 0),
}
},