Table of Contents
- 1. GoBD-Kernanforderungen und deren Umsetzung
- 2. Konkrete Funktionen für deine Rechnungsverwaltung
- A. Rechnungserstellung (GoBD-konform)
- B. Archivierung und Aufbewahrung
- C. Sicherheit und Zugriffskontrolle
- D. Prüfung und Dokumentation
- 3. Code-Beispiele für GoBD-konforme Funktionen
- A. Rechnungsmodell mit GoBD-Logik
- B. Audit-Logging mit laravel-auditing
- C. ZUGFeRD-PDF-Generierung
- D. VIES-API für USt-ID-Prüfung
- E. Automatische Backups mit spatie/laravel-backup
- F. GoBD-konforme Speicherung (Storage-Konfiguration)
- G. Policies für Berechtigungen
- 4. Frontend-Integration (Vue.js + Inertia)
- 5. GoBD-konforme Dokumentation
- 6. Checkliste für GoBD-Konformität
- 7. Tools für die GoBD-Umsetzung
- 8. Zusammenfassung: Was du jetzt tun solltest
- Frage an dich
Zum Vergleich: https://www.odoo.com/documentation/19.0/applications/finance/fiscal_localizations/germany.html
Für eine GoBD-konforme Rechnungsverwaltung in deinem ERP/CRM-System müssen bestimmte Funktionen und Prozesse implementiert sein, um den Anforderungen der Grundsätze zur ordnungsmäßigen Führung und Aufbewahrung von Büchern, Aufzeichnungen und Unterlagen in elektronischer Form (GoBD) zu entsprechen. Hier sind die konkreten Funktionen, die du implementieren solltest, inklusive technischer Umsetzungsvorschläge für Laravel/Vue.js:
1. GoBD-Kernanforderungen und deren Umsetzung
Die GoBD (ab 2020 gültig) stellt folgende zentrale Anforderungen an digitale Rechnungssysteme:
| GoBD-Anforderung | Konkrete Umsetzung in deinem ERP/CRM | Technische Lösung (Laravel/Vue.js) |
|---|---|---|
| 1. Nachvollziehbarkeit | Jede Rechnung muss lückenlos nachvollziehbar sein (wer, wann, was geändert hat). | Laravel Auditing für Änderungen + Revision History in der Datenbank. |
| 2. Unveränderbarkeit | Gestellte Rechnungen dürfen nicht mehr geändert werden (nur Stornierung oder Gutschrift möglich). | Immutable Records: Rechnungen nach dem "Stellen" als readonly markieren. |
| 3. Vollständigkeit | Keine Lücken in der Rechnungsnummernfolge. | Automatische Nummerngenerierung mit Transaktionen (siehe Code-Beispiele unten). |
| 4. Aufbewahrungspflicht | Rechnungen müssen 10 Jahre unverändert archiviert werden. | Automatische Backups (z. B. mit spatie/laravel-backup) + Speicherung als PDF/XML. |
| 5. Zeitgerechte Buchung | Rechnungen müssen zeitnah (innerhalb weniger Tage) erfasst werden. | Automatische Erinnerungen für offene Entwürfe (z. B. mit Laravel Scheduling). |
| 6. Ordnungsgemäße Speicherung | Rechnungen müssen in maschinenlesbarer Form (z. B. ZUGFeRD/XML) gespeichert werden. | PDF + XML-Export (z. B. mit zugferd/php). |
| 7. Zugriffsschutz | Nur autorisierte Personen dürfen Rechnungen ändern/löschen. | Laravel Policies für Berechtigungen. |
| 8. Prüfbarkeit | Rechnungen müssen jederzeit prüfbar sein (z. B. durch Finanzamt). | Prüfpade (Audit Logs) + Dokumentation von Änderungen. |
| 9. Belegabbildung | Rechnungen müssen 1:1 dem Original entsprechen (keine Datenverluste bei Export/Import). | ZUGFeRD/XRechnung für elektronische Rechnungen. |
| 10. Verfahrensdokumentation | Es muss eine Dokumentation der Prozesse existieren (z. B. wie Rechnungen erstellt/archiviert werden). | Automatisch generierte Dokumentation (z. B. mit Laravel Nova oder Markdown-Export). |
2. Konkrete Funktionen für deine Rechnungsverwaltung
A. Rechnungserstellung (GoBD-konform)
| Funktion | Beschreibung | Technische Umsetzung |
|---|---|---|
| Entwürfe vs. gestellte Rechnungen | Rechnungen werden erst als Entwurf gespeichert und erhalten erst bei "Stellen" eine Nummer. | Status-Feld (draft, issued, sent, paid, cancelled) + number-Feld (nullable). |
| Automatische Nummerngenerierung | Rechnungsnummern werden fortlaufend und ohne Lücken vergeben (z. B. RE-2023-001). |
Datenbank-Transaktionen + generateNumber()-Methode (siehe Code unten). |
| Unveränderlichkeit nach Stellung | Gestellte Rechnungen können nicht mehr bearbeitet, sondern nur storniert werden. | readonly-Felder im Frontend + Datenbank-Constraints. |
| ZUGFeRD/XRechnung-Export | Rechnungen werden im GoBD-konformen Format (PDF + XML) exportiert. | zugferd/php-Bibliothek (siehe Beispiel unten). |
| Mehrwertsteuer-Validierung | Prüft gültige USt-IDs (z. B. über die VIES-API). | HTTP-Request an VIES-API (siehe Code unten). |
B. Archivierung und Aufbewahrung
| Funktion | Beschreibung | Technische Umsetzung |
|---|---|---|
| Automatische PDF/XML-Speicherung | Jede gestellte Rechnung wird als PDF + XML in einem unveränderlichen Speicher abgelegt. | Laravel Storage (z. B. S3) + Backups mit spatie/laravel-backup. |
| 10-Jahres-Aufbewahrung | Rechnungen werden automatisch archiviert und sind 10 Jahre lang verfügbar. | Laravel Retention Policies (z. B. mit aws/s3-lifecycle) oder Datenbank-Archivtabellen. |
| Unveränderliche Backups | Backups werden verschlüsselt und signiert abgelegt, um Manipulationen zu verhindern. | spatie/laravel-backup mit Verschlüsselung. |
| Prüfpfade (Audit Logs) | Alle Änderungen an Rechnungen werden protokolliert (wer, wann, was). | laravel-auditing (siehe Beispiel unten). |
C. Sicherheit und Zugriffskontrolle
| Funktion | Beschreibung | Technische Umsetzung |
|---|---|---|
| Rollenbasierte Berechtigungen | Nur Buchhalter/Admins dürfen Rechnungen stellen/stornieren. | Laravel Policies (z. B. InvoicePolicy). |
| Zwei-Faktor-Authentifizierung | Zusätzliche Sicherheit für Rechnungsfreigaben. | laravel-fortify oder laravel-2fa. |
| IP- und Zeitstempel-Logging | Protokolliert wer, wann und von wo Rechnungen bearbeitet wurden. | Middleware für Request-Logging. |
| Datenverschlüsselung | Sensible Rechnungsdaten (z. B. Kundendaten) werden verschlüsselt gespeichert. | Laravel Encryption (Illuminate\Support\Facades\Crypt) oder spatie/laravel-encryption. |
D. Prüfung und Dokumentation
| Funktion | Beschreibung | Technische Umsetzung |
|---|---|---|
| Automatische Prüfprotokolle | Generiert Prüfberichte für das Finanzamt (z. B. monatliche Zusammenfassungen). | Laravel Reports (z. B. mit laravel-excel). |
| Verfahrensdokumentation | Dokumentiert wie Rechnungen erstellt/archiviert werden (Pflicht nach GoBD). | Markdown-Dokumentation im Projekt + automatische API-Docs (z. B. mit Swagger). |
| Revisionssichere Protokolle | Alle Änderungen werden unveränderlich protokolliert. | Datenbank-Trigger oder Laravel Events. |
3. Code-Beispiele für GoBD-konforme Funktionen
A. Rechnungsmodell mit GoBD-Logik
// app/Models/Invoice.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use OwenIt\Auditing\Auditable;
use OwenIt\Auditing\Contracts\Auditable as AuditableContract;
use Illuminate\Support\Facades\Storage;
class Invoice extends Model implements AuditableContract
{
use Auditable; // Für Audit Logs
protected $casts = [
'date' => 'date',
'billing_address' => 'array',
'shipping_address' => 'array',
'metadata' => 'array', // Zusätzliche Metadaten (z. B. für GoBD)
];
protected $auditInclude = [
'number',
'date',
'status',
'total',
'customer_id',
];
/**
* Generiert eine GoBD-konforme Rechnungsnummer.
*/
public static function generateNumber(): string
{
$year = now()->format('Y');
$prefix = 'RE'; // Präfix für Rechnungen
return DB::transaction(function () use ($year, $prefix) {
$lastInvoice = self::where('number', 'like', "{$prefix}-{$year}-%")
->lockForUpdate() // Sperrt die Zeilen für andere Transaktionen
->latest()
->first();
$nextNumber = $lastInvoice ? (int) substr($lastInvoice->number, -3) + 1 : 1;
return sprintf('%s-%s-%03d', $prefix, $year, $nextNumber);
});
}
/**
* Stellt die Rechnung (vergibt Nummer und macht sie unveränderlich).
*/
public function issue(): void
{
if ($this->status !== 'draft') {
throw new \Exception('Nur Entwürfe können gestellt werden.');
}
DB::transaction(function () {
$this->update([
'number' => self::generateNumber(),
'date' => now(),
'status' => 'issued',
]);
// PDF/XML generieren und speichern (GoBD-Pflicht!)
$this->generateAndStorePdf();
});
}
/**
* Generiert ein ZUGFeRD-konformes PDF/XML und speichert es.
*/
protected function generateAndStorePdf(): void
{
$pdfService = new \App\Services\ZugferdPdfService();
$pdfPath = $pdfService->generate($this);
// Speichere PDF in unveränderlichem Storage (z. B. S3 mit Versionierung)
Storage::disk('invoices')->put(
"{$this->number}.pdf",
file_get_contents($pdfPath)
);
// Optional: XML separat speichern (für maschinelle Verarbeitung)
$xml = $pdfService->getXml($this);
Storage::disk('invoices')->put(
"{$this->number}.xml",
$xml
);
// Pfad in der Datenbank speichern (für spätere Referenz)
$this->update([
'pdf_path' => "invoices/{$this->number}.pdf",
'xml_path' => "invoices/{$this->number}.xml",
]);
}
/**
* Storniert die Rechnung (erstellt eine Gutschrift).
*/
public function cancel(string $reason): void
{
if ($this->status === 'cancelled') {
throw new \Exception('Rechnung ist bereits storniert.');
}
DB::transaction(function () use ($reason) {
$this->update([
'status' => 'cancelled',
'cancelled_at' => now(),
'cancel_reason' => $reason,
]);
// Erstelle eine Gutschrift (Credit Note)
$creditNote = $this->replicate();
$creditNote->number = 'CN-' . substr($this->number, 3); // CN-2023-001
$creditNote->status = 'issued';
$creditNote->type = 'credit_note';
$creditNote->total = -$this->total; // Negativer Betrag
$creditNote->save();
});
}
}
B. Audit-Logging mit laravel-auditing
-
Paket installieren:
composer require owen-it/laravel-auditing php artisan vendor:publish --provider="OwenIt\Auditing\AuditingServiceProvider" php artisan migrate -
Model anpassen (bereits im Beispiel oben enthalten).
-
Audit-Logs abfragen:
// Alle Änderungen einer Rechnung abrufen $audits = $invoice->audits() ->with('user') // Wer hat geändert? ->latest() ->get();
C. ZUGFeRD-PDF-Generierung
// app/Services/ZugferdPdfService.php
namespace App\Services;
use App\Models\Invoice;
use ZUGFeRD\ZUGFeRDDocument;
class ZugferdPdfService
{
public function generate(Invoice $invoice): string
{
$document = new ZUGFeRDDocument();
$document->setNumber($invoice->number);
$document->setDate($invoice->date);
$document->setSeller([
'name' => config('app.name'),
'street' => config('app.address.street'),
'zip' => config('app.address.zip'),
'city' => config('app.address.city'),
'country' => config('app.address.country'),
'vat_id' => config('app.vat_id'),
]);
$document->setBuyer([
'name' => $invoice->customer->full_name,
'street' => $invoice->billing_address['street'],
'zip' => $invoice->billing_address['postal_code'],
'city' => $invoice->billing_address['city'],
'country' => $invoice->billing_address['country'],
'vat_id' => $invoice->customer->vat_id,
]);
foreach ($invoice->items as $item) {
$document->addItem([
'name' => $item->description,
'quantity' => $item->quantity,
'price' => $item->unit_price,
'vat_rate' => $item->vat_rate,
]);
}
$pdfPath = tempnam(sys_get_temp_dir(), 'zugferd_') . '.pdf';
$document->render($pdfPath);
return $pdfPath;
}
public function getXml(Invoice $invoice): string
{
// Hier würde die XML-Generierung erfolgen (vereinfacht)
return '<invoice>' . $invoice->toXml() . '</invoice>';
}
}
D. VIES-API für USt-ID-Prüfung
// app/Services/ViesService.php
namespace App\Services;
use Illuminate\Support\Facades\Http;
class ViesService
{
public function validateVatId(string $countryCode, string $vatId): bool
{
$response = Http::get("http://ec.europa.eu/taxation_customs/vies/viesquer.do", [
'countryCode' => $countryCode,
'vatNumber' => $vatId,
]);
$body = $response->body();
return str_contains($body, 'VAT number is valid');
}
}
Verwendung im Model/Controller:
// Im Customer-Model oder Invoice-Controller
$isValid = (new ViesService())->validateVatId(
substr($invoice->customer->vat_id, 0, 2), // Ländercode (z. B. "DE")
substr($invoice->customer->vat_id, 2) // USt-ID ohne Ländercode
);
E. Automatische Backups mit spatie/laravel-backup
-
Paket installieren:
composer require spatie/laravel-backup php artisan vendor:publish --provider="Spatie\Backup\BackupServiceProvider" php artisan backup:install -
Konfiguration (
config/backup.php):'disks' => [ 'invoices', // Speichert Backups der Rechnungen ], 'monitor_backups' => [ [ 'name' => 'invoices', 'disks' => ['invoices'], 'health_checks' => [ \Spatie\Backup\Tasks\Monitor\HealthChecks\MaxAgeInDays::class => 30, \Spatie\Backup\Tasks\Monitor\HealthChecks\MaxStorageInMb::class => 5000, ], ], ], -
Backup-Job einrichten (
app/Console/Kernel.php):protected function schedule(Schedule $schedule) { $schedule->command('backup:run --only-db --disable-notifications')->dailyAt('3:00'); $schedule->command('backup:clean')->dailyAt('4:00'); }
F. GoBD-konforme Speicherung (Storage-Konfiguration)
// config/filesystems.php
'disks' => [
'invoices' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_INVOICES_BUCKET'),
'visibility' => 'private',
'options' => [
'Versioning' => 'Enabled', // Wichtig für GoBD!
],
],
],
G. Policies für Berechtigungen
// app/Policies/InvoicePolicy.php
namespace App\Policies;
use App\Models\Invoice;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class InvoicePolicy
{
use HandlesAuthorization;
public function create(User $user)
{
return $user->can('create invoices');
}
public function issue(User $user, Invoice $invoice)
{
return $user->can('issue invoices') && $invoice->status === 'draft';
}
public function cancel(User $user, Invoice $invoice)
{
return $user->can('cancel invoices') && $invoice->status !== 'cancelled';
}
public function delete(User $user, Invoice $invoice)
{
return false; // Rechnungen dürfen nie gelöscht werden (GoBD)!
}
}
Registrierung im AuthServiceProvider:
// app/Providers/AuthServiceProvider.php
protected $policies = [
Invoice::class => InvoicePolicy::class,
];
4. Frontend-Integration (Vue.js + Inertia)
A. Rechnungsformular mit GoBD-Hinweisen
<!-- resources/js/Pages/Invoices/Create.vue -->
<template>
<div>
<h1>{{ invoice.id ? 'Rechnung bearbeiten' : 'Neue Rechnung erstellen' }}</h1>
<form @submit.prevent="save">
<div class="form-group">
<label>Kunde</label>
<select v-model="form.customer_id" required>
<option v-for="customer in customers" :value="customer.id" :key="customer.id">
{{ customer.full_name }}
</option>
</select>
</div>
<div class="form-group">
<label>Rechnungsdatum</label>
<input type="date" v-model="form.date" :readonly="invoice.status !== 'draft'" />
<small v-if="invoice.status !== 'draft'">
Das Datum kann nach dem Stellen der Rechnung nicht mehr geändert werden (GoBD).
</small>
</div>
<div class="form-group">
<label>Rechnungsnummer</label>
<input type="text" v-model="form.number" readonly />
<small>
Die Rechnungsnummer wird automatisch beim Stellen der Rechnung vergeben (GoBD-konform).
</small>
</div>
<!-- Weitere Felder für Positionen, Steuern etc. -->
<div class="actions">
<button type="submit" v-if="invoice.status === 'draft'">Als Entwurf speichern</button>
<button type="button" @click="issue" v-if="invoice.status === 'draft'">
Rechnung stellen (GoBD)
</button>
</div>
</form>
</div>
</template>
<script setup>
import { useForm } from '@inertiajs/vue3';
const props = defineProps({
invoice: Object,
customers: Array,
});
const form = useForm({
customer_id: props.invoice?.customer_id || '',
date: props.invoice?.date || new Date().toISOString().split('T')[0],
number: props.invoice?.number || 'Entwurf (noch keine Nummer)',
// ... weitere Felder
});
const save = () => {
if (props.invoice?.id) {
form.put(`/api/invoices/${props.invoice.id}`);
} else {
form.post('/api/invoices');
}
};
const issue = () => {
if (confirm('Soll die Rechnung gestellt werden? Danach kann sie nicht mehr bearbeitet werden (GoBD).')) {
form.post(`/api/invoices/${props.invoice.id}/issue`, {}, {
onSuccess: () => alert('Rechnung wurde gestellt!'),
});
}
};
</script>
B. Rechnungsliste mit GoBD-Filtern
<!-- resources/js/Pages/Invoices/Index.vue -->
<template>
<div>
<h1>Rechnungen</h1>
<div class="filters">
<select v-model="statusFilter">
<option value="">Alle</option>
<option value="draft">Entwürfe</option>
<option value="issued">Gestellt</option>
<option value="sent">Versendet</option>
<option value="paid">Bezahlt</option>
<option value="cancelled">Storniert</option>
</select>
<input type="text" v-model="searchQuery" placeholder="Suche..." />
</div>
<table>
<thead>
<tr>
<th>Nummer</th>
<th>Datum</th>
<th>Kunde</th>
<th>Betrag</th>
<th>Status</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<tr v-for="invoice in filteredInvoices" :key="invoice.id">
<td>{{ invoice.number || 'Entwurf' }}</td>
<td>{{ invoice.date || '—' }}</td>
<td>{{ invoice.customer.full_name }}</td>
<td>{{ invoice.total }} €</td>
<td>
<span class="badge" :class="statusBadgeClass(invoice.status)">
{{ invoice.status }}
</span>
</td>
<td>
<button v-if="invoice.status === 'draft'" @click="edit(invoice.id)">Bearbeiten</button>
<button v-if="invoice.status === 'draft'" @click="issue(invoice.id)">
Rechnung stellen
</button>
<a v-if="invoice.status !== 'draft'" :href="`/invoices/${invoice.id}/pdf`" target="_blank">
PDF
</a>
<button v-if="invoice.status !== 'draft' && invoice.status !== 'cancelled'"
@click="cancel(invoice.id)">
Stornieren
</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import axios from 'axios';
const invoices = ref([]);
const statusFilter = ref('');
const searchQuery = ref('');
const fetchInvoices = async () => {
const response = await axios.get('/api/invoices');
invoices.value = response.data.data;
};
const filteredInvoices = computed(() => {
return invoices.value
.filter(invoice => !statusFilter.value || invoice.status === statusFilter.value)
.filter(invoice =>
invoice.number?.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
invoice.customer.full_name.toLowerCase().includes(searchQuery.value.toLowerCase())
);
});
const statusBadgeClass = (status) => ({
'badge-draft': status === 'draft',
'badge-issued': status === 'issued',
'badge-sent': status === 'sent',
'badge-paid': status === 'paid',
'badge-cancelled': status === 'cancelled',
});
const issue = async (id) => {
await axios.post(`/api/invoices/${id}/issue`);
await fetchInvoices();
};
const cancel = async (id) => {
const reason = prompt('Grund für die Stornierung (GoBD-Pflicht):');
if (reason) {
await axios.post(`/api/invoices/${id}/cancel`, { reason });
await fetchInvoices();
}
};
fetchInvoices();
</script>
<style>
.badge {
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.875rem;
}
.badge-draft { background: #6c757d; color: white; }
.badge-issued { background: #0d6efd; color: white; }
.badge-sent { background: #198754; color: white; }
.badge-paid { background: #198754; color: white; }
.badge-cancelled { background: #dc3545; color: white; }
</style>
5. GoBD-konforme Dokumentation
Erstelle eine Verfahrensdokumentation für dein Rechnungssystem (Pflicht nach GoBD!). Beispiel:
# Verfahrensdokumentation: Rechnungsverwaltung (GoBD-konform)
## 1. Systembeschreibung
- **Zweck**: Erstellung, Verwaltung und Archivierung von Rechnungen gemäß §14 UStG und GoBD.
- **Technologie**: Laravel (Backend), Vue.js (Frontend), SQLite/MySQL (Datenbank), S3 (Speicher).
## 2. Prozesse
### 2.1 Rechnungserstellung
1. Nutzer erstellt **Entwurf** (Status: `draft`).
2. System speichert Entwurf **ohne Rechnungsnummer**.
3. Bei "Rechnung stellen":
- System vergibt **fortlaufende Nummer** (z. B. `RE-2023-001`).
- Status wird auf `issued` gesetzt.
- **PDF/XML** wird generiert und **unveränderlich** gespeichert (S3 mit Versionierung).
- **Audit-Log** wird erstellt.
### 2.2 Rechnungsstornierung
1. Nutzer wählt "Stornieren" und gibt **Grund** an (GoBD-Pflicht!).
2. System:
- Setzt Status auf `cancelled`.
- Erstellt **Gutschrift** (Credit Note) mit negativem Betrag.
- Speichert Stornierungsgrund im **Audit-Log**.
### 2.3 Archivierung
- **Automatische Backups** täglich um 3:00 Uhr (`spatie/laravel-backup`).
- **Aufbewahrungsfrist**: 10 Jahre (S3 Lifecycle Policy).
- **Speicherformat**: PDF + XML (ZUGFeRD-konform).
## 3. Technische Maßnahmen
| Maßnahme | Umsetzung |
|------------------------------|---------------------------------------------------------------------------------------------|
| Unveränderbarkeit | Rechnungen nach Stellung als `readonly` markiert. Datenbank-Trigger verhindern Änderungen. |
| Nachvollziehbarkeit | `laravel-auditing` protokolliert alle Änderungen (wer, wann, was). |
| Vollständigkeit | Fortlaufende Nummernvergabe ohne Lücken (Datenbank-Transaktionen). |
| Aufbewahrung | Tägliche Backups auf S3 mit Versionierung (10 Jahre Aufbewahrung). |
| Zugriffsschutz | Laravel Policies schränken Berechtigungen ein (z. B. nur Buchhalter dürfen stornieren). |
| Prüfbarkeit | Audit-Logs und Revisionsprotokolle sind über Admin-Oberfläche einsehbar. |
## 4. Verantwortlichkeiten
| Rolle | Verantwortung |
|---------------------|-------------------------------------------------------------------------------------------------|
| Buchhaltung | Erstellung, Stellung und Stornierung von Rechnungen. |
| IT-Administration | Wartung der Systeme, Backups, Zugriffsrechte. |
| Geschäftsführung | Freigabe von Prozessen, regelmäßige Prüfung der GoBD-Konformität. |
## 5. Prüfprotokolle
- **Monatlich**: Automatischer Report aller gestellten/stornierten Rechnungen (via `laravel-excel`).
- **Jährlich**: Manuelle Prüfung der Archivierung durch Steuerberater.
6. Checkliste für GoBD-Konformität
| Anforderung | Umsetzung in deinem System | Erledigt? |
|---|---|---|
| Nachvollziehbarkeit | Audit-Logs mit laravel-auditing + Revision History. |
☐ |
| Unveränderlichkeit | Rechnungen nach Stellung als readonly + Datenbank-Constraints. |
☐ |
| Vollständigkeit | Fortlaufende Nummernvergabe ohne Lücken. | ☐ |
| Aufbewahrung (10 Jahre) | Automatische Backups auf S3 mit Versionierung. | ☐ |
| Maschinenlesbare Formate | ZUGFeRD/XML-Export für jede Rechnung. | ☐ |
| Zugriffsschutz | Laravel Policies für Berechtigungen. | ☐ |
| Prüfbarkeit | Audit-Logs und Revisionsprotokolle. | ☐ |
| Verfahrensdokumentation | Markdown-Dokumentation im Projekt (siehe oben). | ☐ |
| VIES-Prüfung | Automatische Validierung von USt-IDs über die VIES-API. | ☐ |
| Echtzeit-Protokollierung | Laravel Echo für Benachrichtigungen bei Änderungen. | ☐ |
7. Tools für die GoBD-Umsetzung
| Tool | Zweck | Link |
|---|---|---|
| laravel-auditing | Protokolliert alle Änderungen an Modellen (GoBD-Pflicht!). | https://github.com/owen-it/laravel-auditing |
| spatie/laravel-backup | Automatische Backups für 10 Jahre Aufbewahrung. | https://spatie.be/docs/laravel-backup |
| zugferd/php | Generiert GoBD-konforme ZUGFeRD-Rechnungen (PDF + XML). | https://github.com/zugferd/php |
| laravel-excel | Exportiert Rechnungslisten für Prüfprotokolle. | https://laravel-excel.com/ |
| laravel-nova | Admin-Oberfläche für Prüfung/Audit-Logs. | https://nova.laravel.com/ |
| AWS S3 + Versioning | Unveränderliche Speicherung von Rechnungs-PDFs. | https://aws.amazon.com/s3/ |
| Laravel Echo + Pusher | Echtzeit-Benachrichtigungen bei Rechnungsänderungen. | https://laravel.com/docs/broadcasting |
8. Zusammenfassung: Was du jetzt tun solltest
-
Datenbank-Struktur anpassen:
numberalsstring(nichtinteger!) speichern.- Status-Feld (
draft,issued, etc.) und Audit-Logs hinzufügen.
-
Rechnungsmodell erweitern:
generateNumber()mit Transaktionen.issue()-Methode für das "Stellen" von Rechnungen.- ZUGFeRD-PDF/XML-Generierung.
-
GoBD-konforme Speicherung einrichten:
- S3 mit Versionierung für Rechnungs-PDFs.
- Automatische Backups mit
spatie/laravel-backup.
-
Frontend anpassen:
- Rechnungsformular mit GoBD-Hinweisen.
- Status-Filter für die Liste.
-
Dokumentation erstellen:
- Verfahrensdokumentation (Markdown) im Projekt ablegen.
-
Tests durchführen:
- Prüfe, ob Rechnungen nicht bearbeitbar sind nach dem Stellen.
- Teste Nummernvergabe ohne Lücken.
- Überprüfe Backups und Wiederherstellung.
Frage an dich
Möchtest du:
- Ein komplettes Beispiel für die S3-Konfiguration (mit Versionierung)?
- Hilfe bei der Einrichtung von Laravel Auditing für detaillierte Protokolle?
- Ein Beispiel für die monatlichen Prüfberichte (mit Laravel Excel)?