91e2c1705a
Per-line VAT rates: - Add generatePerRateLines() to group invoice items by vat_rate with separate revenue + VAT lines per rate group (invoice-entries.ts) - Add getAvailableVatRates() and getVatTreatmentForRate() (vat-rules.ts) - PDF template shows per-line VAT column and per-rate totals for mixed-rate invoices - Invoice create/review UI supports per-line rate selection - Types: add vat_rate/vat_amount to InvoiceItem, vat_rate to CreateInvoiceItemInput Invoice document types (proforma, delivery note): - Add InvoiceDocumentType, document_type and converted_from_id to Invoice type - PDF hides prices for delivery notes, adds proforma notice - Email templates support all document types - mark-paid skips journal entries for non-invoice document types - Migration 031: invoice_document_type Accounting method support: - Add AccountingMethod type (accrual/cash) - Migration 032: add_accounting_method column to company_settings VAT declaration rewrite: - Rewrite to read directly from general ledger (26xx/3xxx account lines) instead of aggregating invoices/transactions/receipts - ACCOUNT_RUTA mapping drives momsdeklaration boxes from GL balances Bank reconciliation: - Transaction ingest now pre-fetches unlinked GL lines and attempts auto-reconciliation during import - Add transaction.reconciled event type - Add ReconciliationMethod type and reconciliation_method on Transaction - Migration 030: bank_reconciliation - New reconciliation engine, API routes, and BankReconciliationView component Pagination (fetchAllRows): - New lib/supabase/fetch-all.ts overcomes PostgREST 1000-row limit - Adopted in all report generators, SIE/SRU export, account list APIs Fiscal period validation: - New validate-period-duration.ts enforces max 18 months per BFL 3 kap. - Applied in period-service.ts and fiscal-periods API Account mapper simplification: - Remove Levenshtein/fuzzy matching, use exact account number match only Swedbank parser improvements: - Support abbreviated headers (Clnr, Bokfdag, Radnr) - Use Referens column as counterparty Chart of accounts management: - Add DELETE endpoint with system account and usage protection - PUT uses partial updates - New AccountCombobox, AddAccountDialog, EditAccountDialog, ChartOfAccountsManager Tax deadline corrections: - Rewrite inkomstdeklaration_ab using Skatteverket lookup table - Rewrite arsredovisning deadline to 7 months after FY end per ÅRL 8:3 Onboarding first fiscal year: - Add first fiscal year toggle with date pickers and 18-month validation UI terminology: - Change "okategoriserad/kategorisera" to "obokförd/bokföra" throughout Report column fix: - Fix start_date/end_date to period_start/period_end in report queries Supplier invoice input: - CreateSupplierInvoiceItemInput uses amount field (legacy quantity/unit_price kept) Misc: - SIE import uses upsert for idempotent account creation - account-descriptions.ts falls back to BAS reference data - Add invoice_default_notes to CompanySettings - Update CLAUDE.md to reflect current project state Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
179 lines
6.2 KiB
TypeScript
179 lines
6.2 KiB
TypeScript
'use client'
|
||
|
||
import { useState } from 'react'
|
||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||
import { Button } from '@/components/ui/button'
|
||
import { Badge } from '@/components/ui/badge'
|
||
import { Checkbox } from '@/components/ui/checkbox'
|
||
import { Label } from '@/components/ui/label'
|
||
import {
|
||
ArrowLeft,
|
||
Loader2,
|
||
Play,
|
||
FileText,
|
||
AlertTriangle,
|
||
Link2,
|
||
Calendar,
|
||
} from 'lucide-react'
|
||
import { formatCurrency } from '@/lib/utils'
|
||
import type { BankFileParseResult } from '@/lib/import/bank-file/types'
|
||
|
||
interface BankFileConfirmStepProps {
|
||
parseResult: BankFileParseResult
|
||
onExecute: (options: { skip_duplicates: boolean; auto_categorize: boolean }) => void
|
||
onBack: () => void
|
||
isLoading: boolean
|
||
}
|
||
|
||
export default function BankFileConfirmStep({
|
||
parseResult,
|
||
onExecute,
|
||
onBack,
|
||
isLoading,
|
||
}: BankFileConfirmStepProps) {
|
||
const [skipDuplicates, setSkipDuplicates] = useState(true)
|
||
const [autoCategorize, setAutoCategorize] = useState(true)
|
||
|
||
const { transactions, stats, date_from, date_to, format_name } = parseResult
|
||
const refsCount = transactions.filter((t) => t.reference).length
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
{/* Summary */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>Bekräfta import</CardTitle>
|
||
<CardDescription>
|
||
Granska sammanfattningen och importera transaktionerna.
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent className="space-y-6">
|
||
{/* Stats grid */}
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||
<div className="p-4 bg-muted/50 rounded-lg">
|
||
<div className="flex items-center gap-2 text-muted-foreground mb-1">
|
||
<FileText className="h-4 w-4" />
|
||
<span className="text-xs">Transaktioner</span>
|
||
</div>
|
||
<p className="text-xl font-bold">{stats.parsed_rows}</p>
|
||
</div>
|
||
|
||
<div className="p-4 bg-muted/50 rounded-lg">
|
||
<div className="flex items-center gap-2 text-muted-foreground mb-1">
|
||
<Calendar className="h-4 w-4" />
|
||
<span className="text-xs">Period</span>
|
||
</div>
|
||
<p className="text-sm font-medium">
|
||
{date_from} – {date_to}
|
||
</p>
|
||
</div>
|
||
|
||
<div className="p-4 bg-muted/50 rounded-lg">
|
||
<div className="flex items-center gap-2 text-green-600 mb-1">
|
||
<span className="text-xs">Inkomster</span>
|
||
</div>
|
||
<p className="text-xl font-bold text-green-600">
|
||
{formatCurrency(stats.total_income)}
|
||
</p>
|
||
</div>
|
||
|
||
<div className="p-4 bg-muted/50 rounded-lg">
|
||
<div className="flex items-center gap-2 text-red-600 mb-1">
|
||
<span className="text-xs">Utgifter</span>
|
||
</div>
|
||
<p className="text-xl font-bold text-red-600">
|
||
{formatCurrency(stats.total_expenses)}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Additional info */}
|
||
<div className="flex flex-wrap gap-2">
|
||
<Badge variant="secondary">Format: {format_name}</Badge>
|
||
{refsCount > 0 && (
|
||
<Badge variant="outline" className="text-blue-600 border-blue-300">
|
||
<Link2 className="mr-1 h-3 w-3" />
|
||
{refsCount} med OCR/referens
|
||
</Badge>
|
||
)}
|
||
</div>
|
||
|
||
{/* Options */}
|
||
<div className="border rounded-lg p-4 space-y-4">
|
||
<h3 className="text-sm font-medium">Importinställningar</h3>
|
||
|
||
<div className="flex items-center space-x-3">
|
||
<Checkbox
|
||
id="skip-duplicates"
|
||
checked={skipDuplicates}
|
||
onCheckedChange={(checked) => setSkipDuplicates(checked === true)}
|
||
/>
|
||
<div>
|
||
<Label htmlFor="skip-duplicates" className="text-sm font-medium cursor-pointer">
|
||
Hoppa över dubletter
|
||
</Label>
|
||
<p className="text-xs text-muted-foreground">
|
||
Transaktioner som redan finns i systemet importeras inte igen
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center space-x-3">
|
||
<Checkbox
|
||
id="auto-categorize"
|
||
checked={autoCategorize}
|
||
onCheckedChange={(checked) => setAutoCategorize(checked === true)}
|
||
/>
|
||
<div>
|
||
<Label htmlFor="auto-categorize" className="text-sm font-medium cursor-pointer">
|
||
Auto-bokför kända transaktioner
|
||
</Label>
|
||
<p className="text-xs text-muted-foreground">
|
||
Skapar automatiskt bokföringsposter för transaktioner med hög konfidens
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Warning note */}
|
||
<div className="flex gap-3 p-3 bg-yellow-50 dark:bg-yellow-950/20 border border-yellow-200 dark:border-yellow-800 rounded-lg">
|
||
<AlertTriangle className="h-4 w-4 text-yellow-600 flex-shrink-0 mt-0.5" />
|
||
<p className="text-sm text-muted-foreground">
|
||
Importerade transaktioner som inte automatiskt bokförs visas som
|
||
"obokförda" på transaktionssidan. Du kan bokföra dem manuellt
|
||
efteråt.
|
||
</p>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Actions */}
|
||
<div className="flex justify-between">
|
||
<Button variant="outline" onClick={onBack} disabled={isLoading}>
|
||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||
Tillbaka
|
||
</Button>
|
||
<Button
|
||
onClick={() => onExecute({
|
||
skip_duplicates: skipDuplicates,
|
||
auto_categorize: autoCategorize,
|
||
})}
|
||
disabled={isLoading}
|
||
>
|
||
{isLoading ? (
|
||
<>
|
||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||
Importerar...
|
||
</>
|
||
) : (
|
||
<>
|
||
<Play className="mr-2 h-4 w-4" />
|
||
Importera {stats.parsed_rows} transaktioner
|
||
</>
|
||
)}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|