Initial commit
This commit is contained in:
243
app/Traits/AccountInfoTrait.php
Normal file
243
app/Traits/AccountInfoTrait.php
Normal file
@@ -0,0 +1,243 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use App\Models\Transaction;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
trait AccountInfoTrait
|
||||
{
|
||||
use ProfilePermissionTrait;
|
||||
/**
|
||||
* Get the balance of an account.
|
||||
*
|
||||
* @param int $accountId The ID of the account.
|
||||
* @return float The balance of the account in minutes.
|
||||
*/
|
||||
public function getBalance($accountId)
|
||||
{
|
||||
// TODO: Store balance in extra column of transactions table with model events for create/update/delete
|
||||
// a new getBalanceFast() method could read this column for fast access of balance.
|
||||
// This new method would then be used for non-critical balance info.
|
||||
$cacheKey = "account_balance_{$accountId}";
|
||||
return Cache::remember($cacheKey, 60, function () use ($accountId) {
|
||||
$balance = Transaction::where('from_account_id', $accountId)
|
||||
->orWhere('to_account_id', $accountId)
|
||||
// selectRaw query, secured with input sanitization ans parameter binding [$accountId}]
|
||||
->selectRaw('SUM(CASE WHEN to_account_id = ? THEN amount ELSE -amount END) as balance', [$accountId])
|
||||
->value('balance');
|
||||
|
||||
return $balance ?? 0;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get accounts associated with a profile.
|
||||
* If no profileType and profileId is specified, the active profile is used.
|
||||
* Returns an array with account id, name, and balance (in minutes).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getAccountsInfo($profileType = null, $profileId = null)
|
||||
{
|
||||
if ($profileType === null) {
|
||||
$profileType = session('activeProfileType');
|
||||
}
|
||||
if ($profileId === null) {
|
||||
$profileId = session('activeProfileId');
|
||||
}
|
||||
|
||||
$cacheKey = "accounts_info_{$profileType}_{$profileId}";
|
||||
return Cache::remember($cacheKey, 60, function () use ($profileType, $profileId) {
|
||||
// Get the profile
|
||||
$profile = $profileType::find($profileId);
|
||||
|
||||
if (!$profile) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
// Check if profile has accounts relation (e.g., Admin profiles don't)
|
||||
if (!method_exists($profile, 'accounts')) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
// Get the profile and its accounts in a single query
|
||||
$profile = $profileType::with(['accounts'])->find($profileId);
|
||||
|
||||
if (!$profile) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
// Return early if no accounts exist
|
||||
if ($profile->accounts->isEmpty()) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
// Calculate the total balance of all accounts of the profile in a single query
|
||||
$accountIds = $profile->accounts->pluck('id')->toArray();
|
||||
// Convert array to comma-separated string for selectRaw sanitization and parameter binding
|
||||
$accountIdsString = implode(',', $accountIds);
|
||||
$sumAccounts = DB::table('transactions')
|
||||
->whereIn('from_account_id', $accountIds)
|
||||
->orWhereIn('to_account_id', $accountIds)
|
||||
->selectRaw("SUM(CASE WHEN to_account_id IN ($accountIdsString) THEN amount ELSE -amount END) as balance")
|
||||
->value('balance');
|
||||
|
||||
$limitReceivable = $profile->limit_max - ($sumAccounts ?? 0) - $profile->limit_min;
|
||||
// If the limitReceivable is negative, set it to 0
|
||||
if ($limitReceivable < 0) {
|
||||
$limitReceivable = 0;
|
||||
}
|
||||
|
||||
// Map the collection to include the total balance
|
||||
$accounts = $profile->accounts->map(function ($account) use ($profileType, $limitReceivable) {
|
||||
return [
|
||||
'id' => $account->id,
|
||||
'name' => __('messages.' . $account->name . '_account'),
|
||||
'type' => strtolower(class_basename($profileType)),
|
||||
'balance' => $this->getBalance($account->id), // Use getBalance function
|
||||
'balanceTbFormat' => tbFormat($this->getBalance($account->id)),
|
||||
'limitMin' => $account->limit_min,
|
||||
'limitMax' => $account->limit_max,
|
||||
'limitReceivable' => $limitReceivable,
|
||||
'inactive' => $account->inactive_at ? \Illuminate\Support\Carbon::parse($account->inactive_at)->isPast() : false,
|
||||
'inactiveAt' => $account->inactive_at,
|
||||
'removed' => $account->deleted_at ? \Illuminate\Support\Carbon::parse($account->deleted_at)->isPast() : false,
|
||||
'deletedAt' => $account->deleted_at,
|
||||
];
|
||||
});
|
||||
|
||||
return $accounts;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the account totals of a profile.
|
||||
* If no profileType and profileId is specified, the active profile is used.
|
||||
*
|
||||
* @param string|null $profileType The profile type. If null, the active profile type from the session will be used.
|
||||
* @param int|null $profileId The profile ID. If null, the active profile ID from the session will be used.
|
||||
* @param int|null $sinceDaysAgo The number of days to filter the counted transfers. If null, all transfers will be counted.
|
||||
* @return array Sum of all balances (in minutes), count of transfers, count of transfers received, count of transfers sent.
|
||||
*/
|
||||
public function getAccountsTotals($profileType = null, $profileId = null, $sinceDaysAgo = null)
|
||||
{
|
||||
if ($profileType === null) {
|
||||
$profileType = session('activeProfileType');
|
||||
}
|
||||
if ($profileId === null) {
|
||||
$profileId = session('activeProfileId');
|
||||
}
|
||||
|
||||
// Get the profile instance
|
||||
$profile = $profileType::find($profileId);
|
||||
$type = strtolower(class_basename($profile));
|
||||
|
||||
// Robust check: profile exists and has accounts relation/method
|
||||
if (
|
||||
!$profile ||
|
||||
!(method_exists($profile, 'accounts') || property_exists($profile, 'accounts'))
|
||||
) {
|
||||
return [
|
||||
'sumBalances' => 0,
|
||||
'countTransfersSince' => $sinceDaysAgo !== null ? now()->subDays($sinceDaysAgo) : null,
|
||||
'transfers' => 0,
|
||||
'transfersReceived' => 0,
|
||||
'transfersGiven' => 0,
|
||||
'lastTransferDate' => null,
|
||||
];
|
||||
}
|
||||
|
||||
// Get all accounts for the profile
|
||||
$accounts = $profile->accounts ?? collect();
|
||||
if ($accounts->isEmpty()) {
|
||||
return [
|
||||
'sumBalances' => 0,
|
||||
'countTransfersSince' => $sinceDaysAgo !== null ? now()->subDays($sinceDaysAgo) : null,
|
||||
'transfers' => 0,
|
||||
'transfersReceived' => 0,
|
||||
'transfersGiven' => 0,
|
||||
'lastTransferDate' => null,
|
||||
];
|
||||
}
|
||||
$accountIds = $accounts->pluck('id')->toArray();
|
||||
|
||||
// 1. Calculate sum of balances by iterating through the profile's accounts
|
||||
$sumBalances = 0;
|
||||
foreach ($accounts as $account) {
|
||||
$sumBalances += $this->getBalance($account->id);
|
||||
}
|
||||
|
||||
// 2. Get all relevant transactions in a single query
|
||||
$transfersQuery = Transaction::where(function ($query) use ($accountIds) {
|
||||
$query->whereIn('from_account_id', $accountIds)
|
||||
->orWhereIn('to_account_id', $accountIds);
|
||||
});
|
||||
|
||||
if ($sinceDaysAgo !== null) {
|
||||
$transfersQuery->whereDate('created_at', '>=', now()->subDays($sinceDaysAgo));
|
||||
}
|
||||
|
||||
// Get the date of the most recent transfer before fetching the full collection
|
||||
$lastTransferDate = (clone $transfersQuery)->latest('created_at')->value('created_at');
|
||||
|
||||
$transfers = $transfersQuery->get();
|
||||
|
||||
// 3. Initialize counters
|
||||
$countTransfers = 0;
|
||||
$countTransfersReceived = 0;
|
||||
$countTransfersGiven = 0;
|
||||
|
||||
// 4. Process all transactions in a single loop
|
||||
foreach ($transfers as $transfer) {
|
||||
// Check if the transaction is internal (both from and to accounts belong to the profile)
|
||||
$isInternal = in_array($transfer->from_account_id, $accountIds) && in_array($transfer->to_account_id, $accountIds);
|
||||
|
||||
// Only count non-internal transfers
|
||||
if (!$isInternal) {
|
||||
$countTransfers++;
|
||||
if (in_array($transfer->to_account_id, $accountIds)) {
|
||||
$countTransfersReceived++;
|
||||
}
|
||||
if (in_array($transfer->from_account_id, $accountIds)) {
|
||||
$countTransfersGiven++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Assemble the result with privacy settings from config
|
||||
// Check if viewing own profile
|
||||
$activeProfileType = session('activeProfileType');
|
||||
$activeProfileId = session('activeProfileId');
|
||||
$isViewingOwnProfile = ($activeProfileType === $profileType && $activeProfileId === $profileId);
|
||||
|
||||
if ($this->getCanManageAccounts() || $isViewingOwnProfile) {
|
||||
// Show all account info if user can manage accounts OR is viewing their own profile
|
||||
$totals = [
|
||||
'sumBalances' => $sumBalances,
|
||||
'countTransfersSince' => $sinceDaysAgo !== null ? now()->subDays($sinceDaysAgo) : null,
|
||||
'transfers' => $countTransfers,
|
||||
'transfersReceived' => $countTransfersReceived,
|
||||
'transfersGiven' => $countTransfersGiven,
|
||||
'transfersReceivedOrGiven' => $countTransfersReceived + $countTransfersGiven,
|
||||
'lastTransferDate' => $lastTransferDate,
|
||||
];
|
||||
} else {
|
||||
// Apply privacy settings from config for other viewers
|
||||
$totals = [
|
||||
'sumBalances' => timebank_config('account_info.'.$type.'.sumBalances_public', false) === true ? $sumBalances : null,
|
||||
'countTransfersSince' => $sinceDaysAgo !== null ? now()->subDays($sinceDaysAgo) : null,
|
||||
'transfers' => timebank_config('account_info.' . $type . '.countTransfers_public', false) === true ? $countTransfers : null,
|
||||
'transfersReceived' => timebank_config('account_info.' . $type . '.countTransfersReceived_public', false) === true ? $countTransfersReceived : null,
|
||||
'transfersGiven' => timebank_config('account_info.' . $type . '.countTransfersGiven_public', false) === true ? $countTransfersGiven : null,
|
||||
'transfersReceivedOrGiven' => timebank_config('account_info.' . $type . '.countTransfersReceivedOrGiven_public', false) === true ? $countTransfersReceived + $countTransfersGiven : null,
|
||||
'lastTransferDate' => timebank_config('account_info.' . $type . '.lastTransferDate_public', false) === true ? $lastTransferDate : null,
|
||||
];
|
||||
}
|
||||
|
||||
return $totals;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user