feat: allow replacing completed SIE imports (#227)

* feat: allow replacing completed SIE imports

Users who import a SIE file, make adjustments in the source system, and
re-export can now replace the old import instead of being permanently
blocked by the "overlapping fiscal year" guard.

The old import's entries are cancelled (posted → cancelled) and the
import is marked as 'replaced'. Nothing is deleted — full audit trail
preserved per BFL 5 kap 5§ (rättelse) and BFNAR 2013:2 kap 8.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address Greptile review — atomic RPC, locked_at check

- P1: Wrap entry cancellation + import status update in a single DB RPC
  (replace_sie_import) to prevent inconsistent state on partial failure
- P2: Check locked_at in addition to is_closed for fiscal period guard
- P2: Use RPC return value for accurate cancelled entry count

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-04-13 10:48:49 +02:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 973a2b81a0
commit 258a64a849
6 changed files with 250 additions and 6 deletions
+27 -3
View File
@@ -3,7 +3,8 @@
import { useState, useCallback, useEffect } from 'react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Progress } from '@/components/ui/progress'
import { Upload, FileText, AlertCircle, CheckCircle, Loader2, XCircle } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Upload, FileText, AlertCircle, CheckCircle, Loader2, XCircle, RefreshCw } from 'lucide-react'
const LOADING_PHASES = [
{ message: 'Läser fil...', progress: 10 },
@@ -19,9 +20,12 @@ interface SIEUploadStepProps {
errorType?: 'duplicate' | 'duplicate_period' | 'validation' | 'parse'
validationErrors?: string[]
validationWarnings?: string[]
duplicatePeriodImportId?: string | null
onReplace?: (importId: string) => Promise<void>
isReplacing?: boolean
}
export default function SIEUploadStep({ onFileSelect, isLoading, error, errorType, validationErrors, validationWarnings }: SIEUploadStepProps) {
export default function SIEUploadStep({ onFileSelect, isLoading, error, errorType, validationErrors, validationWarnings, duplicatePeriodImportId, onReplace, isReplacing }: SIEUploadStepProps) {
const [isDragging, setIsDragging] = useState(false)
const [selectedFile, setSelectedFile] = useState<File | null>(null)
const [loadingPhase, setLoadingPhase] = useState(0)
@@ -192,7 +196,27 @@ export default function SIEUploadStep({ onFileSelect, isLoading, error, errorTyp
<p>Om du vill importera om filen, ta först bort den tidigare importen under Bokföring.</p>
)}
{errorType === 'duplicate_period' && (
<p>Varje räkenskapsår kan bara importeras en gång. Ta bort den befintliga importen först om du vill ersätta den.</p>
<div className="space-y-2">
<p>Den befintliga importens verifikationer kommer att makuleras (status ändras till &quot;makulerad&quot;). De finns kvar som spårbar historik.</p>
{duplicatePeriodImportId && onReplace && (
<Button
variant="outline"
size="sm"
className="border-warning/50 text-warning hover:bg-warning/10"
disabled={isReplacing}
onClick={(e) => {
e.stopPropagation()
onReplace(duplicatePeriodImportId)
}}
>
{isReplacing ? (
<><Loader2 className="h-4 w-4 mr-2 animate-spin" />Ersätter...</>
) : (
<><RefreshCw className="h-4 w-4 mr-2" />Ersätt befintlig import</>
)}
</Button>
)}
</div>
)}
{errorType === 'validation' && (
<p>Prova att exportera filen igen från ditt bokföringsprogram. Om felet kvarstår, kontrollera att alla verifikationer är korrekt bokförda i källsystemet.</p>