Feedback-Umsetzung (dev/2026-07-22/feedback-analyse-2026-07-24.md, alle 10 Punkte):
- VIP-Statistik: eigener Berater ('ich', Linie 0) in Neue-Kundenabos- und beiden Top-10-Listen; Top-10-Umsatz sortiert nach abgerechneten Ist-Punkten + Abo-Anzahl-Spalte; Fußnote nicht-payline-wirksame Punkte (SalesVolumePoints::nonPaylinePoints)
- Führungskräfte-Liste: beide Sichten 'direkt' (an nächster FK abgeschnitten, additiv) und 'gesamt' (komplette Downline), Gesamt-Spalte nach Kundenzählweise
- Team-Abos: je Ebene 'abgerechnet (Monat)' und 'bis zum 25. erwartet'; Payline-Karte mit Teamabo-/Kundenabo-Zählern
- Incentive-Live-Ranking: Qualifizierte zuerst nummeriert (fixt auch isWinner rank<=max_winners)
- Porto: Netto-/Brutto-Kennzeichnung (Berater netto, Endkunden inkl. MwSt., portal_view-Flag; auch CRM-/Shop-Warenkorb + Checkout), Yard::shipping_tax-Fix
- Struktur-Ansicht: Abo-Badges 'A' (eigenes Abo) und '(n)' (Kundenabos) je Teampartner, gebatcht
- Abo-Löschung: Softdelete setzt active=0 (UserAbo deleting-Hook) + Bereinigungs-Migration für gelöschte-aber-aktive Abos
- Doku: technische Analyse + Kundinnen-Feedback-Dokument unter dev/2026-07-22/
- Tests: IncentiveRankingTest, TeamStructureAboBadgesTest, YardShippingTaxTest (neu); BackofficeStatisticsListsTest, TeamAboOverviewTest, PaylineForecastServiceTest, AboAdminDeleteTest, AboOneTimeViewTest erweitert
Außerdem enthalten (Branch-WIP vor dem Feedback): DATEV-Abgleich-Fixes 08+09/2025, USt-ID-Live-Validierung sowie restliche unkommittete Stände (u. a. Abo-Pause-Sprachkeys).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
302 lines
11 KiB
PHP
302 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\ShoppingUser;
|
|
use App\Models\ShoppingUserMemberLog;
|
|
use App\Models\UserCleanUpLog;
|
|
use App\Models\UserShop;
|
|
use App\User;
|
|
|
|
class UserUtil
|
|
{
|
|
public static function setShoppingUserToNewMember($pre_member_id, $new_member_id)
|
|
{
|
|
$ShoppingUsers = ShoppingUser::where('member_id', $pre_member_id)->get();
|
|
foreach ($ShoppingUsers as $shopping_user) {
|
|
ShoppingUserMemberLog::create([
|
|
'pre_member_id' => $shopping_user->member_id,
|
|
'shopping_user_id' => $shopping_user->id,
|
|
'new_member_id' => $new_member_id,
|
|
]);
|
|
$shopping_user->member_id = $new_member_id;
|
|
$shopping_user->save();
|
|
}
|
|
}
|
|
|
|
public static function setNewSponsorToChilds($inactive_sponsor_id, $new_sponsor_id)
|
|
{
|
|
// alle User die diesen inaktivien Sponsor haben
|
|
$child_users = User::where('m_sponsor', $inactive_sponsor_id)->get(); // auch deaktiverte
|
|
foreach ($child_users as $child_user) {
|
|
UserCleanUpLog::create([
|
|
'inactive_sponsor_id' => $inactive_sponsor_id,
|
|
'child_user_id' => $child_user->id,
|
|
'new_sponsor_id' => $new_sponsor_id,
|
|
]);
|
|
$child_user->m_sponsor = $new_sponsor_id;
|
|
$child_user->save();
|
|
}
|
|
}
|
|
|
|
public static function resetChildsToSponsor($re_sponsor_id)
|
|
{
|
|
// alle alten Childs vom re_sponsor_id / User wieder herstellen
|
|
$UserCleanUpUsers = UserCleanUpLog::where('inactive_sponsor_id', $re_sponsor_id)->get();
|
|
foreach ($UserCleanUpUsers as $UserCleanUpUser) {
|
|
$child_user = User::find($UserCleanUpUser->child_user_id);
|
|
if ($child_user) {
|
|
// delete Logs from user child where is newer then this
|
|
$deleteUserCleanUpLogs = UserCleanUpLog::where('child_user_id', $UserCleanUpUser->child_user_id)->where('created_at', '>', $UserCleanUpUser->created_at)->get();
|
|
foreach ($deleteUserCleanUpLogs as $deleteUserCleanUpLog) {
|
|
$deleteUserCleanUpLog->delete();
|
|
}
|
|
if ($child_user->m_sponsor) { // child is active
|
|
$child_user->m_sponsor = $re_sponsor_id;
|
|
}
|
|
if ($child_user->pre_sponsor) { // child is inactive
|
|
$child_user->pre_sponsor = $re_sponsor_id;
|
|
}
|
|
$child_user->save();
|
|
// delete this log
|
|
$UserCleanUpUser->delete();
|
|
}
|
|
}
|
|
}
|
|
|
|
public static function setUserToClient($user_id, $sponsor_id)
|
|
{
|
|
$user = User::find($user_id);
|
|
|
|
if (! $user) {
|
|
\Log::channel('cleanup')->error('setUserToClient: User not found, user_id: '.$user_id);
|
|
|
|
return false;
|
|
}
|
|
|
|
if (! $user->account) {
|
|
\Log::channel('cleanup')->error('setUserToClient: User has no account data, user_id: '.$user_id);
|
|
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
$data = [
|
|
'member_id' => $sponsor_id,
|
|
'language' => $user->lang ?? 'de',
|
|
'billing_salutation' => $user->account->salutation,
|
|
'billing_company' => $user->account->company,
|
|
'billing_firstname' => $user->account->first_name,
|
|
'billing_lastname' => $user->account->last_name,
|
|
'billing_address' => $user->account->address,
|
|
'billing_address_2' => $user->account->address_2,
|
|
'billing_zipcode' => $user->account->zipcode,
|
|
'billing_city' => $user->account->city,
|
|
'billing_country_id' => $user->account->country_id,
|
|
'billing_phone' => $user->account->getPhoneNumber(),
|
|
'billing_email' => $user->email,
|
|
'language' => $user->account->getLocale(),
|
|
'same_as_billing' => $user->account->same_as_billing,
|
|
'shipping_salutation' => $user->account->shipping_salutation,
|
|
'shipping_company' => $user->account->shipping_company,
|
|
'shipping_firstname' => $user->account->shipping_firstname,
|
|
'shipping_lastname' => $user->account->shipping_lastname,
|
|
'shipping_address' => $user->account->shipping_address,
|
|
'shipping_address_2' => $user->account->shipping_address_2,
|
|
'shipping_zipcode' => $user->account->shipping_zipcode,
|
|
'shipping_city' => $user->account->shipping_city,
|
|
'shipping_country_id' => $user->account->shipping_country_id,
|
|
'shipping_phone' => $user->account->getShippingPhoneFull(),
|
|
'shipping_postnumber' => $user->account->shipping_postnumber,
|
|
];
|
|
|
|
ShoppingUser::create($data);
|
|
|
|
return true;
|
|
} catch (\Exception $e) {
|
|
\Log::channel('cleanup')->error('setUserToClient failed for user_id: '.$user_id.' | Error: '.$e->getMessage());
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
find next activ sponsor on user id
|
|
first $sponsor_id can user_id, looks has m_sponsor or pre_sponsor.
|
|
*/
|
|
public static function findNextActiveSponsor($sponsor_id)
|
|
{
|
|
|
|
$user = User::withTrashed()->find($sponsor_id);
|
|
if (! $user) { // kein User unter der ID - to root
|
|
return User::find(6);
|
|
}
|
|
// user ist aktiv
|
|
if ($user->isActiveAccount()) {
|
|
return $user;
|
|
}
|
|
if ($user->m_sponsor) { // hat der User einen m_sponsor
|
|
return self::findNextActiveSponsor($user->m_sponsor);
|
|
}
|
|
if ($user->pre_sponsor) { // hat der User einen pre_sponsor - schon inaktiv
|
|
return self::findNextActiveSponsor($user->pre_sponsor);
|
|
}
|
|
|
|
// dump('not sponsor');
|
|
return $user;
|
|
}
|
|
|
|
public static function deactiveUser($user)
|
|
{
|
|
|
|
$user->pre_sponsor = $user->m_sponsor; // den sponsor speichern für wiederherstellung
|
|
$user->m_sponsor = null;
|
|
$user->active = false;
|
|
$user->save();
|
|
}
|
|
|
|
public static function reactiveUser($user)
|
|
{
|
|
if ($user->pre_sponsor) {
|
|
$pre_sponsor = self::findNextActiveSponsor($user->pre_sponsor);
|
|
$user->m_sponsor = $pre_sponsor->id; // den sponsor wiederherstellen
|
|
$user->pre_sponsor = null;
|
|
}
|
|
$user->active = true;
|
|
$user->save();
|
|
}
|
|
|
|
public static function deleteUser(User $user, $complete = false)
|
|
{
|
|
// shop wird gelöscht
|
|
if ($user->shop) {
|
|
$user->shop->name = 'delete'.$user->shop->id;
|
|
$user->shop->slug = 'delete'.$user->shop->id;
|
|
$user->shop->save();
|
|
$user->shop->delete();
|
|
}
|
|
|
|
// user soll nicht komplett gelöscht werden
|
|
$user->email = 'delete-'.$user->email;
|
|
// password wird gelöscht
|
|
$user->password = 'delete'.time();
|
|
$user->confirmed = 0;
|
|
$user->confirmation_code = 'delete'.time();
|
|
$user->confirmation_date = null;
|
|
$user->confirmation_code_to = null;
|
|
$user->confirmation_code_remider = 2;
|
|
// $user->agreement = null;
|
|
$user->active = 0;
|
|
$user->remember_token = '';
|
|
$user->active_date = null;
|
|
$user->admin = 0;
|
|
$user->deleted_at = now();
|
|
$user->pre_deleted_at = now();
|
|
// user soll komplett gelöscht werden
|
|
if ($complete) {
|
|
$user->email = 'delete-'.time().'-'.rand(1000, 9999);
|
|
if ($user->account) {
|
|
$user->account->delete();
|
|
}
|
|
$user->pre_deleted_at = null;
|
|
}
|
|
|
|
$user->save();
|
|
|
|
return true;
|
|
}
|
|
|
|
public static function checkEmailExists($user)
|
|
{
|
|
$email = str_replace('delete-', '', $user->email);
|
|
|
|
$user = User::where('email', $email)->first();
|
|
if ($user) {
|
|
return 'Der Account kann nicht wieder hergestellt werden, da die E-Mail-Adresse <b>'.$email.'</b> bereits in Verwendung ist.';
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static function restoreUser($user, $payment_account)
|
|
{
|
|
if ($user->pre_sponsor) {
|
|
$pre_sponsor = self::findNextActiveSponsor($user->pre_sponsor);
|
|
$user->m_sponsor = $pre_sponsor->id; // den sponsor wiederherstellen
|
|
$user->pre_sponsor = null;
|
|
}
|
|
|
|
$user->email = str_replace('delete-', '', $user->email);
|
|
$user->confirmed = 1;
|
|
$user->confirmation_date = now();
|
|
$user->confirmation_code = null;
|
|
$user->confirmation_code_to = null;
|
|
$user->confirmation_code_remider = 0;
|
|
$user->active = 1;
|
|
$user->active_date = now();
|
|
$user->deleted_at = null;
|
|
$user->pre_deleted_at = null;
|
|
$user->payment_account = $payment_account;
|
|
$user->payment_shop = $payment_account;
|
|
$user->wizard = 100;
|
|
$user->save();
|
|
|
|
$userShop = UserShop::withTrashed()->where('user_id', $user->id)->first();
|
|
if ($userShop) {
|
|
$userShop->name = null;
|
|
$userShop->slug = null;
|
|
$userShop->active = 0;
|
|
$userShop->deleted_at = null;
|
|
$userShop->save();
|
|
}
|
|
}
|
|
|
|
public static function reactiveUserResetChilds($user_id, $info = '')
|
|
{
|
|
|
|
$user = User::find($user_id);
|
|
if (! $user) {
|
|
\Log::channel('cleanup')->error('reactiveUserResetChilds find no user by user_id:'.$user_id);
|
|
|
|
return 0;
|
|
}
|
|
$data = [
|
|
'user_id' => $user->id,
|
|
'email' => $user->email,
|
|
'm_account' => $user->account ? $user->account->m_account : '',
|
|
'm_first_name' => $user->account ? $user->account->m_first_name : '',
|
|
'm_last_name' => $user->account ? $user->account->m_last_name : '',
|
|
];
|
|
\Log::channel('cleanup')->info('reactiveUserResetChilds '.$info.' : '.json_encode($data));
|
|
|
|
self::reactiveUser($user);
|
|
self::resetChildsToSponsor($user->id);
|
|
}
|
|
|
|
public static function deactiveUserNewSponsorChilds($user_id, $info = '')
|
|
{
|
|
|
|
$user = User::find($user_id);
|
|
if (! $user) {
|
|
\Log::channel('cleanup')->error('deactiveUserNewSponsorChilds find no user by user_id:'.$user_id);
|
|
|
|
return 0;
|
|
}
|
|
$data = [
|
|
'user_id' => $user->id,
|
|
'email' => $user->email,
|
|
'm_account' => $user->account ? $user->account->m_account : '',
|
|
'm_first_name' => $user->account ? $user->account->m_first_name : '',
|
|
'm_last_name' => $user->account ? $user->account->m_last_name : '',
|
|
];
|
|
|
|
$active_sponsor = self::findNextActiveSponsor($user->m_sponsor);
|
|
if ($active_sponsor) {
|
|
self::setNewSponsorToChilds($user->id, $active_sponsor->id);
|
|
} else {
|
|
\Log::channel('cleanup')->error('cleanUpInActiveUser find no active_sponsor by inactive_user:'.$user->id);
|
|
}
|
|
\Log::channel('cleanup')->info('deactiveUserNewSponsorChilds '.$info.' : '.json_encode($data));
|
|
self::deactiveUser($user);
|
|
}
|
|
}
|