mirror of
https://github.com/Since-3/Kuma.git
synced 2026-10-12 20:51:18 +00:00
129 lines
3.8 KiB
TypeScript
129 lines
3.8 KiB
TypeScript
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"],
|
||
});
|
||
});
|
||
});
|