'use client' import { useTranslations } from 'next-intl' import { Button } from '@/components/ui/button' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from '@/components/ui/dialog' import { formatCurrency, formatDate } from '@/lib/utils' import { roundOre } from '@/lib/money' import { expectedRotRutPayoutAmount, getRotRutPayoutMatchTargetState, } from '@/lib/invoices/rot-rut-payout-matching' import { CheckCircle2, AlertTriangle } from 'lucide-react' import type { TransactionWithInvoice } from './transaction-types' interface RotRutPayoutMatchDialogProps { open: boolean onOpenChange: (open: boolean) => void /** Row carrying `potential_rot_rut_payout`; the dialog renders nothing without it. */ transaction: TransactionWithInvoice | null isConfirming: boolean onConfirm: () => void } /** * Confirm dialog for matching an income bank row to an open ROT/RUT begäran: * Skatteverkets utbetalning clears the 1513 receivable (debit the row's cash * account, credit 1513) and the row is linked to that voucher. * * Kept separate from InvoiceMatchDialog on purpose: no FX, no preview fetch, * no editable lines. The entry has exactly two legs and the amount is the * bank row's, so everything the user needs to approve is known up front. */ export default function RotRutPayoutMatchDialog({ open, onOpenChange, transaction, isConfirming, onConfirm, }: RotRutPayoutMatchDialogProps) { const t = useTranslations('tx_rot_rut_match') const request = transaction?.potential_rot_rut_payout ?? null const targetState = getRotRutPayoutMatchTargetState(request) const targetBlocked = targetState !== 'matchable' const txAmount = transaction ? roundOre(transaction.amount) : 0 const expected = request ? expectedRotRutPayoutAmount(request) : 0 const requestedTotal = request ? roundOre(Number(request.requested_total)) : 0 const diff = roundOre(Math.abs(txAmount - expected)) const amountsMatch = diff < 0.01 // The settle service refuses a payout below requested_total unless the // beslut (decided_total) is recorded: say so here instead of letting the // button fail. const isPartial = request ? txAmount < requestedTotal - 0.005 : false const partialBlocked = isPartial && request?.decided_total == null // The service refuses more than Skatteverket can owe on this begäran: a // larger row would drive 1513 negative. Block here too, with the reason. const overBlocked = request ? txAmount > expected + 0.005 : false // Skatteverket pays out in SEK only; the route refuses anything else. const currencyBlocked = (transaction?.currency || 'SEK').toUpperCase() !== 'SEK' const currency = transaction?.currency || 'SEK' const typeLabel = request?.deduction_type === 'rut' ? 'RUT' : 'ROT' return ( {t('title')} {targetBlocked ? t('description_blocked') : t('description')} {transaction && request && (

{t('transaction_label')}

{transaction.description}

{formatDate(transaction.date)} +{formatCurrency(transaction.amount, currency)}

{t('request_label')}

{t('request_name', { type: typeLabel, name: request.name })}

{t('requested_total', { amount: formatCurrency(requestedTotal, 'SEK') })} {request.decided_total != null && ( {t('decided_total', { amount: formatCurrency(Number(request.decided_total), 'SEK') })} )}
{request.invoices.length > 0 && (

{t('invoices_title')}

    {request.invoices.map((inv, i) => (
  • {t('invoice_row', { number: inv.invoice_number ?? '' })} {formatCurrency(Number(inv.requested_amount), 'SEK')}
  • ))}
)}
{targetBlocked ? (

{t(targetState === 'settled' ? 'target_settled_title' : 'target_not_open_title')}

{t( targetState === 'settled' ? 'target_settled_description' : 'target_not_open_description', )}

) : amountsMatch ? (

{t('amounts_match')}

) : (

{t('amounts_differ')}

{t('amount_diff', { amount: formatCurrency(diff, currency) })}

{overBlocked &&

{t('over_payout_blocked')}

} {partialBlocked &&

{t('partial_requires_beslut')}

} {isPartial && !partialBlocked && (

{t('partial_with_beslut_note')}

)}
)} {!targetBlocked && (

{t('booking_title')}

{t('booking_debit')}{' '} {t('booking_bank_line')} {formatCurrency(txAmount, currency)}
{t('booking_credit')}{' '} {t('booking_receivable_line')} {formatCurrency(txAmount, currency)}
)} {!targetBlocked && (

{t('on_confirm_title')}

  • • {t('on_confirm_link')}
  • • {t('on_confirm_request')}
  • • {t('on_confirm_voucher')}
)}
)}
) }