Skip to main content

Proposals & Invoices · Article 5.9

Rechnungsnummerierung — fortlaufend pro Jahr und Typ, atomar, ohne Lücken

Jede Rechnung erhält eine fortlaufende Nummer aus einem atomaren Zähler, der pro Freelancer, Jahr und Dokumenttyp begrenzt ist. Keine Lücken gemäß EU-Mehrwertsteuerrichtlinie Art. 226(2) erlaubt; durch eine Sperr auf Zeilenebene in der Datenbank sichergestellt, damit gleichzeitige Vorgänge nicht kollidieren können.

Rechnungsnummerierung wirkt trivial, bis man bedenkt: Zwei gleichzeitig in Bearbeitung befindliche Rechnungen dürfen keine Nummerkollision haben; gelöschte Rechnungen können ihre Nummer nicht für die Wiederverwendung freigeben; eine Jahresgrenze muss sauber zurückgesetzt werden; Lücken in der Sequenz sind rote Fahnen bei Prüfungen. Das Zählerdesign löst all diese Probleme in einem Mechanismus — implementiert in apps/proposals/models.py:DocumentCounter und überall verwendet, wo ein Dokument nummeriert wird.

Why this works this way

Warum keine Lücken gemäß EU-Recht erlaubt sind. EU-Mehrwertsteuerrichtlinie Art. 226(2) verlangt, dass Rechnungsnummern „auf einer oder mehreren Serien beruhen, die die Rechnung eindeutig identifizieren". Nationale Umsetzungen (§14 UStG in DE, BTW Art. 35a in NL) verlangen ausdrücklich fortlaufende Sequenz ohne Lücken. Prüfer betrachten Sequenzlücken als Signal dafür, dass Rechnungen ausgestellt und dann zurückgehalten wurden (ein häufiges Steuerhinterziehungsmuster: eine Rechnung für Barzahlung drucken, sie vernichten, niemand bemerkt die Lücke). Der Prüfungsstandard: Jede Nummer von 0001 aufwärts in einem gegebenen Jahr muss in Ihren Unterlagen existieren.

Das Zählerdesign (apps/proposals/models.py:DocumentCounter):

``python class DocumentCounter(BaseModel): freelancer = ForeignKey(User) # Umfang: pro Freelancer document_type = CharField() # Umfang: pro Typ (PROPOSAL, DEPOSIT, FINAL, …) year = IntegerField() # Umfang: pro Jahr next_number = IntegerField(default=1) # der Wert class Meta: unique_together = [('freelancer', 'document_type', 'year')] ``

Das atomare Inkrement-Muster:

``python with transaction.atomic(): counter, created = DocumentCounter.objects.select_for_update().get_or_create( freelancer=proposal.freelancer, document_type='PROPOSAL', year=2026, defaults={'next_number': 1} ) number = counter.next_number counter.next_number += 1 counter.save() ``

select_for_update() gibt eine SELECT … FOR UPDATE-SQL-Abfrage aus, die eine Zeilensperre auf die Zählerzeile für die Dauer der Transaktion nimmt. Gleichzeitige Transaktionen für dieselbe Zählerzeile warten auf die Sperre; sobald die erste Transaktion committed, sieht die zweite next_number = 2 und fährt fort. Keine Kollision möglich.

Warum das für Änderungen und Storno wichtig ist. Wenn eine Änderung ein Storno (STR-) und eine neue INV (Revision 2) generiert, erhält jede ihre eigene fortlaufende Nummer aus ihrem eigenen Zähler:

- STR-2026-0001 (erstes Storno des Jahres) - INV-2026-0067 (nächste verfügbare INV in Ihrem Jahr, distinct von jeder früheren INV)

Die neue INV hat revision=2 der gleichen logischen Rechnung, erhält aber eine neue Nummer. Die ursprüngliche INV (Revision 1) behält ihre ursprüngliche Nummer; beide werden aufbewahrt (das Original wird als abgelöst markiert, nie gelöscht). So zeigt die INV-Sequenz keine Lücke — jede ausgestellte INV bleibt erhalten, nur mit einem abgelöst-Flag für die ersetzten.

Jahresgrenzen-Reset. Am 1. Januar ändert sich das year-Feld; neue Rechnungen schlagen (freelancer, type, 2027) nach — diese Zeile existiert noch nicht, daher erstellt get_or_create sie mit next_number=1. Die Zählerzeile des letzten Jahres bleibt unverändert (nicht löschen; nützlich für die Prüfung). Die erste Rechnung des neuen Jahres ist INV-2027-0001.

Keine Wiederverwendung, keine Lückenfüllung. Wenn eine Rechnung storniert wird (Storno), bleibt ihre Nummer in der Sequenz — das Storno ist ein neues Dokument, das auf sie verweist; es gibt die ursprüngliche Nummer nicht frei. Wenn eine Rechnung versehentlich ausgestellt wurde (z. B. Testdaten in der Produktion — sollte nie passieren, aber), belegt sie trotzdem ihren Platz. Der Prüfpfad wird aufbewahrt.

Unabhängigkeit pro Dokumenttyp. Ihr DEP-Zähler ist unabhängig von Ihrem INV-Zähler. Sie können also haben:

- DEP-2026-0042 + INV-2026-0042 (ein Projekt, bei dem DEP und INV zufällig auf dieselbe Seriennummer gefallen sind — Zufall, nicht erzwungen). - DEP-2026-0099 + INV-2026-0017 (DEP feuert öfter, wenn Sie viele kleine Kunden mit Anzahlungen, aber wenige Projektabschlüsse in einem Jahr haben).

Das ist beabsichtigt — verschiedene Dokumenttypen dienen verschiedenen rechtlichen Ereignis-Zeitvorgaben, daher sind ihre Sequenzen unabhängig.

Was passiert, wenn die Zählerzeile nicht mehr synchron ist? Äußerst selten (würde einen Datenbankintegritätsfehler erfordern); wenn es passiert, verwendet die nächste Rechnung eine vorhandene Nummer wieder → IntegrityError beim Einfügen wegen der eindeutigen Einschränkung auf (freelancer, type, year, number) auf dem Dokumentenmodell. Clozo fängt dies auf und versucht es mit der nächsten verfügbaren Nummer erneut. Für den Endnutzer sichtbarer Einfluss: zusätzliche ca. 50 ms beim seltenen kollidierenden Speichern.

Benutzerdefinierte Nummerierungspräfixe pro Unternehmen. Derzeit nicht konfigurierbar — jeder Clozo-Nutzer hat dieselben Präfixe INV- / DEP- / STR- / DCR- / CRN- / REC- / PRO- / AGR-. Einige ältere Buchhaltungssysteme bevorzugen rein numerische oder unternehmensspezifische Präfixe (z. B. „RE-2026-0042" für eine deutsche „Rechnung"). Auf der Roadmap; vorerst sind die standardisierten Präfixe EU-weit anerkannt und von jeder Steuerbehörde akzeptiert, gegen die wir getestet haben.

Troubleshooting

Keep reading