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>
378 lines
12 KiB
PHP
378 lines
12 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Created by Reliese Model.
|
|
*/
|
|
|
|
namespace App\Models;
|
|
|
|
use App\User;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
/**
|
|
* Class UserSalesVolume
|
|
*
|
|
* @property int $id
|
|
* @property int $user_id
|
|
* @property int|null $shopping_order_id
|
|
* @property int|null $month
|
|
* @property int|null $year
|
|
* @property Carbon|null $date
|
|
* @property int|null $points
|
|
* @property int|null $month_points
|
|
* @property int $status_points
|
|
* @property float|null $total_net
|
|
* @property float|null $month_total_net
|
|
* @property string|null $message
|
|
* @property string|null $info
|
|
* @property int $status
|
|
* @property Carbon|null $created_at
|
|
* @property Carbon|null $updated_at
|
|
* @property ShoppingOrder|null $shopping_order
|
|
* @property User $user
|
|
* @property int|null $user_invoice_id
|
|
* @property int|null $month_shop_points
|
|
* @property float|null $month_shop_total_net
|
|
* @property-read UserInvoice|null $user_invoice
|
|
*
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume newModelQuery()
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume newQuery()
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume query()
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereCreatedAt($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereDate($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereId($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereMessage($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereMonth($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereMonthPoints($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereMonthShopPoints($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereMonthShopTotalNet($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereMonthTotalNet($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume wherePoints($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereShoppingOrderId($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereStatus($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereTotalNet($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereUpdatedAt($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereUserId($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereUserInvoiceId($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereYear($value)
|
|
*
|
|
* @property array|null $syslog
|
|
*
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereSyslog($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereInfo($value)
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereStatusPoints($value)
|
|
*
|
|
* @property int|null $month_KP_points
|
|
* @property int|null $month_TP_points
|
|
*
|
|
* @method static \Illuminate\Database\Eloquent\Builder|UserSalesVolume whereMonthKPPoints($value)
|
|
* @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
|
|
*/
|
|
class UserSalesVolume extends Model
|
|
{
|
|
protected $table = 'user_sales_volumes';
|
|
|
|
protected $casts = [
|
|
'user_id' => 'int',
|
|
'shopping_order_id' => 'int',
|
|
'user_invoice_id' => 'int',
|
|
'month' => 'int',
|
|
'year' => 'int',
|
|
'points' => 'float',
|
|
'month_KP_points' => 'float',
|
|
'month_TP_points' => 'float',
|
|
'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',
|
|
'status' => 'int',
|
|
'syslog' => 'array',
|
|
];
|
|
|
|
protected $dates = [
|
|
'date',
|
|
];
|
|
|
|
protected $fillable = [
|
|
'user_id',
|
|
'shopping_order_id',
|
|
'user_invoice_id',
|
|
'month',
|
|
'year',
|
|
'date',
|
|
'points',
|
|
'month_KP_points',
|
|
'month_TP_points',
|
|
'month_shop_points',
|
|
'status_points',
|
|
'status_turnover',
|
|
'storno_origin_status',
|
|
'total_net',
|
|
'month_total_net',
|
|
'month_shop_total_net',
|
|
'message',
|
|
'info',
|
|
'status',
|
|
'syslog',
|
|
];
|
|
|
|
public static $statusPointsTypes = [
|
|
1 => 'KU + TP', // Eigene + Team
|
|
2 => 'KU', // nur Eigene nicht Team
|
|
];
|
|
|
|
public static $statusTurnoverTypes = [
|
|
1 => 'advisor_order', // hinzugefügt aus
|
|
2 => 'shoporder', // hinzugefügt aus
|
|
];
|
|
|
|
public static $statusTypes = [
|
|
0 => 'not_assigned',
|
|
1 => 'advisor_order', // hinzugefügt aus
|
|
2 => 'shoporder', // hinzugefügt aus
|
|
3 => 'shoporder_pending', // hinzugefügt aus
|
|
4 => 'credit', // hinzugefügt aus
|
|
5 => 'registration', // hinzugefügt aus
|
|
6 => 'cancelled', // Stornorechnung
|
|
// 10 => ''
|
|
];
|
|
|
|
public static $statusColors = [
|
|
0 => 'warning',
|
|
1 => 'success',
|
|
2 => 'secondary',
|
|
3 => 'warning',
|
|
4 => 'info',
|
|
5 => 'info',
|
|
6 => 'danger',
|
|
10 => 'danger',
|
|
];
|
|
|
|
public function shopping_order()
|
|
{
|
|
return $this->belongsTo(ShoppingOrder::class);
|
|
}
|
|
|
|
public function user()
|
|
{
|
|
return $this->belongsTo(User::class);
|
|
}
|
|
|
|
public function user_invoice()
|
|
{
|
|
return $this->belongsTo(UserInvoice::class);
|
|
}
|
|
|
|
public function getDateAttribute()
|
|
{
|
|
return $this->attributes['date'] ? Carbon::parse($this->attributes['date'])->format(\Util::formatDateDB()) : '';
|
|
}
|
|
|
|
public function setDateAttribute($value)
|
|
{
|
|
$this->attributes['date'] = isset($value) ? (new Carbon($value))->format('Y-m-d') : null;
|
|
}
|
|
|
|
public function getDateRaw()
|
|
{
|
|
return isset($this->attributes['date']) ? $this->attributes['date'] : null;
|
|
}
|
|
|
|
// Points Setter/Getter für deutsches Zahlenformat
|
|
public function setPointsAttribute($value)
|
|
{
|
|
$this->attributes['points'] = $value !== null ? \Util::reFormatNumber($value) : null;
|
|
}
|
|
|
|
public function setMonthKPPointsAttribute($value)
|
|
{
|
|
$this->attributes['month_KP_points'] = $value !== null ? \Util::reFormatNumber($value) : null;
|
|
}
|
|
|
|
public function setMonthTPPointsAttribute($value)
|
|
{
|
|
$this->attributes['month_TP_points'] = $value !== null ? \Util::reFormatNumber($value) : null;
|
|
}
|
|
|
|
public function setMonthShopPointsAttribute($value)
|
|
{
|
|
$this->attributes['month_shop_points'] = $value !== null ? \Util::reFormatNumber($value) : null;
|
|
}
|
|
|
|
public function getFormattedPoints()
|
|
{
|
|
return isset($this->attributes['points']) ? \Util::formatNumber($this->attributes['points']) : '';
|
|
}
|
|
|
|
public function getFormattedMonthKPPoints()
|
|
{
|
|
return isset($this->attributes['month_KP_points']) ? \Util::formatNumber($this->attributes['month_KP_points']) : 0;
|
|
}
|
|
|
|
public function getFormattedMonthTPPoints()
|
|
{
|
|
return isset($this->attributes['month_TP_points']) ? \Util::formatNumber($this->attributes['month_TP_points']) : 0;
|
|
}
|
|
|
|
public function getFormattedMonthShopPoints()
|
|
{
|
|
return isset($this->attributes['month_shop_points']) ? \Util::formatNumber($this->attributes['month_shop_points']) : 0;
|
|
}
|
|
|
|
public function getPointsKPSum()
|
|
{
|
|
return $this->month_KP_points + $this->month_shop_points; // only KP für SUM - KP is for User
|
|
}
|
|
|
|
public function getPointsTPSum()
|
|
{
|
|
return $this->month_TP_points + $this->month_shop_points; // only TP für SUM - TP is only for Payline
|
|
}
|
|
|
|
public function getTotalNetSum()
|
|
{
|
|
return $this->month_total_net + $this->month_shop_total_net;
|
|
}
|
|
|
|
public function getStatusType()
|
|
{
|
|
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 = [];
|
|
foreach (self::$statusTypes as $key => $val) {
|
|
$ret[$key] = trans('payment.'.$val);
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public static function getTransTurnoverTypes()
|
|
{
|
|
$ret = [];
|
|
foreach (self::$statusTurnoverTypes as $key => $val) {
|
|
$ret[$key] = trans('payment.'.$val);
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
public function getStatusColor()
|
|
{
|
|
return isset(self::$statusColors[$this->status]) ? self::$statusColors[$this->status] : 'default';
|
|
}
|
|
|
|
public function getStatusPointsType()
|
|
{
|
|
return isset(self::$statusPointsTypes[$this->status_points]) ? self::$statusPointsTypes[$this->status_points] : '';
|
|
}
|
|
|
|
public function getStatusPointsColor()
|
|
{
|
|
return isset(self::$statusColors[$this->status_points]) ? self::$statusColors[$this->status_points] : 'default';
|
|
}
|
|
|
|
public function getStatusTurnoverType()
|
|
{
|
|
switch ($this->status) {
|
|
case 1: // Bestellung Berater
|
|
return 'E';
|
|
case 2: // Shop
|
|
return 'S';
|
|
case 4: // Gutschrift
|
|
if ($this->status_turnover === 2) {
|
|
return 'S';
|
|
} else {
|
|
return 'E';
|
|
}
|
|
case 5: // Registrierung
|
|
return 'E';
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
public function getStatusTurnoverColor()
|
|
{
|
|
|
|
switch ($this->status) {
|
|
case 1: // Bestellung Berater
|
|
return 'success';
|
|
case 2: // Shop
|
|
return 'secondary';
|
|
case 4: // Gutschrift
|
|
if ($this->status_turnover === 2) {
|
|
return 'secondary';
|
|
} else {
|
|
return 'success';
|
|
}
|
|
case 5: // Registrierung
|
|
return 'success';
|
|
}
|
|
|
|
return 'default';
|
|
}
|
|
|
|
public function getFormatedMonthYear()
|
|
{
|
|
return str_pad($this->month, 2, '0', STR_PAD_LEFT).'/'.$this->year;
|
|
}
|
|
|
|
public function isCurrentMonthYear()
|
|
{
|
|
if ($this->month === intval(date('m')) && $this->year === intval(date('Y'))) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function caluCommissonTotalNet($margin)
|
|
{
|
|
if ($this->total_net > 0 && $margin > 0) {
|
|
return $this->total_net / 100 * $margin;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|