Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command to recalculate user balances and page with an overview of balance states #188

Merged
merged 3 commits into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions app/Console/Commands/RecalculateAllBalances.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace BB\Console\Commands;

use BB\Entities\User;
use BB\Jobs\RecalculateBalance as RecalculateBalanceJob;
use Illuminate\Console\Command;

class RecalculateAllBalances extends Command
{
protected $signature = 'payments:recalculate-all-balances {--apply}';

protected $description = 'Recalculates the cash_balance for all users.';

/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$users = User::where(function ($query) {
$query->active();
})
->orWhere(function ($query) {
$query->recentlyLapsed();
})
->get();

foreach ($users as $user) {
if ($this->option('apply')) {
RecalculateBalanceJob::dispatch($user);
$this->info("Queued a job to recalculate the cash_balance of {$user->name} ({$user->id}).");
} else {
$this->info("Would have queued a job to recalculate the cash_balance of {$user->name} ({$user->id}).");
}
}
}
}
16 changes: 14 additions & 2 deletions app/Entities/Payment.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<?php namespace BB\Entities;
<?php

namespace BB\Entities;


use Illuminate\Database\Eloquent\Model;
Expand Down Expand Up @@ -34,7 +36,17 @@ class Payment extends Model
* @var array
*/
protected $fillable = [
'source', 'source_id', 'user_id', 'amount', 'fee', 'amount_minus_fee', 'status', 'reason', 'created_at', 'reference'
'source',
'source_id',
'user_id',
'amount',
'fee',
'amount_minus_fee',
'status',
'reason',
'created_at',
'reference',
'paid_at',
];


Expand Down
3 changes: 1 addition & 2 deletions app/Exceptions/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,7 @@ public function render($request, Exception $e)
}

if (config('app.debug') && $this->shouldReport($e) && !$request->wantsJson()) {
// @phpstan-ignore-next-line
return $this->renderExceptionWithWhoops($e);
return parent::render($request, $e);
}

if ($request->wantsJson()) {
Expand Down
36 changes: 36 additions & 0 deletions app/Http/Controllers/PaymentBalancesController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace BB\Http\Controllers;

use BB\Entities\User;

class PaymentBalancesController extends Controller
{
public function index()
{
$activeUsersInCreditQty = User::where('active', true)->where('cash_balance', '>', 0)->count();
$activeUsersInCreditSum = User::where('active', true)->where('cash_balance', '>', 0)->sum('cash_balance') / 100;

$activeUsersInDebtQty = User::where('active', true)->where('cash_balance', '<', 0)->count();
$activeUsersInDebtSum = User::where('active', true)->where('cash_balance', '<', 0)->sum('cash_balance') / 100;

$inactiveUsersInCreditQty = User::where('active', false)->where('cash_balance', '>', 0)->count();
$inactiveUsersInCreditSum = User::where('active', false)->where('cash_balance', '>', 0)->sum('cash_balance') / 100;
$inactiveUsersInDebtQty = User::where('active', false)->where('cash_balance', '<', 0)->count();
$inactiveUsersInDebtSum = User::where('active', false)->where('cash_balance', '<', 0)->sum('cash_balance') / 100;

$users = User::where('cash_balance', '!=', 0)->get();

return \View::make('payment_balances.index')->with([
'activeUsersInCreditQty' => $activeUsersInCreditQty,
'activeUsersInCreditSum' => $activeUsersInCreditSum,
'activeUsersInDebtQty' => $activeUsersInDebtQty,
'activeUsersInDebtSum' => $activeUsersInDebtSum,
'inactiveUsersInCreditQty' => $inactiveUsersInCreditQty,
'inactiveUsersInCreditSum' => $inactiveUsersInCreditSum,
'inactiveUsersInDebtQty' => $inactiveUsersInDebtQty,
'inactiveUsersInDebtSum' => $inactiveUsersInDebtSum,
'users' => $users,
]);
}
}
34 changes: 34 additions & 0 deletions app/Jobs/RecalculateBalance.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace BB\Jobs;

use BB\Entities\User;
use BB\Services\Credit;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;

class RecalculateBalance implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

public $user;

public function __construct(User $user)
{
$this->user = $user;
}

public function handle(Credit $bbCredit)
{
$oldBalance = $this->user->cash_balance;

$bbCredit->setUserId($this->user->id);
$bbCredit->recalculate();

$newBlalance = $this->user->fresh()->cash_balance;
\Log::debug("Recalculated balance for user {$this->user->id}. Balance changed from {$oldBalance} to {$newBlalance}");
}
}
60 changes: 60 additions & 0 deletions database/factories/PaymentFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use BB\Entities\Payment;
use Faker\Generator as Faker;

$factory->define(Payment::class, function (Faker $faker) {
return [
'source' => $faker->randomElement([
'gocardless',
'gocardless-variable',
'snackspace',
'balance',
'cash',
'gift',
]),
'source_id' => $faker->randomNumber(),
'user_id' => $faker->randomNumber(),
'amount' => $faker->randomFloat(2, 1, 1000),
'fee' => $faker->randomFloat(2, 0, 100),
'amount_minus_fee' => function (array $payment) {
return $payment['amount'] - $payment['fee'];
},
'status' => $faker->randomElement([
Payment::STATUS_PENDING,
Payment::STATUS_PENDING_SUBMISSION,
Payment::STATUS_CANCELLED,
Payment::STATUS_PAID,
Payment::STATUS_WITHDRAWN
]),
'reason' => $faker->randomElement(array_keys(Payment::getPaymentReasons())),
'created_at' => $faker->dateTime,
'reference' => $faker->uuid,
'paid_at' => $faker->dateTime,
];
});

$factory->state(Payment::class, 'pending', [
'status' => Payment::STATUS_PENDING,
]);

$factory->state(Payment::class, 'pending_submission', [
'status' => Payment::STATUS_PENDING_SUBMISSION,
]);

$factory->state(Payment::class, 'cancelled', [
'status' => Payment::STATUS_CANCELLED,
]);

$factory->state(Payment::class, 'paid', [
'status' => Payment::STATUS_PAID,
]);
$factory->state(Payment::class, 'fromCash', [
'source' => 'cash',
]);

$factory->state(Payment::class, 'fromBalance', [
'source' => 'balance',
]);
77 changes: 77 additions & 0 deletions resources/views/payment_balances/index.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
@extends('layouts.main')

@section('meta-title')
Balances overview
@stop

@section('page-title')
Balances overview
@stop

@section('main-tab-bar')
@include('payments.partials.tabs')
@stop

@section('content')
<table class="table memberList">
<thead>
<tr>
<th>&nbsp;</th>
<th colspan="2">In credit</th>
<th colspan="2">In debt</th>
</tr>
<tr>
<th>&nbsp;</th>
<th>No. users</th>
<th>Sum of balances</th>
<th>No. users</th>
<th>Sum of balances</th>
</tr>
</thead>
<tbody>
<tr>
<th>Active members</th>
<td>{{ $activeUsersInCreditQty }}</td>
<td>&pound;{{ number_format($activeUsersInCreditSum, 2) }}</td>
<td>{{ $activeUsersInDebtQty }}</td>
<td>&pound;{{ number_format($activeUsersInDebtSum, 2) }}</td>
</tr>
<tr>
<th>Left members</th>
<td>{{ $inactiveUsersInCreditQty }}</td>
<td>&pound;{{ number_format($inactiveUsersInCreditSum, 2) }}</td>
<td>{{ $inactiveUsersInDebtQty }}</td>
<td>&pound;{{ number_format($inactiveUsersInDebtSum, 2) }}</td>
</tr>
</tbody>
</table>

<table class="table memberList">
<thead>
<tr>
<th>Member name</th>
<th>Member Status</th>
<th>Balance status (in debt or in credit)</th>
<th>Balance amount</th>
</tr>
</thead>
<tbody>
@foreach ($users as $user)
<tr>
<td>
<a href="{{ route('account.show', $user->id) }}">{{ $user->name }}</a>
</td>
<td>{!! HTML::statusLabel($user->status) !!}</td>
<td>
@if ($user->cash_balance < 0)
<span class="label label-danger">In debt</span>
@else
<span class="label label-info">In credit</span>
@endif
</td>
<td>{{ $user->present()->cashBalance }}</td>
</tr>
@endforeach
</tbody>
</table>
@stop
3 changes: 3 additions & 0 deletions resources/views/payments/partials/tabs.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@
<li class="{{ request()->routeIs('payments.possible-duplicates') ? 'active' : '' }}">
{!! link_to_route('payments.possible-duplicates', 'Possible Duplicates') !!}
</li>
<li class="{{ request()->routeIs('payments.balances') ? 'active' : '' }}">
{!! link_to_route('payments.balances', 'Balances') !!}
</li>
</ul>
</nav>
1 change: 1 addition & 0 deletions routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
Route::group(array('middleware' => 'role:finance'), function () {
Route::resource('payments', 'PaymentController', ['only' => ['index', 'destroy', 'update']]);
Route::get('payments/overview', ['uses' => 'PaymentOverviewController@index', 'as' => 'payments.overview']);
Route::get('payments/balances', ['uses' => 'PaymentBalancesController@index', 'as' => 'payments.balances']);
Route::get('payments/sub-charges', ['as' => 'payments.sub-charges', 'uses' => 'SubscriptionController@listCharges']);
Route::get('payments/possible-duplicates', ['as' => 'payments.possible-duplicates', 'uses' => 'PaymentController@possibleDuplicates']);
});
Expand Down
Loading
Loading