feat: Initial implementation of Annas Rechnungsmanager

Full-stack German accounting & invoice management web application:

- Multi-company management (Mandantenverwaltung) with full CRUD
- Invoice creation with dynamic line items and automatic tax calculation
- Sequential invoice numbering per company (RE-2024-001 format)
- §14 UStG compliant PDF invoice generation via @react-pdf/renderer
- Customer management (Kundenverwaltung) per company
- Tax reports: quarterly USt-Voranmeldung and monthly revenue overview
- Email/password authentication via NextAuth.js v5
- Responsive, modern UI with Tailwind CSS and custom shadcn/ui components
- Prisma v5 ORM with MySQL/MariaDB schema + demo seed data

Stack: Next.js 14 (App Router) · TypeScript · Prisma/MySQL · NextAuth.js

https://claude.ai/code/session_01FN53KKxo5ebrGwqFhxzkT9
This commit is contained in:
Claude
2026-03-07 17:27:57 +00:00
commit 44e79e657f
65 changed files with 12722 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import prisma from "@/lib/prisma";
import { InvoiceStatus } from "@prisma/client";
import { z } from "zod";
async function getInvoice(id: string, userId: string) {
return prisma.invoice.findFirst({
where: { id, company: { userId } },
include: {
items: { orderBy: { position: "asc" } },
customer: true,
company: true,
},
});
}
export async function GET(_req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const session = await auth();
if (!session?.user?.id) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const { id } = await params;
const invoice = await getInvoice(id, session.user.id);
if (!invoice) return NextResponse.json({ error: "Not found" }, { status: 404 });
return NextResponse.json(invoice);
}
const statusSchema = z.object({
status: z.nativeEnum(InvoiceStatus),
});
export async function PATCH(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const session = await auth();
if (!session?.user?.id) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const { id } = await params;
const invoice = await getInvoice(id, session.user.id);
if (!invoice) return NextResponse.json({ error: "Not found" }, { status: 404 });
const body = await req.json();
const parsed = statusSchema.safeParse(body);
if (!parsed.success) return NextResponse.json({ error: parsed.error.issues }, { status: 400 });
const updated = await prisma.invoice.update({
where: { id },
data: { status: parsed.data.status },
});
return NextResponse.json(updated);
}
export async function DELETE(_req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const session = await auth();
if (!session?.user?.id) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const { id } = await params;
const invoice = await getInvoice(id, session.user.id);
if (!invoice) return NextResponse.json({ error: "Not found" }, { status: 404 });
await prisma.invoice.delete({ where: { id } });
return NextResponse.json({ ok: true });
}