23-01-2026
This commit is contained in:
parent
a939cd51ef
commit
a8b395e20d
248 changed files with 29342 additions and 4805 deletions
405
dev/buinessPlan/BusinessUserItem.php
Normal file
405
dev/buinessPlan/BusinessUserItem.php
Normal file
|
|
@ -0,0 +1,405 @@
|
|||
<?php
|
||||
namespace App\Services\BusinessPlan;
|
||||
|
||||
use App\User;
|
||||
use stdClass;
|
||||
use Carbon\Carbon;
|
||||
use App\Models\UserLevel;
|
||||
use App\Models\UserBusiness;
|
||||
use App\Services\TranslationHelper;
|
||||
use App\Models\UserBusinessStructure;
|
||||
|
||||
|
||||
class BusinessUserItem
|
||||
{
|
||||
public $businessUserItems = [];
|
||||
|
||||
private $date;
|
||||
private $b_user;
|
||||
private $user_level_active_pos;
|
||||
|
||||
|
||||
|
||||
public function __construct($date)
|
||||
{
|
||||
$this->date = $date;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function makeUser($user_id){
|
||||
|
||||
//check for user an load is saved
|
||||
$this->b_user = UserBusiness::where('user_id', $user_id)->where('month', $this->date->month)->where('year', $this->date->year)->first();
|
||||
if($this->b_user !== null){
|
||||
return;
|
||||
}
|
||||
//read User here, can delete in stored data.
|
||||
$user = User::find($user_id);
|
||||
if(!$user){
|
||||
return;
|
||||
}
|
||||
$user_level_active = $user->user_level ? $user->user_level : null;
|
||||
$this->user_level_active_pos = $user_level_active ? $user_level_active->pos : 0;
|
||||
$this->b_user = new UserBusiness();
|
||||
$fill = [
|
||||
'user_id' => $user->id,
|
||||
'month' => $this->date->month,
|
||||
'year' => $this->date->year,
|
||||
'm_level_id' => $user->m_level,
|
||||
'user_level_name' => $user_level_active ? $user_level_active->name : '',
|
||||
'active_account' => $user->payment_account ? Carbon::parse($user->payment_account)->gt(Carbon::parse($this->date->start_date)) : false,
|
||||
'payment_account_date' => $user->payment_account ? $user->getPaymentAccountDateFormat(false) : NULL,
|
||||
'active_date' => $user->active_date ? $user->active_date : NULL,
|
||||
'm_account' => $user->account->m_account,
|
||||
'email' => $user->email,
|
||||
'first_name' => $user->account->first_name,
|
||||
'last_name' => $user->account->last_name,
|
||||
'user_birthday' => $user->account->birthday,
|
||||
'user_phone' => $user->account->getPhoneNumber(),
|
||||
|
||||
'sales_volume_KP_points' => $user->getUserSalesVolumeBy($this->date->month, $this->date->year, 'sales_volume_KP_points'),
|
||||
'sales_volume_TP_points' => $user->getUserSalesVolumeBy($this->date->month, $this->date->year, 'sales_volume_TP_points'),
|
||||
'sales_volume_points_shop' => $user->getUserSalesVolumeBy($this->date->month, $this->date->year, 'sales_volume_points_shop'),
|
||||
|
||||
'sales_volume_points_KP_sum' => $user->getUserSalesVolumeBy($this->date->month, $this->date->year, 'sales_volume_points_KP_sum'), //KP + Shop Points
|
||||
'sales_volume_points_TP_sum' => $user->getUserSalesVolumeBy($this->date->month, $this->date->year, 'sales_volume_points_TP_sum'), //TP + Shop Points
|
||||
|
||||
'sales_volume_total' => $user->getUserSalesVolumeBy($this->date->month, $this->date->year, 'sales_volume_total'),
|
||||
'sales_volume_total_shop' => $user->getUserSalesVolumeBy($this->date->month, $this->date->year, 'sales_volume_total_shop'),
|
||||
'sales_volume_total_sum' => $user->getUserSalesVolumeBy($this->date->month, $this->date->year, 'sales_volume_total_sum'),
|
||||
|
||||
'margin' => $user_level_active ? $user_level_active->margin : 0, //is fix Rabatt für Kundenbestellungen
|
||||
'margin_shop' => $user_level_active ? $user_level_active->margin_shop : 0, //is fix Rabatt für Shopbestellungen
|
||||
'qual_kp' => $user_level_active ? $user_level_active->qual_kp : 0, //KP Kundenpoints from level
|
||||
'qual_pp' => $user_level_active ? $user_level_active->qual_pp : 0, //PP Payline Points from level
|
||||
|
||||
'payline_points' => 0,
|
||||
'commission_pp_total' => 0,
|
||||
'commission_shop_sales' => 0,
|
||||
'commission_growth_total' => 0,
|
||||
'version' => 2,
|
||||
];
|
||||
$this->b_user->fill($fill);
|
||||
$this->b_user->business_lines = [];
|
||||
$this->b_user->user_items = [];
|
||||
$this->b_user->commission_shop_sales = round($this->b_user->sales_volume_total_shop / 100 * $this->b_user->margin_shop, 2);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function getSalesVolumeTotalMargin(){
|
||||
return $this->b_user->getSalesVolumeTotalMargin();
|
||||
}
|
||||
|
||||
public function addUserID(){
|
||||
TreeCalcBot::addUserID($this->b_user->user_id);
|
||||
}
|
||||
|
||||
public function getBUser(){
|
||||
return $this->b_user;
|
||||
}
|
||||
|
||||
public function addBusinessLineToUser($line, $obj){
|
||||
$this->b_user->business_lines[$line] = $obj;
|
||||
}
|
||||
|
||||
public function addBusinessLinePoints($line, $points){
|
||||
$obj = $this->business_lines[$line];
|
||||
$obj->points += $points;
|
||||
$this->b_user->business_lines[$line] = $obj;
|
||||
}
|
||||
|
||||
public function addTotalTP($points){
|
||||
$this->b_user->total_pp += $points;
|
||||
}
|
||||
|
||||
public function isQualKP(){
|
||||
return ($this->sales_volume_points_KP_sum >= $this->qual_kp) ? true : false;
|
||||
}
|
||||
|
||||
public function isQualLevel(){
|
||||
return ($this->qual_user_level) ? true : false;
|
||||
}
|
||||
|
||||
public function isQualEqualLevel(){
|
||||
if($this->qual_user_level){
|
||||
return ($this->m_level_id == $this->qual_user_level['id']) ? true : false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getQualLevelPaylines(){
|
||||
if($this->qual_user_level){
|
||||
return $this->qual_user_level['paylines'];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function isQualLevelGrowth($line){
|
||||
if(isset($this->business_lines[$line])){
|
||||
$object = $this->business_lines[$line];
|
||||
if(isset($object->growth_bonus)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function getRestQualKP(){
|
||||
$ret = $this->sales_volume_points_KP_sum - $this->qual_kp;
|
||||
return $ret > 0 ? $ret : 0;
|
||||
}
|
||||
|
||||
public function getCommissionTotal(){
|
||||
return round($this->commission_shop_sales + $this->commission_pp_total + $this->commission_growth_total, 2);
|
||||
}
|
||||
//Provisierungslevel brechnen, Berechnung der Provisionen nach Level
|
||||
public function calcQualPP(){
|
||||
|
||||
//das ist der erreichte Provisierungslevel, nach paylinePoints und KP
|
||||
$qualUserLevel = $this->calcuQualLevel();
|
||||
if($qualUserLevel !== NULL){
|
||||
//prüfe einen Aufsieg im KarriereLevel
|
||||
$this->setNextUserLevel();
|
||||
$this->b_user->qual_user_level = $qualUserLevel->toArray();
|
||||
//setzen nächsten ProvisionsLevel wenn not isQualEqualLevel
|
||||
$this->setQualNextLevel();
|
||||
//Berechnung der Provisionen in der Payline
|
||||
$commission_pp_total = 0;
|
||||
$commission_growth_total = 0;
|
||||
for ($i=1; $i <= $qualUserLevel->paylines ; $i++) {
|
||||
if(isset($this->business_lines[$i])){
|
||||
$object = $this->business_lines[$i];
|
||||
$object->margin = $this->qual_user_level['pr_line_'.$i]; //provision in %
|
||||
$object->commission = round($object->points / 100 * $object->margin, 2); //provision in points/euro
|
||||
$object->payline = true;
|
||||
$commission_pp_total += $object->commission;
|
||||
$this->b_user->business_lines[$i] = $object;
|
||||
}
|
||||
}
|
||||
//Berechnung der Provisionen nach WB
|
||||
if($qualUserLevel->growth_bonus){
|
||||
//['growth_bonus'] //
|
||||
$payline = (int) $this->b_user->qual_user_level['paylines'] + 1;
|
||||
$maxlines = count($this->business_lines) + 1;
|
||||
$growth_bonus = (float) $this->b_user->qual_user_level['growth_bonus'];
|
||||
|
||||
for ($i=$payline; $i <= $maxlines ; $i++) {
|
||||
if(isset($this->business_lines[$i])){
|
||||
$object = $this->business_lines[$i];
|
||||
$object->margin = $growth_bonus; //provision in %
|
||||
$object->commission = round($object->points / 100 * $object->margin, 2); //provision in points/euro
|
||||
$object->growth_bonus = true;
|
||||
$commission_growth_total += $object->commission;
|
||||
$this->b_user->business_lines[$i] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
$this->b_user->commission_pp_total = $commission_pp_total;
|
||||
$this->b_user->commission_growth_total = $commission_growth_total;
|
||||
|
||||
}else{
|
||||
//erste Provisierungslevel als next setzen, hat keine oder wenig points
|
||||
$qualUserLevelNext = UserLevel::where('pos', '=', 1)->orderBy('qual_pp', 'asc')->first();
|
||||
if($qualUserLevelNext){
|
||||
$this->b_user->qual_user_level_next = $qualUserLevelNext->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//qualifikation nach qual_kp (KundenPoints) und qual_pp (PaylinePoints)
|
||||
public function calcuQualLevel(){
|
||||
//alle levels wo die qual_kp erreicht ist, sortiert nach Rang >
|
||||
$qualUserLevels = UserLevel::where('qual_kp', '<=', $this->sales_volume_points_KP_sum)->where('pos', '<=', $this->user_level_active_pos)->orderBy('qual_pp', 'desc')->get();
|
||||
foreach($qualUserLevels as $qualUserLevel){
|
||||
//brechnet die Points nach der Payline
|
||||
$payline_points = $this->getPointsforPayline($qualUserLevel->paylines);
|
||||
$payline_points_qual_kp = $payline_points + $this->getRestQualKP();
|
||||
if($payline_points_qual_kp >= $qualUserLevel->qual_pp){
|
||||
//match payline_points erreicht, ist der akutelle Level für die Provision
|
||||
$this->b_user->payline_points = $payline_points;
|
||||
$this->b_user->payline_points_qual_kp = $payline_points_qual_kp;
|
||||
|
||||
return $qualUserLevel;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// PaylinePoints nach paylines / welche ebenen gezählt werden, 3,4,5,6 ...
|
||||
private function getPointsforPayline($paylines){
|
||||
$payline_points = 0;
|
||||
for ($i=1; $i <= $paylines ; $i++) {
|
||||
if(isset($this->business_lines[$i])){
|
||||
$payline_points += $this->business_lines[$i]->points;
|
||||
}
|
||||
}
|
||||
return $payline_points;
|
||||
}
|
||||
//wenn nicht erreicht, was wäre der nächste Provisionslevel?
|
||||
private function setQualNextLevel(){
|
||||
if(!$this->isQualEqualLevel()){
|
||||
$qualUserLevelNext = UserLevel::where('id', '=', $this->b_user->qual_user_level['next_id'])->orderBy('qual_pp', 'asc')->first();
|
||||
if($qualUserLevelNext){
|
||||
$this->b_user->qual_user_level_next = $qualUserLevelNext->toArray();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function setNextUserLevel(){
|
||||
// $this->b_user->payline_points_qual_kp // das sind die Payline Points + Rest KP
|
||||
//$this->b_user->total_qual_pp = $this->total_pp + $this->getRestQualKP(); //hier werden alle Linien TP gezähle
|
||||
//$this->b_user->total_qual_pp = $this->total_pp + $this->getRestQualKP(); //hier werden alle Linien TP gezähle
|
||||
|
||||
$nextQualUserLevel = UserLevel::where('qual_pp', '<=', $this->payline_points_qual_kp)->where('pos', '>', $this->user_level_active_pos)->orderBy('qual_pp', 'desc')->first();
|
||||
if($nextQualUserLevel && $this->isQualKP()){
|
||||
$this->b_user->next_qual_user_level = $nextQualUserLevel->toArray();
|
||||
}else{
|
||||
//wenn nicht erreicht, was wäre der nächste Karrierelevel?
|
||||
$nextCanUserLevel = UserLevel::where('pos', '>', $this->user_level_active_pos)->orderBy('qual_pp', 'asc')->first();
|
||||
if($nextCanUserLevel){
|
||||
$this->b_user->next_can_user_level = $nextCanUserLevel->toArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*public function storeUser(){
|
||||
$this->b_user->user_items = $this->storeUserItems($this->businessUserItems, 1);
|
||||
$this->b_user->save();
|
||||
}
|
||||
|
||||
private function storeUserItems($userItems, $line){
|
||||
$ret = [];
|
||||
foreach($userItems as $userItem){
|
||||
$temp = null;
|
||||
if(count($userItem->businessUserItems) > 0){
|
||||
$temp = $this->storeUserItems($userItem->businessUserItems, $line+1);
|
||||
}
|
||||
$obj = new stdClass();
|
||||
$obj->user_id = $userItem->user_id;
|
||||
$obj->line = $line;
|
||||
$obj->points = $userItem->sales_volume_points_sum;
|
||||
$obj->parents = $temp;
|
||||
$ret[] = $obj;
|
||||
}
|
||||
return $ret;
|
||||
}*/
|
||||
|
||||
public function readParentsBusinessUsers(){
|
||||
|
||||
$users = User::with('account')->select('users.*')
|
||||
->where('users.deleted_at', '=', null)
|
||||
->where('users.id', '!=', 1)
|
||||
->where('users.admin', "<", 4)
|
||||
->where('users.m_level', "!=", null)
|
||||
->where('users.m_sponsor', "=", $this->b_user->user_id) //<- need the id for parents / sponsors
|
||||
->where('users.payment_account', "!=", null)
|
||||
->where('users.active_date', "<=", $this->date->end_date) // wurde in dem Monat freigeschaltet
|
||||
->get();
|
||||
|
||||
if($users){
|
||||
foreach($users as $user){
|
||||
$BusinessUserItem = new BusinessUserItem($this->date);
|
||||
$BusinessUserItem->makeUser($user->id);
|
||||
$BusinessUserItem->addUserID();
|
||||
$this->businessUserItems[] = $BusinessUserItem;
|
||||
}
|
||||
}
|
||||
foreach($this->businessUserItems as $businessUserItem){
|
||||
$businessUserItem->readParentsBusinessUsers();
|
||||
}
|
||||
}
|
||||
|
||||
public function readStoredParentsBusinessUsers($structure){
|
||||
|
||||
$parents = $this->findParentsBusinessOnStored($this->b_user->user_id, $structure);
|
||||
if($parents){
|
||||
foreach($parents as $obj){
|
||||
$BusinessUserItem = new BusinessUserItem($this->date);
|
||||
$BusinessUserItem->makeUser($obj->user_id);
|
||||
$BusinessUserItem->addUserID();
|
||||
$this->businessUserItems[] = $BusinessUserItem;
|
||||
}
|
||||
foreach($this->businessUserItems as $businessUserItem){
|
||||
$businessUserItem->readStoredParentsBusinessUsers($parents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function findParentsBusinessOnStored($user_id, $structures){
|
||||
if($structures){
|
||||
foreach($structures as $obj){
|
||||
if($user_id === $obj->user_id){
|
||||
return $obj->parents;
|
||||
}
|
||||
if($obj->parents){
|
||||
if($ret = $this->findParentsBusinessOnStored($user_id, $obj->parents)){
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function checkSponsor($user){
|
||||
|
||||
//check is store? has ID
|
||||
if($this->b_user->isSave()){
|
||||
return;
|
||||
}
|
||||
$sponsor = new stdClass();
|
||||
|
||||
$sponsor->is_sponsor = false;
|
||||
$sponsor->user_id = false;
|
||||
$sponsor->first_name = '';
|
||||
$sponsor->last_name = '';
|
||||
$sponsor->email = '';
|
||||
$sponsor->m_account = '';
|
||||
$sponsor->full_name = 'Keinen Sponsor zugewiesen';
|
||||
|
||||
if($user->m_sponsor){
|
||||
|
||||
if($user->user_sponsor){
|
||||
$sponsor->is_sponsor = true;
|
||||
$sponsor->user_id = $user->user_sponsor->id;
|
||||
if($user->user_sponsor->account){
|
||||
$sponsor->full_name = substr('Sponsor: '.$user->user_sponsor->account->first_name.' '.$user->user_sponsor->account->last_name.' | '.$user->user_sponsor->email.' | '.$user->user_sponsor->account->m_account, 0, 250);
|
||||
$sponsor->first_name = $user->user_sponsor->account->last_name;
|
||||
$sponsor->last_name = $user->user_sponsor->account->first_name;
|
||||
$sponsor->m_account = $user->user_sponsor->account->m_account;
|
||||
}else{
|
||||
$sponsor->full_name = 'Sponsor: '.$user->user_sponsor->email;
|
||||
}
|
||||
$sponsor->email = $user->user_sponsor->email;
|
||||
}else{
|
||||
$sponsor->full_name = 'Sponsor wurde gelöscht.';
|
||||
}
|
||||
}
|
||||
$this->b_user->sponsor = $sponsor;
|
||||
return;
|
||||
}
|
||||
|
||||
public function isSave(){
|
||||
return $this->b_user->isSave();
|
||||
}
|
||||
|
||||
public function __get($property) {
|
||||
if($this->b_user === null){
|
||||
return null;
|
||||
}
|
||||
if (property_exists($this->b_user, $property)) {
|
||||
return $this->b_user->$property;
|
||||
}
|
||||
if (isset($this->b_user->{$property})) {
|
||||
return $this->b_user->{$property};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
391
dev/buinessPlan/TreeCalcBot.php
Normal file
391
dev/buinessPlan/TreeCalcBot.php
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
<?php
|
||||
namespace App\Services\BusinessPlan;
|
||||
|
||||
use App\User;
|
||||
use stdClass;
|
||||
use Carbon;
|
||||
use App\Models\UserBusinessStructure;
|
||||
|
||||
class TreeCalcBot
|
||||
{
|
||||
public $date;
|
||||
public $business_user;
|
||||
|
||||
public $business_users = [];
|
||||
public $parentless = [];
|
||||
|
||||
private $sponsor;
|
||||
private $init_from;
|
||||
private static $userIDs = [];
|
||||
|
||||
public static function addUserID($id){
|
||||
self::$userIDs[$id] = $id;
|
||||
}
|
||||
|
||||
public function __construct($month, $year, $init_from = 'member')
|
||||
{
|
||||
$this->date = new stdClass();
|
||||
$date = Carbon::parse($year.'-'.$month.'-1');
|
||||
$this->date->month = $month;
|
||||
$this->date->year = $year;
|
||||
$this->date->start_date = $date->format('Y-m-d H:i:s');
|
||||
$this->date->end_date = $date->endOfMonth()->format('Y-m-d H:i:s');
|
||||
$this->init_from = $init_from;
|
||||
|
||||
}
|
||||
|
||||
public function initStructureAdmin($check = true, $forceLiveCalculation = false)
|
||||
{
|
||||
//check is month is saved.
|
||||
if($check && $UserBusinessStructure = self::isFromStored($this->date->month, $this->date->year)){
|
||||
$this->readStoredRootUsers($UserBusinessStructure);
|
||||
$this->readStoredParentsUsers($UserBusinessStructure);
|
||||
$this->readStoredParentlessUser($UserBusinessStructure);
|
||||
}else{
|
||||
$this->readRootUsers();
|
||||
$this->readParentsUsers();
|
||||
$this->readParentlessUser();
|
||||
}
|
||||
}
|
||||
|
||||
public function initStructureUser($user_id)
|
||||
{
|
||||
|
||||
$BusinessUserItem = new BusinessUserItem($this->date);
|
||||
$BusinessUserItem->makeUser($user_id);
|
||||
$BusinessUserItem->addUserID();
|
||||
$this->business_users[] = $BusinessUserItem;
|
||||
|
||||
//check is month is saved.
|
||||
if($UserBusinessStructure = self::isFromStored($this->date->month, $this->date->year)){
|
||||
$this->readStoredParentsUsers($UserBusinessStructure);
|
||||
|
||||
if(isset($this->business_users[0]) && $this->business_users[0]->sponsor){
|
||||
$this->readStoredSponsorUser($this->business_users[0]->sponsor->user_id);
|
||||
}
|
||||
}else{
|
||||
$this->readParentsUsers();
|
||||
$this->readSponsorUser($user_id);
|
||||
}
|
||||
}
|
||||
|
||||
public function initBusinesslUserDetail($user)
|
||||
{
|
||||
$this->business_user = new BusinessUserItem($this->date);
|
||||
$this->business_user->makeUser($user->id);
|
||||
$this->business_user->checkSponsor($user);
|
||||
if(!$this->business_user->isSave()){
|
||||
//Aufbau der Struktur für den User in die unendliche Tiefe.
|
||||
$this->business_user->readParentsBusinessUsers();
|
||||
//calculate Points in Lines
|
||||
if(count($this->business_user->businessUserItems) > 0){
|
||||
$this->calcUserPoints($this->business_user->businessUserItems, 1);
|
||||
}
|
||||
//qualifikation nach qual_kp (KundenPoints) und qual_pp (PaylinePoints)
|
||||
$this->business_user->calcQualPP();
|
||||
}
|
||||
}
|
||||
|
||||
/*public function storeBusinesslUser()
|
||||
{
|
||||
$this->business_user->storeUser();
|
||||
}*/
|
||||
|
||||
public static function isFromStored($month, $year){
|
||||
//when is stored an completed
|
||||
$UserBusinessStructure = UserBusinessStructure::where('year', $year)->where('month', $month)->first();
|
||||
if($UserBusinessStructure && $UserBusinessStructure->completed){
|
||||
return $UserBusinessStructure;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function calcUserPoints($businessUserItems, $line){
|
||||
if(!isset($this->business_user->business_lines[$line])){
|
||||
$obj = new stdClass();
|
||||
$obj->points = 0;
|
||||
$this->business_user->addBusinessLineToUser($line, $obj);
|
||||
}
|
||||
foreach($businessUserItems as $business_user_item){
|
||||
if(count($business_user_item->businessUserItems) > 0){
|
||||
$this->calcUserPoints($business_user_item->businessUserItems, $line+1);
|
||||
}
|
||||
//business_lines points nach line
|
||||
$this->business_user->addBusinessLinePoints($line, $business_user_item->sales_volume_points_TP_sum); //TP + Shop Points
|
||||
//total_pp gesamte Punkte
|
||||
$this->business_user->addTotalTP($business_user_item->sales_volume_points_TP_sum); //TP + Shop Points
|
||||
}
|
||||
}
|
||||
|
||||
public function getGrowthBonus(){
|
||||
if(count($this->business_user->business_lines) > 6){
|
||||
$b_lines = $this->business_user->business_lines->toArray();
|
||||
return array_slice($b_lines, 6);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
public function getKeybyLine($line, $key){
|
||||
if($this->business_user->business_lines){
|
||||
$b_lines = $this->business_user->business_lines;
|
||||
if(isset($b_lines[$line])){
|
||||
if($b_lines[$line] instanceof stdClass){
|
||||
if(isset($b_lines[$line]->{$key})){
|
||||
return $b_lines[$line]->{$key};
|
||||
}
|
||||
}else{
|
||||
if(isset($b_lines[$line][$key])){
|
||||
return $b_lines[$line][$key];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//* reading from current*//
|
||||
private function readRootUsers(){
|
||||
$users = User::with('account')->select('users.*')
|
||||
->where('users.deleted_at', '=', null)
|
||||
->where('users.id', '!=', 1)
|
||||
->where('users.admin', "<", 4)
|
||||
->where('users.m_level', "!=", null)
|
||||
->where('users.m_sponsor', "=", null)
|
||||
->where('users.payment_account', "!=", null)
|
||||
->where('users.active_date', "<=", $this->date->end_date)
|
||||
->get();
|
||||
if($users){
|
||||
foreach($users as $user){
|
||||
$BusinessUserItem = new BusinessUserItem($this->date);
|
||||
$BusinessUserItem->makeUser($user->id);
|
||||
$BusinessUserItem->addUserID();
|
||||
$this->business_users[] = $BusinessUserItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function readParentsUsers(){
|
||||
foreach($this->business_users as $business_user){
|
||||
$business_user->readParentsBusinessUsers();
|
||||
}
|
||||
}
|
||||
|
||||
private function readParentlessUser(){
|
||||
$users = User::with('account')->select('users.*')
|
||||
->where('users.deleted_at', '=', null)
|
||||
->where('users.id', '!=', 1)
|
||||
->where('users.admin', "<", 4)
|
||||
->where('users.payment_account', "!=", null)
|
||||
->where('users.active_date', "<=", $this->date->end_date)
|
||||
->get();
|
||||
|
||||
foreach($users as $user){
|
||||
if(!isset(self::$userIDs[$user->id])){
|
||||
$BusinessUserItem = new BusinessUserItem($this->date);
|
||||
$BusinessUserItem->makeUser($user->id);
|
||||
$this->parentless[] = $BusinessUserItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//* reading from stored*//
|
||||
private function readStoredRootUsers(UserBusinessStructure $userBusinessStructure){
|
||||
//first level is root
|
||||
if($userBusinessStructure->structure){
|
||||
foreach($userBusinessStructure->structure as $obj){
|
||||
$BusinessUserItem = new BusinessUserItem($this->date);
|
||||
$BusinessUserItem->makeUser($obj->user_id);
|
||||
$BusinessUserItem->addUserID();
|
||||
$this->business_users[] = $BusinessUserItem;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function readStoredParentsUsers(UserBusinessStructure $userBusinessStructure){
|
||||
foreach($this->business_users as $business_user){
|
||||
$business_user->readStoredParentsBusinessUsers($userBusinessStructure->structure);
|
||||
}
|
||||
}
|
||||
|
||||
private function readStoredParentlessUser(UserBusinessStructure $userBusinessStructure){
|
||||
if($userBusinessStructure->parentless){
|
||||
foreach($userBusinessStructure->parentless as $obj){
|
||||
if(!isset(self::$userIDs[$obj->user_id])){
|
||||
$BusinessUserItem = new BusinessUserItem($this->date);
|
||||
$BusinessUserItem->makeUser($obj->user_id);
|
||||
$this->parentless[] = $BusinessUserItem;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function readSponsorUser($user_id){
|
||||
$user = User::find($user_id);
|
||||
$userSponsor = User::find($user->m_sponsor);
|
||||
if($userSponsor){
|
||||
$this->sponsor = new BusinessUserItem($this->date);
|
||||
$this->sponsor->makeUser($userSponsor->id);
|
||||
}
|
||||
}
|
||||
|
||||
public function readStoredSponsorUser($user_id){
|
||||
|
||||
$this->sponsor = new BusinessUserItem($this->date);
|
||||
$this->sponsor->makeUser($user_id);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function getItems(){
|
||||
return $this->business_users;
|
||||
}
|
||||
|
||||
public function makeHtmlTree(){
|
||||
$deep = 0;
|
||||
$ret = '<ol class="dd-list">';
|
||||
foreach($this->business_users as $business_user){
|
||||
$ret .= $this->addItem($business_user, $deep);
|
||||
|
||||
}
|
||||
$ret .= '</ol>';
|
||||
return $ret;
|
||||
}
|
||||
|
||||
private function addItem($item, $deep){
|
||||
|
||||
$button = '';
|
||||
|
||||
if(($this->init_from === 'admin' && \Auth::user()->isAdmin()) || ($this->init_from === 'member')){ // && \Auth::user()->id === $item->user_id
|
||||
$button = ' | <button type="button" class="btn icon-btn btn-xs btn-secondary" data-toggle="modal" data-target="#modals-load-content"
|
||||
data-id="'.$item->user_id.'"
|
||||
data-action="business-user-detail"
|
||||
data-back=""
|
||||
data-modal="modal-xl"
|
||||
data-init_from="'.$this->init_from .'"
|
||||
data-route="'.route('modal_load').'"><span class="fa fa-calculator"></span></button>';
|
||||
}
|
||||
return '<li class="dd-item dd-nodrag" data-id="'.$item->user_id.'">'.
|
||||
'<div class="dd-handle">
|
||||
<div class="media align-items-center">
|
||||
<div class="d-flex flex-column justify-content-center align-items-center">
|
||||
'.(($deep > 0) ? '<div class="text-large font-weight-bolder line-height-1 my-2 text-secondary badge badge-outline-secondary">'.$deep.'</div>' : '').'
|
||||
</div>
|
||||
<div class="media-body ml-2">
|
||||
<span class="'.($item->active_account ? '' : 'text-muted').'">
|
||||
<a href="#" class="text-black" data-toggle="modal" data-target="#modals-load-content" data-id="'.$item->user_id.'" data-action="business-user-show" data-back="" data-modal="modal-md" data-init_from="'.$this->init_from .'" data-route="'.route('modal_load').'">
|
||||
<span class="mr-1 ion ion-ios-contact '.($item->active_account ? 'text-primary' : 'text-danger').'"></span> <strong>'.$item->first_name.' '.$item->last_name.'</strong>
|
||||
</a>
|
||||
<a href="mailto: '.$item->email.'">'.$item->email.'</a>
|
||||
'.($item->user_birthday ? ' | <i class="ion ion-ios-gift text-primary"></i> '.$item->user_birthday : '').'
|
||||
'.($item->user_phone ? ' | <i class="ion ion-ios-call text-primary"></i> '.$item->user_phone : '').'
|
||||
<span class="badge badge-outline-default '.($item->active_account ? '' : 'text-muted').'">'.\App\Services\TranslationHelper::transUserLevelName($item->user_level_name).' | '.$item->m_account.'</span>
|
||||
<br><span class="small">'.
|
||||
($item->active_account ?
|
||||
'<strong>'.__('team.total_points').': '.$item->sales_volume_points_KP_sum.'</strong> | '.__('team.e').': '.$item->sales_volume_KP_points.' | '.__('team.s').': '.$item->sales_volume_points_shop.' <strong>
|
||||
| '.__('team.net_turnover').': '.formatNumber($item->sales_volume_total_sum).' €</strong> | '.__('team.e').': '.formatNumber($item->sales_volume_total).' € | '.__('team.s').': '.formatNumber($item->sales_volume_total_shop).' €'.
|
||||
$button
|
||||
:
|
||||
__('team.account_to').': '.$item->payment_account_date).
|
||||
'</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>'.
|
||||
$this->addParentItem($item, $deep).
|
||||
'</li>';
|
||||
|
||||
}
|
||||
|
||||
private function addParentItem($item, $deep){
|
||||
if($item->businessUserItems){
|
||||
$ret = '<ol class="dd-list dd-nodrag">';
|
||||
foreach($item->businessUserItems as $parent){
|
||||
$ret .= $this->addItem($parent, $deep+1);
|
||||
}
|
||||
$ret .='</ol>';
|
||||
return $ret;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
public function isParentless(){
|
||||
return $this->parentless ? true : false;
|
||||
}
|
||||
|
||||
public function makeParentlessHtml(){
|
||||
$ret = "";
|
||||
foreach($this->parentless as $item){
|
||||
$ret .= '<li class="dd-item dd-nodrag" data-id="'.$item->user_id.'">'.
|
||||
'<div class="dd-handle">
|
||||
<span class="'.($item->active_account ? '' : 'text-muted').'">
|
||||
<a href="#" class="text-black" data-toggle="modal" data-target="#modals-load-content" data-id="'.$item->user_id.'" data-action="business-user-show" data-back="" data-modal="modal-md" data-init_from="'.$this->init_from .'" data-route="'.route('modal_load').'">
|
||||
<span class="mr-1 ion ion-ios-contact '.($item->active_account ? 'text-primary' : 'text-danger').'"></span> <strong>'.$item->first_name.' '.$item->last_name.'</strong>
|
||||
</a>
|
||||
<a href="mailto: '.$item->email.'">'.$item->email.'</a>
|
||||
'.($item->user_birthday ? ' | <i class="ion ion-ios-gift text-primary"></i> '.$item->user_birthday : '').'
|
||||
'.($item->user_phone ? ' | <i class="ion ion-ios-call text-primary"></i> '.$item->user_phone : '').'
|
||||
<span class="badge badge-outline-default '.($item->active_account ? '' : 'text-muted').'">'.\App\Services\TranslationHelper::transUserLevelName($item->user_level_name).' | '.$item->m_account.'</span>
|
||||
<br><span class="small">'.
|
||||
($item->active_account ?
|
||||
'<strong>'.__('team.total_points').': '.$item->sales_volume_points_KP_sum.'</strong> | '.__('team.e').': '.$item->sales_volume_KP_points.' | '.__('team.s').': '.$item->sales_volume_points_shop.' <strong>
|
||||
| '.__('team.net_turnover').': '.formatNumber($item->sales_volume_total_sum).' €</strong> | '.__('team.e').': '.formatNumber($item->sales_volume_total).' € | '.__('team.s').': '.formatNumber($item->sales_volume_total_shop).' €'.
|
||||
' | <button type="button" class="btn icon-btn btn-xs btn-secondary" data-toggle="modal" data-target="#modals-load-content"
|
||||
data-id="'.$item->user_id.'"
|
||||
data-action="business-user-detail"
|
||||
data-back=""
|
||||
data-modal="modal-xl"
|
||||
data-route="'.route('modal_load').'"><span class="fa fa-calculator"></span></button>'
|
||||
:
|
||||
__('team.account_to').' '.$item->payment_account_date).
|
||||
'<br>'.$item->m_sponsor_name.
|
||||
'</span>
|
||||
</span>
|
||||
</div>'.
|
||||
'</li>';
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function makeSponsorHtml(){
|
||||
|
||||
if($this->sponsor){
|
||||
//' | <a href="' . route('admin_business_user_detail', [$this->sponsor->id]) . '" class="btn icon-btn btn-xs btn-secondary"><span class="fa fa-calculator"></span></a>'
|
||||
$ret = '<li class="dd-item dd-nodrag" data-id="">'.
|
||||
'<div class="dd-handle">
|
||||
<span class="'.($this->sponsor->active_account ? '' : 'text-muted').'">
|
||||
<a href="#" class="text-black" data-toggle="modal" data-target="#modals-load-content" data-id="'.$this->sponsor->user_id.'" data-action="business-user-show" data-back="" data-init_from="'.$this->init_from .'" data-modal="modal-md" data-route="'.route('modal_load').'">
|
||||
<span class="mr-1 ion ion-ios-contact '.($this->sponsor->active_account ? 'text-primary' : 'text-danger').'"></span> <strong>'.$this->sponsor->first_name.' '.$this->sponsor->last_name.'</strong>
|
||||
</a>
|
||||
<a href="mailto: '.$this->sponsor->email.'">'.$this->sponsor->email.'</a>
|
||||
'.($this->sponsor->user_birthday ? ' | <i class="ion ion-ios-gift text-primary"></i> '.$this->sponsor->user_birthday : '').'
|
||||
'.($this->sponsor->user_phone ? ' | <i class="ion ion-ios-call text-primary"></i> '.$this->sponsor->user_phone : '').'
|
||||
<span class="badge badge-outline-default '.($this->sponsor->active_account ? '' : 'text-muted').'">'.\App\Services\TranslationHelper::transUserLevelName($this->sponsor->user_level_name).' | '.$this->sponsor->m_account.'</span>';
|
||||
|
||||
if($this->init_from === 'admin'){
|
||||
$ret .= '<br><span class="small">'.
|
||||
($this->sponsor->active_account ?
|
||||
'<strong>'.__('team.total_points').': '.$this->sponsor->sales_volume_points_KP_sum.'</strong> | '.__('team.e').': '.$this->sponsor->sales_volume_KP_points.' | '.__('team.s').': '.$this->sponsor->sales_volume_points_shop.' <strong>
|
||||
| '.__('team.net_turnover').': '.formatNumber($this->sponsor->sales_volume_total_sum).' €</strong> | '.__('team.e').': '.formatNumber($this->sponsor->sales_volume_total).' € | '.__('team.s').': '.formatNumber($this->sponsor->sales_volume_total_shop).' €'
|
||||
:
|
||||
__('team.account_to').' '.$this->sponsor->payment_account_date).
|
||||
'</span>';
|
||||
}
|
||||
$ret .= '</span>
|
||||
</div>
|
||||
</li>';
|
||||
|
||||
return $ret;
|
||||
}
|
||||
return __('team.no_sponsor_assigned');
|
||||
}
|
||||
|
||||
}
|
||||
1271
dev/buinessPlan/_bak/BusinessUserItemOptimized.php
Normal file
1271
dev/buinessPlan/_bak/BusinessUserItemOptimized.php
Normal file
File diff suppressed because it is too large
Load diff
399
dev/buinessPlan/_bak/Growth-Bonus.md
Normal file
399
dev/buinessPlan/_bak/Growth-Bonus.md
Normal file
|
|
@ -0,0 +1,399 @@
|
|||
# Funktionsweise: Tiefenbonus (Growth Bonus)
|
||||
|
||||
## ⚠️ WICHTIG: Bug-Fix November 2025
|
||||
|
||||
### Das Problem (vor November 2025)
|
||||
|
||||
Die Payline-Prozentsätze (`pr_line_1` bis `pr_line_6`) in der Datenbank enthielten **bereits den Growth Bonus**.
|
||||
|
||||
**Beispiel Gold Member (falsche Berechnung):**
|
||||
|
||||
| Ebene | Wert in DB (`pr_line_X`) | Was ausgezahlt wurde | Was korrekt gewesen wäre |
|
||||
| ------- | ------------------------ | -------------------- | ------------------------------ |
|
||||
| Ebene 1 | 9% | 9% | 7% Payline + 2% Growth = 9% |
|
||||
| Ebene 2 | 9% | 9% | 7% Payline + 2% Growth = 9% |
|
||||
| Ebene 3 | 9% | 9% | 7% Payline + 2% Growth = 9% |
|
||||
| Ebene 4 | 6% | 6% | 4% Payline + 2% Growth = 6% |
|
||||
| Ebene 5 | 4% | 4% | 2% Payline + 2% Growth = 4% |
|
||||
| Ebene 6 | 4% | 4% | 2% Payline + 2% Growth = 4% |
|
||||
| Ebene 7 | - | 2% (Growth nochmal!) | 2% Growth (nur mit Differenz!) |
|
||||
|
||||
**Problem:** Der Growth Bonus wurde **doppelt gezählt**:
|
||||
|
||||
1. Einmal IN den Payline-Prozentsätzen (pr_line_1 = 9% statt 7%)
|
||||
2. Nochmal SEPARAT auf Ebenen ab 7+ (Legacy-Berechnung)
|
||||
|
||||
### Die Lösung (ab November 2025)
|
||||
|
||||
1. **Payline-Prozentsätze korrigiert:** `pr_line_X` enthält NUR den Payline-Anteil
|
||||
2. **Growth Bonus separat:** Wird mit Differenz-Logik berechnet
|
||||
3. **Einmal pro Bein:** Growth Bonus wird nur EINMAL pro Firstline-Zweig ausgezahlt
|
||||
|
||||
**Beispiel Gold Member (korrekte Berechnung):**
|
||||
|
||||
| Ebene | Payline (`pr_line_X`) | Growth Bonus (separat) | Gesamt |
|
||||
| -------- | --------------------- | ---------------------- | ------ |
|
||||
| Ebene 1 | 7% | +2% (Differenz-Logik) | 9% |
|
||||
| Ebene 2 | 7% | +2% | 9% |
|
||||
| Ebene 3 | 7% | +2% | 9% |
|
||||
| Ebene 4 | 4% | +2% | 6% |
|
||||
| Ebene 5 | 2% | +2% | 4% |
|
||||
| Ebene 6 | 2% | +2% | 4% |
|
||||
| Ebene 7+ | - | +2% (Differenz-Logik) | 2% |
|
||||
|
||||
**Wichtig:** Der Growth Bonus wird NUR ausgezahlt, wenn kein gleichrangiger oder höherer Partner in der Downline ist (Differenz-Berechnung)!
|
||||
|
||||
---
|
||||
|
||||
## Differenz-Logik (ab November 2025)
|
||||
|
||||
Der Tiefenbonus ist ein **Differenz-Bonus**, der **sofort ab der 1. Ebene** beginnt.
|
||||
|
||||
Es gilt das Prinzip: **"Jeder Partner schützt sein eigenes Team-Volumen."**
|
||||
|
||||
### 1. Die Grundregel
|
||||
|
||||
- **Start:** Der Bonus berechnet sich auf Points ab der **1. Ebene** (direkte Downline).
|
||||
- **Anspruch:** Ein Partner erhält seinen Status-Prozentsatz auf alle Points in seiner Linie, **bis** er auf einen Partner trifft, der selbst einen Status-Anspruch hat.
|
||||
- **Blockade:** Sobald ein Partner in der Downline einen Anspruch hat, zieht er diesen von der Upline ab (Differenz-Rechnung).
|
||||
- **⚠️ WICHTIG - Erreichtes Qualifikations-Level:** Die Blockade erfolgt NUR basierend auf dem **in dem Monat tatsächlich erreichten Level** (`qual_user_level`), NICHT auf dem aktuellen Karriere-Level des Partners!
|
||||
|
||||
### 1.1 Erreichte Qualifikation vs. Aktuelles Level
|
||||
|
||||
Ein Partner kann ein bestimmtes Karriere-Level (z.B. Gold) haben, aber in einem Monat die Qualifikationsvoraussetzungen nicht erfüllen. In diesem Fall:
|
||||
|
||||
| Situation | Aktuelles Level | Erreicht in Monat | Blockiert mit |
|
||||
| --------- | --------------- | ----------------- | ------------- |
|
||||
| Fall A | Gold (2%) | Gold qualifiziert | 2% ✅ |
|
||||
| Fall B | Gold (2%) | Team Leader (0%) | 0% ❌ |
|
||||
| Fall C | Team Leader | Silber (1.5%) | 1.5% ✅ |
|
||||
|
||||
**Technische Umsetzung:**
|
||||
|
||||
- Die Methode `getQualifiedGrowthBonus()` in `BusinessUserItemOptimized` gibt den Growth Bonus basierend auf dem **erreichten Qualifikations-Level** (`qual_user_level`) zurück.
|
||||
- Die alte Methode `getActiveGrowthBonus()` gibt den Growth Bonus basierend auf dem **aktuellen Karriere-Level** zurück (NUR für Legacy-Berechnungen!).
|
||||
- Der `GrowthBonusCalculator` verwendet ab November 2025 ausschließlich `getQualifiedGrowthBonus()`.
|
||||
|
||||
---
|
||||
|
||||
### 2. Die Differenz (Der Normalfall)
|
||||
|
||||
Points entstehen irgendwo im Team von **Partner B** (egal ob in B's Ebene 1 oder B's Ebene 50).
|
||||
|
||||
**Die Verteilung:**
|
||||
|
||||
1. **Sicht Partner B (Silber):**
|
||||
|
||||
- Er hat Anspruch auf **1,5 %** auf sein gesamtes Team.
|
||||
- Da unter ihm (Partner C) niemand einen Status hat, der etwas wegnehmen könnte, erhält B die vollen **1,5 %**.
|
||||
- Damit sind 1,5 % des "Kuchens" verteilt.
|
||||
|
||||
2. **Sicht Partner A (Diamant):**
|
||||
- Du hast Anspruch auf **2,5 %**.
|
||||
- Du schaust auf die Linie von Partner B.
|
||||
- Partner B hat den Status Silber und beansprucht damit **1,5 %** für sich und sein ganzes Team.
|
||||
- **Deine Rechnung:** 2,5 % (Dein Anspruch) - 1,5 % (Anspruch B) = **1,0 %**.
|
||||
- **Ergebnis:** Du erhältst auf das gesamte Volumen unter Partner B exakt **1,0 %**.
|
||||
|
||||
---
|
||||
|
||||
### 2. Das "GAP" (Die direkte Ebene)
|
||||
|
||||
Da der Bonus ab Ebene 1 beginnt, entsteht das GAP (die Auszahlung trotz gleichem Rang) immer am **Eigenumsatz des Partners**:
|
||||
|
||||
- **Partner A** (Diamant, 2,5 %) ist Sponsor von **Partner B** (Diamant, 2,5 %).
|
||||
- **Punkte von B (Eigenbestellung/Kunden):**
|
||||
- Partner B erhält darauf _keinen_ Tiefenbonus (man kriegt keinen Tiefenbonus auf sich selbst).
|
||||
- Partner B zieht also **0 %** vom Topf ab.
|
||||
- **Partner A erhält die vollen 2,5 % auf die Punkte von B.**
|
||||
- **Punkte UNTER B (Team von B):**
|
||||
- Partner B greift hier zu (Start ab Ebene 1) und nimmt sich **2,5 %**.
|
||||
- Partner A rechnet: 2,5 % - 2,5 % = **0 %**.
|
||||
- **Partner A ist hier blockiert.**
|
||||
|
||||
> Fazit: Bei gleichem Rang verdient man nur an den direkten Points des Partners (GAP), aber nicht mehr an dessen Team.
|
||||
|
||||
---
|
||||
|
||||
### 3. Das Szenario (A -> B -> F)
|
||||
|
||||
Wir schauen uns deine Struktur mit 3 Diamanten in einer Linie an. Alle haben Anspruch auf **2,5 %**.
|
||||
|
||||
- **Partner A** (Ebene 1)
|
||||
- **Partner B** (Ebene 2, direkt unter A)
|
||||
- ... dazwischen Berater ohne Status ...
|
||||
- **Partner F** (Ebene 6, unter B)
|
||||
- ... Punkte entstehen unter F ...
|
||||
|
||||
### Bereich 1: Punkte von Partner B
|
||||
|
||||
- Das ist für **A** die Ebene 1.
|
||||
- B blockiert nicht (da Eigenumsatz).
|
||||
- **Ergebnis:** **A erhält 2,5 %**.
|
||||
|
||||
### Bereich 2: Punkte ZWISCHEN B und F (Ebene 3 bis 6)
|
||||
|
||||
- Hier entstehen Punkte im Team von B.
|
||||
- **Sicht B:** Er ist qualifiziert (Start ab Ebene 1). Er nimmt sich **2,5 %**.
|
||||
- **Sicht A:** Er hat Anspruch auf 2,5 %. B hat aber schon 2,5 % genommen. Differenz = 0 %.
|
||||
- **Ergebnis:** **B erhält 2,5 %**. A geht leer aus.
|
||||
|
||||
### Bereich 3: Punkte von Partner F
|
||||
|
||||
- Das ist für **B** eine Ebene in seiner Downline.
|
||||
- F blockiert hier noch nicht (Eigenumsatz).
|
||||
- **Ergebnis:** **B erhält 2,5 %** auf die Punkte von F.
|
||||
|
||||
### Bereich 4: Punkte UNTER F (ab Ebene 7)
|
||||
|
||||
- Hier entstehen Punkte im Team von F.
|
||||
- **Sicht F:** Er ist qualifiziert (Start ab Ebene 1). Er nimmt sich **2,5 %**.
|
||||
- **Sicht B:** Anspruch 2,5 %. F hat schon 2,5 % genommen. Differenz = 0 %.
|
||||
- **Sicht A:** Anspruch 2,5 %. B (und F) haben alles genommen. Differenz = 0 %.
|
||||
- **Ergebnis:** **F erhält 2,5 %**. B und A gehen leer aus.
|
||||
|
||||
---
|
||||
|
||||
### 4. Zusammenfassung für die IT-Logik
|
||||
|
||||
1. **Trigger:** Ein Umsatz (Points) entsteht bei User X.
|
||||
2. **Schleife:** Gehe die Upline hoch (Sponsor -> Sponsor...).
|
||||
3. **Prüfung:**
|
||||
- Hat der Upline-Partner einen Status? (z.B. Diamant).
|
||||
- (Keine Prüfung auf Ebene mehr nötig, da Start immer ab Ebene 1).
|
||||
4. **Rechnung:**
|
||||
- Auszahlung = Mein %-Satz - Bereits verteilter %-Satz.
|
||||
- Wenn Auszahlung > 0: Speichern.
|
||||
- Setze `Bereits verteilter %-Satz` auf den neuen Wert (also `Mein %-Satz`).
|
||||
|
||||
---
|
||||
|
||||
## Code-Implementierung
|
||||
|
||||
Diese Implementierung nutzt eine **rekursive Aggregation von Volumen nach "Schutz-Level"**.
|
||||
Anstatt für jede Transaktion die Upline hochzulaufen ("Push"), holt sich der User die aggregierten Volumina seiner Downline gruppiert nach dem bereits beanspruchten Prozentsatz ("Pull").
|
||||
|
||||
### A. Neue Methode `getVolumeByProtectionLevel()`
|
||||
|
||||
Diese Methode liefert ein Array zurück, das das Volumen nach "bereits verteiltem Prozentsatz" gruppiert.
|
||||
Format: `['0.0' => 1000, '1.5' => 5000, ...]`
|
||||
|
||||
```php
|
||||
/**
|
||||
* Liefert das Volumen der Downline gruppiert nach dem "bereits verteilten Prozentsatz" (Protection Level).
|
||||
* Rekursive Funktion, die die "Differenz-Logik" vorbereitet.
|
||||
*
|
||||
* @return array<string, float> Key = Protected Percent, Value = Volume Points
|
||||
*/
|
||||
public function getVolumeByProtectionLevel(): array
|
||||
{
|
||||
$volumes = [];
|
||||
|
||||
// 1. Eigenes Volumen (Unprotected / GAP)
|
||||
// Man selbst schützt seinen eigenen Umsatz NICHT vor der Upline.
|
||||
// Daher Start mit Protection Level 0.0 (oder dem was von unten kommt, aber hier ist es ja Eigenumsatz)
|
||||
|
||||
// WICHTIG: Wir nutzen das Feld, das auch TreeCalcBot für die Punkte nutzt
|
||||
// sales_volume_points_TP_sum scheint in der DB/Model Logik für das relevante Volumen zu stehen
|
||||
$ownVolume = (float) ($this->b_user->sales_volume_points_TP_sum ?? 0);
|
||||
|
||||
if ($ownVolume > 0) {
|
||||
$key = '0.0';
|
||||
if (!isset($volumes[$key])) $volumes[$key] = 0.0;
|
||||
$volumes[$key] += $ownVolume;
|
||||
}
|
||||
|
||||
// 2. Mein Schutz-Level ermitteln
|
||||
// Das ist der Prozentsatz, den ICH auf mein Team beanspruche.
|
||||
// Alles Volumen, das durch MICH hindurch zur Upline fließt, hat mindestens diesen Schutz-Level.
|
||||
$myProtectionPercent = 0.0;
|
||||
if ($this->isQualLevel()) {
|
||||
$qual = $this->b_user->qual_user_level;
|
||||
if (!empty($qual['growth_bonus'])) {
|
||||
$myProtectionPercent = (float) $qual['growth_bonus'];
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Kinder verarbeiten
|
||||
if (!empty($this->businessUserItems)) {
|
||||
foreach ($this->businessUserItems as $childItem) {
|
||||
|
||||
// Rekursion: Hol dir die Volumen-Töpfe aus der Downline
|
||||
// Hinweis: Hier muss sichergestellt sein, dass die Kinder geladen sind.
|
||||
// initBusinesslUserDetail lädt normalerweise die Struktur.
|
||||
|
||||
// Falls Kinder nicht geladen sind, müssten sie hier theoretisch geladen werden.
|
||||
// Wir gehen davon aus, dass die Struktur bereits rekursiv via readParentsBusinessUsers geladen wurde.
|
||||
|
||||
$childVolumes = $childItem->getVolumeByProtectionLevel();
|
||||
|
||||
// 4. Schutz-Level anwenden (Aggregation)
|
||||
foreach ($childVolumes as $protectedPercentStr => $vol) {
|
||||
$incomingProtection = (float) $protectedPercentStr;
|
||||
|
||||
// Das Volumen ist bereits mit $incomingProtection geschützt.
|
||||
// Da es nun durch MICH fließt, erhöht sich der Schutz auf MEINEN Level (falls meiner höher ist).
|
||||
$effectiveProtection = max($incomingProtection, $myProtectionPercent);
|
||||
|
||||
$newKey = (string) $effectiveProtection;
|
||||
|
||||
if (!isset($volumes[$newKey])) $volumes[$newKey] = 0.0;
|
||||
$volumes[$newKey] += $vol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $volumes;
|
||||
}
|
||||
```
|
||||
|
||||
### B. Neue Methode `calculateGrowthBonusRecursive()`
|
||||
|
||||
Diese Methode ersetzt die bisherige Berechnung und nutzt die oben definierte Aggregation.
|
||||
|
||||
```php
|
||||
/**
|
||||
* Berechnet den Growth Bonus (Tiefenbonus) basierend auf der Differenz-Logik.
|
||||
*/
|
||||
private function calculateGrowthBonusRecursive($qualUserLevel): float
|
||||
{
|
||||
if (empty($qualUserLevel->growth_bonus) || $qualUserLevel->growth_bonus <= 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$myGrowthPercent = (float) $qualUserLevel->growth_bonus;
|
||||
$totalGrowthBonus = 0.0;
|
||||
|
||||
// Wir iterieren über alle direkten Beine (Firstlines)
|
||||
foreach ($this->businessUserItems as $childItem) {
|
||||
|
||||
// Volumen-Verteilung aus diesem Bein abrufen
|
||||
// Das Kind liefert uns: "Hier sind 1000 Punkte geschützt mit 0%, 5000 Punkte geschützt mit 1.5%"
|
||||
$volumeDistribution = $childItem->getVolumeByProtectionLevel();
|
||||
|
||||
foreach ($volumeDistribution as $protectedPercentStr => $volume) {
|
||||
$alreadyDistributedPercent = (float) $protectedPercentStr;
|
||||
|
||||
// Differenz berechnen
|
||||
// Mein Anspruch MINUS was schon verteilt wurde
|
||||
$mySharePercent = max(0, $myGrowthPercent - $alreadyDistributedPercent);
|
||||
|
||||
if ($mySharePercent > 0) {
|
||||
$commission = round($volume / 100 * $mySharePercent, 2);
|
||||
$totalGrowthBonus += $commission;
|
||||
|
||||
// Optional Logging
|
||||
// \Log::debug("Growth Bonus: User {$this->b_user->user_id} earns {$mySharePercent}% on {$volume} pts (Protected: {$alreadyDistributedPercent}%) from leg {$childItem->b_user->user_id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $totalGrowthBonus;
|
||||
}
|
||||
```
|
||||
|
||||
### C. Integration in `calculateCommissions`
|
||||
|
||||
```php
|
||||
private function calculateCommissions($qualUserLevel): void
|
||||
{
|
||||
$commission_pp_total = 0;
|
||||
|
||||
// 1. Normale Unilevel Provision (Payline) - NUR pr_line_X Werte
|
||||
for ($i = 1; $i <= $qualUserLevel->paylines; $i++) {
|
||||
if (isset($this->b_user->business_lines[$i])) {
|
||||
$object = $this->b_user->business_lines[$i];
|
||||
$margin = (float) $this->b_user->qual_user_level['pr_line_' . $i];
|
||||
|
||||
$points = is_array($object) ? ((float)($object['points'] ?? 0)) : ((float)($object->points ?? 0));
|
||||
|
||||
$commission = round($points / 100 * $margin, 2);
|
||||
$commission_pp_total += $commission;
|
||||
|
||||
// Rückschreiben
|
||||
if (is_array($object)) {
|
||||
$object['margin'] = $margin;
|
||||
$object['commission'] = $commission;
|
||||
$object['payline'] = true;
|
||||
} else {
|
||||
$object->margin = $margin;
|
||||
$object->commission = $commission;
|
||||
$object->payline = true;
|
||||
}
|
||||
$this->b_user->business_lines[$i] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Growth Bonus - Unterscheidung Legacy vs. Neu
|
||||
$commission_growth_total = 0;
|
||||
|
||||
if (!empty($qualUserLevel->growth_bonus)) {
|
||||
// Stichtag: 01.11.2025
|
||||
$isLegacy = ($this->date->year < 2025) ||
|
||||
($this->date->year == 2025 && $this->date->month < 11);
|
||||
|
||||
if ($isLegacy) {
|
||||
// ALT: Pauschal ab Ebene paylines+1 (FALSCH - doppelte Auszahlung!)
|
||||
$commission_growth_total = $this->calculateLegacyGrowthBonus($qualUserLevel);
|
||||
} else {
|
||||
// NEU: Differenz-Logik via GrowthBonusCalculator
|
||||
$commission_growth_total = $this->calculateGrowthBonusRecursive($qualUserLevel);
|
||||
}
|
||||
}
|
||||
|
||||
$this->b_user->commission_pp_total = $commission_pp_total;
|
||||
$this->b_user->commission_growth_total = $commission_growth_total;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Legacy-Berechnung (vor November 2025) - DEPRECATED
|
||||
|
||||
**⚠️ Diese Logik war FALSCH und führte zu doppelter Auszahlung!**
|
||||
|
||||
```php
|
||||
/**
|
||||
* ALT: Pauschal Growth Bonus ab Ebene paylines+1
|
||||
* PROBLEM: Growth Bonus war bereits in pr_line_X enthalten!
|
||||
*/
|
||||
private function calculateLegacyGrowthBonus($qualUserLevel): float
|
||||
{
|
||||
$commission_growth_total = 0;
|
||||
|
||||
// Start ab Ebene paylines+1 (z.B. 7 bei Gold)
|
||||
$payline = (int) ($this->b_user->qual_user_level['paylines'] ?? 0) + 1;
|
||||
$maxlines = count($this->b_user->business_lines ?? []) + 1;
|
||||
$growth_bonus = (float) ($this->b_user->qual_user_level['growth_bonus'] ?? 0);
|
||||
|
||||
// Auf JEDE Ebene ab payline wird der volle Growth Bonus gezahlt
|
||||
// OHNE Differenz-Prüfung = FALSCH!
|
||||
for ($i = $payline; $i <= $maxlines; $i++) {
|
||||
if (isset($this->b_user->business_lines[$i])) {
|
||||
$points = $this->b_user->business_lines[$i]['points'] ?? 0;
|
||||
$commission = round($points / 100 * $growth_bonus, 2);
|
||||
$commission_growth_total += $commission;
|
||||
}
|
||||
}
|
||||
|
||||
return $commission_growth_total;
|
||||
}
|
||||
```
|
||||
|
||||
**Warum war das falsch?**
|
||||
|
||||
1. `pr_line_1` bei Gold = 9% (enthielt bereits 2% Growth Bonus)
|
||||
2. Growth Bonus wurde ab Ebene 7 NOCHMAL mit 2% berechnet
|
||||
3. = **Doppelte Auszahlung** auf tieferen Ebenen
|
||||
|
||||
---
|
||||
|
||||
## Neue Berechnung (ab November 2025) - KORREKT
|
||||
|
||||
Der `GrowthBonusCalculator` verwendet die Differenz-Logik:
|
||||
|
||||
1. **Aggregation:** Sammelt Volumen gruppiert nach "Schutz-Level"
|
||||
2. **Differenz:** Berechnet nur die Differenz (mein Anspruch - bereits verteilt)
|
||||
3. **Einmal pro Bein:** Growth Bonus wird nur einmal pro Firstline-Zweig ausgezahlt
|
||||
|
||||
Siehe `GrowthBonusCalculator.php` für die Implementation.
|
||||
318
dev/buinessPlan/_bak/GrowthBonusCalculator.php
Normal file
318
dev/buinessPlan/_bak/GrowthBonusCalculator.php
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\BusinessPlan;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Service für die Berechnung des Growth Bonus (Tiefenbonus)
|
||||
* Implementiert die Differenz-Bonus-Logik ab Ebene 1
|
||||
*/
|
||||
class GrowthBonusCalculator
|
||||
{
|
||||
/**
|
||||
* Berechnet den Growth Bonus für einen BusinessUserItemOptimized
|
||||
*
|
||||
* @param BusinessUserItemOptimized $userItem Der User, für den der Bonus berechnet wird
|
||||
* @param object $qualUserLevel Das Qualifikations-Level-Objekt des Users
|
||||
* @return float Der berechnete Bonus
|
||||
*/
|
||||
public function calculate(BusinessUserItemOptimized $userItem, $qualUserLevel): float
|
||||
{
|
||||
// Basis-Check: Hat der User überhaupt Anspruch auf Growth Bonus?
|
||||
if (empty($qualUserLevel->growth_bonus) || $qualUserLevel->growth_bonus <= 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Falls keine direkte Downline-Struktur geladen ist, kann kein Growth Bonus berechnet werden
|
||||
if (empty($userItem->businessUserItems) && !empty($userItem->business_lines)) {
|
||||
Log::warning("GrowthBonusCalculator: Growth Bonus calculation requires loaded child structure (businessUserItems is empty for user {$userItem->user_id})");
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return $this->calculateRecursive($userItem, $qualUserLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Führt die eigentliche Berechnung basierend auf der Differenz-Logik durch
|
||||
*/
|
||||
private function calculateRecursive(BusinessUserItemOptimized $userItem, $qualUserLevel): float
|
||||
{
|
||||
$myGrowthPercent = (float) $qualUserLevel->growth_bonus;
|
||||
$totalGrowthBonus = 0.0;
|
||||
|
||||
// Iteriere über alle direkten Beine (Firstlines)
|
||||
foreach ($userItem->businessUserItems as $childItem) {
|
||||
|
||||
// Hole die Volumen-Verteilung aus diesem Bein
|
||||
// Array-Format: ['0.0' => 1000, '1.5' => 5000]
|
||||
// Bedeutung: 1000 Punkte sind mit 0% geschützt, 5000 Punkte mit 1.5%
|
||||
$volumeDistribution = $this->getVolumeByProtectionLevel($childItem);
|
||||
|
||||
foreach ($volumeDistribution as $protectedPercentStr => $volume) {
|
||||
$alreadyDistributedPercent = (float) $protectedPercentStr;
|
||||
|
||||
// Differenz berechnen: Mein Anspruch MINUS was schon verteilt wurde
|
||||
$mySharePercent = max(0, $myGrowthPercent - $alreadyDistributedPercent);
|
||||
|
||||
if ($mySharePercent > 0) {
|
||||
$commission = round($volume / 100 * $mySharePercent, 2);
|
||||
$totalGrowthBonus += $commission;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $totalGrowthBonus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Liefert detaillierte Informationen zur Berechnung für die Anzeige
|
||||
*
|
||||
* @return array Detaillierte Aufschlüsselung pro Bein
|
||||
*/
|
||||
public function getCalculationDetails(BusinessUserItemOptimized $userItem, $qualUserLevel): array
|
||||
{
|
||||
$details = [];
|
||||
if (empty($qualUserLevel->growth_bonus) || $qualUserLevel->growth_bonus <= 0) {
|
||||
return $details;
|
||||
}
|
||||
|
||||
$myGrowthPercent = (float) $qualUserLevel->growth_bonus;
|
||||
|
||||
// Iteriere über alle direkten Beine (Firstlines)
|
||||
foreach ($userItem->businessUserItems as $childItem) {
|
||||
$legDetails = [
|
||||
'user_id' => $childItem->user_id,
|
||||
'first_name' => $childItem->first_name,
|
||||
'last_name' => $childItem->last_name,
|
||||
'level_name' => $childItem->user_level_name,
|
||||
'volume_distribution' => [],
|
||||
'total_commission' => 0.0,
|
||||
'total_volume' => 0.0
|
||||
];
|
||||
|
||||
$volumeDistribution = $this->getVolumeByProtectionLevel($childItem);
|
||||
|
||||
foreach ($volumeDistribution as $protectedPercentStr => $volume) {
|
||||
$alreadyDistributedPercent = (float) $protectedPercentStr;
|
||||
$mySharePercent = max(0, $myGrowthPercent - $alreadyDistributedPercent);
|
||||
$commission = 0.0;
|
||||
|
||||
if ($mySharePercent > 0) {
|
||||
$commission = round($volume / 100 * $mySharePercent, 2);
|
||||
}
|
||||
|
||||
$legDetails['volume_distribution'][] = [
|
||||
'protected_percent' => $alreadyDistributedPercent,
|
||||
'volume' => $volume,
|
||||
'my_share_percent' => $mySharePercent,
|
||||
'commission' => $commission
|
||||
];
|
||||
|
||||
$legDetails['total_commission'] += $commission;
|
||||
$legDetails['total_volume'] += $volume;
|
||||
}
|
||||
|
||||
// Sortiere nach Protection Level
|
||||
usort($legDetails['volume_distribution'], function ($a, $b) {
|
||||
return $a['protected_percent'] <=> $b['protected_percent'];
|
||||
});
|
||||
|
||||
if ($legDetails['total_volume'] > 0) {
|
||||
$details[] = $legDetails;
|
||||
}
|
||||
}
|
||||
|
||||
// Sortiere Beine nach höchster Provision
|
||||
usort($details, function ($a, $b) {
|
||||
return $b['total_commission'] <=> $a['total_commission'];
|
||||
});
|
||||
|
||||
return $details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Liefert eine Matrix-Sicht für die detaillierte Darstellung
|
||||
* Zeilen = Beine (Legs), Spalten = Ebenen (Levels)
|
||||
*/
|
||||
public function getMatrixDetails(BusinessUserItemOptimized $userItem, $qualUserLevel): array
|
||||
{
|
||||
$details = [];
|
||||
if (empty($qualUserLevel->growth_bonus) || $qualUserLevel->growth_bonus <= 0) {
|
||||
return $details;
|
||||
}
|
||||
|
||||
$myGrowthPercent = (float) $qualUserLevel->growth_bonus;
|
||||
|
||||
foreach ($userItem->businessUserItems as $childItem) {
|
||||
|
||||
$legData = [
|
||||
'user' => [
|
||||
'id' => $childItem->user_id,
|
||||
'name' => $childItem->first_name . ' ' . $childItem->last_name,
|
||||
'level' => $childItem->user_level_name
|
||||
],
|
||||
'levels' => [],
|
||||
'total_commission' => 0.0,
|
||||
'total_volume' => 0.0
|
||||
];
|
||||
|
||||
// Rekursiv die Ebenen dieses Beins einsammeln
|
||||
// Start bei Ebene 1 (das ist das Kind selbst)
|
||||
// Initial Protection ist 0 (vom Upline/Mir kommt kein Schutz, der relevant wäre, da ICH ja der Empfänger bin)
|
||||
$this->collectLegLevels($childItem, 1, 0.0, $myGrowthPercent, $legData);
|
||||
|
||||
if (!empty($legData['levels'])) {
|
||||
// Sortieren nach Ebenen-Index
|
||||
ksort($legData['levels']);
|
||||
$details[] = $legData;
|
||||
}
|
||||
}
|
||||
|
||||
// Sortieren nach Gesamt-Provision
|
||||
usort($details, function ($a, $b) {
|
||||
return $b['total_commission'] <=> $a['total_commission'];
|
||||
});
|
||||
|
||||
return $details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rekursive Hilfsfunktion für Matrix-Daten
|
||||
*/
|
||||
private function collectLegLevels(BusinessUserItemOptimized $item, int $level, float $incomingProtection, float $myPercent, array &$legData)
|
||||
{
|
||||
// 1. Eigenen Status ermitteln (Schutz für Downline)
|
||||
// WICHTIG: Nutze getQualifiedGrowthBonus() für das ERREICHTE Qualifikations-Level des Monats
|
||||
// Nicht getActiveGrowthBonus() verwenden, da das das aktuelle Karriere-Level wäre!
|
||||
$userProtection = $item->getQualifiedGrowthBonus();
|
||||
$userLevelName = '';
|
||||
|
||||
if ($userProtection > 0) {
|
||||
$qual = $item->getQualUserLevel();
|
||||
$userLevelName = is_array($qual) ? ($qual['name'] ?? '') : ($qual->name ?? '');
|
||||
}
|
||||
|
||||
// Berechnung für diesen User (Ebene)
|
||||
$volume = (float) ($item->sales_volume_points_TP_sum ?? 0);
|
||||
|
||||
// Auch User ohne Volumen in die Matrix aufnehmen, wenn sie einen Status haben (Blocker sichtbar machen)
|
||||
// Aber wir brauchen Volumen für die Relevanz. Wenn Volumen 0, dann ist der Block hier (noch) egal,
|
||||
// wirkt aber auf die Ebenen darunter.
|
||||
|
||||
if ($volume > 0 || $userProtection > 0) {
|
||||
// WICHTIG: Der effektive Schutz ist das MAXIMUM aus:
|
||||
// - Schutz von oben ($incomingProtection)
|
||||
// - Eigener Schutz des Users ($userProtection)
|
||||
// Der User schützt sein EIGENES Volumen mit seinem eigenen Growth Bonus!
|
||||
$effectiveProtection = max($incomingProtection, $userProtection);
|
||||
$diffPercent = max(0, $myPercent - $effectiveProtection);
|
||||
$commission = round($volume / 100 * $diffPercent, 2);
|
||||
|
||||
if (!isset($legData['levels'][$level])) {
|
||||
$legData['levels'][$level] = [
|
||||
'volume' => 0.0,
|
||||
'commission' => 0.0,
|
||||
'details' => [],
|
||||
'has_blocker' => false, // Flag für UI
|
||||
'blocker_name' => ''
|
||||
];
|
||||
}
|
||||
|
||||
$legData['levels'][$level]['volume'] += $volume;
|
||||
$legData['levels'][$level]['commission'] += $commission;
|
||||
|
||||
// Markiere Blocker
|
||||
if ($userProtection > 0) {
|
||||
$legData['levels'][$level]['has_blocker'] = true;
|
||||
$legData['levels'][$level]['blocker_name'] = $userLevelName . ' (' . $userProtection . '%)';
|
||||
}
|
||||
|
||||
// Detail-Information für Hover/Debug
|
||||
$legData['levels'][$level]['details'][] = [
|
||||
'u' => $item->user_id,
|
||||
'n' => $item->first_name . ' ' . $item->last_name, // Name für Tooltip
|
||||
'v' => $volume,
|
||||
'p_in' => $incomingProtection,
|
||||
'p_own' => $userProtection,
|
||||
'pct' => $diffPercent
|
||||
];
|
||||
|
||||
$legData['total_volume'] += $volume;
|
||||
$legData['total_commission'] += $commission;
|
||||
}
|
||||
|
||||
// Protection für nächste Ebene: Maximum aus was von oben kam und was dieser User beansprucht
|
||||
$nextProtection = max($incomingProtection, $userProtection);
|
||||
|
||||
// Rekursion (Begrenzt auf 30 Ebenen für Anzeige)
|
||||
if ($level < 30 && !empty($item->businessUserItems)) {
|
||||
foreach ($item->businessUserItems as $child) {
|
||||
$this->collectLegLevels($child, $level + 1, $nextProtection, $myPercent, $legData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Liefert das Volumen der Downline eines Users gruppiert nach dem "bereits verteilten Prozentsatz" (Protection Level).
|
||||
* Rekursive Funktion, die die "Differenz-Logik" vorbereitet.
|
||||
*
|
||||
* @param BusinessUserItemOptimized $item
|
||||
* @return array<string, float> Key = Protected Percent, Value = Volume Points
|
||||
*/
|
||||
public function getVolumeByProtectionLevel(BusinessUserItemOptimized $item): array
|
||||
{
|
||||
// WICHTIG: Nur calcQualPP aufrufen wenn KEINE gespeicherten Daten vorhanden sind
|
||||
// Bei gespeicherten Daten ist qual_user_level bereits vorhanden, auch wenn qualificationCalculated=false
|
||||
if (!$item->isQualificationCalculated() && !$item->isQualLevel()) {
|
||||
$item->calcQualPP();
|
||||
}
|
||||
|
||||
$volumes = [];
|
||||
|
||||
// 1. Eigenes Volumen (Unprotected / GAP)
|
||||
// Man selbst schützt seinen eigenen Umsatz NICHT vor der Upline.
|
||||
// Daher Start mit Protection Level 0.0
|
||||
$ownVolume = (float) ($item->sales_volume_points_TP_sum ?? 0);
|
||||
|
||||
if ($ownVolume > 0) {
|
||||
$key = '0.0';
|
||||
$volumes[$key] = $ownVolume;
|
||||
}
|
||||
|
||||
// 2. Mein Schutz-Level ermitteln (für das Volumen, das durch mich hindurch fließt)
|
||||
// WICHTIG: Nutze getQualifiedGrowthBonus() für das ERREICHTE Qualifikations-Level des Monats
|
||||
// Nicht getActiveGrowthBonus() verwenden, da das das aktuelle Karriere-Level wäre!
|
||||
$myProtectionPercent = $item->getQualifiedGrowthBonus();
|
||||
|
||||
// Debug-Logging für Troubleshooting
|
||||
Log::debug("GrowthBonusCalculator: User {$item->user_id} - qualifiedGrowthBonus={$myProtectionPercent}%, activeGrowthBonus={$item->getActiveGrowthBonus()}%, isQualLevel=" . ($item->isQualLevel() ? 'true' : 'false'));
|
||||
|
||||
// 3. Rekursive Aggregation der Kinder
|
||||
if (!empty($item->businessUserItems)) {
|
||||
foreach ($item->businessUserItems as $childItem) {
|
||||
|
||||
// Rekursiver Aufruf
|
||||
$childVolumes = $this->getVolumeByProtectionLevel($childItem);
|
||||
|
||||
// 4. Schutz-Level anwenden und aggregieren
|
||||
foreach ($childVolumes as $protectedPercentStr => $vol) {
|
||||
$incomingProtection = (float) $protectedPercentStr;
|
||||
|
||||
// Das Volumen ist bereits mit $incomingProtection geschützt.
|
||||
// Da es nun durch diesen User fließt, erhöht sich der Schutz auf dessen Level (falls höher).
|
||||
$effectiveProtection = max($incomingProtection, $myProtectionPercent);
|
||||
|
||||
$newKey = (string) $effectiveProtection;
|
||||
|
||||
if (!isset($volumes[$newKey])) {
|
||||
$volumes[$newKey] = 0.0;
|
||||
}
|
||||
$volumes[$newKey] += $vol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $volumes;
|
||||
}
|
||||
}
|
||||
263
dev/buinessPlan/_bak/SalesPointsVolume.php
Normal file
263
dev/buinessPlan/_bak/SalesPointsVolume.php
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\BusinessPlan;
|
||||
|
||||
use App\User;
|
||||
use stdClass;
|
||||
use App\Services\Util;
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Models\UserSalesVolume;
|
||||
use App\Events\BusinessDataChanged;
|
||||
|
||||
class SalesPointsVolume
|
||||
{
|
||||
|
||||
public static function changeSalesPointsVolumeUser(ShoppingOrder $shoppingOrder, $to_user_id)
|
||||
{
|
||||
|
||||
if ($shoppingOrder->user_sales_volume) {
|
||||
$to_user_id = intval($to_user_id);
|
||||
if ($shoppingOrder->user_sales_volume->user_id === $to_user_id) {
|
||||
\Session()->flash('alert-error', 'Keine Änderung: selber Berater');
|
||||
return;
|
||||
}
|
||||
if (!$shoppingOrder->user_sales_volume->isCurrentMonthYear()) {
|
||||
\Session()->flash('alert-error', 'Änderung muss im selben Monat sein');
|
||||
return;
|
||||
}
|
||||
|
||||
$month = $shoppingOrder->user_sales_volume->month;
|
||||
$year = $shoppingOrder->user_sales_volume->year;
|
||||
$form_user_id = $shoppingOrder->user_sales_volume->user_id;
|
||||
|
||||
$to_user = User::find($to_user_id);
|
||||
$form_user = User::find($form_user_id);
|
||||
|
||||
$shoppingOrder->user_sales_volume->user_id = $to_user_id;
|
||||
$shoppingOrder->user_sales_volume->message = 'zugewiesen: ' . date('d.m.Y');
|
||||
|
||||
$syslog = $shoppingOrder->user_sales_volume->syslog;
|
||||
$from_email = $form_user ? $form_user->email : '';
|
||||
$to_email = $to_user ? $to_user->email : '';
|
||||
$syslog[date('d.m.Y-h:i:s')] = 'change form: #' . $form_user_id . ' ' . $from_email . ' to: #' . $to_user_id . ' ' . $to_email;
|
||||
$shoppingOrder->user_sales_volume->syslog = $syslog;
|
||||
|
||||
$shoppingOrder->user_sales_volume->save();
|
||||
|
||||
//recalculate
|
||||
self::reCalculateSalesPointsVolume($to_user_id, $month, $year);
|
||||
self::reCalculateSalesPointsVolume($form_user_id, $month, $year);
|
||||
\Session()->flash('alert-save', true);
|
||||
}
|
||||
}
|
||||
|
||||
private static function add_KP_TP_Points($userSalesVolume, $month_points)
|
||||
{
|
||||
if ($userSalesVolume->status_points === 2) { //KP
|
||||
$month_points->KP += $userSalesVolume->points;
|
||||
} else {
|
||||
// === 1 //TP + KP
|
||||
$month_points->KP += $userSalesVolume->points;
|
||||
$month_points->TP += $userSalesVolume->points;
|
||||
}
|
||||
return $month_points;
|
||||
}
|
||||
|
||||
public static function reCalculateSalesPointsVolume($user_id, $month, $year)
|
||||
{
|
||||
|
||||
$userSalesVolumes = UserSalesVolume::where('user_id', $user_id)->where('month', $month)->where('year', $year)->orderBy('id', 'ASC')->get();
|
||||
$month_points = new stdClass();
|
||||
$month_points->KP = 0;
|
||||
$month_points->TP = 0;
|
||||
$month_total_net = 0;
|
||||
$month_shop_points = 0;
|
||||
$month_shop_total_net = 0;
|
||||
//TDOO Status === 3???
|
||||
|
||||
foreach ($userSalesVolumes as $userSalesVolume) {
|
||||
switch ($userSalesVolume->status) {
|
||||
case 1: //Bestellung Berater
|
||||
$month_points = self::add_KP_TP_Points($userSalesVolume, $month_points);
|
||||
$month_total_net += $userSalesVolume->total_net;
|
||||
break;
|
||||
case 2: //Shop
|
||||
$month_shop_points += $userSalesVolume->points;
|
||||
$month_shop_total_net += $userSalesVolume->total_net;
|
||||
break;
|
||||
case 4: //Gutschrift
|
||||
$month_points = self::add_KP_TP_Points($userSalesVolume, $month_points);
|
||||
|
||||
if ($userSalesVolume->status_turnover === 2) {
|
||||
$month_shop_total_net += $userSalesVolume->total_net;
|
||||
//ggf hier zu den Shop Points zählen wäre aber immer KP + TP kann nicht keine trennung bei month_shop_points
|
||||
} else {
|
||||
$month_total_net += $userSalesVolume->total_net;
|
||||
}
|
||||
|
||||
break;
|
||||
case 5: //Registrierung
|
||||
$month_points = self::add_KP_TP_Points($userSalesVolume, $month_points);
|
||||
$month_total_net += $userSalesVolume->total_net;
|
||||
break;
|
||||
}
|
||||
$userSalesVolume->month_shop_points = $month_shop_points;
|
||||
$userSalesVolume->month_shop_total_net = $month_shop_total_net;
|
||||
$userSalesVolume->month_KP_points = $month_points->KP;
|
||||
$userSalesVolume->month_TP_points = $month_points->TP;
|
||||
$userSalesVolume->month_total_net = $month_total_net;
|
||||
$userSalesVolume->save();
|
||||
}
|
||||
|
||||
// Event für Business-Neuberechnung (Bubble Up zur Upline)
|
||||
if ($user_id) {
|
||||
event(new BusinessDataChanged($user_id, BusinessDataChanged::TYPE_SALES_VOLUME, (int)$month, (int)$year));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function addSalesPointsVolumeUser(ShoppingOrder $shoppingOrder)
|
||||
{
|
||||
|
||||
/*
|
||||
status
|
||||
1 => 'hinzugefügt aus Bestellung',
|
||||
2 => 'hinzugefügt aus Shop',
|
||||
3 => 'hinzugefügt aus Shop / pending',
|
||||
*/
|
||||
|
||||
$status = self::getStatusByOrderPaymentFor($shoppingOrder);
|
||||
$user_id = $shoppingOrder->auth_user_id ? $shoppingOrder->auth_user_id : $shoppingOrder->member_id;
|
||||
//akuteller tag / Monat.
|
||||
$month = date('m');
|
||||
$year = date('Y');
|
||||
$date = date('d.m.Y');
|
||||
|
||||
|
||||
if ($status === 3) { //shop bestellung User pending if is_like
|
||||
$user_id = NULL;
|
||||
}
|
||||
$user_sales_volume = UserSalesVolume::create([
|
||||
'user_id' => $user_id,
|
||||
'shopping_order_id' => $shoppingOrder->id,
|
||||
'month' => $month,
|
||||
'year' => $year,
|
||||
'date' => $date,
|
||||
'points' => $shoppingOrder->points,
|
||||
'total_net' => $shoppingOrder->subtotal,
|
||||
'status_points' => 1, //KP + TP
|
||||
'message' => '',
|
||||
'status' => $status,
|
||||
]);
|
||||
|
||||
if ($status !== 3) {
|
||||
self::reCalculateSalesPointsVolume($user_sales_volume->user_id, $user_sales_volume->month, $user_sales_volume->year);
|
||||
}
|
||||
|
||||
return $user_sales_volume;
|
||||
}
|
||||
|
||||
public static function setToUserAndReCalculate(UserSalesVolume $user_sales_volume, $user_id)
|
||||
{
|
||||
|
||||
//set month year date new, calculate it in the currently month!
|
||||
//If the month has changed, it can no longer be added to the month before
|
||||
$month = date('m');
|
||||
$year = date('Y');
|
||||
$date = date('d.m.Y');
|
||||
|
||||
$user_sales_volume->user_id = $user_id;
|
||||
$user_sales_volume->month = $month;
|
||||
$user_sales_volume->year = $year;
|
||||
$user_sales_volume->date = $date;
|
||||
$user_sales_volume->status = 2; //hinzugefügt aus Shop can only Pending
|
||||
$user_sales_volume->save();
|
||||
|
||||
self::reCalculateSalesPointsVolume($user_id, $month, $year);
|
||||
}
|
||||
|
||||
public static function getStatusByOrderPaymentFor(ShoppingOrder $shoppingOrder)
|
||||
{
|
||||
if ($shoppingOrder->payment_for) {
|
||||
if ($shoppingOrder->payment_for === 6) { //Kunde-Shop
|
||||
if ($shoppingOrder->shopping_user && $shoppingOrder->shopping_user->is_like) {
|
||||
return 3; //shop Kunden, berater zuordnen <- need?
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
public static function editSalesPointsVolume($data)
|
||||
{
|
||||
$user_sales_volume = UserSalesVolume::findOrFail($data['id']);
|
||||
if (!$user_sales_volume->isCurrentMonthYear()) {
|
||||
\Session()->flash('alert-error', 'Änderung muss im selben Monat sein');
|
||||
return;
|
||||
}
|
||||
$old_points = $user_sales_volume->points;
|
||||
$old_total_net = $user_sales_volume->total_net;
|
||||
$user_sales_volume->total_net = Util::reFormatNumber($data['total_net']);
|
||||
$user_sales_volume->points = Util::reFormatNumber($data['points']);
|
||||
|
||||
$user_sales_volume->message = 'geändert: ' . date('d.m.Y');
|
||||
$user_sales_volume->info = $data['info'];
|
||||
$user_sales_volume->status_points = $data['status_points'];
|
||||
$user_sales_volume->status_turnover = isset($data['status_turnover']) ? intval($data['status_turnover']) : null;
|
||||
|
||||
$syslog = $user_sales_volume->syslog;
|
||||
$syslog[date('d.m.Y-h:i:s')] = 'edit points: #' . $old_points . ' ' . $user_sales_volume->points . ' total: #' . $old_total_net . ' ' . $user_sales_volume->total_net;
|
||||
$user_sales_volume->syslog = $syslog;
|
||||
|
||||
$user_sales_volume->save();
|
||||
|
||||
self::reCalculateSalesPointsVolume($user_sales_volume->user_id, $user_sales_volume->month, $user_sales_volume->year);
|
||||
|
||||
\Session()->flash('alert-success', "Points geändert");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public static function addSalesPointsVolume($data)
|
||||
{
|
||||
|
||||
if (!isset($data['user_id'])) {
|
||||
\Session()->flash('alert-error', 'Kein Berater ausgewählt');
|
||||
return;
|
||||
}
|
||||
$user = User::findOrFail($data['user_id']);
|
||||
$month = date('m');
|
||||
$year = date('Y');
|
||||
$date = date('d.m.Y');
|
||||
|
||||
$total_net = isset($data['total_net']) ? Util::reFormatNumber($data['total_net']) : 0;
|
||||
$points = isset($data['points']) ? Util::reFormatNumber($data['points']) : 0;
|
||||
$syslog[date('d.m.Y-h:i:s')] = 'add points: #' . $points . ' total: #' . $total_net;
|
||||
$status = isset($data['status']) ? intval($data['status']) : 4;
|
||||
$status_turnover = isset($data['status_turnover']) ? intval($data['status_turnover']) : null;
|
||||
|
||||
$user_sales_volume = UserSalesVolume::create([
|
||||
'user_id' => $user->id,
|
||||
'shopping_order_id' => null,
|
||||
'month' => $month,
|
||||
'year' => $year,
|
||||
'date' => $date,
|
||||
'points' => $points,
|
||||
'status_points' => $data['status_points'],
|
||||
'status_turnover' => $status_turnover,
|
||||
'total_net' => $total_net,
|
||||
'message' => 'hinzugefügt: ' . date('d.m.Y'),
|
||||
'info' => $data['info'],
|
||||
'syslog' => $syslog,
|
||||
'status' => $status,
|
||||
]);
|
||||
|
||||
|
||||
self::reCalculateSalesPointsVolume($user_sales_volume->user_id, $user_sales_volume->month, $user_sales_volume->year);
|
||||
|
||||
\Session()->flash('alert-success', "Points hinzugefügt");
|
||||
}
|
||||
}
|
||||
1080
dev/buinessPlan/_bak/TreeCalcBotOptimized.php
Normal file
1080
dev/buinessPlan/_bak/TreeCalcBotOptimized.php
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue