feat(import): support Wise balance statements (#1368)
* feat(import): support Wise balance statements * fix(import): fail closed on ambiguous Wise rows * fix(import): guard Wise statement netted-fee assumption with running-balance continuity check Swedish accounting review asked whether balance-statement Total fees is netted into Amount. It is: Running Balance moves by exactly the signed Amount per row, so a separate fee row would double-count the cost. Codify the assumption with a pairwise continuity warning (order-agnostic, chain resets across skipped rows) and document the decision. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(api): cap bank-import validation payload and harden issue assertion CodeRabbit review: bound the VALIDATION_ERROR issues array to 20 entries with issue_count carrying the full total, so a large malformed file cannot balloon the response or log sink. Gate stays format-agnostic on purpose: error severity means do-not-ingest for every parser, and no non-Wise parser emits per-row errors alongside parsed transactions today. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
0510d4c13f
commit
24911abde0
@@ -35,7 +35,8 @@ export default function BankFilePreviewStep({
|
||||
onBack,
|
||||
}: BankFilePreviewStepProps) {
|
||||
const { transactions, stats, issues, date_from, date_to } = parseResult
|
||||
const hasIssues = issues.filter((i) => i.severity === 'error').length > 0
|
||||
const errors = issues.filter((i) => i.severity === 'error')
|
||||
const hasIssues = errors.length > 0
|
||||
const warnings = issues.filter((i) => i.severity === 'warning')
|
||||
// Wise/camt.053 files can mix currencies per row: the parser-level totals
|
||||
// sum across currencies, so income/expenses are grouped per currency here.
|
||||
@@ -190,11 +191,15 @@ export default function BankFilePreviewStep({
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex gap-3">
|
||||
<AlertTriangle className="h-5 w-5 text-destructive flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<div className="min-w-0 space-y-2">
|
||||
<p className="font-medium text-destructive">Filen innehåller fel som förhindrar import</p>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Kontrollera felet ovan och försök ladda upp en korrigerad fil.
|
||||
</p>
|
||||
<div className="max-h-32 space-y-1 overflow-y-auto">
|
||||
{errors.map((issue, i) => (
|
||||
<p key={i} className="text-xs text-muted-foreground">
|
||||
Rad {issue.row}: {issue.message}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useCallback } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
@@ -54,6 +55,7 @@ export default function BankFileUploadStep({
|
||||
detectedFormat,
|
||||
detectedFormatName,
|
||||
}: BankFileUploadStepProps) {
|
||||
const t = useTranslations('import')
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
||||
const [formatOverride, setFormatOverride] = useState<BankFileFormatId | undefined>(undefined)
|
||||
@@ -137,6 +139,7 @@ export default function BankFileUploadStep({
|
||||
<SelectItem value="lunar">Lunar</SelectItem>
|
||||
<SelectItem value="northmill">Northmill</SelectItem>
|
||||
<SelectItem value="wise">Wise</SelectItem>
|
||||
<SelectItem value="wise_statement">{t('bank_format_wise_statement')}</SelectItem>
|
||||
<SelectItem value="camt053">ISO 20022 camt.053 (XML)</SelectItem>
|
||||
<SelectItem value="generic_csv">Annan CSV (manuell mappning)</SelectItem>
|
||||
</SelectContent>
|
||||
@@ -181,7 +184,9 @@ export default function BankFileUploadStep({
|
||||
</p>
|
||||
<Badge variant="secondary" className="mt-2">
|
||||
<Building2 className="mr-1 h-3 w-3" />
|
||||
{detectedFormatName || FORMAT_NAMES[detectedFormat] || detectedFormat}
|
||||
{detectedFormat === 'wise_statement'
|
||||
? t('bank_format_wise_statement')
|
||||
: detectedFormatName || FORMAT_NAMES[detectedFormat] || detectedFormat}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user