mivita/resources/views/portal/abo/_create_check.blade.php
Kevin ac3bd6e8ea Statistiken/Abo: Kundenfeedback 2026-07-24 komplett umgesetzt + Steuern: DATEV-Fixes 08+09/2025 (Sammel-Commit Branch-WIP)
Feedback-Umsetzung (dev/2026-07-22/feedback-analyse-2026-07-24.md, alle 10 Punkte):
- VIP-Statistik: eigener Berater ('ich', Linie 0) in Neue-Kundenabos- und beiden Top-10-Listen; Top-10-Umsatz sortiert nach abgerechneten Ist-Punkten + Abo-Anzahl-Spalte; Fußnote nicht-payline-wirksame Punkte (SalesVolumePoints::nonPaylinePoints)
- Führungskräfte-Liste: beide Sichten 'direkt' (an nächster FK abgeschnitten, additiv) und 'gesamt' (komplette Downline), Gesamt-Spalte nach Kundenzählweise
- Team-Abos: je Ebene 'abgerechnet (Monat)' und 'bis zum 25. erwartet'; Payline-Karte mit Teamabo-/Kundenabo-Zählern
- Incentive-Live-Ranking: Qualifizierte zuerst nummeriert (fixt auch isWinner rank<=max_winners)
- Porto: Netto-/Brutto-Kennzeichnung (Berater netto, Endkunden inkl. MwSt., portal_view-Flag; auch CRM-/Shop-Warenkorb + Checkout), Yard::shipping_tax-Fix
- Struktur-Ansicht: Abo-Badges 'A' (eigenes Abo) und '(n)' (Kundenabos) je Teampartner, gebatcht
- Abo-Löschung: Softdelete setzt active=0 (UserAbo deleting-Hook) + Bereinigungs-Migration für gelöschte-aber-aktive Abos
- Doku: technische Analyse + Kundinnen-Feedback-Dokument unter dev/2026-07-22/
- Tests: IncentiveRankingTest, TeamStructureAboBadgesTest, YardShippingTaxTest (neu); BackofficeStatisticsListsTest, TeamAboOverviewTest, PaylineForecastServiceTest, AboAdminDeleteTest, AboOneTimeViewTest erweitert

Außerdem enthalten (Branch-WIP vor dem Feedback): DATEV-Abgleich-Fixes 08+09/2025, USt-ID-Live-Validierung sowie restliche unkommittete Stände (u. a. Abo-Pause-Sprachkeys).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 13:56:02 +00:00

292 lines
16 KiB
PHP

{!! Form::open(['action' => route('portal.my_subscriptions.create', 5), 'method' => 'POST', 'class' => '']) !!}
<div class="card-body">
@if (Yard::instance('subscription')->content()->count())
<style>
.cart-item {
position: relative;
padding: 15px 0;
border-bottom: 1px solid #ddd;
}
.cart-item:last-child {
border-bottom: none;
}
.cart-item .product-image {
max-width: 80px;
}
.cart-item .product-title {
font-size: 1.1em;
font-weight: 500;
color: #393939;
}
.cart-item .product-details {
font-size: 0.9em;
color: #666;
}
.cart-item .quantity-input {
width: 80px;
text-align: center;
}
.cart-summary {
background: #f8f9fa;
padding: 20px;
border-radius: 5px;
}
.cart-summary .table {
margin-bottom: 0;
}
.cart-summary .table td {
border-top: none;
padding: 8px 0;
}
.cart-summary .total-row {
font-weight: bold;
border-top: 1px solid #ddd;
}
</style>
<div class="cart-overview">
<h4 class="mb-4">{{ __('abo.abo_order_hl') }}</h4>
@if (isset($error))
<div class="alert alert-danger">
<strong>{{ $error }}</strong>
</div>
@endif
@foreach (Yard::instance('subscription')->getContentByOrder() as $row)
@php($product = \App\Models\Product::find($row->id))
<div class="cart-item">
<div class="row align-items-center">
<div class="col-3 col-sm-2">
@if ($row->options->has('image'))
<img src="{{ route('product_image', [$row->options->image]) }}" class="product-image"
alt="{{ $row->name }}">
@endif
</div>
<div class="col-9 col-sm-10">
<div class="row">
<div class="col-12 col-sm-6 col-md-7">
<div class="product-title">
{{ $row->name }}
@if ($row->options->abo_addon)
<span class="badge badge-pill badge-warning"><i class="fa fa-bolt"></i> {{ __('abo.onetime_badge_first_delivery') }}</span>
@else
{!! get_abo_type_badge_by_product($product) !!}
@endif
</div>
<div class="product-details">
<div>{{ __('order.content') }}: {{ $product->contents }}</div>
<div>{{ __('order.art_no') }}: {{ $product->number }}</div>
</div>
</div>
<div class="col-6 col-sm-3 col-md-2">
<div class="price-single">
<div style="">{{ $row->price() }} €
{{ Yard::instance('subscription')->getUserTaxFree() }}</div>
@if (Yard::instance('subscription')->isPriceCurrency())
<span
class="small">~{{ Yard::instance('subscription')->getCurrencyByKey('price', $row, 2) }}
{{ Yard::instance('subscription')->getPriceCurrencyUnit() }} </span>
@endif
</div>
</div>
<div class="col-6 col-sm-3 col-md-3">
<div class="quantity-select text-right">
@if ($row->options->comp)
<span class="text-right">1 x</span>
@else
<span class="text-right">{{ $row->qty }} x</span>
@endif
</div>
<div class="price-total text-right mt-2">
<span class="font-bold text-price-total"><strong>{{ $row->subtotal() }} &euro;
</strong></span>
@if (Yard::instance('subscription')->isPriceCurrency())
<br>
<span
class="small">~{{ Yard::instance('subscription')->getCurrencyByKey('subtotal', $row, 2) }}
{{ Yard::instance('subscription')->getPriceCurrencyUnit() }} </span>
@endif
</div>
</div>
</div>
</div>
</div>
</div>
@endforeach
@php($hasOneTimeRows = Yard::instance('subscription')->getContentByOrder()->contains(function ($item) { return (bool) ($item->options->abo_addon ?? false); }))
@if ($hasOneTimeRows)
<div class="alert alert-info mt-3">
<i class="fa fa-bolt text-warning"></i> {{ __('abo.onetime_create_info') }}
</div>
@endif
<div class="cart-summary mt-4">
<table class="table">
<tbody>
<tr>
<td>{{ __('order.subtotal') }}:</td>
<td class="text-right">
{{ Yard::instance('subscription')->total() }} €
@if (Yard::instance('subscription')->isPriceCurrency())
<span
class="small">~{{ Yard::instance('subscription')->getCurrencyByKey('subtotal') }}
{{ Yard::instance('subscription')->getPriceCurrencyUnit() }}</span>
@endif
</td>
</tr>
<tr>
<td>{{ __('Delivery country') }}:</td>
<td class="text-right">
{{ Yard::instance('subscription')->getShippingCountryName() }}
</td>
</tr>
@php($checkTaxFree = Yard::instance('subscription')->getUserTaxFree())
<tr>
<td>{{ __('order.shipping_costs') }}
({{ $checkTaxFree ? __('order.net') : __('order.incl_VAT') }}):</td>
<td class="text-right">
{{ $checkTaxFree ? Yard::instance('subscription')->shippingNet() : Yard::instance('subscription')->shipping() }}
€
</td>
</tr>
@if (Yard::instance('subscription')->getUserTaxFree())
<tr>
<td class="">{{ __('order.sum_net') }}:</td>
<td class="text-right">{{ Yard::instance('subscription')->subtotalWithShipping() }} €
</td>
</tr>
@else
<tr>
<td class="">{{ __('order.total_without_VAT') }}:</td>
<td class="text-right">{{ Yard::instance('subscription')->subtotalWithShipping() }} €
</td>
</tr>
<tr>
<td class="">{{ __('order.plus_VAT') }}:</td>
<td class="text-right">{{ Yard::instance('subscription')->taxWithShipping() }} €</td>
</tr>
@endif
<tr class="total-row">
<td>
@if (Yard::instance('subscription')->getUserTaxFree())
{{ __('order.total_net') }}:
@else
{{ __('order.total_gross') }}:
@endif
</td>
<td class="text-right">
{{ Yard::instance('subscription')->totalWithShipping() }} €
@if (Yard::instance('subscription')->isPriceCurrency())
<span
class="small">~{{ Yard::instance('subscription')->getCurrencyByKey('totalWithShipping') }}
{{ Yard::instance('subscription')->getPriceCurrencyUnit() }}</span>
@endif
</td>
</tr>
</tbody>
</table>
</div>
<div class="abo-settings mt-4 text-right">
<div class="alert alert-info text-left">
<h4>{{ __('abo.abo_settings') }}</h4>
<div class="form-row">
<div class="col-6 col-sm-8 col-md-9 col-lg-9 mb-1"></div>
<div class="col-12 col-sm-4 col-md-3 col-lg-3 mb-1 text-right">
<label class="form-label">{{ __('abo.delivery_day') }}*</label>
<select class="custom-select" name="abo_interval" id="abo_interval_select">
{!! HTMLHelper::getAboDeliveryOptions() !!}
</select>
<div id="abo_interval_info" class="alert alert-info mt-2 small d-none"></div>
<div id="abo_interval_warning" class="alert alert-warning mt-2 small d-none"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
var select = document.getElementById('abo_interval_select');
var info = document.getElementById('abo_interval_info');
var warning = document.getElementById('abo_interval_warning');
if (!select || !info || !warning) return;
var infoTpl = @json(__('abo.info_next_execution_select', ['placeholder_days' => '__DAYS__', 'placeholder_date' => '__DATE__']));
var warnTpl = @json(__('abo.warning_next_date_soon_select', ['placeholder_days' => '__DAYS__', 'placeholder_date' => '__DATE__']));
function checkDays() {
var option = select.options[select.selectedIndex];
var days = parseInt(option.getAttribute('data-days'), 10);
var date = option.getAttribute('data-date') || '';
if (isNaN(days)) {
return;
}
var showWarning = days < 20;
if (showWarning) {
info.innerHTML = '';
info.classList.add('d-none');
warning.innerHTML =
'<i class="fa fa-exclamation-triangle"></i> ' +
warnTpl.replace('__DAYS__', days).replace('__DATE__', date);
warning.classList.remove('d-none');
} else {
warning.classList.add('d-none');
info.innerHTML =
'<i class="fa fa-info-circle"></i> ' +
infoTpl.replace('__DAYS__', days).replace('__DATE__', date);
info.classList.remove('d-none');
}
}
select.addEventListener('change', checkDays);
checkDays();
});
</script>
</div>
<div class="col-12 col-sm-12 col-md-4 col-lg-6 mb-1"></div>
<div class="col-12 col-sm-12 col-md-8 col-lg-6 mb-1">
<div class="text-right">
<em class="small"><i>{!! __('abo.abo_order_info_check') !!}</i></em>
<hr class="my-2" style="border-color: #b4b4b4;">
<em class="font-weight-bold"><i>{!! __('abo.abo_order_info_check_2') !!}</i></em>
<hr class="my-2" style="border-color: #b4b4b4;">
<em class="small"><i>{!! __('abo.abo_order_info_check_3', [
'abo-min-duration' => \App\Models\Setting::getContentBySlug('abo-min-duration'),
]) !!}</i></em>
<hr class="my-2" style="border-color: #b4b4b4;">
<label class="switcher switcher-success d-inline-flex align-items-center">
<input type="checkbox" class="switcher-input" name="abo_order_info_checkbox"
value="true" required>
<span class="switcher-indicator">
<span class="switcher-yes"><span class="ion ion-md-checkmark"></span></span>
<span class="switcher-no"><span class="ion ion-md-close"></span></span>
</span>
<span class="switcher-label"><strong>{{ __('abo.abo_order_info_checkbox') }}</strong></span>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
@endif
</div>
<div class="card-footer">
<button type="submit" class="btn btn-default" name="action" value="back">{{ __('abo.back') }}</button>
<div class="float-right">
<button type="submit" class="btn btn-secondary" name="action" value="checkout"><i
class="ion ion-ios-redo"></i> {{ __('order.confirm_and_send_order') }}</button>
</div>
<br><br>
<div class="text-right">
<em class="small"><i class="fa fa-info-circle"></i> {!! __('order.confirm_send_order_info') !!}</em>
</div>
</div>
{!! Form::close() !!}