feat(expenses): expense claims module (utlägg) (#2145)
Contributed by @joakimhew. Maintainer commits on top: migration re-versioned to 20260904170000 (main's 20260901210000 took the original version), payout batches booked atomically through the create_expense_payout_batch RPC, accounted-api skill regenerated, main merged. Closes #2143.
This commit is contained in:
@@ -9,6 +9,7 @@ import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
LayoutDashboard,
|
||||
Home,
|
||||
Receipt,
|
||||
ReceiptText,
|
||||
Users,
|
||||
ArrowLeftRight,
|
||||
@@ -137,6 +138,7 @@ type NavLabelKey =
|
||||
| 'reports'
|
||||
| 'import'
|
||||
| 'salary'
|
||||
| 'expenses'
|
||||
| 'mileage'
|
||||
| 'employees'
|
||||
| 'vat_declaration'
|
||||
@@ -244,6 +246,10 @@ const navItems: NavItem[] = [
|
||||
// must still reach its already-imported orders (accounting underlag).
|
||||
{ href: '/orders', labelKey: 'webshop_orders', icon: ShoppingCart, group: 'arbeta', requiresWebshop: true, betaBadge: true },
|
||||
{ href: '/supplier-invoices', labelKey: 'supplier_invoices', icon: Wallet, group: 'arbeta' },
|
||||
// Utlägg: out-of-pocket purchases and their reimbursement batches. The
|
||||
// /expenses route previously redirected to supplier invoices; the nav key
|
||||
// has existed in the nav namespace since then.
|
||||
{ href: '/expenses', labelKey: 'expenses', icon: Receipt, group: 'arbeta' },
|
||||
{ href: '/salary', labelKey: 'salary', icon: HandCoins, group: 'arbeta', employerOnly: true },
|
||||
// Körjournal: hidden by default (most companies have no car); shows when
|
||||
// the settings toggle is on or trips already exist (hybrid gate, same
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useCallback, useEffect, useRef, useMemo } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useCompanySettings } from '@/lib/reference-data/hooks'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
@@ -430,6 +431,7 @@ const WorkspaceSkeleton = InvoiceInboxSkeleton
|
||||
|
||||
export default function InvoiceInboxWorkspace(_props: WorkspaceComponentProps) {
|
||||
const { toast } = useToast()
|
||||
const router = useRouter()
|
||||
const t = useTranslations('inbox_workspace')
|
||||
const tStart = useTranslations('start_cards')
|
||||
const dismissKeyCompanyId = useCompanyOptional()?.company?.id ?? null
|
||||
@@ -2153,6 +2155,7 @@ export default function InvoiceInboxWorkspace(_props: WorkspaceComponentProps) {
|
||||
onDelete={() => handleDelete(selected.id)}
|
||||
onBookDirect={() => setBookDirectOpen(true)}
|
||||
onCreateSupplierInvoice={() => setCreateSupplierInvoiceOpen(true)}
|
||||
onRegisterExpense={() => router.push(`/expenses?new=1&inbox_item=${selected.id}`)}
|
||||
onMatchTransaction={() => setMatchPickerOpen(true)}
|
||||
onUnmatchTransaction={async () => {
|
||||
const targetId = selected.id
|
||||
@@ -3059,6 +3062,7 @@ function FieldsRail({
|
||||
onDelete,
|
||||
onBookDirect,
|
||||
onCreateSupplierInvoice,
|
||||
onRegisterExpense,
|
||||
onMatchTransaction,
|
||||
onUnmatchTransaction,
|
||||
onAskAssistant,
|
||||
@@ -3073,6 +3077,7 @@ function FieldsRail({
|
||||
onDelete: () => void
|
||||
onBookDirect: () => void
|
||||
onCreateSupplierInvoice: () => void
|
||||
onRegisterExpense: () => void
|
||||
onMatchTransaction: () => void
|
||||
onUnmatchTransaction: () => Promise<void>
|
||||
onAskAssistant?: (transactionId: string) => void
|
||||
@@ -3623,6 +3628,15 @@ function FieldsRail({
|
||||
För leverantörsskulder du vill följa (periodisering).
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={onRegisterExpense}
|
||||
className="flex flex-col items-start gap-1"
|
||||
>
|
||||
<span>Registrera som utlägg</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
För köp du eller en anställd betalat privat.
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={onBookDirect}
|
||||
className="flex flex-col items-start gap-1"
|
||||
|
||||
Reference in New Issue
Block a user