mivita/resources/views/admin/payment/salesvolume.blade.php
Kevin d01b9bca3a Absatzmengen: nach Warenausgang (Versanddatum) oder nach Rechnung auswertbar
- shopping_orders.shipped_at (+ Herkunft live/dhl/geschaetzt): gesetzt im
  Model, sobald der Versandstatus auf versendet wechselt, egal an welcher
  Stelle der Status gesetzt wird. sales:backfill-shipped-at traegt
  Altbestellungen aus dem DHL-Label nach oder schaetzt.
- SalesVolumeService rechnet Absatzmengen je Produkt wahlweise nach
  Versand (inkl. externer/API-Bestellungen, Stornos im Monat des
  Stornobelegs) oder nach Rechnung. Sets werden fuer die zweite Liste in
  Einzelartikel aufgeloest.
- Admin-Seite: Umschalter Versand/Rechnung, Datenqualitaet (Anteil
  geschaetzter Versanddaten), Export Produkte und Einzelartikel.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 15:42:39 +00:00

194 lines
9.3 KiB
PHP

@extends('layouts.layout-2')
@section('content')
@if ($errors->any())
<div class="row">
<div class="col-sm-12">
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
</div>
</div>
@endif
<div class="card">
<h5 class="card-header">
{{ __('navigation.sales_volumes') }} {{ __('navigation.products') }} &ndash; {{ $basis_label }}
</h5>
<div class="card-body">
{!! Form::open(['action' => route('admin_payments_sales_volumes_download'), 'class' => '']) !!}
{!! Form::hidden('key', 'value') !!}
<button type="submit" name="action" value="export" class="btn btn-sm btn-primary mb-2"><i class="ion ion-md-download"></i> &nbsp;Export Produkte als xls</button>
&nbsp;
<button type="submit" name="action" value="export_articles" class="btn btn-sm btn-primary mb-2"><i class="ion ion-md-download"></i> &nbsp;Export Einzelartikel als xls</button>
&nbsp;
<button type="submit" name="action" value="exportfull_paid_invoice" class="btn btn-sm btn-primary mb-2"><i class="ion ion-md-download"></i> &nbsp;Export vollständige Liste als xls</button>
<hr>
<div class="form-row align-items-center px-0 pb-2 pt-0">
<div class="col-12 col-sm-4 col-md-4 col-lg-4 mb-1">
<select class="custom-select" id="payment_sales_vol_filter_basis" name="payment_sales_vol_filter_basis">
@foreach($filter_basis as $key=>$value)
<option value="{{$key}}" @if($basis === $key) selected @endif>{{$value}}</option>
@endforeach
</select>
</div>
<div class="col-6 col-sm-4 col-md-4 col-lg-4 mb-1">
<select class="custom-select on_change_select_filter" name="payment_sales_vol_filter_month">
@foreach($filter_months as $key=>$value)
<option value="{{$key}}" @if(session('payment_sales_vol_filter_month') == $key) selected @endif>{{$value}}</option>
@endforeach
</select>
</div>
<div class="col-6 col-sm-4 col-md-4 col-lg-4 mb-1">
<select class="custom-select on_change_select_filter" name="payment_sales_vol_filter_year">
@foreach($filter_years as $key=>$value)
<option value="{{$value}}" @if(session('payment_sales_vol_filter_year') == $value) selected @endif>{{$value}}</option>
@endforeach
</select>
</div>
</div>
{!! Form::close() !!}
@if($basis === \App\Services\SalesVolumeService::BASIS_SHIPPING)
<div class="alert alert-light border small mb-3">
<i class="fa fa-truck text-muted"></i>
Gezählt wird, was im gewählten Monat versendet wurde (Warenausgang), inklusive der externen/API-Bestellungen.
Stornos und Retouren werden im Monat der Stornorechnung abgezogen.
<br>
<strong>Datengrundlage:</strong> {{ $quality['orders'] }} versendete Bestellungen im Monat,
davon {{ $quality['estimated'] }} mit geschätztem Versanddatum ({{ $quality['estimated_percent'] }} %).
@if($quality['estimated_percent'] > 0)
Geschätzt wird das Versanddatum bei Bestellungen ohne DHL-Label &ndash; also aus der Zeit vor der DHL-Anbindung (produktiv ab 02/2026)
oder bei anderem Versandweg. Grundlage ist dann die letzte Änderung der Bestellung.
@endif
@if($quality['without_date'] > 0)
<br><span class="text-danger">{{ $quality['without_date'] }} versendete Bestellungen haben noch kein Versanddatum &ndash; <code>php artisan sales:backfill-shipped-at --fix</code> ausführen.</span>
@endif
</div>
@else
<div class="alert alert-light border small mb-3">
<i class="fa fa-file-invoice text-muted"></i>
Gezählt wird, was im gewählten Monat fakturiert wurde (Rechnungsdatum = Zahlungseingang).
Externe/API-Bestellungen erscheinen hier erst über die bezahlte Sammelrechnung des Partners &ndash; teils im Folgemonat.
Stornorechnungen werden abgezogen.
</div>
@endif
<h6 class="mt-3">Absatz je verkauftem Artikel <small class="text-muted">(Sets zählen als ein Set)</small></h6>
<div class="card mb-4">
<div class="card-datatable table-responsive">
<table class="table table-striped table-bordered" id="datatable-sales-volume">
<thead>
<tr>
<th>{{__('#') }}</th>
<th>{{__('tables.product')}}</th>
<th>{{__('tables.article_no')}}</th>
<th>{{__('tables.quantity')}}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<h6>Absatz je Einzelartikel <small class="text-muted">(Sets/Kits in ihre hinterlegten Anlagen aufgelöst)</small></h6>
<div class="card">
<div class="card-datatable table-responsive">
<table class="table table-striped table-bordered" id="datatable-sales-volume-articles">
<thead>
<tr>
<th>{{__('#') }}</th>
<th>{{__('tables.product')}}</th>
<th>{{__('tables.article_no')}}</th>
<th>{{__('tables.quantity')}}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
$( document ).ready(function() {
function filterData(d) {
d.payment_sales_vol_filter_month = $('select[name=payment_sales_vol_filter_month]').val();
d.payment_sales_vol_filter_year = $('select[name=payment_sales_vol_filter_year]').val();
d.payment_sales_vol_filter_basis = $('select[name=payment_sales_vol_filter_basis]').val();
}
var tableOptions = {
"processing": true,
"serverSide": true,
"stateSave": true,
"searching": false,
"order": [[0, "asc" ]],
"columns": [
{ data: 'id', orderable: true, searchable: false },
{ data: 'name', name: 'name', orderable: true, searchable: false },
{ data: 'number', name: 'number', orderable: true, searchable: false },
{ data: 'value', name: 'value', orderable: true, searchable: false },
],
"bLengthChange": false,
"iDisplayLength": 100,
"language": {
"url": "/js/datatables-{{ \App::getLocale() }}.json"
}
};
var oTable = $('#datatable-sales-volume').DataTable($.extend({}, tableOptions, {
ajax: {
url: '{!! route('admin_payments_sales_volumes_datatable') !!}',
data: function(d) {
filterData(d);
d.list = 'products';
}
}
}));
var oTableArticles = $('#datatable-sales-volume-articles').DataTable($.extend({}, tableOptions, {
ajax: {
url: '{!! route('admin_payments_sales_volumes_datatable') !!}',
data: function(d) {
filterData(d);
d.list = 'articles';
}
}
}));
$('#payment_sales_vol_filter_basis').on('change', function(){
window.location = '{!! route('admin_payments_sales_volumes') !!}?payment_sales_vol_filter_basis=' + $(this).val();
});
$('select.on_change_select_filter').on('change', function(){
oTable.draw();
oTableArticles.draw();
});
$('input.on_keyup_input_filter').on('keyup', function(){
oTable.draw();
oTableArticles.draw();
});
});
</script>
@endsection