Belegupload nach Freigabe und Bezahlt-Logik korrigieren
All checks were successful
CI / Build (push) Successful in 2m1s
CI / Deploy (push) Successful in 2m1s

This commit is contained in:
jan
2026-05-01 17:16:18 +02:00
parent 796e134ea2
commit bd59e50a51
4 changed files with 33 additions and 7 deletions

View File

@@ -576,7 +576,8 @@ export async function POST(_: Request, { params }: Context) {
data: { data: {
proofUrl: asNullableString(rollback.previousProofUrl), proofUrl: asNullableString(rollback.previousProofUrl),
invoiceDate: asDate(rollback.previousInvoiceDate, "Vorheriges Rechnungsdatum"), invoiceDate: asDate(rollback.previousInvoiceDate, "Vorheriges Rechnungsdatum"),
documentedAt: asDate(rollback.previousDocumentedAt, "Vorheriger Dokumentationszeitpunkt") documentedAt: asDate(rollback.previousDocumentedAt, "Vorheriger Dokumentationszeitpunkt"),
paidAt: asDate(rollback.previousPaidAt, "Vorheriger Bezahlt-Zeitpunkt")
} }
}); });
break; break;

View File

@@ -81,7 +81,8 @@ export async function POST(request: Request, { params }: Context) {
data: { data: {
proofUrl, proofUrl,
invoiceDate, invoiceDate,
documentedAt: expense.documentedAt ?? new Date() documentedAt: expense.documentedAt ?? new Date(),
paidAt: expense.paidAt ?? new Date()
} }
}); });
@@ -103,7 +104,9 @@ export async function POST(request: Request, { params }: Context) {
previousDocumentedAt: expense.documentedAt?.toISOString() ?? null, previousDocumentedAt: expense.documentedAt?.toISOString() ?? null,
nextProofUrl: updatedExpense.proofUrl, nextProofUrl: updatedExpense.proofUrl,
nextInvoiceDate: updatedExpense.invoiceDate?.toISOString() ?? null, nextInvoiceDate: updatedExpense.invoiceDate?.toISOString() ?? null,
nextDocumentedAt: updatedExpense.documentedAt?.toISOString() ?? null nextDocumentedAt: updatedExpense.documentedAt?.toISOString() ?? null,
previousPaidAt: expense.paidAt?.toISOString() ?? null,
nextPaidAt: updatedExpense.paidAt?.toISOString() ?? null
} }
} }
}); });

View File

@@ -186,6 +186,10 @@ export function BudgetColumn({
() => group.budgets.reduce((sum, budget) => sum + getApprovedSpend(budget.expenses), 0), () => group.budgets.reduce((sum, budget) => sum + getApprovedSpend(budget.expenses), 0),
[group.budgets] [group.budgets]
); );
const paidSpend = useMemo(
() => group.budgets.reduce((sum, budget) => sum + getPaidSpend(budget.expenses), 0),
[group.budgets]
);
const pendingSpend = useMemo( const pendingSpend = useMemo(
() => group.budgets.reduce((sum, budget) => sum + getPendingSpend(budget.expenses), 0), () => group.budgets.reduce((sum, budget) => sum + getPendingSpend(budget.expenses), 0),
[group.budgets] [group.budgets]
@@ -340,6 +344,12 @@ export function BudgetColumn({
label={`Freigegeben: ${formatCurrency(approvedSpend)}`} label={`Freigegeben: ${formatCurrency(approvedSpend)}`}
sx={{ ...wrappingChipSx, width: "fit-content" }} sx={{ ...wrappingChipSx, width: "fit-content" }}
/> />
<Chip
icon={<CheckCircleRoundedIcon />}
label={`Bezahlt: ${formatCurrency(paidSpend)}`}
color="info"
sx={{ ...wrappingChipSx, width: "fit-content" }}
/>
<Chip <Chip
icon={<ReceiptLongRoundedIcon />} icon={<ReceiptLongRoundedIcon />}
label={`Geplant: ${formatCurrency(pendingSpend)}`} label={`Geplant: ${formatCurrency(pendingSpend)}`}
@@ -391,12 +401,13 @@ export function BudgetColumn({
const draft = getDraft(budget); const draft = getDraft(budget);
const isEditing = editingBudgetId === budget.id; const isEditing = editingBudgetId === budget.id;
const budgetApproved = getApprovedSpend(budget.expenses); const budgetApproved = getApprovedSpend(budget.expenses);
const budgetPaid = getPaidSpend(budget.expenses);
const budgetPending = getPendingSpend(budget.expenses); const budgetPending = getPendingSpend(budget.expenses);
const budgetReleasedByPayments = getPaidSpend(budget.expenses); const budgetReleasedByPayments = getPaidSpend(budget.expenses);
const budgetReleasedTotal = budget.releasedAmount + budgetReleasedByPayments; const budgetReleasedTotal = budget.releasedAmount + budgetReleasedByPayments;
const budgetCommitted = budgetApproved + budgetPending; const budgetCommitted = budgetApproved + budgetPending;
const budgetRemaining = budget.totalBudget - budgetCommitted; const budgetRemaining = budget.totalBudget - budgetCommitted;
const approvedPercent = budget.totalBudget > 0 ? Math.min((budgetApproved / budget.totalBudget) * 100, 100) : 0; const paidPercent = budget.totalBudget > 0 ? Math.min((budgetPaid / budget.totalBudget) * 100, 100) : 0;
const cumulativePercent = const cumulativePercent =
budget.totalBudget > 0 ? Math.min((budgetCommitted / budget.totalBudget) * 100, 100) : 0; budget.totalBudget > 0 ? Math.min((budgetCommitted / budget.totalBudget) * 100, 100) : 0;
const releasedPercent = const releasedPercent =
@@ -485,7 +496,7 @@ export function BudgetColumn({
position: "absolute", position: "absolute",
insetInline: 0, insetInline: 0,
bottom: 0, bottom: 0,
height: `${approvedPercent}%`, height: `${paidPercent}%`,
backgroundColor: budget.colorCode, backgroundColor: budget.colorCode,
transition: "height 220ms ease" transition: "height 220ms ease"
}} }}
@@ -512,6 +523,12 @@ export function BudgetColumn({
label={`Freigegeben: ${formatCurrency(budgetApproved)}`} label={`Freigegeben: ${formatCurrency(budgetApproved)}`}
sx={{ ...wrappingChipSx, width: "fit-content", bgcolor: alpha(budget.colorCode, 0.14) }} sx={{ ...wrappingChipSx, width: "fit-content", bgcolor: alpha(budget.colorCode, 0.14) }}
/> />
<Chip
icon={<CheckCircleRoundedIcon />}
label={`Bezahlt: ${formatCurrency(budgetPaid)}`}
color="info"
sx={{ ...wrappingChipSx, width: "fit-content" }}
/>
<Chip <Chip
icon={<ReceiptLongRoundedIcon />} icon={<ReceiptLongRoundedIcon />}
label={`Geplant: ${formatCurrency(budgetPending)}`} label={`Geplant: ${formatCurrency(budgetPending)}`}
@@ -630,6 +647,7 @@ export function BudgetColumn({
: []; : [];
const isRecurringSeries = expense.recurrence === "MONTHLY"; const isRecurringSeries = expense.recurrence === "MONTHLY";
const isRecurringExpanded = expandedRecurringExpenses[expense.id] ?? false; const isRecurringExpanded = expandedRecurringExpenses[expense.id] ?? false;
const canUploadProof = expense.creator.id === viewer.id || canDocumentExpense(viewer.role);
return ( return (
<Box <Box
@@ -844,7 +862,7 @@ export function BudgetColumn({
{!expense.paidAt && {!expense.paidAt &&
expense.approvalStatus === "APPROVED" && expense.approvalStatus === "APPROVED" &&
!expense.proofUrl && !expense.proofUrl &&
canDocumentExpense(viewer.role) ? ( canUploadProof ? (
<Stack direction={{ xs: "column", sm: "row" }} gap={1} alignItems={{ sm: "center" }}> <Stack direction={{ xs: "column", sm: "row" }} gap={1} alignItems={{ sm: "center" }}>
<TextField <TextField
label="Rechnungsdatum" label="Rechnungsdatum"
@@ -910,7 +928,6 @@ export function BudgetColumn({
} }
await onUploadProof(expense.id, proofFile, invoiceDate); await onUploadProof(expense.id, proofFile, invoiceDate);
await onMarkPaid(expense.id);
}} }}
> >
Rechnung abgeben und bezahlt setzen Rechnung abgeben und bezahlt setzen

View File

@@ -769,6 +769,11 @@ export function DashboardShell({
}) })
)) as { proofUrl: string }; )) as { proofUrl: string };
setMessage({ type: "success", text: "Rechnung wurde abgegeben und die Ausgabe ist jetzt bezahlt." });
startTransition(() => {
router.refresh();
});
return result.proofUrl; return result.proofUrl;
} catch (error) { } catch (error) {
const text = error instanceof Error ? error.message : "Beleg konnte nicht hochgeladen werden."; const text = error instanceof Error ? error.message : "Beleg konnte nicht hochgeladen werden.";