Abo: Einmalprodukte bereits bei der Abo-Anlage + es/fr-Sprachsync
Einmalprodukte (Normalsortiment, show_on 3) können jetzt schon beim Abo-Abschluss hinzugefügt werden – im Portal-Wizard (Abschnitt im Upgrade-Schritt) und in allen drei Sales-Center-Abo-Strecken (eigene Produktliste). Variante A: Zeilen laufen als Cart-Option abo_addon mit durch den Checkout, werden sofort bezahlt und mit der Erstlieferung verschickt (Rechnung/Lieferschein gruppieren automatisch). - Checkout persistiert is_abo_addon (CheckoutRepository, PaymentHelper) - createAboItems/ensureUserAboItemsFromLatestOrder überspringen Addons (sonst würden Einmalprodukte dauerhaft wiederkehren); amount ohne Addon-Anteil; aboHasBaseProduct ignoriert Addon-Zeilen - CreateAboReplacementOrder propagiert is_abo_addon in Ersatzbestellungen - iq-shopping-cart.js: zweite Datatable + abo_addon-Flag-Weitergabe - i18n: neue abo.onetime_create_*-Keys (de/en/es/fr); zusätzlich es/fr komplett synchronisiert (abo-Kartenblock, navigation, payment, weborder inkl. Entfernung PAYONE-Altlasten, withdrawal.php neu) - Tests: AboCreateOneTimeItemsTest, AboCreateOneTimeCartTest, AboPortalCreateOneTimeTest (17 neue, 122 Tests gesamt grün) Plan: dev/2026-07-21-abo-einmalprodukte-anlage/ Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
1e7ae92d0c
commit
cdb121fde0
32 changed files with 1479 additions and 52 deletions
73
resources/views/user/order/_onetime_list.blade.php
Normal file
73
resources/views/user/order/_onetime_list.blade.php
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
{{-- Einmalprodukte bei der Abo-Anlage: eigene Produktliste (Normalsortiment), Zeilen landen
|
||||
mit Cart-Option abo_addon=true im selben Warenkorb (nur erste Lieferung, kein Abo-Bestandteil). --}}
|
||||
<div class="card mt-4">
|
||||
<div class="card-body pb-2">
|
||||
<h4 class="mb-1"><i class="fa fa-bolt text-warning"></i> {{ __('abo.onetime_create_hl') }}</h4>
|
||||
<p class="small text-muted mb-0">{{ __('abo.onetime_create_info') }}</p>
|
||||
</div>
|
||||
<div class="card-datatable table-responsive">
|
||||
<table class="table table-striped table-bordered" id="datatables-onetime-list" data-url="{{ route('user_order_my_perform_request') }}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ __('tables.image') }}</th>
|
||||
<th>{{ __('tables.product') }}</th>
|
||||
@if ($template === 'me')
|
||||
<th>{{ __('tables.my_price_net') }}</th>
|
||||
<th>{{ __('tables.my_price_gross') }}</th>
|
||||
@else
|
||||
<th>{{ __('tables.vk_price') }} {{ __('tables.net') }}</th>
|
||||
<th>{{ __('tables.vk_price') }} {{ __('tables.gross') }}</th>
|
||||
@endif
|
||||
<th>{{ __('tables.points') }}</th>
|
||||
<th><span class="no-line-break">{{ __('tables.contents') }} (ml)</span></th>
|
||||
<th>{{ __('tables.article_no') }}</th>
|
||||
<th>#</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#datatables-onetime-list').DataTable({
|
||||
"processing": true,
|
||||
"serverSide": true,
|
||||
ajax: {
|
||||
url: '{!! route('user_order_my_datatable') !!}',
|
||||
data: function (d) {
|
||||
d.shipping_is_for = $('input[name=shipping_is_for]').val();
|
||||
d.is_abo = $('input[name=is_abo]').val();
|
||||
d.abo_addon = 1;
|
||||
}
|
||||
},
|
||||
"order": [[6, "asc"]],
|
||||
"columns": [
|
||||
{ data: 'picture', name: 'picture', searchable: false, width: 35 },
|
||||
{ data: 'product', name: 'product' },
|
||||
@if ($template === 'me')
|
||||
{ data: 'price_net', name: 'price_net', searchable: false, orderable: false },
|
||||
{ data: 'price_gross', name: 'price_gross', searchable: false, orderable: false },
|
||||
@else
|
||||
{ data: 'customer_price_net', name: 'customer_price_net', searchable: false, orderable: false },
|
||||
{ data: 'customer_price_gross', name: 'customer_price_gross', searchable: false, orderable: false },
|
||||
@endif
|
||||
{ data: 'points', name: 'points', searchable: false },
|
||||
{ data: 'contents_total', name: 'contents_total', searchable: false },
|
||||
{ data: 'number', name: 'number' },
|
||||
{ data: 'action', name: 'action', searchable: false, orderable: false },
|
||||
],
|
||||
"bLengthChange": false,
|
||||
"iDisplayLength": 1000,
|
||||
"paging": false,
|
||||
"language": {
|
||||
"url": "/js/datatables-{{ \App::getLocale() }}.json"
|
||||
},
|
||||
drawCallback: function (settings) {
|
||||
IqShoppingCart.reInit();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
|
@ -23,6 +23,10 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
@if($is_abo)
|
||||
@include('user.order._onetime_list')
|
||||
@endif
|
||||
|
||||
{!! Form::open(['action' => route('user_order_my_payment', [$for, $delivery_id]), 'class' => 'form-horizontal']) !!}
|
||||
<input type="hidden" name="shipping_is_for" value="{{$for}}">
|
||||
<input type="hidden" name="is_abo" value="{{$is_abo}}">
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
@if($is_abo)
|
||||
@include('user.order._onetime_list')
|
||||
@endif
|
||||
|
||||
{!! Form::open(['action' => route('user_order_my_payment', [$for, $delivery_id]), 'class' => 'form-horizontal']) !!}
|
||||
<input type="hidden" name="shipping_is_for" value="{{$for}}">
|
||||
<input type="hidden" name="is_abo" value="{{$is_abo}}">
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
@if($is_abo)
|
||||
@include('user.order._onetime_list')
|
||||
@endif
|
||||
|
||||
{!! Form::open(['action' => route('user_order_my_payment', [$for, $delivery_id]), 'class' => 'form-horizontal']) !!}
|
||||
<input type="hidden" name="shipping_is_for" value="{{$for}}">
|
||||
<input type="hidden" name="is_abo" value="{{$is_abo}}">
|
||||
|
|
|
|||
|
|
@ -165,7 +165,9 @@
|
|||
<div class="col-12 col-sm-6 col-md-7 description">
|
||||
<div class="media-body">
|
||||
<div class="d-block text-body" style="font-size: 15px; font-weight: 500;">
|
||||
{{ $row->name }} @if (isset($is_abo) && $is_abo)
|
||||
{{ $row->name }} @if ($row->options->abo_addon)
|
||||
<span class="badge badge-warning">{{ __('abo.onetime_badge_first_delivery') }}</span>
|
||||
@elseif (isset($is_abo) && $is_abo)
|
||||
{!! get_abo_type_badge_by_product($product) !!}
|
||||
@endif
|
||||
</div>
|
||||
|
|
@ -184,7 +186,8 @@
|
|||
@if (!$row->options->comp)
|
||||
<a href="#"
|
||||
class="auto-delete-product remove_item_form_cart product-tooltip"
|
||||
data-row-id="{{ $row->rowId }}" data-product-id="{{ $product->id }}"><i
|
||||
data-row-id="{{ $row->rowId }}" data-product-id="{{ $product->id }}"
|
||||
data-abo-addon="{{ $row->options->abo_addon ? 1 : 0 }}"><i
|
||||
class="fa fa-times"></i> {{ __('order.article_remove') }}</a>
|
||||
@else
|
||||
@if (Yard::instance('shopping')->getNumComp() > 1)
|
||||
|
|
@ -218,18 +221,21 @@
|
|||
<button type="button"
|
||||
class="btn btn-secondary icon-btn md-btn-extra cart-remove-event"
|
||||
data-row-id="{{ $row->rowId }}"
|
||||
data-product-id="{{ $product->id }}">-</button>
|
||||
data-product-id="{{ $product->id }}"
|
||||
data-abo-addon="{{ $row->options->abo_addon ? 1 : 0 }}">-</button>
|
||||
</span>
|
||||
<input type="text"
|
||||
class="form-control text-center input-extra cart-input-event-onchange"
|
||||
data-row-id="{{ $row->rowId }}"
|
||||
data-product-id="{{ $product->id }}" value="{{ $row->qty }}"
|
||||
data-abo-addon="{{ $row->options->abo_addon ? 1 : 0 }}"
|
||||
name="quantity[{{ $row->rowId }}]">
|
||||
<span class="input-group-append">
|
||||
<button type="button"
|
||||
class="btn btn-secondary icon-btn md-btn-extra cart-add-event"
|
||||
data-row-id="{{ $row->rowId }}"
|
||||
data-product-id="{{ $product->id }}">+</button>
|
||||
data-product-id="{{ $product->id }}"
|
||||
data-abo-addon="{{ $row->options->abo_addon ? 1 : 0 }}">+</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue