Kuma/tests/zahlungen/invoice-actions.test.ts
2026-10-01 23:28:32 +02:00

129 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, it, expect, vi, beforeEach } from "vitest";
vi.mock("@/src/lib/prisma", () => ({
prisma: {
courseBooking: { findFirst: vi.fn() },
$transaction: vi.fn(),
},
}));
import { assembleInvoiceData } from "@/src/modules/zahlungen/actions/invoice-actions";
import { prisma } from "@/src/lib/prisma";
const manager = { businesses: [{ id: "biz-1" }] } as never;
function bookingWithTax(tax: Record<string, unknown>, billingAddress: unknown = null) {
return {
billingAddress,
amountPaid: 2950,
platformFee: 50,
platformFeeOnTop: true,
refundedAmount: null,
paymentStatus: "paid",
course: {
name: "Yoga",
date: new Date("2099-01-01"),
price: 29,
business: {
id: "biz-1",
name: "Studio",
address: "Weg 1, 12345 Stadt",
email: "studio@example.com",
ustId: null,
taxStatus: null,
vatRate: null,
taxNumber: null,
taxExemptionNote: null,
banking: null,
branding: null,
...tax,
},
},
user: {
name: "Kunde",
email: "k@example.com",
street: null,
houseNumber: null,
plz: null,
city: null,
},
guestLead: null,
};
}
beforeEach(() => {
vi.clearAllMocks();
});
describe("assembleInvoiceData – Steuerangaben", () => {
it("vergibt ohne Steuerangaben KEINE Rechnungsnummer", async () => {
vi.mocked(prisma.courseBooking.findFirst).mockResolvedValue(bookingWithTax({}) as never);
const result = await assembleInvoiceData("booking-1", manager);
expect(result).toEqual({ ok: false, reason: "missing_tax_settings" });
// Die Nummernvergabe läuft in einer Transaktion - sie darf gar nicht erst
// starten, sonst entsteht eine Lücke im Nummernkreis.
expect(prisma.$transaction).not.toHaveBeenCalled();
});
it("behandelt eine vorhandene USt-IdNr. ohne Status nicht als Steuerangabe", async () => {
vi.mocked(prisma.courseBooking.findFirst).mockResolvedValue(
bookingWithTax({ ustId: "DE123456789" }) as never
);
const result = await assembleInvoiceData("booking-1", manager);
expect(result).toEqual({ ok: false, reason: "missing_tax_settings" });
});
it("erstellt die Rechnung mit Steuersatz, wenn alles hinterlegt ist", async () => {
vi.mocked(prisma.courseBooking.findFirst).mockResolvedValue(
bookingWithTax({ taxStatus: "regular", vatRate: 19, taxNumber: "12/345/67890" }) as never
);
vi.mocked(prisma.$transaction).mockResolvedValue({
invoiceNumber: "INV-1",
issuedAt: new Date("2026-10-01"),
} as never);
const result = await assembleInvoiceData("booking-1", manager);
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.data.business.tax).toEqual({
status: "regular",
vatRate: 19,
taxNumber: "12/345/67890",
ustId: null,
});
// Die Studio-Rechnung zeigt nur den Kurspreis, nicht die Servicegebühr.
expect(result.data.amountCents).toBe(2900);
});
it("adressiert die Rechnung an die Anschrift aus dem Checkout", async () => {
vi.mocked(prisma.courseBooking.findFirst).mockResolvedValue(
bookingWithTax(
{ taxStatus: "small_business" },
{
name: "Erika Mustermann",
line1: "Sandweg 91",
line2: null,
postalCode: "60316",
city: "Frankfurt",
country: "DE",
}
) as never
);
vi.mocked(prisma.$transaction).mockResolvedValue({
invoiceNumber: "INV-2",
issuedAt: new Date("2026-10-01"),
} as never);
const result = await assembleInvoiceData("booking-1", manager);
expect(result.ok && result.data.customer).toMatchObject({
name: "Erika Mustermann",
addressLines: ["Sandweg 91", "60316 Frankfurt"],
});
});
});