Neuer Nav-Punkt „Bestellungen" (Abwicklung + API-Bestellungen als Unterpunkte). Board für Versand-Mitarbeiter (copyreader): 4 Filter-Kacheln (heute eingegangen / zu konfektionieren / versandfertig / heute versendet), Packliste je Bestellung (Sets mit Komponenten), Dokumente (Rechnung/Lieferschein/Kombi-Download über eigene copyreader-Route, Erstellen ohne Mailversand; API-Bestellungen verlinken die externe Rechnung), Aktionen Konfektioniert/Versendet (nur vorwärts, Storno bleibt Verkäufe/admin). Versandfertig-Regel per Kundenentscheid: Rechnung sofort, Vorkasse nach Zahlungseingang, API immer (ShoppingOrder::isReadyToShip()). Statuswechsel-Logik in OrderStatusService extrahiert (Verkäufe-Modal + Board teilen Transaktion/Lock/Margin-Folgen/AP-13-Abzug). Kundenentscheid Punkt 3: Board-Statuswechsel setzt shipped_status_locked — die Bestell-Detailseiten ändern den Status dann nicht mehr automatisch (offen → in Bearbeitung). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
237 lines
16 KiB
PHP
237 lines
16 KiB
PHP
@extends('layouts.layout-2')
|
||
|
||
@section('content')
|
||
@include('admin.inventory.partials.wawi-ui')
|
||
|
||
<div class="wawi-page">
|
||
<div class="wawi-page-head">
|
||
<div>
|
||
<h1 class="wawi-page-head__title">{{ __('Bestell-Abwicklung') }}</h1>
|
||
<p class="wawi-page-head__subtitle">{{ __('Tagesübersicht: konfektionieren, Dokumente drucken, versenden — versandfertig = bezahlt (Rechnung sofort, Vorkasse nach Zahlungseingang, API immer)') }}</p>
|
||
</div>
|
||
<div class="wawi-page-head__actions">
|
||
<a href="{{ route('admin.inventory.api-orders.index') }}" class="btn btn-outline-secondary btn-sm">
|
||
<span class="fas fa-cloud-download-alt mr-1"></span>{{ __('API-Bestellungen') }}
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="wawi-stats">
|
||
<a class="wawi-stat is-clickable text-reset" href="{{ route('admin.inventory.orders.index', ['filter' => 'today']) }}">
|
||
<div class="wawi-stat__icon"><span class="fas fa-inbox"></span></div>
|
||
<div class="wawi-stat__body">
|
||
<div class="wawi-stat__value">{{ $counts['today'] }}</div>
|
||
<div class="wawi-stat__label">{{ __('Heute eingegangen') }}</div>
|
||
</div>
|
||
</a>
|
||
<a class="wawi-stat wawi-stat--warning is-clickable text-reset" href="{{ route('admin.inventory.orders.index', ['filter' => 'pick']) }}">
|
||
<div class="wawi-stat__icon"><span class="fas fa-box-open"></span></div>
|
||
<div class="wawi-stat__body">
|
||
<div class="wawi-stat__value">{{ $counts['pick'] }}</div>
|
||
<div class="wawi-stat__label">{{ __('Zu konfektionieren') }}</div>
|
||
</div>
|
||
</a>
|
||
<a class="wawi-stat wawi-stat--ok is-clickable text-reset" href="{{ route('admin.inventory.orders.index', ['filter' => 'ready']) }}">
|
||
<div class="wawi-stat__icon"><span class="fas fa-truck-loading"></span></div>
|
||
<div class="wawi-stat__body">
|
||
<div class="wawi-stat__value">{{ $counts['ready'] }}</div>
|
||
<div class="wawi-stat__label">{{ __('Versandfertig') }}</div>
|
||
</div>
|
||
</a>
|
||
<a class="wawi-stat is-clickable text-reset" href="{{ route('admin.inventory.orders.index', ['filter' => 'shipped_today']) }}">
|
||
<div class="wawi-stat__icon"><span class="fas fa-shipping-fast"></span></div>
|
||
<div class="wawi-stat__body">
|
||
<div class="wawi-stat__value">{{ $counts['shipped_today'] }}</div>
|
||
<div class="wawi-stat__label">{{ __('Heute versendet') }}</div>
|
||
</div>
|
||
</a>
|
||
</div>
|
||
|
||
<div class="wawi-card">
|
||
<div class="wawi-toolbar">
|
||
<div class="d-flex align-items-center" style="gap:.75rem;">
|
||
@if ($filter !== '')
|
||
<a href="{{ route('admin.inventory.orders.index') }}" class="btn btn-sm btn-outline-secondary">
|
||
<span class="fas fa-times mr-1"></span>{{ __('Filter zurücksetzen') }}
|
||
</a>
|
||
@endif
|
||
<input type="search" id="of-search" class="form-control form-control-sm" style="width:240px"
|
||
placeholder="{{ __('Suche Bestellnr. / Kunde …') }}">
|
||
</div>
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table class="table wawi-table" id="of-table">
|
||
<thead>
|
||
<tr>
|
||
<th>{{ __('Bestellung') }}</th>
|
||
<th>{{ __('Datum') }}</th>
|
||
<th>{{ __('Kunde') }}</th>
|
||
<th class="text-right">{{ __('Positionen') }}</th>
|
||
<th class="text-right">{{ __('Gewicht') }}</th>
|
||
<th>{{ __('Zahlung') }}</th>
|
||
<th>{{ __('Status') }}</th>
|
||
<th>{{ __('Dokumente') }}</th>
|
||
<th class="text-right">{{ __('Aktionen') }}</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@forelse ($orders as $order)
|
||
@php
|
||
$customer = $order->shopping_user
|
||
? trim(($order->shopping_user->shipping_firstname ?: $order->shopping_user->billing_firstname ?? '').' '.($order->shopping_user->shipping_lastname ?: $order->shopping_user->billing_lastname ?? ''))
|
||
: '';
|
||
$clearingtype = $order->shopping_payment_last?->clearingtype;
|
||
$readyToShip = $order->isReadyToShip();
|
||
$itemCount = $order->shopping_order_items->sum('qty');
|
||
@endphp
|
||
<tr class="of-row" data-search="{{ mb_strtolower($order->id.' '.$customer) }}">
|
||
<td>
|
||
<a href="{{ route('admin_sales_detail', $order->id) }}">#{{ $order->id }}</a>
|
||
@if ($order->isApiOrder())
|
||
<span class="wawi-pill" style="background:rgba(24,28,33,.06);color:#6c757d;">API</span>
|
||
@elseif ($order->user_shop_id)
|
||
<span class="wawi-pill" style="background:rgba(24,28,33,.06);color:#6c757d;">{{ __('Shop') }}</span>
|
||
@elseif ($order->promotion_user_id)
|
||
<span class="wawi-pill" style="background:rgba(24,28,33,.06);color:#6c757d;">{{ __('Promo') }}</span>
|
||
@endif
|
||
</td>
|
||
<td>{{ $order->created_at->format('d.m.Y H:i') }}</td>
|
||
<td>
|
||
<span class="wawi-item-name">{{ $customer !== '' ? $customer : '—' }}</span>
|
||
@if ($order->shipping_country?->country)
|
||
<span class="text-muted small">{{ strtoupper($order->shipping_country->country->code) }}</span>
|
||
@endif
|
||
</td>
|
||
<td class="text-right">
|
||
<button type="button" class="btn btn-sm btn-link p-0 js-of-toggle" data-target="of-items-{{ $order->id }}">
|
||
{{ $itemCount }} <span class="fas fa-chevron-down small"></span>
|
||
</button>
|
||
</td>
|
||
<td class="text-right">{{ $order->weight ? number_format($order->weight / 1000, 2, ',', '.').' kg' : '—' }}</td>
|
||
<td>
|
||
@if ($readyToShip)
|
||
<span class="wawi-pill wawi-pill--ok">{{ __('versandfertig') }}</span>
|
||
@else
|
||
<span class="wawi-pill wawi-pill--warning">{{ __('wartet auf Zahlung') }}</span>
|
||
@endif
|
||
@if ($clearingtype === 'fnc')
|
||
<div class="small text-muted">{{ __('Rechnung') }}</div>
|
||
@elseif ($clearingtype === 'vor')
|
||
<div class="small text-muted">{{ __('Vorkasse') }}</div>
|
||
@endif
|
||
</td>
|
||
<td>
|
||
@if ((int) $order->shipped === 0)
|
||
<span class="wawi-pill wawi-pill--warning">{{ __('offen') }}</span>
|
||
@else
|
||
<span class="wawi-pill" style="background:rgba(3,155,229,.12);color:#0277bd;">{{ $order->getShippedType() }}</span>
|
||
@endif
|
||
</td>
|
||
<td>
|
||
@if ($order->isApiOrder())
|
||
@if ($order->wp_invoice_path)
|
||
<a href="{{ $order->wp_invoice_path }}" target="_blank" rel="noopener" title="{{ __('Externe Rechnung (Shop)') }}">
|
||
<span class="fas fa-external-link-alt"></span> {{ __('Extern') }}
|
||
</a>
|
||
@else
|
||
<span class="text-muted small">{{ __('extern') }}</span>
|
||
@endif
|
||
@elseif (\App\Services\Invoice::isInvoice($order))
|
||
<a href="{{ route('admin.inventory.orders.document', [$order->id, 'invoice']) }}" target="_blank" title="{{ __('Rechnung') }}" class="mr-1"><span class="far fa-file-alt"></span> {{ __('Re.') }}</a>
|
||
<a href="{{ route('admin.inventory.orders.document', [$order->id, 'delivery']) }}" target="_blank" title="{{ __('Lieferschein') }}" class="mr-1"><span class="fas fa-dolly"></span> {{ __('LS') }}</a>
|
||
<a href="{{ route('admin.inventory.orders.document', [$order->id, 'combined']) }}" target="_blank" title="{{ __('Rechnung + Lieferschein') }}"><span class="fas fa-print"></span></a>
|
||
@else
|
||
<form action="{{ route('admin.inventory.orders.documents', $order->id) }}" method="post" class="d-inline">
|
||
@csrf
|
||
<input type="hidden" name="back_filter" value="{{ $filter }}">
|
||
<button type="submit" class="btn btn-sm btn-outline-secondary" title="{{ __('Rechnung + Lieferschein erstellen (ohne E-Mail-Versand)') }}"
|
||
onclick="return confirm(@json(__('Rechnung + Lieferschein jetzt erstellen? (Es wird keine E-Mail versendet — Versand wie gewohnt über die Bestell-Details.)')));">
|
||
<span class="fas fa-file-medical mr-1"></span>{{ __('erstellen') }}
|
||
</button>
|
||
</form>
|
||
@endif
|
||
</td>
|
||
<td class="text-right" style="white-space:nowrap;">
|
||
@if ((int) $order->shipped === 0)
|
||
<form action="{{ route('admin.inventory.orders.status', $order->id) }}" method="post" class="d-inline">
|
||
@csrf
|
||
<input type="hidden" name="shipped" value="1">
|
||
<input type="hidden" name="back_filter" value="{{ $filter }}">
|
||
<button type="submit" class="btn btn-sm btn-outline-primary" title="{{ __('Als konfektioniert (in Bearbeitung) markieren') }}">
|
||
<span class="fas fa-box mr-1"></span>{{ __('Konfektioniert') }}
|
||
</button>
|
||
</form>
|
||
@endif
|
||
@if (in_array((int) $order->shipped, [0, 1], true))
|
||
<form action="{{ route('admin.inventory.orders.status', $order->id) }}" method="post" class="d-inline"
|
||
onsubmit="return confirm(@json(__('Bestellung als versendet buchen? Der Produktbestand wird abgezogen.')));">
|
||
@csrf
|
||
<input type="hidden" name="shipped" value="2">
|
||
<input type="hidden" name="back_filter" value="{{ $filter }}">
|
||
<button type="submit" class="btn btn-sm btn-primary" title="{{ __('Als versendet buchen (Bestandsabzug)') }}">
|
||
<span class="fas fa-shipping-fast mr-1"></span>{{ __('Versendet') }}
|
||
</button>
|
||
</form>
|
||
@endif
|
||
</td>
|
||
</tr>
|
||
<tr class="of-items d-none" id="of-items-{{ $order->id }}">
|
||
<td colspan="9" class="pl-4" style="background:rgba(24,28,33,.02);">
|
||
<strong class="small text-muted">{{ __('Packliste') }}</strong>
|
||
<ul class="mb-1 small">
|
||
@foreach ($order->shopping_order_items as $item)
|
||
<li>
|
||
{{ $item->qty }} × {{ $item->product?->name ?? __('(Produkt gelöscht)') }}
|
||
@if ($item->product?->is_set)
|
||
<span class="wawi-pill" style="background:rgba(24,28,33,.06);color:#6c757d;">SET</span>
|
||
<ul class="text-muted">
|
||
@foreach ($item->product->setItems as $component)
|
||
<li>{{ $item->qty * $component->pivot->quantity }} × {{ $component->name }}</li>
|
||
@endforeach
|
||
</ul>
|
||
@endif
|
||
</li>
|
||
@endforeach
|
||
</ul>
|
||
</td>
|
||
</tr>
|
||
@empty
|
||
<tr>
|
||
<td colspan="9">
|
||
<div class="wawi-empty">
|
||
<div><span class="fas fa-mug-hot"></span></div>
|
||
{{ __('Nichts zu tun — keine Bestellungen in diesem Filter.') }}
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
@endforelse
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
@if (($listLimit ?? null) !== null && $orders->count() >= $listLimit)
|
||
<p class="text-muted small px-3 pb-2 mb-0">
|
||
{{ __('Es werden die neuesten :n Bestellungen angezeigt.', ['n' => $listLimit]) }}
|
||
<a href="{{ route('admin.inventory.orders.index', array_filter(['all' => 1, 'filter' => $filter])) }}">{{ __('Alle anzeigen') }}</a>
|
||
</p>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
$(function () {
|
||
$('.js-of-toggle').on('click', function () {
|
||
$('#' + $(this).data('target')).toggleClass('d-none');
|
||
});
|
||
$('#of-search').on('input', function () {
|
||
var term = ($(this).val() || '').toLowerCase().trim();
|
||
$('#of-table tbody tr.of-row').each(function () {
|
||
var show = term === '' || ($(this).data('search') || '').toString().indexOf(term) !== -1;
|
||
$(this).toggle(show);
|
||
if (!show) {
|
||
$('#of-items-' + $(this).find('a').first().text().replace('#', '')).addClass('d-none');
|
||
}
|
||
});
|
||
});
|
||
});
|
||
</script>
|
||
@endsection
|