laravel iteration adding value to the next user if the user rank does not exist

Viewed 265

I have function that will insert an amount to the users up to the last rank based on parent_id. I can get the parent_id up to the last rank, its fine and I'm using an iteration or something like recursion. But I can't add the amount if the user rank is not present.

The expected result of the function is to insert an amount to each User Rank, but if the iteration/recursion didn't find a user with the rank the amount to be inserted to him will be added on the next user rank.

The function will trigger once a user bought a product, so whoever user buy a product it will get his parent_id and insert the amount assigned to that rank.

I have a list of rank in order who will receive the amount.

Basic - 50
Junior - 100
Premium - 150
Advanced - 200
Senior - 250

Database:

+------+------------+-------------+------------+
|  id  |  username  |  parent_id  |   rank     |
+------+------------+-------------+------------+
|  1   |   john     |    NULL     |  Senior    |
|  2   |   jane     |    1        |  Advanced  |
|  3   |   josh     |    2        |  Premium   |
|  4   |   joey     |    3        |  Junior    |
|  5   |   jade     |    4        |  Basic     |
------------------------------------------------

For example:

Jade bought an item, once the order is success I will call the function to insert the earnings.

In this scenario Joey, Josh, Jane and John will received their earnings based on the corresponding amount of their rank.

This is working fine in this scenario because all the ranks are present, but my problem is to add the amount set to rank if the user is not in the iteration,

For example, Joey with the rank - Junior is not in the iteration, the amount designated to him which is 100 will be added on the next rank which is the Premium, and proceed the earnings insertion on the next ranks, but prevent adding the amount to them, because the amount should only be added to the next rank if the user with that rank is not present in the iteration.

// successfully bought a product and saved to database

$user_id = 5; // jade
$parent_id = 4;
    
// call the function to insert the earnings

self::insertEarnings($user_id,$parent_id);

This is the function that will insert the earnings:

private function insertEarnings($user_id,$parent_id) {

    if ($parent_id > 0) {
        
        $user_parent = $parent_id;
        
        $has_parent = true;
        
        // start iteration
        while($has_parent == true){
            
            $account = User::where('id',$user_parent)->first();
            
            $amount = 0;

            if ($account->rank == "Junior" ) {
                
                $amount = 100;
                    
            } elseif ($account->rank == "Premium") {

                $amount = 150;

            } elseif ($account->rank == "Advanced") {

                $amount = 200;
                
            } elseif ($account->rank == "Senior") {

                $amount = 250;
                
                // set to false to stop the iteration
                $has_parent = false;
            }

            $earnings = new Earnings;
            $earnings->user_id = $account->id;
            $earnings->amount = $amount;
            $earnings->save();
                    
            $next_parent = User::where('id',$user_parent)->first();
            $user_parent = $next_parent->parent_id;

            if($user_parent == 0){
                $has_parent = false;
            }
        }
    }
}

The earnings was inserted to Joey, Josh, Jane and John because they are all present in the iteration, the real question is, how to insert the amount assigned to the rank if he is not present in the iteration.

EDIT:

Please someone help me? I can't really make a solution for this code. Adding the amount to the next user rank if the rank is not found in the iteration/loop.

Jade order a product, let's assume it is successful. Now I will trigger the insertEarnings function. I will get his parent_id which is Joey,

Joey will get an amount of 100, also Josh, Jane and John they will get an amount according to their rank.

The main problem is what if someone is NOT in the iteration/loop, the amount for that rank whatever that rank is will be added to the next rank.

5 Answers

Reading the comments, I think your problem now is how to sum up the amount if certain user rank is not exist right?

I have a logic to iterate the ranks by using this code.

public function insertEarnings()
{
    // Rank Map
    $ranks  = [[
        'name' => 'Basic',
        'point' => 50,
    ], [
        'name' => 'Junior',
        'point' => 100,
    ], [
        'name' => 'Premium',
        'point' => 150,
    ], [
        'name' => 'Advanced',
        'point' => 200,
    ], [
        'name' => 'Senior',
        'point' => 250,
    ]];

    // Find Current Rank & Init Others
    $current = array_search($this->rank, array_column($ranks, 'name'));
    $rank    = null;
    $user    = null;
    $amount  = 0;

    // Loop while next rank still exist
    while (array_key_exists($current + 1, $ranks)) {
        // Find next rank, user within that rank, and sum up the amount.
        $rank   = $ranks[$current + 1];
        $user   = User::where('rank', $rank['name'])->first();
        $amount += $rank['point'];

        // Add current to iterate trough ranks
        $current++;

        // If user is found save the earning and restart the amount to 0
        if(!is_null($user)){
            $earnings = new Earning;
            $earnings->user_id = $user->id;
            $earnings->amount = $amount;
            $earnings->save();

            $amount = 0;
        }
    }
}

And using that code I able to make the earnings like in the table bellow by calling

$user->insertEarnings() // jane

+------+--------+----------+
|  id  |  name  |  amount  | 
+------+--------+----------+
|  1   |  josh  |  250     |
|  2   |  jane  |  200     |
|  3   |  john  |  250     |
----------------------------

Is that what you want to achieve?

Not really sure I did understand what you are trying to do here but from what I understood, you have a problem if the user who bought the item is not at the bottom of your list (in your example Jade with id=5).

So, if e.g. Josh would buy the item, Joey and Jade would miss out their earnings since they appear lower down in the database. You start your loop with Josh’s parent ID at parent_id=2 and set the pointers to the next entry further up in the database (the logic in your code is really confusing here and could do with some cleanup or even a reconsideration).

Anyway, if you still want to consider all other candidates - in our example John, Jane, Joey and Jade - you need to fetch the last database entry first and start your loop

private function insertEarnings($user_id, $parent_id) {

  $mumEntries = User::where('id')->last() // not sure if this is the right syntax but you get the point! You want the highest possible user ID in the DB.

  for ( $i=$numEntries; $i>=0; $i—- ) {
    
    if ( $i !== $user_id ) { // skip the user who has purchased an item
      $user = User::where(‘id’, $i) // again, only if this means ‘get user with id=$i’
      
      // ... do logic with $amount
      
      $earnings = new Earnings;
      $earnings->user_id = $i;
      $earnings->amount = $amount;
      $earnings->save();
    }
  }
}

I don’t have the syntax for Laravel Database Objects in mind right now but I hope this helps.

I spent 30 minutes trying to understand what you want, but heres an improved and cleaner function for you to use :

    public function insertEarnings($user_id) {
        $ranks = [
            'Basic' => 50,
            'Junior' => 100,
            'Premium' => 150,
            'Advanced' => 200,
            'Senior' => 250
        ];

        $user = User::find($user_id);
        if($user) {
            //create earning for current user
            if(array_key_exists($user->rank, $ranks)) {
                $earnings = new Earnings();
                $earnings->user_id = $user->id;
                $earnings->amount  = $ranks[$user->rank];
                $earnings->save();
            }

            if($user->parent_id != null) {
                $this->insertEarnings($user->parent_id);
            }
        }
        return false;
    }

In order to achieve what you are trying to do you will need to edit how users are removed from the database.

Here is a link to the documentation for soft deleting. This can be applied to existing database structures with a new migration. Please test before pushing to production. https://laravel.com/docs/8.x/eloquent#soft-deleting

Once we have soft deletes in place we can modify the insert earnings function.

// passed in the id of user that made a purchase
private function insertEarnings($user_id) {

$user = User::where('id',$user_id)->first();

if ($user->parent_id > 0) {
    
    $next_parent_id = $user->parent_id;
    
    $has_parent = true;

    $amount = 0;
    
    // start iteration
    while($has_parent === true){
        
        $parent = User::withTrashed()->where('id',$next_parent_id)->first();

        if ($parent->rank == "Junior" ) {
            
            $amount += 100;
                
        } elseif ($parent->rank == "Premium") {

            $amount += 150;

        } elseif ($parent->rank == "Advanced") {

            $amount += 200;
            
        } elseif ($parent->rank == "Senior") {

            $amount += 250;
            
            // set to false to stop the iteration
            $has_parent = false;
        }

        if (!$parent->trashed()) {

            $earnings = new Earnings;
            $earnings->user_id = $parent->id;
            $earnings->amount = $amount;
            $earnings->save();

            $amount = 0;
        }
                
        $next_parent_id = $parent->parent_id;

        if($user_parent_id === 0){
            $has_parent = false;
        }
    }
}

I have cleaned up your original function a little, but I would refer to Konjesh Esbrading for a cleaner example. An explanation of what I have done. If the parent has been soft deleted from the database they will not get the earnings and the $amount will not be reset to 0. The next availible parent will then get both amounts.

Because of the iteration loop this may take a while on larger databases which could cause timeout errors. In order to prevent that from happening I would like to suggest you look into using queues and jobs: https://laravel.com/docs/8.x/queues

You are not checking for a model's existence before accessing properties. maybe that's a problem.

private function insertEarnings($user_id,$parent_id) {

    if ($parent = User::find($parent_id)) {
        
        $has_parent = false;
        // start iteration
        while($has_parent){
            $amount = 0;
            if ($parent->rank === "Junior" ) {
                $amount = 100;
            } elseif ($parent->rank === "Premium") {
                $amount = 150;
            } elseif ($parent->rank === "Advanced") {
                $amount = 200;
            } elseif ($parent->rank == "Senior") {
                $amount = 250;
                // set to false to stop the iteration
                $has_parent = false;
            }

            $earning = Earnings::create([
                'user_id' => $parent->id,
                'amount'  => $amount,
            ]);

            if($parent->parent()->exists()){ // define hasOne() relation in model
                $parent = $parent->parent;
            } else {
                $has_parent = false;
            }
        }
    }
}
Related