Statistik: Stornos mindern die richtige Punktekategorie

Ein Storno einer Shop-Bestellung wurde als Eigenpunkt gebucht und liess damit
die Shop-Provisionsbasis ungemindert. Betroffen sind 7 Faelle aus April bis
August 2026 (178,82 EUR Differenz in der Provisionsbasis, gegen user_businesses
geprueft).

Ursache: `status_turnover` beschreibt nur, wohin der UMSATZ gehoert. Die Punkte
folgen einer anderen Regel — eine Gutschrift (Status 4) bucht ihre Punkte immer
auf KP/TP, auch wenn ihr Umsatz dem Shop zugeordnet ist. Die Gegenbuchung kann
diese Aufteilung ohne die Ursprungsart nicht spiegeln, deshalb haelt
`storno_origin_status` sie jetzt auf der Stornozeile fest. NULL heisst
"Altzeile ohne Angabe" und faellt auf das bisherige Verhalten zurueck —
abgeschlossene Monate aendern sich dadurch nicht.

Stornoregel (Kundenentscheidung): Ein Storno mindert nur den Monat der
Ursprungsbestellung. Ist der Monat abgeschlossen, passiert nichts mehr, denn
Abrechnung und Gutschriften stehen. Ein Minus in den neuen Monat mitzunehmen
waere nicht nachvollziehbar. Fuer den Ausnahmefall gibt es den Settings-Schalter
`storno-points-current-month`, standardmaessig aus.

Ausserdem: Die persoenliche Monatsstatistik summierte Status 0 (nicht
zugeordnet) und 3 (pending) mit, die gemeinsame Punkteauswertung nicht. Beide
nutzen jetzt dieselbe Statusliste, und ein Storno mindert das Segment seiner
Ursprungsbuchung statt pauschal in "Sonstige" zu laufen.

Die Migration fuer `status_turnover` zieht nur das Schema auf den tatsaechlichen
Stand nach (nullable statt NOT NULL) — auf Produktion faktisch wirkungslos,
aber Neuinstallationen und die Testdatenbank haben damit dieselbe Semantik.

Audit-Befunde A02, A06, A15, R01-R03.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin 2026-09-10 11:18:40 +00:00
parent 75c2790bd2
commit 1900860c74
10 changed files with 733 additions and 24 deletions

View file

@ -34,7 +34,7 @@ use Illuminate\Database\Eloquent\Model;
* @property int|null $user_invoice_id
* @property int|null $month_shop_points
* @property float|null $month_shop_total_net
* @property-read \App\Models\UserInvoice|null $user_invoice
* @property-read UserInvoice|null $user_invoice
*
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume newQuery()
@ -70,8 +70,10 @@ use Illuminate\Database\Eloquent\Model;
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereMonthTPPoints($value)
*
* @property int|null $status_turnover
* @property int|null $storno_origin_status
*
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereStatusTurnover($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereStornoOriginStatus($value)
*
* @mixin \Eloquent
*/
@ -91,6 +93,7 @@ class UserSalesVolume extends Model
'month_shop_points' => 'float',
'status_points' => 'int',
'status_turnover' => 'int',
'storno_origin_status' => 'int',
'total_net' => 'float',
'month_total_net' => 'float',
'month_shop_total_net' => 'float',
@ -115,6 +118,7 @@ class UserSalesVolume extends Model
'month_shop_points',
'status_points',
'status_turnover',
'storno_origin_status',
'total_net',
'month_total_net',
'month_shop_total_net',
@ -247,6 +251,32 @@ class UserSalesVolume extends Model
return isset(self::$statusTypes[$this->status]) ? __('payment.'.self::$statusTypes[$this->status]) : '';
}
/**
* Gehoert die Buchung zu einer Abo-Lieferung?
*
* Setzt eine geladene `shopping_order`-Relation voraus — jede Liste, die das
* auswertet, laedt sie bereits eager mit.
*/
public function isAboOrder(): bool
{
return (bool) $this->shopping_order?->is_abo;
}
/**
* Art der Buchung, bei Abo-Lieferungen mit dem Abo-Zusatz dahinter
* ("Shopbestellung (Abo)") — Kundenwunsch 2026-09-08.
*/
public function getStatusTypeWithAbo(): string
{
$type = (string) $this->getStatusType();
if (! $this->isAboOrder()) {
return $type;
}
return trim($type.' '.__('abo.abo_order_short'));
}
public static function getTransStatusType()
{
$ret = [];