gruene-seele/resources/views/admin/stats/salesvolume.blade.php
Kevin Adametz ef19dc2ced Absatzstatistik: Summen aus Bestellpositionen, Kompensationsprodukte ausgewiesen, Extern-Bestellungen nachgefüllt
- Stats\Sales: alle Summenzeilen (Angezeigt/Ausgeblendet/Gesamt/Rabatte/Netto)
  aus price_net × qty der Positionen statt aus subtotal_full/discount des
  Bestellkopfs; WooCommerce-Importe (txaction extern) hatten dort NULL und
  zählten seit 06/2026 mit 0 € (Aug 2026: 449 € statt 3.318 € angezeigt)
- Kompensationsprodukte (comp > 0) bleiben in der Menge, neue Spalten
  „davon Kompensation“ (aktuell + Vorjahr) und Summenzeile 9993
- Gelöschte Produkte bleiben im Umsatz sichtbar (withTrashed, „(gelöscht)“)
- WP-Import setzt subtotal_full und discount jetzt wie der Checkout
- Migration füllt subtotal_full/discount bestehender Extern-Bestellungen nach
- Excel-Export um die beiden Kompensationsspalten erweitert
- Test tests/Feature/StatsSalesVolumeTest.php (Konsistenz, Filter, Jahr)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 08:15:30 +00:00

151 lines
7.6 KiB
PHP
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@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.products') }} {{ __('navigation.sales_volumes') }}
</h5>
<div class="card-body">
{!! Form::open(['action' => route('admin_stats_sales_volumes_download'), 'class' => 'form-horizontal']) !!}
{!! Form::hidden('key', 'value') !!}
<button type="submit" name="action" value="export" class="btn btn-md btn-primary mb-2"><i class="ion ion-md-download"></i> &nbsp;Export als xls</button>
<hr>
<div class="form-row align-items-center px-0 pb-2 pt-0">
<div class="col-6 col-sm-4 col-md-4 col-lg-4 mb-1">
<select class="custom-select on_change_select_filter" name="product_sales_vol_filter_month">
@foreach($filter_months as $key=>$value)
<option value="{{$key}}" @if(session('product_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="product_sales_vol_filter_year">
@foreach($filter_years as $key=>$value)
<option value="{{$value}}" @if(session('product_sales_vol_filter_year') == $value) selected @endif>{{$value}}</option>
@endforeach
</select>
</div>
</div>
<div class="form-row align-items-center px-0 pb-2 pt-0">
<div class="col-12 col-sm-6 col-md-4 col-lg-4 mb-1">
<select class="selectpicker" name="product_sales_vol_filter_products[]" id="product_sales_vol_filter_products" data-style="btn-light" data-live-search="true" multiple>
<option value="0" selected>Produkte ausschließen</option>
@foreach($filter_products as $key=>$value)
<option value="{{$key}}" @if(in_array($key, session('product_sales_vol_filter_products'))) selected @endif>{{$value}}</option>
@endforeach
</select> <br>
<em class="">Die Produktliste erst nach der Filtern von Monat und Jahr aktualisieren</em>
</div>
<div class="col-6 col-sm-4 col-md-4 col-lg-4 mt-1 mb-3">
<button type="submit" name="action" value="filter" class="btn btn-md btn-primary mb-4"><i class="ion ion-md-refresh"></i> &nbsp;Filtern</button>
</div>
</div>
{!! Form::close() !!}
<div class="card">
<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>
<th>davon Kompensation</th>
<th>{{__('tables.total')}} netto €</th>
<th>Vorjahr {{__('tables.quantity')}}</th>
<th>Vorjahr davon Kompensation</th>
<th>Vorjahr {{__('tables.total')}} netto €</th>
<th>{{__('Einzelrabatt')}}</th>
<th>{{__('Einzelrabatt %')}}</th>
<th>{{__('VP Einzelrabatt in %')}}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<br>
<div class="alert alert-info m-2">
Alle Umsätze werden aus den Bestellpositionen (Netto-Einzelpreis × Menge) berechnet: „Angezeigter Umsatz“ + „Ausgeblendeter Umsatz“ = „Gesamter Umsatz“. Bei "Gesamter Umsatz", "Gesamte Rabatte", "Gesamt netto" sind die ausgeschlossenen Produkte einberechnet, da Rabatte auf Warenkorb-Ebene verrechnet werden und nicht auf Produkt-Ebene.<br>
Kompensationsprodukte (0 € Umsatz) sind in der Menge enthalten und werden in „davon Kompensation“ separat ausgewiesen.
</div>
</div>
</div>
</div>
<script>
$( document ).ready(function() {
var oTable = $('#datatable-sales-volume').DataTable({
"processing": true,
"serverSide": true,
"stateSave": true,
"searching": false,
ajax: {
url: '{!! route('admin_stats_sales_volumes_datatable') !!}',
data: function(d) {
d.product_sales_vol_filter_month = $('select[name=product_sales_vol_filter_month]').val();
d.product_sales_vol_filter_year = $('select[name=product_sales_vol_filter_year]').val();
}
},
"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: 'qty', name: 'qty', orderable: true, searchable: false },
{ data: 'comp_qty', name: 'comp_qty', orderable: true, searchable: false },
{ data: 'total', name: 'total', orderable: true, searchable: false },
{ data: 'pre_qty', name: 'pre_qty', orderable: true, searchable: false },
{ data: 'pre_comp_qty', name: 'pre_comp_qty', orderable: true, searchable: false },
{ data: 'pre_total', name: 'pre_total', orderable: true, searchable: false },
{ data: 'single_commission', name: 'single_commission', orderable: true, searchable: false },
{ data: 'value_commission', name: 'value_commission', orderable: true, searchable: false },
{ data: 'partner_commission', name: 'partner_commission', orderable: true, searchable: false },
],
"bLengthChange": false,
"iDisplayLength": 100,
"language": {
"url": "/js/datatables-{{ \App::getLocale() }}.json"
}
});
$('select.on_change_select_filter').on('change', function(){
// oTable.draw();
});
$('input.on_keyup_input_filter').on('keyup', function(){
// oTable.draw();
});
});
</script>
@endsection