feat: add client-side validation utilities and debugging tools
Build and Push Docker Image / build (push) Successful in 1m23s

- Implemented client-side validation functions for tax ID, VAT ID, IBAN, BIC, and website URL.
- Added debug logging functionality to assist in development.
- Created a comprehensive validation function for company form data.

feat: initialize database with Prisma migrations

- Added a server-side script to run Prisma migrations and check database health.
- Ensured safe initialization of the database to prevent concurrent migrations.

feat: comprehensive server-side error logging

- Developed an error logging system that captures detailed error context, including request details and stack traces.
- Implemented logging functions for different error types (route, action, database, API, startup).

fix: validate user ID existence in audit logs

- Updated the logging function to validate that the user ID exists in the database before logging actions.

fix: update schemas for optional fields and validation

- Modified schemas to allow for nullable fields and refined validation logic for tax ID, VAT ID, IBAN, and BIC.

feat: enhance error boundary for better debugging

- Improved error boundary to log detailed error information in development mode.
- Added a debug panel to the main application layout for real-time error tracking.

feat: implement company deletion functionality in admin routes

- Added a new API route for deleting companies with appropriate logging.
- Integrated delete confirmation in the admin interface for better user experience.

fix: handle API errors gracefully

- Wrapped API actions in try-catch blocks to log errors and return appropriate responses.

feat: generate and save invoice PDFs

- Implemented functionality to generate and save invoice PDFs upon status updates.
- Added a new column in the database for storing the URL of the generated PDF.

chore: update Docker image reference

- Changed the Docker image reference to point to the new Git repository.

chore: update package dependencies

- Added @radix-ui/react-tooltip for enhanced UI components.
- Updated package-lock.json to reflect new dependencies.
This commit is contained in:
hwinkel
2026-05-03 08:46:58 +02:00
parent c3e7a97c8a
commit b22e5baa5c
26 changed files with 1573 additions and 134 deletions
+48 -1
View File
@@ -5,6 +5,8 @@ import { calcItemAmounts, calcInvoiceTotals, calcItemAmountsKleinunternehmer } f
import { log } from "@/lib/logger.server";
import { InvoiceStatus } from "@prisma/client";
import { invoiceUpdateSchema, invoiceStatusSchema, invoiceItemSchema } from "@/lib/schemas";
import { writeFile, mkdir } from "node:fs/promises";
import { join, resolve } from "node:path";
async function getInvoice(id: string, userId: string) {
return prisma.invoice.findFirst({
@@ -13,6 +15,39 @@ async function getInvoice(id: string, userId: string) {
});
}
/** Storage root for documents */
function storageRoot(): string {
return resolve(process.env.BELEG_STORAGE_PATH ?? "data/documents");
}
/** Generate and save invoice PDF as beleg (receipt) */
async function generateAndSaveInvoicePDF(invoice: Awaited<ReturnType<typeof getInvoice>>, userId: string): Promise<string | null> {
if (!invoice) return null;
try {
const { renderToBuffer } = await import("@react-pdf/renderer");
const React = (await import("react")).default;
const { InvoicePDFDocument } = await import("@/components/invoice/invoice-pdf");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const element = React.createElement(InvoicePDFDocument as any, { invoice }) as any;
const buffer = await renderToBuffer(element);
// Save to storage
const safeName = `${invoice.id}-${Date.now()}.pdf`;
const userDir = join(storageRoot(), userId);
await mkdir(userDir, { recursive: true });
await writeFile(join(userDir, safeName), Buffer.from(buffer));
// Return as "beleg:{userId}/{storedName}|{originalName}"
const originalName = `rechnung-${invoice.number ?? invoice.id}.pdf`;
return `beleg:${userId}/${safeName}|${originalName}`;
} catch {
console.error("Failed to generate invoice PDF");
return null;
}
}
export async function loader({ request, params }: { request: Request; params: { id: string } }) {
const user = await getApiUser(request);
if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 });
@@ -148,6 +183,16 @@ export async function action({ request, params }: { request: Request; params: {
// Handle Buchung sync: Create when PAID, delete when unpaying
if (newStatus === "PAID" && oldStatus !== "PAID") {
// Generate and save invoice PDF as beleg
const belegUrl = await generateAndSaveInvoicePDF(invoice, user.id);
// Calculate weighted average tax rate (kann mehrere Items mit unterschiedlichen Steuersätzen geben)
let averageTaxRate = 0;
if (invoice.taxTotal > 0 && invoice.netTotal > 0) {
// steuersatz = (taxTotal / netTotal) * 100
averageTaxRate = Math.round((invoice.taxTotal / invoice.netTotal) * 100);
}
// Create a Buchung for the invoice payment
const buchung = await prisma.buchung.create({
data: {
@@ -159,6 +204,8 @@ export async function action({ request, params }: { request: Request; params: {
description: `Rechnung ${invoice.number}`,
kategorie: "Rechnungseinnahme",
isBusinessRecord: true,
steuersatz: invoice.kleinunternehmer ? 0 : averageTaxRate, // 0 for Kleinunternehmer
belegUrl: belegUrl, // Attach the generated invoice PDF
},
});
@@ -179,7 +226,7 @@ export async function action({ request, params }: { request: Request; params: {
action: "UPDATE_INVOICE_STATUS",
entity: "Invoice",
entityId: params.id,
metadata: { oldStatus, newStatus, buchungId: buchung.id },
metadata: { oldStatus, newStatus, buchungId: buchung.id, belegUrl, steuersatz: averageTaxRate },
request,
});