Clone
1
GoBD-konforme Rechnungsverwaltung
Daniel Stock edited this page 2025-12-04 14:24:55 +01:00

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

  1. Paket installieren:

    composer require owen-it/laravel-auditing
    php artisan vendor:publish --provider="OwenIt\Auditing\AuditingServiceProvider"
    php artisan migrate
    
  2. Model anpassen (bereits im Beispiel oben enthalten).

  3. 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

  1. Paket installieren:

    composer require spatie/laravel-backup
    php artisan vendor:publish --provider="Spatie\Backup\BackupServiceProvider"
    php artisan backup:install
    
  2. 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,
            ],
        ],
    ],
    
  3. 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

  1. Datenbank-Struktur anpassen:

    • number als string (nicht integer!) speichern.
    • Status-Feld (draft, issued, etc.) und Audit-Logs hinzufügen.
  2. Rechnungsmodell erweitern:

    • generateNumber() mit Transaktionen.
    • issue()-Methode für das "Stellen" von Rechnungen.
    • ZUGFeRD-PDF/XML-Generierung.
  3. GoBD-konforme Speicherung einrichten:

    • S3 mit Versionierung für Rechnungs-PDFs.
    • Automatische Backups mit spatie/laravel-backup.
  4. Frontend anpassen:

    • Rechnungsformular mit GoBD-Hinweisen.
    • Status-Filter für die Liste.
  5. Dokumentation erstellen:

    • Verfahrensdokumentation (Markdown) im Projekt ablegen.
  6. 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:

  1. Ein komplettes Beispiel für die S3-Konfiguration (mit Versionierung)?
  2. Hilfe bei der Einrichtung von Laravel Auditing für detaillierte Protokolle?
  3. Ein Beispiel für die monatlichen Prüfberichte (mit Laravel Excel)?