Gutschriften Phase 2a: Kleinunternehmer-Flag am Kunden + USt-Default

Kunden-Feld vatExempt (Kleinunternehmer/USt-befreit §19) + Migration
(ADD COLUMN IF NOT EXISTS). Checkbox im Kundenformular nur fuer
Firmenkunden.

Gutschrift-Vorbelegung vatRelevant wird aus dem Kunden abgeleitet:
Firmenkunde ohne USt-Befreiung -> USt-relevant an (Netto), sonst aus
(wie Privat). Jede Gutschrift speichert ihren eigenen Snapshot, ein
spaeterer Statuswechsel des Kunden aendert bestehende Gutschriften
nicht.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-06 10:10:13 +02:00
co-authored by Claude Opus 4.8
parent f1c37a7d25
commit 3580c51cb1
9 changed files with 54 additions and 8 deletions
@@ -0,0 +1,3 @@
-- Kleinunternehmer/USt-befreit-Flag am Kunden. Steuert nur die Vorbelegung
-- neuer Gutschriften (Firmenkunde mit vatExempt=true -> keine USt-Vorbelegung).
ALTER TABLE `Customer` ADD COLUMN IF NOT EXISTS `vatExempt` BOOLEAN NOT NULL DEFAULT false;
+6
View File
@@ -156,6 +156,12 @@ model Customer {
phone String?
mobile String?
taxNumber String?
// Kleinunternehmer nach §19 UStG bzw. sonst USt-befreit: dann wird der
// (Firmen-)Kunde bei Gutschriften wie ein Privatkunde behandelt (keine USt),
// bis er ggf. USt-pflichtig wird. Steuert nur die VORBELEGUNG neuer
// Gutschriften; bereits erstellte Gutschriften speichern ihren eigenen
// Snapshot und bleiben von einem späteren Wechsel unberührt.
vatExempt Boolean @default(false)
businessRegistrationPath String? // PDF-Pfad zur Gewerbeanmeldung
commercialRegisterPath String? // PDF-Pfad zum Handelsregisterauszug
commercialRegisterNumber String? // Handelsregisternummer (Text)
@@ -60,11 +60,11 @@ export async function formDefaults(req: AuthRequest, res: Response): Promise<voi
if (blockPortal(req, res)) return;
const contractId = idParam(req, res, 'contractId');
if (contractId === null) return;
const [customerType, nextNumber] = await Promise.all([
creditNoteService.getDefaultCustomerType(contractId),
const [defaults, nextNumber] = await Promise.all([
creditNoteService.getCreditNoteDefaults(contractId),
numberRangeService.previewNextNumber(),
]);
res.json({ success: true, data: { customerType, nextNumber } } as ApiResponse);
res.json({ success: true, data: { ...defaults, nextNumber } } as ApiResponse);
} catch (error) {
handleError(res, error, 'Fehler beim Laden der Vorbelegung');
}
+17 -4
View File
@@ -132,13 +132,26 @@ export async function getCreditNoteById(id: number) {
return prisma.creditNote.findUnique({ where: { id } });
}
// Ermittelt den Default-Kundentyp aus dem Vertrag (Firma vs. Privat).
export async function getDefaultCustomerType(contractId: number): Promise<CreditNoteCustomerType> {
// Ermittelt die Formular-Vorbelegung aus dem Kunden:
// - customerType: Firma vs. Privat (aus Kunde.type)
// - vatRelevant : Default nur bei Firmenkunde OHNE USt-Befreiung
// (Kleinunternehmer §19 → wie Privat, keine USt-Vorbelegung).
// WICHTIG: Das ist nur die Vorbelegung. Jede angelegte Gutschrift speichert
// ihren eigenen Snapshot; ein späterer Statuswechsel des Kunden ändert
// bestehende Gutschriften nicht.
export async function getCreditNoteDefaults(
contractId: number,
): Promise<{ customerType: CreditNoteCustomerType; vatRelevant: boolean }> {
const contract = await prisma.contract.findUnique({
where: { id: contractId },
select: { customer: { select: { type: true } } },
select: { customer: { select: { type: true, vatExempt: true } } },
});
return contract?.customer?.type === 'BUSINESS' ? 'FIRMA' : 'PRIVAT';
const isBusiness = contract?.customer?.type === 'BUSINESS';
const vatExempt = !!contract?.customer?.vatExempt;
return {
customerType: isBusiness ? 'FIRMA' : 'PRIVAT',
vatRelevant: isBusiness && !vatExempt,
};
}
export async function createCreditNote(
+2
View File
@@ -146,6 +146,7 @@ export async function createCustomer(data: {
phone?: string;
mobile?: string;
taxNumber?: string;
vatExempt?: boolean;
businessRegistration?: string;
commercialRegister?: string;
notes?: string;
@@ -173,6 +174,7 @@ export async function updateCustomer(
phone?: string;
mobile?: string;
taxNumber?: string;
vatExempt?: boolean;
businessRegistration?: string;
commercialRegister?: string;
notes?: string;
+1
View File
@@ -618,6 +618,7 @@ const CUSTOMER_UPDATABLE_FIELDS = [
'phone',
'mobile',
'taxNumber',
'vatExempt',
'commercialRegisterNumber',
'notes',
'portalEnabled',