Enhance Cospend with debt breakdown and predefined users
- Add EnhancedBalance component with integrated single-user debt display - Create DebtBreakdown component for multi-user debt overview - Add predefined users configuration (alexander, anna) - Implement personal + equal split payment method - Add profile pictures throughout payment interfaces - Integrate debt information with profile pictures in balance view - Auto-hide debt breakdown when single user (shows in balance instead) - Support both manual and predefined user management modes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
110
src/routes/api/cospend/debts/+server.ts
Normal file
110
src/routes/api/cospend/debts/+server.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { PaymentSplit } from '../../../../models/PaymentSplit';
|
||||
import { Payment } from '../../../../models/Payment';
|
||||
import { dbConnect, dbDisconnect } from '../../../../utils/db';
|
||||
import { error, json } from '@sveltejs/kit';
|
||||
|
||||
interface DebtSummary {
|
||||
username: string;
|
||||
netAmount: number; // positive = you owe them, negative = they owe you
|
||||
transactions: {
|
||||
paymentId: string;
|
||||
title: string;
|
||||
amount: number;
|
||||
date: Date;
|
||||
category: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export const GET: RequestHandler = async ({ locals }) => {
|
||||
const auth = await locals.auth();
|
||||
if (!auth || !auth.user?.nickname) {
|
||||
throw error(401, 'Not logged in');
|
||||
}
|
||||
|
||||
const currentUser = auth.user.nickname;
|
||||
|
||||
await dbConnect();
|
||||
|
||||
try {
|
||||
// Get all splits for the current user
|
||||
const userSplits = await PaymentSplit.find({ username: currentUser })
|
||||
.populate('paymentId')
|
||||
.lean();
|
||||
|
||||
// Get all other users who have splits with payments involving the current user
|
||||
const paymentIds = userSplits.map(split => split.paymentId._id);
|
||||
const allRelatedSplits = await PaymentSplit.find({
|
||||
paymentId: { $in: paymentIds },
|
||||
username: { $ne: currentUser }
|
||||
})
|
||||
.populate('paymentId')
|
||||
.lean();
|
||||
|
||||
// Group debts by user
|
||||
const debtsByUser = new Map<string, DebtSummary>();
|
||||
|
||||
// Process current user's splits to understand what they owe/are owed
|
||||
for (const split of userSplits) {
|
||||
const payment = split.paymentId as any;
|
||||
if (!payment) continue;
|
||||
|
||||
// Find other participants in this payment
|
||||
const otherSplits = allRelatedSplits.filter(s =>
|
||||
s.paymentId._id.toString() === split.paymentId._id.toString()
|
||||
);
|
||||
|
||||
for (const otherSplit of otherSplits) {
|
||||
const otherUser = otherSplit.username;
|
||||
|
||||
if (!debtsByUser.has(otherUser)) {
|
||||
debtsByUser.set(otherUser, {
|
||||
username: otherUser,
|
||||
netAmount: 0,
|
||||
transactions: []
|
||||
});
|
||||
}
|
||||
|
||||
const debt = debtsByUser.get(otherUser)!;
|
||||
|
||||
// Current user's amount: positive = they owe, negative = they are owed
|
||||
// We want to show net between the two users
|
||||
debt.netAmount += split.amount;
|
||||
|
||||
debt.transactions.push({
|
||||
paymentId: payment._id.toString(),
|
||||
title: payment.title,
|
||||
amount: split.amount,
|
||||
date: payment.date,
|
||||
category: payment.category
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Convert map to array and sort by absolute amount (largest debts first)
|
||||
const debtSummaries = Array.from(debtsByUser.values())
|
||||
.filter(debt => Math.abs(debt.netAmount) > 0.01) // Filter out tiny amounts
|
||||
.sort((a, b) => Math.abs(b.netAmount) - Math.abs(a.netAmount));
|
||||
|
||||
// Separate into who owes you vs who you owe
|
||||
const whoOwesMe = debtSummaries.filter(debt => debt.netAmount < 0).map(debt => ({
|
||||
...debt,
|
||||
netAmount: Math.abs(debt.netAmount) // Make positive for display
|
||||
}));
|
||||
|
||||
const whoIOwe = debtSummaries.filter(debt => debt.netAmount > 0);
|
||||
|
||||
return json({
|
||||
whoOwesMe,
|
||||
whoIOwe,
|
||||
totalOwedToMe: whoOwesMe.reduce((sum, debt) => sum + debt.netAmount, 0),
|
||||
totalIOwe: whoIOwe.reduce((sum, debt) => sum + debt.netAmount, 0)
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
console.error('Error calculating debt breakdown:', e);
|
||||
throw error(500, 'Failed to calculate debt breakdown');
|
||||
} finally {
|
||||
await dbDisconnect();
|
||||
}
|
||||
};
|
@@ -48,7 +48,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
throw error(400, 'Amount must be positive');
|
||||
}
|
||||
|
||||
if (!['equal', 'full', 'proportional'].includes(splitMethod)) {
|
||||
if (!['equal', 'full', 'proportional', 'personal_equal'].includes(splitMethod)) {
|
||||
throw error(400, 'Invalid split method');
|
||||
}
|
||||
|
||||
@@ -56,6 +56,17 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
throw error(400, 'Invalid category');
|
||||
}
|
||||
|
||||
// Validate personal + equal split method
|
||||
if (splitMethod === 'personal_equal' && splits) {
|
||||
const totalPersonal = splits.reduce((sum: number, split: any) => {
|
||||
return sum + (parseFloat(split.personalAmount) || 0);
|
||||
}, 0);
|
||||
|
||||
if (totalPersonal > amount) {
|
||||
throw error(400, 'Personal amounts cannot exceed total payment amount');
|
||||
}
|
||||
}
|
||||
|
||||
await dbConnect();
|
||||
|
||||
try {
|
||||
@@ -77,7 +88,8 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
paymentId: payment._id,
|
||||
username: split.username,
|
||||
amount: split.amount,
|
||||
proportion: split.proportion
|
||||
proportion: split.proportion,
|
||||
personalAmount: split.personalAmount
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -75,7 +75,8 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => {
|
||||
paymentId: id,
|
||||
username: split.username,
|
||||
amount: split.amount,
|
||||
proportion: split.proportion
|
||||
proportion: split.proportion,
|
||||
personalAmount: split.personalAmount
|
||||
});
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user