mivita/resources/views/user/abo/index.blade.php
Kevin ec3ecadc5d Abo (A3): Status und Grund eines Abos sichtbar machen
Neue Spalten user_abos.status_reason / paused_until und
user_abo_orders.error_message. Der Grund wird zentral am Model gepflegt
(saving-Event): Zahlungsfehler, Kuendigung/Deaktivierung mit Akteur aus dem
Formular-Kanal, Loeschen beim Zurueckkehren in den Lauf; die Pause setzt
paused_until und paused_by_*. Cron- und Retry-Zahlung schreiben den Fehlertext
des Anbieters an die Abo-Bestellung.

AboStatusExplainer uebersetzt das in einen Satz ("Pausiert bis ... vom Kunden
am ...", "Zahlung am ... fehlgeschlagen - Grund: ...", "Gekuendigt am ... vom
Berater"), Partial _status_explain in Abo-Detail (CRM, Portal, Admin),
Abo-Uebersicht, Kundenabo-Liste, Team-Kundenabos und Admin-Datatable; dort
neuer Filter "Zustand" (pausiert / Zahlung fehlgeschlagen). Backfill-Kommando
abo:backfill-status-reasons fuer Bestandsdaten, Fallback ueber das
Statusprotokoll und die Pausenfelder.

Sprachdateien de/en/es/fr, Tests in AboStatusExplainTest, Plan-Status.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 10:24:18 +00:00

291 lines
16 KiB
PHP

@extends('layouts.layout-2')
@section('content')
<h4 class="font-weight-bold py-2 mb-2 d-flex justify-content-between align-items-center w-100 ">
<div>
@if($view === 'ot') {{ __('navigation.customerabos') }} @endif
@if($view === 'me') {{ __('navigation.myabos') }} @endif
/ {{ __('navigation.overview') }}
</div>
</h4>
@if(isset($chartData))
@include('user.abo._abo_chart')
@endif
@if($view === 'ot' && isset($filterMonth))
<div class="card mb-4">
<div class="card-body py-3">
<form method="GET" action="{{ url()->current() }}" class="form-inline">
<label class="text-muted small mr-2 mb-0">{{ __('abo.filter_added_period') }}</label>
<select name="month" class="form-control form-control-sm mr-2 mb-1">
<option value="0">{{ __('tables.all') }}</option>
@foreach($chartMonths as $monthNum => $monthName)
<option value="{{ $monthNum }}" {{ (int) $filterMonth === (int) $monthNum ? 'selected' : '' }}>{{ $monthName }}</option>
@endforeach
</select>
<select name="year" class="form-control form-control-sm mr-2 mb-1">
@foreach($chartYears as $y)
<option value="{{ $y }}" {{ (int) $filterYear === (int) $y ? 'selected' : '' }}>{{ $y }}</option>
@endforeach
</select>
<button type="submit" class="btn btn-sm btn-secondary mb-1">{{ __('abo.filter') }}</button>
@if($filterMonth)
<a href="{{ url()->current() }}?year={{ $filterYear }}" class="btn btn-sm btn-outline-secondary ml-2 mb-1">{{ __('abo.reset') }}</a>
@endif
</form>
</div>
</div>
@endif
@if (Session::has('alert-success'))
<div class="alert alert-success">
{{ Session::get('alert-success') }}
</div>
@endif
@if (Session::has('alert-error'))
<div class="alert alert-danger">
{{ Session::get('alert-error') }}
</div>
@endif
<div class="row">
<div class="col-12">
@php
$abo_payment_links = $abo_payment_links ?? collect();
@endphp
@if($view === 'ot' && $abo_payment_links->isNotEmpty())
@foreach($abo_payment_links as $paymentLink)
<div class="card mb-4 border-warning">
<div class="card-body pb-2">
<div class="row">
<div class="col-sm-7">
<h5 class="mb-1">
<a href="{{ route('user_order_payment_links_detail', [$paymentLink->identifier]) }}">
{{ __('navigation.abo') }} <span class="text-muted">#{{ $paymentLink->identifier }}</span>
</a>
{!! \App\Services\OrderPaymentService::getTypeBadge($paymentLink) !!}
</h5>
</div>
<div class="col-sm-5 text-right">
<a href="{{ route('user_order_payment_links_detail', [$paymentLink->identifier]) }}" class="btn btn-sm btn-secondary">
<span class="fa fa-edit"></span>{{ __('abo.abo_details') }}
</a>
@if(\App\Services\OrderPaymentService::canDeleteInstance($paymentLink))
{!! Form::open(['action' => route('user_abos_payment_link_delete', [$paymentLink->identifier]), 'class' => 'd-inline-block ml-1']) !!}
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('{{ __('confirm_delete') }}');">
<span class="fa fa-trash"></span> {{ __('delete') }}
</button>
{!! Form::close() !!}
@endif
</div>
</div>
</div>
<hr class="m-0">
<div class="card-body pb-1">
@if(\App\Services\OrderPaymentService::isPaymentFailed($paymentLink))
<div class="alert alert-danger mb-3">
<strong>{{ __('abo.pending_payment_failed_title') }}</strong><br>
{{ __('abo.payment_link_failed_copy') }}
</div>
@elseif(\App\Services\OrderPaymentService::isPaymentProcessing($paymentLink))
<div class="alert alert-info mb-3">
<strong>{{ __('abo.pending_payment_processing_title') }}</strong><br>
{{ __('abo.payment_link_processing_copy') }}
</div>
@else
<div class="alert alert-warning mb-3">
<strong>{{ __('abo.payment_link_sent_title') }}</strong><br>
{{ __('abo.payment_link_sent_copy') }}
</div>
@endif
<div class="row">
<div class="col-md-3 mb-3">
<div class="text-muted small">{{ __('tables.date') }}</div>
<div class="font-weight-bold">{{ $paymentLink->created_at->format('d.m.Y') }}</div>
</div>
<div class="col-md-3 mb-3">
<div class="text-muted small">{{ __('tables.status') }}</div>
{!! \App\Services\OrderPaymentService::getStatusBadge($paymentLink) !!}
</div>
<div class="col-md-3 mb-3">
<div class="text-muted small">{{ __('tables.amount') }}</div>
{{ $paymentLink->getAmountFormatted() }} €
</div>
<div class="col-md-3 mb-3">
<div class="text-muted small">{{ __('tables.email') }}</div>
{{ $paymentLink->shopping_data['billing_email'] ?? '-' }}
</div>
</div>
</div>
</div>
@endforeach
@endif
@if((!$user_abos || $user_abos->isEmpty()) && $abo_payment_links->isEmpty())
<div class="card mb-4">
<div class="card-body pb-2">
<div class="row">
<div class="col-sm-12">
<h5>{{ __('abo.abo_delivery') }}</h5>
</div>
<hr class="m-0">
<div class="col-sm-12 mb-4">
<a href="{{ route('user_order_my_delivery', ['abo-me']) }}" class="btn btn-secondary rounded-pill">
<span class="ion ion-md-add"></span>&nbsp; {{ __('abo.add_new_abo') }} </a>
</div>
</div>
</div>
</div>
@endif
@php
// Kundenabos laufen ueber die sortierbare Liste; als Karte bleiben nur
// die Ausnahmefaelle mit offener Erstzahlung, weil dort Aktionsbuttons
// haengen (Kundenwunsch 2026-09-07).
$cardAbos = $view === 'ot'
? collect($user_abos)->filter(fn ($abo) => $abo->isPendingInitialPayment())
: collect($user_abos);
$listAbos = $view === 'ot'
? collect($user_abos)->reject(fn ($abo) => $abo->isPendingInitialPayment())->values()
: collect();
@endphp
@foreach($cardAbos as $user_abo)
<div class="card mb-4">
@if($user_abo->isPendingInitialPayment())
<div class="card-body pb-0">
@if($user_abo->hasFailedInitialPayment())
<div class="alert alert-danger mb-3">
<strong>{{ __('abo.pending_payment_failed_title') }}</strong><br>
{{ __('abo.pending_payment_failed_copy') }}
{!! Form::open(['action' => route('user_abos_delete_pending', [$view, $user_abo->id]), 'class' => 'd-inline-block ml-2']) !!}
<button type="submit" class="btn btn-sm btn-danger">{{ __('abo.pending_delete_button') }}</button>
{!! Form::close() !!}
</div>
@elseif($user_abo->hasInitialPaymentStatus())
<div class="alert alert-info mb-3">
<strong>{{ __('abo.pending_payment_processing_title') }}</strong><br>
{{ __('abo.pending_payment_processing_copy') }}
</div>
@else
<div class="alert alert-warning mb-3">
<strong>{{ __('abo.pending_payment_waiting_title') }}</strong><br>
{{ __('abo.pending_payment_waiting_copy') }}
</div>
@endif
</div>
@endif
<div class="card-body pb-2">
<div class="row">
<div class="col-sm-6">
<h5 class="mb-1">
<a href="{{ route('user_abos_detail', [$view, $user_abo->id]) }}">
{{ __('navigation.abo') }} <span class="text-muted">{{ '#'.$user_abo->payone_userid }}</span>
</a>
</h5>
</div>
<div class="col-sm-6 text-right">
<a href="{{ route('user_abos_detail', [$view, $user_abo->id]) }}" class="btn btn-sm btn-secondary float-right">
<span class="fa fa-edit"></span>{{ __('abo.abo_details') }}
</a>
</div>
</div>
</div>
<hr class="m-0">
<div class="card-body pb-1">
<div class="row">
<div class="col-md-3 mb-2">
<div class="text-muted small">{{ __('tables.start_date') }}</div>
<div class="font-weight-bold">{{ $user_abo->start_date }}</div>
</div>
<div class="col-md-3 mb-2">
<div class="text-muted small">{{ __('tables.next_date') }} </div>
<div class="font-weight-bold">{{ $user_abo->next_date }}</div>
</div>
<div class="col-md-3 mb-2">
<div class="text-muted small">{{ __('tables.abo_delivery_day') }}</div>
<div class="font-weight-bold">{{ \App\Services\HTMLHelper::getAboStrLang($user_abo->abo_interval) }}</div>
</div>
<div class="col-md-3 mb-2">
<div class="text-muted small">{{ __('tables.last_date') }}</div>
<div class="font-weight-bold">{{ $user_abo->last_date }}</div>
</div>
</div>
</div>
<hr class="m-0">
<div class="card-body pb-1">
<div class="row">
<div class="col-md-3 mb-3">
<div class="text-muted small">{{ __('tables.status') }} / {{ __('tables.active') }}</div>
{!! get_active_badge($user_abo->active) !!}
@include('admin.abo._status_explain', ['abo' => $user_abo])
</div>
<div class="col-md-3 mb-3">
<div class="text-muted small">{{ __('tables.abo_delivery') }}</div>
{{ $user_abo->getCountOrders() }}
</div>
<div class="col-md-3 mb-3">
@if($view === 'ot')
<div class="text-muted small">{{ __('navigation.points') }}</div>
<strong class="text-primary">{{ $user_abo->getFormattedTotalPoints() }} Pkt.</strong>
@else
<div class="text-muted small">{{ __('tables.amount') }}</div>
{{ $user_abo->getFormattedAmount() }} €
@endif
</div>
<div class="col-md-3 mb-3">
<div class="text-muted small">{{ __('tables.payment') }}</div>
{{ $user_abo->getPaymentType() }}
</div>
</div>
</div>
@if($view === 'ot')
<hr class="m-0">
<div class="card-body pb-1">
<div class="row">
<div class="col-md-3 mb-3">
<div class="text-muted small">{{ __('First name') }}</div>
@if($user_abo->shopping_user)
{{ $user_abo->shopping_user->billing_firstname }}
@endif
</div>
<div class="col-md-3 mb-3">
<div class="text-muted small">{{ __('Last name') }}</div>
@if($user_abo->shopping_user)
{{ $user_abo->shopping_user->billing_lastname }}
@endif
</div>
<div class="col-md-3 mb-3">
<div class="text-muted small">{{ __('E-Mail') }}</div>
@if($user_abo->shopping_user)
{{ $user_abo->shopping_user->billing_email }}
@endif
</div>
<div class="col-md-3 mb-3">
<div class="text-muted small">{{ __('tables.customer') }}</div>
@if($user_abo->shopping_user)
<a class="btn btn-sm btn-secondary" href="{{ route('user_customer_detail', $user_abo->shopping_user->id) }}"><i class="fa fa-edit"></i></a>
@endif
</div>
</div>
</div>
@endif
</div>
@endforeach
@if($view === 'ot' && $listAbos->isNotEmpty())
@include('user.abo._customer_abo_list', [
'listAbos' => $listAbos,
'customerAboForecast' => $customerAboForecast ?? null,
'customerAboBilled' => $customerAboBilled ?? null,
'customerAboBilledTotal' => $customerAboBilledTotal ?? null,
])
@endif
</div>
</div>
@endsection