Skip to content

Commit

Permalink
✨ Add ranges to reports, add new report type
Browse files Browse the repository at this point in the history
  • Loading branch information
saleem-hadad committed Jan 15, 2022
1 parent f4b916d commit 331819f
Show file tree
Hide file tree
Showing 23 changed files with 14,051 additions and 130 deletions.
1 change: 0 additions & 1 deletion app/Domain/Ranges/Range.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

abstract class Range implements JsonSerializable
{
protected $key;
protected $name;
protected $start;
protected $end;
Expand Down
32 changes: 32 additions & 0 deletions app/GraphQL/Queries/ExpensesPerCategory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace App\GraphQL\Queries;

use App\Models\Category;
use Illuminate\Support\Facades\DB;

class ExpensesPerCategory
{
/**
* @param null $_
* @param array<string, mixed> $args
*/
public function __invoke($_, array $args)
{
$rangeData = app('findRangeByKey', ["key" => $args['range']]);

$query = Category::query()
->where('type', Category::EXPENSES)
->join('brands', 'brands.category_id', '=', 'categories.id')
->join('transactions', 'transactions.brand_id', '=', 'brands.id')
->select("categories.name as label", DB::raw("SUM(transactions.amount) as value"))
->groupBy("categories.id")
->orderBy('value', 'DESC');

if($rangeData) {
$query->whereBetween('transactions.created_at', [$rangeData->start(), $rangeData->end()]);
}

return $query->get();
}
}
32 changes: 32 additions & 0 deletions app/GraphQL/Queries/IncomePerCategory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace App\GraphQL\Queries;

use App\Models\Category;
use Illuminate\Support\Facades\DB;

class IncomePerCategory
{
/**
* @param null $_
* @param array<string, mixed> $args
*/
public function __invoke($_, array $args)
{
$rangeData = app('findRangeByKey', ["key" => $args['range']]);

$query = Category::query()
->where('type', Category::INCOME)
->join('brands', 'brands.category_id', '=', 'categories.id')
->join('transactions', 'transactions.brand_id', '=', 'brands.id')
->select("categories.name as label", DB::raw("SUM(transactions.amount) as value"))
->groupBy("categories.id")
->orderBy('value', 'DESC');

if($rangeData) {
$query->whereBetween('transactions.created_at', [$rangeData->start(), $rangeData->end()]);
}

return $query->get();
}
}
25 changes: 25 additions & 0 deletions app/GraphQL/Queries/TotalExpenses.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace App\GraphQL\Queries;

use App\Models\Transaction;

class TotalExpenses
{
/**
* @param null $_
* @param array<string, mixed> $args
*/
public function __invoke($_, array $args)
{
$rangeData = app('findRangeByKey', ["key" => $args['range']]);

$query = Transaction::query()->expenses();

if($rangeData) {
$query->whereBetween('created_at', [$rangeData->start(), $rangeData->end()]);
}

return $query->sum('amount');
}
}
25 changes: 25 additions & 0 deletions app/GraphQL/Queries/TotalIncome.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace App\GraphQL\Queries;

use App\Models\Transaction;

class TotalIncome
{
/**
* @param null $_
* @param array<string, mixed> $args
*/
public function __invoke($_, array $args)
{
$rangeData = app('findRangeByKey', ["key" => $args['range']]);

$query = Transaction::query()->income();

if($rangeData) {
$query->whereBetween('created_at', [$rangeData->start(), $rangeData->end()]);
}

return $query->sum('amount');
}
}
9 changes: 1 addition & 8 deletions app/Http/Controllers/DashboardController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,8 @@ class DashboardController extends Controller
{
public function index()
{
$metrics = config('finance.reports');

$graphqlQueries = array_map(function($metric) {
return $metric['graphql_query'];
}, $metrics);

return Inertia::render('Dashboard', [
'metrics' => $metrics,
'graphqlQueries' => implode("\n", $graphqlQueries)
'metrics' => config('finance.reports')
]);
}
}
12 changes: 0 additions & 12 deletions app/Models/Transaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace App\Models;

use Illuminate\Support\Facades\DB;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;

Expand Down Expand Up @@ -35,17 +34,6 @@ public function scopeIncome($query)
});
}

public function statistics2()
{
return Transaction::query()
->income()
->with(['brand', 'brand.category'])
->join('brands', 'brands.id', '=', 'transactions.brand_id')
// ->join('categories', 'categories.id', '=', 'brands.category_id')
->select(DB::raw("brands.category_id, SUM(transactions.amount) as total"))
->groupBy("brands.category_id");
}

public static function tryCreateFromSms($sms)
{
$brandFromSms = $sms->meta['data']['brand'] ?? null;
Expand Down
19 changes: 19 additions & 0 deletions app/Providers/AppServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
use App\Contracts\SmsParser as SmsParserContract;
use App\Contracts\SmsTemplateDetector as SmsTemplateDetectorContract;
use App\Contracts\SmsTransactionProcessor as SmsTransactionProcessorContract;
use App\Domain\Ranges\CurrentMonth;
use App\Domain\Ranges\CurrentYear;
use App\Domain\Ranges\LastMonth;
use App\Domain\Ranges\LastYear;

class AppServiceProvider extends ServiceProvider
{
Expand All @@ -22,6 +26,21 @@ public function register()
$this->app->bind(SmsParserContract::class, SmsParser::class);
$this->app->bind(SmsTemplateDetectorContract::class, SmsTemplateDetector::class);
$this->app->bind(SmsTransactionProcessorContract::class, SmsTransactionProcessor::class);

// TODO: find a better way to load classes based on base class Range
$this->app->bind('findRangeByKey', function($_, $params) {
$key = $params['key'];

return collect([
new CurrentMonth,
new LastMonth,
new CurrentYear,
new LastYear,
])->first(function ($range) use($key) {
return $key === $range->key();
}) ?: null;

});
}

/**
Expand Down
28 changes: 22 additions & 6 deletions config/finance.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
<?php

use \App\Domain\Ranges\CurrentMonth;
use \App\Domain\Ranges\LastMonth;
use \App\Domain\Ranges\CurrentYear;
use \App\Domain\Ranges\LastYear;

$ranges = [
new CurrentMonth,
new LastMonth,
new CurrentYear,
new LastYear,
];

return [
'sms_templates' => [
'Purchase of AED {amount} with {card} at {brand},',
Expand All @@ -13,24 +25,28 @@
'graphql_query' => 'totalIncome',
'component' => 'value-metric',
'width' => '1/2',
'ranges' => $ranges,
],
[
'name' => 'Total Expenses',
'graphql_query' => 'totalExpenses',
'component' => 'value-metric',
'width' => '1/2',
'ranges' => $ranges,
],
[
'name' => 'Expenses per Category',
'graphql_query' => 'expensesPerCategory',
'component' => 'partition-metric',
'width' => '1/2',
'ranges' => [
\App\Domain\Ranges\CurrentMonth::class,
\App\Domain\Ranges\LastMonth::class,
\App\Domain\Ranges\CurrentYear::class,
\App\Domain\Ranges\LastYear::class,
],
'ranges' => $ranges,
],
[
'name' => 'Income per Category',
'graphql_query' => 'incomePerCategory',
'component' => 'partition-metric',
'width' => '1/2',
'ranges' => $ranges,
],

// Brand per category partition for this month
Expand Down
15 changes: 5 additions & 10 deletions graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,6 @@ type Transaction {
created_at: Date!
}

type TransactionStatistics {
category_id: Int!
total: Float!
category: Category
}

type Sms {
id: ID!
body: String!
Expand All @@ -42,16 +36,17 @@ type Query {
transactions: [Transaction!]! @paginate(defaultCount: 25) @orderBy(column: id direction: DESC)

allBrands: [Brand!]! @all
brands: [Brand!]! @paginate(defaultCount: 25) @orderBy(column: id direction: DESC)
brands: [Brand!]! @paginate(defaultCount: 25) @orderBy(column: category_id direction: ASC)

allCategories: [Category!]! @all
categories: [Category!]! @paginate(defaultCount: 25) @orderBy(column: id direction: DESC)

sms: [Sms!]! @paginate(defaultCount: 100) @orderBy(column: transaction_id direction: ASC)

totalExpenses: Float! @aggregate(model: Transaction column: amount function: SUM scopes:["expenses"])
totalIncome: Float! @aggregate(model: Transaction column: amount function: SUM scopes: ["income"])
expensesPerCategory: Float! @aggregate(model: Transaction column: amount function: SUM scopes: ["income"])
totalExpenses(range: String): Json
totalIncome(range: String): Json
expensesPerCategory(range: String): Json
incomePerCategory(range: String): Json
}

type Mutation {
Expand Down
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
},
"dependencies": {
"@heroicons/react": "^1.0.5",
"chart.js": "^3.7.0",
"graphql": "^16.2.0",
"numbro": "^2.3.6"
"numbro": "^2.3.6",
"react-chartjs-2": "^4.0.0"
}
}
Loading

0 comments on commit 331819f

Please sign in to comment.