Rechnungsseite: neue STR/MAN-Rechnungen gelistet, STR bei Stripe verlinkt
Die Kundenseite /admin/me/invoices zeigte bisher nur das Legacy-Archiv. Neue Sektion "Aktuelle Rechnungen" mit Netto/USt/Gesamt und Status; STR-Rechnungen oeffnen die gehostete Stripe-Rechnungsansicht ueber einen Redirect-Endpunkt (URL wird je Aufruf frisch geholt, Zugriff nur fuer den Eigentuemer). KPIs zaehlen neu + Archiv zusammen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
2a8fd0179e
commit
5166fc7b40
4 changed files with 226 additions and 25 deletions
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\InvoiceStatus;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\LegacyInvoice;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Attributes\Title;
|
||||
|
|
@ -31,6 +33,9 @@ new #[Layout('components.layouts.app'), Title('Rechnungen')] class extends Compo
|
|||
$baseQuery = LegacyInvoice::query()
|
||||
->where('user_id', auth()->id());
|
||||
|
||||
// Neue Rechnungen (STR = Stripe-Spiegelung, MAN = Bestandskreis).
|
||||
$currentQuery = Invoice::query()->where('user_id', auth()->id());
|
||||
|
||||
$invoices = (clone $baseQuery)
|
||||
->when(filled($this->search), function ($query): void {
|
||||
$query->where('number', 'like', '%'.$this->search.'%');
|
||||
|
|
@ -40,6 +45,11 @@ new #[Layout('components.layouts.app'), Title('Rechnungen')] class extends Compo
|
|||
->paginate(100);
|
||||
|
||||
return [
|
||||
'currentInvoices' => (clone $currentQuery)
|
||||
->latest('invoice_date')
|
||||
->latest('id')
|
||||
->limit(100)
|
||||
->get(),
|
||||
'invoices' => $invoices,
|
||||
'statusOptions' => (clone $baseQuery)
|
||||
->whereNotNull('status')
|
||||
|
|
@ -49,10 +59,12 @@ new #[Layout('components.layouts.app'), Title('Rechnungen')] class extends Compo
|
|||
->filter()
|
||||
->values(),
|
||||
'stats' => [
|
||||
'count' => (clone $baseQuery)->count(),
|
||||
'total_cents' => (int) (clone $baseQuery)->sum('total_cents'),
|
||||
'paid_count' => (clone $baseQuery)->whereNotNull('paid_at')->count(),
|
||||
'downloadable_count' => (clone $baseQuery)->count(),
|
||||
'count' => (clone $baseQuery)->count() + (clone $currentQuery)->count(),
|
||||
'total_cents' => (int) (clone $baseQuery)->sum('total_cents') + (int) (clone $currentQuery)->sum('total_cents'),
|
||||
'paid_count' => (clone $baseQuery)->whereNotNull('paid_at')->count()
|
||||
+ (clone $currentQuery)->where('status', InvoiceStatus::Paid->value)->count(),
|
||||
'downloadable_count' => (clone $baseQuery)->count()
|
||||
+ (clone $currentQuery)->whereNotNull('stripe_invoice_id')->count(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
|
@ -65,13 +77,12 @@ new #[Layout('components.layouts.app'), Title('Rechnungen')] class extends Compo
|
|||
<div class="flex items-center gap-3 mb-3 flex-wrap">
|
||||
<span class="badge hub dot">{{ __('User Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Mein Bereich · Finanzen') }}</span>
|
||||
<span class="badge hub">{{ __('Archivdaten') }}</span>
|
||||
</div>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Rechnungen') }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Ihr Rechnungsarchiv im User Backend. PDFs werden bei Bedarf aus den Archivdaten erzeugt.') }}
|
||||
{{ __('Aktuelle Rechnungen Ihrer Buchungen sowie das Archiv aus dem Altsystem.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -90,42 +101,117 @@ new #[Layout('components.layouts.app'), Title('Rechnungen')] class extends Compo
|
|||
</div>
|
||||
@endif
|
||||
|
||||
{{-- ============== HINWEIS-PANEL ============== --}}
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Hinweis zu Rechnungen') }}</span>
|
||||
@if (session('invoice-error'))
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px] flex items-center gap-2
|
||||
bg-[color:var(--color-warn-soft)] border-[color:var(--color-warn)]/30 text-[color:var(--color-ink-2)]">
|
||||
<flux:icon.exclamation-triangle class="size-[16px] flex-shrink-0" />
|
||||
{{ session('invoice-error') }}
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<p class="text-[13px] text-[color:var(--color-ink-2)] m-0">
|
||||
{{ __('Aktuell sehen Sie hier die aus dem Legacy-System übernommenen Rechnungen. Neue Abrechnungen werden später in dieselbe Finanznavigation integriert.') }}
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
@endif
|
||||
|
||||
{{-- ============== KPI-Reihe ============== --}}
|
||||
<section class="grid grid-cols-2 gap-4 lg:grid-cols-4">
|
||||
<x-portal.stat-card variant="primary" :label="__('Rechnungen')" :value="number_format($stats['count'])">
|
||||
<x-slot:meta>{{ __('Archivdatensätze') }}</x-slot:meta>
|
||||
<x-slot:meta>{{ __('neu + Archiv') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('User-spezifisch') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="muted" :label="__('Archivsumme')" :value="number_format($stats['total_cents'] / 100, 2, ',', '.').' €'">
|
||||
<x-slot:meta>{{ __('historisch') }}</x-slot:meta>
|
||||
<x-portal.stat-card variant="muted" :label="__('Gesamtsumme')" :value="number_format($stats['total_cents'] / 100, 2, ',', '.').' €'">
|
||||
<x-slot:meta>{{ __('alle Rechnungen') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('alle Portale') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="ok" :label="__('Bezahlt')" :value="number_format($stats['paid_count'])">
|
||||
<x-slot:meta>{{ __('vollständig') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('mit Zahlungsdatum') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="warn" :label="__('PDF-Download')" :value="number_format($stats['downloadable_count'])">
|
||||
<x-slot:meta>{{ __('verfügbar') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('aus Archivdaten') }}</x-slot:trend>
|
||||
<x-portal.stat-card variant="warn" :label="__('Beleg verfügbar')" :value="number_format($stats['downloadable_count'])">
|
||||
<x-slot:meta>{{ __('PDF / Stripe') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('direkt abrufbar') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
</section>
|
||||
|
||||
{{-- ============== AKTUELLE RECHNUNGEN (STR/MAN) ============== --}}
|
||||
<article class="panel overflow-hidden">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Aktuelle Rechnungen') }}</span>
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ __(':count Einträge', ['count' => $currentInvoices->count()]) }}
|
||||
</span>
|
||||
</div>
|
||||
<flux:table>
|
||||
<flux:table.columns>
|
||||
<flux:table.column>{{ __('Rechnungsnr.') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Netto') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('USt.') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Gesamt') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Status') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Rechnungsdatum') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Beleg') }}</flux:table.column>
|
||||
</flux:table.columns>
|
||||
|
||||
@forelse ($currentInvoices as $invoice)
|
||||
<flux:table.row wire:key="invoice-{{ $invoice->id }}">
|
||||
<flux:table.cell>
|
||||
<span class="text-[13px] font-semibold text-[color:var(--color-ink)]">{{ $invoice->number }}</span>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<span class="text-[12.5px] text-[color:var(--color-ink-2)] tabular-nums">
|
||||
{{ number_format($invoice->amount_cents / 100, 2, ',', '.') }} €
|
||||
</span>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<span class="text-[12.5px] text-[color:var(--color-ink-2)] tabular-nums">
|
||||
{{ number_format($invoice->tax_cents / 100, 2, ',', '.') }} €
|
||||
</span>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<span class="text-[13px] font-semibold text-[color:var(--color-ink)] tabular-nums">
|
||||
{{ number_format($invoice->total_cents / 100, 2, ',', '.') }} €
|
||||
</span>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
@if ($invoice->status === \App\Enums\InvoiceStatus::Paid)
|
||||
<span class="badge ok dot">{{ $invoice->status->label() }}</span>
|
||||
@else
|
||||
<span class="badge warn dot">{{ $invoice->status->label() }}</span>
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<span class="text-[12px] text-[color:var(--color-ink-3)]">
|
||||
{{ $invoice->invoice_date?->format('d.m.Y') ?? '–' }}
|
||||
</span>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
@if ($invoice->stripe_invoice_id)
|
||||
<flux:button
|
||||
size="sm"
|
||||
variant="filled"
|
||||
icon="arrow-top-right-on-square"
|
||||
:href="route('me.invoices.stripe', $invoice)"
|
||||
target="_blank"
|
||||
>
|
||||
{{ __('Bei Stripe öffnen') }}
|
||||
</flux:button>
|
||||
@else
|
||||
<span class="text-[12px] text-[color:var(--color-ink-3)]">–</span>
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@empty
|
||||
<flux:table.row>
|
||||
<flux:table.cell colspan="7">
|
||||
<div class="px-4 py-6 text-center text-[12.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ __('Noch keine Rechnungen aus neuen Buchungen — sie erscheinen hier automatisch nach der ersten Abrechnung.') }}
|
||||
</div>
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@endforelse
|
||||
</flux:table>
|
||||
</article>
|
||||
|
||||
{{-- ============== FILTER-PANEL ============== --}}
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Filter & Suche') }}</span>
|
||||
<span class="section-eyebrow">{{ __('Filter & Suche (Archiv)') }}</span>
|
||||
</div>
|
||||
<div class="p-5 flex flex-col gap-3 sm:flex-row">
|
||||
<flux:input wire:model.live.debounce.300ms="search" placeholder="{{ __('Rechnungsnummer suchen…') }}" icon="magnifying-glass" class="flex-1" />
|
||||
|
|
@ -141,7 +227,7 @@ new #[Layout('components.layouts.app'), Title('Rechnungen')] class extends Compo
|
|||
{{-- ============== TABELLE ============== --}}
|
||||
<article class="panel overflow-hidden">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Alle Rechnungen') }}</span>
|
||||
<span class="section-eyebrow">{{ __('Rechnungsarchiv (Altsystem)') }}</span>
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ __(':count Einträge', ['count' => $invoices->count()]) }}
|
||||
</span>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue