Finanzuebersicht Stichtage und Spenden ergaenzen
All checks were successful
CI / Build and Deploy (push) Successful in 3m11s

This commit is contained in:
jan
2026-05-11 23:41:07 +02:00
parent c93616f09e
commit c738b35d06
14 changed files with 884 additions and 62 deletions

View File

@@ -7,6 +7,7 @@ import { getRollbackMetadata } from "@/lib/audit-log";
import type {
DashboardAccountingPeriod,
DashboardAuditLog,
DashboardDonation,
DashboardManagedUser,
DashboardSettings,
DashboardViewer,
@@ -141,6 +142,45 @@ export default async function DashboardPage() {
})
: [];
const periodCutoffs = await prisma.$queryRaw<
{ id: string; cutoff_name: string; cutoff_date: Date | null }[]
>`SELECT id, cutoff_name, cutoff_date FROM accounting_periods`;
const expenseCutoffs = await prisma.$queryRaw<
{ id: string; cutoff_phase: "PRE" | "POST" }[]
>`SELECT id, cutoff_phase FROM expenses WHERE period_id = ${currentPeriod.id}`;
const donationRows = await prisma.$queryRaw<
{
id: string;
title: string;
description: string | null;
amount: unknown;
donated_at: Date;
period_id: string;
expense_id: string | null;
created_at: Date;
creator_id: string;
creator_name: string;
}[]
>`
SELECT d.id, d.title, d.description, d.amount, d.donated_at, d.period_id, d.expense_id, d.created_at,
u.id AS creator_id, u.username AS creator_name
FROM donations d
JOIN users u ON u.id = d.creator_id
WHERE d.period_id = ${currentPeriod.id}
ORDER BY d.donated_at DESC
`;
const periodCutoffById = new Map(periodCutoffs.map((period) => [period.id, period]));
const expenseCutoffById = new Map(expenseCutoffs.map((expense) => [expense.id, expense.cutoff_phase]));
const donationsByExpenseId = new Map<string, number>();
for (const donation of donationRows) {
if (donation.expense_id) {
donationsByExpenseId.set(
donation.expense_id,
(donationsByExpenseId.get(donation.expense_id) ?? 0) + Number(donation.amount)
);
}
}
const serializedViewer: DashboardViewer = {
id: viewer.id,
name: viewer.username,
@@ -197,6 +237,13 @@ export default async function DashboardPage() {
approvalStatus: expense.approvalStatus,
recurrence: expense.recurrence,
recurrenceStartAt,
cutoffPhase: expenseCutoffById.get(expense.id) ?? "PRE",
donationAmount: donationsByExpenseId.get(expense.id) ?? 0,
netPeriodAmount: Math.max(
0,
getExpensePeriodAmount(amount, expense.recurrence, occurrences.length) -
(donationsByExpenseId.get(expense.id) ?? 0)
),
paidAt: expense.paidAt?.toISOString() ?? null,
documentedAt: expense.documentedAt?.toISOString() ?? null,
documents: expense.documents.map((document) => ({
@@ -249,7 +296,24 @@ export default async function DashboardPage() {
name: period.name,
startsAt: period.startsAt.toISOString(),
endsAt: period.endsAt.toISOString(),
isCurrent: period.isCurrent
isCurrent: period.isCurrent,
cutoffName: periodCutoffById.get(period.id)?.cutoff_name ?? "Open Air",
cutoffDate: periodCutoffById.get(period.id)?.cutoff_date?.toISOString() ?? null
}));
const serializedDonations: DashboardDonation[] = donationRows.map((donation) => ({
id: donation.id,
title: donation.title,
description: donation.description,
amount: Number(donation.amount),
donatedAt: donation.donated_at.toISOString(),
periodId: donation.period_id,
expenseId: donation.expense_id,
createdAt: donation.created_at.toISOString(),
creator: {
id: donation.creator_id,
name: donation.creator_name
}
}));
const serializedSettings: DashboardSettings = serializeAppSettings(appSettings);
@@ -282,6 +346,7 @@ export default async function DashboardPage() {
accountingPeriods={serializedPeriods}
currentPeriodId={currentPeriod.id}
settings={serializedSettings}
donations={serializedDonations}
/>
);
}