Level 2
Category model:
class Category extends Model
{
use HasFactory;
use HasTranslations;
use SoftDeletes;
/**
* @var array
*/
protected $fillable = [
'icon',
'name',
'visible',
'color',
'parent_id',
'user_id',
];
/**
* @var array
*/
public $translatable = [
'name',
];
/**
* @var array
*/
protected $appends = [
'money_spent',
'total_expenses',
'money_earned',
'total_income',
];
/**
* @return float
*/
public function getMoneySpentAttribute(): float
{
return $this->getExpenseSumWithinRange(request('startDate'), request('endDate'), request('userId'));
}
/**
* @return float
*/
public function getTotalExpensesAttribute(): float
{
return $this->getTotalExpenseSumWithinRange(request('startDate'), request('endDate'), request('userId'));
}
/**
* @return float
*/
public function getMoneyEarnedAttribute(): float
{
return $this->getIncomeSumWithinRange(request('startDate'), request('endDate'), request('userId'));
}
/**
* @return float
*/
public function getTotalIncomeAttribute(): float
{
return $this->getTotalIncomeSumWithinRange(request('startDate'), request('endDate'), request('userId'));
}
/**
* @param string|null $startDate
* @param string|null $endDate
* @param int|null $userId
*
* @return float
*/
public function getExpenseSumWithinRange(string $startDate = null, string $endDate = null, int $userId = null): float
{
return $this->records()
->where('type', Record::TYPE_EXPENSE)
->when($userId, function ($query) use ($userId) {
$query->where('user_id', $userId);
})
->when($startDate && $endDate, function ($query) use ($startDate, $endDate) {
$query->whereBetween('date', [$startDate, $endDate]);
})
->sum('amount');
}
/**
* @param string|null $startDate
* @param string|null $endDate
* @param int|null $userId
*
* @return float
*/
public function getTotalExpenseSumWithinRange(string $startDate = null, string $endDate = null, int $userId = null): float
{
$totalExpenses = $this->allExpenseRecords()
->when($userId, function ($query) use ($userId) {
$query->where('records.user_id', $userId);
})
->when($startDate && $endDate, function ($query) use ($startDate, $endDate) {
$query->whereBetween('records.date', [$startDate, $endDate]);
})
->whereHas('account', function ($query) {
$query->where('exclude_from_statistics', false);
})
->sum('amount');
foreach ($this->descendants as $child) {
$totalExpenses += $child->allExpenseRecords()
->when($userId, function ($query) use ($userId) {
$query->where('records.user_id', $userId);
})
->when($startDate && $endDate, function ($query) use ($startDate, $endDate) {
$query->whereBetween('records.date', [$startDate, $endDate]);
})
->whereHas('account', function ($query) {
$query->where('exclude_from_statistics', false);
})
->sum('amount');
}
return $totalExpenses;
}
/**
* @param string|null $startDate
* @param string|null $endDate
* @param int|null $userId
*
* @return float
*/
public function getIncomeSumWithinRange(string $startDate = null, string $endDate = null, int $userId = null): float
{
return $this->records()
->where('type', Record::TYPE_INCOME)
->when($userId, function ($query) use ($userId) {
$query->where('user_id', $userId);
})
->when($startDate && $endDate, function ($query) use ($startDate, $endDate) {
$query->whereBetween('date', [$startDate, $endDate]);
})
->sum('amount');
}
/**
* @param string|null $startDate
* @param string|null $endDate
* @param int|null $userId
*
* @return float
*/
public function getTotalIncomeSumWithinRange(string $startDate = null, string $endDate = null, int $userId = null): float
{
$totalIncome = $this->allIncomeRecords()
->when($userId, function ($query) use ($userId) {
$query->where('records.user_id', $userId);
})
->when($startDate && $endDate, function ($query) use ($startDate, $endDate) {
$query->whereBetween('records.date', [$startDate, $endDate]);
})
->whereHas('account', function ($query) {
$query->where('exclude_from_statistics', false);
})
->sum('amount');
foreach ($this->descendants as $child) {
$totalIncome += $child->allIncomeRecords()
->when($userId, function ($query) use ($userId) {
$query->where('records.user_id', $userId);
})
->when($startDate && $endDate, function ($query) use ($startDate, $endDate) {
$query->whereBetween('records.date', [$startDate, $endDate]);
})
->whereHas('account', function ($query) {
$query->where('exclude_from_statistics', false);
})
->sum('amount');
}
return $totalIncome;
}
/**
* @param string|null $startDate
* @param string|null $endDate
* @param int|null $userId
*
* @return array
*/
public static function calculateTotalsForDateRange(string $startDate = null, string $endDate = null, int $userId = null): array
{
$categories = self::with('descendants')->get();
$totalExpenses = 0;
$totalIncome = 0;
foreach ($categories as $category) {
$totalExpenses += $category->getTotalExpenseSumWithinRange($startDate, $endDate, $userId);
$totalIncome += $category->getTotalIncomeSumWithinRange($startDate, $endDate, $userId);
}
return [
'total_expenses' => $totalExpenses,
'total_income' => $totalIncome,
];
}
public function allExpenseRecords()
{
return $this->hasManyThrough(Record::class, Category::class, 'parent_id', 'category_id')
->where('records.type', Record::TYPE_EXPENSE);
}
public function allIncomeRecords()
{
return $this->hasManyThrough(Record::class, Category::class, 'parent_id', 'category_id')
->where('records.type', Record::TYPE_INCOME);
}
public function descendants()
{
return $this->hasMany(Category::class, 'parent_id', 'id')->with('descendants');
}
/**
* @return BelongsTo
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* @return HasMany
*/
public function children(): HasMany
{
return $this->hasMany(Category::class, 'parent_id', 'id');
}
/**
* @return BelongsTo
*/
public function parent(): BelongsTo
{
return $this->belongsTo(Category::class, 'parent_id', 'id');
}
/**
* @return HasMany
*/
public function records(): HasMany
{
return $this->hasMany(Record::class);
}
}