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>
106 lines
3.6 KiB
TypeScript
106 lines
3.6 KiB
TypeScript
import type { SupabaseClient } from '@supabase/supabase-js'
|
|
import type { ExpensePayoutDue, SuggestedMatch } from './types'
|
|
import type { WorklistCounts } from './types'
|
|
import {
|
|
countDeadlinesNeedingAction,
|
|
countExpensePayoutsDue,
|
|
countInboxDocuments,
|
|
countOverdueInvoices,
|
|
countPendingOperations,
|
|
countReconciliationDue,
|
|
countSuggestedMatches,
|
|
countSupplierInvoicesAwaitingApproval,
|
|
countUnbookedSkattekontoRows,
|
|
countUnbookedTransactions,
|
|
countVerifikatMissingDocument,
|
|
} from './categories'
|
|
|
|
/**
|
|
* All worklist counts in one round-trip burst. Each count is a bounded query
|
|
* (mostly head-only; suggested_match revalidates its candidates, see
|
|
* categories.ts) and individually soft-fails to 0, so this is safe to call
|
|
* from layouts and server components on every render.
|
|
*
|
|
* `total` is the number of distinct actionable items: suggested_match is a
|
|
* fast path over transactions already counted in book_transaction, so it is
|
|
* excluded to avoid double-counting (see lib/worklist/types.ts).
|
|
*/
|
|
export interface GetWorklistCountsOptions {
|
|
/**
|
|
* Suggested matches the caller is already fetching (Hem renders them in
|
|
* the Att göra pane): the count is taken from this list instead of a
|
|
* second scan of the same rows. A promise is accepted so it can run in
|
|
* parallel with the other counts.
|
|
*/
|
|
suggestedMatches?: SuggestedMatch[] | Promise<SuggestedMatch[]>
|
|
/**
|
|
* People owed for unpaid utlägg the caller is already fetching (Hem
|
|
* renders one row per person): the count is the list's length instead of
|
|
* a second scan of expense_claims.
|
|
*/
|
|
expensePayoutsDue?: ExpensePayoutDue[] | Promise<ExpensePayoutDue[]>
|
|
}
|
|
|
|
export async function getWorklistCounts(
|
|
supabase: SupabaseClient,
|
|
companyId: string,
|
|
options: GetWorklistCountsOptions = {},
|
|
): Promise<WorklistCounts> {
|
|
const [
|
|
bookTransaction,
|
|
bookSkattekonto,
|
|
inboxDocument,
|
|
suggestedMatch,
|
|
supplierInvoiceApproval,
|
|
verifikatMissingDocument,
|
|
overdueInvoice,
|
|
deadlineAction,
|
|
pendingOperations,
|
|
reconciliationDue,
|
|
expensePayout,
|
|
] = await Promise.all([
|
|
countUnbookedTransactions(supabase, companyId),
|
|
countUnbookedSkattekontoRows(supabase, companyId),
|
|
countInboxDocuments(supabase, companyId),
|
|
options.suggestedMatches
|
|
? Promise.resolve(options.suggestedMatches).then((m) => m.length)
|
|
: countSuggestedMatches(supabase, companyId),
|
|
countSupplierInvoicesAwaitingApproval(supabase, companyId),
|
|
countVerifikatMissingDocument(supabase, companyId),
|
|
countOverdueInvoices(supabase, companyId),
|
|
countDeadlinesNeedingAction(supabase, companyId),
|
|
countPendingOperations(supabase, companyId),
|
|
countReconciliationDue(supabase, companyId),
|
|
options.expensePayoutsDue
|
|
? Promise.resolve(options.expensePayoutsDue).then((p) => p.length)
|
|
: countExpensePayoutsDue(supabase, companyId),
|
|
])
|
|
|
|
return {
|
|
counts: {
|
|
book_transaction: bookTransaction,
|
|
book_skattekonto: bookSkattekonto,
|
|
inbox_document: inboxDocument,
|
|
suggested_match: suggestedMatch,
|
|
supplier_invoice_approval: supplierInvoiceApproval,
|
|
verifikat_missing_document: verifikatMissingDocument,
|
|
overdue_invoice: overdueInvoice,
|
|
deadline_action: deadlineAction,
|
|
pending_operations: pendingOperations,
|
|
reconciliation_due: reconciliationDue,
|
|
expense_payout: expensePayout,
|
|
},
|
|
total:
|
|
bookTransaction +
|
|
bookSkattekonto +
|
|
inboxDocument +
|
|
supplierInvoiceApproval +
|
|
verifikatMissingDocument +
|
|
overdueInvoice +
|
|
deadlineAction +
|
|
pendingOperations +
|
|
reconciliationDue +
|
|
expensePayout,
|
|
}
|
|
}
|