143 lines
3.8 KiB
TypeScript
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 };
|
|
};
|