anyone wants to help ๐๐๐๐๐
laravel add amount to the next parent in loop
EDIT:
I mean, I have a referral system which is every parent_id and the parent_id of that parent_id until it gets to the last parent which is the Senior.
Given that a user has purchased an item and it is successful, now I am calling the insertEarnings function to insert the corresponding amount to his parent_id, if his parent_id is Junior his parent_id will get 100.
This is how the question begin, what if his parent doesn't have a rank meaning not active, the amount to be insert to his parent_id will go through the next rank which is the Premium so the Premium will no have 250 total, after that proceed the normal insertion of the amount to the parent_id of Premium which is the Advanced- he will get the corresponding amount which is 200, because the rank below him which is the Premium is existing, the insertion will go through until it gets to the last rank which is the Senior
Imagine that it is looping through the parent until it gets to the last rank - Senior.
The ranks are in order
Junior
Premium
Advanced
Senior
Ranks with their corresponding amount value
Junior - 100
Premium - 150
Advanced - 200
Senior - 250
Users table
+------+------------+-------------+------------+
| 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 |
+----------------------------------------------+
Code
$user_id = 5; // jade
$parent_id = 4;
// call the function to insert the earnings
self::insertEarnings($user_id,$parent_id);
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;
// for example this user/parent does not exist the amount(150) for him will be added to the next rank which is the Advance
} 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 $user_id is not used in this example, but there's a use to that that didn't included in the question because that is not the main problem.
@whoami1509 see this question not the first time, but it's still not clear for me. But - ok, let's try
$userId = 5; //jade
//call function with $user as argument
self::insertEarnings($userId);
use \Illuminate\Support\Arr;
protected $rankAmount = [
'Junior' => 100,
'Premium' => 150,
'Advanced' => 200,
'Senior' => 250,
];
private function insertEarnings($userId) {
$user = User::get($userId);
if ($user) {
if (Arr::has($this->rankAmount, $user->rank)) {
$earnings = new Earnings;
$earnings->user_id = $user->id;
$earnings->amount = Arr::get($this->rankAmount, $user->rank);
$earnings->save();
}
if ($user->parent_id) {
$this->insertEarnings($user->parent_id);
}
}
}
Thank you for you answer and sorry for my vague question,
I mean, I have a referral system which is every parent_id and the parent_id of that parent_id until it gets to the last parent which is the Senior.
Given that a user has purchased an item and it is successful, now I am calling the insertEarnings function to insert the corresponding amount to his parent_id, if his parent_id is Junior his parent_id will get 100.
This is how the question begin, what if his parent doesn't have a rank meaning not active, the amount to be insert to his parent_id will go through the next rank which is the Premium so the Premium will no have 250 total, after that proceed the normal insertion of the amount to the parent_id of Premium which is the Advanced- he will get the corresponding amount which is 200, because the rank below him which is the Premium is existing, the insertion will go through until it gets to the last rank which is the Senior
@whoami1509 did you try my code?
Yes @silencebringer, but it's not adding the amount to the parent_id
@whoami1509 what you mean under adding the amount? In your code example you just create new earnings with appropriate amounts. do you have some field with total amount, or?
@silencebringer please read the question again. thank you
I would use recursion to solve this.
-
Usermodel:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
// ... other code on your User model
protected const RANKS = ['Basic', 'Junior', 'Premium', 'Advanced', 'Senior'];
protected const AMOUNTS = [
'Basic' => 0,
'Junior' => 100,
'Premium' => 150,
'Advanced' => 200,
'Senior' => 250,
];
// startup call, earnings are added to parents,
// not to the user who made a purchase
public function recordEarning()
{
// as this method is called for the user who just
// made a purchase, this user is required to be active
if (! $this->rank) {
throw new \ErrorException('Cannot record earning for an inactive user');
}
$this->recordEarningToParent($this->rank, 0);
}
protected function recordEarningToParent($currentRank, $carry)
{
$parentRank = $this->parentRank($currentRank);
if (! $parentRank) {
return;
}
$parent = $this->parent;
if (! $parent) {
return;
}
$amount = self::AMOUNTS[$parentRank] + $carry;
// check if active and has amount to record
if ($parent->rank && ($amount > 0)) {
$earnings = new Earnings();
$earnings->user_id = $parent->getKey();
$earnings->amount = $amount;
$earnings->save();
// as the amount was recorded, there
// is nothing to carry to the next parent
$amount = 0;
}
// recursive call to the next parent
$parent->recordEarningToParent($parentRank, $amount);
}
// find the parent rank, as nullable rank means inactive user
protected function parentRank($currentRank)
{
$currentRankIndex = array_search($currentRank, self::RANKS);
return self::RANKS[$currentRankIndex + 1] ?? null;
}
// relation definition, for reference
public function parent()
{
return $this->belongsTo(User::class, 'parent_id');
}
}
-
Earningmodel (for reference)
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Earning extends Model
{
// ... other code in your Earning Model
public function user()
{
return $this->belongsTo(User::class);
}
}
- Test route
<?php
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
$user = \App\Models\User::query()->find(5); // Basic
$user->recordEarning();
return \App\Models\Earnings::all();
});
Note:
Using a loop - in most cases - is more efficient than recursion. But using recursion makes a problem easier to solve. As you have a limited number of levels I chose to give recursion a try.
Other note:
This is not the kind of response you should expect from Laracasts forums as granted. Usually forum's users would try to hint you towards the solution, not just spit code that solves your problem.
The reason I dig into this is because I need to wait for a very long process to finish running before I go to sleep, and I found the challenge interesting.
So always have in mind that providing clear descriptions, having patience on answering questions, and having empathy is a path to succeeding on getting help. Remember everyone here are volunteers.
One suggestion (related to the problem):
This would be much easier to solve if the "null rank means inactive" rule was changed.
I would suggest you to add a is_active column where 1 represents active and 0 represents inactive, and keep the rank column always filled.
This would simplify the code a lot and might even help avoiding recursion altogether.
Hope it helps.
@rodrigo.pedra may I ask a question? I tried your code and I don't have any error. I just want to ask if it's filtering the parent rank? I mean the parent are in-order based on rank
$user = 5; // Jane
Jane is Basic in rank, it will get his parent which is Junior if his parent is not rank with Junior it will get the parent of his parent until it gets to Junior, and so on until it ends with Senior. Thank you I hope you understand what I'm trying to say.
Well the code traverses a user's parent chain recursively using the relationship defined. So as long you database is in a consistent state, it will work.
For example, let's start with you sample user
- User with ID = 5, Name = Jane, and Rank = Basic
The code will try to fetch this user's parent using the relationship parent() which will use the record parent_id field.
So as long the parent is correctly, assigned it will work.
For example, let's say Jane's parent chain is like this:
+--+---------+----+-------+
|id|parent_id|name|rank |
+--+---------+----+-------+
|5 |40 |Jane|Basic |
|40|31 |John|Junior |
|31|22 |Mark|Premium|
|22|14 |Mary|NULL |
|14|NULL |Anna|Senior |
+--+---------+----+-------+
So the code will traverse Jane's parent in this order: John, then Mark, then Mary, then Anna. Because the next parent is fetched using a record parent_id
As Mary was supposed to have a rank = Advanced, but has no rank, the amount will be accumulated to the next parent which is Anna.
There is one problem though, if your parent's chain is in an inconsistent state, the code doesn't account for that. For example, let's say a user's parent chain is like this:
+--+---------+------+------+
|id|parent_id|name |rank |
+--+---------+------+------+
|13|97 |Adam |Basic |
|97|75 |Eve |Junior|
|75|53 |Bob |Junior|
|53|31 |Arthur|Junior|
|31|NULL |Joanne|Junior|
+--+---------+------+------+
All Adam's parent have a rank value of Junior it is not the chain you described before.
The code only skips blank ranks (NULL), but not wrong ranks, and then it would fail.
If you want to skip wrong/unexpected ranks you only need to change this line:t
// check if active and has amount to record
if ($parent->rank && ($amount > 0)) {
To
// check if active and has amount to record
if (($parent->rank === $parentRank) && ($amount > 0)) {
So it will check if the current record's rank matches the expected rank for that iteration.
Hope it is clearer.
@rodrigo.pedra just wow, every answer you provide leads to an additional knowledge for me, I'm sorry if I did not explain it well in the question because English is not my first language and still practicing, Thank you once again for your clear and well explained answer.
EDIT:
I'm sorry this is the chain of what I am trying to do. It will skip the NULL and the already passed rank, in the below table it will skip Mary and Joey, because Mary does have a NULL rank and Joey is another Premium rank be there's a first Premium rank before him which is Mark. I'm so sorry for my question I know this is a really advance task, but I'm grateful that someone is helping me to achieve this.
+--+---------+----+-------+
|id|parent_id|name|rank |
+--+---------+----+-------+
|5 |40 |Jane|Basic |
|40|31 |John|Junior |
|31|22 |Mark|Premium|
|22|11 |Mary|NULL |
|11|14 |Joey|Premium|
|14|NULL |Anna|Senior |
+--+---------+----+-------+
Youโre welcome!
English is also not my native language, so donโt worry, might have been a misunderstanding from my side.
Great it helped you out.
Have a nice day =)
Hello @rodrigo.pedra Sorry, I've updated my comment, I don't know it it notify you, just to make sure. Thank you
Hi @whoami1509 , I don't know how Laracast's notification system works, but I only receive email notifications for threads I explicitly hit the "FOLLOW" button right below the "REPLY" button on the sidebar.
For threads I don't hit that button, or if I am mentioned on another thread, I never received any notifications, not by email, and not in the "Notifications" tab under my user profile. Actually I never saw a in-app notification. I wonder if it is because my username has a dot (.) within it.
But I frequently check the "My Participation" section on the forums, this is how I can catch up with updates.
Back to your last response update (after your edit).
The way code is now (after our last if clause update), it will never hit user 14 - Anna.
Just for reference I will copy the user's table sample below:
+--+---------+----+-------+
|id|parent_id|name|rank |
+--+---------+----+-------+
|5 |40 |Jane|Basic |
|40|31 |John|Junior |
|31|22 |Mark|Premium|
|22|11 |Mary|NULL |
|11|14 |Joey|Premium|
|14|NULL |Anna|Senior |
+--+---------+----+-------+
When you call recordEarning() on user 5 - Jane, the code will:
First Call to recordEarningToParent('Basic', 0) from user 5 - Jane
-
$parentRankwill resolve toJunior -
$parentwill resolve to user40 - John, which has a$rankofJunior -
$amountwill resolve to100(based on$parentRank === 'Junior') - As
$parent->rank === $parentRankand$amount > 0a newEarningrecord will be record related to user40 - Johnwithamountequals to100. - Then next iteration on
recordEarningToParent()will lead to:
Second Call to recordEarningToParent('Junior', 0), now from user 40 - John
-
$parentRankwill resolve toPremium -
$parentwill resolve to user31 - Mark, which has a$rankofPremium -
$amountwill resolve to150(based on$parentRank === 'Premium') - As
$parent->rank === $parentRankand$amount > 0a newEarningrecord will be record related to user31 - Markwithamountequals to200. - Then next iteration on
recordEarningToParent()will lead to:
Third Call to recordEarningToParent('Premium', 0), now from user 31 - Mark
-
$parentRankwill resolve toAdvanced -
$parentwill resolve to user22 - Mary, which has a$rankofNULL -
$amountwill resolve to200(based on$parentRank === 'Advanced') - As
$parent->rank !== $parentRankNOEarningrecords will be recorded. - Then next iteration on
recordEarningToParent()will lead to:
Fourth Call to recordEarningToParent('Advanced', 200), now from user 22 - Mary
-
$parentRankwill resolve toSenior -
$parentwill resolve to user11 - Joey, which has a$rankofPremium -
$amountwill resolve to450(based on$parentRank === 'Senior'plus the carried over amount) - As
$parent->rank !== $parentRank(Premiumis different thanSenior) NOEarningrecords will be recorded. - Then next iteration on
recordEarningToParent()will lead to:
Fifth Call to recordEarningToParent('Senior', 450), now from user 11 - Joey
-
$parentRankwill resolve toNULL(no ranks available afterSenior) - As
$parentRank === NULLthe function returns and the recursive call is stopped.
-=-=-=-=-=-=-=-=-=-=-=-
I don't know if that fits your app's requirements, but it how it works now.
Problem is you have more levels in 5 - Jane's parent chain than rank levels available. So it leads to a "Pigeonhole principle", as have more parents in a chain than ranks available.
Reference on "Pigeonhole principle": https://en.wikipedia.org/wiki/Pigeonhole_principle
Stretching it would require doing some changes on how it works now, and more clear description on what to do when such scenario happens.
@rodrigo.pedra Sorry for the late reply. Am I correct that this line of code below will the the chain of:
Basic -> Junior -> Premium -> Advanced -> Senior
Code:
protected function recordEarningToParent($currentRank, $carry)
{
$parentRank = $this->parentRank($currentRank);
if (! $parentRank) {
return;
}
$parent = $this->parent;
if (! $parent) {
return;
}
$amount = self::AMOUNTS[$parentRank] + $carry;
// check if active and has amount to record
if (($parent->rank === $parentRank) && ($amount > 0)) {
$earnings = new Earnings();
$earnings->user_id = $parent->getKey();
$earnings->amount = $amount;
$earnings->save();
// as the amount was recorded, there
// is nothing to carry to the next parent
$amount = 0;
}
// recursive call to the next parent
$parent->recordEarningToParent($parentRank, $amount);
}
Sorry @rodrigo.pedra I'm a little confused within these two lines of code:
$parent->rank === $parentRank
$parent->rank !== $parentRank
I mean where and when to put the condition !==
Well, I don't know if I understand.
If you are referring to the comment I made in the "Fourth Cal to recordEarning..." where I say
As
$parent->rank !== $parentRank(Premiumis different thanSenior) NOEarningrecords will be recorded.
That was a comment on a walktrrough on code, highlighting how it works, and not meant to change the code.
You could read that line as this:
As
Premiumis different thenSenior(condition$parent->rank === $parentRankevaluates tofalse) NOEarningrecords will be recorded, as it would not go inside theifblock.
It wasn't a suggestion to change the code in anyway. I was just highlighting what is happening on that code execution iteration.
If you think it is confusing, I can review my last response and try to be more explicit on any potential code suggestions like that.
What I meant to show in my last response is that user 5-Jane have 5 parents in their parents' chain, while available ranks for parents are only 4 (not counting that user own Basic rank), so when traversing the parent's chain up the available ranks will end before reaching user 14-Anna.
Hope this makes it clearer.
@rodrigo.pedra Thank you, sorry I didn't understand it correctly, now it's clear to me. I will try some code that you gave me and return here if I have question. Thank you very much for your time and effort in this thread.
@rodrigo.pedra Hello, sorry too long no update. I've implemented your code and I think it's working as what I'm expecting (thank you for that). Today I'm thinking about the guest orders.
This is how the guest order works with this bonuses. Users are allowed to share product links and send them to anyone (guest) now if the person that received the link ordered a product I have a cookie that will get the parameter ref=username and saving the ID in the sponsor_id field in the orders table. The ordering of guest is working fine I'm also able to save the ID of the user in the orders table.
Now what I wanted to do is if the order is from Guest it will get the sponsor_id value, if no referral is made sponsor_id remains empty so it will not call the recordEarnings().
The sponsor_id will receive a bonus based on the rank the same as we did before. The only difference is the Basic rank will also received a reward now because he can give his link and become a sponsor_id, unlike in the previous that he is just making the orders and he didn't receive any reward.
This is the code from my app now:
$order = Order::findOrFail($id);
if ($order->user_id > 0) {
$user = User::query()->find($order->user_id);
} elseif ($order->sponsor_id > 0) {
$user = User::query()->find($order->sponsor_id);
}
// Calling the recordEarning
if ($user_id) {
$user->recordEarning();
}
I'm thinking of adding a parameter to identify if it from guest or not and pass it when calling the recordEarning.
$order = Order::findOrFail($id);
$from_guest = false;
if ($order->user_id > 0) {
$user = User::query()->find($order->user_id);
} elseif ($order->sponsor_id > 0) {
$user = User::query()->find($order->sponsor_id);
$from_guest = true;
}
// Checking if $user has a value either order from `user_id` or `sponsor_id` (sponsor_id for referral)
if ($user) {
// Calling the recordEarning
$user->recordEarning($from_guest);
}
Now I don't know how to implement it to the User model in this function public function recordEarning()
public function recordEarning($from_guest)
{
if(!$from_guest) {
// as this method is called for the user who just
// made a purchase, this user is required to be active
if (! $this->rank) {
throw new \ErrorException('Cannot record earning for an inactive user');
}
$this->recordEarningToParent($this->rank, 0);
} else {
// this is where the logic is change because if the user is from sponsor_id and he's rank is Basic, he will receive for example I've set the amount of 50 for Basic.
}
}
@rodrigo.pedra I'm also trying to make the amount dynamic for each rank but I'm not able to make it work I think something's wrong in checking the amounts in this line $amount = $this->amounts[$parentRank] + $carry;
Code including the order from guest:
public function recordEarning($from_guest, $basic_amount, $junior_amount, $premium_amount, $advanced_amount, $senior_amount)
{
if(!$from_guest) {
// as this method is called for the user who just
// made a purchase, this user is required to be active
if (! $this->rank) {
throw new \ErrorException('Cannot record earning for an inactive user');
}
$this->recordEarningToParent($this->rank, $from_guest, $basic_amount, $junior_amount, $advanced_amount, $senior_amount, 0);
} else {
// this is where the logic is change because if the user is from sponsor_id and he's rank is Basic,
// he will receive for example I've set the amount of 50 for Basic.
}
}
protected function recordEarningToParent($rank, $from_guest, $basic_amount, $junior_amount, $premium_amount, $advanced_amount, $senior_amount, $carry)
{
$amounts = [
'Basic' => $basic_amount,
'Junior' => $junior_amount,
'Premium' => $premium_amount,
'Advanced' => $advanced_amount,
'Senior' => $senior_amount,
];
$parentRank = $this->parentRank($currentRank);
if (! $parentRank) {
return;
}
$parent = $this->parent;
if (! $parent) {
return;
}
$amount = $this->amounts[$parentRank] + $carry;
// check if active and has amount to record
if (($parent->rank=== $parentRank) && ($amount > 0)) {
$earnings = new DirectSalesProfit;
$earnings->user_id = $parent->getKey();
$earnings->amount = $amount;
$earnings->save();
// as the amount was recorded, there
// is nothing to carry to the next parent
$amount = 0;
}
// recursive call to the next parent
$parent->recordEarningToParent($parentRank, $from_guest, $basic_amount, $junior_amount, $premium_amount, $advanced_amount, $senior_amount, $amount);
}
Controller:
$order = Order::findOrFail($id);
$from_guest = false;
if ($order->user_id > 0) {
$user = User::query()->find($order->user_id);
} elseif ($order->sponsor_id > 0) {
$user = User::query()->find($order->sponsor_id);
$from_guest = true;
}
// Checking if $user has a value either order from `user_id` or `sponsor_id` (sponsor_id for referral)
if ($user) {
// Calling the recordEarning
$user->recordEarning($from_guest, $basic_amount, $junior_amount, $premium_amount, $advanced_amount, $senior_amount);
}
Hi @whoami1509 this is how I would do it:
New feature request
- A "guest" user can make purchases
- A "guest" user is a user with no record in the database
- A "guest" is a related to a regular user through a cookie with the user who sent this "guest" user a referral link. This related user will be considered the "guest" user's parent user.
- The purchase from a "guest" user should record new earnings to the "guest" user's parent through the same algorithm already in place.
- If a "guest" user's parent user has a rank of
Basicthey will earn aEarningwith an amount value of50
Implementation
1 - Add new Guest rank and change Basic rank's earning amount
In your User model change the following:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
// ... other code on your User model
protected const RANKS = [
'Guest', // <<<<< ADDED
'Basic',
'Junior',
'Premium',
'Advanced',
'Senior',
];
protected const AMOUNTS = [
'Guest' => 0, // <<<<< ADDED
'Basic' => 50, // <<<<< CHANGED
'Junior' => 100,
'Premium' => 150,
'Advanced' => 200,
'Senior' => 250,
];
}
2 - Add a factory method to create a unsaved User instance for a Guest
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
// ... other code on your User model
public static function makeGuest($parentId)
{
$parent = User::query()->findOrFail($parentId);
$guest = new User();
$guest->rank = 'Guest'; // <<<<< Using the newly created rank
$guest->parent_id = $parentId;
$guest->parent()->associate($parent);
return $guest;
}
}
3 - Refactor the controller
To use our new makeGuest() rank and record a earning to it
$order = Order::findOrFail($id);
if ($order->user_id > 0) {
$user = User::query()->findOrFail($order->user_id);
} elseif ($order->sponsor_id > 0) {
$user = User::makeGuest($order->sponsor_id);
}
if ($user) {
// user will be either a Guest or a regular user
// In case of a Guest user, as the recordEarning doesn't
// use the starting user data from database, we
// can start from a unsaved user object
$user->recordEarning();
}
Note I removed the parameters from the ->recordEarning(...) and reverted back to having them defined in a User model constant. From your code sample I didn't follow the reason why this change was made.
If you have a reason to have them by parameter, please adjust this to your requirements.
Hope it helps.
@rodrigo.pedra thank you, I'm happy you're here again helping me. I will try your answer and I think it will work :), one more thing I also updated the method in the User model because trying to make the amount dynamic for each rank but I'm not able to make it work I think something's wrong in checking the amounts in this line $amount = $this->amounts[$parentRank] + $carry;
Sorry I feel shy because I'm like asking you all the things, but I know this will improve me as a developer and be like you in the future. Thank you very much @rodrigo.pedra
I added an array inside the recordEarningToParent(..)
protected function recordEarningToParent($currentRank, $carry)
{
$amounts = [
'Basic' => $basic_amount,
'Junior' => $junior_amount,
'Premium' => $premium_amount,
'Advanced' => $advanced_amount,
'Senior' => $senior_amount,
];
$parentRank = $this->parentRank($currentRank);
$amount = $this->amounts[$parentRank] + $carry;
// but this is returning NULL, I think I'm doing it wrong.
dd($amount);
}
Did you add Guest to the RANKS array as in the step 1 from my last response?
@rodrigo.pedra I'm currently doing it right now, sorry if I added another question too fast
@rodrigo.pedra do I need to remove this line?
EDIT: Sorry I'm doing it wrong, It's working now! Thank you @rodrigo.pedra
if (! $this->package_type) {
throw new \ErrorException('Cannot record earning for an inactive user');
}
It's showing Cannot record earning for an inactive user
I tested with this code:
User model
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable implements MustVerifyEmail
{
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
// ... other code on your User model
protected const RANKS = [
'Guest', // <<<<<<<<<< IMPORTANT TO ADD THIS
'Basic',
'Junior',
'Premium',
'Advanced',
'Senior',
];
// startup call, earnings are added to parents,
// not to the user who made a purchase
public function recordEarning(array $amounts)
{
// as this method is called for the user who just
// made a purchase, this user is required to be active
if (! $this->rank) {
throw new \ErrorException('Cannot record earning for an inactive user');
}
$this->recordEarningToParent($this->rank, $amounts, 0);
}
protected function recordEarningToParent($currentRank, array $amounts, $carry)
{
$parentRank = $this->parentRank($currentRank);
if (! $parentRank) {
return;
}
$parent = $this->parent;
if (! $parent) {
return;
}
$amount = $amounts[$parentRank] + $carry;
// check if active and has amount to record
if ($parent->rank && ($amount > 0)) {
$earnings = new Earnings();
$earnings->user_id = $parent->getKey();
$earnings->amount = $amount;
$earnings->save();
// as the amount was recorded, there
// is nothing to carry to the next parent
$amount = 0;
}
// recursive call to the next parent
$parent->recordEarningToParent($parentRank, $amounts, $amount);
}
// find the parent rank, as nullable rank means inactive user
protected function parentRank($currentRank)
{
$currentRankIndex = array_search($currentRank, self::RANKS);
return self::RANKS[$currentRankIndex + 1] ?? null;
}
// relation definition, for reference
public function parent()
{
return $this->belongsTo(User::class, 'parent_id');
}
public static function makeGuest($parentId)
{
$parent = User::query()->findOrFail($parentId);
$guest = new User();
$guest->rank = 'Guest';
$guest->parent_id = $parentId;
$guest->parent()->associate($parent);
return $guest;
}
}
Earning Model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Earning extends Model
{
}
Order Model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
}
users table data
select `id`, `parent_id`, `rank`, `name`
from `users`;
+--+---------+--------+----+
|id|parent_id|rank |name|
+--+---------+--------+----+
|1 |NULL |Senior |john|
|2 |1 |Advanced|jane|
|3 |2 |Premium |josh|
|4 |3 |Junior |joey|
|5 |4 |Basic |jade|
+--+---------+--------+----+
Test code
<?php
// ./routes/web.php
use App\Models\Earning;
use App\Models\Order;
use App\Models\User;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
// new up an order as I don't have an orders table
$order = new Order();
$order->sponsor_id = 5; // jade with rank Basic
if ($order->user_id > 0) {
$user = User::query()->findOrFail($order->user_id);
} elseif ($order->sponsor_id > 0) {
$user = User::makeGuest($order->sponsor_id);
}
if ($user) {
// Inform the amounts as an array
$user->recordEarning([
'Basic' => 50,
'Junior' => 100,
'Premium' => 150,
'Advanced' => 200,
'Senior' => 250,
]);
}
return Earning::all();
});
Results
[
{
"id": 10,
"user_id": 5,
"amount": 50,
"created_at": "2020-11-20T16:01:13.000000Z",
"updated_at": "2020-11-20T16:01:13.000000Z"
},
{
"id": 11,
"user_id": 4,
"amount": 100,
"created_at": "2020-11-20T16:01:13.000000Z",
"updated_at": "2020-11-20T16:01:13.000000Z"
},
{
"id": 12,
"user_id": 3,
"amount": 150,
"created_at": "2020-11-20T16:01:13.000000Z",
"updated_at": "2020-11-20T16:01:13.000000Z"
},
{
"id": 13,
"user_id": 2,
"amount": 200,
"created_at": "2020-11-20T16:01:13.000000Z",
"updated_at": "2020-11-20T16:01:13.000000Z"
},
{
"id": 14,
"user_id": 1,
"amount": 250,
"created_at": "2020-11-20T16:01:13.000000Z",
"updated_at": "2020-11-20T16:01:13.000000Z"
}
]
Hope it helps.
do I need to remove this line?
if (! $this->package_type) { throw new \ErrorException('Cannot record earning for an inactive user'); }
I don't know what this package_type column is, you might have added it later.
Also, this is the first time I see package_type in any of your code samples.
@rodrigo.pedra wow! you're making it complete, I'm really grateful that you're helping everyone not only me, with this kind of problem I know this is very complex this is for advanced programmers like you and I know this is hard to solved. Thank you very @rodrigo.pedra for your time and effort in helping me. I really appreciate it. I will try this code now on my end and update you after that.
@rodrigo.pedra sorry I didn't change that, it should be rank.
@rodrigo.pedra I've applied your code now and in testing phase in different scenarios of user rank, I'll let you know when I'm done, Thank you very much
Youโre welcome!
More important is that you understand well what is happening and build up on new solutions from this knowledge.
Have a nice day =)
@rodrigo.pedra have a nice day too, Yes I learned a lot from you and you inspire me too, thank you very much again for your time for this I know you did use your time here a lot :)
@rodrigo.pedra Hello again, I'm testing it now, no error in code which is good, but when I do this scenario it's not getting the parent with the expected rank.
Steps to reproduce:
- User Jade (Basic) made a new order
In this scenario I made the parent of Joe to same with him Basic what happened is it insert the amount assigned to Basic which should be inserted to Junior because the ordering of ranks is from Basic to Junior up to parent until it reach Senior
In this scenario it should skip the Joey, the expected rank of Joey is Junior but since he is Basic the amount for him will be added to Josh (Premium). If you still have your code on your machine you cant try it this scenario.
EDIT:
Sorry I previously copied your complete answer earlier and see this line if ($parent->package_type && ($amount > 0)) {. I remember that we recently changed this to if (($parent->rank === $parentRank) && ($amount > 0)) {
It's working fine now =) thank you very much @rodrigo.pedra, you are the first one to help me for this long answering every question and like that. I hope more like you will come in the community a person like you really helping like me who's starting to learn and improve more in this industry, many many thanks! =)
+--+---------+--------+----+
|id|parent_id|rank |name|
+--+---------+--------+----+
|1 |NULL |Senior |john|
|2 |1 |Advanced|jane|
|3 |2 |Premium |josh|
|4 |3 |Basic |joey|
|5 |4 |Basic |jade|
+--+---------+--------+----+
You're welcome! And thanks for the kind words.
I might have copied from a previous more extensive code sample and missed this update.
But glad you got it working.
Have a nice day =)
@rodrigo.pedra Hello, I think I found a problem, in this scenario the order is from guest and let's say Joey Junior is the sponsor_id now, it's not saving the amount for Premium and Advanced it goes directly to Senior, that is what I got when I tested it on guest.
EDIT:
But if the scenario is Jade is thesponsor_id, it's inserting amount with other ranks, but when we skipped a rank and start with any besides Jade, it's not behaving.
+--+---------+--------+----+
|id|parent_id|rank |name|
+--+---------+--------+----+
|1 |NULL |Senior |john|
|2 |1 |Advanced|jane|
|3 |2 |Premium |josh|
|4 |3 |Junior |joey|
|5 |4 |Basic |jade|
+--+---------+--------+----+
What's saved in the database is the total for all ranks (750) including the amount for himself and it goes all to the Senior rank it didn't pass or distribute amounts for the Premium and Advanced
You're welcome rodrigo =)
Hmmm, ok. I changed makeGuest(...) to account for that case. Now it will look for the first active parent and set the Guest's rank as the rank just before the parent's rank.
I also fixed the condition inside recordEarningToParent(...) to match the fix we made previously:
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable implements MustVerifyEmail
{
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
// ... other code on your User model
protected const RANKS = [
'Guest',
'Basic',
'Junior',
'Premium',
'Advanced',
'Senior',
];
// startup call, earnings are added to parents,
// not to the user who made a purchase
public function recordEarning(array $amounts)
{
// as this method is called for the user who just
// made a purchase, this user is required to be active
if (! $this->rank) {
throw new \ErrorException('Cannot record earning for an inactive user');
}
$this->recordEarningToParent($this->rank, $amounts, 0);
}
protected function recordEarningToParent($currentRank, array $amounts, $carry)
{
$parentRank = $this->parentRank($currentRank);
if (! $parentRank) {
return;
}
$parent = $this->parent;
if (! $parent) {
return;
}
$amount = $amounts[$parentRank] + $carry;
// <<<<< UPDATED TO match the last condition fix
if (($parent->rank === $parentRank) && ($amount > 0)) {
$earnings = new Earning();
$earnings->user_id = $parent->getKey();
$earnings->amount = $amount;
$earnings->save();
// as the amount was recorded, there
// is nothing to carry to the next parent
$amount = 0;
}
// recursive call to the next parent
$parent->recordEarningToParent($parentRank, $amounts, $amount);
}
// find the parent rank, as nullable rank means inactive user
protected function parentRank($currentRank)
{
$currentRankIndex = array_search($currentRank, self::RANKS);
return self::RANKS[$currentRankIndex + 1] ?? null;
}
// relation definition, for reference
public function parent()
{
return $this->belongsTo(User::class, 'parent_id');
}
public static function makeGuest($parentId)
{
$parent = User::query()->findOrFail($parentId);
// find the first parent with a rank
while (! $parent->rank) {
$parent = $parent->parent;
if (! $parent) {
// If you want to silently fail here, just
// replace this error by a:
// return;
throw new \ErrorException('Could not find an active sponsor for this guest');
}
}
$count = count(self::RANKS);
// Find the rank a Guest should have,
// based on its first active parent rank.
// The guest rank should be the
// first active parent.
$rank = 'Guest';
for ($i = 0; $i < $count - 1; $i++) {
if (self::RANKS[$i + 1] === $parent->rank) {
$rank = self::RANKS[$i];
break;
}
}
$guest = new User();
$guest->rank = $rank;
$guest->parent_id = $parent->id; // FIX
$guest->parent()->associate($parent);
return $guest;
}
}
EDIT: just added a minor fix. But it actually shouldn't be needed, nor impact the code as we call ->parent()->associate() right after it (which would overwrite the property anyway).
@rodrigo.pedra wow, what a fast response =) I will try this asap, and let you know once I've tested it. Thank you very much. I will read and understand how this works.
@rodrigo.pedra , I didn't quite understand this line of code sorry,
public static function makeGuest($parentId)
{
$parent = User::query()->findOrFail($parentId);
// find the first parent with a rank
while (! $parent->rank) {
// I didn't understand how this works is it a "parent" column in the users table?
$parent = $parent->parent;
.....
$parent = User::query()->findOrFail($parentId);
$parent will be a user instance
while (! $parent->rank) {
It will check if the $parent->rank is NULL (! $parent->rank will evaluate to true when $parent->rank is NULL).
If it is not it will skip the while loop altogether.
Remember inactive users can have NULL ranks?
$parent = $parent->parent;
If it gets inside the while loop, it means the current value of $parent->rank is NULL.
So we need to look at the current $parent's parent user.
As the User model has a parent() relationship defined when we call
$parent = $parent->parent;
We are saying:
- Hey Laravel execute the current
$parentvariable (which is a user instance)->parent()relationship and give me the result - After you get the result replace the
$parentvariable with the value you just fetched.
This way we are traversing up the parent chain until we find a parent will a non-NULL rank value.
(Or if we find no parent we return/thrown in the if inside the loop)
Read this line:
$parent = $parent->parent;
As this:
$parent = $parent->parent()->first();
Which one could read aloud like this:
- execute the
->parent()method on the current$parentuser instance - grab the
->first()record from the relationship returned by the->parent()method - and update the
$parentwith the new record returned by->first()
Hope it makes it clearer.
@rodrigo.pedra I've found another issue, sorry am I asking too much? or is this ok for you and you still have time to answer. Thank you
Now I'm in this scenario that the guest ordered and the sponsor_id is either anyone as long as it's skipped a rank
Scenario:
Josh is the sponsor_id of the guest, currently it inserted the amount below:
Josh - 150
Joey - 200
Senior - 250
But Josh should received the total of 300 because the order is from Guest and it skipped the Basic and Junior so the amount for them will be added to Josh.
+--+---------+--------+----+
|id|parent_id|rank |name|
+--+---------+--------+----+
|1 |NULL |Senior |john|
|2 |1 |Advanced|jane|
|3 |2 |Premium |josh|
|4 |3 |Junior |joey|
|5 |4 |Basic |jade|
+--+---------+--------+----+
Thank you @rodrigo.pedra it's clear now to me. =)
Yes it's clearer now, Thank you for explaining it for me =)
public function recordEarning(array $amounts)
{
// as this method is called for the user who just
// made a purchase, this user is required to be active
if (! $this->rank) {
throw new \ErrorException('Cannot record earning for an inactive user');
}
$initialAmount = 0;
if (! $this->exists) {
// a guest record is not saved
foreach ($amounts as $rank => $amount) {
$initialAmount += $amount;
if ($rank === $this->rank) {
// stop accumulating after reaching the guest's rank
break;
}
}
}
$this->recordEarningToParent($this->rank, $amounts, $initialAmount);
}
Added initial amount calculation for guests
@rodrigo.pedra Thank you very much! =) I will try this now
@rodrigo.pedra wow it's working fine now it added the amount that's skipped as what I've said in the scenario, I will test it more just to make all scenarios are ok =) Thank you very much. I'm wondering how you think, you really mastered PHP and Laravel right? you are blazing fast
Thanks for the kind words.
I've been programming for about 25 years now, so some stuff becomes natural after a while. Not all this years I was a full-time programmer, but programming has always been a great part of may day-to-day workflow. Either by working as a developer or by using it as tool for helping me out in other fields (worked some years in the financial industry more with credit models, statistics, and BI/reporting).
I don't want to mean I am a master on everything, actually I see myself as an apprentice yet, and think I still have a lot to learn and grow as a programmer. On the last 3 to 4 years I became a full-time developer I improved a lot of my skills from daily practice, collaboration, doing courses/training, and helping others.
In Brazil we have a saying (maybe it is a Universal thing): One who teaches, learns twice.
Maybe I am not the most concise responder here at Laracasts forums, or the most clever one, but I really like to collaborate when I am comfortable with the subject and I can tell you I learned a lot from doing this.
I had to deal with hierarchical data-structures/traversal several times in past jobs/projects, so this is a subject I am more familiar with.
"One who teaches, learns twice." Yes this saying is correct =).
I'm lucky guy that you've found my question and this is your specialization. Thank you very much
Actually I'm new here in Laracasts and you're the only one who helped me at this kind of level, like answering every question I have, explaining every answers you give, you're just amazing. I'm grateful I've collaborate with you with this kind of problem I'm facing in the project.
You said you're doing courses/training? do you have a YouTube channel so I will subscribe or any course.
Edit: I'm lack in studying maybe I will study more just like what you did here (but not doing courses yes =))
On the last 3 to 4 years I became a full-time developer I improved a lot of my skills from daily practice, collaboration, doing courses/training, and helping others.
Thanks again for the kind words.
I might have expressed myself better, by:
doing courses/training
I really meant
attending/watching courses/training
In Portuguese (my native language) the first version would be more idiomatic (if translated literally), but I see it means the other way in English, sorry for the confusion.
I have no plans on recording online courses, at least not in the foreseeable future.
Laracasts is my top-of-mind recommendation, and its subscription is worth every cent. You can learn a lot from older series, if you don't mind dealing with some outdated Laravel features. @JeffreyWay is an amazing educator.
There are other learning online resources I also learned a lot, but I don't feel it is right to advertise they here. But for sure just with Laracasts you are already in good company, again, it is my top-of-mind recommendation.
@rodrigo.pedra Thank you for your recommendation, yeah I will purchase a subscription here soon and learn more, Thank you very much. one thing do you have a Facebook or a Twitter?
My Twitter profile is linked in my Laracasts profile page.
Thank you, I will reach you there also =) you're really nice to me and also to the other here who needs help
@rodrigo.pedra Hello and Good day, it's weird now because I tested it now in Guest Scenario. Sorry if I only found this now because I thought we fixed this but re-tested it again and I found a problem.
Scenario:
Guest ordered on Jade's link
Jane rank is Basic
The amount inserted is different now unlike what you tested before, Jane received here a total of 800 which should be 50. I think it's adding all the amounts and it gets inserted to Jane
This is what I get now:
[
{
"id": 10,
"user_id": 5,
"amount": 800,
"created_at": "2020-11-20T16:01:13.000000Z",
"updated_at": "2020-11-20T16:01:13.000000Z"
},
{
"id": 11,
"user_id": 4,
"amount": 100,
"created_at": "2020-11-20T16:01:13.000000Z",
"updated_at": "2020-11-20T16:01:13.000000Z"
},
{
"id": 12,
"user_id": 3,
"amount": 150,
"created_at": "2020-11-20T16:01:13.000000Z",
"updated_at": "2020-11-20T16:01:13.000000Z"
},
{
"id": 13,
"user_id": 2,
"amount": 200,
"created_at": "2020-11-20T16:01:13.000000Z",
"updated_at": "2020-11-20T16:01:13.000000Z"
},
{
"id": 14,
"user_id": 1,
"amount": 250,
"created_at": "2020-11-20T16:01:13.000000Z",
"updated_at": "2020-11-20T16:01:13.000000Z"
}
]
The result of your test is:
[
{
"id": 10,
"user_id": 5,
"amount": 50,
"created_at": "2020-11-20T16:01:13.000000Z",
"updated_at": "2020-11-20T16:01:13.000000Z"
},
{
"id": 11,
"user_id": 4,
"amount": 100,
"created_at": "2020-11-20T16:01:13.000000Z",
"updated_at": "2020-11-20T16:01:13.000000Z"
},
{
"id": 12,
"user_id": 3,
"amount": 150,
"created_at": "2020-11-20T16:01:13.000000Z",
"updated_at": "2020-11-20T16:01:13.000000Z"
},
{
"id": 13,
"user_id": 2,
"amount": 200,
"created_at": "2020-11-20T16:01:13.000000Z",
"updated_at": "2020-11-20T16:01:13.000000Z"
},
{
"id": 14,
"user_id": 1,
"amount": 250,
"created_at": "2020-11-20T16:01:13.000000Z",
"updated_at": "2020-11-20T16:01:13.000000Z"
}
]
The last thing we changed was you added the below code because of this scenario:
Scenario:
Josh is the sponsor_id of the guest, currently it inserted the amount below:
Josh - 150
Joey - 200
Senior - 250
But Josh should received the total of 300 because the order is from Guest and it skipped the Basic and Junior so the amount for them will be added to Josh.
+--+---------+--------+----+
|id|parent_id|rank |name|
+--+---------+--------+----+
|1 |NULL |Senior |john|
|2 |1 |Advanced|jane|
|3 |2 |Premium |josh|
|4 |3 |Junior |joey|
|5 |4 |Basic |jade|
+--+---------+--------+----+
Code that solved this scenario:
public function recordEarning(array $amounts)
{
// as this method is called for the user who just
// made a purchase, this user is required to be active
if (! $this->rank) {
throw new \ErrorException('Cannot record earning for an inactive user');
}
$initialAmount = 0;
if (! $this->exists) {
// a guest record is not saved
foreach ($amounts as $rank => $amount) {
$initialAmount += $amount;
if ($rank === $this->rank) {
// stop accumulating after reaching the guest's rank
break;
}
}
}
$this->recordEarningToParent($this->rank, $amounts, $initialAmount);
}
Indeed it will sum up all amounts when guest user's rank is not listed as a key in the $amounts array, for example when their rank is Guest.
One way to fix is to check if the user guest has an known rank with amount before summing it up:
public function recordEarning(array $amounts)
{
// as this method is called for the user who just
// made a purchase, this user is required to be active
if (! $this->rank) {
throw new \ErrorException('Cannot record earning for an inactive user');
}
$initialAmount = 0;
if (! $this->exists && array_key_exists($this->rank, $amounts)) { // <<<<< UPDATED
// a guest record is not saved
foreach ($amounts as $rank => $amount) {
$initialAmount += $amount;
if ($rank === $this->rank) {
// stop accumulating after reaching the guest's rank
break;
}
}
}
$this->recordEarningToParent($this->rank, $amounts, $initialAmount);
}
Another, maybe more straightforward way, would be informing the Guest rank in the $amounts array:
$user->recordEarning([
'Guest' => 0, // <<<<< ADDED
'Basic' => 50,
'Junior' => 100,
'Premium' => 150,
'Advanced' => 200,
'Senior' => 250,
]);
I would go with the first option.
@rodrigo.pedra hello =) I will try this now and will let you know if it's ok, thank you very much! =)
@rodrigo.pedra I've tried the first option and it's working find if I the sponsor_id is the Basic rank, but when I tried the Junior it's inserted 800 and a different user id (I mean not the id of the last rank based from parent_id).
I've also tried the second option and it inserts the correct value when I tried to make the Basic rank as sponsor_id I also tried making Junior as sponsor_id and Junior got 150 because 50 was added from the Basic rank.
First option:
public function recordEarning(array $amounts)
{
// as this method is called for the user who just
// made a purchase, this user is required to be active
if (! $this->rank) {
throw new \ErrorException('Cannot record earning for an inactive user');
}
$initialAmount = 0;
if (! $this->exists && array_key_exists($this->rank, $amounts)) { // <<<<< UPDATED
// a guest record is not saved
foreach ($amounts as $rank => $amount) {
$initialAmount += $amount;
if ($rank === $this->rank) {
// stop accumulating after reaching the guest's rank
break;
}
}
}
$this->recordEarningToParent($this->rank, $amounts, $initialAmount);
}
Second option:
$user->recordEarning([
'Guest' => 0, // <<<<< ADDED
'Basic' => 50,
'Junior' => 100,
'Premium' => 150,
'Advanced' => 200,
'Senior' => 250,
]);
Well, I got different results than you. Check if you didn't change anything else.
Earning model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Earning extends Model
{
}
User model
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable implements MustVerifyEmail
{
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
// ... other code on your User model
protected const RANKS = [
'Guest',
'Basic',
'Junior',
'Premium',
'Advanced',
'Senior',
];
// startup call, earnings are added to parents,
// not to the user who made a purchase
public function recordEarning(array $amounts)
{
// as this method is called for the user who just
// made a purchase, this user is required to be active
if (! $this->rank) {
throw new \ErrorException('Cannot record earning for an inactive user');
}
$initialAmount = 0;
if (! $this->exists && array_key_exists($this->rank, $amounts)) {
// a guest record is not saved
foreach ($amounts as $rank => $amount) {
$initialAmount += $amount;
if ($rank === $this->rank) {
// stop accumulating after reaching the guest's rank
break;
}
}
}
$this->recordEarningToParent($this->rank, $amounts, $initialAmount);
}
protected function recordEarningToParent($currentRank, array $amounts, $carry)
{
$parentRank = $this->parentRank($currentRank);
if (! $parentRank) {
return;
}
$parent = $this->parent;
if (! $parent) {
return;
}
$amount = $amounts[$parentRank] + $carry;
// <<<<< UPDATED TO match the last condition fix
if (($parent->rank === $parentRank) && ($amount > 0)) {
$earnings = new Earning();
$earnings->user_id = $parent->getKey();
$earnings->amount = $amount;
$earnings->save();
// as the amount was recorded, there
// is nothing to carry to the next parent
$amount = 0;
}
// recursive call to the next parent
$parent->recordEarningToParent($parentRank, $amounts, $amount);
}
// find the parent rank, as nullable rank means inactive user
protected function parentRank($currentRank)
{
$currentRankIndex = array_search($currentRank, self::RANKS);
return self::RANKS[$currentRankIndex + 1] ?? null;
}
// relation definition, for reference
public function parent()
{
return $this->belongsTo(User::class, 'parent_id');
}
public static function makeGuest($parentId)
{
$parent = User::query()->findOrFail($parentId);
// find the first parent with a rank
while (! $parent->rank) {
$parent = $parent->parent;
if (! $parent) {
// If you want to silently fail here, just
// replace this error by a:
// return;
throw new \ErrorException('Could not find an active sponsor for this guest');
}
}
$count = count(self::RANKS);
// Find the rank a Guest should have,
// based on its first active parent rank.
// The guest rank should be the
// first active parent.
$rank = 'Guest';
for ($i = 0; $i < $count - 1; $i++) {
if (self::RANKS[$i + 1] === $parent->rank) {
$rank = self::RANKS[$i];
break;
}
}
$guest = new User();
$guest->rank = $rank;
$guest->parent_id = $parent->id; // FIXED
$guest->parent()->associate($parent);
return $guest;
}
}
users table data
select `id`, `parent_id`, `rank`, `name` from `users`
+--+---------+--------+----+
|id|parent_id|rank |name|
+--+---------+--------+----+
|1 |NULL |Senior |john|
|2 |1 |Advanced|jane|
|3 |2 |Premium |josh|
|4 |3 |Junior |joey|
|5 |4 |Basic |jade|
+--+---------+--------+----+
First run (sponsor is Jade (id: 5, rank: Basic)
<?php
// ./routes/web.php
use App\Models\Earning;
use App\Models\Order;
use App\Models\User;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
// new up an order as I don't have an orders table
$order = new Order();
$order->sponsor_id = 5; // jade with rank = Basic
if ($order->user_id > 0) {
$user = User::query()->findOrFail($order->user_id);
} elseif ($order->sponsor_id > 0) {
$user = User::makeGuest($order->sponsor_id);
}
if ($user) {
// Inform the amounts as an array
$user->recordEarning([
'Basic' => 50,
'Junior' => 100,
'Premium' => 150,
'Advanced' => 200,
'Senior' => 250,
]);
}
return Earning::query()
// just the ones we just added
// this is just for easier testing,
// DON'T USE IN PRODUCTION
->where('updated_at', '>', now()->subSecond())
->get();
});
[
{
"id": 124,
"user_id": 5,
"amount": 50,
"created_at": "2020-11-21T13:18:48.000000Z",
"updated_at": "2020-11-21T13:18:48.000000Z"
},
{
"id": 125,
"user_id": 4,
"amount": 100,
"created_at": "2020-11-21T13:18:48.000000Z",
"updated_at": "2020-11-21T13:18:48.000000Z"
},
{
"id": 126,
"user_id": 3,
"amount": 150,
"created_at": "2020-11-21T13:18:48.000000Z",
"updated_at": "2020-11-21T13:18:48.000000Z"
},
{
"id": 127,
"user_id": 2,
"amount": 200,
"created_at": "2020-11-21T13:18:48.000000Z",
"updated_at": "2020-11-21T13:18:48.000000Z"
},
{
"id": 128,
"user_id": 1,
"amount": 250,
"created_at": "2020-11-21T13:18:48.000000Z",
"updated_at": "2020-11-21T13:18:48.000000Z"
}
]
Second Run, sponsor is Joey (id: 4, rank: Junior)
<?php
// ./routes/web.php
use App\Models\Earning;
use App\Models\Order;
use App\Models\User;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
// new up an order as I don't have an orders table
$order = new Order();
$order->sponsor_id = 4; // joey with rank = Junior
if ($order->user_id > 0) {
$user = User::query()->findOrFail($order->user_id);
} elseif ($order->sponsor_id > 0) {
$user = User::makeGuest($order->sponsor_id);
}
if ($user) {
// Inform the amounts as an array
$user->recordEarning([
'Basic' => 50,
'Junior' => 100,
'Premium' => 150,
'Advanced' => 200,
'Senior' => 250,
]);
}
return Earning::query()
// just the ones we just added
// this is just for easier testing,
// DON'T USE IN PRODUCTION
->where('updated_at', '>', now()->subSecond())
->get();
});
[
{
"id": 129,
"user_id": 4,
"amount": 150,
"created_at": "2020-11-21T13:20:29.000000Z",
"updated_at": "2020-11-21T13:20:29.000000Z"
},
{
"id": 130,
"user_id": 3,
"amount": 150,
"created_at": "2020-11-21T13:20:29.000000Z",
"updated_at": "2020-11-21T13:20:29.000000Z"
},
{
"id": 131,
"user_id": 2,
"amount": 200,
"created_at": "2020-11-21T13:20:29.000000Z",
"updated_at": "2020-11-21T13:20:29.000000Z"
},
{
"id": 132,
"user_id": 1,
"amount": 250,
"created_at": "2020-11-21T13:20:29.000000Z",
"updated_at": "2020-11-21T13:20:29.000000Z"
}
]
Third Run, sponsor is Jane (id: 2, rank: Advanced)
<?php
// ./routes/web.php
use App\Models\Earning;
use App\Models\Order;
use App\Models\User;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
// new up an order as I don't have an orders table
$order = new Order();
$order->sponsor_id = 2; // jane with rank = Advanced
if ($order->user_id > 0) {
$user = User::query()->findOrFail($order->user_id);
} elseif ($order->sponsor_id > 0) {
$user = User::makeGuest($order->sponsor_id);
}
if ($user) {
// Inform the amounts as an array
$user->recordEarning([
'Basic' => 50,
'Junior' => 100,
'Premium' => 150,
'Advanced' => 200,
'Senior' => 250,
]);
}
return Earning::query()
// just the ones we just added
// this is just for easier testing,
// DON'T USE IN PRODUCTION
->where('updated_at', '>', now()->subSecond())
->get();
});
[
{
"id": 133,
"user_id": 2,
"amount": 500,
"created_at": "2020-11-21T13:21:30.000000Z",
"updated_at": "2020-11-21T13:21:30.000000Z"
},
{
"id": 134,
"user_id": 1,
"amount": 250,
"created_at": "2020-11-21T13:21:30.000000Z",
"updated_at": "2020-11-21T13:21:30.000000Z"
}
]
Be sure to either remove the bogus records from Earnings table, or to check only the records added in the last run.
@rodrigo.pedra thank you for showing me some scenarios and the complete code, I'll review this and check my code if I made a mistake.
Thank you, currently I'm removing the bogus records from the earnings table
@rodrigo.pedra In this function, last time you update this line of code
// before
$guest->parent_id = $parentId;
// after, I'm using this now
$guest->parent_id = $parent->id;
public static function makeGuest($parentId)
{
$parent = User::query()->findOrFail($parentId);
// find the first parent with a rank
while (! $parent->rank) {
$parent = $parent->parent;
if (! $parent) {
// If you want to silently fail here, just
// replace this error by a:
// return;
throw new \ErrorException('Could not find an active sponsor for this guest');
}
}
$count = count(self::RANKS);
// Find the rank a Guest should have,
// based on its first active parent rank.
// The guest rank should be the
// first active parent.
$rank = 'Guest';
for ($i = 0; $i < $count - 1; $i++) {
if (self::RANKS[$i + 1] === $parent->rank) {
$rank = self::RANKS[$i];
break;
}
}
$guest = new User();
$guest->rank = $rank;
// before
$guest->parent_id = $parentId;
// after, I'm using this now
$guest->parent_id = $parent->id;
$guest->parent()->associate($parent);
return $guest;
}
Yes, I made a comment in an edit I made here:
EDIT: just added a minor fix. But it actually shouldn't be needed, nor impact the code as we call
->parent()->associate()right after it (which would overwrite the property anyway).
So as commented there, it doesn't really matter as ->parent()->associate() is called just after that. Actually you can remove the $guest->parent_id = $parent->id; line and it would still work as expected.
Now I see I edited directly in the comment and forgot to update my local code. Will update last code sample.
Thank you for explaining it to me, I got it working =) I just copy again the code you commented and it's working now using the first option.
Just fixed locally and no changes on the results.
Updated the test code to take the sponsor id as a query parameter for easier testing
<?php
// ./routes/web.php
use App\Models\Earning;
use App\Models\Order;
use App\Models\User;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
// new up an order as I don't have an orders table
$order = new Order();
$order->sponsor_id = request()->query('sponsor');
// this block is just to mimic your usage sample
if ($order->user_id > 0) {
$user = User::query()->findOrFail($order->user_id);
} elseif ($order->sponsor_id > 0) {
$user = User::makeGuest($order->sponsor_id);
} else {
// added as fallback condition
abort(404);
}
if ($user) {
// Inform the amounts as an array
$user->recordEarning([
'Basic' => 50,
'Junior' => 100,
'Premium' => 150,
'Advanced' => 200,
'Senior' => 250,
]);
}
return Earning::query()
// just the ones we just added
// this is just for easier testing,
// DON'T USE IN PRODUCTION
->where('updated_at', '>', now()->subSecond())
->get();
});
Sponsor is Jade (id: 5, rank: Basic)
http://127.0.0.1:8000/?sponsor=5
[
{
"id": 155,
"user_id": 5,
"amount": 50,
"created_at": "2020-11-21T13:40:51.000000Z",
"updated_at": "2020-11-21T13:40:51.000000Z"
},
{
"id": 156,
"user_id": 4,
"amount": 100,
"created_at": "2020-11-21T13:40:51.000000Z",
"updated_at": "2020-11-21T13:40:51.000000Z"
},
{
"id": 157,
"user_id": 3,
"amount": 150,
"created_at": "2020-11-21T13:40:51.000000Z",
"updated_at": "2020-11-21T13:40:51.000000Z"
},
{
"id": 158,
"user_id": 2,
"amount": 200,
"created_at": "2020-11-21T13:40:51.000000Z",
"updated_at": "2020-11-21T13:40:51.000000Z"
},
{
"id": 159,
"user_id": 1,
"amount": 250,
"created_at": "2020-11-21T13:40:51.000000Z",
"updated_at": "2020-11-21T13:40:51.000000Z"
}
]
Sponsor is Joey (id: 4, rank: Junior)
http://127.0.0.1:8000/?sponsor=4
[
{
"id": 160,
"user_id": 4,
"amount": 150,
"created_at": "2020-11-21T13:41:31.000000Z",
"updated_at": "2020-11-21T13:41:31.000000Z"
},
{
"id": 161,
"user_id": 3,
"amount": 150,
"created_at": "2020-11-21T13:41:31.000000Z",
"updated_at": "2020-11-21T13:41:31.000000Z"
},
{
"id": 162,
"user_id": 2,
"amount": 200,
"created_at": "2020-11-21T13:41:31.000000Z",
"updated_at": "2020-11-21T13:41:31.000000Z"
},
{
"id": 163,
"user_id": 1,
"amount": 250,
"created_at": "2020-11-21T13:41:31.000000Z",
"updated_at": "2020-11-21T13:41:31.000000Z"
}
]
Sponsor is Josh (id: 3, rank: Premium)
http://127.0.0.1:8000/?sponsor=3
[
{
"id": 164,
"user_id": 3,
"amount": 300,
"created_at": "2020-11-21T13:42:40.000000Z",
"updated_at": "2020-11-21T13:42:40.000000Z"
},
{
"id": 165,
"user_id": 2,
"amount": 200,
"created_at": "2020-11-21T13:42:40.000000Z",
"updated_at": "2020-11-21T13:42:40.000000Z"
},
{
"id": 166,
"user_id": 1,
"amount": 250,
"created_at": "2020-11-21T13:42:40.000000Z",
"updated_at": "2020-11-21T13:42:40.000000Z"
}
]
Sponsor is Jane (id: 2, rank: Advanced)
http://127.0.0.1:8000/?sponsor=2
[
{
"id": 167,
"user_id": 2,
"amount": 500,
"created_at": "2020-11-21T13:43:08.000000Z",
"updated_at": "2020-11-21T13:43:08.000000Z"
},
{
"id": 168,
"user_id": 1,
"amount": 250,
"created_at": "2020-11-21T13:43:08.000000Z",
"updated_at": "2020-11-21T13:43:08.000000Z"
}
]
Sponsor is John (id: 1, rank: Senior)
http://127.0.0.1:8000/?sponsor=1
[
{
"id": 169,
"user_id": 1,
"amount": 750,
"created_at": "2020-11-21T13:43:24.000000Z",
"updated_at": "2020-11-21T13:43:24.000000Z"
}
]
thank you you make it more easier to test =) I'm very grateful on how you help me with this. I tested it on my end for the Guest and all is working fine =), now I will test for the authenticated user.
Youโre welcome! Have a nice day =)
Hi @rodrigo.pedra , Thank you very much sir! =) I'm testing also in authenticated user and it's also working and inserting the correct amount as what I'm expecting, you're really amazing with this work, you said before that this is not the kind of help that the user will receive and just a hint about how to make all working. But in this question you really helped me a lot explaining all the code you make. I'm sorry if I'm like spoon feed, but because I don't really have an idea for this kind of code I know it's complex, but since this is your specialty you really helped. Thank you much @rodrigo.pedra . Have nice day =)
@rodrigo.pedra Hello, sorry I have a problem again now but not in the amounts inserted it's all ok for both Guest and Authenticated, this is actually the ranks I have and I didn't included in the question because I thought I can make it work but suddenly I can't make it work. I think it's cutting the recursion I think in this function recordEarningToParent(...) because I'm making the Rank II,III,IV read as only Rank without II, III, IV to be able to read it in the ranks below:
protected const RANKS = [
'Guest',
'Basic',
'Junior',
'Premium',
'Advanced',
'Senior',
];
This are the real ranks that actually saved on the column rank in my users table.
Basic
Junior
- Junior
- Junior II
- Junior III
- Junior IV
Premium
- Premium
- Premium II
- Premium III
- Premium IV
Advanced
- Advanced
- Advanced II
- Advanced III
- Advanced IV
Senior
- Senior
- Senior II
- Senior III
- Senior IV
The amount are inserted correctly if the rank doesn't have II, III, IV but for example the parent_id became Junior II now it's not inserting the amount for him even I'm doing a condition to merge it as Junior only without II
This is my code I'm working on:
public function recordEarning(array $amounts)
{
// as this method is called for the user who just
// made a purchase, this user is required to be active
if (! $this->rank) {
throw new \ErrorException('Cannot record earning for an inactive user');
}
// trying to make the rank with Rank Name II, Rank Name III, Rank Name IV to Rank Name only
$rank = 'Guest';
if ($this->rank == 'Basic') {
$rank = 'Basic';
} elseif ($this->rank == 'Junior' || $this->rank == "Junior II" || $this->rank == "Junior III" || $this->rank == "Junior IV") {
$rank = 'Junior';
} elseif ($this->rank == 'Premium' || $this->rank == "Premium II" || $this->rank == "Premium III" || $this->rank == "Premium IV") {
$rank = 'Premium';
} elseif ($this->rank == 'Advanced' || $this->rank == "Advanced II" || $this->rank == "Advanced III" || $this->rank == "Advanced IV") {
$rank = 'Advanced';
} elseif ($this->rank == 'Senior' || $this->rank == "Senior II" || $this->rank == "Senior III" || $this->rank == "Senior IV") {
$rank = 'Senior';
}
$initialAmount = 0;
if (! $this->exists && array_key_exists($rank, $amounts)) {
// a guest record is not saved
foreach ($amounts as $rank => $amount) {
$initialAmount += $amount;
if ($rank === $rank) {
// stop accumulating after reaching the guest's rank
break;
}
}
}
$this->recordEarningToParent($this->rank, $amounts, $initialAmount);
}
protected function recordEarningToParent($rank, array $amounts, $carry)
{
// trying to make the rank with Rank Name II, Rank Name III, Rank Name IV to Rank Name only
if ($rank == 'Guest') {
$currentRank = 'Guest';
} elseif ($rank == 'Basic') {
$currentRank = 'Basic';
} elseif ($rank == 'Junior' || $rank == "Junior II" || $rank == "Junior III" || $rank == "Junior IV") {
$currentRank = 'Junior';
} elseif ($rank == 'Premium' || $rank == "Premium II" || $rank == "Premium III" || $rank == "Premium IV") {
$currentRank = 'Premium';
} elseif ($rank == 'Advanced' || $rank == "Advanced II" || $rank == "Advanced III" || $rank == "Advanced IV") {
$currentRank = 'Advanced';
} elseif ($rank == 'Senior' || $rank == "Senior II" || $rank == "Senior III" || $rank == "Senior IV") {
$currentRank = 'Senior';
}
$parentRank = $this->parentRank($currentRank);
if (! $parentRank) {
return;
}
$parent = $this->parent;
if (! $parent) {
return;
}
$amount = $amounts[$parentRank] + $carry;
// check if active and has amount to record
if (($parent->rank === $parentRank) && ($amount > 0)) {
$earnings = new Earning();
$earnings->user_id = $parent->getKey();
$earnings->amount = $amount;
$earnings->save();
// as the amount was recorded, there
// is nothing to carry to the next parent
$amount = 0;
}
// recursive call to the next parent
$parent->recordEarningToParent($parentRank, $amounts, $amount,);
}
I'm thinking that the problem is in this line if it's calling the next parent
// recursive call to the next parent
$parent->recordEarningToParent($parentRank, $amounts, $amount, $orderID);
Right now with the current implementation, If you have more ranks you'll need to add them to the RANKS array and to your $amounts.
So when calculating the next parent rank, or the current amount it works as expected. Next parent rank just looks in an array for the next element based on a given element. So it needs all possible elements.
// in User model
protected const RANKS = [
'Guest',
'Basic',
'Junior',
'Junior II',
'Junior III',
'Junior IV',
'Premium',
'Premium II',
'Premium III',
'Premium IV',
'Advanced',
'Advanced II',
'Advanced III',
'Advanced IV',
'Senior',
'Senior II',
'Senior III',
'Senior IV',
];
// in controller
$user->recordEarning([
'Basic' => 50,
'Junior' => 100,
'Junior II' => 100,
'Junior III' => 100,
'Junior IV' => 100,
'Premium' => 150,
'Premium II' => 150,
'Premium III' => 150,
'Premium IV' => 150,
'Advanced' => 200,
'Advanced II' => 200,
'Advanced III' => 200,
'Advanced IV' => 200,
'Senior' => 250,
'Senior II' => 250,
'Senior III' => 250,
'Senior IV' => 250,
]);
If you need clustered ranks, or a user's parent rank can have "holes" (not every rank will be fulfilled by some other parent user) you'll need to change the current implementation to account for that.
For accounting for clustered ranks, you mostly will need to change how the parentRank() method works. As already said, right now it only searches for the next element within an array based on a given one.
Maybe having these ranks in a table, where you could have a "group" column to cluster similar ranks and aid with calculation, or having a custom class to calculate the earnings might be a better idea.
But this is out of scope of this thread which already has almost 70 messages, and every time there is a different unknown aspect that you did not mention before. I fear I can no long help you with that task, sorry.
@rodrigo.pedra sorry for late response, Thank you very much again for helping me, I'm sorry that I didn't included all in my question and taking it to the comments to build all the requirements. I understand you @rodrigo.pedra Thank you again for helping me through. Have a nice day =)
You're welcome! Have a nice day too =)
@rodrigo.pedra I'm sorry for this but I found a different scenario, I don't know if you can still help me with this I'm grateful for all of your help within this thread and I know it's too much now. But if you consider still helping me I'm really grateful.
I found this problem just now in testing a scenario, I didn't think of this before when I'm testing it but now it's a problem.
The problem happens because it's expecting the chain would be Basic->Junior->Premium->Advanced->Senior but without a record in the database it's failing to chain. But if there's a record to skip, it's working fine.
Scenario 1:
In this scenario a Guest ordered on Jade, the sponsor_id is Jade
- Jade received 50
- Joey didn't receive because the
parentrank should beJunior - Josh also didn't receive because the code expecting him to be
JuniorbutSenioris the parent ofJoey, but it should insert the amount from the missing ranksJunior, Premium, Advanced.
+--+---------+---------+------+
|id|parent_id| rank |name|
+--+---------+---------+------+
|1 |NULL | Senior |josh|
|2 |1 | Basic |joey|
|3 |2 | Basic |jade|
+--+---------+--------+----+
I think this function is the one who handles that
protected function parentRank($currentRank)
{
$currentRankIndex = array_search($currentRank, self::RANKS);
return self::RANKS[$currentRankIndex + 1] ?? null;
}
Scenario 2:
If this is the scenario John received earnings instead of Josh it skipped Josh because it's expecting it to be 'Advanced, but the earnings should insert to Joshbecause he's the firstparentwith the rank ofSeniorthe total amounts for the missing rankJunior, Premium, Advanced` should go to him.
+--+---------+--------+----+
|id|parent_id| rank |name|
+--+---------+--------+----+
|1 |NULL | Senior |john|
|2 |1 | Senior |josh|
|3 |2 | Basic |joey|
|4 |3 | Basic |jade|
+--+---------+--------+----+
Working Scenario:
In this is the scenario it's working because it's skipping a record before it reach to Senior
- Jade received 50
- John recevied 700 because it skipped
Junior,Premium,Advanced
+--+---------+--------+----+
|id|parent_id| rank |name|
+--+---------+--------+----+
|1 |NULL | Senior |john|
|2 |1 | Basic |jane|
|3 |2 | Basic |josh|
|4 |3 | Basic |joey|
|5 |4 | Basic |jade|
+--+---------+--------+----+
I hope you understand what I'm trying to explain and still help me @rodrigo.pedra Thank you very much
Hey @whoami1509 ,
To be honest it is becoming very hard to follow up all different use-cases and requirements.
I asked before (some days ago) about a scenario where a user's parent chain could have a different levels than the one "ideal" flow. By then that scenario didn't draw your attention as a potential problem, and we moved on considering the happy path (no holes in the levels).
But that didn't last long, as you remembered you need to add guest users, and more recently that each level has sub-levels, among other changes/requirements.
The implementation has already become messy as for each new use-case we add an if clause or condition here and there, and then another use-case you "forgot" or wasn't aware comes up.
I would start over with a dedicated class to calculate this, and maybe I would consider storing the ranks (and likely rank groups/clusters) in a table for easier retrieval.
I really mean it, if I were the one hired for this job I would start from scratch at this point. The code we already have become hard to understand, and hard to modify. But I wouldn't start over from nowhere.
My suggestion is that you:
- Write down all use-cases and requirements for this feature.
- Really, do this. Write "ALL" use-case and requirements down. Be it on a paper, or text document.
- Try to identify similar cases, cluster different cases, and then come up with a structure you think would fit all those use-cases
- Problem-solving, specially solving complex problems, is more about planning and trying to understand the problem, break it into smaller pieces, than trying to rush and implementation and fix any missing requirements with "band-aids" or ingenious solutions fixes.
- Consider adopting a design pattern to help building the solution
- Although we are into this task for 2 weeks now, I don't feel I know sufficiently about on what you need to really solve, as every time a new use-case arises, but I think a Behavioral pattern, such as the State, Strategy, or Chain of responsibility pattern could help you into breaking this problem into smaller pieces easier to understand.
- Use a TDD (Test Driven Development) approach with proper unit tests to implement and address each use-case and requirement listed above.
- Start with unit tests for basic use-cases (the happy paths/ideal use cases).
- Then add the more complex ones
- Then start testing when things could break, and how your solution handles that.
Being honest again, I don't use TDD for every feature in the projects I work on. I do write tests for most features, but often I write them after I have some code working. And I often write feature tests instead of unit tests.
But I learned to reach for TDD when I have a larger feature with lots of conflicting use-cases as it seems to be your case there. It specially helps out when the client comes up with new features and requirements every time a use-case is solved.
Don't get me wrong, this a more common scenario than every one would like to, and doesn't mean someone his hiding information or doing a bad job on the requirements analysis phase for such a feature or project.
I recently had a project around a custom FFMpeg command builder. Every time we deployed a new version both the end-users come up with a new requirement. Mostly because they weren't aware beforehand of what was possible to do through a web interface regarding video editing, or because when using the app for editing more videos other than the ones they first thought on editing, they found those videos need additional processing requirements (cropping, color correction, resizing, etc.)
As FFMpeg has a very cumbersome and sometimes inconsistent way of composing parameters and building filters, the only way I could make progress was starting over from scratch, use a TDD approach, and ensure a new requirement wouldn't blow up an already implemented use-case, or if it did blow-up, having unit tests made clear where I need to focus on solving any problems.
As I already said, it become very hard to follow up all different use-cases and requirements that keeps changing, and the solution I built for you is already messy and hard to grasp.
Also sometimes when you have a problem your code sample have a different implementation with changes you made for reasons I am not aware. I feel there is always something you don't share, either due to hide commercial knowledge, or because you deem it is not relevant, or that you can handle that use-case later by yourself. But that often lead to a never-ending "we are almost there...", "can't make this work...", "I forgot about this..." and that is not being productive.
I am afraid I won't be able to help you further with this task. It grew very out-of-scope of what a forums assistance is expected to be.
I think I am largely responsible for letting this grow out of control, and I apologize for that. Really, it is mostly my fault we got to this point, sorry for that.
Please consider the advice on writing down use-cases and using unit test for re-building this feature from scratch. Of course the current implementation can be useful as a reference for some use-cases, but I would really start over.
Laracasts has some videos about using TDD, and there is a recent series about long refactorings that might help you. Here are some links on those series:
https://laracasts.com/series/refactoring-workshops
https://laracasts.com/series/build-a-laravel-app-with-tdd
I wish you the best luck and hope anything from this response helps you out. I also hope I could share anything useful on any of the responses in this thread.
If you read until here, thanks for your patience. Have a nice day =)
Hello @rodrigo.pedra , First of all thank you for your support in this task I know it's very hard and I'm sorry that it's like we're going step by step on the problems until its become ok on all use-case, but that leads to a problem and I'm very sorry for that, the solution you made for me is working fine but up to the last problem I have it didn't fit because I didn't have proper way of how this feature works on all use-cases, as what you've said write down the use-cases I should've done that before asking a help from you to be able to identify all the problems and not relying on every problem encountered. I'm very sorry for that because it leads to the code become messy.
Actually, I'm not following the TDD in Laravel and haven't tried that before. Thank you for your suggestion and linking through tutorials. I will study of that because that is really required in Laravel development and I'm not following that I may not call myself a Laravel programmer for that, really new to this maybe but I'm trying hard.
Thank you for sharing the problem you previously had about FFMpeg, I understand how it affects every code that's already built and leading to starting again from scratch.
Thank you again @rodrigo.pedra for the time you spent in helping me in this task the effort you give, testing it on your side and everything else, I'm really grateful for that. For now I really don't know how will I start this from scratch.
Also I group the ranks with II,III,IV into one as you suggested before. I saved them in the database as Basic,Junior,Premium,Advanced,Senior.
@rodrigo.pedra may I post this to another thread and try to ask for help from others?
Sure, no problem at all
thank you very much @rodrigo.pedra
@rodrigo.pedra hello, I posted the question just to let you know. Thank you
https://laracasts.com/discuss/channels/laravel/adding-amount-in-parents-recursion
wow you are amazing @rodrigo.pedra , actually yes I have the is_active on my users table but didn't include it in the question, I really appreciate your help sir, it's very clear.
you inspire me in your answer :)
You're welcome! Have a nice day =)
Hello @rodrigo.pedra I hope the notification will reach you, I've another question, Thank you
Hi @whoami1509
I only receive e-mail notifications for threads I explicitly hit the "FOLLOW" button below the "REPLY" button on the sidebar.
I never received notifications for threads I just reply to, neither by email or in app (on the sidebar that shows up when one click their avatar).
Actually I never received any in-app notification whatsoever. Don't know if it is due my username having a dot in it. But I am not willing to change my username so I don't loose any history.
I usually track past threads once in while when I visit the "My participation" filter in the sidebar. That is why I might take a while to see any new messages.
Also, for some much older threads I rather not respond and expect the interested person to open a new thread instead, so more people can help quicker.
Thank you so much @rodrigo.pedra for your time in helping me. I really appreciate it. :)
You're welcome =)
@rodrigo.pedra sorry, can you help me?
Please or to participate in this conversation.