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:
co-authored by
Claude Opus 4.6
parent
9dceb6285c
commit
d23cb4c859
+66
-14
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user