Fix/user feedback (#210)
* Add delete policies for provider consent tokens and provider OTC * Add trade name support for companies in settings and documents * Resolved currency selection issue * Enhance invoice line display with foreign currency support and update delivery date schema to allow empty values * Add currency display for journal entries and include currency metadata in transaction creation * Add trade_name column to company_settings for external display
This commit is contained in:
@@ -23,6 +23,9 @@ export interface FormLine {
|
||||
debit_amount: string
|
||||
credit_amount: string
|
||||
line_description: string
|
||||
currency?: string
|
||||
amount_in_currency?: number
|
||||
exchange_rate?: number
|
||||
}
|
||||
|
||||
interface Props {
|
||||
@@ -159,6 +162,9 @@ export default function JournalEntryForm({
|
||||
debit_amount: parseFloat(l.debit_amount) || 0,
|
||||
credit_amount: parseFloat(l.credit_amount) || 0,
|
||||
line_description: l.line_description || undefined,
|
||||
...(l.currency ? { currency: l.currency } : {}),
|
||||
...(l.amount_in_currency != null ? { amount_in_currency: l.amount_in_currency } : {}),
|
||||
...(l.exchange_rate != null ? { exchange_rate: l.exchange_rate } : {}),
|
||||
}))
|
||||
|
||||
const url = submitUrl ?? '/api/bookkeeping/journal-entries'
|
||||
|
||||
@@ -390,11 +390,18 @@ export default function JournalEntryList({ periodId }: Props) {
|
||||
<span className="text-muted-foreground">
|
||||
{Number(line.debit_amount) > 0 ? 'Debet' : 'Kredit'}
|
||||
</span>
|
||||
<span className="font-mono tabular-nums font-medium">
|
||||
{Number(line.debit_amount) > 0
|
||||
? Number(line.debit_amount).toLocaleString('sv-SE', { minimumFractionDigits: 2 })
|
||||
: Number(line.credit_amount).toLocaleString('sv-SE', { minimumFractionDigits: 2 })}
|
||||
</span>
|
||||
<div className="text-right">
|
||||
<span className="font-mono tabular-nums font-medium">
|
||||
{Number(line.debit_amount) > 0
|
||||
? Number(line.debit_amount).toLocaleString('sv-SE', { minimumFractionDigits: 2 })
|
||||
: Number(line.credit_amount).toLocaleString('sv-SE', { minimumFractionDigits: 2 })}
|
||||
</span>
|
||||
{line.currency && line.currency !== 'SEK' && line.amount_in_currency != null && (
|
||||
<span className="block text-xs text-muted-foreground font-mono tabular-nums">
|
||||
{Number(line.amount_in_currency).toLocaleString('sv-SE', { minimumFractionDigits: 2 })} {line.currency}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -28,6 +28,18 @@ export function CompanyInfoForm({ settings }: CompanyInfoFormProps) {
|
||||
<p className="text-xs text-muted-foreground">Kan inte ändras efter att kontot skapats</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="trade_name">Handelsnamn</Label>
|
||||
<Input
|
||||
id="trade_name"
|
||||
name="trade_name"
|
||||
defaultValue={settings.trade_name || ''}
|
||||
placeholder="Visas på fakturor istället för företagsnamnet"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Valfritt. Visas som huvudnamn på fakturor och e-post, med det juridiska namnet i parentes.
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="org_number">Organisationsnummer</Label>
|
||||
<Input
|
||||
|
||||
@@ -11,6 +11,7 @@ import JournalEntryForm from '@/components/bookkeeping/JournalEntryForm'
|
||||
import DocumentUploadZone from '@/components/bookkeeping/DocumentUploadZone'
|
||||
import type { UploadedFile } from '@/components/bookkeeping/DocumentUploadZone'
|
||||
import type { FormLine } from '@/components/bookkeeping/JournalEntryForm'
|
||||
import { resolveSekAmount, buildCurrencyMetadata } from '@/lib/bookkeeping/currency-utils'
|
||||
import type { TransactionWithInvoice } from './transaction-types'
|
||||
|
||||
interface TransactionBookingDialogProps {
|
||||
@@ -21,21 +22,40 @@ interface TransactionBookingDialogProps {
|
||||
}
|
||||
|
||||
function buildInitialLines(transaction: TransactionWithInvoice): FormLine[] {
|
||||
const amount = Math.round(Math.abs(transaction.amount_sek ?? transaction.amount) * 100) / 100
|
||||
const amountStr = amount.toFixed(2)
|
||||
const sekAmount = Math.round(Math.abs(resolveSekAmount(
|
||||
transaction.amount,
|
||||
transaction.amount_sek,
|
||||
transaction.currency,
|
||||
transaction.exchange_rate
|
||||
)) * 100) / 100
|
||||
const amountStr = sekAmount.toFixed(2)
|
||||
const isExpense = transaction.amount < 0
|
||||
|
||||
if (isExpense) {
|
||||
return [
|
||||
{ account_number: '1930', debit_amount: '', credit_amount: amountStr, line_description: 'Företagskonto' },
|
||||
{ account_number: '', debit_amount: amountStr, credit_amount: '', line_description: '' },
|
||||
]
|
||||
const isForeign = !!transaction.currency && transaction.currency !== 'SEK'
|
||||
const currencyMeta = isForeign
|
||||
? buildCurrencyMetadata(
|
||||
transaction.currency,
|
||||
Math.abs(transaction.amount),
|
||||
transaction.exchange_rate
|
||||
)
|
||||
: {}
|
||||
|
||||
const bankLine: FormLine = {
|
||||
account_number: '1930',
|
||||
debit_amount: isExpense ? '' : amountStr,
|
||||
credit_amount: isExpense ? amountStr : '',
|
||||
line_description: 'Företagskonto',
|
||||
...currencyMeta,
|
||||
}
|
||||
|
||||
return [
|
||||
{ account_number: '1930', debit_amount: amountStr, credit_amount: '', line_description: 'Företagskonto' },
|
||||
{ account_number: '', debit_amount: '', credit_amount: amountStr, line_description: '' },
|
||||
]
|
||||
const counterLine: FormLine = {
|
||||
account_number: '',
|
||||
debit_amount: isExpense ? amountStr : '',
|
||||
credit_amount: isExpense ? '' : amountStr,
|
||||
line_description: '',
|
||||
}
|
||||
|
||||
return isExpense ? [bankLine, counterLine] : [bankLine, counterLine]
|
||||
}
|
||||
|
||||
export default function TransactionBookingDialog({
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { cn, formatCurrency, formatDate } from '@/lib/utils'
|
||||
import { ArrowUpRight, ArrowDownRight, FileText, Loader2, Paperclip } from 'lucide-react'
|
||||
import { ArrowUpRight, ArrowDownRight, FileText, Loader2, Paperclip, Trash2 } from 'lucide-react'
|
||||
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '@/components/ui/info-tooltip'
|
||||
import { getAccountName, formatAccountWithName } from '@/lib/bookkeeping/client-account-names'
|
||||
import { getTemplateById } from '@/lib/bookkeeping/booking-templates'
|
||||
@@ -26,7 +26,7 @@ interface TransactionInboxCardProps {
|
||||
onMarkPrivate: (id: string) => void
|
||||
onOpenMatchDialog: (transaction: TransactionWithInvoice) => void
|
||||
onOpenCategoryDialog: (transaction: TransactionWithInvoice) => void
|
||||
|
||||
onDelete?: (id: string) => void
|
||||
onOpenQuickReview?: (transaction: TransactionWithInvoice, suggestion: SuggestedCategory) => void
|
||||
onOpenTemplateReview?: (transaction: TransactionWithInvoice, templateId: string) => void
|
||||
onToggleSelect: (id: string) => void
|
||||
@@ -45,7 +45,7 @@ export default function TransactionInboxCard({
|
||||
onMarkPrivate,
|
||||
onOpenMatchDialog,
|
||||
onOpenCategoryDialog,
|
||||
|
||||
onDelete,
|
||||
onOpenQuickReview,
|
||||
onOpenTemplateReview,
|
||||
onToggleSelect,
|
||||
@@ -60,6 +60,7 @@ export default function TransactionInboxCard({
|
||||
const isUncategorized = transaction.is_business === null && !transaction.journal_entry_id
|
||||
const showCheckbox = isBatchMode && isUncategorized
|
||||
const hasDocumentMatch = !!transaction.matched_inbox_item
|
||||
const isManualTransaction = !transaction.bank_connection_id && !transaction.import_source && !transaction.journal_entry_id
|
||||
|
||||
function handleSuggestionClick(suggestion: SuggestedCategory) {
|
||||
if (onOpenQuickReview) {
|
||||
@@ -249,6 +250,20 @@ export default function TransactionInboxCard({
|
||||
>
|
||||
Välj mall...
|
||||
</Button>
|
||||
|
||||
{/* Delete button — only for manually added, unbooked transactions */}
|
||||
{isManualTransaction && onDelete && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-9 w-9 p-0 ml-auto text-muted-foreground hover:text-destructive"
|
||||
onClick={() => onDelete(transaction.id)}
|
||||
disabled={isProcessing || isDisabled}
|
||||
aria-label="Ta bort transaktion"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
Reference in New Issue
Block a user