'use client' import { useState } from 'react' import { useTranslations } from 'next-intl' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Checkbox } from '@/components/ui/checkbox' import { formatCurrency } from '@/lib/utils' import { Calendar, FileText, CheckCircle, AlertCircle, XCircle, ArrowRight, BarChart3, Info, Briefcase, } from 'lucide-react' import type { ImportPreview, ParseIssue } from '@/lib/import/types' interface SIEPreviewStepProps { preview: ImportPreview issues: ParseIssue[] missingAccounts: { number: string; name: string }[] onCreateAccounts: () => Promise isCreatingAccounts: boolean onContinue: () => void onBack: () => void /** * Opens the manual "Ingående balanser" wizard (issue #2082). Offered next to * the IB-imbalance acknowledgement so a user who rightly hesitates to book an * unexplained amount to 2099 has somewhere to go other than support. */ onOpenManualOpeningBalances?: () => void } export default function SIEPreviewStep({ preview, issues, missingAccounts, onCreateAccounts, isCreatingAccounts, onContinue, onBack, onOpenManualOpeningBalances, }: SIEPreviewStepProps) { const t = useTranslations('import') const errors = issues.filter((i) => i.severity === 'error') const warnings = issues.filter((i) => i.severity === 'warning') // Both blocks are optional: a preview built by an older parse response // lacks them, and the card then falls back to the BAS-reference counts. const chart = preview.chart const fiscalYear = preview.fiscalYear // Both refusals carry the import's own text: an overlap with a period that // has content, or #RAR dates that break a BFL 3 kap. shape rule. const fiscalYearRefused = fiscalYear?.verdict === 'conflict' || fiscalYear?.verdict === 'invalid' // Opening-balance imbalance. The importer plugs any diff > 0.01 to 2099, but a // diff under ~1 SEK is genuine öresavrundning. Anything larger is a real // imbalance (incomplete export: missing liabilities / unappropriated prior-year // result) that would silently book a bogus amount to 2099. Mirrors the importer's // own `fileImbalance > 1.00` "serious" threshold (lib/import/sie-import.ts). const ibDiff = Math.round((preview.trialBalance.totalDebit - preview.trialBalance.totalCredit) * 100) / 100 const significantImbalance = !preview.trialBalance.isBalanced && Math.abs(ibDiff) > 1 const [ackImbalance, setAckImbalance] = useState(false) // Only block on actual parsing errors, not unmapped accounts // (users need to proceed to mapping step to fix unmapped accounts). // A significant IB imbalance is a soft block: the user must acknowledge it. const hasBlockingErrors = errors.length > 0 const blockContinue = hasBlockingErrors || (significantImbalance && !ackImbalance) return (
{/* Company info */} Företagsinformation Information från SIE-filen

Företagsnamn

{preview.companyName || 'Ej angivet'}

Organisationsnummer

{preview.orgNumber || 'Ej angivet'}

{/* Fiscal year */} Räkenskapsår

Start

{preview.fiscalYearStart ?? 'Okänt'}

Slut

{preview.fiscalYearEnd ?? 'Okänt'}

{fiscalYear && (fiscalYear.verdict === 'match' || fiscalYear.verdict === 'create') && ( {fiscalYear.verdict === 'match' ? t('fiscal_year_match') : fiscalYear.replacesEmptyPeriodId ? t('fiscal_year_create_replaces') : t('fiscal_year_create')} )}
{fiscalYear && (fiscalYear.verdict === 'conflict' || fiscalYear.verdict === 'invalid') && (
{fiscalYear.message}
)}
{/* Statistics */}
Konton

{preview.accountCount}

Verifikationer

{preview.voucherCount}

Transaktionsrader

{preview.transactionLineCount}

IB, summa debet

{formatCurrency(preview.openingBalanceTotal)}

Summan av alla debetsaldon i ingående balans, inte ett enskilt kontosaldo.

{/* Trial balance check */} {preview.trialBalance.isBalanced ? ( ) : ( )} Balansräkning (IB)

Total debet

{formatCurrency(preview.trialBalance.totalDebit)}

Total kredit

{formatCurrency(preview.trialBalance.totalCredit)}

Status

{preview.trialBalance.isBalanced ? ( Balanserar ) : ( Diff: {formatCurrency(ibDiff)} )}
{/* Significant imbalance: explain + require acknowledgement before continuing */} {significantImbalance && (

Ingående balanser balanserar inte ({formatCurrency(Math.abs(ibDiff))})

Vanligaste orsaken är att föregående års resultat aldrig fördes över till eget kapital i det gamla programmet. SpeedLedger parkerar det till exempel på egna 9xxx-konton (9030/9031 Obokat resultat), och då summerar inte filens ingående balanser till noll: differensen är det oförda resultatet. En annan orsak är en ofullständig export, till exempel att skulder saknas.

Rätta i källsystemet och ladda upp filen på nytt, eller lägg in de ingående balanserna för hand i guiden Ingående balanser, där du kan rätta raderna själv innan de bokförs (den här filen importeras då inte). Fortsätter du ändå bokförs differensen på konto 2099 (Årets resultat), vilket nästan alltid blir fel.

{onOpenManualOpeningBalances && ( )}
)}
{/* Mapping status */} 0 ? 'border-destructive/50' : preview.mappingStatus.lowConfidence > 0 ? 'border-border' : 'border-success/50' } > {preview.mappingStatus.unmapped > 0 ? ( ) : preview.mappingStatus.lowConfidence > 0 ? ( ) : ( )} {t('chart_card_title')} {chart ? t('chart_summary', { toCreate: chart.toCreate, existing: chart.existing }) : 'Hur väl kunde kontona i filen matchas mot din kontoplan'}
{chart ? ( <>

{t('chart_to_create')}

{chart.toCreate}

{t('chart_existing')}

{chart.existing}

) : ( <>

Totalt

{preview.mappingStatus.total}

Mappade

{preview.mappingStatus.mapped}

)}

Ej mappade

0 ? 'text-destructive' : ''}`}> {preview.mappingStatus.unmapped}

Osäkra

0 ? 'text-warning' : ''}`}> {preview.mappingStatus.lowConfidence}

{chart && chart.sample.length > 0 && (
{chart.sample.map((acc) => (
{acc.number} {acc.name}
))} {chart.toCreate > chart.sample.length && (
{t('chart_sample_more', { count: chart.toCreate - chart.sample.length })}
)}
)}
{/* Excluded system accounts info */} {preview.excludedSystemAccounts.length > 0 && (
{preview.excludedSystemAccounts.length} internt systemkonto från källsystemet exkluderades ({preview.excludedSystemAccounts.map((a) => a.number).join(', ')}), inte bokföringskonton
)} {/* Create missing accounts */} {missingAccounts.length > 0 ? ( Skapa saknade konton {missingAccounts.length} konton från SIE-filen finns inte i din kontoplan ännu. Klicka nedan för att skapa dem: de kopplas sedan automatiskt i nästa steg.
{missingAccounts.slice(0, 10).map((acc) => (
{acc.number} {acc.name}
))} {missingAccounts.length > 10 && (
... och {missingAccounts.length - 10} till
)}
) : preview.mappingStatus.mapped === preview.mappingStatus.total && preview.mappingStatus.total > 0 && (
Alla konton skapade och automatiskt kopplade
)} {/* Errors */} {errors.length > 0 && ( Tolkningsfel ({errors.length}) Dessa fel hittades under tolkningen av SIE-filen och kan påverka importresultatet.
{errors.map((issue, i) => (
Rad {issue.line}{' '} {issue.message}
))}
)} {/* Warnings */} {warnings.length > 0 && ( Varningar ({warnings.length}) Dessa varningar blockerar inte importen men bör granskas.
{warnings.map((issue, i) => (
Rad {issue.line}{' '} {issue.message}
))}
)} {/* Actions */}
) }