GoBD-konforme Rechnungsverwaltung hinzugefügt
@@ -0,0 +1,701 @@
|
||||
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://ec.europa.eu/taxation_customs/vies/)). | **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**
|
||||
```php
|
||||
// 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**:
|
||||
```bash
|
||||
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**:
|
||||
```php
|
||||
// Alle Änderungen einer Rechnung abrufen
|
||||
$audits = $invoice->audits()
|
||||
->with('user') // Wer hat geändert?
|
||||
->latest()
|
||||
->get();
|
||||
```
|
||||
|
||||
---
|
||||
### **C. ZUGFeRD-PDF-Generierung**
|
||||
```php
|
||||
// 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**
|
||||
```php
|
||||
// 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**:
|
||||
```php
|
||||
// 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**:
|
||||
```bash
|
||||
composer require spatie/laravel-backup
|
||||
php artisan vendor:publish --provider="Spatie\Backup\BackupServiceProvider"
|
||||
php artisan backup:install
|
||||
```
|
||||
|
||||
2. **Konfiguration** (`config/backup.php`):
|
||||
```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`):
|
||||
```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)**
|
||||
```php
|
||||
// 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**
|
||||
```php
|
||||
// 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`**:
|
||||
```php
|
||||
// app/Providers/AuthServiceProvider.php
|
||||
protected $policies = [
|
||||
Invoice::class => InvoicePolicy::class,
|
||||
];
|
||||
```
|
||||
|
||||
---
|
||||
## **4. Frontend-Integration (Vue.js + Inertia)**
|
||||
### **A. Rechnungsformular mit GoBD-Hinweisen**
|
||||
```vue
|
||||
<!-- 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**
|
||||
```vue
|
||||
<!-- 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:
|
||||
|
||||
```markdown
|
||||
# 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](https://github.com/owen-it/laravel-auditing) |
|
||||
| **spatie/laravel-backup** | Automatische Backups für 10 Jahre Aufbewahrung. | [https://spatie.be/docs/laravel-backup](https://spatie.be/docs/laravel-backup) |
|
||||
| **zugferd/php** | Generiert GoBD-konforme ZUGFeRD-Rechnungen (PDF + XML). | [https://github.com/zugferd/php](https://github.com/zugferd/php) |
|
||||
| **laravel-excel** | Exportiert Rechnungslisten für Prüfprotokolle. | [https://laravel-excel.com/](https://laravel-excel.com/) |
|
||||
| **laravel-nova** | Admin-Oberfläche für Prüfung/Audit-Logs. | [https://nova.laravel.com/](https://nova.laravel.com/) |
|
||||
| **AWS S3 + Versioning** | Unveränderliche Speicherung von Rechnungs-PDFs. | [https://aws.amazon.com/s3/](https://aws.amazon.com/s3/) |
|
||||
| **Laravel Echo + Pusher** | Echtzeit-Benachrichtigungen bei Rechnungsänderungen. | [https://laravel.com/docs/broadcasting](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)?
|
||||
Reference in New Issue
Block a user