added billing creation

This commit is contained in:
Rajpreet0 2026-09-29 13:05:38 +02:00
parent 0af1f37286
commit 0d1eb75127
40 changed files with 2040 additions and 152 deletions

View file

@ -0,0 +1,9 @@
import { Params } from "@/modules/shared/types/user"
import { BillingPanel } from "@/modules/shared/ui/views/billing-panel"
const BillingAdminPage = async ({ params } : { params: Params }) => {
const { id } = await params
return <BillingPanel profileId={id} isAdmin />
}
export default BillingAdminPage

View file

@ -1,10 +1,18 @@
import StammdatenAdminViewPage from "@/modules/admin/ui/views/stammdaten-admin-view"
type Params = Promise<{ id: string }>;
import { getStammdaten } from "@/modules/shared/queries/stammdaten"
import { Params } from "@/modules/shared/types/user"
import { StammdatenPanel } from "@/modules/shared/ui/views/stammdaten-panel"
const DataAdminPage = async ({ params }: { params: Params }) => {
const { id } = await params
return <StammdatenAdminViewPage clientProfileId={id} />
const stammdaten = await getStammdaten(id)
return (
<StammdatenPanel
profileId={id}
client={stammdaten?.client ?? null}
paymentDetails={stammdaten?.paymentDetails ?? null}
/>
)
}
export default DataAdminPage

View file

@ -1,8 +1,7 @@
import { notFound, redirect } from "next/navigation";
import { createAdminClient } from "@/lib/supabase/admin";
import { verifySession } from "@/lib/supabase/dal";
type Params = Promise<{ id: string }>;
import { Params } from "@/modules/shared/types/user";
export default async function ClientDetailLayout({
children,

View file

@ -1,7 +1,7 @@
import { notFound } from "next/navigation";
import { createAdminClient } from "@/lib/supabase/admin";
import { Params } from "@/modules/shared/types/user";
type Params = Promise<{ id: string }>;
const ClientDetailPage = async ({ params }: { params: Params }) => {
const { id } = await params;

View file

@ -1,10 +1,9 @@
import PasswordAdminViewPage from "@/modules/admin/ui/views/password-admin-view"
type Params = Promise<{id: string}>
import { Params } from "@/modules/shared/types/user"
import { CredentialsPanel } from "@/modules/shared/ui/views/credentials-panel"
const PasswordAdminPage = async ({ params } : { params: Params }) => {
const { id } = await params
return <PasswordAdminViewPage clientProfileId={id}/>
return <CredentialsPanel profileId={id} isAdmin />
}
export default PasswordAdminPage
export default PasswordAdminPage

View file

@ -0,0 +1,45 @@
import * as z from "zod"
import { zipSync } from "fflate"
import type { NextRequest } from "next/server"
import { getSession } from "@/lib/supabase/dal"
import { getInvoicesForPdf } from "@/modules/shared/queries/invoices"
import { invoiceFileName, renderInvoicePdf } from "@/modules/shared/pdf/render-invoice"
const IdsSchema = z.array(z.string().uuid()).min(1).max(100)
// Zugriff regelt RLS: Clients bekommen nur eigene, nicht stornierte Rechnungen zurück.
export async function GET(request: NextRequest) {
const user = await getSession()
if (!user) return Response.json({ error: "Nicht angemeldet." }, { status: 401 })
const parsed = IdsSchema.safeParse(request.nextUrl.searchParams.getAll("id"))
if (!parsed.success) return Response.json({ error: "Ungültige Auswahl." }, { status: 400 })
const invoices = await getInvoicesForPdf(parsed.data)
if (invoices.length === 0) return Response.json({ error: "Keine Rechnungen gefunden." }, { status: 404 })
if (invoices.length === 1) {
const pdf = await renderInvoicePdf(invoices[0])
const disposition = request.nextUrl.searchParams.has("inline") ? "inline" : "attachment"
return new Response(new Uint8Array(pdf), {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": `${disposition}; filename="${invoiceFileName(invoices[0].invoiceNumber)}"`,
"Cache-Control": "no-store",
},
})
}
const files: Record<string, Uint8Array> = {}
for (const invoice of invoices) {
files[invoiceFileName(invoice.invoiceNumber)] = new Uint8Array(await renderInvoicePdf(invoice))
}
return new Response(zipSync(files, { level: 0 }), {
headers: {
"Content-Type": "application/zip",
"Content-Disposition": "attachment; filename=\"Rechnungen.zip\"",
"Cache-Control": "no-store",
},
})
}

View file

@ -0,0 +1,41 @@
import * as z from "zod"
import { getProfile } from "@/lib/supabase/dal"
import { InvoiceDraftSchema, firstIssueMessage } from "@/modules/shared/schemas/invoice"
import { renderInvoicePdf } from "@/modules/shared/pdf/render-invoice"
const PreviewSchema = z.object({
draft: InvoiceDraftSchema,
invoiceNumber: z.string().trim().min(1).max(40),
})
export async function POST(request: Request) {
const profile = await getProfile()
if (!profile) return Response.json({ error: "Nicht angemeldet." }, { status: 401 })
if (profile.role !== "admin") return Response.json({ error: "Keine Berechtigung." }, { status: 403 })
const parsed = PreviewSchema.safeParse(await request.json().catch(() => null))
if (!parsed.success) {
return Response.json({ error: firstIssueMessage(parsed.error) }, { status: 400 })
}
const { draft, invoiceNumber } = parsed.data
const pdf = await renderInvoicePdf({
invoiceNumber,
invoiceDate: draft.invoiceDate,
dueDate: draft.dueDate,
companyName: draft.companyName,
billingStreet: draft.billingStreet,
billingPostalCode: draft.billingPostalCode,
billingCity: draft.billingCity,
billingCountry: draft.billingCountry,
items: draft.items,
})
return new Response(new Uint8Array(pdf), {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": "inline; filename=\"Vorschau.pdf\"",
"Cache-Control": "no-store",
},
})
}

View file

@ -0,0 +1,9 @@
import { verifySession } from "@/lib/supabase/dal"
import { BillingPanel } from "@/modules/shared/ui/views/billing-panel";
const BillingPage = async () => {
const session = await verifySession();
return <BillingPanel profileId={session.userId} isAdmin={false}/>
}
export default BillingPage

View file

@ -1,7 +1,18 @@
import StammdatenClientViewPage from "@/modules/client/ui/views/stammdaten-client-view"
import { verifySession } from "@/lib/supabase/dal"
import { getStammdaten } from "@/modules/shared/queries/stammdaten"
import { StammdatenPanel } from "@/modules/shared/ui/views/stammdaten-panel"
const DataPage = () => {
return <StammdatenClientViewPage/>
const DataPage = async () => {
const session = await verifySession()
const stammdaten = await getStammdaten(session.userId)
return (
<StammdatenPanel
profileId={session.userId}
client={stammdaten?.client ?? null}
paymentDetails={stammdaten?.paymentDetails ?? null}
/>
)
}
export default DataPage
export default DataPage

View file

@ -1,7 +1,9 @@
import PasswordClientViewPage from "@/modules/client/ui/views/password-client-view"
import { verifySession } from "@/lib/supabase/dal"
import { CredentialsPanel } from "@/modules/shared/ui/views/credentials-panel"
const PasswordPage = () => {
return <PasswordClientViewPage/>
const PasswordPage = async () => {
const session = await verifySession()
return <CredentialsPanel profileId={session.userId} isAdmin={false} />
}
export default PasswordPage
export default PasswordPage