Skip to content

Commit 9a9720d

Browse files
committed
Improve import log messages with detailed context for debugging without DB access
- Show amount, currency, date, and group name for skipped/failed expenses - Show user names instead of raw IDs in dedup warnings - Include DB user IDs in user mapping logs for traceability - Show restored member names in group restoration logs - Upgrade missing-user expense skips from warn to error level - Add human-readable context to Splitwise import logs too Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wMSFyaUDFVGr31qqm7tk7
1 parent 18aeea3 commit 9a9720d

1 file changed

Lines changed: 93 additions & 13 deletions

File tree

src/server/api/services/splitService.ts

Lines changed: 93 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,16 @@ export async function importSplitProData(
470470
`Starting import: ${data.users.length} users, ${data.groups.length} groups, ${data.expenses.length} expenses`,
471471
);
472472

473+
// Build lookup maps for human-readable log messages
474+
const exportUserNames = new Map<number, string>();
475+
for (const u of data.users) {
476+
exportUserNames.set(u.id, u.name ?? u.email ?? `User #${u.id}`);
477+
}
478+
const exportGroupNames = new Map<number, string>();
479+
for (const g of data.groups) {
480+
exportGroupNames.set(g.id, g.name);
481+
}
482+
473483
// Build mapping from exported userId → existing or new local userId
474484
const userIdMap = new Map<number, number>();
475485

@@ -484,7 +494,10 @@ export async function importSplitProData(
484494
const existing = await db.user.findUnique({ where: { email: exportedUser.email } });
485495
if (existing) {
486496
userIdMap.set(exportedUser.id, existing.id);
487-
log('info', `User mapped: ${exportedUser.name ?? exportedUser.email} → existing account`);
497+
log(
498+
'info',
499+
`User mapped: "${exportedUser.name ?? 'unknown'}" (${exportedUser.email}) → existing account (DB #${existing.id})`,
500+
);
488501
continue;
489502
}
490503
}
@@ -496,7 +509,10 @@ export async function importSplitProData(
496509
});
497510
if (existingLocal) {
498511
userIdMap.set(exportedUser.id, existingLocal.id);
499-
log('info', `User mapped: ${localName} → existing local user`);
512+
log(
513+
'info',
514+
`User mapped: "${localName}" (no email, local user) → existing DB #${existingLocal.id}`,
515+
);
500516
continue;
501517
}
502518

@@ -509,7 +525,7 @@ export async function importSplitProData(
509525
},
510526
});
511527
userIdMap.set(exportedUser.id, newUser.id);
512-
log('info', `User created: ${localName}`);
528+
log('info', `User created: "${localName}" (no email) → new DB #${newUser.id}`);
513529
}
514530

515531
// Warn about multiple export IDs mapping to the same DB user
@@ -519,11 +535,12 @@ export async function importSplitProData(
519535
list.push(exportId);
520536
reverseMap.set(dbId, list);
521537
}
522-
for (const [, exportIds] of reverseMap) {
538+
for (const [dbId, exportIds] of reverseMap) {
523539
if (exportIds.length > 1) {
540+
const names = exportIds.map((id) => `"${exportUserNames.get(id) ?? id}"`).join(', ');
524541
log(
525542
'warn',
526-
`${exportIds.length} exported user IDs (${exportIds.join(', ')}) map to same DB user — participants will be deduplicated`,
543+
`${exportIds.length} exported users (${names}) all map to same DB user #${dbId} — their expenses will be merged. This usually means duplicate local users existed during export.`,
527544
);
528545
}
529546
}
@@ -551,9 +568,22 @@ export async function importSplitProData(
551568
data: missingMembers.map((userId) => ({ userId, groupId: existing.id })),
552569
skipDuplicates: true,
553570
});
554-
log('info', `Group "${exportedGroup.name}": restored ${missingMembers.length} member(s)`);
571+
const memberNames = exportedGroup.members
572+
.filter((m) => {
573+
const uid = userIdMap.get(m.userId);
574+
return uid !== undefined && !existingMemberIds.has(uid);
575+
})
576+
.map((m) => exportUserNames.get(m.userId) ?? `#${m.userId}`)
577+
.map((n) => `"${n}"`);
578+
log(
579+
'info',
580+
`Group "${exportedGroup.name}": restored ${missingMembers.length} member(s): ${memberNames.join(', ')}`,
581+
);
555582
} else {
556-
log('info', `Group "${exportedGroup.name}": already exists`);
583+
log(
584+
'info',
585+
`Group "${exportedGroup.name}": already exists (${existing.groupUsers.length} members)`,
586+
);
557587
}
558588
continue;
559589
}
@@ -608,9 +638,13 @@ export async function importSplitProData(
608638
skipDuplicates: true,
609639
});
610640
}
641+
const groupName = exportedExpense.groupId
642+
? (exportGroupNames.get(exportedExpense.groupId) ?? 'unknown group')
643+
: 'no group';
644+
const expDate = new Date(exportedExpense.expenseDate).toLocaleDateString('de-DE');
611645
log(
612646
'warn',
613-
`Expense "${exportedExpense.name}" (${exportedExpense.id}): skipped (already exists)`,
647+
`Expense skipped (already exists): "${exportedExpense.name}" ${exportedExpense.amount} ${exportedExpense.currency}, ${expDate}, ${groupName}`,
614648
);
615649
expensesSkipped++;
616650
continue;
@@ -619,9 +653,22 @@ export async function importSplitProData(
619653
const paidByUserId = userIdMap.get(exportedExpense.paidByUserId);
620654
const addedByUserId = userIdMap.get(exportedExpense.addedByUserId);
621655
if (!paidByUserId || !addedByUserId) {
656+
const paidByName = exportUserNames.get(exportedExpense.paidByUserId) ?? 'unknown';
657+
const addedByName = exportUserNames.get(exportedExpense.addedByUserId) ?? 'unknown';
658+
const groupName = exportedExpense.groupId
659+
? (exportGroupNames.get(exportedExpense.groupId) ?? 'unknown group')
660+
: 'no group';
661+
const expDate = new Date(exportedExpense.expenseDate).toLocaleDateString('de-DE');
662+
const missing = [];
663+
if (!paidByUserId) {
664+
missing.push(`payer "${paidByName}" not found`);
665+
}
666+
if (!addedByUserId) {
667+
missing.push(`creator "${addedByName}" not found`);
668+
}
622669
log(
623-
'warn',
624-
`Expense "${exportedExpense.name}" (${exportedExpense.id}): skipped (paidBy=${exportedExpense.paidByUserId} addedBy=${exportedExpense.addedByUserId} not found in user map)`,
670+
'error',
671+
`Expense skipped (${missing.join(', ')}): "${exportedExpense.name}" ${exportedExpense.amount} ${exportedExpense.currency}, ${expDate}, ${groupName}`,
625672
);
626673
expensesSkipped++;
627674
continue;
@@ -661,7 +708,16 @@ export async function importSplitProData(
661708
});
662709
expensesImported++;
663710
} catch (err) {
664-
log('error', `Expense "${exportedExpense.name}": ${String(err)}`);
711+
const groupName = exportedExpense.groupId
712+
? (exportGroupNames.get(exportedExpense.groupId) ?? 'unknown group')
713+
: 'no group';
714+
const expDate = new Date(exportedExpense.expenseDate).toLocaleDateString('de-DE');
715+
const paidByName = exportUserNames.get(exportedExpense.paidByUserId) ?? 'unknown';
716+
const errMsg = err instanceof Error ? err.message : String(err);
717+
log(
718+
'error',
719+
`Expense failed: "${exportedExpense.name}" — ${exportedExpense.amount} ${exportedExpense.currency}, ${expDate}, ${groupName}, paid by "${paidByName}". Error: ${errMsg}`,
720+
);
665721
expensesSkipped++;
666722
}
667723
}
@@ -849,6 +905,14 @@ export async function importFromSplitwisePro(
849905
const expenseId = splitwiseIdToUuid(swExp.id);
850906
const existing = await db.expense.findUnique({ where: { id: expenseId } });
851907
if (existing) {
908+
const groupName = swExp.group_id
909+
? (data.groups.find((g) => g.id === swExp.group_id)?.name ?? 'unknown group')
910+
: 'no group';
911+
const expDate = new Date(swExp.date).toLocaleDateString('de-DE');
912+
log(
913+
'warn',
914+
`Expense skipped (already exists): "${swExp.description}" — ${swExp.cost} ${swExp.currency_code}, ${expDate}, ${groupName}`,
915+
);
852916
expensesSkipped++;
853917
continue;
854918
}
@@ -862,7 +926,15 @@ export async function importFromSplitwisePro(
862926

863927
const paidByUserId = userIdMap.get(payerSwId);
864928
if (!paidByUserId) {
865-
log('warn', `Expense "${swExp.description}": skipped (unknown payer)`);
929+
const payerName = data.friends.find((f) => f.id === payerSwId);
930+
const groupName = swExp.group_id
931+
? (data.groups.find((g) => g.id === swExp.group_id)?.name ?? 'unknown group')
932+
: 'no group';
933+
const expDate = new Date(swExp.date).toLocaleDateString('de-DE');
934+
log(
935+
'error',
936+
`Expense skipped (payer "${payerName ? [payerName.first_name, payerName.last_name].filter(Boolean).join(' ') : `Splitwise #${payerSwId}`}" not found): "${swExp.description}" — ${swExp.cost} ${swExp.currency_code}, ${expDate}, ${groupName}`,
937+
);
866938
expensesSkipped++;
867939
continue;
868940
}
@@ -931,7 +1003,15 @@ export async function importFromSplitwisePro(
9311003
});
9321004
expensesImported++;
9331005
} catch (err) {
934-
log('error', `Expense "${swExp.description}": ${String(err)}`);
1006+
const groupName = swExp.group_id
1007+
? (data.groups.find((g) => g.id === swExp.group_id)?.name ?? 'unknown group')
1008+
: 'no group';
1009+
const expDate = new Date(swExp.date).toLocaleDateString('de-DE');
1010+
const errMsg = err instanceof Error ? err.message : String(err);
1011+
log(
1012+
'error',
1013+
`Expense failed: "${swExp.description}" — ${swExp.cost} ${swExp.currency_code}, ${expDate}, ${groupName}. Error: ${errMsg}`,
1014+
);
9351015
expensesSkipped++;
9361016
}
9371017
}

0 commit comments

Comments
 (0)