Add balance transactions to user admin panel

Introduces BalanceTransaction model to track per-user balance history
(prepayments, refunds, partner credits). Admin can add/delete transactions;
current balance is computed as the running sum.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-07 09:24:25 +05:00
parent 48721759d3
commit 93e74951a7
8 changed files with 593 additions and 13 deletions
+19
View File
@@ -59,6 +59,25 @@ export async function updateUserContact(
revalidatePath(`/admin/users/${userId}`);
}
export async function addBalanceTransaction(
userId: string,
data: { amount: string; description: string }
) {
await requireAdmin();
const amount = parseFloat(data.amount.replace(",", "."));
if (isNaN(amount) || amount === 0) throw new Error("Некорректная сумма");
await prisma.balanceTransaction.create({
data: { userId, amount, description: data.description.trim() },
});
revalidatePath(`/admin/users/${userId}`);
}
export async function deleteBalanceTransaction(userId: string, txId: string) {
await requireAdmin();
await prisma.balanceTransaction.delete({ where: { id: txId } });
revalidatePath(`/admin/users/${userId}`);
}
export async function revokeUserAccess(userId: string, courseId: string) {
const session = await requireAdmin();
await prisma.courseEnrollment.delete({
+20
View File
@@ -3,6 +3,7 @@ import { notFound } from "next/navigation";
import Link from "next/link";
import { UserEnrollmentManager } from "@/components/admin/user-enrollment-manager";
import { UserContactEditor } from "@/components/admin/user-contact-editor";
import { UserBalanceBlock } from "@/components/admin/user-balance-block";
interface Props {
params: Promise<{ userId: string }>;
@@ -27,6 +28,9 @@ export default async function UserPage({ params }: Props) {
grantedBy: { select: { name: true } },
},
},
balanceTransactions: {
orderBy: { createdAt: "desc" },
},
},
}),
prisma.course.findMany({
@@ -73,6 +77,22 @@ export default async function UserPage({ params }: Props) {
</div>
</section>
{/* Balance */}
<section className="card-aubade p-6 mb-6">
<p className="text-xs font-bold uppercase tracking-widest mb-5" style={{ color: "var(--muted-foreground)" }}>
Баланс
</p>
<UserBalanceBlock
userId={userId}
transactions={user.balanceTransactions.map((tx) => ({
id: tx.id,
amount: Number(tx.amount),
description: tx.description,
createdAt: tx.createdAt,
}))}
/>
</section>
{/* Enrollments + bulk grant */}
<section className="card-aubade p-6 mb-6">
<p className="text-xs font-bold uppercase tracking-widest mb-5" style={{ color: "var(--muted-foreground)" }}>