Bettersplit/src/lib/utils/expense.ts
2025-04-18 11:39:03 +02:00

143 lines
3.8 KiB
TypeScript

import type { SplitType } from "@/lib/store/expense-store";
import type { z } from "zod";
import { expenseSplitSchema } from "../validations/expense";
import type { ExpenseSplit } from "@/server/db/schema";
const debtSchema = expenseSplitSchema.pick({
owedFromId: true,
owedToId: true,
amount: true,
});
export type Debt = z.infer<typeof debtSchema>;
export type Payment = {
userId: string;
amount: number;
};
export function calculateSplits(args: {
amount: number;
users: Array<string>;
splitType: SplitType;
payments?: Array<Payment>;
}): Record<string, string> {
const result: Record<string, string> = {};
const { splitType = "Equal", amount, users, payments } = args;
switch (splitType) {
case "Equal":
var splits: Record<string, string> = {};
users.forEach((userId) => {
splits[userId] = `${amount / users.length}`;
});
return splits;
// case "Percentage":
// if (!customDepts) {
// throw new Error("Splits must be provided for percentage type.");
// }
// participants.forEach((userId) => {
// if (customDepts[userId] !== undefined) {
// const percentage = customDepts[userId] / 100;
// const amountSplit = amount * percentage;
// result[userId] = `${amountSplit.toFixed(2)}%`; // Add "%" for percentage
// } else {
// throw new Error(`No percentage split defined for user ID: ${userId}`);
// }
// });
// break;
// case "Fixed":
// if (!customDepts) {
// throw new Error("Splits must be provided for fixed type.");
// }
// participants.forEach((userId) => {
// if (customDepts[userId] !== undefined) {
// result[userId] = `${customDepts[userId].toFixed(2)}`; // Format to 2 decimal places
// } else {
// throw new Error(`No fixed amount defined for user ID: ${userId}`);
// }
// });
// break;
default:
return {};
// throw new Error("Invalid split type.");
}
return result;
}
export function calculateDepts(
total: number,
users: string[],
payments: Array<Payment>
): Array<Debt> {
console.log("Payments: ", payments);
const share = total / users.length;
const balances: Record<string, number> = {};
// Initialize balances
users.forEach((userId) => {
const paid = payments.find((p) => p.userId === userId)?.amount || 0;
balances[userId] = paid - share;
});
// Separate creditors and debtors
const creditors = Object.entries(balances)
.filter(([_, balance]) => balance > 0)
.map(([userId, balance]) => ({ userId, balance }));
const debtors = Object.entries(balances)
.filter(([_, balance]) => balance < 0)
.map(([userId, balance]) => ({ userId, balance: -balance }));
console.log("debtors: ", debtors);
console.log("creditors: ", creditors);
const debts: Debt[] = [];
for (const debtor of debtors) {
let amountToPay = debtor.balance;
for (const creditor of creditors) {
if (amountToPay === 0) break;
if (creditor.balance === 0) continue;
const payment = Math.min(amountToPay, creditor.balance);
debts.push({
owedToId: debtor.userId,
owedFromId: creditor.userId,
amount: parseFloat(payment.toFixed(2)), // for rounding safety
});
amountToPay -= payment;
creditor.balance -= payment;
}
}
return debts;
}
export const calculateTotalValue: (
userId: string,
splits: Array<ExpenseSplit>
) => {
owed: number;
owes: number;
} = (userId, splits) => {
let owed = 0;
let owes = 0;
splits.forEach((split) => {
if (split.owedToId === userId) {
owes += Number(split.amount); // You are the debitor
} else if (split.owedFromId === userId) {
owed += Number(split.amount); // You are the creditor
}
});
return { owed, owes };
};