I'm hoping somebody will help me with this.. Thank you.
adding amount in parents recursion
Good day everyone, I recently posted a question here regarding adding the amount to the next parent in loop and it goes well with the help of @rodrigo.pedra, he really helped me a lot and the code below is the result of that, it's been a week now after I posted the origianal question below:
https://laracasts.com/discuss/channels/laravel/laravel-add-amount-to-the-next-parent-in-loop
The below code is working like this:
- Jade sent a link to a person and that person order without logging in (Guest)
- After making an order the earnings will insert to the parents with rank in chain of
Basic -> Junior -> Premium -> Advanced -> Senior - In that case earnings will insert like this:
Jade = 50
Joey = 100
Josh = 150
Jane = 200
John = 250
Each rank does have a corresponding value
Basic = 50
Junior = 100
Premium = 150
Advanced = 200
Senior = 250
users table
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|
+--+---------+--------+----+
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;
protected $fillable = [
'name',
'email',
'password',
];
protected $hidden = [
'password',
'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
];
protected const RANKS = [
'Guest',
'Basic',
'Junior',
'Premium',
'Advanced',
'Senior',
];
// startup call, earnings are added to parents,
// unless order is from Guest
public function recordEarning(array $amounts)
{
// as this method is called for the user who just
// made a purchase, this user is required to have rank
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;
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) {
throw new \ErrorException('Could not find an active sponsor for this guest');
}
}
$count = count(self::RANKS);
$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;
$guest->parent()->associate($parent);
return $guest;
}
}
Running the insertion of Earnings
<?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 order from Guest
$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()
// showing of earnings
->where('updated_at', '>', now()->subSecond())
->get();
});
Adding the amount to parent if the chain is not in order
If this is the scenario:
+--+---------+--------+----+
|id|parent_id|rank |name|
+--+---------+--------+----+
|1 |NULL |Senior |john|
|2 |1 |Advanced|jane|
|3 |2 |Junior |josh|
|4 |3 |Basic |joey|
|5 |4 |Basic |jade|
+--+---------+--------+----+
- Jade sent a link to a person and that person order without logging in (Guest)
- After making an order the earnings will insert to the parents with rank in chain of
Basic -> Junior -> Premium -> Advanced -> Senior - But in that case the chain is not in order so the earnings will insert like this:
Jade = 50
Jane = 450
John = 250
- This happened because
Joeyrank should beJuniorandJoshrank should bePremiumso it skippedJuniorandPremium, therefore - the amount that should be inserted to
JoeyandJoshis added to the amountJanewill received,Janereceived a total of450because100is from rankJunior(Joey) and150is from rankPremium(Josh)
The above code is working fine if the above are the scenarios
But if the scenario below; the amount is not inserting because the records in database is not complete or some parents doesn't exist, and the current code is expecting some parents to skip.
Problem Scenario #1
+--+---------+--------+----+
|id|parent_id|rank |name|
+--+---------+--------+----+
|1 |NULL |Senior |john|
|2 |1 |Basic |jade|
+--+---------+--------+----+
- Jade sent a link to a person and that person order without logging in (Guest)
- After making an order the earnings will insert to the parents with rank in chain of
Basic -> Junior -> Premium -> Advanced -> Senior - But in that case the it's
Basic -> Senior, it skipsJunior, Premium, Advancedso the amount for them should be inserted toSenior(John)
Problem Scenario #2
+--+---------+--------+----+
|id|parent_id|rank |name|
+--+---------+--------+----+
|1 |NULL |Senior |john|
|2 |1 |Advanced|jane|
|3 |2 |Advanced|josh|
|4 |3 |Basic |joey|
|5 |4 |Basic |jade|
+--+---------+--------+----+
- Jade sent a link to a person and that person order without logging in (Guest)
- After making an order the earnings will insert to the parents with rank in chain of
Basic -> Junior -> Premium -> Advanced -> Senior - But in that case the it's
Basic -> Basic -> Advanced, it skipsPremiumso the amount for them should be inserted toAdvanced(Josh), But - what's happening is, it also skips
Joshbecause in the code it's expectingJoshrank isPremium. - The amount of
450is inserted toJanewhich is wrong because there isAdvancedrank beforeJane, thereforeJoshshould received the450instead ofJane - Jane should be ignored here because there's a first
Advancedrank before here.
Amounts inserted on the current code if that is the Scenario
Jade = 50
Jane = 450
John = 250
Amounts expecting to insert
Jade = 50
Josh = 450
John = 250
Please or to participate in this conversation.