mirror of
https://github.com/spliit-app/spliit.git
synced 2025-12-06 01:19:29 +01:00
* Added computed expenses per balance to fix #127 * add missing import that got lost during merge * if we are in percentage mode or amount mode, the shares have to be multiplied by 100
This commit is contained in:
@@ -40,6 +40,7 @@ import {
|
||||
SplittingOptions,
|
||||
expenseFormSchema,
|
||||
} from '@/lib/schemas'
|
||||
import { calculateShare } from '@/lib/totals'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { AppRouterOutput } from '@/trpc/routers/_app'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
@@ -591,6 +592,42 @@ export function ExpenseForm({
|
||||
</FormControl>
|
||||
<FormLabel className="text-sm font-normal flex-1">
|
||||
{name}
|
||||
{field.value?.some(
|
||||
({ participant }) => participant === id,
|
||||
) &&
|
||||
!form.watch('isReimbursement') && (
|
||||
<span className="text-muted-foreground ml-2">
|
||||
({group.currency}{' '}
|
||||
{(
|
||||
calculateShare(id, {
|
||||
amount:
|
||||
Number(form.watch('amount')) * 100, // Convert to cents
|
||||
paidFor: field.value.map(
|
||||
({ participant, shares }) => ({
|
||||
participant: {
|
||||
id: participant,
|
||||
name: '',
|
||||
groupId: '',
|
||||
},
|
||||
shares:
|
||||
form.watch('splitMode') ===
|
||||
'BY_PERCENTAGE' ||
|
||||
form.watch('splitMode') ===
|
||||
'BY_AMOUNT'
|
||||
? Number(shares) * 100 // Convert percentage to basis points (e.g., 50% -> 5000)
|
||||
: shares,
|
||||
expenseId: '',
|
||||
participantId: '',
|
||||
}),
|
||||
),
|
||||
splitMode: form.watch('splitMode'),
|
||||
isReimbursement:
|
||||
form.watch('isReimbursement'),
|
||||
}) / 100
|
||||
).toFixed(2)}
|
||||
)
|
||||
</span>
|
||||
)}
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
{form.getValues().splitMode !== 'EVENLY' && (
|
||||
|
||||
@@ -23,48 +23,56 @@ export function getTotalActiveUserPaidFor(
|
||||
)
|
||||
}
|
||||
|
||||
type Expense = NonNullable<Awaited<ReturnType<typeof getGroupExpenses>>>[number]
|
||||
|
||||
export function calculateShare(
|
||||
participantId: string | null,
|
||||
expense: Pick<
|
||||
Expense,
|
||||
'amount' | 'paidFor' | 'splitMode' | 'isReimbursement'
|
||||
>,
|
||||
): number {
|
||||
if (expense.isReimbursement) return 0
|
||||
|
||||
const paidFors = expense.paidFor
|
||||
const userPaidFor = paidFors.find(
|
||||
(paidFor) => paidFor.participant.id === participantId,
|
||||
)
|
||||
|
||||
if (!userPaidFor) return 0
|
||||
|
||||
const shares = Number(userPaidFor.shares)
|
||||
|
||||
switch (expense.splitMode) {
|
||||
case 'EVENLY':
|
||||
// Divide the total expense evenly among all participants
|
||||
return expense.amount / paidFors.length
|
||||
case 'BY_AMOUNT':
|
||||
// Directly add the user's share if the split mode is BY_AMOUNT
|
||||
return shares
|
||||
case 'BY_PERCENTAGE':
|
||||
// Calculate the user's share based on their percentage of the total expense
|
||||
return (expense.amount * shares) / 10000 // Assuming shares are out of 10000 for percentage
|
||||
case 'BY_SHARES':
|
||||
// Calculate the user's share based on their shares relative to the total shares
|
||||
const totalShares = paidFors.reduce(
|
||||
(sum, paidFor) => sum + Number(paidFor.shares),
|
||||
0,
|
||||
)
|
||||
return (expense.amount * shares) / totalShares
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
export function getTotalActiveUserShare(
|
||||
activeUserId: string | null,
|
||||
expenses: NonNullable<Awaited<ReturnType<typeof getGroupExpenses>>>,
|
||||
): number {
|
||||
let total = 0
|
||||
|
||||
expenses.forEach((expense) => {
|
||||
if (expense.isReimbursement) return
|
||||
|
||||
const paidFors = expense.paidFor
|
||||
const userPaidFor = paidFors.find(
|
||||
(paidFor) => paidFor.participant.id === activeUserId,
|
||||
)
|
||||
|
||||
if (!userPaidFor) {
|
||||
// If the active user is not involved in the expense, skip it
|
||||
return
|
||||
}
|
||||
|
||||
switch (expense.splitMode) {
|
||||
case 'EVENLY':
|
||||
// Divide the total expense evenly among all participants
|
||||
total += expense.amount / paidFors.length
|
||||
break
|
||||
case 'BY_AMOUNT':
|
||||
// Directly add the user's share if the split mode is BY_AMOUNT
|
||||
total += userPaidFor.shares
|
||||
break
|
||||
case 'BY_PERCENTAGE':
|
||||
// Calculate the user's share based on their percentage of the total expense
|
||||
total += (expense.amount * userPaidFor.shares) / 10000 // Assuming shares are out of 10000 for percentage
|
||||
break
|
||||
case 'BY_SHARES':
|
||||
// Calculate the user's share based on their shares relative to the total shares
|
||||
const totalShares = paidFors.reduce(
|
||||
(sum, paidFor) => sum + paidFor.shares,
|
||||
0,
|
||||
)
|
||||
total += (expense.amount * userPaidFor.shares) / totalShares
|
||||
break
|
||||
}
|
||||
})
|
||||
const total = expenses.reduce(
|
||||
(sum, expense) => sum + calculateShare(activeUserId, expense),
|
||||
0,
|
||||
)
|
||||
|
||||
return parseFloat(total.toFixed(2))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user