cbe5580886
An out-of-pocket purchase differs from any other receipt only in the credit account, so the Underlag pane now asks one question for an unmatched underlag (Företaget / Jag, privat / En anställd / Ingen ännu) and books a privately paid receipt in place through POST /api/expense-claims, replacing the "Andra sätt att bokföra" dropdown and the deep link into the two-step wizard. The verifikat editor stays reachable below as the escape hatch (BFL 5 kap 6-7 §). The person owed surfaces in Att göra under a new Betala band, one row per person (lib/worklist expense_payout, counted in the total and exposed to agents through the attention resource). The Utlägg nav row is gated on existing claims, the same hybrid gate as Körjournal, since the entry point for a new utlägg is now the Underlag pane. Claude-Session: https://claude.ai/code/session_01P8YsvPqjfGxGZUkGeBVUWQ Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
71 lines
2.7 KiB
TypeScript
71 lines
2.7 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import type { SupabaseClient } from '@supabase/supabase-js'
|
|
|
|
vi.mock('../categories', () => ({
|
|
countUnbookedTransactions: vi.fn().mockResolvedValue(4),
|
|
countUnbookedSkattekontoRows: vi.fn().mockResolvedValue(7),
|
|
countInboxDocuments: vi.fn().mockResolvedValue(6),
|
|
countSuggestedMatches: vi.fn().mockResolvedValue(2),
|
|
countSupplierInvoicesAwaitingApproval: vi.fn().mockResolvedValue(1),
|
|
countVerifikatMissingDocument: vi.fn().mockResolvedValue(3),
|
|
countOverdueInvoices: vi.fn().mockResolvedValue(5),
|
|
countDeadlinesNeedingAction: vi.fn().mockResolvedValue(1),
|
|
countPendingOperations: vi.fn().mockResolvedValue(2),
|
|
countReconciliationDue: vi.fn().mockResolvedValue(1),
|
|
countExpensePayoutsDue: vi.fn().mockResolvedValue(2),
|
|
}))
|
|
|
|
import { getWorklistCounts } from '../aggregate'
|
|
|
|
const supabase = {} as SupabaseClient
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
describe('getWorklistCounts', () => {
|
|
it('aggregates every category', async () => {
|
|
const { counts } = await getWorklistCounts(supabase, 'company-1')
|
|
expect(counts).toEqual({
|
|
book_transaction: 4,
|
|
book_skattekonto: 7,
|
|
inbox_document: 6,
|
|
suggested_match: 2,
|
|
supplier_invoice_approval: 1,
|
|
verifikat_missing_document: 3,
|
|
overdue_invoice: 5,
|
|
deadline_action: 1,
|
|
pending_operations: 2,
|
|
reconciliation_due: 1,
|
|
expense_payout: 2,
|
|
})
|
|
})
|
|
|
|
it('takes the suggested-match count from a caller-supplied list instead of rescanning', async () => {
|
|
const { countSuggestedMatches } = await import('../categories')
|
|
const matches = [{ transactionId: 't1' }, { transactionId: 't2' }, { transactionId: 't3' }] as never[]
|
|
const { counts } = await getWorklistCounts(supabase, 'company-1', {
|
|
suggestedMatches: Promise.resolve(matches),
|
|
})
|
|
expect(counts.suggested_match).toBe(3)
|
|
expect(countSuggestedMatches).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('takes the expense-payout count from a caller-supplied list instead of rescanning', async () => {
|
|
const { countExpensePayoutsDue } = await import('../categories')
|
|
const people = [{ key: 'owner:Anna' }, { key: 'emp-1' }, { key: 'emp-2' }] as never[]
|
|
const { counts } = await getWorklistCounts(supabase, 'company-1', {
|
|
expensePayoutsDue: Promise.resolve(people),
|
|
})
|
|
expect(counts.expense_payout).toBe(3)
|
|
expect(countExpensePayoutsDue).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('excludes suggested_match from the total (subset of book_transaction)', async () => {
|
|
const { total } = await getWorklistCounts(supabase, 'company-1')
|
|
// 4 + 7 + 6 + 1 + 3 + 5 + 1 + 2 + 1 + 2 (people owed for utlägg), without
|
|
// the 2 suggested matches.
|
|
expect(total).toBe(32)
|
|
})
|
|
})
|