mirror of
https://github.com/spliit-app/spliit.git
synced 2026-03-05 12:16:13 +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,
|
SplittingOptions,
|
||||||
expenseFormSchema,
|
expenseFormSchema,
|
||||||
} from '@/lib/schemas'
|
} from '@/lib/schemas'
|
||||||
|
import { calculateShare } from '@/lib/totals'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { AppRouterOutput } from '@/trpc/routers/_app'
|
import { AppRouterOutput } from '@/trpc/routers/_app'
|
||||||
import { zodResolver } from '@hookform/resolvers/zod'
|
import { zodResolver } from '@hookform/resolvers/zod'
|
||||||
@@ -591,6 +592,42 @@ export function ExpenseForm({
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<FormLabel className="text-sm font-normal flex-1">
|
<FormLabel className="text-sm font-normal flex-1">
|
||||||
{name}
|
{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>
|
</FormLabel>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
{form.getValues().splitMode !== 'EVENLY' && (
|
{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(
|
export function getTotalActiveUserShare(
|
||||||
activeUserId: string | null,
|
activeUserId: string | null,
|
||||||
expenses: NonNullable<Awaited<ReturnType<typeof getGroupExpenses>>>,
|
expenses: NonNullable<Awaited<ReturnType<typeof getGroupExpenses>>>,
|
||||||
): number {
|
): number {
|
||||||
let total = 0
|
const total = expenses.reduce(
|
||||||
|
(sum, expense) => sum + calculateShare(activeUserId, expense),
|
||||||
expenses.forEach((expense) => {
|
0,
|
||||||
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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return parseFloat(total.toFixed(2))
|
return parseFloat(total.toFixed(2))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user