fix: SIE import duplicate check, performance, and UX improvements (#162)

* fix: catch duplicate SIE import early with clear error message

- Add duplicate check in execute route before doing any work (defense in depth)
- Handle duplicate error from execute route in frontend
- Show "Filen har redan importerats" heading instead of generic "Kunde inte läsa filen"

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

* fix: catch duplicate SIE import early and batch account creation for performance

- Add duplicate check in execute route before doing any work (defense in depth)
- Handle duplicate error from execute route in frontend with clear Swedish message
- Replace sequential ensureAccountExists loop (50-100 DB round trips) with single
  batch SELECT + batch INSERT — reduces import time from 3+ min to seconds

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

* fix: update mappings optimistically after creating missing accounts

Previously re-parsed the SIE file after account creation, which could fail
with a 409 duplicate error (leaving the "create accounts" card stuck).
Now optimistically marks created accounts as self-mapped and updates the
preview stats immediately.

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

* fix: address Greptile P2 feedback — error handling and typed errorType prop

- Check batch insert error in executeSIEImport account creation safety net
- Replace brittle string-match error detection with typed errorType prop
- Remove stale file dependency from handleCreateAccounts useCallback

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-02 11:21:51 +02:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 9dceb6285c
commit d23cb4c859
4 changed files with 119 additions and 36 deletions
+66 -14
View File
@@ -1250,20 +1250,72 @@ export async function executeSIEImport(
const accountMap = mappingsToMap(mappings)
// Ensure all mapped target accounts exist in chart_of_accounts.
// The mapping contains every account referenced in the SIE file; accounts
// that were not seeded during onboarding need to be created here so that
// journal entry lines can link to them via account_id.
const seenTargets = new Set<string>()
for (const mapping of mappings) {
if (mapping.targetAccount && !seenTargets.has(mapping.targetAccount)) {
seenTargets.add(mapping.targetAccount)
await ensureAccountExists(
supabase,
companyId,
userId,
mapping.targetAccount,
mapping.targetName
)
// Uses a single batch query + batch insert instead of per-account round trips.
const targetAccounts = [...new Set(
mappings.filter(m => m.targetAccount).map(m => m.targetAccount!)
)]
if (targetAccounts.length > 0) {
const { data: existing } = await supabase
.from('chart_of_accounts')
.select('account_number')
.eq('company_id', companyId)
.in('account_number', targetAccounts)
const existingSet = new Set((existing || []).map(a => a.account_number))
const missing = targetAccounts.filter(num => !existingSet.has(num))
if (missing.length > 0) {
const targetNameMap = new Map<string, string>()
for (const m of mappings) {
if (m.targetAccount) targetNameMap.set(m.targetAccount, m.targetName || m.sourceName)
}
const inserts = missing.map(num => {
const basRef = getBASReference(num)
if (basRef) {
return {
user_id: userId,
company_id: companyId,
account_number: num,
account_name: basRef.account_name,
account_class: basRef.account_class,
account_group: basRef.account_group,
account_type: basRef.account_type,
normal_balance: basRef.normal_balance,
sru_code: basRef.sru_code ?? computeSRUCode(num),
k2_excluded: basRef.k2_excluded,
plan_type: 'full_bas' as const,
is_active: true,
is_system_account: false,
}
}
const classNum = parseInt(num.charAt(0), 10)
const group = num.substring(0, 2)
const accountType = classNum === 1 ? 'asset'
: classNum === 2 ? (group === '21' ? 'untaxed_reserves' : (group === '20' ? 'equity' : 'liability'))
: classNum === 3 ? 'revenue' : 'expense'
return {
user_id: userId,
company_id: companyId,
account_number: num,
account_name: targetNameMap.get(num) || `Konto ${num}`,
account_class: classNum,
account_group: group,
account_type: accountType,
normal_balance: classNum <= 1 || classNum >= 4 ? 'debit' : 'credit',
sru_code: computeSRUCode(num),
plan_type: 'full_bas' as const,
is_active: true,
is_system_account: false,
}
})
const { error: insertError } = await supabase.from('chart_of_accounts').insert(inserts)
if (insertError && !insertError.message.includes('duplicate')) {
result.errors.push(`Failed to create accounts: ${insertError.message}`)
return result
}
}
}